diff --git a/canvas/src/components/CookieConsent.tsx b/canvas/src/components/CookieConsent.tsx index 6b607b04..898ffef1 100644 --- a/canvas/src/components/CookieConsent.tsx +++ b/canvas/src/components/CookieConsent.tsx @@ -98,9 +98,17 @@ export function CookieConsent() { }; return ( -
@@ -130,20 +138,20 @@ export function CookieConsent() {
- + ); } diff --git a/canvas/src/components/__tests__/CookieConsent.test.tsx b/canvas/src/components/__tests__/CookieConsent.test.tsx index 188c6f9c..66012a17 100644 --- a/canvas/src/components/__tests__/CookieConsent.test.tsx +++ b/canvas/src/components/__tests__/CookieConsent.test.tsx @@ -40,7 +40,7 @@ afterEach(() => { describe("CookieConsent", () => { it("renders the banner when no decision is stored", () => { render(); - expect(screen.getByRole("dialog")).toBeTruthy(); + expect(screen.getByRole("region")).toBeTruthy(); expect(screen.getByRole("button", { name: "Accept all" })).toBeTruthy(); expect(screen.getByRole("button", { name: "Necessary only" })).toBeTruthy(); }); @@ -48,7 +48,7 @@ describe("CookieConsent", () => { it("stores 'accepted' and hides the banner when user clicks Accept all", () => { render(); fireEvent.click(screen.getByRole("button", { name: "Accept all" })); - expect(screen.queryByRole("dialog")).toBeNull(); + expect(screen.queryByRole("region")).toBeNull(); const raw = window.localStorage.getItem(STORAGE_KEY); expect(raw).not.toBeNull(); @@ -61,7 +61,7 @@ describe("CookieConsent", () => { it("stores 'rejected' and hides the banner when user clicks Necessary only", () => { render(); fireEvent.click(screen.getByRole("button", { name: "Necessary only" })); - expect(screen.queryByRole("dialog")).toBeNull(); + expect(screen.queryByRole("region")).toBeNull(); const parsed = JSON.parse(window.localStorage.getItem(STORAGE_KEY)!); expect(parsed.decision).toBe("rejected"); @@ -73,7 +73,7 @@ describe("CookieConsent", () => { JSON.stringify({ decision: "accepted", decidedAt: new Date().toISOString(), version: 1 }), ); render(); - expect(screen.queryByRole("dialog")).toBeNull(); + expect(screen.queryByRole("region")).toBeNull(); }); it("re-prompts when the stored decision is on an older policy version", () => { @@ -82,13 +82,13 @@ describe("CookieConsent", () => { JSON.stringify({ decision: "accepted", decidedAt: new Date().toISOString(), version: 0 }), ); render(); - expect(screen.getByRole("dialog")).toBeTruthy(); + expect(screen.getByRole("region")).toBeTruthy(); }); it("re-prompts when localStorage contains invalid JSON", () => { window.localStorage.setItem(STORAGE_KEY, "{not json"); render(); - expect(screen.getByRole("dialog")).toBeTruthy(); + expect(screen.getByRole("region")).toBeTruthy(); }); it("exposes a privacy-policy link with target='_blank'", () => { @@ -99,11 +99,19 @@ describe("CookieConsent", () => { expect(link.getAttribute("rel")).toContain("noreferrer"); }); - it("uses role=dialog with aria-labelledby and aria-describedby for screen readers", () => { + it("uses role=region (NOT dialog) with aria-labelledby/describedby — banner is informational, not modal", () => { + // Regression guard: an earlier version claimed role="dialog" + // aria-modal="true" without a focus trap. That falsely told screen + // readers the rest of the page was inert, trapping AT users in a + // banner they couldn't escape. role="region" lets assistive tech + // navigate around it normally; the banner stays informational. render(); - const dialog = screen.getByRole("dialog"); - expect(dialog.getAttribute("aria-labelledby")).toBe("cookie-consent-title"); - expect(dialog.getAttribute("aria-describedby")).toBe("cookie-consent-body"); + const banner = screen.getByRole("region"); + expect(banner.getAttribute("aria-labelledby")).toBe("cookie-consent-title"); + expect(banner.getAttribute("aria-describedby")).toBe("cookie-consent-body"); + // No aria-modal claim — explicit guard against regression. + expect(banner.getAttribute("aria-modal")).toBeNull(); + expect(screen.queryByRole("dialog")).toBeNull(); }); it("does NOT render on local dev (non-SaaS hostname)", () => { @@ -116,7 +124,7 @@ describe("CookieConsent", () => { value: { ...window.location, hostname: "localhost" }, }); render(); - expect(screen.queryByRole("dialog")).toBeNull(); + expect(screen.queryByRole("region")).toBeNull(); }); it("does NOT render on a LAN hostname (192.168.*, *.local)", () => { @@ -125,7 +133,7 @@ describe("CookieConsent", () => { value: { ...window.location, hostname: "192.168.1.74" }, }); render(); - expect(screen.queryByRole("dialog")).toBeNull(); + expect(screen.queryByRole("region")).toBeNull(); }); });