mirror of
https://seed.radicle.dev/rad:z4D5UCArafTzTQpDZNQRuqswh3ury.git
synced 2026-10-18 19:10:45 +08:00
An ADR records how we decide what to test and where, and a checklist lets people and agents check a change against it.
2.9 KiB
2.9 KiB
1. Testing strategy
Date: 2026-09-30
Status
Accepted
Context
The app has many features and few tests. We need a way to decide what to test, at which layer, and in what order, without trying to reach full coverage.
Decision
Prioritise by risk: how bad a failure is (corrupt COB writes are worse than wrong display), how likely it is (complex logic, churn, crossing the Rust/TS boundary) and how hard it is to notice manually (multi-peer, caching, timing).
Test each behaviour at the cheapest layer that can catch its failure:
| Layer | Tool | Scope |
|---|---|---|
| TS unit | Vitest, tests/unit/ |
Pure logic: parsing, routing, formatting, filters, diff and review logic |
| Rust | cargo test |
Trait default implementations, type conversions, error codes |
| E2E | Playwright + test-http-api |
User journeys and every write path against a real Radicle profile |
| Manual | Tauri build | Native-only features: dialogs, clipboard images, drag and drop, badges |
Rules:
- Unit tests come first because they are the cheapest.
- Every bug fix adds a regression test, at the cheapest layer that reproduces the bug, and the test fails without the fix.
- Non-trivial logic inside a component (branching, ordering, grouping,
deduplication, parsing) is extracted into a module in
src/lib/and tested there. A one-line helper with a single caller stays in the component. Comments that explain why the code is the way it is move with it. - Tests assert behaviour: an outcome, a property or a relationship between inputs. They don't restate UI copy, CSS classes, selector strings or constant tables, since such tests only ever change in lockstep with the code.
- A test earns its place by failing when the logic it covers breaks. New tests are checked by mutating that logic (flip a comparison, drop a filter, shift a boundary), and a test that never fails is removed.
- Extracted logic is tested in isolation, so the component flow that wires it into a write path still needs its E2E test.
- Every mutating command gets at least one E2E happy path and one permission or error case. Reads are covered by unit tests plus one E2E test showing the data renders.
- Combinations such as filters and sort orders are covered in unit tests, not in E2E.
- State persisted in
localStoragegets one reload test in E2E. - Sanitisation is asserted in E2E only, since DOMPurify is a no-op under happy-dom.
test-http-apimirrors every Tauri command. Logic lives inradicle-typesso that both drivers stay thin wrappers, and a parity test fails when a command, route or frontendinvokehas no counterpart.
New features follow the testing checklist.