Testing rules and requirements. Load before writing tests or reviewing PRs. Defines what must be tested, coverage targets, adversarial testing, and the confidence checklist.
Write tests. Not too many. Mostly E2E.
Tests exist to catch regressions, not to prove we can write tests. Every test should answer: "would this catch a bug that would hurt a user?"
| Change Type | Required Test | Target |
|---|---|---|
| New util / pure function | Unit test | Cover happy path + 2-3 edge cases |
| New hook with logic | Unit test | Mock deps, test state transitions |
| Bug fix | Regression test | Reproduce the bug as a test FIRST, then fix |
| New feature / page | E2E lite + full | See case count targets below |
| New API endpoint handler | Unit test for transform logic | Test request/response mapping |
| Complex component logic |
| Unit test recommended |
| Conditional rendering, computed values |
Every E2E spec needs both lite (CI smoke gate) and full (comprehensive) variants.
| Feature Type | Happy Path | Error/Validation | Edge Cases | Interaction | Total Target |
|---|---|---|---|---|---|
| CRUD page (create, read, edit, delete) | 2-3 | 1-2 | 1 | — | 4-6 |
| Complex form (multi-step, pickers, dates) | 1-2 | 2-3 | 1-2 | — | 4-7 |
| Data table (sort, filter, paginate) | 1 | 1 | 1 | 2-3 | 5-6 |
| Timeline / visualization | 1 | — | 1 | 5-10 | 7-12 |
| Dashboard / read-only | 1 | — | 1 | — | 2 |
| Settings / config | 1 | 1 | — | — | 2 |
These are targets, not mandates. A simple CRUD page with 4 well-chosen tests beats a complex form with 20 shallow ones.
Pick the ones that apply to your feature. Not every feature needs all of these.
After proving it works, try to break it:
<script>, emoji, 200-char strings)These catch the bugs users actually hit. One adversarial test is worth five more happy-path tests.
Unit tests are for pure logic — functions you can call with inputs and assert outputs. Don't unit-test React components unless they contain genuinely complex conditional logic.
| What | Target | Example |
|---|---|---|
| Utility function | Happy path + 3-5 edge cases | formatDate(), filterItems() |
| Validation schema | All branches + boundaries | schema.parse(invalid) |
| Data transformer | Input/output pairs covering shapes | transformResponse() |
| Custom hook with logic | State transitions, cleanup | useActions() |
| Complex filter logic | All operators × types | applyFilter() |
Don't unit-test: simple getters, thin wrappers, components that just render props.
| Status | Meaning | When It's OK |
|---|---|---|
| GREEN | Unit tests for logic + E2E coverage | Ship freely |
| YELLOW | Has some coverage but gaps | OK to ship if gaps are in low-risk areas |
| RED | No automated test coverage | Needs at least a lite E2E before next release |
RED → YELLOW (minimum viable):
YELLOW → GREEN (solid):
Don't backfill tests on stable low-risk code just to improve metrics. Add tests when you touch a domain.
DO backfill RED domains that are high-risk (core workflows users depend on daily). Prioritize by file count × risk.
When you can only write N tests, pick based on risk × frequency:
| Priority | What | Why |
|---|---|---|
| P0 | Auth, core CRUD, data mutations | Bugs here lose user trust |
| P1 | Complex business logic (filters, timelines, assignments) | Bugs here waste user time |
| P2 | Forms, multi-step flows | Bugs here block user tasks |
| P3 | Display/read-only pages, dashboards | Bugs here are annoying but not blocking |
| P4 | Settings, admin, rarely-used features | Low impact |
src/foo/foo.test.tstests/playwright/{feature}.{lite|full}.spec.ts (or project convention)