diff --git a/canvas/src/components/ui/__tests__/StatusBadge.test.tsx b/canvas/src/components/ui/__tests__/StatusBadge.test.tsx new file mode 100644 index 00000000..3e1469e4 --- /dev/null +++ b/canvas/src/components/ui/__tests__/StatusBadge.test.tsx @@ -0,0 +1,88 @@ +// @vitest-environment jsdom +/** + * StatusBadge — secret key connection status indicator. + * + * Per spec §4: always icon + color (never colour-only) for colour-blind users. + * Covers: verified / invalid / unverified render branches, icon, aria-label, className. + */ +import { afterEach, describe, expect, it } from "vitest"; +import { render } from "@testing-library/react"; +import React from "react"; + +import { StatusBadge } from "../StatusBadge"; + +afterEach(() => { + // Prevent DOM accumulation across tests (maxWorkers=1 means all test + // files share the same jsdom worker). + const { cleanup } = require("@testing-library/react"); + cleanup(); +}); + +function getBadge(status: "verified" | "invalid" | "unverified") { + const { container } = render(); + return container.querySelector("[role=status]") as HTMLElement; +} + +describe("StatusBadge — icon", () => { + it("renders ✓ for verified", () => { + expect(getBadge("verified").textContent).toBe("✓"); + }); + + it("renders ✗ for invalid", () => { + expect(getBadge("invalid").textContent).toBe("✗"); + }); + + it("renders ○ for unverified", () => { + expect(getBadge("unverified").textContent).toBe("○"); + }); +}); + +describe("StatusBadge — aria-label", () => { + it("sets 'Connection status: verified' for verified", () => { + expect(getBadge("verified").getAttribute("aria-label")).toBe( + "Connection status: verified", + ); + }); + + it("sets 'Connection status: invalid' for invalid", () => { + expect(getBadge("invalid").getAttribute("aria-label")).toBe( + "Connection status: invalid", + ); + }); + + it("sets 'Connection status: unverified' for unverified", () => { + expect(getBadge("unverified").getAttribute("aria-label")).toBe( + "Connection status: unverified", + ); + }); +}); + +describe("StatusBadge — className", () => { + it("applies status-badge--valid for verified", () => { + expect(getBadge("verified").className).toContain("status-badge--valid"); + }); + + it("applies status-badge--invalid for invalid", () => { + expect(getBadge("invalid").className).toContain("status-badge--invalid"); + }); + + it("applies status-badge--unverified for unverified", () => { + expect(getBadge("unverified").className).toContain( + "status-badge--unverified", + ); + }); +}); + +describe("StatusBadge — role", () => { + it("sets role=status", () => { + const el = getBadge("verified"); + expect(el.getAttribute("role")).toBe("status"); + }); +}); + +describe("StatusBadge — structural", () => { + it("renders exactly one status element", () => { + const { container } = render(); + expect(container.querySelectorAll("[role=status]").length).toBe(1); + }); +});