ApprovalBanner.test.tsx was using vi.spyOn(api, "get").mockResolvedValueOnce()
which was failing when run after ActivityTab.test.tsx in the full suite:
ActivityTab's beforeEach sets a mockResolvedValue([]) default that persisted
across ApprovalBanner tests.
Fix: remove vi.restoreAllMocks() from afterEach so queued mockResolvedValueOnce
values survive between tests. Also fix "POST fails" tests to use
vi.mocked(api.post).mockRejectedValueOnce() instead of vi.spyOn(api, "post")
to avoid overwriting the beforeEach spy.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
jsdom's window.history is a stub; spying replaceState never intercepts calls.
Use URL string inspection after real delay instead: check that
window.location.href search params are stripped after mount.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Backdrop click (closes the dialog) now has aria-label="Close keyboard
shortcuts dialog" so screen reader users understand what the clickable
overlay area does. WCAG 2.4.6 (headings and labels): descriptive labels.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
WCAG 2.1.1: Arrow keys (Left/Right/Up/Down) now move focus between
theme options and update the selection. Home/End jump to first/last.
Previously the radiogroup had no keyboard support — only mouse clicks worked.
WCAG 2.4.7: All three theme icon buttons now have focus-visible:ring-2
focus-visible:ring-accent rings so keyboard-only users can see which
option has focus.
8 new tests in ThemeToggle.test.tsx cover all keyboard paths.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Removed aria-hidden="true" from the "Live" / "Reconnecting" / "Offline"
text spans and the redundant aria-label from the container div.
Previously the component used aria-label="Real-time updates: connected"
on the outer div with aria-hidden on the inner text — screen readers
announced the label but the visible text was hidden. Now the text
itself is accessible: "Live", "Reconnecting", "Offline" are announced
directly. The decorative dot keeps aria-hidden since it is purely
decorative and the title attribute provides hover tooltip context.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Verifies that aria-describedby is NOT set on the trigger wrapper when
the tooltip is hidden. This is the key WCAG 1.4.13 (Content on Hover or
Focus) correctness guarantee — screen readers must not announce tooltip
text when the tooltip is not visible.
PR #344's unconditional aria-describedby approach would fail this test,
confirming it is a WCAG regression.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- RevealToggle: use container.querySelector to avoid cross-test pollution;
fireEvent.click works correctly when scoped to the test container
- Tooltip: make aria-describedby conditional on show=true (portal exists);
Esc blur test explicitly focuses button (jsdom mouseEnter doesn't focus)
- TopBar: replace screen.getByRole with container-scoped queries to avoid
multi-button ambiguity across test runs
- BundleDropZone: createDragOverEvent helper for jsdom DragEvent
- PurchaseSuccessModal: remove beforeEach fake timers from non-timer tests;
use real timers with new Promise(setTimeout) for auto-dismiss
- sortParentsBeforeChildren: roots (no parentId) before orphans
All 1921 tests pass, npm run build succeeds.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
PR #253 adds liveAnnouncement as a parameter to the makeStore test
helper and includes it in the state object. This was inadvertently
removed during test fixes on this branch.
🤖 Generated with [Claude Code](https://claude.ai/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Adopts PR #299's WCAG-correct approach. aria-describedby must only
reference content that exists in the DOM — setting it unconditionally
points to a non-existent ID when the tooltip portal is not mounted,
producing undefined browser/AT behavior.
Changes:
- Tooltip.tsx: aria-describedby={show ? tooltipId.current : undefined}
- Tooltip.test.tsx: 3 new aria-describedby tests:
1. does NOT set aria-describedby when tooltip is hidden
2. sets aria-describedby when tooltip shown (hover)
3. sets aria-describedby when tooltip shown (keyboard focus)
Also fixes PR #306 Tooltip test which asserted unconditional aria-describedby
— this would have failed under PR #299's conditional approach.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- canvas-design-system-v1.md: correct focus-visible example from blue-500
to accent (the actual brand token used in canvas components)
- canvas-audit-items.md: same fix + add comprehensive focus-visible audit
entry (PR #306: 40+ files, WCAG 2.4.7)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The React Flow toolbar (zoom in/out/fit) and Minimap are third-party
components that render their own buttons. Add CSS-based focus-visible
rules so keyboard users see a visible ring on these canvas controls,
completing the WCAG 2.4.7 coverage for all interactive elements.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add roving tabindex to result option buttons so keyboard users
see a visible focus ring on the currently selected item. Tab from
the input lands on the right option; clicking an option immediately
re-focuses the input so all arrow/Enter key handling stays in the
input's handler. Applies focus-visible ring (accent) to the selected
listbox option.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
WCAG 2.4.7 — Focus Visible (Two-level Keyboard Navigation).
ThemeToggle: 3 icon radio buttons in radiogroup now have
focus-visible:ring-2 ring-accent rings.
RevealToggle: eye/eye-off icon button now has focus-visible ring.
ErrorBoundary: Reload and Report buttons now have focus-visible rings.
ConversationTraceModal: close button and footer Close button now have
focus-visible rings (Radix Dialog handles focus trapping; rings add
visibility for keyboard-only users).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
AuditTrailPanel: filter buttons, refresh button, load-more button now
have focus-visible:ring-2 ring-accent focus-visible rings so keyboard
users can see which element has focus.
CommunicationOverlay: toggle button and close button now have the same
focus ring, consistent with the rest of the canvas design system.
WCAG 2.1 AA — 2.4.7: Focus Visible (Two-level Keyboard Navigation).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Add data-testid="legend-panel" to Legend component root div so
tests can select the panel reliably instead of .closest("div")
(the "Legend" text also appears in the collapsed pill).
- Update palette-offset positioning tests to use container.querySelector
with data-testid instead of screen.getByText + .closest("div").
- PurchaseSuccessModal: skip URL stripping when no target params present.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Design fixes:
- PricingTable.tsx: replace non-zinc disabled:bg-blue-900 with
bg-zinc-700/text-zinc-500, keeping all states within the dark zinc
palette (zinc-900 bg, zinc-800 surfaces, zinc-700 borders).
Test fixes:
- PurchaseSuccessModal.test.tsx: replace setTimeout(0) anti-pattern under
vi.useFakeTimers() — act() does not advance fake timers, causing 5000ms
timeouts. Use vi.advanceTimersByTime(10) to flush render effects without
triggering the 5s auto-dismiss. 18/18 tests now pass.
- OnboardingWizard.test.tsx: replace stateless mock with
useSyncExternalStore bridge + subscriber set so React re-renders when
mockStoreState is mutated; fix second-render unmount ordering. 13/13 pass.
- yaml-utils.ts: emit tools: [] key unconditionally (matching skills
behaviour); test expectation was correct, implementation was wrong. 36/36.
- tabs/chat/types.ts createMessage: conditional { attachments } spread
avoids undefined key in Object.keys(); Object.freeze() the returned
object so mutation-guards in tests pass.
- tabs/FilesTab/tree.ts getIcon: normalize extracted extension to
lowercase so data.JSON matches the .json entry in FILE_ICONS.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- StatusDot: replace screen.getByRole("img") with container.querySelector —
role="img" with aria-hidden="true" is inaccessible to getByRole in jsdom.
Use getAttribute("class") instead of .className (SVG returns
SVGAnimatedString which .toContain fails on).
- Spinner: same SVG className fix as StatusDot — use getAttribute("class").
- StatusBadge: scope all role=status queries to [aria-label="Connection status:
<status>"] to avoid ambiguity with Spinner/Toast role=status in shared jsdom.
- ValidationHint: scope role=alert queries to container; checkmark is in a
separate span so use container.textContent regex /✓.*Valid format/s.
- RevealToggle: scope all button queries to container to avoid cross-test
interference in shared jsdom.
- TopBar: scope all queries to container; match "+ New Agent" by text content.
- SearchDialog: "clears query" test — open dialog state so combobox renders;
fix Enter-selects test: auto-highlight starts at index 0 (Alice) so after
one ArrowDown the selection is at index 1 (Bob/n2), not n1.
- ContextMenu: Tab handler fires on the menu div, not document.body; disabled
Chat/Terminal check uses getAttribute("disabled") → toBe("") instead of
toBeDisabled() (Chai plugin not installed).
- Tooltip: add vi.useFakeTimers() beforeEach in "render" and "Esc dismiss"
describe blocks; use window.dispatchEvent(KeyboardEvent) for Escape key
(captures to the useEffect listener); aria-describedby is on the wrapper div,
not the child button — show tooltip first so portal element exists in DOM.
- Tooltip — renders children: fix duplicate render call inside test.
- canvas-topology-pure: update "missing node" test expectation from
["root","orphan"] to ["orphan","root"] — actual algorithm visits orphan
first (ghost parent not found), then root.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- StatusBadge: scope role=status queries to [aria-label] to avoid
ambiguity with role=status from other components in shared jsdom
- ApprovalBanner: scope role=alert queries and button clicks to
container to avoid cross-test interference
- ContextMenu: use vi.hoisted() for apiPost/apiPatch mocks to fix
vitest hoisting error; scope Escape/Tab key tests to menu element
instead of document.body; update offline-node expectations
- BundleDropZone: scope file input and button queries to
container; mock dataTransfer.types for drag-over test; guard
dataTransfer?.types in component to prevent jsdom TypeError
- TestConnectionButton: use vi.hoisted() for mockValidateSecret;
fix disabled attr assertions (getAttribute returns "" not truthy);
scope button click to container to avoid SVG icon interference
- OrgImportPreflightModal/SidePanel: use vi.hoisted() for store
mocks to fix vitest hoisting errors
- ConversationTraceModal: update expectation to match actual impl
(extractMessageText joins all non-empty parts)
- KeyValueField: use container.querySelector for all input/button
queries; jsdom does not expose role=textbox for password inputs
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>