- Fork the repo and clone your fork.
- Create a branch from
main:git checkout -b your-initials/description-of-change
- Install dependencies and verify your toolchain:
npm install npm run lint && npm test && npm run build
- Make your changes, commit, push, and open a pull request.
See the PR template for the pre-flight checklist.
npm test # run all tests
npm test -- --watch # watch mode
npm test -- --coverage # generate coverage reportCoverage expectations are documented in the PR template; the project targets 95% minimum coverage for impacted modules.
Jest is configured (jest.config.js) with the same @/ path alias defined in tsconfig.json:
^@/(.*)$ → src/$1
Import components and utilities using the alias:
import StatusBadge from '@/components/StatusBadge';
import { formatAmount } from '@/lib/preferences';This works in both source files and test files without relative-path gymnastics.
Tests live next to the units they cover in __tests__ directories. Jest discovers files matching **/*.test.{ts,tsx}.
| Location | Tests |
|---|---|
src/components/__tests__/ |
UI component tests |
src/lib/__tests__/ |
Pure utility / helper tests |
src/contexts/__tests__/ |
Context / provider tests |
src/app/**/__tests__/ |
Page-level / route tests |
import { render, screen } from '@testing-library/react';
import StatusBadge from '@/components/StatusBadge';
describe('StatusBadge', () => {
it('renders the status text', () => {
render(<StatusBadge status="Active" />);
expect(screen.getByText('Active')).toBeInTheDocument();
});
});The project uses jest-axe to audit rendered DOM against WCAG rules. Three helpers are exported from src/test-utils/a11y.tsx, and toHaveNoViolations is extended globally in jest.setup.ts.
Render a component and immediately assert zero axe violations. The canonical one-liner for most a11y tests:
import { testA11y } from '@/test-utils/a11y';
import EmptyState from '@/components/EmptyState';
it('has no a11y violations', async () => {
await testA11y(
<EmptyState title="No items" description="Nothing here yet." />
);
});Run axe against an arbitrary HTMLElement. Useful when you need to interact with the component before auditing (e.g. open a modal, click a button):
import { render, fireEvent, screen } from '@testing-library/react';
import { assertNoA11yViolations } from '@/test-utils/a11y';
it('toast has no violations after trigger', async () => {
const view = render(<MyToast />);
fireEvent.click(screen.getByRole('button'));
await assertNoA11yViolations(view.container);
});A thin wrapper around @testing-library/react's render. Use it when you need the rendered view but want to defer the axe audit or skip it entirely:
import { renderWithA11y } from '@/test-utils/a11y';
it('renders without crashing', () => {
const view = renderWithA11y(<MyComponent />);
expect(view.container.firstChild).toBeInTheDocument();
});src/components/__tests__/a11y.test.tsx is the primary reference. It demonstrates:
- Using
testA11yfor static component snapshots. - Using
assertNoA11yViolationsafter user interactions (toast triggers). - Using
renderWithA11yfor non-audit assertions alongside a11y checks. - Testing components under multiple themes via
data-themetoggles. - Importing from
@/test-utils/a11y,@/components/*, and@/contexts/*.
Run npm test -- --coverage to view per-file coverage. The project enforces a 95% line and branch coverage floor on any module touched by a PR. If your changes introduce new components or utilities, add corresponding tests to maintain that threshold.
npm run lint # ESLint
npm run build # Next.js production buildBoth must pass before a PR can be merged. CI runs lint, build, tests, and npm audit on every push and pull request to main.