| Command | Description |
|---|---|
npm test |
Run the full Jest suite once (no coverage) |
npm run test:coverage |
Run the full Jest suite and collect coverage |
npm run test:watch |
Run Jest in interactive watch mode (local dev only) |
test:coverage is the canonical way to verify coverage gates locally and is what CI checks on every PR.
When you run npm run test:coverage, Jest writes three report formats into coverage/:
| Format | Location | Use |
|---|---|---|
text |
printed to the terminal | Quick scan after a run |
lcov |
coverage/lcov.info |
Import into IDE coverage overlays (VS Code Coverage Gutters, etc.) |
json-summary |
coverage/coverage-summary.json |
Machine-readable; consumed by CI badge scripts |
Open coverage/lcov-report/index.html in a browser for the full line-by-line HTML report.
Thresholds are enforced by Jest itself via coverageThreshold in jest.config.ts. A build fails if any threshold is not met — the script exits non-zero.
Applies to the aggregate of every file not listed in the per-file table below.
| Metric | Threshold |
|---|---|
| Statements | 20% |
| Branches | 40% |
| Functions | 28% |
| Lines | 20% |
The global floor is intentionally low because many pages are stubs. It exists to prevent wholesale regressions, not to enforce completeness on every file.
These files are fully tested and locked at their current coverage level. Any PR that drops coverage below these numbers will fail CI.
| File | Statements | Branches | Functions | Lines |
|---|---|---|---|---|
src/app/page.tsx |
100% | 100% | 100% | 100% |
src/components/Badge.tsx |
100% | 100% | 100% | 100% |
src/components/Breadcrumb.tsx |
100% | 100% | 100% | 100% |
src/components/Card.tsx |
100% | 100% | 100% | 100% |
src/components/ConfirmDialog.tsx |
100% | 94% | 100% | 100% |
src/components/Header.tsx |
100% | 100% | 100% | 100% |
src/components/Pagination.tsx |
100% | 100% | 100% | 100% |
src/components/TextField.tsx |
100% | 100% | 100% | 100% |
src/components/ThemeToggle.tsx |
100% | 100% | 100% | 100% |
src/components/ToastProvider.tsx |
100% | 100% | 100% | 100% |
src/lib/apiClient.ts |
80% | 80% | 70% | 80% |
src/lib/resolveApiBase.ts |
100% | 100% | 100% | 100% |
src/lib/securityHeaders.ts |
100% | 100% | 100% | 100% |
src/lib/useDebounce.ts |
100% | 100% | 100% | 100% |
src/lib/format.ts |
100% | 92% | 100% | 100% |
src/lib/theme.ts |
100% | 81% | 100% | 100% |
Near-100% files (
format.ts,theme.ts,ConfirmDialog.tsx): branches are locked at their current measured value rather than 100% because a small number of defensive branches are not reachable through the public API. The locks still prevent regression.
collectCoverageFrom in jest.config.ts includes:
src/**/*.{ts,tsx}
and explicitly excludes:
src/**/*.d.ts— type declaration filessrc/**/__tests__/**— test files themselves
After npm run test:coverage the terminal prints a table like:
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
src/components/Badge.tsx | 100 | 100 | 100 | 100 |
src/lib/format.ts | 100 | 92.30 | 100 | 100 | 47
The Uncovered Line #s column points directly to the lines not exercised by any test. Use those line numbers with the lcov HTML report for context.
- Write tests until the file's metrics meet or exceed the per-file thresholds above.
- Add a matching entry to the
coverageThresholdblock injest.config.tsto lock it in. - Run
npm run test:coveragelocally to confirm the gate passes before opening a PR.
The GitHub Actions workflow (.github/workflows/ci.yml) runs npm test on every push and pull request to main. To add coverage enforcement to CI, change that step to npm run test:coverage — the non-zero exit from Jest will fail the job automatically if any threshold is breached.