Skip to content

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:

ScriptWhen
test / test:unitLocal development; runs unit tests under src/__tests__/
test:integrationIntegration tests under tests/integration/ (when present)
test:ciWhat 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:

  1. Edit one file.
  2. Run that one file's spec, e.g. pnpm --filter @open-matrix/<pkg> test --grep <case>.
  3. Run the package's full suite: pnpm --filter @open-matrix/<pkg> test.
  4. Run pnpm test:affected only 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.ts

before 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 test

Don't declare a webapp change "done" without a Playwright run.

See also

Source: package.json:scripts.test* per package; the runner is projects/matrix-3/scripts/run-node-tests.cjs. Workspace orchestration is in turbo.json.