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(