TESTS.md — the test suite & the two test rules
Everything here follows the repo rules (README — "The rules of this repo"):
- Rule 2 — every solved edge case gets a test. A bug fix, or a change that handles an edge case, is not done until a regression test that reproduces the case is committed with it (same commit, test named after the case).
- Rule 3 — passing tests is as important as commit + push. A commit, a
push, or a deploy that ships with a red suite is not done.
scripts/deploy.shruns the suite first and aborts on failure — a red suite is exactly as bad as an unpushed commit.
Run
Bun's built-in test runner — no dependency, no extra install:
bun --smol test --timeout 30000 --isolate # whole suite (or: npm run test)
bun --smol test --timeout 30000 --isolate tests/intake.test.ts # one file
bun --smol test --timeout 30000 --isolate -t "rate" # one named case
bun --smol test --timeout 30000 --isolate is step 1 of the deploy loop (scripts/deploy.sh). Run it
before commit and re-run it before push whenever the diff changed
code since the last green run.
Where tests live
tests/ in the repo root, named after the module under test and mirroring
src/lib/server/*.ts:
| Under test | File | Pinned to |
|---|---|---|
| costing/conversion engine | tests/yarnMath.test.ts |
docs/COSTING.md §5 (owner-sheet trace) |
| capture text parser (edge cases incl. rate fallback) | tests/intake.test.ts |
git 96f102a ("intake rate regex fallback") |
| tippan krill-parts solver | tests/tippanSolver.test.ts |
PLANNING §9/C1 |
| UI theme config — light mode only (Rule 1 guard) | tests/ui.test.ts |
README Rule 1 — daisyUI v5 auto-dark default (light --default, dark --prefersdark) |
Only pure logic is unit-tested here — no DB, no network, no browser.
The engine modules (yarnMath, tippanSolver, intake) are pure on purpose so
they can be pinned by tests. Runtime/environment gotchas that can't be
unit-tested (e.g. bun-sqlite's synchronous .all()) are documented in
BUILD-LOG notes where they were hit — they are notes, not tests.
Rule 2 in practice — the edge-case test
When a change solves an edge case (a crash, a wrong parse, a wrong conversion, a rounding trap, a comma/₹/etc. format…):
- Write the test that reproduces the edge case first, named after the case, not the feature.
- Fix the code.
- Both land in the same commit; the test passes on the fixed code and would have failed on the pre-fix code.
Seed examples already in the suite:
// rate "/-" fallback — "125/-" with no at/rate/@ prefix (fixed in 96f102a)
test('rate "/-" fallback: "125/-" → 125', () => {
const r = parseCaptureText('please make 32 CW 1250 mtrs 125/-');
expect(r.rate).toBe(125);
});
// comma-grouped qty is normalised
test('"1,250 mtrs" → qty 1250', () => {
expect(parseCaptureText('please make 32 CW 1,250 mtrs').qty).toBe(1250);
});
Config guards are tests too: tests/ui.test.ts locks Rule 1 — daisyUI v5
defaults to light --default, dark --prefersdark, so src/app.css must
keep the themes: light --default pin. That guard exists because the bare
plugin line shipped auto dark-mode to the live site (hit 2026-09-03).
Rule 3 in practice — the green gate
scripts/deploy.shstep 1 isbun --smol test --timeout 30000 --isolate; withset -euo pipefaila red suite aborts the deploy before build/restart/commit/push.- Manually:
bun --smol test --timeout 30000 --isolatebefore commit, and again before push if the diff changed code. - A deploy that failed tests is not done — fix the test or the code; never push around it.
Knobs
- New engine module? Add its
tests/<name>.test.tsthe same way — pure exports only, no server-only imports. - Numbers in
tests/yarnMath.test.tsare locked to the owner-sheet values in docs/COSTING.md §5 — if the engine formulas change, COSTING.md and its tests change together, and the sheet stays the arbiter.