Skip to content

feat(frontend): add jest-axe automated accessibility testing (closes #383) - #458

Open
Olajcodes wants to merge 7 commits into
Stellar-Ecosystem:mainfrom
Olajcodes:fix/383-jest-axe-a11y-testing
Open

feat(frontend): add jest-axe automated accessibility testing (closes #383)#458
Olajcodes wants to merge 7 commits into
Stellar-Ecosystem:mainfrom
Olajcodes:fix/383-jest-axe-a11y-testing

Conversation

@Olajcodes

@Olajcodes Olajcodes commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Summary

Closes #383

Adds automated accessibility testing with jest-axe to the frontend test suite, including fixing a11y violations found during the audit and adding axe assertions to all page-level component tests.

Changes

Infrastructure

  • package.json: Added jest-axe dev dependency
  • jest.setup.js: Added import 'jest-axe/extend-expect' to register the toHaveNoViolations matcher
  • types/jest-axe.d.ts: TypeScript declaration for jest-axe module

A11y Violations Fixed

Component Issue Fix
WalletPickerModal Missing dialog role, unlabelled close button Added role="dialog", aria-modal="true", aria-label on dialog and close button
WalletConnect Unlabelled disconnect button Added aria-label="Disconnect wallet"
ServiceCard Unlabelled vote buttons, wrong heading order Added aria-label to upvote/downvote buttons; h3h2 for card title
RegistryPage Sort <select> has no accessible name Added aria-label="Sort services"
RegistryPage test Mock data missing registered_at field Added field to makeServices() helper

Axe Assertions Added (5 test files)

  • Navbar.test.tsx — verifies nav has no violations
  • WalletPickerModal.test.tsx — verifies modal dialog is accessible
  • ActivityFeed.test.tsx — verifies activity list has no violations
  • AgentsPage.test.tsx — verifies agent listing page is accessible
  • RegistryPage.test.tsx — verifies service registry page is accessible

Each test renders the component, waits for content to load, then asserts expect(await axe(container)).toHaveNoViolations().

Test Results

  • TypeScript typecheck: ✅ passed
  • Jest: 17 passed, 1 pre-existing failure (lib/sort.test.ts — unrelated)
  • All 5 new axe a11y tests: ✅ passing
  • CI will now automatically catch a11y regressions

Summary by CodeRabbit

  • Accessibility

    • Improved accessibility across service listings, voting controls, wallet actions, dialogs, and navigation.
    • Added clearer labels and semantic headings for assistive technologies.
    • Enhanced wallet picker dialog focus management, keyboard navigation, and dismissal behavior.
  • Tests

    • Added automated accessibility checks for key pages and components to help prevent regressions.
    • Added coverage for dialog focus trapping, restoration, and Escape-key handling.

…tellar-Ecosystem#383)

Add automated a11y testing with jest-axe to the test suite:

Setup:
- Install jest-axe dev dependency
- Add jest-axe/extend-expect to jest.setup.js
- Add TypeScript declaration for jest-axe module

A11y fixes found and resolved:
- WalletPickerModal: add role='dialog', aria-modal='true', aria-label
  to dialog and close button
- WalletConnect: add aria-label='Disconnect wallet' to close button
- ServiceCard: add aria-label to upvote/downvote buttons
- ServiceCard: fix heading order (h3→h2 for card title)
- RegistryPage: add aria-label='Sort services' to sort select
- RegistryPage: add registered_at to test mock data (pre-existing bug)

Axe assertions added to page-level tests:
- Navbar, WalletPickerModal, ActivityFeed, AgentsPage, RegistryPage
- Each test renders the component and asserts no axe violations

CI will now catch a11y regressions on these components automatically.
@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@Olajcodes Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@Olajcodes, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 40 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7a9cf996-7935-4e5c-b302-e40598cf525d

📥 Commits

Reviewing files that changed from the base of the PR and between 023c939 and 9ad804a.

⛔ Files ignored due to path filters (2)
  • backend/package-lock.json is excluded by !**/package-lock.json
  • contract/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • backend/package.json
  • backend/src/routes/registry.js
  • backend/test/demo.test.js
  • frontend/__tests__/Navbar.test.tsx
  • frontend/__tests__/RegisterForm.test.tsx
  • frontend/app/globals.css
  • frontend/components/RegisterForm.tsx
  • frontend/components/ThemeProvider.tsx
📝 Walkthrough

Walkthrough

The frontend adds jest-axe support, improves accessible labels and heading semantics, adds wallet modal focus management, and introduces automated accessibility audits for pages and interactive components.

Changes

Frontend accessibility

Layer / File(s) Summary
Configure testing and accessible markup
frontend/package.json, frontend/jest.setup.js, frontend/types/jest-axe.d.ts, frontend/app/registry/page.tsx, frontend/components/ServiceCard.tsx, frontend/components/WalletConnect.tsx
Adds jest-axe, configures its matcher, declares its types, and updates control labels and service heading semantics.
Implement wallet modal focus management
frontend/components/WalletPickerModal.tsx, frontend/__tests__/WalletPickerModal.test.tsx
Adds dialog semantics, focus trapping, Escape handling, background inertness, focus restoration, body portal rendering, and corresponding tests.
Add page accessibility audits
frontend/__tests__/ActivityFeed.test.tsx, frontend/__tests__/AgentsPage.test.tsx, frontend/__tests__/Navbar.test.tsx, frontend/__tests__/RegistryPage.test.tsx
Adds axe audits for loaded ActivityFeed, AgentsPage, Navbar, and RegistryPage renders.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant WalletPickerModal
  participant DocumentBody
  User->>WalletPickerModal: Open modal
  WalletPickerModal->>DocumentBody: Portal dialog and mark background inert
  WalletPickerModal->>WalletPickerModal: Move focus into dialog
  User->>WalletPickerModal: Press Tab, Shift+Tab, or Escape
  WalletPickerModal->>WalletPickerModal: Trap focus or close dialog
  WalletPickerModal->>DocumentBody: Restore background and previous focus
Loading

Possibly related PRs

Suggested reviewers: ritik4ever

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the addition of automated frontend accessibility testing with jest-axe.
Linked Issues check ✅ Passed The changes add jest-axe tests, fix reported accessibility issues, and use assertions that fail when violations occur.
Out of Scope Changes check ✅ Passed All reviewed changes support the linked issue by adding accessibility tests, test infrastructure, or accessibility fixes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ritik4ever

Copy link
Copy Markdown
Collaborator

Hi @Olajcodes,

This PR could not be merged because it has merge conflicts with the target branch.

Please resolve the merge conflicts, push the updated changes, and the PR can be reviewed and merged.

Thank you!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/components/WalletPickerModal.tsx`:
- Around line 45-54: Update the WalletPickerModal component to use an accessible
dialog primitive or equivalent focus-management logic: move focus into the
dialog on open, trap Tab navigation, close on Escape, and mark background
content inert while open. Preserve the existing onClose behavior and restore
focus to the opening “Connect Wallet” control when the modal closes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 8959d633-2271-4040-83b2-7ef704dcdbcd

📥 Commits

Reviewing files that changed from the base of the PR and between 0f7a750 and c062e2c.

📒 Files selected for processing (12)
  • frontend/__tests__/ActivityFeed.test.tsx
  • frontend/__tests__/AgentsPage.test.tsx
  • frontend/__tests__/Navbar.test.tsx
  • frontend/__tests__/RegistryPage.test.tsx
  • frontend/__tests__/WalletPickerModal.test.tsx
  • frontend/app/registry/page.tsx
  • frontend/components/ServiceCard.tsx
  • frontend/components/WalletConnect.tsx
  • frontend/components/WalletPickerModal.tsx
  • frontend/jest.setup.js
  • frontend/package.json
  • frontend/types/jest-axe.d.ts

Comment thread frontend/components/WalletPickerModal.tsx
@Olajcodes

Copy link
Copy Markdown
Contributor Author

@ritik4ever I have resolved the conflicts. Kindly check through and merge

…loses Stellar-Ecosystem#383 review)

Address CodeRabbit/maintainer review on PR Stellar-Ecosystem#458: the dialog declared role=dialog/aria-modal but had no focus management. The modal now renders via a portal to document.body (the modal lives inside the Navbar subtree, so marking any ancestor inert would make the dialog itself inert), moves focus into the dialog on open, traps Tab/Shift+Tab within it, closes on Escape via the existing onClose, marks all background body children inert while open, and restores focus to the opener on close.

Adds 5 focus-management tests (focus-in, Tab trap, Escape, inert background, focus restore) and rescopes the axe test to the dialog subtree since portal content renders outside the RTL container.

Also commits the jest-axe package-lock.json sync that belongs to this PR (package.json declares jest-axe but the lockfile at HEAD had no entry for it). Note: a lib/sort.test.ts makeAgent fix was not needed here - upstream main (bf3d21b) already added the missing makeAgent factory.
@Olajcodes
Olajcodes requested a review from ritik4ever as a code owner August 2, 2026 08:16
…d CI green)

The maintainer merged main into this branch, which reintroduced 5 pre-existing frontend bugs (all identical to fixes previously made on the fix/381 branch but never merged to main):

1. globals.css: focus:ring-primary/30 -> focus:ring-primary (Tailwind cannot alpha-modify a CSS-var color in @apply; broke next build).

2. ThemeProvider: remove the `if (!mounted) return <>{children}>` early return so ThemeContext.Provider always renders - SSR/prerender had mounted=false, so Navbar useTheme() threw on every page and broke the build.

3. RegisterForm.test.tsx: fix 2 syntax errors (render(<RegisterForm ... }); -> />);) that prevented the suite from running.

4. RegisterForm.tsx: add id/htmlFor label associations for getByLabelText, change price input to type=text inputMode=decimal so string-based validation is not sanitized by the number input, and disable submit via live validate(form) so an empty form is disabled on first render.

5. Navbar.test.tsx: mock useTheme from ThemeProvider (jsdom lacks matchMedia, so wrapping in the real provider would crash); Navbar gained a theme toggle in main.

Validated: full jest suite 20/20 suites / 122 tests, next build, and tsc all pass.
…erge (backend CI green)

The maintainer merged current upstream/main into this branch, which brought in upstream main bugs (main itself is red on Backend/Frontend/Contract). Fixed on this branch so the merge-ref CI can go green:

1. registry.js: removed a duplicate annotateTtlWarning function left by the merge (old simple version + upstream new parseFiniteNumericValue version in the same strict-mode module) that threw SyntaxError and killed the whole 68-test registry suite. Kept the improved upstream version.

2. demo.test.js: (a) mocked the global fetch() to reject with AbortError - buildHttpClient() overwrites x402HTTPClient.fetchWithTx with a real-fetch implementation, so the old x402HTTPClient.mockImplementationOnce never fired (route returned 500 instead of 499); (b) added vi.mock for @x402/express (paymentMiddlewareFromConfig no-op), @x402/core/server and @x402/stellar/exact/server - mirroring services.test.js - so the real x402 server/facilitator is not contacted and no unhandled rejection occurs at services.js module load.

3. backend/package.json + package-lock.json: restored valid JSON (upstream main had corrupted them with a trailing comma + blank line after removing pino-pretty/rate-limit-redis, breaking npm ci EUSAGE on the merge ref) and regenerated the lockfile including pino-http ^10.5.0 for the new requestContext.js.
Current stable Rust cannot compile ethnum 1.5.2 (E0512 transmute),
which fails the Contract build & test check on this PR. Bump to
1.5.3 — the same proven fix already applied on the fix/382 branch
where all checks pass, and matching contract/agents/Cargo.lock which
already pins 1.5.3 with this checksum.
@Olajcodes

Copy link
Copy Markdown
Contributor Author

@ritik4ever kindly merge. All checks have passed already.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Frontend: no automated accessibility testing in the test suite

2 participants