Skip to content

feat: add accessibility tests for pages#3420

Merged
arkid15r merged 9 commits intoOWASP:mainfrom
Utkarsh-0304:test/a11y_pages
Jan 23, 2026
Merged

feat: add accessibility tests for pages#3420
arkid15r merged 9 commits intoOWASP:mainfrom
Utkarsh-0304:test/a11y_pages

Conversation

@Utkarsh-0304
Copy link
Contributor

Proposed change

Resolves #3364

This PR introduces accessibility tests for pages, building upon the work done in pr #3162. Also, all the existing accessibility conflicts thus encountered are also fixed.

Checklist

  • Required: I followed the contributing workflow
  • Required: I verified that my code works as intended and resolves the issue as described
  • Required: I ran make check-test locally: all warnings addressed, tests passed
  • I used AI for code, documentation, tests, or communication related to this PR

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 19, 2026

Summary by CodeRabbit

  • Tests

    • Added many page-level accessibility tests to broaden coverage.
    • Centralized accessibility matcher in test setup and expanded test scope.
  • Accessibility

    • Added ARIA labels to buttons and links; improved semantic heading structure.
    • Adjusted avatar alt text handling for decorative images.
  • Chores

    • Reorganized accessibility test files and test-running script for consistency.

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

Consolidates jest-axe matcher registration into global Jest setup, removes per-file matcher imports from many component a11y tests, adds ~33 new page-level accessibility tests, expands the a11y test scope in package.json, and applies small accessibility attribute changes across several UI components and tests.

Changes

