diff --git a/web/stovepipe/README.md b/web/stovepipe/README.md index 04a61752b..9bb53bd18 100644 --- a/web/stovepipe/README.md +++ b/web/stovepipe/README.md @@ -24,7 +24,7 @@ projects; `?projects=` carries its continuation and can be shared. First-page navigation removes the cursor. Pages are live rather than a snapshot; return to the first page to see newly recorded projects. -The repository result appears in the request summary above history; the project results table contains only projects. Recorded results show Green or Breakage without percentages. +The repository result appears in the request summary above history; the project results table contains only projects. Recorded results show Green at zero, Partial Breakage between zero and one, and Breakage at one, without percentages. Repository breakage, per-project breakage, and recording completion are separate. Zero breakage is green; absent results are never treated as green. Completion diff --git a/web/stovepipe/view/app.test.tsx b/web/stovepipe/view/app.test.tsx index bae9e3c9e..26198f8ee 100644 --- a/web/stovepipe/view/app.test.tsx +++ b/web/stovepipe/view/app.test.tsx @@ -21,7 +21,8 @@ const model: StovepipePageModel = { complete: false, projects: [ { project: "green-project", breakageDegree: 0 }, - { project: "broken-project", breakageDegree: 0.25 }, + { project: "partial-breakage-project", breakageDegree: 0.25 }, + { project: "broken-project", breakageDegree: 1 }, { project: "unknown-project", breakageDegree: null }, ], firstHref: null, @@ -133,6 +134,7 @@ it("renders recorded project results, completion and shareable pagination", () = expect(within(table).queryByText(/Repository/)).toBeNull(); expect(within(table).getByRole("columnheader", { name: "Project" })).toBeVisible(); expect(within(projects).getByText("Green")).toBeVisible(); + expect(within(table).getByRole("row", { name: "partial-breakage-project Partial Breakage" })).toBeVisible(); expect(within(projects).getByText("Breakage")).toBeVisible(); expect(within(projects).getByText("No result recorded")).toBeVisible(); expect(within(projects).queryByText(/%/)).toBeNull(); @@ -175,7 +177,8 @@ it("shows complete empty results without inventing green or a continuation", () it.each([ [0, "Green"], - [0.25, "Breakage"], + [0.25, "Partial Breakage"], + [1, "Breakage"], [null, "No result recorded"], ] as const)("shows repository result %s above history as %s", (degree, label) => { if (!model.projectStatus.ok) throw new Error("No projects"); diff --git a/web/stovepipe/view/projects.tsx b/web/stovepipe/view/projects.tsx index fca867e21..a9674ba2a 100644 --- a/web/stovepipe/view/projects.tsx +++ b/web/stovepipe/view/projects.tsx @@ -13,7 +13,7 @@ export function RecordedResult({ degree }: { degree: number | null }) { - {degree === null ? "No result recorded" : degree === 0 ? "Green" : "Breakage"} + {degree === null ? "No result recorded" : degree === 0 ? "Green" : degree === 1 ? "Breakage" : "Partial Breakage"} ); } diff --git a/web/test/e2e/stovepipe/stovepipe.spec.ts b/web/test/e2e/stovepipe/stovepipe.spec.ts index 10fcb206e..b4bf082b0 100644 --- a/web/test/e2e/stovepipe/stovepipe.spec.ts +++ b/web/test/e2e/stovepipe/stovepipe.spec.ts @@ -25,7 +25,7 @@ test.describe("server-rendered styling", () => { const rows = status.locator("dl > div"); await expect(rows).toHaveCount(7); await expect(status.getByText("Repository result", { exact: true })).toBeVisible(); - await expect(status.getByText("Breakage", { exact: true })).toBeVisible(); + await expect(status.getByText("Partial Breakage", { exact: true })).toBeVisible(); const layout = await rows.evaluateAll(fields => fields.map(field => ({ height: field.getBoundingClientRect().height, labelGap: field.querySelector("dd")!.getBoundingClientRect().top - field.querySelector("dt")!.getBoundingClientRect().bottom, @@ -337,11 +337,16 @@ test("real ingestion, queue selection, cursors, request summaries, history, proj await expect(projects.locator("tbody tr").nth(1).getByRole("cell", { name: "project-01", exact: true })).toBeVisible(); await expect(projects.locator("tbody tr").nth(2).getByRole("cell", { name: "project-02", exact: true })).toBeVisible(); await expect(status.getByText("Repository result", { exact: true })).toBeVisible(); - await expect(status.getByText("Breakage", { exact: true })).toBeVisible(); + await expect(status.getByText("Partial Breakage", { exact: true })).toBeVisible(); await expect(projects.getByText(/Repository|%/)).toHaveCount(0); await expect( projects.getByRole("row").filter({ has: page.getByRole("cell", { name: "project-01", exact: true }), + }).getByRole("cell", { name: "Partial Breakage", exact: true }), + ).toBeVisible(); + await expect( + projects.getByRole("row").filter({ + has: page.getByRole("cell", { name: "project-50", exact: true }), }).getByRole("cell", { name: "Breakage", exact: true }), ).toBeVisible(); await expect( @@ -358,7 +363,7 @@ test("real ingestion, queue selection, cursors, request summaries, history, proj expect(cursor).toBeTruthy(); await page.reload(); await expect(projects.locator("tbody tr")).toHaveCount(1); - await expect(status.getByText("Breakage", { exact: true })).toBeVisible(); + await expect(status.getByText("Partial Breakage", { exact: true })).toBeVisible(); expect(new URL(page.url()).searchParams.get("projects")).toBe(cursor); await expect(section.getByRole("link", { name: "Next projects →", exact: true })).toHaveCount(0); await section.getByRole("link", { name: "First projects", exact: true }).click(); diff --git a/web/test/package/react18/react18-runtime.test.mjs b/web/test/package/react18/react18-runtime.test.mjs index 17c0bb3cd..302b4ace4 100644 --- a/web/test/package/react18/react18-runtime.test.mjs +++ b/web/test/package/react18/react18-runtime.test.mjs @@ -114,7 +114,7 @@ test("renders and hydrates packaged pages with React 18 and host themes", async expected.remove(); if (route === "/repo%2Fmain/request/1") { assert.match(container.textContent, /example-project/u); - assert.match(container.textContent, /Repository resultBreakage/u); + assert.match(container.textContent, /Repository resultPartial Breakage/u); assert.match(container.textContent, /example-projectGreen/u); assert.doesNotMatch(container.textContent, /%/u); }