diff --git a/.github/workflows/run-ci-cd.yaml b/.github/workflows/run-ci-cd.yaml index b3dfaccab6..b8295fa1af 100644 --- a/.github/workflows/run-ci-cd.yaml +++ b/.github/workflows/run-ci-cd.yaml @@ -271,6 +271,40 @@ jobs: docker run --env-file frontend/.env.example owasp/nest:test-frontend-e2e-latest pnpm run test:e2e timeout-minutes: 10 + run-frontend-a11y-tests: + name: Run frontend accessibility tests + needs: + - scan-code + - scan-ci-dependencies + permissions: + contents: read + runs-on: ubuntu-latest + steps: + - name: Check out repository + uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 + + - name: Set up Docker buildx + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f + + - name: Build frontend a11y-testing image + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 + with: + cache-from: | + type=gha + type=registry,ref=owasp/nest:test-frontend-a11y-cache + cache-to: | + type=gha,compression=zstd + context: frontend + file: docker/frontend/Dockerfile.a11y.test + load: true + platforms: linux/amd64 + tags: owasp/nest:test-frontend-a11y-latest + + - name: Run frontend a11y tests + run: | + docker run --env-file frontend/.env.example owasp/nest:test-frontend-a11y-latest pnpm run test:a11y + timeout-minutes: 10 + set-release-version: name: Set release version outputs: @@ -297,6 +331,7 @@ jobs: github.ref == 'refs/heads/main' needs: - run-backend-tests + - run-frontend-a11y-tests - run-frontend-e2e-tests - run-frontend-unit-tests - set-release-version @@ -662,6 +697,7 @@ jobs: github.event.action == 'published' needs: - run-backend-tests + - run-frontend-a11y-tests - run-frontend-e2e-tests - run-frontend-unit-tests - set-release-version diff --git a/docker/frontend/Dockerfile.a11y.test b/docker/frontend/Dockerfile.a11y.test new file mode 100644 index 0000000000..05c19451ce --- /dev/null +++ b/docker/frontend/Dockerfile.a11y.test @@ -0,0 +1,27 @@ +FROM node:24-alpine + +ENV FORCE_COLOR=1 \ + NPM_CACHE="/app/.npm" \ + PNPM_HOME="/pnpm" + +ENV NPM_CONFIG_RETRY=5 \ + NPM_CONFIG_TIMEOUT=30000 \ + PATH="$PNPM_HOME:$PATH" + +RUN --mount=type=cache,target=${NPM_CACHE} \ + npm install --ignore-scripts -g pnpm --cache ${NPM_CACHE} + +WORKDIR /app + +COPY --chmod=444 --chown=root:root package.json pnpm-lock.yaml ./ +RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ + pnpm install --frozen-lockfile --ignore-scripts && \ + chown node:node /app + +COPY __tests__/a11y __tests__/a11y +COPY __tests__/mockData __tests__/mockData +COPY .pnpmrc jest.config.ts jest.setup.ts tsconfig.json ./ +COPY public public +COPY src src + +USER node diff --git a/docker/frontend/Dockerfile.e2e.test b/docker/frontend/Dockerfile.e2e.test index 681ef18691..bd03a0130c 100644 --- a/docker/frontend/Dockerfile.e2e.test +++ b/docker/frontend/Dockerfile.e2e.test @@ -18,7 +18,7 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ pnpm install --frozen-lockfile --ignore-scripts COPY __tests__/e2e __tests__/e2e -COPY __tests__/unit/data __tests__/unit/data +COPY __tests__/mockData __tests__/mockData COPY .pnpmrc next.config.ts postcss.config.js playwright.config.ts tailwind.config.mjs tsconfig.json ./ COPY public public COPY src src diff --git a/docker/frontend/Dockerfile.unit.test b/docker/frontend/Dockerfile.unit.test index 7a0e19e2bd..b93d0f6aec 100644 --- a/docker/frontend/Dockerfile.unit.test +++ b/docker/frontend/Dockerfile.unit.test @@ -19,6 +19,7 @@ RUN --mount=type=cache,id=pnpm,target=/pnpm/store \ chown node:node /app COPY __tests__/unit __tests__/unit +COPY __tests__/mockData __tests__/mockData COPY .pnpmrc jest.config.ts jest.setup.ts tsconfig.json ./ COPY public public COPY src src diff --git a/frontend/Makefile b/frontend/Makefile index 468a16c1fb..c61cd6aed7 100644 --- a/frontend/Makefile +++ b/frontend/Makefile @@ -69,8 +69,16 @@ shell-frontend: test-frontend: \ test-frontend-unit \ + test-frontend-a11y \ test-frontend-e2e +test-frontend-a11y: + @DOCKER_BUILDKIT=1 NEXT_PUBLIC_ENVIRONMENT=local docker build \ + --cache-from nest-test-frontend-a11y \ + -f docker/frontend/Dockerfile.a11y.test frontend \ + -t nest-test-frontend-a11y + @docker run --env-file frontend/.env.example --rm nest-test-frontend-a11y pnpm run test:a11y + test-frontend-e2e: @DOCKER_BUILDKIT=1 NEXT_PUBLIC_ENVIRONMENT=local docker build \ --cache-from nest-test-frontend-e2e \ diff --git a/frontend/__tests__/a11y/components/ActionButton.a11y.test.tsx b/frontend/__tests__/a11y/components/ActionButton.a11y.test.tsx new file mode 100644 index 0000000000..802871689f --- /dev/null +++ b/frontend/__tests__/a11y/components/ActionButton.a11y.test.tsx @@ -0,0 +1,32 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import ActionButton from 'components/ActionButton' + +expect.extend(toHaveNoViolations) + +describe('ActionButton Accessibility', () => { + it('should not have any accessibility violations when no url is provided', async () => { + const { container } = render(Sample Text) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when url is provided', async () => { + const { container } = render(Visit Site) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when tooltipLabel is provided', async () => { + const { baseElement } = render( + Test Button + ) + const results = await axe(baseElement) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/AnchorTitle.a11y.test.tsx b/frontend/__tests__/a11y/components/AnchorTitle.a11y.test.tsx new file mode 100644 index 0000000000..fa1a1a201e --- /dev/null +++ b/frontend/__tests__/a11y/components/AnchorTitle.a11y.test.tsx @@ -0,0 +1,15 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import AnchorTitle from 'components/AnchorTitle' + +expect.extend(toHaveNoViolations) + +describe('AnchorTitle Accessibility', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/AutoScrollToTop.a11y.test.tsx b/frontend/__tests__/a11y/components/AutoScrollToTop.a11y.test.tsx new file mode 100644 index 0000000000..cb7418b321 --- /dev/null +++ b/frontend/__tests__/a11y/components/AutoScrollToTop.a11y.test.tsx @@ -0,0 +1,27 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import AutoScrollToTop from 'components/AutoScrollToTop' + +expect.extend(toHaveNoViolations) + +jest.mock('next/navigation', () => ({ + usePathname: () => '/test-path', +})) + +beforeAll(() => { + window.scrollTo = jest.fn() +}) + +afterAll(() => { + jest.clearAllMocks() +}) + +describe('AutoScrollToTop Accessibility', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/Badges.a11y.test.tsx b/frontend/__tests__/a11y/components/Badges.a11y.test.tsx new file mode 100644 index 0000000000..fb344b1770 --- /dev/null +++ b/frontend/__tests__/a11y/components/Badges.a11y.test.tsx @@ -0,0 +1,28 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import Badges from 'components/Badges' + +const defaultProps = { + name: 'Test Badge', + cssClass: 'medal', +} + +expect.extend(toHaveNoViolations) + +describe('Badges Accessibility', () => { + it('should not have any accessibility violations when tooltip is enabled', async () => { + const { baseElement } = render() + + const results = await axe(baseElement) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when tooltip is disabled', async () => { + const { baseElement } = render() + + const results = await axe(baseElement) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/BarChart.a11y.test.tsx b/frontend/__tests__/a11y/components/BarChart.a11y.test.tsx new file mode 100644 index 0000000000..5829397c99 --- /dev/null +++ b/frontend/__tests__/a11y/components/BarChart.a11y.test.tsx @@ -0,0 +1,83 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { ReactNode } from 'react' +import BarChart from 'components/BarChart' + +const mockProps = { + title: 'Calories Burned', + labels: ['Mon', 'Tue', 'Wed'], + days: [200, 150, 100], + requirements: [180, 170, 90], +} + +jest.mock('react-apexcharts', () => { + return function MockChart(props: { + options: unknown + series: unknown + height: number + type: string + }) { + const mockOptions = props.options as Record + + return ( + + ) + } +}) + +jest.mock('next/dynamic', () => { + return function mockDynamic() { + return jest.requireMock('react-apexcharts') + } +}) + +jest.mock('next-themes', () => ({ + ThemeProvider: ({ children, ...props }: { children: ReactNode; [key: string]: unknown }) => ( + {children} + ), + useTheme: () => ({ theme: 'light', setTheme: jest.fn() }), +})) + +jest.mock('components/AnchorTitle', () => { + return function MockAnchorTitle({ title }: { title: string }) { + return {title} + } +}) + +jest.mock('components/SecondaryCard', () => { + return function MockSecondaryCard({ + title, + icon, + children, + }: { + title: ReactNode + icon?: unknown + children: ReactNode + }) { + return ( + + {title} + {icon && icon} + {children} + + ) + } +}) + +expect.extend(toHaveNoViolations) + +describe('BarChart Accessibility', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/BreadCrumbs.a11y.test.tsx b/frontend/__tests__/a11y/components/BreadCrumbs.a11y.test.tsx new file mode 100644 index 0000000000..775a35a797 --- /dev/null +++ b/frontend/__tests__/a11y/components/BreadCrumbs.a11y.test.tsx @@ -0,0 +1,15 @@ +import { Breadcrumbs } from '@heroui/react' +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' + +expect.extend(toHaveNoViolations) + +describe('Breadcrumbs a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/BreadCrumbsWrapper.a11y.test.tsx b/frontend/__tests__/a11y/components/BreadCrumbsWrapper.a11y.test.tsx new file mode 100644 index 0000000000..03588a6b4d --- /dev/null +++ b/frontend/__tests__/a11y/components/BreadCrumbsWrapper.a11y.test.tsx @@ -0,0 +1,34 @@ +import { render } from '@testing-library/react' +import { useBreadcrumbs } from 'hooks/useBreadcrumbs' +import { axe, toHaveNoViolations } from 'jest-axe' +import { usePathname } from 'next/navigation' +import BreadCrumbsWrapper from 'components/BreadCrumbsWrapper' + +expect.extend(toHaveNoViolations) + +jest.mock('next/navigation', () => ({ + usePathname: jest.fn(), +})) + +jest.mock('hooks/useBreadcrumbs', () => ({ + useBreadcrumbs: jest.fn(), +})) + +describe('BreadcrumbsWrapper a11y', () => { + beforeAll(() => { + ;(usePathname as jest.Mock).mockReturnValue('/projects/test-project') + ;(useBreadcrumbs as jest.Mock).mockReturnValue([ + { title: 'Home', path: '/' }, + { title: 'Projects', path: '/projects' }, + { title: 'Test Project', path: '/projects/test-project' }, + ]) + }) + + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/CalendarButton.a11y.test.tsx b/frontend/__tests__/a11y/components/CalendarButton.a11y.test.tsx new file mode 100644 index 0000000000..54a7fa84a1 --- /dev/null +++ b/frontend/__tests__/a11y/components/CalendarButton.a11y.test.tsx @@ -0,0 +1,38 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { FaCalendarAlt } from 'react-icons/fa' +import CalendarButton from 'components/CalendarButton' + +expect.extend(toHaveNoViolations) + +const mockEvent = { + title: 'Test Event', + description: 'Test description', + location: 'Test Location', + startDate: '2025-12-01', + endDate: '2025-12-02', +} + +describe('CalendarButton Accessibility', () => { + it('should not have any accessibility violations as an icon-only button', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when showLabel is enabled', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when custom icon is provided', async () => { + const { container } = render(} />) + const results = await axe(container) + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/Card.a11y.test.tsx b/frontend/__tests__/a11y/components/Card.a11y.test.tsx new file mode 100644 index 0000000000..70bfb52418 --- /dev/null +++ b/frontend/__tests__/a11y/components/Card.a11y.test.tsx @@ -0,0 +1,83 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { ReactNode } from 'react' +import { FaCrown } from 'react-icons/fa6' +import Card from 'components/Card' + +interface MockLinkProps { + children: ReactNode + href: string + target?: string + rel?: string + className?: string +} + +jest.mock('components/MarkdownWrapper', () => ({ + __esModule: true, + default: ({ children }) => {children}, +})) + +jest.mock('next/link', () => { + return function MockedLink({ children, href, ...props }: MockLinkProps) { + return ( + + {children} + + ) + } +}) + +const baseProps = { + title: 'Test Project', + url: 'https://github.com/test/project', + summary: 'This is a test project summary', + button: { + label: 'View Project', + url: 'https://github.com/test', + icon: github, + onclick: jest.fn(), + }, +} + +expect.extend(toHaveNoViolations) + +describe('Card Accessibility', () => { + it('should not have any accessibility violations with minimal props', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when level is provided', async () => { + const { container } = render( + + ) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when project name is provided', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when socials is provided', async () => { + const { container } = render( + + ) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/CardDetailsPage.a11y.test.tsx b/frontend/__tests__/a11y/components/CardDetailsPage.a11y.test.tsx new file mode 100644 index 0000000000..b1e63f91ae --- /dev/null +++ b/frontend/__tests__/a11y/components/CardDetailsPage.a11y.test.tsx @@ -0,0 +1,213 @@ +import { mockChapterData } from '@mockData/mockChapterData' +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import React from 'react' +import { FaCode, FaTags } from 'react-icons/fa6' +import { ExperienceLevelEnum } from 'types/__generated__/graphql' +import { DetailsCardProps } from 'types/card' +import DetailsCard from 'components/CardDetailsPage' + +jest.mock('next/link', () => { + return function MockLink({ + children, + href, + ...props + }: { + children: React.ReactNode + href: string + [key: string]: unknown + }) { + return ( + + {children} + + ) + } +}) + +jest.mock('components/ChapterMapWrapper', () => ({ + __esModule: true, + default: ({ + geoLocData: _geoLocData, + showLocal, + style, + showLocationSharing: _showLocationSharing, + ...otherProps + }: { + geoLocData?: unknown + showLocal: boolean + style: React.CSSProperties + showLocationSharing?: boolean + [key: string]: unknown + }) => { + return ( + + Chapter Map {showLocal ? '(Local)' : ''} + + ) + }, +})) + +jest.mock('react-apexcharts', () => ({ + __esModule: true, + default: () => , +})) + +jest.mock('next/dynamic', () => ({ + __esModule: true, + default: () => { + return function MockDynamicComponent({ + children, + ...props + }: React.ComponentPropsWithoutRef<'div'>) { + return {children} + } + }, +})) + +jest.mock('next-auth/react', () => ({ + useSession: jest.fn(() => ({ data: null, status: 'unauthenticated' })), +})) + +const mockHealthMetricsData = [ + { + ageDays: 365, + ageDaysRequirement: 365, + id: 'test-id', + createdAt: '2023-01-01', + contributorsCount: 10, + forksCount: 5, + isFundingRequirementsCompliant: true, + isLeaderRequirementsCompliant: true, + lastCommitDays: 1, + lastCommitDaysRequirement: 30, + lastPullRequestDays: 2, + lastPullRequestDaysRequirement: 30, + lastReleaseDays: 10, + lastReleaseDaysRequirement: 90, + openIssuesCount: 5, + openPullRequestsCount: 3, + owaspPageLastUpdateDays: 30, + owaspPageLastUpdateDaysRequirement: 90, + projectName: 'Test Project', + projectKey: 'test-project', + recentReleasesCount: 2, + score: 85, + starsCount: 100, + totalIssuesCount: 20, + totalReleasesCount: 5, + unassignedIssuesCount: 2, + unansweredIssuesCount: 1, + }, +] + +const mockStats = [ + { + icon: FaCode, + pluralizedName: 'repositories', + unit: '', + value: 10, + }, + { + icon: FaTags, + pluralizedName: 'stars', + unit: '', + value: 100, + }, +] + +const mockDetails = [ + { label: 'Created', value: '2023-01-01' }, + { label: 'Leaders', value: 'John Doe, Jane Smith' }, + { label: 'Status', value: 'Active' }, +] + +const defaultProps: DetailsCardProps = { + title: 'Test Project', + description: 'A test project for demonstration', + type: 'project', + details: mockDetails, + stats: mockStats, + isActive: true, + showAvatar: true, + languages: ['JavaScript', 'TypeScript'], + topics: ['web', 'frontend'], + repositories: [], + recentIssues: [], + recentMilestones: [], + recentReleases: [], + pullRequests: [], + topContributors: [], + healthMetricsData: mockHealthMetricsData, + socialLinks: [], +} + +expect.extend(toHaveNoViolations) + +describe('CardDetailsPage a11y', () => { + it('should have no accessibility violations', async () => { + const { container } = render() + const results = await axe(container) + expect(results).toHaveNoViolations() + }) + + it('should have no violations for chapter type', async () => { + const { container } = render( + + ) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should have no violations for program type', async () => { + const { container } = render( + + ) + const results = await axe(container) + expect(results).toHaveNoViolations() + }) + + it('should have no violations in archived state', async () => { + let container: HTMLElement + + await React.act(async () => { + const renderResult = render( + + ) + container = renderResult.container + }) + + const results = await axe(container) + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/ChapterMap.a11y.test.tsx b/frontend/__tests__/a11y/components/ChapterMap.a11y.test.tsx new file mode 100644 index 0000000000..adce4d0b7b --- /dev/null +++ b/frontend/__tests__/a11y/components/ChapterMap.a11y.test.tsx @@ -0,0 +1,170 @@ +import { mockChapterData } from '@mockData/mockChapterData' +import { screen, fireEvent, render, waitFor } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import * as L from 'leaflet' +import React, { useEffect } from 'react' +import ChapterMap from 'components/ChapterMap' + +expect.extend(toHaveNoViolations) + +const mockMap = { + setView: jest.fn().mockReturnThis(), + fitBounds: jest.fn().mockReturnThis(), + scrollWheelZoom: { + enable: jest.fn(), + disable: jest.fn(), + }, +} + +const mockZoomControl = { + addTo: jest.fn().mockReturnThis(), + remove: jest.fn(), +} + +/* eslint-disable @typescript-eslint/naming-convention */ +jest.mock('leaflet', () => ({ + map: jest.fn(() => mockMap), + tileLayer: jest.fn(() => ({ + addTo: jest.fn().mockReturnThis(), + })), + marker: jest.fn(() => ({ + bindPopup: jest.fn().mockReturnThis(), + })), + popup: jest.fn(() => ({ + setContent: jest.fn().mockReturnThis(), + })), + latLngBounds: jest.fn(() => ({})), + Icon: jest.fn(() => ({})), + divIcon: jest.fn(() => ({})), + control: { + zoom: jest.fn(() => mockZoomControl), + }, +})) +/* eslint-enable @typescript-eslint/naming-convention */ + +// Mock CSS imports +jest.mock('leaflet/dist/leaflet.css', () => ({})) +jest.mock('leaflet.markercluster/dist/MarkerCluster.css', () => ({})) +jest.mock('leaflet.markercluster/dist/MarkerCluster.Default.css', () => ({})) +jest.mock('leaflet.markercluster', () => ({})) + +// Mock react-leaflet +jest.mock('react-leaflet', () => ({ + MapContainer: ({ + children, + center, + zoom, + scrollWheelZoom, + style, + zoomControl, + maxBounds, + maxBoundsViscosity, + className, + }: { + children: React.ReactNode + center: L.LatLngExpression + zoom: number + scrollWheelZoom: boolean + style: React.CSSProperties + zoomControl: boolean + maxBounds: L.LatLngBoundsExpression + maxBoundsViscosity: number + className: string + }) => { + useEffect(() => { + L.map('chapter-map', { + worldCopyJump: false, + maxBounds, + maxBoundsViscosity, + scrollWheelZoom, + zoomControl, + }).setView(center, zoom) + }, [center, zoom, scrollWheelZoom, zoomControl, maxBounds, maxBoundsViscosity]) + return ( + + {children} + + ) + }, + TileLayer: ({ + attribution, + url, + className, + }: { + attribution: string + url: string + className: string + }) => { + useEffect(() => { + L.tileLayer(url, { attribution, className }).addTo(mockMap as unknown as L.Map) + }, [url, attribution, className]) + return null + }, + Marker: ({ + children, + position, + icon, + }: { + children: React.ReactNode + position: L.LatLngExpression + icon: L.Icon + }) => { + useEffect(() => { + const marker = L.marker(position, { icon }) + marker.bindPopup(L.popup().setContent('mock content')) + }, [position, icon]) + return {children} + }, + Popup: ({ children }: { children: React.ReactNode }) => {children}, + useMap: () => mockMap as unknown as L.Map, +})) + +// Mock react-leaflet-cluster +jest.mock('react-leaflet-cluster', () => ({ + __esModule: true, + default: ({ children }: { children: React.ReactNode }) => ( + {children} + ), +})) + +const defaultProps = { + geoLocData: mockChapterData.chapters, + showLocal: false, + style: { width: '100%', height: '400px' }, +} + +describe('ChapterMap a11y', () => { + it('should not have any accessibility violations in locked state', async () => { + const { baseElement } = render() + + const results = await axe(baseElement) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when map is unlocked', async () => { + const { baseElement } = render() + + const unlockButton = screen.getByLabelText('Unlock map') + fireEvent.click(unlockButton) + + await waitFor(() => expect(screen.getByLabelText(/Share location/i)).toBeInTheDocument()) + + const results = await axe(baseElement) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when user location is shared', async () => { + const { baseElement } = render( + + ) + + const results = await axe(baseElement) + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/ContributionHeatmap.a11y.test.tsx b/frontend/__tests__/a11y/components/ContributionHeatmap.a11y.test.tsx new file mode 100644 index 0000000000..f1e7cc1bf7 --- /dev/null +++ b/frontend/__tests__/a11y/components/ContributionHeatmap.a11y.test.tsx @@ -0,0 +1,94 @@ +import { render, screen } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { ReactNode } from 'react' +import ContributionHeatmap from 'components/ContributionHeatmap' + +expect.extend(toHaveNoViolations) + +jest.mock('react-apexcharts', () => { + return function MockChart(props: { + options: unknown + series: unknown + height: string | number + type: string + }) { + const mockSeries = props.series as Array<{ + name: string + data: Array<{ x: string; y: number; date: string }> + }> + const mockOptions = props.options as Record + + if (mockOptions.tooltip && typeof mockOptions.tooltip === 'object') { + const tooltip = mockOptions.tooltip as { custom?: (...args: unknown[]) => unknown } + if (tooltip.custom) { + if (mockSeries[0]?.data.length > 0) { + tooltip.custom({ + seriesIndex: 0, + dataPointIndex: 0, + w: { config: { series: mockSeries } }, + }) + } + tooltip.custom({ + seriesIndex: 0, + dataPointIndex: 999, + w: { config: { series: mockSeries } }, + }) + } + } + + return ( + + {mockSeries.map((series) => ( + + {series.name}: {series.data.length} data points + + ))} + + ) + } +}) + +jest.mock('next-themes', () => ({ + useTheme: () => ({ theme: 'light', setTheme: jest.fn() }), + ThemeProvider: ({ children }: { children: ReactNode }) => {children}, +})) + +const mockData: Record = { + '2024-01-01': 5, + '2024-01-02': 8, + '2024-01-03': 12, + '2024-01-04': 15, + '2024-01-05': 0, + '2024-01-08': 3, + '2024-01-15': 20, +} +const defaultProps = { + contributionData: mockData, + startDate: '2024-01-01', + endDate: '2024-01-31', +} + +describe('ContributionHeatmap Accessibility', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + await screen.findByTestId('mock-heatmap-chart') + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when title is provided', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/ContributorAvatar.a11y.test.tsx b/frontend/__tests__/a11y/components/ContributorAvatar.a11y.test.tsx new file mode 100644 index 0000000000..b6fa9d9e8f --- /dev/null +++ b/frontend/__tests__/a11y/components/ContributorAvatar.a11y.test.tsx @@ -0,0 +1,57 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { ReactNode } from 'react' +import { Contributor } from 'types/contributor' +import ContributorAvatar from 'components/ContributorAvatar' + +expect.extend(toHaveNoViolations) + +jest.mock('@heroui/tooltip', () => ({ + Tooltip: ({ children, content, id }: { children: ReactNode; content: string; id: string }) => ( + + {children} + + {content} + + + ), +})) + +jest.mock('next/link', () => { + return ({ + children, + href, + target, + rel, + }: { + children: ReactNode + href: string + target?: string + rel?: string + }) => ( + + {children} + + ) +}) + +const mockGitHubContributor: Contributor = { + login: 'jane-doe', + name: 'Jane Doe', + avatarUrl: 'https://avatars.githubusercontent.com/u/12345', + contributionsCount: 15, + projectName: 'OWASP-Nest', + projectKey: 'test-key', +} + +describe('ContributorAvatar a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render( + + ) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/DashboardCard.a11y.test.tsx b/frontend/__tests__/a11y/components/DashboardCard.a11y.test.tsx new file mode 100644 index 0000000000..3f97aa505c --- /dev/null +++ b/frontend/__tests__/a11y/components/DashboardCard.a11y.test.tsx @@ -0,0 +1,23 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { FaUser } from 'react-icons/fa' +import DashboardCard from 'components/DashboardCard' + +expect.extend(toHaveNoViolations) + +const baseProps = { + title: 'Test Card', + icon: FaUser, + className: undefined, + stats: undefined, +} + +describe('DashboardCard a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/DisplayIcon.a11y.test.tsx b/frontend/__tests__/a11y/components/DisplayIcon.a11y.test.tsx new file mode 100644 index 0000000000..0376309147 --- /dev/null +++ b/frontend/__tests__/a11y/components/DisplayIcon.a11y.test.tsx @@ -0,0 +1,25 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { Icon } from 'types/icon' +import DisplayIcon from 'components/DisplayIcon' + +expect.extend(toHaveNoViolations) + +const mockIcons: Icon = { + starsCount: 1250, + forksCount: 350, + contributorsCount: 25, + contributionCount: 25, + issuesCount: 42, + license: 'MIT', +} + +describe('DisplayIcon a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/DonutBarChart.a11y.test.tsx b/frontend/__tests__/a11y/components/DonutBarChart.a11y.test.tsx new file mode 100644 index 0000000000..913858fb4c --- /dev/null +++ b/frontend/__tests__/a11y/components/DonutBarChart.a11y.test.tsx @@ -0,0 +1,38 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { FaChartPie } from 'react-icons/fa' +import DonutBarChart from 'components/DonutBarChart' + +expect.extend(toHaveNoViolations) + +jest.mock('next/dynamic', () => { + return jest.fn(() => { + // Mock Chart component that mimics react-apexcharts + const MockChart = ({ options, series, height, type, ...props }) => ( + + ApexCharts Mock + + ) + MockChart.displayName = 'Chart' + return MockChart + }) +}) + +describe('DonutBarChart a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render( + + ) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/EntityActions.a11y.test.tsx b/frontend/__tests__/a11y/components/EntityActions.a11y.test.tsx new file mode 100644 index 0000000000..ee85711ccf --- /dev/null +++ b/frontend/__tests__/a11y/components/EntityActions.a11y.test.tsx @@ -0,0 +1,44 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import EntityActions from 'components/EntityActions' + +expect.extend(toHaveNoViolations) + +describe('EntityActions a11y', () => { + it('should not have any accessibility violations', async () => { + const setStatus = jest.fn() + + const { container } = render( + + ) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when dropDown is open', async () => { + const setStatus = jest.fn() + + const { container } = render( + + ) + + const toggleButton = screen.getByTestId('program-actions-button') + fireEvent.click(toggleButton) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/Footer.a11y.test.tsx b/frontend/__tests__/a11y/components/Footer.a11y.test.tsx new file mode 100644 index 0000000000..48443c4ab5 --- /dev/null +++ b/frontend/__tests__/a11y/components/Footer.a11y.test.tsx @@ -0,0 +1,26 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import Footer from 'components/Footer' + +expect.extend(toHaveNoViolations) + +describe('Footer a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when section is opened', async () => { + const { container } = render() + + const button = screen.getByTestId('footer-section-button-Resources') + fireEvent.click(button) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/GeneralCompliantComponent.a11y.test.tsx b/frontend/__tests__/a11y/components/GeneralCompliantComponent.a11y.test.tsx new file mode 100644 index 0000000000..6e806cccf1 --- /dev/null +++ b/frontend/__tests__/a11y/components/GeneralCompliantComponent.a11y.test.tsx @@ -0,0 +1,29 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { IconType } from 'react-icons' +import { FaCertificate } from 'react-icons/fa6' +import GeneralCompliantComponent from 'components/GeneralCompliantComponent' + +expect.extend(toHaveNoViolations) + +type GeneralCompliantComponentProps = { + compliant: boolean + icon: IconType + title: string +} + +const baseProps: GeneralCompliantComponentProps = { + compliant: true, + icon: FaCertificate, + title: 'Test Title', +} + +describe('GeneralCompliantComponent Accessibility', () => { + it('should not have any accessibility violations', async () => { + const { baseElement } = render() + + const results = await axe(baseElement) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/HealthMetrics.a11y.test.tsx b/frontend/__tests__/a11y/components/HealthMetrics.a11y.test.tsx new file mode 100644 index 0000000000..b730d0d9b2 --- /dev/null +++ b/frontend/__tests__/a11y/components/HealthMetrics.a11y.test.tsx @@ -0,0 +1,66 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { IconType } from 'react-icons' +import { HealthMetricsProps } from 'types/healthMetrics' +import HealthMetrics from 'components/HealthMetrics' + +expect.extend(toHaveNoViolations) + +jest.mock('components/BarChart', () => (props: { title: string; icon?: IconType }) => ( + + {props.title} + +)) +jest.mock('components/LineChart', () => (props: { title: string }) => ( + + {props.title} + +)) + +const getMockHealthMetric = (): HealthMetricsProps[] => [ + { + createdAt: '2025-07-23T00:00:00Z', + openIssuesCount: 12, + unassignedIssuesCount: 4, + unansweredIssuesCount: 2, + openPullRequestsCount: 3, + starsCount: 45, + forksCount: 5, + lastCommitDays: 1, + lastReleaseDays: 7, + lastCommitDaysRequirement: 10, + lastReleaseDaysRequirement: 14, + ageDays: 730, + ageDaysRequirement: 365, + contributorsCount: 8, + isFundingRequirementsCompliant: true, + isLeaderRequirementsCompliant: true, + lastPullRequestDays: 5, + lastPullRequestDaysRequirement: 30, + owaspPageLastUpdateDays: 20, + owaspPageLastUpdateDaysRequirement: 90, + projectName: 'nest', + recentReleasesCount: 5, + score: 85, + totalIssuesCount: 100, + totalReleasesCount: 15, + id: '123', + projectKey: 'owasp', + }, +] + +describe('HealthMetrics a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/InfoBlock.a11y.test.tsx b/frontend/__tests__/a11y/components/InfoBlock.a11y.test.tsx new file mode 100644 index 0000000000..5ed08dae0b --- /dev/null +++ b/frontend/__tests__/a11y/components/InfoBlock.a11y.test.tsx @@ -0,0 +1,33 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { FaStar } from 'react-icons/fa6' +import InfoBlock from 'components/InfoBlock' + +expect.extend(toHaveNoViolations) + +const baseProps = { + icon: FaStar, + value: 1500, + unit: 'contributor', + pluralizedName: 'contributors', + precision: 2, + className: 'custom-class', +} + +describe('InfoBlock a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when label is provided', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/InfoItem.a11y.test.tsx b/frontend/__tests__/a11y/components/InfoItem.a11y.test.tsx new file mode 100644 index 0000000000..9c2a01593b --- /dev/null +++ b/frontend/__tests__/a11y/components/InfoItem.a11y.test.tsx @@ -0,0 +1,16 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { FaUser } from 'react-icons/fa6' +import InfoItem from 'components/InfoItem' + +expect.extend(toHaveNoViolations) + +describe('InfoItem a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/ItemCardList.a11y.test.tsx b/frontend/__tests__/a11y/components/ItemCardList.a11y.test.tsx new file mode 100644 index 0000000000..cc14eefd45 --- /dev/null +++ b/frontend/__tests__/a11y/components/ItemCardList.a11y.test.tsx @@ -0,0 +1,80 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { ReactNode } from 'react' +import { Issue } from 'types/issue' +import ItemCardList from 'components/ItemCardList' + +expect.extend(toHaveNoViolations) + +jest.mock('next/link', () => ({ + __esModule: true, + default: ({ + children, + href, + target, + className, + }: { + children: ReactNode + href: string + target?: string + className?: string + }) => ( + + {children} + + ), +})) + +const mockUser = { + avatarUrl: 'https://github.com/author1.png', + contributionsCount: 50, + createdAt: 1640995200000, + followersCount: 100, + followingCount: 50, + key: 'author1', + login: 'author1', + name: 'Author One', + publicRepositoriesCount: 25, + url: 'https://github.com/author1', +} + +const mockIssue: Issue = { + author: mockUser, + createdAt: 1640995200000, + hint: 'Good first issue', + labels: ['bug', 'help-wanted'], + number: '123', + organizationName: 'test-org', + projectName: 'Test Project', + projectUrl: 'https://github.com/test-org/test-project', + summary: 'This is a test issue summary', + title: 'Test Issue Title', + updatedAt: 1641081600000, + url: 'https://github.com/test-org/test-project/issues/123', + objectID: 'issue-123', +} + +const defaultProps = { + title: 'Test Title', + data: [mockIssue], + renderDetails: jest.fn((item) => ( + + Created: {item.createdAt} + Org: {item.organizationName} + + )), +} + +describe('ItemCardList a11y', () => { + it('should not have any accessibility violations', async () => { + const { baseElement } = render( + + + + ) + + const results = await axe(baseElement) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/Leaders.a11y.test.tsx b/frontend/__tests__/a11y/components/Leaders.a11y.test.tsx new file mode 100644 index 0000000000..80b74aadaf --- /dev/null +++ b/frontend/__tests__/a11y/components/Leaders.a11y.test.tsx @@ -0,0 +1,16 @@ +import { mockProjectDetailsData } from '@mockData/mockProjectDetailsData' +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import Leaders from 'components/Leaders' + +expect.extend(toHaveNoViolations) + +describe('Leaders a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/LeadersList.a11y.test.tsx b/frontend/__tests__/a11y/components/LeadersList.a11y.test.tsx new file mode 100644 index 0000000000..5fd2b3a8b8 --- /dev/null +++ b/frontend/__tests__/a11y/components/LeadersList.a11y.test.tsx @@ -0,0 +1,44 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { ReactNode } from 'react' +import LeadersList from 'components/LeadersList' + +expect.extend(toHaveNoViolations) + +jest.mock('next/link', () => { + return function MockLink({ + children, + href, + 'aria-label': ariaLabel, + className, + title, + }: { + children: ReactNode + href: string + 'aria-label'?: string + className?: string + title?: string + }) { + return ( + + {children} + + ) + } +}) + +describe('LeadersList a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/LineChart.a11y.test.tsx b/frontend/__tests__/a11y/components/LineChart.a11y.test.tsx new file mode 100644 index 0000000000..4d71cc99b1 --- /dev/null +++ b/frontend/__tests__/a11y/components/LineChart.a11y.test.tsx @@ -0,0 +1,52 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { ApexLineChartSeries } from 'types/healthMetrics' +import LineChart from 'components/LineChart' + +expect.extend(toHaveNoViolations) + +jest.mock('next/dynamic', () => { + return jest.fn().mockImplementation(() => { + const MockChart = ({ options, series, height }) => { + return ( + + ) + } + return MockChart + }) +}) + +const defaultProps = { + title: 'Test Chart', + series: [ + { + name: 'Test Series', + data: [10, 20, 30, 40, 50], + }, + ] as ApexLineChartSeries[], +} + +describe('LineChart a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/LoadingSpinner.a11y.test.tsx b/frontend/__tests__/a11y/components/LoadingSpinner.a11y.test.tsx new file mode 100644 index 0000000000..b79a2adb79 --- /dev/null +++ b/frontend/__tests__/a11y/components/LoadingSpinner.a11y.test.tsx @@ -0,0 +1,15 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import LoadingSpinner from 'components/LoadingSpinner' + +expect.extend(toHaveNoViolations) + +describe('LoadingSpinner a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/LoginPageContent.a11y.test.tsx b/frontend/__tests__/a11y/components/LoginPageContent.a11y.test.tsx new file mode 100644 index 0000000000..d7ebbb1d22 --- /dev/null +++ b/frontend/__tests__/a11y/components/LoginPageContent.a11y.test.tsx @@ -0,0 +1,68 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { useSession } from 'next-auth/react' +import { userAuthStatus } from 'utils/constants' +import LoginPageContent from 'components/LoginPageContent' + +expect.extend(toHaveNoViolations) + +// mock dependency +jest.mock('next-auth/react', () => ({ + useSession: jest.fn(), + signIn: jest.fn(), +})) + +describe('LoginPage a11y', () => { + const mockUseSession = useSession as jest.MockedFunction + + describe('when GitHub auth is enabled', () => { + it('should not have any accessibility violations', async () => { + mockUseSession.mockReturnValue({ + status: userAuthStatus.UNAUTHENTICATED as 'unauthenticated', + data: null, + update: jest.fn(), + }) + + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + }) + + describe('when GitHub auth is disabled', () => { + it('should not have any accessibility violations when loading', async () => { + mockUseSession.mockReturnValue({ + status: userAuthStatus.LOADING as 'loading', + data: null, + update: jest.fn(), + }) + const { container } = render() + const results = await axe(container) + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when authenticated', async () => { + mockUseSession.mockReturnValue({ + status: userAuthStatus.AUTHENTICATED as 'authenticated', + data: null, + update: jest.fn(), + }) + const { container } = render() + const results = await axe(container) + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when unauthenticated', async () => { + mockUseSession.mockReturnValue({ + status: userAuthStatus.UNAUTHENTICATED as 'unauthenticated', + data: null, + update: jest.fn(), + }) + const { container } = render() + const results = await axe(container) + expect(results).toHaveNoViolations() + }) + }) +}) diff --git a/frontend/__tests__/a11y/components/LogoCarousel.a11y.test.tsx b/frontend/__tests__/a11y/components/LogoCarousel.a11y.test.tsx new file mode 100644 index 0000000000..a278123818 --- /dev/null +++ b/frontend/__tests__/a11y/components/LogoCarousel.a11y.test.tsx @@ -0,0 +1,60 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import React from 'react' +import { Sponsor } from 'types/home' +import LogoCarousel from 'components/LogoCarousel' + +expect.extend(toHaveNoViolations) + +jest.mock('next/link', () => { + return function MockLink({ + href, + children, + target, + rel, + className, + }: { + href: string + children: React.ReactNode + target?: string + rel?: string + className?: string + }) { + return ( + + {children} + + ) + } +}) + +const mockSponsors: Sponsor[] = [ + { + name: 'Test Sponsor 1', + imageUrl: 'https://example.com/logo1.png', + url: 'https://sponsor1.com', + sponsorType: 'Gold', + }, + { + name: 'Test Sponsor 2', + imageUrl: 'https://example.com/logo2.png', + url: 'https://sponsor2.com', + sponsorType: 'Silver', + }, + { + name: 'Test Sponsor 3', + imageUrl: '', + url: 'https://sponsor3.com', + sponsorType: 'Bronze', + }, +] + +describe('LogoCarousel a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/MarkdownWrapper.a11y.test.tsx b/frontend/__tests__/a11y/components/MarkdownWrapper.a11y.test.tsx new file mode 100644 index 0000000000..167b5d6fc8 --- /dev/null +++ b/frontend/__tests__/a11y/components/MarkdownWrapper.a11y.test.tsx @@ -0,0 +1,25 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import MarkdownWrapper from 'components/MarkdownWrapper' + +expect.extend(toHaveNoViolations) + +jest.mock('markdown-it', () => { + return jest.fn().mockImplementation(() => ({ + render: (content: string) => { + // Very simple mock: replace **bold** + return content.replaceAll(/\*\*(.*?)\*\*/g, '$1') + }, + use: jest.fn().mockReturnThis(), + })) +}) + +describe('MarkdownWrapper a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/MetricsCard.a11y.test.tsx b/frontend/__tests__/a11y/components/MetricsCard.a11y.test.tsx new file mode 100644 index 0000000000..afdc61c640 --- /dev/null +++ b/frontend/__tests__/a11y/components/MetricsCard.a11y.test.tsx @@ -0,0 +1,47 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { HealthMetricsProps } from 'types/healthMetrics' +import MetricsCard from 'components/MetricsCard' + +expect.extend(toHaveNoViolations) + +const makeMetric = (overrides: Partial = {}): HealthMetricsProps => ({ + projectKey: 'test-project', + projectName: 'Test Project', + starsCount: 42, + forksCount: 13, + contributorsCount: 5, + createdAt: '2023-03-25T12:00:00Z', + score: 80, + id: 'id-123', + ageDays: 500, + ageDaysRequirement: 365, + isFundingRequirementsCompliant: true, + isLeaderRequirementsCompliant: true, + openIssuesCount: 0, + unassignedIssuesCount: 0, + unansweredIssuesCount: 0, + openPullRequestsCount: 0, + lastCommitDays: 0, + lastReleaseDays: 0, + lastCommitDaysRequirement: 0, + lastReleaseDaysRequirement: 0, + lastPullRequestDays: 0, + lastPullRequestDaysRequirement: 0, + owaspPageLastUpdateDays: 0, + owaspPageLastUpdateDaysRequirement: 0, + recentReleasesCount: 0, + totalIssuesCount: 0, + totalReleasesCount: 0, + ...overrides, +}) + +describe('MetricsCard a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/MetricsScoreCircle.a11y.test.tsx b/frontend/__tests__/a11y/components/MetricsScoreCircle.a11y.test.tsx new file mode 100644 index 0000000000..2317362f96 --- /dev/null +++ b/frontend/__tests__/a11y/components/MetricsScoreCircle.a11y.test.tsx @@ -0,0 +1,23 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import MetricsScoreCircle from 'components/MetricsScoreCircle' + +expect.extend(toHaveNoViolations) + +describe('MetricsScoreCircle a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when clickable', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/Milestones.a11y.test.tsx b/frontend/__tests__/a11y/components/Milestones.a11y.test.tsx new file mode 100644 index 0000000000..231b03b492 --- /dev/null +++ b/frontend/__tests__/a11y/components/Milestones.a11y.test.tsx @@ -0,0 +1,51 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { Milestone } from 'types/milestone' +import { User } from 'types/user' +import Milestones from 'components/Milestones' + +expect.extend(toHaveNoViolations) + +jest.mock('next/navigation', () => ({ + ...jest.requireActual('next/navigation'), + useRouter: jest.fn(), +})) + +const createMockUser = (): User => ({ + login: 'testuser', + name: 'Test User', + avatarUrl: 'https://github.com/testuser.png', + url: 'https://github.com/testuser', + key: 'testuser', + followersCount: 10, + followingCount: 5, + publicRepositoriesCount: 20, + contributionsCount: 50, + createdAt: 1640995200000, + updatedAt: 1640995200000, +}) + +const createMockMilestone = (overrides: Partial = {}): Milestone => ({ + author: createMockUser(), + body: 'Test milestone description', + closedIssuesCount: 5, + createdAt: '2023-01-01T00:00:00Z', + openIssuesCount: 3, + organizationName: 'test-org', + progress: 75, + repositoryName: 'test-repo', + state: 'open', + title: 'Test Milestone', + url: 'https://github.com/test-org/test-repo/milestone/1', + ...overrides, +}) + +describe('Milestones a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/Modal.a11y.test.tsx b/frontend/__tests__/a11y/components/Modal.a11y.test.tsx new file mode 100644 index 0000000000..14b362ecf9 --- /dev/null +++ b/frontend/__tests__/a11y/components/Modal.a11y.test.tsx @@ -0,0 +1,44 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { FaCheck } from 'react-icons/fa6' +import Modal from 'components/Modal' + +expect.extend(toHaveNoViolations) + +jest.mock('@/components/MarkdownWrapper', () => { + return ({ content, className }: { content: string; className?: string }) => ( + + ) +}) + +const mockOnClose = jest.fn() +const mockOnClick = jest.fn() + +const defaultProps = { + title: 'Test Title', + summary: 'Test Summary', + hint: 'Test Hint', + description: 'Test Description', + isOpen: true, + onClose: mockOnClose, + button: { + label: 'Action', + icon: , + url: 'https://example.com', + onPress: mockOnClick, + }, +} + +describe('Modal a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/ModeToggle.a11y.test.tsx b/frontend/__tests__/a11y/components/ModeToggle.a11y.test.tsx new file mode 100644 index 0000000000..42e4b4ff15 --- /dev/null +++ b/frontend/__tests__/a11y/components/ModeToggle.a11y.test.tsx @@ -0,0 +1,21 @@ +import { render, screen } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import ModeToggle from 'components/ModeToggle' + +jest.mock('next-themes', () => ({ + useTheme: () => ({ theme: 'light', setTheme: jest.fn() }), +})) + +expect.extend(toHaveNoViolations) + +describe('ModeToggle a11y', () => { + it('should not have any accessibility violations', async () => { + const { baseElement } = render() + + await screen.findByRole('button', { name: /Enable dark mode/i }) + + const results = await axe(baseElement) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/MultiSearch.a11y.test.tsx b/frontend/__tests__/a11y/components/MultiSearch.a11y.test.tsx new file mode 100644 index 0000000000..f91f90c184 --- /dev/null +++ b/frontend/__tests__/a11y/components/MultiSearch.a11y.test.tsx @@ -0,0 +1,23 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import MultiSearchBar from 'components/MultiSearch' + +expect.extend(toHaveNoViolations) + +const defaultProps = { + isLoaded: true, + placeholder: 'Search...', + indexes: ['chapters', 'users', 'projects'], + initialValue: 'test-value', + eventData: [], +} + +describe('MultiSearch a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/NavButton.a11y.test.tsx b/frontend/__tests__/a11y/components/NavButton.a11y.test.tsx new file mode 100644 index 0000000000..20b66aed5c --- /dev/null +++ b/frontend/__tests__/a11y/components/NavButton.a11y.test.tsx @@ -0,0 +1,27 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { FaHome } from 'react-icons/fa' +import { FaUser } from 'react-icons/fa6' +import { NavButtonProps } from 'types/button' +import NavButton from 'components/NavButton' + +expect.extend(toHaveNoViolations) + +jest.mock('next/link', () => ({ children, href }) => {children}) + +const defaultProps: NavButtonProps & { defaultIcon: typeof FaHome; hoverIcon: typeof FaUser } = { + href: '/test-path', + defaultIcon: FaHome, + hoverIcon: FaUser, + text: 'Test Button', +} + +describe('NavButton a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/NavDropDown.a11y.test.tsx b/frontend/__tests__/a11y/components/NavDropDown.a11y.test.tsx new file mode 100644 index 0000000000..1d37a881e2 --- /dev/null +++ b/frontend/__tests__/a11y/components/NavDropDown.a11y.test.tsx @@ -0,0 +1,46 @@ +import { render, screen, fireEvent, waitFor } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import type { Link as LinkType } from 'types/link' +import NavDropDown from 'components/NavDropDown' + +expect.extend(toHaveNoViolations) + +const mockLink: LinkType = { + text: 'Documentation', + href: '/docs', + submenu: [ + { text: 'Getting Started', href: '/docs/getting-started' }, + { text: 'API Reference', href: '/docs/api' }, + { text: 'Examples', href: '/docs/examples' }, + ], +} + +const defaultProps = { + pathname: '/current-page', + link: mockLink, +} + +describe('NavDropDown a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when open', async () => { + const { container } = render() + + const button = screen.getByRole('button') + fireEvent.click(button) + + await waitFor(() => { + expect(screen.getByText('Getting Started')).toBeInTheDocument() + }) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/PageLayout.a11y.test.tsx b/frontend/__tests__/a11y/components/PageLayout.a11y.test.tsx new file mode 100644 index 0000000000..9d8591695b --- /dev/null +++ b/frontend/__tests__/a11y/components/PageLayout.a11y.test.tsx @@ -0,0 +1,19 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import PageLayout from 'components/PageLayout' + +expect.extend(toHaveNoViolations) + +describe('PageLayout a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render( + + Child Content + + ) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/Pagination.a11y.test.tsx b/frontend/__tests__/a11y/components/Pagination.a11y.test.tsx new file mode 100644 index 0000000000..8e6a66876d --- /dev/null +++ b/frontend/__tests__/a11y/components/Pagination.a11y.test.tsx @@ -0,0 +1,22 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import Pagination from 'components/Pagination' + +expect.extend(toHaveNoViolations) + +const props = { + currentPage: 6, + totalPages: 10, + isLoaded: true, + onPageChange: jest.fn(), +} + +describe('Pagination a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/ProgramCard.a11y.test.tsx b/frontend/__tests__/a11y/components/ProgramCard.a11y.test.tsx new file mode 100644 index 0000000000..3560a6dab5 --- /dev/null +++ b/frontend/__tests__/a11y/components/ProgramCard.a11y.test.tsx @@ -0,0 +1,35 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { ProgramStatusEnum } from 'types/__generated__/graphql' +import { Program } from 'types/mentorship' +import ProgramCard from 'components/ProgramCard' + +expect.extend(toHaveNoViolations) + +jest.mock('hooks/useUpdateProgramStatus', () => ({ + useUpdateProgramStatus: () => ({ updateProgramStatus: jest.fn() }), +})) + +const baseMockProgram: Program = { + id: '1', + key: 'test-program', + name: 'Test Program', + description: 'This is a test program description', + status: ProgramStatusEnum.Published, + startedAt: '2024-01-01T00:00:00Z', + endedAt: '2024-12-31T12:00:00Z', + userRole: 'admin', +} + +describe('ProgramCard Accessibility', () => { + it('should not have any accessibility violations', async () => { + const { baseElement } = render( + + + + ) + const results = await axe(baseElement) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/ProjectTypeDashboardCard.a11y.test.tsx b/frontend/__tests__/a11y/components/ProjectTypeDashboardCard.a11y.test.tsx new file mode 100644 index 0000000000..dfaffbe587 --- /dev/null +++ b/frontend/__tests__/a11y/components/ProjectTypeDashboardCard.a11y.test.tsx @@ -0,0 +1,22 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { FaHeartPulse } from 'react-icons/fa6' +import ProjectTypeDashboardCard from 'components/ProjectTypeDashboardCard' + +expect.extend(toHaveNoViolations) + +const baseProps = { + type: 'healthy' as const, + count: 42, + icon: FaHeartPulse, +} + +describe('ProjectTypeDashboardCard a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/ProjectsDashboardDropDown.a11y.test.tsx b/frontend/__tests__/a11y/components/ProjectsDashboardDropDown.a11y.test.tsx new file mode 100644 index 0000000000..73c6b4646d --- /dev/null +++ b/frontend/__tests__/a11y/components/ProjectsDashboardDropDown.a11y.test.tsx @@ -0,0 +1,42 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { FaFilter } from 'react-icons/fa6' +import ProjectsDashboardDropDown from 'components/ProjectsDashboardDropDown' + +expect.extend(toHaveNoViolations) + +const defaultProps = { + buttonDisplayName: 'Filter', + onAction: jest.fn(), + selectedKeys: ['Active'], + selectedLabels: ['Selected Item'], + selectionMode: 'single' as 'single' | 'multiple', + sections: [ + { + title: 'Status', + items: [ + { key: 'Active', label: 'Active' }, + { key: 'Inactive', label: 'Inactive' }, + ], + }, + { + title: 'Priority', + items: [ + { key: 'High Priority', label: 'High Priority' }, + { key: 'Low Priority', label: 'Low Priority' }, + ], + }, + ], + icon: FaFilter, + isOrdering: false, +} + +describe('ProjectsDashboardDropDown a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/ProjectsDashboardNavBar.a11y.test.tsx b/frontend/__tests__/a11y/components/ProjectsDashboardNavBar.a11y.test.tsx new file mode 100644 index 0000000000..5b8bbafc03 --- /dev/null +++ b/frontend/__tests__/a11y/components/ProjectsDashboardNavBar.a11y.test.tsx @@ -0,0 +1,22 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { ReactNode } from 'react' +import ProjectsDashboardNavBar from 'components/ProjectsDashboardNavBar' + +expect.extend(toHaveNoViolations) + +jest.mock('next/link', () => { + return ({ children, href }: { children: ReactNode; href: string }) => { + return {children} + } +}) + +describe('ProjectsDashboardNavBar a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/RecentIssues.a11y.test.tsx b/frontend/__tests__/a11y/components/RecentIssues.a11y.test.tsx new file mode 100644 index 0000000000..9f1662fc31 --- /dev/null +++ b/frontend/__tests__/a11y/components/RecentIssues.a11y.test.tsx @@ -0,0 +1,42 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import RecentIssues from 'components/RecentIssues' + +expect.extend(toHaveNoViolations) + +const baseIssue = { + author: { + avatarUrl: 'https://example.com/avatar.png', + login: 'user1', + name: 'User One', + contributionsCount: 10, + createdAt: 1234567890, + followersCount: 5, + followingCount: 2, + key: 'user1', + publicRepositoriesCount: 3, + url: 'https://github.com/user1', + }, + createdAt: 1710000000, + hint: 'Hint', + labels: ['bug'], + organizationName: 'org', + projectName: 'proj', + projectUrl: 'https://github.com/org/proj', + summary: 'Summary', + title: 'Issue Title', + updatedAt: 1710000100, + url: 'https://github.com/org/proj/issues/1', + objectID: 'id1', + repositoryName: 'repo', +} + +describe('RecentIssues a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/RecentPullRequests.a11y.test.tsx b/frontend/__tests__/a11y/components/RecentPullRequests.a11y.test.tsx new file mode 100644 index 0000000000..78a395a2a1 --- /dev/null +++ b/frontend/__tests__/a11y/components/RecentPullRequests.a11y.test.tsx @@ -0,0 +1,70 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import React from 'react' +import RecentPullRequests from 'components/RecentPullRequests' + +expect.extend(toHaveNoViolations) + +jest.mock('next/navigation', () => ({ + useRouter: () => ({ + push: jest.fn(), + }), +})) + +jest.mock('next/link', () => ({ + __esModule: true, + default: ({ + children, + href, + ...props + }: { + children: React.ReactNode + href: string + [key: string]: unknown + }) => ( + + {children} + + ), +})) + +const mockUser = { + avatarUrl: 'https://example.com/avatar.png', + bio: 'Test bio', + company: 'Test Company', + contributionsCount: 42, + createdAt: 1577836800000, + email: 'test@example.com', + followersCount: 10, + followingCount: 5, + key: 'user-key', + location: 'Test City', + login: 'testuser', + name: 'Test User', + publicRepositoriesCount: 3, + url: 'https://github.com/testuser', +} + +const minimalData = [ + { + id: 'mock-pull-request', + author: mockUser, + createdAt: '2024-06-01T12:00:00Z', + organizationName: 'test-org', + repositoryName: 'test-repo', + title: 'Test Pull Request', + url: 'https://github.com/test-org/test-repo/pull/1', + state: 'open', + mergedAt: '2024-06-02T12:00:00Z', + }, +] + +describe('RecentPullRequests a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/RecentRelease.a11y.test.tsx b/frontend/__tests__/a11y/components/RecentRelease.a11y.test.tsx new file mode 100644 index 0000000000..79a33840eb --- /dev/null +++ b/frontend/__tests__/a11y/components/RecentRelease.a11y.test.tsx @@ -0,0 +1,58 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { Release } from 'types/release' +import RecentReleases from 'components/RecentReleases' + +expect.extend(toHaveNoViolations) + +const mockReleases: Release[] = [ + { + name: 'v1.0 The First Release', + publishedAt: Date.now(), + repositoryName: 'our-awesome-project', + organizationName: 'our-org', + tagName: 'v1.0', + isPreRelease: false, + author: { + login: 'testuser', + name: 'Test User', + avatarUrl: 'https://example.com/avatar.png', + key: 'testuser', + contributionsCount: 0, + createdAt: 0, + followersCount: 0, + followingCount: 0, + publicRepositoriesCount: 0, + url: 'https://example.com/user/testuser', + }, + }, + { + name: 'v2.0 The Second Release', + publishedAt: Date.now(), + repositoryName: 'another-cool-project', + organizationName: 'our-org', + tagName: 'v2.0', + isPreRelease: false, + author: { + login: 'jane-doe', + name: 'Jane Doe', + avatarUrl: 'https://example.com/avatar2.png', + key: 'jane-doe', + contributionsCount: 0, + createdAt: 0, + followersCount: 0, + followingCount: 0, + publicRepositoriesCount: 0, + url: 'https://example.com/user/jane-doe', + }, + }, +] + +describe('RecentReleases a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/Release.a11y.test.tsx b/frontend/__tests__/a11y/components/Release.a11y.test.tsx new file mode 100644 index 0000000000..98137e3445 --- /dev/null +++ b/frontend/__tests__/a11y/components/Release.a11y.test.tsx @@ -0,0 +1,41 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import type { Release as ReleaseType } from 'types/release' +import Release from 'components/Release' + +expect.extend(toHaveNoViolations) + +const release: ReleaseType = { + name: 'v1.0 The First Release', + publishedAt: Date.now(), + repositoryName: 'our-awesome-project', + organizationName: 'our-org', + tagName: 'v1.0', + isPreRelease: false, + author: { + login: 'testuser', + name: 'Test User', + avatarUrl: 'https://example.com/avatar.png', + key: 'testuser', + contributionsCount: 0, + createdAt: 0, + followersCount: 0, + followingCount: 0, + publicRepositoriesCount: 0, + url: 'https://example.com/user/testuser', + }, +} + +describe('Release a11y', () => { + it('should not have any accessibility violations', async () => { + const { baseElement } = render( + + + + ) + + const results = await axe(baseElement) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/RepositoryCard.a11y.test.tsx b/frontend/__tests__/a11y/components/RepositoryCard.a11y.test.tsx new file mode 100644 index 0000000000..9690ef47db --- /dev/null +++ b/frontend/__tests__/a11y/components/RepositoryCard.a11y.test.tsx @@ -0,0 +1,65 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { Organization } from 'types/organization' +import { RepositoryCardProps } from 'types/project' +import RepositoryCard from 'components/RepositoryCard' + +expect.extend(toHaveNoViolations) + +const createMockRepository = (index: number): RepositoryCardProps => ({ + contributorsCount: 10 + index, + forksCount: 5 + index, + key: `repo-${index}`, + name: `Repository ${index}`, + openIssuesCount: 3 + index, + organization: { + login: `org-${index}`, + name: `Organization ${index}`, + key: `org-${index}`, + url: `https://github.com/org-${index}`, + avatarUrl: `https://github.com/org-${index}.png`, + description: `Organization ${index} description`, + objectID: `org-${index}`, + collaboratorsCount: 10, + followersCount: 50, + publicRepositoriesCount: 20, + createdAt: Date.now(), + updatedAt: Date.now(), + } as Organization, + starsCount: 100 + index, + subscribersCount: 20 + index, + url: `https://github.com/org-${index}/repo-${index}`, +}) + +describe('RepositoryCard a11y', () => { + it('should not have any accessibility violations', async () => { + const repositories = Array.from({ length: 6 }, (_, i) => createMockRepository(i)) + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when expanded', async () => { + const repositories = Array.from({ length: 6 }, (_, i) => createMockRepository(i)) + const { container } = render() + + const showMoreButton = screen.getByTestId('show-more-button') + fireEvent.click(showMoreButton) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when containing at least one archived repository', async () => { + const repositories = Array.from({ length: 6 }, (_, i) => createMockRepository(i)) + repositories[2].isArchived = true + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/ScrollToTop.a11y.test.tsx b/frontend/__tests__/a11y/components/ScrollToTop.a11y.test.tsx new file mode 100644 index 0000000000..0f72e0033f --- /dev/null +++ b/frontend/__tests__/a11y/components/ScrollToTop.a11y.test.tsx @@ -0,0 +1,15 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import ScrollToTop from 'components/ScrollToTop' + +expect.extend(toHaveNoViolations) + +describe('ScrollToTop a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/Search.a11y.test.tsx b/frontend/__tests__/a11y/components/Search.a11y.test.tsx new file mode 100644 index 0000000000..ef812d0735 --- /dev/null +++ b/frontend/__tests__/a11y/components/Search.a11y.test.tsx @@ -0,0 +1,43 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import SearchBar from 'components/Search' + +expect.extend(toHaveNoViolations) + +const mockOnSearch = jest.fn() +const defaultProps = { + isLoaded: false, + onSearch: mockOnSearch, + placeholder: 'Search projects...', +} + +describe('SearchBar a11y', () => { + describe('when not loaded', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + }) + + describe('when loaded', () => { + it('should not have any accessibility violations when searchQuery is empty', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + it('should not have any accessibility violations when searchQuery has value', async () => { + const { container } = render( + + ) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + }) +}) diff --git a/frontend/__tests__/a11y/components/SearchPageLayout.a11y.test.tsx b/frontend/__tests__/a11y/components/SearchPageLayout.a11y.test.tsx new file mode 100644 index 0000000000..aa529c90a6 --- /dev/null +++ b/frontend/__tests__/a11y/components/SearchPageLayout.a11y.test.tsx @@ -0,0 +1,63 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import SearchPageLayout from 'components/SearchPageLayout' + +expect.extend(toHaveNoViolations) + +const baseProps = { + currentPage: 1, + searchQuery: '', + onSearch: () => {}, + onPageChange: () => {}, + searchPlaceholder: 'Search...', + empty: 'No results', + indexName: 'chapters', +} + +describe('SearchPageLayout Accessibility', () => { + describe('should not have any accessibility violations when isLoaded is true', () => { + it('has more than one total pages', async () => { + const { container } = render( + + Mock Content + + ) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('has 0 total page', async () => { + const { container } = render( + + Mock Content + + ) + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + }) + + describe('should not have any accessibility violations when isLoaded is false', () => { + it('has more than one total pages', async () => { + const { container } = render( + + ) + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('has 0 total page', async () => { + const { container } = render( + + ) + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + }) +}) diff --git a/frontend/__tests__/a11y/components/SecondaryCard.a11y.test.tsx b/frontend/__tests__/a11y/components/SecondaryCard.a11y.test.tsx new file mode 100644 index 0000000000..f3af935a95 --- /dev/null +++ b/frontend/__tests__/a11y/components/SecondaryCard.a11y.test.tsx @@ -0,0 +1,22 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { FaUser } from 'react-icons/fa6' +import SecondaryCard from 'components/SecondaryCard' + +expect.extend(toHaveNoViolations) + +const baseProps = { + title: 'Test Title', + icon: FaUser, + children: Test children, +} + +describe('SecondaryCard a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/SingleModuleCard.a11y.test.tsx b/frontend/__tests__/a11y/components/SingleModuleCard.a11y.test.tsx new file mode 100644 index 0000000000..1ccdc65634 --- /dev/null +++ b/frontend/__tests__/a11y/components/SingleModuleCard.a11y.test.tsx @@ -0,0 +1,74 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import React from 'react' +import { ExperienceLevelEnum, ProgramStatusEnum } from 'types/__generated__/graphql' +import { Module } from 'types/mentorship' +import SingleModuleCard from 'components/SingleModuleCard' + +expect.extend(toHaveNoViolations) + +jest.mock('next/link', () => ({ + __esModule: true, + default: ({ + children, + href, + target, + rel, + className, + ...props + }: { + children: React.ReactNode + href: string + target?: string + rel?: string + className?: string + [key: string]: unknown + }) => ( + + {children} + + ), +})) + +const mockModule: Module = { + id: '1', + key: 'test-module', + name: 'Test Module', + description: 'This is a test module description', + status: ProgramStatusEnum.Published, + experienceLevel: ExperienceLevelEnum.Intermediate, + mentors: [ + { + name: 'user1', + login: 'user1', + avatarUrl: 'https://example.com/avatar1.jpg', + }, + { + name: 'user2', + login: 'user2', + avatarUrl: 'https://example.com/avatar2.jpg', + }, + ], + startedAt: '2024-01-01T00:00:00Z', + endedAt: '2024-12-31T23:59:59Z', + domains: ['frontend', 'backend'], + tags: ['react', 'nodejs'], + labels: ['good first issue', 'bug'], +} + +describe('SingleModuleCard a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/SkeletonBase.a11y.test.tsx b/frontend/__tests__/a11y/components/SkeletonBase.a11y.test.tsx new file mode 100644 index 0000000000..ccc3ae2c35 --- /dev/null +++ b/frontend/__tests__/a11y/components/SkeletonBase.a11y.test.tsx @@ -0,0 +1,121 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import SkeletonBase from 'components/SkeletonsBase' + +expect.extend(toHaveNoViolations) + +const defaultProps = { + loadingImageUrl: 'https://example.com/loading.gif', +} + +describe('SkeletonBase a11y', () => { + it('should not have any accessibility violations for projects index', async () => { + const { container } = render() + + const results = await axe(container, { + rules: { + 'empty-heading': { enabled: false }, + }, + }) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations for users index', async () => { + const { container } = render() + + const results = await axe(container, { + rules: { + 'empty-heading': { enabled: false }, + }, + }) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations for issues index', async () => { + const { container } = render() + + const results = await axe(container, { + rules: { + 'empty-heading': { enabled: false }, + }, + }) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations for committees index', async () => { + const { container } = render() + + const results = await axe(container, { + rules: { + 'empty-heading': { enabled: false }, + }, + }) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations for organizations index', async () => { + const { container } = render() + + const results = await axe(container, { + rules: { + 'empty-heading': { enabled: false }, + }, + }) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations for snapshots index', async () => { + const { container } = render() + + const results = await axe(container, { + rules: { + 'empty-heading': { enabled: false }, + }, + }) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations for about index', async () => { + const { container } = render() + + const results = await axe(container, { + rules: { + 'empty-heading': { enabled: false }, + }, + }) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations for members index', async () => { + const { container } = render() + + const results = await axe(container, { + rules: { + 'empty-heading': { enabled: false }, + }, + }) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations for organizations-details index', async () => { + const { container } = render( + + ) + + const results = await axe(container, { + rules: { + 'empty-heading': { enabled: false }, + }, + }) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/SnapshotCard.a11y.test.tsx b/frontend/__tests__/a11y/components/SnapshotCard.a11y.test.tsx new file mode 100644 index 0000000000..f4a649a126 --- /dev/null +++ b/frontend/__tests__/a11y/components/SnapshotCard.a11y.test.tsx @@ -0,0 +1,22 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import SnapshotCard from 'components/SnapshotCard' +expect.extend(toHaveNoViolations) + +const defaultProps = { + key: 'test-snapshot-1', + title: 'Test Snapshot', + button: { label: 'Open', onclick: jest.fn() }, + startAt: '2025-01-01T00:00:00Z', + endAt: '2025-01-10T00:00:00Z', +} + +describe('SnapshotCard a11y', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/SortBy.a11y.test.tsx b/frontend/__tests__/a11y/components/SortBy.a11y.test.tsx new file mode 100644 index 0000000000..c864de063c --- /dev/null +++ b/frontend/__tests__/a11y/components/SortBy.a11y.test.tsx @@ -0,0 +1,30 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import SortBy from 'components/SortBy' + +expect.extend(toHaveNoViolations) + +const defaultProps = { + sortOptions: [ + { label: 'Name', key: 'name' }, + { label: 'Date', key: 'date' }, + ], + selectedSortOption: 'name', + onSortChange: jest.fn(), + selectedOrder: 'asc', + onOrderChange: jest.fn(), +} + +describe('SortBy a11y', () => { + it('should not have any accessibility violations', async () => { + const { baseElement } = render( + + + + ) + + const results = await axe(baseElement) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/SponsorCard.a11y.test.tsx b/frontend/__tests__/a11y/components/SponsorCard.a11y.test.tsx new file mode 100644 index 0000000000..de8de0889c --- /dev/null +++ b/frontend/__tests__/a11y/components/SponsorCard.a11y.test.tsx @@ -0,0 +1,20 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import SponsorCard from 'components/SponsorCard' + +expect.extend(toHaveNoViolations) + +const defaultProps = { + target: 'test-target', + title: 'Test Title', + type: 'project', +} + +describe('SponsorCard Accessibility', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/StatusBadge.a11y.test.tsx b/frontend/__tests__/a11y/components/StatusBadge.a11y.test.tsx new file mode 100644 index 0000000000..e9f0550c8d --- /dev/null +++ b/frontend/__tests__/a11y/components/StatusBadge.a11y.test.tsx @@ -0,0 +1,23 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import StatusBadge from 'components/StatusBadge' + +expect.extend(toHaveNoViolations) + +describe('StatusBadge Accessibility', () => { + it('should not have any accessibility violations when status is archived', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when status is inactive', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/ToggleableList.a11y.test.tsx b/frontend/__tests__/a11y/components/ToggleableList.a11y.test.tsx new file mode 100644 index 0000000000..cade5287e1 --- /dev/null +++ b/frontend/__tests__/a11y/components/ToggleableList.a11y.test.tsx @@ -0,0 +1,16 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import ToggleableList from 'components/ToggleableList' + +expect.extend(toHaveNoViolations) + +describe('ToggleableList Accessibility', () => { + it('should not have any accessibility violations', async () => { + const mockItems = Array.from({ length: 15 }, (_, i) => `Item ${i + 1}`) + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/TopContributorsList.a11y.test.tsx b/frontend/__tests__/a11y/components/TopContributorsList.a11y.test.tsx new file mode 100644 index 0000000000..a763ea4e34 --- /dev/null +++ b/frontend/__tests__/a11y/components/TopContributorsList.a11y.test.tsx @@ -0,0 +1,61 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { ReactNode } from 'react' +import { Contributor } from 'types/contributor' +import TopContributorsList from 'components/TopContributorsList' + +expect.extend(toHaveNoViolations) + +jest.mock('next/link', () => { + return function MockLink({ + children, + href, + className, + ...props + }: { + children: ReactNode + href: string + className?: string + [key: string]: unknown + }) { + return ( + + {children} + + ) + } +}) + +const mockContributors: Contributor[] = [ + { + avatarUrl: 'https://github.com/developer1.avatar', + login: 'developer1', + name: 'Alex Developer', + projectKey: 'project1', + contributionsCount: 50, + }, + { + avatarUrl: 'https://github.com/contributor2.avatar', + login: 'contributor2', + name: 'Jane Developer', + projectKey: 'project1', + contributionsCount: 30, + }, + { + avatarUrl: 'https://github.com/user3.avatar', + login: 'user3', + name: '', + projectKey: 'project1', + contributionsCount: 20, + }, +] + +describe('TopContributorsList Accessibility', () => { + it('should not have any accessibility violations', async () => { + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/TruncatedText.a11y.test.tsx b/frontend/__tests__/a11y/components/TruncatedText.a11y.test.tsx new file mode 100644 index 0000000000..8d88fae904 --- /dev/null +++ b/frontend/__tests__/a11y/components/TruncatedText.a11y.test.tsx @@ -0,0 +1,16 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { TruncatedText } from 'components/TruncatedText' + +expect.extend(toHaveNoViolations) + +describe('TruncatedText Accessibility', () => { + it('should not have any accessibility violations', async () => { + const longText = 'This is a long text that should be truncated' + const { container } = render() + + const results = await axe(container) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/UserCard.a11y.test.tsx b/frontend/__tests__/a11y/components/UserCard.a11y.test.tsx new file mode 100644 index 0000000000..f13f02b0c3 --- /dev/null +++ b/frontend/__tests__/a11y/components/UserCard.a11y.test.tsx @@ -0,0 +1,33 @@ +import { render } from '@testing-library/react' +import { axe, toHaveNoViolations } from 'jest-axe' +import { UserCardProps } from 'types/card' +import UserCard from 'components/UserCard' + +expect.extend(toHaveNoViolations) + +const defaultProps: UserCardProps = { + name: 'John Doe', + avatar: '', + button: { + label: 'View Profile', + onclick: jest.fn(), + }, + className: '', + company: '', + description: 'This is a sample description', + email: 'test@test.com', + followersCount: 100, + location: '', + repositoriesCount: 12, + badgeCount: 3, +} + +describe('UserCard Accessibility', () => { + it('should not have any accessibility violations', async () => { + const { baseElement } = render() + + const results = await axe(baseElement) + + expect(results).toHaveNoViolations() + }) +}) diff --git a/frontend/__tests__/a11y/components/UserMenu.a11y.test.tsx b/frontend/__tests__/a11y/components/UserMenu.a11y.test.tsx new file mode 100644 index 0000000000..3c1980d7ee --- /dev/null +++ b/frontend/__tests__/a11y/components/UserMenu.a11y.test.tsx @@ -0,0 +1,133 @@ +import { fireEvent, render, screen } from '@testing-library/react' +import { useDjangoSession } from 'hooks/useDjangoSession' +import { useLogout } from 'hooks/useLogout' +import { axe, toHaveNoViolations } from 'jest-axe' +import { ReactNode } from 'react' +import UserMenu from 'components/UserMenu' + +expect.extend(toHaveNoViolations) + +jest.mock('hooks/useDjangoSession', () => ({ + useDjangoSession: jest.fn(), +})) + +jest.mock('hooks/useLogout', () => ({ + useLogout: jest.fn(), +})) + +jest.mock( + 'next/link', + () => + ({ + children, + href, + ...props + }: { + children: ReactNode + href: string + [key: string]: unknown + }) => ( + + {children} + + ) +) + +describe('UserMenu Accessibility', () => { + const mockUseSession = useDjangoSession as jest.MockedFunction + const mockUseLogout = useLogout as jest.MockedFunction + + it('should not have any accessibility violations when syncing', async () => { + mockUseSession.mockReturnValue({ + session: null, + isSyncing: true, + status: 'loading', + }) + mockUseLogout.mockReturnValue({ + logout: jest.fn(), + isLoggingOut: false, + }) + + const { container } = render() + const results = await axe(container) + expect(results).toHaveNoViolations() + }) + + describe('when unauthenticated', () => { + it('should not have any accessibility violations', async () => { + mockUseSession.mockReturnValue({ + session: null, + isSyncing: false, + status: 'unauthenticated', + }) + mockUseLogout.mockReturnValue({ + logout: jest.fn(), + isLoggingOut: false, + }) + + const { container } = render() + const results = await axe(container) + expect(results).toHaveNoViolations() + }) + }) + + describe('when authenticated', () => { + describe('isOpen is true', () => { + it('should not have any accessibility violations when user is project leader', async () => { + mockUseSession.mockReturnValue({ + session: { + user: { + name: 'John Doe', + email: 'john@example.com', + image: 'https://example.com/avatar.jpg', + isLeader: true, + }, + expires: '2024-12-31', + }, + isSyncing: false, + status: 'authenticated', + }) + mockUseLogout.mockReturnValue({ + logout: jest.fn(), + isLoggingOut: false, + }) + + const { container } = render() + + const button = screen.getByRole('button') + fireEvent.click(button) + + const results = await axe(container) + expect(results).toHaveNoViolations() + }) + + it('should not have any accessibility violations when user is owasp staff', async () => { + mockUseSession.mockReturnValue({ + session: { + user: { + name: 'John Doe', + email: 'john@example.com', + image: 'https://example.com/avatar.jpg', + isOwaspStaff: true, + }, + expires: '2024-12-31', + }, + isSyncing: false, + status: 'authenticated', + }) + mockUseLogout.mockReturnValue({ + logout: jest.fn(), + isLoggingOut: false, + }) + + const { container } = render() + + const button = screen.getByRole('button') + fireEvent.click(button) + + const results = await axe(container) + expect(results).toHaveNoViolations() + }) + }) + }) +}) diff --git a/frontend/__tests__/e2e/pages/About.spec.ts b/frontend/__tests__/e2e/pages/About.spec.ts index 7fca8dfd21..9b968b4dd5 100644 --- a/frontend/__tests__/e2e/pages/About.spec.ts +++ b/frontend/__tests__/e2e/pages/About.spec.ts @@ -1,6 +1,6 @@ import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects' +import { mockAboutData } from '@mockData/mockAboutData' import { test, expect } from '@playwright/test' -import { mockAboutData } from '@unit/data/mockAboutData' test.describe('About Page', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/__tests__/e2e/pages/ChapterDetails.spec.ts b/frontend/__tests__/e2e/pages/ChapterDetails.spec.ts index fe72ecb87f..0811c41278 100644 --- a/frontend/__tests__/e2e/pages/ChapterDetails.spec.ts +++ b/frontend/__tests__/e2e/pages/ChapterDetails.spec.ts @@ -1,5 +1,5 @@ +import { mockChapterDetailsData } from '@mockData/mockChapterDetailsData' import { test, expect } from '@playwright/test' -import { mockChapterDetailsData } from '@unit/data/mockChapterDetailsData' test.describe('Chapter Details Page', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/__tests__/e2e/pages/Chapters.spec.ts b/frontend/__tests__/e2e/pages/Chapters.spec.ts index 9b85d8af3a..5ffda82696 100644 --- a/frontend/__tests__/e2e/pages/Chapters.spec.ts +++ b/frontend/__tests__/e2e/pages/Chapters.spec.ts @@ -1,6 +1,6 @@ import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects' +import { mockChapterData } from '@mockData/mockChapterData' import { test, expect } from '@playwright/test' -import { mockChapterData } from '@unit/data/mockChapterData' test.describe('Chapters Page', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/__tests__/e2e/pages/CommitteeDetails.spec.ts b/frontend/__tests__/e2e/pages/CommitteeDetails.spec.ts index 944e470ec2..a60cead2b9 100644 --- a/frontend/__tests__/e2e/pages/CommitteeDetails.spec.ts +++ b/frontend/__tests__/e2e/pages/CommitteeDetails.spec.ts @@ -1,5 +1,5 @@ +import { mockCommitteeDetailsData } from '@mockData/mockCommitteeDetailsData' import { test, expect } from '@playwright/test' -import { mockCommitteeDetailsData } from '@unit/data/mockCommitteeDetailsData' test.describe('Committee Details Page', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/__tests__/e2e/pages/Committees.spec.ts b/frontend/__tests__/e2e/pages/Committees.spec.ts index 10bce0825c..f96de9fa4c 100644 --- a/frontend/__tests__/e2e/pages/Committees.spec.ts +++ b/frontend/__tests__/e2e/pages/Committees.spec.ts @@ -1,6 +1,6 @@ import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects' +import { mockCommitteeData } from '@mockData/mockCommitteeData' import { test, expect } from '@playwright/test' -import { mockCommitteeData } from '@unit/data/mockCommitteeData' test.describe('Committees Page', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/__tests__/e2e/pages/Contribute.spec.ts b/frontend/__tests__/e2e/pages/Contribute.spec.ts index a87eb18308..75e23ddf04 100644 --- a/frontend/__tests__/e2e/pages/Contribute.spec.ts +++ b/frontend/__tests__/e2e/pages/Contribute.spec.ts @@ -1,6 +1,6 @@ import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects' +import { mockContributeData } from '@mockData/mockContributeData' import { test, expect } from '@playwright/test' -import { mockContributeData } from '@unit/data/mockContributeData' test.describe('Contribute Page', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/__tests__/e2e/pages/OrganizationDetails.spec.ts b/frontend/__tests__/e2e/pages/OrganizationDetails.spec.ts index 2798012c9f..604f9213e4 100644 --- a/frontend/__tests__/e2e/pages/OrganizationDetails.spec.ts +++ b/frontend/__tests__/e2e/pages/OrganizationDetails.spec.ts @@ -1,5 +1,5 @@ +import { mockOrganizationDetailsData } from '@mockData/mockOrganizationData' import { test, expect } from '@playwright/test' -import { mockOrganizationDetailsData } from '@unit/data/mockOrganizationData' test.describe('Organization Details Page', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/__tests__/e2e/pages/Organizations.spec.ts b/frontend/__tests__/e2e/pages/Organizations.spec.ts index 11cfd5f162..5708c34ad4 100644 --- a/frontend/__tests__/e2e/pages/Organizations.spec.ts +++ b/frontend/__tests__/e2e/pages/Organizations.spec.ts @@ -1,6 +1,6 @@ import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects' +import { mockOrganizationData } from '@mockData/mockOrganizationData' import { test, expect } from '@playwright/test' -import { mockOrganizationData } from '@unit/data/mockOrganizationData' test.describe('Organization Page', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/__tests__/e2e/pages/ProjectDetails.spec.ts b/frontend/__tests__/e2e/pages/ProjectDetails.spec.ts index 3931924930..d6d9b5baa2 100644 --- a/frontend/__tests__/e2e/pages/ProjectDetails.spec.ts +++ b/frontend/__tests__/e2e/pages/ProjectDetails.spec.ts @@ -1,5 +1,5 @@ +import { mockProjectDetailsData } from '@mockData/mockProjectDetailsData' import { test, expect } from '@playwright/test' -import { mockProjectDetailsData } from '@unit/data/mockProjectDetailsData' test.describe('Project Details Page', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/__tests__/e2e/pages/ProjectHealthDashboardMetricsDetails.spec.ts b/frontend/__tests__/e2e/pages/ProjectHealthDashboardMetricsDetails.spec.ts index d41f7e3a70..f246c835d4 100644 --- a/frontend/__tests__/e2e/pages/ProjectHealthDashboardMetricsDetails.spec.ts +++ b/frontend/__tests__/e2e/pages/ProjectHealthDashboardMetricsDetails.spec.ts @@ -1,6 +1,6 @@ import { mockDashboardCookies } from '@e2e/helpers/mockDashboardCookies' +import { mockProjectsDashboardMetricsDetailsData } from '@mockData/mockProjectsDashboardMetricsDetailsData' import { expect, test } from '@playwright/test' -import { mockProjectsDashboardMetricsDetailsData } from '@unit/data/mockProjectsDashboardMetricsDetailsData' test.describe('Project Health Metrics Details Page', () => { test('renders 404 when user is not OWASP staff', async ({ page }) => { diff --git a/frontend/__tests__/e2e/pages/Projects.spec.ts b/frontend/__tests__/e2e/pages/Projects.spec.ts index 230ca6d403..12ba933835 100644 --- a/frontend/__tests__/e2e/pages/Projects.spec.ts +++ b/frontend/__tests__/e2e/pages/Projects.spec.ts @@ -1,6 +1,6 @@ import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects' +import mockProjectData from '@mockData/mockProjectData' import { test, expect } from '@playwright/test' -import mockProjectData from '@unit/data/mockProjectData' test.describe('Projects Page', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/__tests__/e2e/pages/ProjectsHealthDashboardMetrics.spec.ts b/frontend/__tests__/e2e/pages/ProjectsHealthDashboardMetrics.spec.ts index 02959b3148..999beea379 100644 --- a/frontend/__tests__/e2e/pages/ProjectsHealthDashboardMetrics.spec.ts +++ b/frontend/__tests__/e2e/pages/ProjectsHealthDashboardMetrics.spec.ts @@ -1,6 +1,6 @@ import { mockDashboardCookies } from '@e2e/helpers/mockDashboardCookies' +import { mockHealthMetricsData } from '@mockData/mockProjectsHealthMetricsData' import { test, expect } from '@playwright/test' -import { mockHealthMetricsData } from '@unit/data/mockProjectsHealthMetricsData' test.describe('Projects Health Dashboard Metrics', () => { test('renders 404 when user is not OWASP staff', async ({ page }) => { await mockDashboardCookies(page, mockHealthMetricsData, false) diff --git a/frontend/__tests__/e2e/pages/ProjectsHealthDashboardOverview.spec.ts b/frontend/__tests__/e2e/pages/ProjectsHealthDashboardOverview.spec.ts index 97ad015d54..fec176aeb8 100644 --- a/frontend/__tests__/e2e/pages/ProjectsHealthDashboardOverview.spec.ts +++ b/frontend/__tests__/e2e/pages/ProjectsHealthDashboardOverview.spec.ts @@ -1,6 +1,6 @@ import { mockDashboardCookies } from '@e2e/helpers/mockDashboardCookies' +import { mockProjectsDashboardOverviewData } from '@mockData/mockProjectsDashboardOverviewData' import { test, expect } from '@playwright/test' -import { mockProjectsDashboardOverviewData } from '@unit/data/mockProjectsDashboardOverviewData' import millify from 'millify' test.describe('Projects Health Dashboard Overview', () => { diff --git a/frontend/__tests__/e2e/pages/RepositoryDetails.spec.ts b/frontend/__tests__/e2e/pages/RepositoryDetails.spec.ts index ee2376296b..5ef12097af 100644 --- a/frontend/__tests__/e2e/pages/RepositoryDetails.spec.ts +++ b/frontend/__tests__/e2e/pages/RepositoryDetails.spec.ts @@ -1,5 +1,5 @@ +import { mockRepositoryData } from '@mockData/mockRepositoryData' import { test, expect } from '@playwright/test' -import { mockRepositoryData } from '@unit/data/mockRepositoryData' test.describe('Repository Details Page', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/__tests__/e2e/pages/UserDetails.spec.ts b/frontend/__tests__/e2e/pages/UserDetails.spec.ts index 9e2f4e43ab..e82d073479 100644 --- a/frontend/__tests__/e2e/pages/UserDetails.spec.ts +++ b/frontend/__tests__/e2e/pages/UserDetails.spec.ts @@ -1,5 +1,5 @@ +import { mockUserDetailsData } from '@mockData/mockUserDetails' import { test, expect } from '@playwright/test' -import { mockUserDetailsData } from '@unit/data/mockUserDetails' test.describe('User Details Page', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/__tests__/e2e/pages/Users.spec.ts b/frontend/__tests__/e2e/pages/Users.spec.ts index d760883615..ec058f46c5 100644 --- a/frontend/__tests__/e2e/pages/Users.spec.ts +++ b/frontend/__tests__/e2e/pages/Users.spec.ts @@ -1,6 +1,6 @@ import { expectBreadCrumbsToBeVisible } from '@e2e/helpers/expects' +import { mockUserData } from '@mockData/mockUserData' import { test, expect } from '@playwright/test' -import { mockUserData } from '@unit/data/mockUserData' test.describe('Users Page', () => { test.beforeEach(async ({ page }) => { diff --git a/frontend/__tests__/unit/data/mockAboutData.ts b/frontend/__tests__/mockData/mockAboutData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockAboutData.ts rename to frontend/__tests__/mockData/mockAboutData.ts diff --git a/frontend/__tests__/unit/data/mockApiKeysData.ts b/frontend/__tests__/mockData/mockApiKeysData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockApiKeysData.ts rename to frontend/__tests__/mockData/mockApiKeysData.ts diff --git a/frontend/__tests__/unit/data/mockBadgeData.ts b/frontend/__tests__/mockData/mockBadgeData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockBadgeData.ts rename to frontend/__tests__/mockData/mockBadgeData.ts diff --git a/frontend/__tests__/unit/data/mockChapterData.ts b/frontend/__tests__/mockData/mockChapterData.ts similarity index 87% rename from frontend/__tests__/unit/data/mockChapterData.ts rename to frontend/__tests__/mockData/mockChapterData.ts index 7b4e935489..6e6449e902 100644 --- a/frontend/__tests__/unit/data/mockChapterData.ts +++ b/frontend/__tests__/mockData/mockChapterData.ts @@ -27,6 +27,12 @@ export const mockChapterData = { lat: 35.1815, lng: 136.9066, }, + suggestedLocation: 'Nagoya, Japan', + geoLocation: { + lat: 35.1815, + lng: 136.9066, + }, + region: 'Japan', }, ], } diff --git a/frontend/__tests__/unit/data/mockChapterDetailsData.ts b/frontend/__tests__/mockData/mockChapterDetailsData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockChapterDetailsData.ts rename to frontend/__tests__/mockData/mockChapterDetailsData.ts diff --git a/frontend/__tests__/unit/data/mockCommitteeData.ts b/frontend/__tests__/mockData/mockCommitteeData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockCommitteeData.ts rename to frontend/__tests__/mockData/mockCommitteeData.ts diff --git a/frontend/__tests__/unit/data/mockCommitteeDetailsData.ts b/frontend/__tests__/mockData/mockCommitteeDetailsData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockCommitteeDetailsData.ts rename to frontend/__tests__/mockData/mockCommitteeDetailsData.ts diff --git a/frontend/__tests__/unit/data/mockContributeData.ts b/frontend/__tests__/mockData/mockContributeData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockContributeData.ts rename to frontend/__tests__/mockData/mockContributeData.ts diff --git a/frontend/__tests__/unit/data/mockHomeData.ts b/frontend/__tests__/mockData/mockHomeData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockHomeData.ts rename to frontend/__tests__/mockData/mockHomeData.ts diff --git a/frontend/__tests__/unit/data/mockModuleData.ts b/frontend/__tests__/mockData/mockModuleData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockModuleData.ts rename to frontend/__tests__/mockData/mockModuleData.ts diff --git a/frontend/__tests__/unit/data/mockOrganizationData.ts b/frontend/__tests__/mockData/mockOrganizationData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockOrganizationData.ts rename to frontend/__tests__/mockData/mockOrganizationData.ts diff --git a/frontend/__tests__/unit/data/mockProgramData.ts b/frontend/__tests__/mockData/mockProgramData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockProgramData.ts rename to frontend/__tests__/mockData/mockProgramData.ts diff --git a/frontend/__tests__/unit/data/mockProjectData.ts b/frontend/__tests__/mockData/mockProjectData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockProjectData.ts rename to frontend/__tests__/mockData/mockProjectData.ts diff --git a/frontend/__tests__/unit/data/mockProjectDetailsData.ts b/frontend/__tests__/mockData/mockProjectDetailsData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockProjectDetailsData.ts rename to frontend/__tests__/mockData/mockProjectDetailsData.ts diff --git a/frontend/__tests__/unit/data/mockProjectsDashboardMetricsDetailsData.ts b/frontend/__tests__/mockData/mockProjectsDashboardMetricsDetailsData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockProjectsDashboardMetricsDetailsData.ts rename to frontend/__tests__/mockData/mockProjectsDashboardMetricsDetailsData.ts diff --git a/frontend/__tests__/unit/data/mockProjectsDashboardOverviewData.ts b/frontend/__tests__/mockData/mockProjectsDashboardOverviewData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockProjectsDashboardOverviewData.ts rename to frontend/__tests__/mockData/mockProjectsDashboardOverviewData.ts diff --git a/frontend/__tests__/unit/data/mockProjectsHealthMetricsData.ts b/frontend/__tests__/mockData/mockProjectsHealthMetricsData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockProjectsHealthMetricsData.ts rename to frontend/__tests__/mockData/mockProjectsHealthMetricsData.ts diff --git a/frontend/__tests__/unit/data/mockRepositoryData.ts b/frontend/__tests__/mockData/mockRepositoryData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockRepositoryData.ts rename to frontend/__tests__/mockData/mockRepositoryData.ts diff --git a/frontend/__tests__/unit/data/mockSnapshotData.ts b/frontend/__tests__/mockData/mockSnapshotData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockSnapshotData.ts rename to frontend/__tests__/mockData/mockSnapshotData.ts diff --git a/frontend/__tests__/unit/data/mockUserData.ts b/frontend/__tests__/mockData/mockUserData.ts similarity index 100% rename from frontend/__tests__/unit/data/mockUserData.ts rename to frontend/__tests__/mockData/mockUserData.ts diff --git a/frontend/__tests__/unit/data/mockUserDetails.ts b/frontend/__tests__/mockData/mockUserDetails.ts similarity index 100% rename from frontend/__tests__/unit/data/mockUserDetails.ts rename to frontend/__tests__/mockData/mockUserDetails.ts diff --git a/frontend/__tests__/unit/components/ActionButton.test.tsx b/frontend/__tests__/unit/components/ActionButton.test.tsx index 866a4b946a..ecfc8a654d 100644 --- a/frontend/__tests__/unit/components/ActionButton.test.tsx +++ b/frontend/__tests__/unit/components/ActionButton.test.tsx @@ -7,28 +7,6 @@ jest.mock('@heroui/tooltip', () => ({ Tooltip: ({ children }: { children: React.ReactNode }) => {children}, })) -jest.mock('next/link', () => { - return ({ - children, - href, - onClick, - }: { - children: React.ReactNode - href: string - onClick?: () => void - }) => ( - { - e.preventDefault() - onClick?.() - }} - > - {children} - - ) -}) - describe('ActionButton', () => { afterEach(() => { cleanup() diff --git a/frontend/__tests__/unit/components/CardDetailsPage.test.tsx b/frontend/__tests__/unit/components/CardDetailsPage.test.tsx index 78acfe216c..b387ad4270 100644 --- a/frontend/__tests__/unit/components/CardDetailsPage.test.tsx +++ b/frontend/__tests__/unit/components/CardDetailsPage.test.tsx @@ -4,23 +4,6 @@ import '@testing-library/jest-dom' import { FaCode, FaTags } from 'react-icons/fa6' import type { DetailsCardProps } from 'types/card' import CardDetailsPage, { type CardType } from 'components/CardDetailsPage' -jest.mock('next/link', () => { - const MockLink = ({ - children, - href, - ...props - }: { - children: React.ReactNode - href: string - [key: string]: unknown - }) => ( - - {children} - - ) - MockLink.displayName = 'MockLink' - return MockLink -}) jest.mock('next/navigation', () => ({ useRouter: () => ({ diff --git a/frontend/__tests__/unit/components/ContributorAvatar.test.tsx b/frontend/__tests__/unit/components/ContributorAvatar.test.tsx index f725b657ca..e5eecbd315 100644 --- a/frontend/__tests__/unit/components/ContributorAvatar.test.tsx +++ b/frontend/__tests__/unit/components/ContributorAvatar.test.tsx @@ -3,22 +3,6 @@ import React from 'react' import { Contributor } from 'types/contributor' import ContributorAvatar from 'components/ContributorAvatar' -jest.mock('@heroui/tooltip', () => ({ - Tooltip: ({ - children, - content, - id, - }: { - children: React.ReactNode - content: string - id: string - }) => ( - - {children} - - ), -})) - jest.mock('next/link', () => { return ({ children, @@ -37,6 +21,22 @@ jest.mock('next/link', () => { ) }) +jest.mock('@heroui/tooltip', () => ({ + Tooltip: ({ + children, + content, + id, + }: { + children: React.ReactNode + content: string + id: string + }) => ( + + {children} + + ), +})) + jest.mock('next/image', () => { return ({ src, diff --git a/frontend/__tests__/unit/components/Footer.test.tsx b/frontend/__tests__/unit/components/Footer.test.tsx index d54882bbb3..234daa3553 100644 --- a/frontend/__tests__/unit/components/Footer.test.tsx +++ b/frontend/__tests__/unit/components/Footer.test.tsx @@ -6,16 +6,6 @@ import { render, screen, fireEvent } from '@testing-library/react' import '@testing-library/jest-dom' import React, { ReactNode } from 'react' -// Define proper types for mock props -interface MockLinkProps { - children: ReactNode - href: string - target?: string - rel?: string - className?: string - 'aria-label'?: string -} - interface MockButtonProps { children: ReactNode onPress?: () => void @@ -25,16 +15,6 @@ interface MockButtonProps { 'aria-controls'?: string } -jest.mock('next/link', () => { - return function MockedLink({ children, href, ...props }: MockLinkProps) { - return ( - - {children} - - ) - } -}) - jest.mock('@heroui/button', () => ({ Button: ({ children, onPress, className, disableAnimation, ...props }: MockButtonProps) => ( { return function MockLink({ children, diff --git a/frontend/__tests__/unit/components/LogoCarousel.test.tsx b/frontend/__tests__/unit/components/LogoCarousel.test.tsx index 53caa5cd49..eee363af11 100644 --- a/frontend/__tests__/unit/components/LogoCarousel.test.tsx +++ b/frontend/__tests__/unit/components/LogoCarousel.test.tsx @@ -3,23 +3,6 @@ import React from 'react' import { Sponsor } from 'types/home' import MovingLogos from 'components/LogoCarousel' -jest.mock('next/image', () => { - return function MockImage({ - src, - alt, - style, - fill, - }: { - src: string - alt: string - style?: React.CSSProperties - fill?: boolean - }) { - // eslint-disable-next-line @next/next/no-img-element - return - } -}) - jest.mock('next/link', () => { return function MockLink({ href, @@ -41,6 +24,24 @@ jest.mock('next/link', () => { ) } }) + +jest.mock('next/image', () => { + return function MockImage({ + src, + alt, + style, + fill, + }: { + src: string + alt: string + style?: React.CSSProperties + fill?: boolean + }) { + // eslint-disable-next-line @next/next/no-img-element + return + } +}) + const mockSponsors: Sponsor[] = [ { name: 'Test Sponsor 1', diff --git a/frontend/__tests__/unit/components/NavButton.test.tsx b/frontend/__tests__/unit/components/NavButton.test.tsx index 5cd8a9afc2..3c011733fd 100644 --- a/frontend/__tests__/unit/components/NavButton.test.tsx +++ b/frontend/__tests__/unit/components/NavButton.test.tsx @@ -5,7 +5,6 @@ import { render, screen, fireEvent } from '@testing-library/react' import React from 'react' import '@testing-library/jest-dom' -import type { ComponentPropsWithoutRef } from 'react' import { FaHome } from 'react-icons/fa' import { FaUser } from 'react-icons/fa6' import type { NavButtonProps } from 'types/button' @@ -38,17 +37,6 @@ jest.mock('wrappers/IconWrapper', () => ({ }, })) -// Mock Next.js Link -jest.mock('next/link', () => { - return function MockLink({ children, href, ...props }: ComponentPropsWithoutRef<'a'>) { - return ( - - {children} - - ) - } -}) - describe('', () => { const defaultProps: NavButtonProps & { defaultIcon: typeof FaHome; hoverIcon: typeof FaUser } = { href: '/test-path', diff --git a/frontend/__tests__/unit/components/NavDropDown.test.tsx b/frontend/__tests__/unit/components/NavDropDown.test.tsx index da02f417f2..d6a401610b 100644 --- a/frontend/__tests__/unit/components/NavDropDown.test.tsx +++ b/frontend/__tests__/unit/components/NavDropDown.test.tsx @@ -5,24 +5,6 @@ import '@testing-library/jest-dom' import type { Link as LinkType } from 'types/link' import NavDropdown from 'components/NavDropDown' -// Mock Next.js Link component -jest.mock('next/link', () => { - return ({ href, children, ...props }) => { - return ( - { - e.preventDefault() - props.onClick?.(e) - }} - > - {children} - - ) - } -}) - jest.mock('react-icons/fa', () => ({ FaChevronDown: (props: React.SVGProps) => ( diff --git a/frontend/__tests__/unit/components/ProgramCard.test.tsx b/frontend/__tests__/unit/components/ProgramCard.test.tsx index 61eed44911..487d4dcb58 100644 --- a/frontend/__tests__/unit/components/ProgramCard.test.tsx +++ b/frontend/__tests__/unit/components/ProgramCard.test.tsx @@ -55,12 +55,6 @@ jest.mock('@heroui/tooltip', () => ({ jest.mock('components/EntityActions', () => jest.requireActual('components/EntityActions')) -jest.mock('next/link', () => { - return ({ children, href }: { children: React.ReactNode; href: string }) => { - return {children} - } -}) - describe('ProgramCard', () => { const mockPush = jest.fn() diff --git a/frontend/__tests__/unit/components/ProjectTypeDashboardCard.test.tsx b/frontend/__tests__/unit/components/ProjectTypeDashboardCard.test.tsx index 1f59806a0c..805578a9df 100644 --- a/frontend/__tests__/unit/components/ProjectTypeDashboardCard.test.tsx +++ b/frontend/__tests__/unit/components/ProjectTypeDashboardCard.test.tsx @@ -7,26 +7,6 @@ import { FaHeartPulse, FaSkull } from 'react-icons/fa6' import type { ProjectHealthType } from 'types/project' import ProjectTypeDashboardCard from 'components/ProjectTypeDashboardCard' -jest.mock('next/link', () => { - return function MockedLink({ - children, - href, - className, - ...props - }: { - children: React.ReactNode - href: string - className?: string - [key: string]: unknown - }) { - return ( - - {children} - - ) - } -}) - jest.mock('components/SecondaryCard', () => { return function MockedSecondaryCard({ title, diff --git a/frontend/__tests__/unit/components/SingleModuleCard.test.tsx b/frontend/__tests__/unit/components/SingleModuleCard.test.tsx index 454273d9c7..e356e6bf13 100644 --- a/frontend/__tests__/unit/components/SingleModuleCard.test.tsx +++ b/frontend/__tests__/unit/components/SingleModuleCard.test.tsx @@ -7,16 +7,6 @@ import { ExperienceLevelEnum, ProgramStatusEnum } from 'types/__generated__/grap import type { Module } from 'types/mentorship' import SingleModuleCard from 'components/SingleModuleCard' -// Mock dependencies -jest.mock('next/navigation', () => ({ - useRouter: jest.fn(), - usePathname: jest.fn(), -})) - -jest.mock('next-auth/react', () => ({ - useSession: jest.fn(), -})) - jest.mock('next/link', () => ({ __esModule: true, default: ({ @@ -47,6 +37,16 @@ jest.mock('next/link', () => ({ ), })) +// Mock dependencies +jest.mock('next/navigation', () => ({ + useRouter: jest.fn(), + usePathname: jest.fn(), +})) + +jest.mock('next-auth/react', () => ({ + useSession: jest.fn(), +})) + jest.mock('react-icons/hi', () => ({ HiUserGroup: (props: React.SVGProps) => ( diff --git a/frontend/__tests__/unit/pages/About.test.tsx b/frontend/__tests__/unit/pages/About.test.tsx index 9c0542ecd2..0d9cee5263 100644 --- a/frontend/__tests__/unit/pages/About.test.tsx +++ b/frontend/__tests__/unit/pages/About.test.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@apollo/client/react' import { addToast } from '@heroui/toast' +import { mockAboutData } from '@mockData/mockAboutData' import { fireEvent, screen, waitFor, within } from '@testing-library/react' -import { mockAboutData } from '@unit/data/mockAboutData' import { useRouter } from 'next/navigation' import React, { act } from 'react' import { render } from 'wrappers/testUtil' diff --git a/frontend/__tests__/unit/pages/ApiKeysPage.test.tsx b/frontend/__tests__/unit/pages/ApiKeysPage.test.tsx index 5aed8d3781..49be7ba55d 100644 --- a/frontend/__tests__/unit/pages/ApiKeysPage.test.tsx +++ b/frontend/__tests__/unit/pages/ApiKeysPage.test.tsx @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/naming-convention */ import { useQuery, useMutation } from '@apollo/client/react' import { addToast } from '@heroui/toast' +import { mockApiKeys, mockCreateApiKeyResult } from '@mockData/mockApiKeysData' import { screen, waitFor, fireEvent, within } from '@testing-library/react' -import { mockApiKeys, mockCreateApiKeyResult } from '@unit/data/mockApiKeysData' import { format, addDays } from 'date-fns' import React from 'react' import { render } from 'wrappers/testUtil' diff --git a/frontend/__tests__/unit/pages/ChapterDetails.test.tsx b/frontend/__tests__/unit/pages/ChapterDetails.test.tsx index 1baabf745e..a9a4e1ec85 100644 --- a/frontend/__tests__/unit/pages/ChapterDetails.test.tsx +++ b/frontend/__tests__/unit/pages/ChapterDetails.test.tsx @@ -1,6 +1,6 @@ import { useQuery } from '@apollo/client/react' +import { mockChapterDetailsData } from '@mockData/mockChapterDetailsData' import { screen, waitFor } from '@testing-library/react' -import { mockChapterDetailsData } from '@unit/data/mockChapterDetailsData' import { render } from 'wrappers/testUtil' import ChapterDetailsPage from 'app/chapters/[chapterKey]/page' diff --git a/frontend/__tests__/unit/pages/Chapters.test.tsx b/frontend/__tests__/unit/pages/Chapters.test.tsx index 6ac844a677..ac1f8e2a3b 100644 --- a/frontend/__tests__/unit/pages/Chapters.test.tsx +++ b/frontend/__tests__/unit/pages/Chapters.test.tsx @@ -1,6 +1,6 @@ +import { mockChapterData } from '@mockData/mockChapterData' import { fireEvent, screen, waitFor } from '@testing-library/react' -import { mockChapterData } from '@unit/data/mockChapterData' import { useRouter } from 'next/navigation' import { render } from 'wrappers/testUtil' import ChaptersPage from 'app/chapters/page' diff --git a/frontend/__tests__/unit/pages/CommitteeDetails.test.tsx b/frontend/__tests__/unit/pages/CommitteeDetails.test.tsx index 61d608c86f..b592d7db3a 100644 --- a/frontend/__tests__/unit/pages/CommitteeDetails.test.tsx +++ b/frontend/__tests__/unit/pages/CommitteeDetails.test.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@apollo/client/react' +import { mockCommitteeDetailsData } from '@mockData/mockCommitteeDetailsData' import { screen, waitFor } from '@testing-library/react' -import { mockCommitteeDetailsData } from '@unit/data/mockCommitteeDetailsData' import { render } from 'wrappers/testUtil' import CommitteeDetailsPage from 'app/committees/[committeeKey]/page' diff --git a/frontend/__tests__/unit/pages/Committees.test.tsx b/frontend/__tests__/unit/pages/Committees.test.tsx index 6c59eb11ae..825521e649 100644 --- a/frontend/__tests__/unit/pages/Committees.test.tsx +++ b/frontend/__tests__/unit/pages/Committees.test.tsx @@ -1,5 +1,5 @@ +import { mockCommitteeData } from '@mockData/mockCommitteeData' import { fireEvent, screen, waitFor } from '@testing-library/react' -import { mockCommitteeData } from '@unit/data/mockCommitteeData' import { useRouter } from 'next/navigation' import { render } from 'wrappers/testUtil' import CommitteesPage from 'app/committees/page' diff --git a/frontend/__tests__/unit/pages/Contribute.test.tsx b/frontend/__tests__/unit/pages/Contribute.test.tsx index 596be0d5aa..d61cd83461 100644 --- a/frontend/__tests__/unit/pages/Contribute.test.tsx +++ b/frontend/__tests__/unit/pages/Contribute.test.tsx @@ -1,5 +1,5 @@ +import { mockContributeData } from '@mockData/mockContributeData' import { fireEvent, screen, waitFor } from '@testing-library/react' -import { mockContributeData } from '@unit/data/mockContributeData' import { useRouter } from 'next/navigation' import { render } from 'wrappers/testUtil' import ContributePage from 'app/contribute/page' @@ -9,10 +9,6 @@ jest.mock('server/fetchAlgoliaData', () => ({ fetchAlgoliaData: jest.fn(), })) -jest.mock('next/link', () => { - return ({ children }) => {children} -}) - jest.mock('components/Pagination', () => jest.fn(({ currentPage, onPageChange, totalPages }) => totalPages > 1 ? ( diff --git a/frontend/__tests__/unit/pages/Header.test.tsx b/frontend/__tests__/unit/pages/Header.test.tsx index 092454557c..9c08ce9898 100644 --- a/frontend/__tests__/unit/pages/Header.test.tsx +++ b/frontend/__tests__/unit/pages/Header.test.tsx @@ -20,18 +20,6 @@ jest.mock('next/image', () => ({ }, })) -// Mock next/link -jest.mock('next/link', () => { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - return function MockLink({ href, children, onClick, className, ...props }: any) { - return ( - - {children} - - ) - } -}) - jest.mock('react-icons/fa', () => ({ FaBars: (props: React.SVGProps) => , FaTimes: (props: React.SVGProps) => , diff --git a/frontend/__tests__/unit/pages/Home.test.tsx b/frontend/__tests__/unit/pages/Home.test.tsx index 8300ff8a4c..1fa3cdf5cf 100644 --- a/frontend/__tests__/unit/pages/Home.test.tsx +++ b/frontend/__tests__/unit/pages/Home.test.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@apollo/client/react' import { addToast } from '@heroui/toast' +import { mockAlgoliaData, mockGraphQLData } from '@mockData/mockHomeData' import { fireEvent, screen, waitFor } from '@testing-library/react' -import { mockAlgoliaData, mockGraphQLData } from '@unit/data/mockHomeData' import millify from 'millify' import { useRouter } from 'next/navigation' import { render } from 'wrappers/testUtil' @@ -57,10 +57,6 @@ jest.mock('components/Modal', () => { return ModalMock }) -jest.mock('next/link', () => { - return ({ children }) => children -}) - describe('Home', () => { let mockRouter: { push: jest.Mock } diff --git a/frontend/__tests__/unit/pages/ModuleDetails.test.tsx b/frontend/__tests__/unit/pages/ModuleDetails.test.tsx index 48d802c06e..2d82af0111 100644 --- a/frontend/__tests__/unit/pages/ModuleDetails.test.tsx +++ b/frontend/__tests__/unit/pages/ModuleDetails.test.tsx @@ -1,6 +1,6 @@ import { useQuery } from '@apollo/client/react' +import { mockModuleData } from '@mockData/mockModuleData' import { screen, waitFor } from '@testing-library/react' -import { mockModuleData } from '@unit/data/mockModuleData' import { useParams } from 'next/navigation' import { render } from 'wrappers/testUtil' import { handleAppError } from 'app/global-error' diff --git a/frontend/__tests__/unit/pages/ModuleDetailsPage.test.tsx b/frontend/__tests__/unit/pages/ModuleDetailsPage.test.tsx index 32829067ac..b1e6e78911 100644 --- a/frontend/__tests__/unit/pages/ModuleDetailsPage.test.tsx +++ b/frontend/__tests__/unit/pages/ModuleDetailsPage.test.tsx @@ -1,6 +1,6 @@ import { useQuery } from '@apollo/client/react' +import { mockModuleData } from '@mockData/mockModuleData' import { screen, waitFor } from '@testing-library/react' -import { mockModuleData } from '@unit/data/mockModuleData' import { useParams } from 'next/navigation' import { render } from 'wrappers/testUtil' import { handleAppError } from 'app/global-error' diff --git a/frontend/__tests__/unit/pages/Organization.test.tsx b/frontend/__tests__/unit/pages/Organization.test.tsx index 0dc1cc476a..f96a5f93dc 100644 --- a/frontend/__tests__/unit/pages/Organization.test.tsx +++ b/frontend/__tests__/unit/pages/Organization.test.tsx @@ -1,5 +1,5 @@ +import { mockOrganizationData } from '@mockData/mockOrganizationData' import { fireEvent, screen, waitFor } from '@testing-library/react' -import { mockOrganizationData } from '@unit/data/mockOrganizationData' import { useRouter } from 'next/navigation' import { render } from 'wrappers/testUtil' import Organization from 'app/organizations/page' diff --git a/frontend/__tests__/unit/pages/OrganizationDetails.test.tsx b/frontend/__tests__/unit/pages/OrganizationDetails.test.tsx index 0098fad1bd..4d89222941 100644 --- a/frontend/__tests__/unit/pages/OrganizationDetails.test.tsx +++ b/frontend/__tests__/unit/pages/OrganizationDetails.test.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@apollo/client/react' import { addToast } from '@heroui/toast' +import { mockOrganizationDetailsData } from '@mockData/mockOrganizationData' import { screen, waitFor } from '@testing-library/react' -import { mockOrganizationDetailsData } from '@unit/data/mockOrganizationData' import { render } from 'wrappers/testUtil' import OrganizationDetailsPage from 'app/organizations/[organizationKey]/page' import { formatDate } from 'utils/dateFormatter' diff --git a/frontend/__tests__/unit/pages/Program.test.tsx b/frontend/__tests__/unit/pages/Program.test.tsx index f46b6107ab..51bfdabf46 100644 --- a/frontend/__tests__/unit/pages/Program.test.tsx +++ b/frontend/__tests__/unit/pages/Program.test.tsx @@ -1,5 +1,5 @@ +import { mockPrograms } from '@mockData/mockProgramData' import { fireEvent, screen, waitFor } from '@testing-library/react' -import { mockPrograms } from '@unit/data/mockProgramData' import { useRouter } from 'next/navigation' import { render } from 'wrappers/testUtil' import ProgramsPage from 'app/mentorship/programs/page' diff --git a/frontend/__tests__/unit/pages/ProgramDetails.test.tsx b/frontend/__tests__/unit/pages/ProgramDetails.test.tsx index e1a035113d..79b1079f8f 100644 --- a/frontend/__tests__/unit/pages/ProgramDetails.test.tsx +++ b/frontend/__tests__/unit/pages/ProgramDetails.test.tsx @@ -1,6 +1,6 @@ import { useQuery } from '@apollo/client/react' +import mockProgramDetailsData from '@mockData/mockProgramData' import { screen, waitFor } from '@testing-library/react' -import mockProgramDetailsData from '@unit/data/mockProgramData' import { render } from 'wrappers/testUtil' import ProgramDetailsPage from 'app/mentorship/programs/[programKey]/page' import '@testing-library/jest-dom' diff --git a/frontend/__tests__/unit/pages/ProgramDetailsMentorship.test.tsx b/frontend/__tests__/unit/pages/ProgramDetailsMentorship.test.tsx index 7c405b8d88..a636d3973a 100644 --- a/frontend/__tests__/unit/pages/ProgramDetailsMentorship.test.tsx +++ b/frontend/__tests__/unit/pages/ProgramDetailsMentorship.test.tsx @@ -1,6 +1,6 @@ import { useQuery, useMutation } from '@apollo/client/react' +import mockProgramDetailsData from '@mockData/mockProgramData' import { screen, waitFor, fireEvent } from '@testing-library/react' -import mockProgramDetailsData from '@unit/data/mockProgramData' import { useSession } from 'next-auth/react' import { render } from 'wrappers/testUtil' import ProgramDetailsPage from 'app/my/mentorship/programs/[programKey]/page' diff --git a/frontend/__tests__/unit/pages/ProjectDetails.test.tsx b/frontend/__tests__/unit/pages/ProjectDetails.test.tsx index 563a62d161..7875b874ed 100644 --- a/frontend/__tests__/unit/pages/ProjectDetails.test.tsx +++ b/frontend/__tests__/unit/pages/ProjectDetails.test.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@apollo/client/react' import { addToast } from '@heroui/toast' +import { mockProjectDetailsData } from '@mockData/mockProjectDetailsData' import { act, fireEvent, screen, waitFor, within } from '@testing-library/react' -import { mockProjectDetailsData } from '@unit/data/mockProjectDetailsData' import { render } from 'wrappers/testUtil' import ProjectDetailsPage from 'app/projects/[projectKey]/page' diff --git a/frontend/__tests__/unit/pages/ProjectHealthDashboardMetricsDetails.test.tsx b/frontend/__tests__/unit/pages/ProjectHealthDashboardMetricsDetails.test.tsx index 0a7d7fd480..a05bca689e 100644 --- a/frontend/__tests__/unit/pages/ProjectHealthDashboardMetricsDetails.test.tsx +++ b/frontend/__tests__/unit/pages/ProjectHealthDashboardMetricsDetails.test.tsx @@ -1,6 +1,6 @@ import { useQuery } from '@apollo/client/react' +import { mockProjectsDashboardMetricsDetailsData } from '@mockData/mockProjectsDashboardMetricsDetailsData' import { render, screen, waitFor } from '@testing-library/react' -import { mockProjectsDashboardMetricsDetailsData } from '@unit/data/mockProjectsDashboardMetricsDetailsData' import ProjectHealthMetricsDetails from 'app/projects/dashboard/metrics/[projectKey]/page' jest.mock('react-apexcharts', () => { diff --git a/frontend/__tests__/unit/pages/Projects.test.tsx b/frontend/__tests__/unit/pages/Projects.test.tsx index 245c249fa9..c8a8399fcc 100644 --- a/frontend/__tests__/unit/pages/Projects.test.tsx +++ b/frontend/__tests__/unit/pages/Projects.test.tsx @@ -1,5 +1,5 @@ +import mockProjectData from '@mockData/mockProjectData' import { waitFor, screen, fireEvent } from '@testing-library/react' -import mockProjectData from '@unit/data/mockProjectData' import { useRouter } from 'next/navigation' import { render } from 'wrappers/testUtil' import ProjectsPage from 'app/projects/page' diff --git a/frontend/__tests__/unit/pages/ProjectsHealthDashboardMetrics.test.tsx b/frontend/__tests__/unit/pages/ProjectsHealthDashboardMetrics.test.tsx index b4ec1e4a95..346c6599fd 100644 --- a/frontend/__tests__/unit/pages/ProjectsHealthDashboardMetrics.test.tsx +++ b/frontend/__tests__/unit/pages/ProjectsHealthDashboardMetrics.test.tsx @@ -1,6 +1,6 @@ import { useQuery } from '@apollo/client/react' +import { mockHealthMetricsData } from '@mockData/mockProjectsHealthMetricsData' import { render, screen, waitFor, fireEvent } from '@testing-library/react' -import { mockHealthMetricsData } from '@unit/data/mockProjectsHealthMetricsData' import MetricsPage from 'app/projects/dashboard/metrics/page' jest.mock('@apollo/client/react', () => ({ diff --git a/frontend/__tests__/unit/pages/ProjectsHealthDashboardOverview.test.tsx b/frontend/__tests__/unit/pages/ProjectsHealthDashboardOverview.test.tsx index 1fde2f27f9..a1c2c9e649 100644 --- a/frontend/__tests__/unit/pages/ProjectsHealthDashboardOverview.test.tsx +++ b/frontend/__tests__/unit/pages/ProjectsHealthDashboardOverview.test.tsx @@ -1,6 +1,6 @@ import { useQuery } from '@apollo/client/react' +import { mockProjectsDashboardOverviewData } from '@mockData/mockProjectsDashboardOverviewData' import { render, screen, waitFor } from '@testing-library/react' -import { mockProjectsDashboardOverviewData } from '@unit/data/mockProjectsDashboardOverviewData' import millify from 'millify' import ProjectsDashboardPage from 'app/projects/dashboard/page' diff --git a/frontend/__tests__/unit/pages/RepositoryDetails.test.tsx b/frontend/__tests__/unit/pages/RepositoryDetails.test.tsx index c64784a179..226eba0fd6 100644 --- a/frontend/__tests__/unit/pages/RepositoryDetails.test.tsx +++ b/frontend/__tests__/unit/pages/RepositoryDetails.test.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@apollo/client/react' import { addToast } from '@heroui/toast' +import { mockRepositoryData } from '@mockData/mockRepositoryData' import { act, fireEvent, screen, waitFor } from '@testing-library/react' -import { mockRepositoryData } from '@unit/data/mockRepositoryData' import { render } from 'wrappers/testUtil' import RepositoryDetailsPage from 'app/organizations/[organizationKey]/repositories/[repositoryKey]/page' diff --git a/frontend/__tests__/unit/pages/SnapshotDetails.test.tsx b/frontend/__tests__/unit/pages/SnapshotDetails.test.tsx index c9559e8b99..60f48db4b9 100644 --- a/frontend/__tests__/unit/pages/SnapshotDetails.test.tsx +++ b/frontend/__tests__/unit/pages/SnapshotDetails.test.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@apollo/client/react' import { addToast } from '@heroui/toast' +import { mockSnapshotDetailsData } from '@mockData/mockSnapshotData' import { fireEvent, screen, waitFor } from '@testing-library/react' -import { mockSnapshotDetailsData } from '@unit/data/mockSnapshotData' import { render } from 'wrappers/testUtil' import SnapshotDetailsPage from 'app/community/snapshots/[id]/page' diff --git a/frontend/__tests__/unit/pages/UserDetails.test.tsx b/frontend/__tests__/unit/pages/UserDetails.test.tsx index 67f8e15409..5f29316b20 100644 --- a/frontend/__tests__/unit/pages/UserDetails.test.tsx +++ b/frontend/__tests__/unit/pages/UserDetails.test.tsx @@ -1,7 +1,7 @@ import { useQuery } from '@apollo/client/react' import { addToast } from '@heroui/toast' +import { mockUserDetailsData } from '@mockData/mockUserDetails' import { screen, waitFor } from '@testing-library/react' -import { mockUserDetailsData } from '@unit/data/mockUserDetails' import { render } from 'wrappers/testUtil' import '@testing-library/jest-dom' import UserDetailsPage from 'app/members/[memberKey]/page' diff --git a/frontend/__tests__/unit/pages/Users.test.tsx b/frontend/__tests__/unit/pages/Users.test.tsx index c52f9eec97..e8a6d49bac 100644 --- a/frontend/__tests__/unit/pages/Users.test.tsx +++ b/frontend/__tests__/unit/pages/Users.test.tsx @@ -1,5 +1,5 @@ +import { mockUserData } from '@mockData/mockUserData' import { fireEvent, screen, waitFor } from '@testing-library/react' -import { mockUserData } from '@unit/data/mockUserData' import { useRouter } from 'next/navigation' import { render } from 'wrappers/testUtil' import UsersPage from 'app/members/page' diff --git a/frontend/jest.config.ts b/frontend/jest.config.ts index 20b43c21f2..1529d2eea8 100644 --- a/frontend/jest.config.ts +++ b/frontend/jest.config.ts @@ -35,11 +35,16 @@ const config: Config = { globals: {}, setupFilesAfterEnv: ['/jest.setup.ts'], testEnvironment: 'jest-environment-jsdom', - testPathIgnorePatterns: ['/__tests__/unit/data/', '/__tests__/e2e/'], + testPathIgnorePatterns: [ + '/__tests__/unit/data/', + '/__tests__/e2e/', + '/__tests__/mockData/', + ], transform: { '^.+\\.tsx?$': '@swc/jest', }, moduleNameMapper: { + '^@mockData/(.*)$': '/__tests__/mockData/$1', '^@unit/(.*)$': '/__tests__/unit/$1', '^@/(.*)$': '/src/$1', '\\.(scss|sass|css)$': 'identity-obj-proxy', diff --git a/frontend/jest.setup.ts b/frontend/jest.setup.ts index 83961a455a..3c700f73e8 100644 --- a/frontend/jest.setup.ts +++ b/frontend/jest.setup.ts @@ -39,11 +39,66 @@ jest.mock('next/navigation', () => { return { ...actual, useParams: jest.fn(() => ({})), + usePathname: jest.fn(() => '/'), useRouter: jest.fn(() => mockRouter), useSearchParams: jest.fn(() => new URLSearchParams()), } }) +jest.mock('next/link', () => ({ + __esModule: true, + default: function MockedLink({ + children, + href, + className, + onClick, + ...props + }: { + children: React.ReactNode + href?: string + className?: string + onClick?: (e: React.MouseEvent) => void + [key: string]: unknown + }) { + return React.createElement( + 'a', + { + href, + className, + ...props, + onClick: (e) => { + e.preventDefault() + onClick?.(e as React.MouseEvent) + }, + }, + children + ) + }, +})) + +jest.mock('next/image', () => ({ + __esModule: true, + default: ({ + src, + alt, + fill, + objectFit, + ...props + }: { + src: string + alt: string + fill?: boolean + objectFit?: 'fill' | 'contain' | 'cover' | 'none' | 'scale-down' + [key: string]: unknown + }) => + React.createElement('img', { + src, + alt, + style: fill ? { objectFit: objectFit as React.CSSProperties['objectFit'] } : undefined, + ...props, + }), +})) + beforeAll(() => { if (globalThis !== undefined) { jest.spyOn(globalThis, 'requestAnimationFrame').mockImplementation((cb) => { diff --git a/frontend/package.json b/frontend/package.json index 01d7a6a0ba..b379b6f97f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "lint": "eslint . --config eslint.config.mjs --fix --max-warnings=0", "lint:check": "eslint . --config eslint.config.mjs --max-warnings=0", "start": "next start", + "test:a11y": "NODE_OPTIONS='--experimental-vm-modules --no-warnings=DEP0040' jest __tests__/a11y/components --coverage=false", "test:e2e": "npx playwright test", "test:unit": "tsc --noEmit && NODE_OPTIONS='--experimental-vm-modules --no-warnings=DEP0040' jest" }, diff --git a/frontend/src/app/settings/api-keys/page.tsx b/frontend/src/app/settings/api-keys/page.tsx index d65febe491..b050fb620a 100644 --- a/frontend/src/app/settings/api-keys/page.tsx +++ b/frontend/src/app/settings/api-keys/page.tsx @@ -251,7 +251,7 @@ export default function Page() { - API Key Limits + API Key Limits You can have a maximum of {MAX_ACTIVE_KEYS} active API keys at any time. Currently:{' '} diff --git a/frontend/src/components/DonutBarChart.tsx b/frontend/src/components/DonutBarChart.tsx index c610275c1b..c002d8ad52 100644 --- a/frontend/src/components/DonutBarChart.tsx +++ b/frontend/src/components/DonutBarChart.tsx @@ -23,7 +23,7 @@ const DonutBarChart: React.FC<{ return ( } icon={icon}> - + = ({ {dropdownOpen && ( - + {options.map((option) => { const handleMenuItemClick = (e: React.MouseEvent) => { e.preventDefault() diff --git a/frontend/src/components/Footer.tsx b/frontend/src/components/Footer.tsx index 54e98b3bd2..e0b0877920 100644 --- a/frontend/src/components/Footer.tsx +++ b/frontend/src/components/Footer.tsx @@ -31,6 +31,7 @@ export default function Footer() { className="flex w-full items-center justify-between bg-transparent pl-0 text-left text-lg font-semibold focus:ring-slate-400 focus:outline-hidden lg:cursor-default" aria-expanded={openSection === section.title} aria-controls={`footer-section-${section.title}`} + data-testid={`footer-section-button-${section.title}`} > {section.title} diff --git a/frontend/src/components/LogoCarousel.tsx b/frontend/src/components/LogoCarousel.tsx index 1a05054a0d..4eaae45ed5 100644 --- a/frontend/src/components/LogoCarousel.tsx +++ b/frontend/src/components/LogoCarousel.tsx @@ -45,7 +45,7 @@ export default function MovingLogos({ sponsors }: MovingLogosProps) { style={{ objectFit: 'contain' }} /> ) : ( - + {sponsor.name} )} diff --git a/frontend/src/components/Pagination.tsx b/frontend/src/components/Pagination.tsx index e0f69b1e3b..b9875cb4e3 100644 --- a/frontend/src/components/Pagination.tsx +++ b/frontend/src/components/Pagination.tsx @@ -71,10 +71,7 @@ const Pagination: React.FC = ({ {pageNumbers.map((number, index) => ( {number === '...' ? ( - + ) : ( diff --git a/frontend/src/components/ShowMoreButton.tsx b/frontend/src/components/ShowMoreButton.tsx index 54f01a3ff1..a3f9e4eed0 100644 --- a/frontend/src/components/ShowMoreButton.tsx +++ b/frontend/src/components/ShowMoreButton.tsx @@ -13,6 +13,7 @@ const ShowMoreButton = ({ onToggle }: { onToggle: () => void }) => { return (
Test children
You can have a maximum of {MAX_ACTIVE_KEYS} active API keys at any time. Currently:{' '} diff --git a/frontend/src/components/DonutBarChart.tsx b/frontend/src/components/DonutBarChart.tsx index c610275c1b..c002d8ad52 100644 --- a/frontend/src/components/DonutBarChart.tsx +++ b/frontend/src/components/DonutBarChart.tsx @@ -23,7 +23,7 @@ const DonutBarChart: React.FC<{ return ( } icon={icon}> -