Cohort / File(s) Summary
A11y component test cleanup
frontend/__tests__/a11y/components/*.a11y.test.tsx (56+ files: ActionButton, AnchorTitle, AutoScrollToTop, Badges, BarChart, BreadCrumbs, BreadCrumbsWrapper, CalendarButton, Card, CardDetailsPage, ChapterMap, ContributionHeatmap, ContributorAvatar, ContributorsList, DashboardCard, DisplayIcon, DonutBarChart, EntityActions, Footer, GeneralCompliantComponent, HealthMetrics, InfoBlock, InfoItem, ItemCardList, Leaders, LeadersList, LineChart, LoadingSpinner, LoginPageContent, LogoCarousel, MarkdownWrapper, MetricsCard, MetricsScoreCircle, Milestones, Modal, ModeToggle, MultiSearch, NavButton, NavDropDown, PageLayout, Pagination, ProgramCard, ProjectTypeDashboardCard, ProjectsDashboardDropDown, ProjectsDashboardNavBar, RecentIssues, RecentPullRequests, RecentRelease, Release, RepositoryCard, ScrollToTop, Search, SearchPageLayout, SecondaryCard, SingleModuleCard, SkeletonBase, SnapshotCard, SortBy, SponsorCard, StatusBadge, ToggleableList, TruncatedText, UserCard, UserMenu)
Removed per-file toHaveNoViolations named imports and expect.extend(toHaveNoViolations) calls; retained axe usage and existing assertions. Matcher is registered globally instead.
New page-level a11y tests
frontend/__tests__/a11y/pages/*.a11y.test.tsx (About, ApiKeysPage, ChapterDetails, Chapters, CommitteeDetails, Committees, Contribute, CreateModule, CreateProgram, EditModule, EditProgram, Home, IssuesPage, MenteeProfilePage, ModuleDetails, ModuleDetailsPage, ModuleIssueDetailsPage, MyMentorship, Organization, OrganizationDetails, Program, ProgramDetails, ProgramDetailsMentorship, ProjectDetails, ProjectHealthDashboardMetricsDetails, Projects, ProjectsHealthDashboardMetrics, ProjectsHealthDashboardOverview, RepositoryDetails, SnapshotDetails, Snapshots, UserDetails, Users)
Added ~33 new a11y test files that mock data/navigation, render pages across loaded/loading/error states, run axe scans, and assert no violations.
Global Jest setup & script scope
frontend/jest.setup.ts, frontend/package.json
Added import { toHaveNoViolations } from 'jest-axe' and expect.extend(toHaveNoViolations) in Jest setup. Broadened test:a11y script from __tests__/a11y/components to __tests__/a11y/.
Small component accessibility updates
frontend/src/app/my/mentorship/.../issues/[issueId]/page.tsx, frontend/src/app/settings/api-keys/page.tsx, frontend/src/components/CardDetailsPage.tsx, frontend/src/components/IssuesTable.tsx, frontend/src/components/ModuleForm.tsx, frontend/src/components/ProgramCard.tsx
Added aria-labels to buttons/links, set some avatar alt to empty string, added clearButtonProps aria-label in ModuleForm Autocomplete, and changed a Tooltip heading from h3 to h2.
Tests & mock data adjustments
frontend/__tests__/unit/components/IssuesTable.test.tsx, frontend/__tests__/mockData/mockCommitteeDetailsData.ts
Modified DOM querying in IssuesTable unit test for resilience; extended mockCommitteeDetailsData with additional numeric and metadata fields.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #3162: Introduced jest-axe accessibility testing and initial component a11y tests — this change globalizes the matcher and expands page-level tests, directly overlapping setup and test files.
  • PR #1951: Adds unit tests for CardDetailsPage — touches the same component (CardDetailsPage.tsx) that received aria-label additions; tests may need alignment.
  • PR #2398: Modifies the my/mentorship module issue page — overlaps with the avatar alt changes in frontend/src/app/my/mentorship/.../issues/[issueId]/page.tsx.

Suggested reviewers

  • kasya
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The PR title 'feat: add accessibility tests for pages' is concise, directly summarizes the main change, and matches the primary objective of implementing page-level accessibility tests.
Description check ✅ Passed The PR description provides relevant context by referencing issue #3364, mentioning prior work from PR #3162, and noting that accessibility conflicts were also fixed during implementation.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #3364 by adding accessibility tests for 30+ frontend pages and fixing accessibility conflicts in existing component tests.
Out of Scope Changes check ✅ Passed Changes are scoped to accessibility test implementation, fixture setup, and necessary fixes to existing tests to support the new global matcher setup. No extraneous modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 14

🤖 Fix all issues with AI agents
In `@frontend/__tests__/a11y/pages/ApiKeysPage.a11y.test.tsx`:
- Around line 45-55: Test lacks a mock for useQuery so the "Create New Key"
button may not render; in the 'should have no violations when Create Modal is
open' test, call the mockUseQuery helper (or jest mock for useQuery) to return
the expected data/loading state that causes ApiKeysPage to render the Create New
Key button before rendering <ApiKeysPage />, then proceed to click the button
and assert the dialog; reference mockUseQuery and the test name to locate where
to add the setup.
- Around line 57-67: The test fails to set up data and uses a fragile button
selector: initialize the mock data for the page by calling the existing
mockUseQuery (or appropriate test data helper) to return API keys before
rendering ApiKeysPage so revoke buttons exist; then replace the fragile
revokeButtons[0] selector with a specific query that targets the revoke control
(e.g., screen.getByRole('button', { name: /revoke/i }) or within the ApiKey list
item using getByText to locate the key and querySelector/getByRole scoped to
that item) and keep the waitFor asserting the dialog via getByRole('dialog') to
ensure the click opened the confirmation.

In `@frontend/__tests__/a11y/pages/CommitteeDetails.a11y.test.tsx`:
- Line 22: Tests render CommitteeDetails without the required Next.js params
prop; update each render call of CommitteeDetails (all occurrences in this test:
the calls around lines where render(<CommitteeDetails />) appears) to pass a
params prop shaped as Promise.resolve({ committeeKey: '<some-key>' }) (or an
async function returning that object) so the component receives params:
Promise<{ committeeKey: string }>; apply this change to all four render
invocations so the component receives a realistic committeeKey during tests.

In `@frontend/__tests__/a11y/pages/CreateProgram.a11y.test.tsx`:
- Around line 23-35: Replace the global afterAll teardown with afterEach to
clear mocks between tests (change afterAll(() => jest.clearAllMocks()) to
afterEach(...)) so the useApolloClient mock set in beforeEach cannot leak;
additionally, update the loading test to explicitly stub useQuery and
useMutation (mockReturnValue or jest.Mock for useQuery/useMutation) to return a
loading: true state so the loading UI is exercised rather than inheriting the
previous test's data-backed return values.

In `@frontend/__tests__/a11y/pages/EditProgram.a11y.test.tsx`:
- Around line 51-61: The loading-state test for EditProgramPage only mocks
useSession but not useQuery, which can break the component if useQuery is called
unconditionally; update the test to also mock useQuery (the hook used in the
component) to return a loading-like shape (e.g., { data: undefined, isLoading:
true } or matching the component's expected fields) before rendering
EditProgramPage, using the same jest.Mock pattern as useSession so the component
receives consistent loading state from both hooks.

In `@frontend/__tests__/a11y/pages/ModuleDetails.a11y.test.tsx`:
- Around line 66-76: The "no data exists" test currently uses mockUseQuery
returning { data: null, loading: true, error: null } which duplicates the
"loading" case; update the mock in the "should have no accessibility violations
when no data exists" test (the mockUseQuery call) to return loading: false
(e.g., { data: null, loading: false, error: null }) so it represents a completed
query with no data for the ModuleDetailsPage render and keep the rest of the
test (rendering ModuleDetailsPage, running axe, and expect toHaveNoViolations)
unchanged.

In `@frontend/__tests__/a11y/pages/ModuleIssueDetailsPage.a11y.test.tsx`:
- Around line 103-137: The tests for ModuleIssueDetailsPage call the component
without mocking useIssueMutations, which can cause failures; add a consistent
mock for the useIssueMutations hook (e.g., mockUseIssueMutations) and set its
mockReturnValue before each test (move mockUseQuery and useIssueMutations setup
into a beforeEach) so loading/error/no-data cases all have a predictable
useIssueMutations return value; locate references to useIssueMutations and
mockUseQuery in the test file to update their setup and ensure the mock provides
the expected methods/shape the component uses.

In `@frontend/__tests__/a11y/pages/ProgramDetailsMentorship.a11y.test.tsx`:
- Line 2: The test imports mockProgramDetailsData as a default but the module
exports it as a named export; update the import in
ProgramDetailsMentorship.a11y.test.tsx to use a named import for
mockProgramDetailsData from the '@mockData/mockProgramData' module (i.e.,
replace the default-style import with a named import) so the test receives the
actual mockProgramDetailsData value.

In `@frontend/__tests__/a11y/pages/ProjectDetails.a11y.test.tsx`:
- Around line 8-11: The test mocks next/navigation with useParams returning the
wrong key; update the jest.mock call so useParams (in the mock defined for
next/navigation) returns an object with projectKey instead of programKey to
match the ProjectDetailsPage route (app/projects/[projectKey]/page) and ensure
any assertions referencing useParams or ProjectDetailsPage receive the correct
param name.

In `@frontend/__tests__/a11y/pages/ProjectsHealthDashboardOverview.a11y.test.tsx`:
- Line 48: The test description in the it(...) for the
ProjectsHealthDashboardOverview a11y test has a typo ("exits" → "exists");
update the test case string used in the it(...) call in the test function (the
spec that currently reads "should have no accessibility violations when no data
exits") to "should have no accessibility violations when no data exists" so the
description is correct.

In `@frontend/__tests__/a11y/pages/RepositoryDetails.a11y.test.tsx`:
- Around line 12-17: The test's next/navigation mock only returns repositoryKey
but the route also provides organizationKey; update the jest.mock for
'next/navigation' (the useParams mock) to include organizationKey alongside
repositoryKey so components reading organizationKey won't break tests; keep the
existing useRouter mock intact (useRouter, useParams) and ensure the returned
shape matches what the page expects.

In `@frontend/__tests__/a11y/pages/Users.a11y.test.tsx`:
- Around line 14-16: The test mock for fetchAlgoliaData currently returns only {
hits: mockUserData.users } but must match the function contract by also
returning totalPages; update the mockResolvedValue call for fetchAlgoliaData (in
Users.a11y.test.tsx) to return an object shaped like { hits: mockUserData.users,
totalPages: <number> } (e.g. derive totalPages from mockUserData or set to 1) so
the test mirrors the fetchAlgoliaData contract and surfaces any consumer logic
that reads totalPages.

In `@frontend/__tests__/unit/components/IssuesTable.test.tsx`:
- Around line 216-222: Tests in IssuesTable.test.tsx still query avatar alt text
(which was changed to alt="") causing false positives; update any assertions
that look for alt="user1" (including the mobile-view test that references the
assignee) to instead use visible username text or role-based queries (e.g.,
getByText('user1'), getAllByText('user1'), or
getByRole('img')/getByRole('presentation') within the correct container). Locate
the relevant tests referencing IssuesTable, mockIssues, or avatar alt attributes
and replace those alt-based expects with assertions that verify the username is
rendered and/or the avatar element exists using role/text queries.

In `@frontend/src/components/DonutBarChart.tsx`:
- Around line 25-53: The Chart lost its accessible container when the wrapper
div was removed; restore a wrapper element around the <Chart> (inside the
SecondaryCard in DonutBarChart) and give it role="img" and an aria-label (e.g.,
describing the donut like "Donut chart depicting health statuses: Healthy, Need
Attention, Unhealthy" or use the component's title prop) so screen readers can
announce the chart; ensure the wrapper is placed where the commented div used to
be (around Chart) and do not pass ARIA props directly to the ApexCharts <Chart>
component.
♻️ Duplicate comments (16)
frontend/__tests__/a11y/components/ProjectTypeDashboardCard.a11y.test.tsx (1)

1-2: Same global matcher wiring concern as StatusBadge.
Please ensure toHaveNoViolations is still registered via Jest setup for this a11y suite.

frontend/__tests__/a11y/components/MultiSearch.a11y.test.tsx (1)

1-2: Same global matcher wiring concern as StatusBadge.
Please ensure toHaveNoViolations is still registered via Jest setup for this a11y suite.

frontend/__tests__/a11y/components/SponsorCard.a11y.test.tsx (1)

1-2: Same global matcher wiring concern as StatusBadge.
Please ensure toHaveNoViolations is still registered via Jest setup for this a11y suite.

frontend/__tests__/a11y/components/MetricsScoreCircle.a11y.test.tsx (1)

1-2: Same global matcher wiring concern as StatusBadge.
Please ensure toHaveNoViolations is still registered via Jest setup for this a11y suite.

frontend/__tests__/a11y/components/MetricsCard.a11y.test.tsx (1)

2-43: Same matcher-registration concern as above.
See the note on ensuring toHaveNoViolations is registered in Jest setup.

frontend/__tests__/a11y/components/RecentPullRequests.a11y.test.tsx (1)

2-66: Same matcher-registration concern as above.
See the note on ensuring toHaveNoViolations is registered in Jest setup.

frontend/__tests__/a11y/components/RepositoryCard.a11y.test.tsx (1)

2-51: Same matcher-registration concern as above.
See the note on ensuring toHaveNoViolations is registered in Jest setup.

frontend/__tests__/a11y/components/Modal.a11y.test.tsx (1)

2-40: Same matcher-registration concern as above.
See the note on ensuring toHaveNoViolations is registered in Jest setup.

frontend/__tests__/a11y/components/CalendarButton.a11y.test.tsx (1)

2-34: Same matcher-registration concern as above.
See the note on ensuring toHaveNoViolations is registered in Jest setup.

frontend/__tests__/a11y/components/LogoCarousel.a11y.test.tsx (1)

1-3: Same verification note as BarChart.a11y.test.tsx (global matcher setup).

frontend/__tests__/a11y/components/SnapshotCard.a11y.test.tsx (1)

1-3: Same verification note as BarChart.a11y.test.tsx (global matcher setup).

frontend/__tests__/a11y/pages/Snapshots.a11y.test.tsx (1)

1-22: Same verification note as BarChart.a11y.test.tsx (global matcher setup).

frontend/__tests__/a11y/components/SecondaryCard.a11y.test.tsx (1)

1-3: Same verification note as BarChart.a11y.test.tsx (global matcher setup).

frontend/__tests__/a11y/components/ActionButton.a11y.test.tsx (1)

2-2: Verify global toHaveNoViolations matcher availability for this file.

frontend/__tests__/a11y/components/AnchorTitle.a11y.test.tsx (1)

2-2: Verify global toHaveNoViolations matcher availability for this file.

frontend/__tests__/a11y/components/BreadCrumbs.a11y.test.tsx (1)

3-3: Verify global toHaveNoViolations matcher availability for this file.

🧹 Nitpick comments (20)
frontend/__tests__/a11y/pages/MyMentorship.a11y.test.tsx (1)

34-46: Consider adding loading and error state tests for comprehensive a11y coverage.

The test covers the successfully-loaded state. However, loading: true and error: {...} states may render different UI elements (spinners, skeletons, error messages) that could have their own accessibility considerations.

♻️ Suggested addition for loading and error states
 describe('MyMentorshipPage', () => {
   it('should have no accessibility violations', async () => {
     ;(useQuery as unknown as jest.Mock).mockReturnValue({
       data: mockProgramData,
       loading: false,
       error: null,
     })

     const { container } = render(<MyMentorshipPage />)
     const results = await axe(container)
     expect(results).toHaveNoViolations()
   })
+
+  it('should have no accessibility violations while loading', async () => {
+    ;(useQuery as unknown as jest.Mock).mockReturnValue({
+      data: undefined,
+      loading: true,
+      error: null,
+    })
+
+    const { container } = render(<MyMentorshipPage />)
+    const results = await axe(container)
+    expect(results).toHaveNoViolations()
+  })
+
+  it('should have no accessibility violations on error', async () => {
+    ;(useQuery as unknown as jest.Mock).mockReturnValue({
+      data: undefined,
+      loading: false,
+      error: new Error('Test error'),
+    })
+
+    const { container } = render(<MyMentorshipPage />)
+    const results = await axe(container)
+    expect(results).toHaveNoViolations()
+  })
 })
frontend/__tests__/a11y/pages/CommitteeDetails.a11y.test.tsx (1)

12-64: Add mock cleanup to ensure test isolation.

While each test explicitly calls mockReturnValue, it's best practice to reset mocks between tests to prevent potential state leakage and ensure complete test isolation.

♻️ Suggested improvement

Add cleanup hooks to the test suite:

 describe('CommitteeDetailsPage Accessibility', () => {
   const mockUseQuery = useQuery as unknown as jest.Mock
+
+  beforeEach(() => {
+    jest.clearAllMocks()
+  })

   it('should have no accessibility violations', async () => {
frontend/__tests__/a11y/pages/MenteeProfilePage.a11y.test.tsx (3)

11-18: Consider providing a more complete useRouter mock.

useRouter: jest.fn() returns undefined, which may cause runtime errors if the component calls router methods like push() or back(). If the test passes as-is, the component likely doesn't use these methods, but providing a complete mock improves robustness for future changes.

💡 Suggested improvement
 jest.mock('next/navigation', () => ({
   useParams: jest.fn(() => ({
     programKey: 'prog1',
     moduleKey: 'mod1',
     menteeKey: 'test-mentee',
   })),
-  useRouter: jest.fn(),
+  useRouter: jest.fn(() => ({
+    push: jest.fn(),
+    back: jest.fn(),
+    replace: jest.fn(),
+  })),
 }))

96-105: Add null check before cloning children.

React.Children.map will include null or undefined children in iteration. The React.cloneElement call on line 100 will throw if child is not a valid React element.

🛠️ Suggested fix
           {isOpen && (
             <div id="select-popover" data-testid="select-popover" aria-label="Options">
-              {React.Children.map(children, (child: React.ReactElement) => {
+              {React.Children.map(children, (child) => {
+                if (!React.isValidElement(child)) return null
                 const itemKey = String(child.key ?? '')
                 return React.cloneElement(child, {
                   'data-key': itemKey,
                   onClick: () => handleItemClick(itemKey),
                 } as Partial<unknown>)
               })}
             </div>
           )}

173-189: Consider using afterEach and adding tests for loading/error states.

Two suggestions for improved test reliability:

  1. afterEach over afterAll: Currently only one test exists, but if more are added, mocks won't reset between tests, potentially causing flaky failures.

  2. Loading and error states may have different accessibility characteristics: Spinners, skeleton loaders, and error messages should also be free of violations.

💡 Suggested improvements
 describe('MenteeProfilePage Accessibility', () => {
-  afterAll(() => {
+  afterEach(() => {
     jest.clearAllMocks()
   })

   it('should have no accessibility violations', async () => {
     ;(useQuery as unknown as jest.Mock).mockReturnValue({
       data: mockMenteeData,
       loading: false,
       error: null,
     })

     const { container } = render(<MenteeProfilePage />)

     const results = await axe(container)
     expect(results).toHaveNoViolations()
   })
+
+  it('should have no accessibility violations while loading', async () => {
+    ;(useQuery as unknown as jest.Mock).mockReturnValue({
+      data: null,
+      loading: true,
+      error: null,
+    })
+
+    const { container } = render(<MenteeProfilePage />)
+
+    const results = await axe(container)
+    expect(results).toHaveNoViolations()
+  })
 })
frontend/__tests__/a11y/pages/CreateModule.a11y.test.tsx (1)

28-30: Prefer afterEach for mock cleanup to keep tests isolated.
Using afterAll can let mock state leak if additional tests are added later in this suite. Consider switching to afterEach for safer isolation.

♻️ Suggested tweak
-  afterAll(() => {
-    jest.clearAllMocks()
-  })
+  afterEach(() => {
+    jest.clearAllMocks()
+  })
frontend/__tests__/a11y/pages/UserDetails.a11y.test.tsx (1)

47-63: Consider adding test cleanup for consistency.

The test works correctly. For consistency with other a11y tests in this PR (e.g., Home.a11y.test.tsx), consider adding cleanup:

Optional: Add afterAll cleanup
 describe('UserDetailsPage Accessibility', () => {
+  afterAll(() => {
+    jest.clearAllMocks()
+  })
+
   it('should have no accessibility violations', async () => {
frontend/__tests__/a11y/pages/OrganizationDetails.a11y.test.tsx (1)

11-22: Consider adding waitFor before running axe for consistency.

Other page-level a11y tests in this PR (e.g., Home.a11y.test.tsx, UserDetails.a11y.test.tsx) use waitFor to ensure the component has fully rendered before running axe checks. While the synchronous mock may work here, adding waitFor improves test reliability and maintains consistency.

Suggested improvement
+import { render, waitFor, screen } from '@testing-library/react'
-import { render } from '@testing-library/react'
     const { container } = render(<OrganizationDetailsPage />)
+
+    await waitFor(() => {
+      expect(screen.getByText('Test Organization')).toBeInTheDocument()
+    })
+
     const results = await axe(container)
frontend/__tests__/a11y/pages/Program.a11y.test.tsx (1)

18-20: Consider including totalPages in the mock return value.

Based on the fetchAlgoliaData function signature in frontend/src/server/fetchAlgoliaData.ts, it returns { hits, totalPages }. While the component may handle the missing totalPages gracefully, including it makes the mock more accurate.

Suggested improvement
     ;(fetchAlgoliaData as jest.Mock).mockResolvedValue({
       hits: mockPrograms,
+      totalPages: 1,
     })
frontend/__tests__/a11y/pages/ProjectsHealthDashboardMetrics.a11y.test.tsx (2)

3-3: Inconsistent render import — consider using wrappers/testUtil.

Other accessibility tests in this PR import render from wrappers/testUtil, which likely provides necessary context providers. Using @testing-library/react directly may cause the component to render without required context, potentially affecting test reliability.

Suggested fix
-import { render } from '@testing-library/react'
+import { render } from 'wrappers/testUtil'

38-40: Consider waiting for content to load before running axe.

Other page-level accessibility tests in this PR use waitFor to ensure content is rendered before running accessibility checks. This helps avoid false positives/negatives from testing loading states.

Suggested pattern
+import { waitFor, screen } from '@testing-library/react'
...
     const { container } = render(<MetricsPage />)
+    await waitFor(() => {
+      expect(screen.getByText('Project One')).toBeInTheDocument()
+    })
     const results = await axe(container)
frontend/__tests__/a11y/pages/IssuesPage.a11y.test.tsx (2)

2-2: Inconsistent render import — consider using wrappers/testUtil.

For consistency with other accessibility tests and to ensure proper context providers are available, consider importing from wrappers/testUtil.

Suggested fix
-import { render } from '@testing-library/react'
+import { render } from 'wrappers/testUtil'

54-57: Consider waiting for content to load before running axe.

Other page-level tests wait for content to render before running accessibility checks. This ensures the test validates the fully-rendered page rather than a loading/intermediate state.

Suggested pattern
+import { waitFor, screen } from '@testing-library/react'
...
     const { container } = render(<IssuesPage />)
+    await waitFor(() => {
+      expect(screen.getByText('First Issue Title')).toBeInTheDocument()
+    })
     const results = await axe(container)
frontend/__tests__/a11y/pages/Chapters.a11y.test.tsx (1)

23-36: Consider adding loading and error state tests for consistency.

Other page tests (e.g., ModuleDetailsPage.a11y.test.tsx) include tests for loading and error states. For consistency and comprehensive coverage, consider adding similar test cases here.

💡 Example additional test cases
it('should have no accessibility violations when loading', async () => {
  ;(fetchAlgoliaData as jest.Mock).mockImplementation(() => new Promise(() => {}))

  const { container } = render(<ChaptersPage />)
  const results = await axe(container)
  expect(results).toHaveNoViolations()
})

it('should have no accessibility violations when error occurs', async () => {
  ;(fetchAlgoliaData as jest.Mock).mockRejectedValue(new Error('test error'))

  const { container } = render(<ChaptersPage />)
  const results = await axe(container)
  expect(results).toHaveNoViolations()
})
frontend/__tests__/a11y/pages/EditModule.a11y.test.tsx (1)

19-33: Consider adding loading and error state tests for comprehensive coverage.

The test currently only covers the success state. For consistency with other page tests like ModuleDetailsPage.a11y.test.tsx, consider adding tests for loading and error states.

frontend/__tests__/a11y/pages/ModuleDetailsPage.a11y.test.tsx (1)

3-3: Consider using the render wrapper from wrappers/testUtil for consistency.

Other page tests in this PR (e.g., Chapters.a11y.test.tsx, EditModule.a11y.test.tsx, ProgramDetails.a11y.test.tsx) import render from wrappers/testUtil, while this file imports directly from @testing-library/react. Using the project's wrapper ensures consistent test context (providers, etc.).

♻️ Suggested fix
-import { render } from '@testing-library/react'
+import { render } from 'wrappers/testUtil'
frontend/__tests__/a11y/pages/ProgramDetails.a11y.test.tsx (1)

18-34: Consider adding loading and error state tests for consistency.

For comprehensive accessibility coverage, consider adding tests for loading and error states similar to ModuleDetailsPage.a11y.test.tsx.

💡 Example additional test cases
it('should have no accessibility violations when loading', async () => {
  ;(useQuery as unknown as jest.Mock).mockReturnValue({
    data: null,
    loading: true,
    error: null,
  })

  const { container } = render(<ProgramDetailsPage />)
  const results = await axe(container)
  expect(results).toHaveNoViolations()
})

it('should have no accessibility violations when error occurs', async () => {
  ;(useQuery as unknown as jest.Mock).mockReturnValue({
    data: null,
    loading: false,
    error: new Error('test error'),
  })

  const { container } = render(<ProgramDetailsPage />)
  const results = await axe(container)
  expect(results).toHaveNoViolations()
})
frontend/__tests__/a11y/pages/ModuleIssueDetailsPage.a11y.test.tsx (1)

2-2: Inconsistent render import.

Other page-level a11y tests in this PR (e.g., Organization, Committees, ApiKeysPage) use render from wrappers/testUtil, which likely provides necessary providers. This test imports directly from @testing-library/react, which may cause issues if the component requires context providers.

Suggested fix
-import { render } from '@testing-library/react'
+import { render } from 'wrappers/testUtil'
frontend/__tests__/a11y/pages/ChapterDetails.a11y.test.tsx (1)

22-24: Reset the mocked useQuery between tests for isolation.

This mock is shared across multiple tests; resetting it avoids accidental leakage when tests are extended or reordered.

♻️ Suggested update
 describe('ChapterDetailsPage Accessibility', () => {
   const mockUseQuery = useQuery as unknown as jest.Mock
+
+  afterEach(() => {
+    mockUseQuery.mockReset()
+  })
frontend/__tests__/a11y/pages/About.a11y.test.tsx (1)

133-135: Reset the mocked useQuery between tests for isolation.

Keeping this mock clean avoids unintended cross-test coupling as new scenarios are added.

♻️ Suggested update
 describe('About Page Accessibility', () => {
   const mockUseQuery = useQuery as unknown as jest.Mock
+
+  afterEach(() => {
+    mockUseQuery.mockReset()
+  })

@Utkarsh-0304 Utkarsh-0304 marked this pull request as draft January 19, 2026 13:23
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@frontend/__tests__/a11y/pages/CommitteeDetails.a11y.test.tsx`:
- Around line 18-33: The mockCommitteeDetailsData object used by the
CommitteeDetailsPage test is missing numeric stats that the component renders;
update mockCommitteeDetailsData in the test mocks so the committee entry
includes contributorsCount, forksCount, starsCount, repositoriesCount, and
issuesCount with numeric values (e.g., integers) so CommitteeDetailsPage renders
the stats section fully during the a11y test; ensure the mock object shape
matches what CommitteeDetailsPage expects so useQuery in the test returns the
updated mockCommitteeDetailsData.

In `@frontend/__tests__/a11y/pages/Projects.a11y.test.tsx`:
- Line 1: The test currently uses a default import for mockProjectData which is
exported as a named export; update the import in Projects.a11y.test.tsx to use
the named export (reference symbol: mockProjectData) — i.e., replace the default
import with a named import for mockProjectData from '@mockData/mockProjectData'
(or alternatively add a default export to that module) so mockProjectData is
defined at runtime.
🧹 Nitpick comments (2)
frontend/__tests__/a11y/pages/Committees.a11y.test.tsx (1)

21-26: Consider moving mock setup into beforeEach or beforeAll.

Configuring the mock at the describe block's top-level scope works, but placing it in a setup hook is more idiomatic and improves test isolation if additional tests are added later.

♻️ Suggested refactor
 describe('CommitteesPage Accessibility', () => {
-  ;(fetchAlgoliaData as jest.Mock).mockResolvedValue({
-    hits: mockCommitteeData.committees,
-    totalPages: 1,
-  })
+  beforeEach(() => {
+    (fetchAlgoliaData as jest.Mock).mockResolvedValue({
+      hits: mockCommitteeData.committees,
+      totalPages: 1,
+    })
+  })

   it('should have no accessibility violations', async () => {
frontend/__tests__/a11y/pages/Program.a11y.test.tsx (1)

25-27: Prefer findByText for the async wait.

This is a bit cleaner and improves failure output.

♻️ Suggested tweak
-    await waitFor(() => {
-      expect(screen.getByText('Program 1')).toBeInTheDocument()
-    })
+    await screen.findByText('Program 1')

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@frontend/__tests__/a11y/pages/EditProgram.a11y.test.tsx`:
- Around line 7-16: The test fails to simulate an authenticated user because the
next-auth useSession hook isn't mocked; update the EditProgram.a11y.test.tsx
mocks to include jest.mock('next-auth/react', ...) and provide a useSession mock
that returns an authenticated session object (e.g., { data: { user: { email:
'test@example.com', name: 'Test' }, ... }, status: 'authenticated' }) and any
admin/roles needed by EditProgramPage to treat the user as a program
admin—mirror the mock shape used in CreateProgram.a11y.test.tsx so
EditProgramPage's useSession checks pass during the a11y test.
🧹 Nitpick comments (2)
frontend/__tests__/a11y/pages/EditProgram.a11y.test.tsx (2)

21-26: Consider including getProgramModules in the mock response.

The mockProgramDetailsData fixture includes both getProgram and getProgramModules, but only getProgram is provided here. If the component queries for modules as well, missing this field could cause rendering issues or incomplete accessibility testing.

Suggested fix
     ;(useQuery as unknown as jest.Mock).mockReturnValue({
       data: {
         getProgram: mockProgramDetailsData.getProgram,
+        getProgramModules: mockProgramDetailsData.getProgramModules,
       },
       loading: false,
     })

18-32: Consider adding accessibility tests for loading and error states.

The current test only covers the successfully loaded state. Testing additional UI states (loading spinner, error message) would improve accessibility coverage, as these states often have different DOM structures and ARIA attributes.

Example additional test cases
it('should have no accessibility violations when loading', async () => {
  ;(useMutation as unknown as jest.Mock).mockReturnValue([jest.fn(), { loading: false }])
  ;(useQuery as unknown as jest.Mock).mockReturnValue({
    data: null,
    loading: true,
  })

  const { container } = render(<EditProgramPage />)

  const results = await axe(container)
  expect(results).toHaveNoViolations()
})

it('should have no accessibility violations on error', async () => {
  ;(useMutation as unknown as jest.Mock).mockReturnValue([jest.fn(), { loading: false }])
  ;(useQuery as unknown as jest.Mock).mockReturnValue({
    data: null,
    loading: false,
    error: new Error('Failed to load'),
  })

  const { container } = render(<EditProgramPage />)

  const results = await axe(container)
  expect(results).toHaveNoViolations()
})

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 20, 2026
@Utkarsh-0304 Utkarsh-0304 marked this pull request as ready for review January 20, 2026 09:13
Copy link
Collaborator

@kasya kasya left a comment

Choose a reason for hiding this comment

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

This is great 🔥

Thank you @Utkarsh-0304 !

@kasya kasya enabled auto-merge January 23, 2026 04:22
@codecov
Copy link

codecov bot commented Jan 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 85.56%. Comparing base (c716b07) to head (a2ef2ec).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@           Coverage Diff           @@
##             main    #3420   +/-   ##
=======================================
  Coverage   85.56%   85.56%           
=======================================
  Files         461      461           
  Lines       14227    14227           
  Branches     1895     1895           
=======================================
  Hits        12174    12174           
  Misses       1680     1680           
  Partials      373      373           
Flag Coverage Δ
backend 84.47% <ø> (ø)
frontend 88.62% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
[...Key]/modules/[moduleKey]/issues/[issueId]/page.tsx](https://app.codecov.io/gh/OWASP/Nest/pull/3420?src=pr&el=tree&filepath=frontend%2Fsrc%2Fapp%2Fmy%2Fmentorship%2Fprograms%2F%5BprogramKey%5D%2Fmodules%2F%5BmoduleKey%5D%2Fissues%2F%5BissueId%5D%2Fpage.tsx&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=OWASP#diff-ZnJvbnRlbmQvc3JjL2FwcC9teS9tZW50b3JzaGlwL3Byb2dyYW1zL1twcm9ncmFtS2V5XS9tb2R1bGVzL1ttb2R1bGVLZXldL2lzc3Vlcy9baXNzdWVJZF0vcGFnZS50c3g=) 84.49% <ø> (ø)
frontend/src/components/CardDetailsPage.tsx 88.00% <ø> (ø)
frontend/src/components/IssuesTable.tsx 92.10% <ø> (ø)
frontend/src/components/ModuleForm.tsx 80.82% <ø> (ø)
frontend/src/components/ProgramCard.tsx 95.45% <ø> (ø)

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update c716b07...a2ef2ec. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Utkarsh-0304 Utkarsh-0304 requested a review from kasya January 23, 2026 04:58
@arkid15r arkid15r disabled auto-merge January 23, 2026 18:03
@arkid15r arkid15r enabled auto-merge January 23, 2026 20:03
@sonarqubecloud
Copy link

@arkid15r arkid15r added this pull request to the merge queue Jan 23, 2026
Merged via the queue into OWASP:main with commit c6c413c Jan 23, 2026
36 of 37 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add accessibility tests for pages

3 participants