diff --git a/packages/memory-graph/src/__tests__/controls-a11y.e2e.test.tsx b/packages/memory-graph/src/__tests__/controls-a11y.e2e.test.tsx new file mode 100644 index 00000000..8fb57293 --- /dev/null +++ b/packages/memory-graph/src/__tests__/controls-a11y.e2e.test.tsx @@ -0,0 +1,95 @@ +/** + * Mounted-render accessibility checks for the graph control surfaces: + * accessible names on icon-only zoom buttons, disclosure state on the legend, + * and a live region on the loading indicator. + */ + +// @vitest-environment happy-dom + +import { cleanup, fireEvent, render } from "@testing-library/react" +import { afterEach, describe, expect, it } from "vitest" + +afterEach(cleanup) + +import { Legend } from "../components/legend" +import { LoadingIndicator } from "../components/loading-indicator" +import { NavigationControls } from "../components/navigation-controls" +import { DEFAULT_COLORS, DEFAULT_LABELS } from "../constants" +import type { GraphNode } from "../types" + +const node: GraphNode = { + id: "doc-1", + type: "document", + x: 0, + y: 0, + data: { + id: "doc-1", + title: "Doc", + summary: null, + type: "text", + createdAt: "2026-01-01", + updatedAt: "2026-01-01", + memories: [], + }, + size: 40, + borderColor: "#fff", + isHovered: false, + isDragging: false, +} + +const noop = () => {} + +describe("NavigationControls accessibility", () => { + it("gives the icon-only zoom buttons accessible names", () => { + const { getByLabelText } = render( + , + ) + + expect(getByLabelText("Zoom in")).toBeTruthy() + expect(getByLabelText("Zoom out")).toBeTruthy() + }) +}) + +describe("Legend accessibility", () => { + it("exposes disclosure state via aria-expanded and toggles it", () => { + const { getByRole } = render( + , + ) + + const toggle = getByRole("button", { name: "Legend" }) + expect(toggle.getAttribute("aria-expanded")).toBe("false") + + fireEvent.click(toggle) + expect(toggle.getAttribute("aria-expanded")).toBe("true") + }) +}) + +describe("LoadingIndicator accessibility", () => { + it("announces loading through a status live region", () => { + const { getByRole } = render( + , + ) + + const status = getByRole("status") + expect(status.getAttribute("aria-live")).toBe("polite") + }) +}) diff --git a/packages/memory-graph/src/components/legend.tsx b/packages/memory-graph/src/components/legend.tsx index c65073f4..1ef5ef12 100644 --- a/packages/memory-graph/src/components/legend.tsx +++ b/packages/memory-graph/src/components/legend.tsx @@ -244,6 +244,7 @@ function StatRow({ onClick={expandable ? onToggle : undefined} style={buttonStyle} type="button" + aria-expanded={expandable ? expanded : undefined} >
{icon} @@ -430,6 +431,7 @@ export const Legend = memo(function Legend({ onClick={() => setIsExpanded(!isExpanded)} style={headerBtnStyle} type="button" + aria-expanded={isExpanded} > {isExpanded ? ( diff --git a/packages/memory-graph/src/components/loading-indicator.tsx b/packages/memory-graph/src/components/loading-indicator.tsx index 8d550eb9..596f9dbd 100644 --- a/packages/memory-graph/src/components/loading-indicator.tsx +++ b/packages/memory-graph/src/components/loading-indicator.tsx @@ -76,7 +76,7 @@ export const LoadingIndicator = memo< } return ( -
+
-
+ ) }, ) diff --git a/packages/memory-graph/src/components/navigation-controls.tsx b/packages/memory-graph/src/components/navigation-controls.tsx index fd4497d9..ed04f1b2 100644 --- a/packages/memory-graph/src/components/navigation-controls.tsx +++ b/packages/memory-graph/src/components/navigation-controls.tsx @@ -169,6 +169,7 @@ export const NavigationControls = memo( onClick={onZoomOut} style={zoomBtnStyle} type="button" + aria-label="Zoom out" onMouseEnter={(e) => { e.currentTarget.style.opacity = "0.8" }} @@ -176,13 +177,16 @@ export const NavigationControls = memo( e.currentTarget.style.opacity = "1" }} > - − +