Testing.
How we think about testing — why we write a test at all, where it belongs, and how to keep the suite fast. This is the mindset, not the syntax. The exact runners, helpers, and assertions live in the code and in each stack's AGENTS.md; the cross-cutting strategy lives in TESTING.md, which you should read before adding tests for any non-trivial change.
Philosophy
- Pure logic → unit test, no mocks. If a function is hard to test without mocks, extract the pure part instead of adding more mocks.
- Test for one of three reasons: regression (lock a bug fix), intent (document a requirement), or refactor safety. If none apply, don't write the test.
- Names read like requirements — a test title should describe the behavior, not the function.
The five-layer pyramid
| Layer | What | Speed | Share |
|---|---|---|---|
| L1 | Pure unit — no mocks | <10ms | 70% |
| L2 | Service + mocked dependencies | ~50ms | 20% |
| L3 | Emulator integration (real Firestore emulator) | ~500ms | 5% |
| L4 | Component (React Testing Library, jsdom) | — | 4% |
| L5 | End-to-end golden path | — | 1% |
Read the percentages as intent, not a quota: most of our confidence should come from fast, pure tests at the base, and only a thin layer of slow end-to-end tests at the top. A suite shaped like this stays fast and rarely flakes.
Where a test goes
Most "where does this test go?" questions answer themselves if you ask what you're actually trying to protect. The bias is always to push a test as far down the pyramid as it will go.
- It's a calculation or a decision. Pure unit test, no mocks. If you can't test it without mocks, the logic isn't pure yet — extract the pure part first.
- It coordinates several pieces. A service-level test with its dependencies stubbed. Test the orchestration, not the collaborators.
- It depends on real datastore behavior. An integration test against the emulator — but only when the behavior genuinely lives in the database, not in your code.
- It's something a user sees or clicks. A component test, and sparingly. Prefer to pull the logic out of the component and unit-test that instead.
- It's a whole flow that must not break. One end-to-end test on the golden path. These are expensive — keep them few.
Regression-lock convention
When a test exists purely to lock a fixed bug, name it with the short commit SHA so the reason is traceable:
it('regression_a1b2c3d: does not double-charge on retry', ...)
Source: TESTING.md and the per-stack AGENTS.md files. Compiled 2026-06-07. This page is the mindset; the code is the source of truth for test syntax and helpers.