T
Sign in

TESTS.md — the test suite & the two test rules

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.sh runs 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…):

  1. Write the test that reproduces the edge case first, named after the case, not the feature.
  2. Fix the code.
  3. 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.sh step 1 is bun --smol test --timeout 30000 --isolate; with set -euo pipefail a red suite aborts the deploy before build/restart/commit/push.
  • Manually: bun --smol test --timeout 30000 --isolate before 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.ts the same way — pure exports only, no server-only imports.
  • Numbers in tests/yarnMath.test.ts are 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.