Appearance
Test
Tests in this repo are package-local and use the Node built-in test runner (node:test). The workspace orchestrator runs them through Turbo. CLAUDE.md Rule 10 documents the canonical test commands.
Top-level commands
bash
pnpm test # run every package's tests (slow)
pnpm test:affected # run only what changed (fast)test:affected is the daily-driver command. The full sweep is appropriate after large rebases.
Per-package test scripts
Most packages declare three test scripts:
json
"scripts": {
"test": "node ../../scripts/run-node-tests.cjs -- src/__tests__/*.spec.ts",
"test:unit": "node ../../scripts/run-node-tests.cjs -- src/__tests__/*.spec.ts",
"test:ci": "node ../../scripts/run-node-tests.cjs -- src/__tests__/*.spec.ts"
}The variants differ on which tests run:
| Script | When |
|---|---|
test / test:unit | Local development; runs unit tests under src/__tests__/ |
test:integration | Integration tests under tests/integration/ (when present) |
test:ci | What CI runs; usually equals test:unit minus tests known to need infrastructure |
The mx-cli package, for example, additionally has:
json
"test:integration": "...tests/integration/*.spec.ts"Writing a test
The mx actor <name> scaffold writes a starter spec. The shape is:
ts
import { describe, it } from "node:test";
import { strict as assert } from "node:assert";
import { UserStoreActor } from "../src/UserStoreActor.ts";
describe("UserStoreActor", () => {
it("declares getStatus command", () => {
assert.ok(
Object.prototype.hasOwnProperty.call(
UserStoreActor.accepts,
"getStatus",
),
);
});
});The test imports from ../src/..., not from ../dist/.... Tests run through tsx (loaded by run-node-tests.cjs), which transpiles TypeScript on the fly. You still need to pnpm build if your test exercises generated artifacts (e.g. browser bundles).
Focused-first discipline
CLAUDE.md Rule 10:
Run the small in-place check first, then the focused package test/build for the touched boundary, and only then the full proof. Do not run broad regression suites after each line edit.
Practical translation:
- Edit one file.
- Run that one file's spec, e.g.
pnpm --filter @open-matrix/<pkg> test --grep <case>. - Run the package's full suite:
pnpm --filter @open-matrix/<pkg> test. - Run
pnpm test:affectedonly when the focused suite passes.
The audit script
For changes that touch the device-identity surface, CLAUDE.md requires running:
bash
projects/matrix-3/scripts/audit-device-identity-schema.tsbefore committing. This isn't a per-package test; it's a global gate on schema-sensitive areas.
Integration tests
Integration tests live under <package>/tests/integration/ and exercise real actors against a real (often embedded) NATS bus. They are slower than unit tests; ship them in test:integration, not test:unit, so quick feedback stays fast.
mx-cli's tests/integration/fp30-auth-session.spec.ts is a canonical example: it boots a real Host Service flow end to end.
End-to-end / Playwright
Browser packages that ship a web app use Playwright for end-to-end tests. CLAUDE.md "END TO END TESTING": HTTP 200 is not proof. Run a real browser, log in, click through, read the console.
bash
cd projects/matrix-3/packages/<webapp>
npx playwright testDon't declare a webapp change "done" without a Playwright run.
See also
Source:
package.json:scripts.test*per package; the runner isprojects/matrix-3/scripts/run-node-tests.cjs. Workspace orchestration is inturbo.json.