Test-Driven Development
Iron Law
No prod code without failing test first. No exceptions.
Anti-Pattern: Horizontal Slices
DO NOT write all tests first, then all impl. Bulk tests test imagined behavior, not actual.
Correct: Vertical slices -- one test -> one impl -> repeat.
WRONG: RED: test1,test2,test3 -> GREEN: impl1,impl2,impl3
RIGHT: RED->GREEN: test1->impl1 -> RED->GREEN: test2->impl2
State Machine
Full state diagram: REFERENCE.md#state-machine.
Workflow
0. PLAN -- Coverage gap analysis
- Use project's domain glossary for test/interface names; respect ADRs in the area
- Run
vitest run --coverage.enabled --coverage.reporter=text - Find uncovered lines/branches/functions in changed files -> test targets
- Confirm behaviors w/ user (prioritize gaps over covered)
- Find deep module chance (small interface, deep impl)
- Design interfaces for testability; test behaviour through public interface, not impl (tests.md)
1. RED -- Failing test (tracer bullet)
- ONE test, ONE behavior, clear name
- Real code, no mocks (unless unavoidable -- see mocking.md)
- Verify fails for RIGHT reason
2. GREEN -- Minimal code to pass
- Only enough to pass | no premature optimization
- Run test | see green
3. REFACTOR -- Clean up while green
- Kill duplication | fix naming | deepen modules
- Tests after every change -- stay green
- Never refactor while RED. Get GREEN first.
- Flag unit tests >500ms, integration >2s
- Avoid per-keystroke sim (slow, flaky) -> bulk input
- Commit when clean
Reactive TDD with Monitor
Monitor: vitest --watch -- stream pass/fail as edit. Edit->fail->fix->pass->refactor->repeat.
4. REPEAT -- Next behavior
RED->GREEN->REFACTOR per behavior. One at a time.
Per-Cycle Checklist
- Test describe behavior, not impl
- Test use public interface only
- Test survive internal refactor
- Code minimal for this test
- No speculative features
Test Classification
| Suffix | Purpose | DOM? |
|---|---|---|
.test.ts |
Unit -- pure logic | No |
.test.tsx / .integration.tsx |
Integration -- render components | Yes |
e2e/*.spec.ts |
E2E -- Playwright browser | Browser |
Visual Regression Tests (Route Files)
New TanStack Router routes need *.browser.test.tsx sibling -- only if project use vitest browser mode (existing *.browser.test.* files or @vitest/browser dep). Skip layout/redirect-only routes. See REFERENCE.md.
When Done
- All pass (
vitest run) - Zero warnings -- green run w/
DeprecationWarning/ Reactact()/ unhandled rejection /@ts-ignoreis NOT done.test-warning-checkhook surfaces; fix at source. Escape:// allow: test-warningw/ reason. - No async leaks (
vitest run --detectAsyncLeaks) -- Stop hook run auto - No
setTimeouthacks -- condition-based wait - Coverage gaps closed -- re-run coverage, verify changed files
- Selector priority:
getByRole>getByText>getByTestId>querySelector - Portal tests:
defaultOpenfor content tests |waitForfor close assertions - Tests verify behavior, not impl |
expect.soft()for multi-assertion state tests - CI green AND
ci-warning-auditclean --gh run view <id> --log | grep -E 'Warning|Deprecation'returns nothing
See REFERENCE.md for element selectors, portal testing, mock patterns, diagnostics, Vitest config.