Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 11 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -370,14 +370,17 @@ The `/agents` page lists every agent identity seen by the backend, paginated wit

## Commands

| Command | Description |
| ----------------------- | --------------------------- | ----------------------------------- |
| `npm run build` | Production build |
| `npm test` | Run Jest tests |
| `npm run test:coverage` | Run Jest with coverage | (not defined in this repo snapshot) |
| `npm run dev` | Development server |
| `npm run lint` | Run ESLint |
| `npm run typecheck` | Run the TypeScript compiler |
| Command | Description |
| ----------------------- | -------------------------------------------------------- |
| `npm run build` | Production build |
| `npm test` | Run Jest tests (no coverage) |
| `npm run test:coverage` | Run Jest with coverage — enforces all threshold gates |
| `npm run test:watch` | Run Jest in interactive watch mode (local dev) |
| `npm run dev` | Development server |
| `npm run lint` | Run ESLint |
| `npm run typecheck` | Run the TypeScript compiler |

See [docs/testing.md](docs/testing.md) for the full coverage reference — per-file thresholds, report locations, and how to read the terminal output.

## CI/CD

Expand Down
100 changes: 100 additions & 0 deletions docs/testing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# Testing & Coverage

## Running tests

| 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.

## Coverage reports

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.

## Coverage thresholds

Thresholds are enforced by Jest itself via `coverageThreshold` in [`jest.config.ts`](../jest.config.ts). A build **fails** if any threshold is not met — the script exits non-zero.

### Global floor

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.

### Per-file gates

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.

## Coverage scope

`collectCoverageFrom` in `jest.config.ts` includes:

```
src/**/*.{ts,tsx}
```

and explicitly excludes:

- `src/**/*.d.ts` — type declaration files
- `src/**/__tests__/**` — test files themselves

## Reading the terminal report

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.

## Adding coverage for a new file

1. Write tests until the file's metrics meet or exceed the per-file thresholds above.
2. Add a matching entry to the `coverageThreshold` block in `jest.config.ts` to lock it in.
3. Run `npm run test:coverage` locally to confirm the gate passes before opening a PR.

## CI

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.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
"start": "next start",
"lint": "eslint .",
"typecheck": "tsc --noEmit",
"test": "jest"
"test": "jest",
"test:coverage": "jest --coverage",
"test:watch": "jest --watch"
},
"dependencies": {
"next": "16.1.6",
Expand Down
Loading