Cory Policht
← All references
REF · IP-OUI-11-3-ADP-UI-TEST-A11Y · IP

Ip Oui 11 3 Adp Ui Test A11y

source: ADP-UI/IPs/IP-OUI-11.3-ADP-UI-Test-And-A11y-Hardening.mdscrubbed extract

Scrubbed extract of ADP-UI/IPs/IP-OUI-11.3-ADP-UI-Test-And-A11y-Hardening.md — cited by the Chronicle (06-extraction-and-divergence). Redactions: phone / service detail / secrets / clearance / personal / internal hosts.


type: implementation-plan category: Work project: ADP-UI epic: null priority: Medium status: in-review created: 2026-07-12 effort: null precedence:

  • “[[IP-OUI-11.2-Consume-Back-Refactor]]” parallel_group: null review-profile: standard model: null tokens: 0 tags: [implementation-plan, adp, adp-ui, testing, a11y, gap-review]

IP-OUI-11.3: Harden @adp/ui test coverage, a11y verification, and coverage config

Mission

From the 2026-07-12 ADP deep gap review of the adp-ui repo (Epic-11 shared package). Findings: (1) 35 component/module .tsx files but only 5 test files — the 22 src/components/common/* components share one aggregate test with no per-component visibility; (2) many components carry aria-/role attributes (Countdown, IconFrame, MeterBar, NumberInput, OptionCard, ProgressBar, Select, Toggle, ConnectionIndicator, GateAlertBanner) but no axe dependency exists — a11y is asserted by convention, never verified; (3) vitest.config.ts:1-9 has no coverage: section, so CI’s adp-ui-coverage artifact step likely uploads nothing meaningful; (4) no Storybook/visual-regression tooling for a design-system package — decide whether that’s an accepted scope cut and record it.

Execution Phases

  • Per-component (or grouped-with-assertions-per-component) tests for src/components/common/* — 5 new files, 79 → 217 tests
  • vitest-axe/axe-core added; a11y assertions on the aria-bearing components
  • coverage: config in vitest with a threshold; CI artifact verified non-empty
  • Storybook/visual-regression decision recorded — recommendation: descope; awaiting user confirmation (the only open phase)

Outcome (2026-08-22)

Delivered in [PR #14](https://internal host/homelab/adp-ui/pulls/14), release:minor. Implementation dispatched to localmoe-qwen3.8-27b across six bounded lanes (cluster 0 + A–E), each frontier-reviewed against git ground truth before the next was dispatched.

Metric Before After
Test files 7 13
Tests 79 217
Statements 72.12 79
Branches 59.64 81.36
Functions 71.84 83.33
Lines 72.56 79.33

src/components/common reaches 100% statements / functions / lines, 94.11% branches. Thresholds enforced at 70 / 55 / 70 / 70 — measured, then rounded down to the nearest 5 so the gate has headroom without being decorative. Gate itself proved by inversion (lines: 100 → exit 1 on the threshold error alone).

Finding 2 of the mission is why this IP mattered

“a11y is asserted by convention, never verified” turned out to be understating it. Composer’s label prop rendered as a disconnected <span> — no htmlFor, no aria-label — so its <textarea> had no accessible name at all. axe’s label rule fires on every instance; screen-reader users got an unlabelled edit field. It survived the full extraction (11.1/11.2) and both consumers precisely because nothing asserted on the accessibility tree. Fixed with a real <label htmlFor> via useId(); prop contract and class names unchanged.

Three tests that passed for the wrong reason

Each was caught by inversion, not by reading:

  1. NumberInput “rejects letters” — type="number" sanitizes 'abc' to '' before the handler runs, so it never reaches Number.isNaN. Kept with a scope comment; it still asserts the real user-facing contract.
  2. Toggle disabled — fireEvent.click dispatches straight at the node and reaches React’s handler even on a disabled input. Rewritten through userEvent, which honours disabled.
  3. Tabs call count — Radix activates a trigger on both mousedown and focus, so one click emits two value changes. Asserts toHaveBeenCalledWith rather than a count that would pin Radix’s internals.

22 inverted proofs across the five lanes; all five clusters logged PASS in the model-perf ledger.

Follow-up, deliberately not in this PR

Tabs.tsx:30 renders item.description with className="adp-progress-bar__label" — a copy-paste from ProgressBar. Renaming it is a consumer-visible styling-hook change and does not belong in test hardening.

References

  • Origin: captured via vault-pipeline 2026-07-12 from staged task Tasks/Work/adp-ui-test-a11y-coverage-hardening.md (ADP deep gap review 2026-07-12)