Testing
Bun's runner for units, Playwright for the paths that must not break, and one command that gates a commit.
Two layers, and a single command that runs everything a commit has to pass.
bun run verifyThat is lint, typecheck, test, rules:check and lint:cyrillic in
sequence. It is also what the pre-commit hook runs, so a commit that would fail
CI usually fails on your machine first.
bun run verify:changed narrows the same gate to what the working tree
touched, which is the one to reach for mid-feature.
Unit tests
33 .test.ts files ship, run by Bun's own runner. No Jest, no Vitest, no
configuration file.
bun run test # everything, through Turbo
bun test packages/entitlements # one package
bun test --watch packages/email # while writingThey sit next to the code they cover: catalog.test.ts beside catalog.ts.
Worth reading before writing your own, because they show what this codebase
thinks is worth asserting: render.test.ts renders every email template so a
broken one fails in CI rather than in an inbox, and schema.test.ts guards the
shape the whole app depends on.
End-to-end
Playwright, two specs, against the real API and a real database.
bun run test:e2eplaywright.config.ts starts both servers itself. Postgres and Redis must
already be up:
bun run setup # or docker compose up -d
bun run test:e2eThe critical-path specs exercise real authentication and real API calls, which
is the point: a smoke test against mocks proves the mocks work. It also means
these tests write to whatever database .env points at. Point it at a
throwaway one.
Useful knobs, all optional:
| Variable | Effect |
|---|---|
PLAYWRIGHT_BASE_URL | Test a deployed environment instead of a local server |
PLAYWRIGHT_API_URL | Where the API is expected, default http://localhost:3001 |
PLAYWRIGHT_PORT | Web port, default 3002 |
PLAYWRIGHT_CHANNEL | Set to chrome to use the system browser when Playwright's CDN is unreachable |
In CI the config sets forbidOnly, retries twice, and switches to the GitHub
reporter. trace: "on-first-retry" means a flake leaves a trace file to open
rather than a line of red text.
The two checks that are not tests
bun run rules:check regenerates the rules documentation from
rules/registry.ts and fails if the committed copy has drifted. The rules page
cannot silently stop matching the rules.
bun run lint:cyrillic fails on non-English text inside .ts and .tsx
sources. Translations belong in JSON catalogs, where a translator can edit them
without touching code that compiles. See email for the
same rule applied to templates.
check-no-cyrillic.sh uses mapfile, which bash 3.2 does not have, so
bun run verify fails on stock macOS while every other task passes. Run the
other tasks individually there, or install a newer bash.