From 2d0126d6dd3b3727e8a99549a5e0d3678039960c Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sun, 16 Aug 2026 20:02:31 +0800 Subject: [PATCH 1/2] test(ui): add low-confidence AccessibleTable mockup journey evidence coverage --- docs/design-system/adoption-manifest.json | 1 + docs/site-map.md | 1 + .../accessible-table-browser-fixture/page.tsx | 27 ++++++++++ tests/ui-tools.spec.ts | 52 +++++++++++++++++++ 4 files changed, 81 insertions(+) create mode 100644 src/app/mockups/accessible-table-browser-fixture/page.tsx diff --git a/docs/design-system/adoption-manifest.json b/docs/design-system/adoption-manifest.json index dc67c932b7..c0dc63b243 100644 --- a/docs/design-system/adoption-manifest.json +++ b/docs/design-system/adoption-manifest.json @@ -112,6 +112,7 @@ "tests/accessible-table.dom.test.tsx", "tests/design-sync-visual-exports.test.ts", "tests/design-system-adoption.test.ts", + "tests/ui-tools.spec.ts", "tests/ward-output.test.ts" ], "baseline": { diff --git a/docs/site-map.md b/docs/site-map.md index d76c558633..c0282f85c6 100644 --- a/docs/site-map.md +++ b/docs/site-map.md @@ -985,6 +985,7 @@ This file is generated by `npm run docs:update` (or `npm run sitemap:update` dir ## Mockup/prototype routes +- `/mockups/accessible-table-browser-fixture` - Route discovered from app directory Source: `src/app/mockups/accessible-table-browser-fixture/page.tsx`. - `/mockups/answer-evidence-popups` - Route discovered from app directory Source: `src/app/mockups/answer-evidence-popups/page.tsx`. - `/mockups/answer-home-proposal` - Route discovered from app directory Source: `src/app/mockups/answer-home-proposal/page.tsx`. - `/mockups/calculators-bedside-sheet` - Route discovered from app directory Source: `src/app/mockups/calculators-bedside-sheet/page.tsx`. diff --git a/src/app/mockups/accessible-table-browser-fixture/page.tsx b/src/app/mockups/accessible-table-browser-fixture/page.tsx new file mode 100644 index 0000000000..cc4e4fd98e --- /dev/null +++ b/src/app/mockups/accessible-table-browser-fixture/page.tsx @@ -0,0 +1,27 @@ +import { AccessibleTable } from "@/components/AccessibleTable"; + +// An interleaved unnamed clinical column is the conservative normalizer's +// `ambiguous_generic_column` case: it preserves the raw grid and marks it +// low-confidence instead of guessing which dose belongs to which action. +const columns = ["ANC level", "", "Action"]; +const rows = [ + ["1.5", "", "Continue clozapine and monitor according to the local protocol"], + ["1.0", "", "Withhold dose and seek specialist advice"], +]; + +export default function AccessibleTableBrowserFixturePage() { + return ( +
+

Accessible table browser fixture

+

+ Low-confidence clinical extraction with an explicitly missing dose value. +

+
+ +
+
+ ); +} diff --git a/tests/ui-tools.spec.ts b/tests/ui-tools.spec.ts index 877af366be..1eeaa29a1c 100644 --- a/tests/ui-tools.spec.ts +++ b/tests/ui-tools.spec.ts @@ -3119,3 +3119,55 @@ test.describe("Responsive layout guards", () => { await expectNoPageHorizontalOverflow(page); }); }); + +test("low-confidence AccessibleTable keeps its full missing-value phrase readable at 320px @mockup", async ({ + page, +}, testInfo) => { + await page.setViewportSize({ width: 320, height: 700 }); + await page.goto("/mockups/accessible-table-browser-fixture", { waitUntil: "domcontentloaded" }); + + const fixture = visibleByTestId(page, "accessible-table-browser-fixture"); + await expect(fixture).toBeVisible({ timeout: 15_000 }); + await expect(fixture.getByTestId("table-low-confidence-note")).toContainText( + "verify values against the source document", + ); + + const table = fixture.getByRole("table", { name: "Clozapine ANC response" }); + await expect(table).toBeVisible(); + const missingValues = table.getByTestId("missing-value"); + await expect(missingValues).toHaveCount(2); + await expect(missingValues.first()).toHaveText("Not recorded"); + + const layout = await missingValues.first().evaluate((value) => { + const wrapper = value.parentElement; + if (!wrapper) throw new Error("Missing-value wrapper was not rendered"); + const valueRect = value.getBoundingClientRect(); + const wrapperRect = wrapper.getBoundingClientRect(); + const style = getComputedStyle(wrapper); + return { + valueLeft: valueRect.left, + valueRight: valueRect.right, + wrapperLeft: wrapperRect.left, + wrapperRight: wrapperRect.right, + wrapperClientWidth: wrapper.clientWidth, + wrapperScrollWidth: wrapper.scrollWidth, + whiteSpace: style.whiteSpace, + overflow: style.overflow, + textOverflow: style.textOverflow, + }; + }); + + expect(layout.whiteSpace).toBe("normal"); + expect(layout.textOverflow).not.toBe("ellipsis"); + expect(layout.wrapperScrollWidth - layout.wrapperClientWidth).toBeLessThanOrEqual(1); + expect(layout.valueLeft).toBeGreaterThanOrEqual(layout.wrapperLeft - 1); + expect(layout.valueRight).toBeLessThanOrEqual(layout.wrapperRight + 1); + await expectNoPageHorizontalOverflow(page); + + const screenshotPath = testInfo.outputPath("low-confidence-accessible-table-320px.png"); + await fixture.screenshot({ path: screenshotPath }); + await testInfo.attach("low-confidence-accessible-table-320px", { + path: screenshotPath, + contentType: "image/png", + }); +}); From b76bc4b426814f6d9a13be72183190acdcc82415 Mon Sep 17 00:00:00 2001 From: BigSimmo <87357024+BigSimmo@users.noreply.github.com> Date: Sun, 16 Aug 2026 21:01:20 +0800 Subject: [PATCH 2/2] Restore current-base files while preserving PR #2006 hunks --- docs/design-system/adoption-manifest.json | 2 - tests/ui-tools.spec.ts | 63 ++++++++++++++--------- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/docs/design-system/adoption-manifest.json b/docs/design-system/adoption-manifest.json index c0dc63b243..1eafffd1c2 100644 --- a/docs/design-system/adoption-manifest.json +++ b/docs/design-system/adoption-manifest.json @@ -1516,7 +1516,6 @@ "src/components/forms/forms-search-results-page.tsx", "src/components/in-page-nav/in-page-nav-header.tsx", "src/components/mode-nav/mode-nav.tsx", - "src/components/services/service-group-nav.tsx", "src/components/tools/tools-search-results-page.tsx", "src/components/ui/confirm-dialog.tsx" ], @@ -1545,7 +1544,6 @@ "src/components/forms/forms-search-results-page.tsx", "src/components/in-page-nav/in-page-nav-header.tsx", "src/components/mode-nav/mode-nav.tsx", - "src/components/services/service-group-nav.tsx", "src/components/tools/tools-search-results-page.tsx" ], "designSync": { diff --git a/tests/ui-tools.spec.ts b/tests/ui-tools.spec.ts index 1eeaa29a1c..46f8d08075 100644 --- a/tests/ui-tools.spec.ts +++ b/tests/ui-tools.spec.ts @@ -1153,13 +1153,18 @@ test.describe("Clinical KB tools directory and legacy launcher", () => { await page.setViewportSize({ width: 390, height: 820 }); await gotoLauncher(page, "/services?q=13YARN&focus=1&run=1"); await expect(page.getByRole("button", { name: "Mode Services" })).toBeVisible(); + await expect(page.getByTestId("service-quick-search-suggestions")).toHaveCount(0); const input = visibleGlobalSearchInput(page).first(); await expect(input).toBeVisible(); const quickFilter = page.getByTestId("service-filter-trigger-phone"); await expect(quickFilter).toBeVisible(); await expect(quickFilter).toHaveAccessibleName(/No filters active/); - await page.getByTestId("service-quick-search-suggestions").getByRole("button", { name: "Crisis" }).click(); - await expect(page).toHaveURL(/\/services\?.*q=crisis/); + await expect(page.getByRole("navigation", { name: "Service groups" })).toHaveCount(0); + await quickFilter.click(); + const phoneFilterPanel = page.getByTestId("service-filter-panel"); + await phoneFilterPanel.getByRole("button", { name: "Service category" }).click(); + await expect(phoneFilterPanel.getByRole("button", { name: /^Crisis & urgent/ })).toBeVisible(); + await phoneFilterPanel.getByRole("button", { name: "Close", exact: true }).click(); // Phones keep the full search results in the page instead of opening a // command sheet over the small viewport. @@ -1355,6 +1360,7 @@ test.describe("Clinical KB tools directory and legacy launcher", () => { await gotoLauncher(page, "/services?focus=1"); await expect(page.getByTestId("services-home").getByTestId("global-search-input")).toBeVisible(); await expect(page.getByTestId("services-home").getByTestId("global-search-input")).toBeFocused(); + await expect(page.getByTestId("service-quick-search-suggestions")).toHaveCount(0); await gotoLauncher(page, "/forms?focus=1"); await expect(visibleByTestId(page, "forms-home").getByTestId("global-search-input")).toBeVisible(); @@ -1369,6 +1375,7 @@ test.describe("Clinical KB tools directory and legacy launcher", () => { await expect(page.locator('input[placeholder="Search services..."]:visible').first()).toHaveValue("13YARN"); await expect(page.getByTestId("service-search-results")).toBeVisible(); await expect(page.getByTestId("service-search-result-13yarn")).toContainText("13YARN"); + await expect(page.getByTestId("service-quick-search-suggestions")).toHaveCount(0); await expect( page.getByTestId("service-search-result-13yarn").getByLabel("Review referral for 13YARN"), ).toHaveAttribute("href", "/services/13yarn"); @@ -1386,20 +1393,22 @@ test.describe("Clinical KB tools directory and legacy launcher", () => { await expectNoPageHorizontalOverflow(page); }); - test("services results keep browse navigation without a walkthrough", async ({ page }) => { + test("services results keep category filtering and compact referral progress without suggestion rows", async ({ + page, + }) => { await page.setViewportSize({ width: 1280, height: 900 }); await gotoLauncher(page, "/services?q=13YARN&focus=1&run=1"); await expect(page.getByRole("heading", { level: 1, name: "13YARN" })).toBeVisible(); await expect(page.getByLabel("Referral workflow")).toHaveCount(0); - // The four-card numbered walkthrough stays gone (assertion above); what - // replaces it is a one-line dot rail under a DIFFERENT accessible name, - // so the check above cannot be satisfied by quietly renaming the old - // component back onto this route (ledger #163). + // The four-card numbered walkthrough stays gone (assertion above). A + // compact progress rail sits between the results heading and the list, + // under a different accessible name (ledger #163). const referralProgress = page.getByRole("navigation", { name: "Referral progress" }); await expect(referralProgress).toBeVisible(); await expect(referralProgress.locator('[aria-current="step"]')).toHaveText("Search"); - await expect(page.getByRole("navigation", { name: "Service groups" })).toBeVisible(); + await expect(referralProgress).toContainText("Step 1 of 4"); + await expect(page.getByRole("navigation", { name: "Service groups" })).toHaveCount(0); await expect(page.getByTestId("services-shortlist-bar")).toHaveCount(0); // The row is compact by contract: the Catchment/Eligibility/Cost strip @@ -1408,27 +1417,33 @@ test.describe("Clinical KB tools directory and legacy launcher", () => { const firstResult = page.getByTestId("service-search-result-13yarn"); await expect(firstResult.getByText("Catchment", { exact: true })).toHaveCount(0); await expect(firstResult.getByRole("button", { name: "Save 13YARN to favourites" })).toBeVisible(); + const headingBox = await page.getByRole("heading", { level: 1, name: "13YARN" }).boundingBox(); + const progressBox = await referralProgress.boundingBox(); + const resultBox = await firstResult.boundingBox(); + expect(headingBox).not.toBeNull(); + expect(progressBox).not.toBeNull(); + expect(resultBox).not.toBeNull(); + expect(progressBox!.y).toBeGreaterThan(headingBox!.y); + expect(progressBox!.y).toBeLessThan(resultBox!.y); - const culturallySafe = page - .getByTestId("service-quick-search-suggestions") - .getByRole("button", { name: "Culturally safe" }); - await expect(culturallySafe).toBeVisible(); - await waitForReactEventHandler(culturallySafe); - await culturallySafe.click(); - await expect(page).toHaveURL(/q=Aboriginal\+Torres\+Strait\+Islander/); - await expect(page.getByTestId("service-search-result-13yarn")).toBeVisible(); + await expect(page.getByTestId("service-quick-search-suggestions")).toHaveCount(0); - // Quick search suggestions and facet clearing are separate contracts. - // Exercise a real facet, then clear only that facet while preserving q. + // The former service-group strip is now an honest, overlapping facet in + // the shared filter sheet. Clearing it preserves the active query. await page.getByTestId("service-filter-trigger-desktop").click(); const filterPanel = page.getByTestId("service-filter-panel"); - await filterPanel.getByRole("button", { name: /^Acuity/ }).click(); - const crisisFacet = filterPanel.getByRole("button", { name: /^Crisis \/ urgent/ }); - await expect(crisisFacet).toBeVisible(); - await crisisFacet.click(); - await expect(page).toHaveURL(/acuity_flags=crisis_high/); + await filterPanel.getByRole("button", { name: "Service category" }).click(); + await expect(filterPanel.getByRole("button", { name: /^Public mental health/ })).toBeVisible(); + await expect(filterPanel.getByRole("button", { name: /^Alcohol & other drugs/ })).toBeVisible(); + await expect(filterPanel.getByRole("button", { name: /^Community & specialist support/ })).toBeVisible(); + const crisisCategory = filterPanel.getByRole("button", { name: /^Crisis & urgent/ }); + await expect(crisisCategory).toBeVisible(); + await crisisCategory.click(); + await expect(page).toHaveURL(/group=urgent/); + await expect(page.getByRole("button", { name: "Remove Crisis & urgent filter" })).toBeVisible(); await filterPanel.getByTestId("service-filter-panel-clear").click(); - await expect(page).toHaveURL(/q=Aboriginal\+Torres\+Strait\+Islander/); + await expect(page).toHaveURL(/q=13YARN/); + await expect(page).not.toHaveURL(/group=/); await expect(page.getByTestId("service-search-result-13yarn")).toBeVisible(); await filterPanel.getByRole("button", { name: "Close", exact: true }).click();