From 19f9e463afe16c58c1901a38644a6b5ae33f4478 Mon Sep 17 00:00:00 2001 From: Molecule AI Core-UIUX Date: Wed, 13 May 2026 14:28:00 +0000 Subject: [PATCH] test(ConfirmDialog): add 6 WCAG accessibility tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add coverage for dialog a11y guarantees already implemented: - role=dialog + aria-modal=true - aria-labelledby pointing to title (WCAG 1.3.1) - Escape → onCancel, Enter → onConfirm (WCAG 2.1.1) - Focus moves to first button on open (WCAG 2.4.3) - Backdrop click → onCancel - aria-label on backdrop (WCAG 4.1.2) Co-Authored-By: Claude Opus 4.7 --- .../__tests__/ConfirmDialog.test.tsx | 106 +++++++++++++++++- 1 file changed, 104 insertions(+), 2 deletions(-) diff --git a/canvas/src/components/__tests__/ConfirmDialog.test.tsx b/canvas/src/components/__tests__/ConfirmDialog.test.tsx index 7798fdc5..adf5fa92 100644 --- a/canvas/src/components/__tests__/ConfirmDialog.test.tsx +++ b/canvas/src/components/__tests__/ConfirmDialog.test.tsx @@ -1,12 +1,114 @@ // @vitest-environment jsdom -import { describe, it, expect, vi, afterEach } from "vitest"; -import { render, screen, fireEvent, cleanup } from "@testing-library/react"; +import { describe, it, expect, vi, afterEach, beforeEach } from "vitest"; +import { render, screen, fireEvent, cleanup, act } from "@testing-library/react"; import { ConfirmDialog } from "../ConfirmDialog"; afterEach(() => { cleanup(); }); +describe("ConfirmDialog — WCAG dialog accessibility", () => { + it("dialog has role=dialog and aria-modal=true", () => { + render( + + ); + const dialog = screen.getByRole("dialog"); + expect(dialog).toBeTruthy(); + expect(dialog.getAttribute("aria-modal")).toBe("true"); + }); + + it("dialog has aria-labelledby pointing to the title", () => { + render( + + ); + const dialog = screen.getByRole("dialog"); + const labelledBy = dialog.getAttribute("aria-labelledby"); + expect(labelledBy).toBeTruthy(); + const titleEl = document.getElementById(labelledBy!); + expect(titleEl?.textContent?.trim()).toBe("Delete workspace"); + }); + + it("Escape key invokes onCancel", () => { + const onCancel = vi.fn(); + render( + + ); + fireEvent.keyDown(window, { key: "Escape" }); + expect(onCancel).toHaveBeenCalledTimes(1); + }); + + it("Enter key invokes onConfirm", () => { + const onConfirm = vi.fn(); + render( + + ); + fireEvent.keyDown(window, { key: "Enter" }); + expect(onConfirm).toHaveBeenCalledTimes(1); + }); + + it("moves focus to the first button when dialog opens (WCAG 2.4.3)", async () => { + const onConfirm = vi.fn(); + render( + + ); + // Flush requestAnimationFrame so ConfirmDialog's internal rAF focus fires + await act(async () => { + await new Promise((r) => requestAnimationFrame(() => requestAnimationFrame(r))); + }); + const firstButton = screen.getAllByRole("button")[0]; + expect(document.activeElement).toBe(firstButton); + }); +}); + +describe("ConfirmDialog — backdrop", () => { + it("backdrop click invokes onCancel", () => { + const onCancel = vi.fn(); + render( + + ); + const backdrop = document.querySelector('[aria-label="Dismiss dialog"]') as HTMLElement; + expect(backdrop).toBeTruthy(); + fireEvent.click(backdrop); + expect(onCancel).toHaveBeenCalledTimes(1); + }); +}); + describe("ConfirmDialog singleButton prop", () => { it("renders Cancel button by default", () => { render(