diff --git a/src/components/accessibility.spec.ts b/src/components/accessibility.spec.ts new file mode 100644 index 00000000..d5e00113 --- /dev/null +++ b/src/components/accessibility.spec.ts @@ -0,0 +1,139 @@ +import { describe, expect, it } from "vitest"; +import { readFileSync } from "fs"; +import { resolve } from "path"; + +/** + * Accessibility compliance tests + * + * Validates that interactive elements across the CODA UI include the required + * ARIA attributes and alt text for WCAG 2.1 AA compliance. These tests read + * the component source files and assert that the accessibility attributes are + * present, catching regressions without requiring a full browser/jsdom render. + */ + +function readComponent(relativePath: string): string { + return readFileSync(resolve(__dirname, relativePath), "utf8"); +} + +// --------------------------------------------------------------------------- +// GPS Location Pane +// --------------------------------------------------------------------------- + +describe("gps-location accessibility", () => { + const source = readComponent("panes/gps-location.tsx"); + + it("marker images include descriptive alt text", () => { + expect(source).toContain("alt={" + "`GPS marker icon for ${key}`}"); + }); + + it("lock button has aria-label", () => { + expect(source).toContain('aria-label="Toggle map scroll lock"'); + }); +}); + +// --------------------------------------------------------------------------- +// Dockview Header Actions +// --------------------------------------------------------------------------- + +describe("dockview-header-actions accessibility", () => { + const source = readComponent("framework/dockview/dockview-header-actions.tsx"); + + it("add panel button has aria-label", () => { + expect(source).toContain('aria-label="Add panel"'); + }); + + it("collapsed controls button has aria-label", () => { + expect(source).toContain('aria-label="Toggle panel controls"'); + }); +}); + +// --------------------------------------------------------------------------- +// Dockview Watermark +// --------------------------------------------------------------------------- + +describe("dockview watermark accessibility", () => { + const source = readComponent("framework/dockview/dockview.tsx"); + + it("watermark add panel button has aria-label", () => { + expect(source).toContain('aria-label="Add panel"'); + }); +}); + +// --------------------------------------------------------------------------- +// Header +// --------------------------------------------------------------------------- + +describe("header accessibility", () => { + const source = readComponent("interface/header.tsx"); + + it("help menu button has role and aria-label", () => { + expect(source).toContain('role="button"'); + expect(source).toContain('aria-label="Toggle help menu"'); + }); + + it("cancel time button has aria-label", () => { + expect(source).toContain('aria-label="Cancel time edit"'); + }); + + it("go time button has aria-label", () => { + expect(source).toContain('aria-label="Apply time change"'); + }); + + it("CODA wordmark has role and aria-label", () => { + expect(source).toContain('aria-label="Go to CODA home"'); + }); +}); + +// --------------------------------------------------------------------------- +// Comm Pane Controls +// --------------------------------------------------------------------------- + +describe("comm controls accessibility", () => { + const source = readComponent("panes/comm.tsx"); + + it("channel dropdown button has aria-label and aria-expanded", () => { + expect(source).toContain('aria-label="Toggle channel selection"'); + expect(source).toContain("aria-expanded={dropdownOpen}"); + }); + + it("filter button has aria-label", () => { + expect(source).toContain('aria-label="Toggle utterance filter"'); + }); + + it("scroll lock button has aria-label", () => { + expect(source).toContain('aria-label="Toggle auto-scroll lock"'); + }); +}); + +// --------------------------------------------------------------------------- +// Video Controls +// --------------------------------------------------------------------------- + +describe("video controls accessibility", () => { + const source = readComponent("panes/video/video-controls.tsx"); + + it("mute button has dynamic aria-label based on state", () => { + expect(source).toContain("Unmute audio"); + expect(source).toContain("Mute audio"); + }); + + it("IO info button has aria-label", () => { + expect(source).toContain('aria-label="Toggle IO information"'); + }); +}); + +// --------------------------------------------------------------------------- +// Help Button (shared component) +// --------------------------------------------------------------------------- + +describe("pane help button accessibility", () => { + const source = readComponent("interface/pane-help-control-button.tsx"); + + it("has role='button' for the clickable div", () => { + expect(source).toContain('role="button"'); + }); + + it("has aria-label", () => { + expect(source).toContain('aria-label="Toggle help overlay"'); + }); +}); diff --git a/src/components/framework/dockview/dockview-header-actions.tsx b/src/components/framework/dockview/dockview-header-actions.tsx index 402d7918..e53d3513 100644 --- a/src/components/framework/dockview/dockview-header-actions.tsx +++ b/src/components/framework/dockview/dockview-header-actions.tsx @@ -118,6 +118,7 @@ const CollapsedControls: FunctionComponent = ({ className={styles.collapseButton} onClick={handleClick} title="Panel controls" + aria-label="Toggle panel controls" > @@ -179,7 +180,7 @@ export const DockviewLeftActions: FunctionComponent }, [containerApi, dispatch, group]); return ( - ); diff --git a/src/components/framework/dockview/dockview.tsx b/src/components/framework/dockview/dockview.tsx index cb0afeb3..cbb1727a 100644 --- a/src/components/framework/dockview/dockview.tsx +++ b/src/components/framework/dockview/dockview.tsx @@ -75,7 +75,7 @@ const DockviewWatermark: FunctionComponent = ({ containerA return (
Use - to add a new panel diff --git a/src/components/interface/header.tsx b/src/components/interface/header.tsx index 6ac6d734..67e4c0bd 100644 --- a/src/components/interface/header.tsx +++ b/src/components/interface/header.tsx @@ -43,6 +43,8 @@ const LoaderHelpMenu: FunctionComponent<{ <>
{ setHelpLoaderOpen(!helpLoaderOpen); }} @@ -265,10 +267,10 @@ const Clock: FunctionComponent = () => { className={styles.timeButtonsContainer} style={{ top: buttonPos.top, left: buttonPos.left, width: buttonPos.width }} > - -
, @@ -359,6 +361,8 @@ const Header: FunctionComponent<{
{ window.location.assign(location.origin); }} diff --git a/src/components/interface/pane-help-control-button.tsx b/src/components/interface/pane-help-control-button.tsx index 00c4f771..0e95d70e 100644 --- a/src/components/interface/pane-help-control-button.tsx +++ b/src/components/interface/pane-help-control-button.tsx @@ -12,6 +12,8 @@ export const HelpButton: FunctionComponent<{ clickHandler: () => void; selected?
{ if (clickHandler) { clickHandler(); diff --git a/src/components/panes/comm.tsx b/src/components/panes/comm.tsx index e31035e5..163486ed 100644 --- a/src/components/panes/comm.tsx +++ b/src/components/panes/comm.tsx @@ -189,6 +189,8 @@ export const CommControls: FunctionComponent<{
diff --git a/src/components/panes/video/video-controls.tsx b/src/components/panes/video/video-controls.tsx index 5a40bee3..b09cdb03 100644 --- a/src/components/panes/video/video-controls.tsx +++ b/src/components/panes/video/video-controls.tsx @@ -38,6 +38,7 @@ export const IOInfoButton: FunctionComponent<{ );