# tests — what the scaffold ships, and what you have to add
`npm test` runs, in order:
1. `clojure -M:cljfmt check src` — the formatting gate, config in
`client/.cljfmt.edn`. It is the FIRST link of the `test` script rather than a
`pretest` hook on purpose: an npm lifecycle hook is skipped outright under
`ignore-scripts=true`, which is a gate that passes by not running.
2. `npm run check:app` — compiles the `:app` build, the one that actually
reaches a browser, into a throwaway `target/check-app`. It costs a compile
and writes nothing into `dist/`.
This gate is here because of a real defect. The scaffold vendors
`lib/mailbox.cljs` from chat; that file requires `promesa.core`, `<name>.fx`
and `<name>.wire`, and for a long while the template shipped none of them —
so every rooms scaffold's `:app` build did not compile, and every scaffold's
`npm test` was green anyway, because nothing in the suite ever built `:app`.
**A gate that never compiles what ships is a gate that passes by not
looking.** Keep this link first among the compiling ones: it is the cheapest
check that the app is still buildable at all.
3. `node --test test/*.test.mjs` — today that is exactly one file,
`source-hygiene.test.mjs`, the template's own app-agnostic copy of the lint
whose canon is chat's (chat's file also carries chat-only façade checks a
fresh app cannot pass, so it is not vendored verbatim). Note the
**unquoted** glob: `'test/*.test.mjs'` is not expanded by the shell, and node
does not expand it on every version either, so the quoted form can run ZERO
tests and still exit 0 — the same failure mode as the paragraph above.
All three are real gates and all three pass on a fresh scaffold. None is a test
of **your** app, and the scaffold does not pretend otherwise: there are no
golden vectors here, because there is nothing yet to pin.
## source-hygiene.test.mjs
A source lint over `src/`, for the CLJS traps that each shipped a bug in this
suite — a local or a `defn` shadowing a core macro/var (core PREDICATES
included), a `#js {}` or `js-obj` literal with pairs (key order silently
becomes hash order at nine pairs), a sibling form dedented below the one above
it (the paren-depth error that took chat and board down in production), an
interop call whose target is a function literal (always that same bug), and
the raw-HTML gate: an allowlist-with-budgets over `innerHTML`-family sinks and
`esc` definitions that ships EMPTY, because the demo builds DOM nodes
(`app/dom.cljs`) and never markup. Every check carries a "would have caught
the bug that motivated it" case beside it.
When you add a `:testlib` build, also add a façade freeze —
`helpers/facade.mjs` is already vendored, and chat's copy of this file shows
the canon shape (exact-match against a recorded vector, a non-vacuousness
probe, by-name `:exports` resolution).
It scans **this repo's** namespaces only. The shared rooms core
(`ardegazu.rooms.*`) comes off the classpath from `ardegazu-rooms-kit` and is
linted there, by that repo's copy of this file. Between the two, every `.cljs`
this app compiles is covered — so if you ever move code the other way, ask what
was scanning it yesterday and whether that still reaches.
## What to add, roughly in this order
1. **A `:testlib` build** in `client/shadow-cljs.edn`: `:target :esm`,
`:output-dir "test-dist"`, `:optimizations :simple`, `:source-map false`,
`:js-options {:js-provider :import}`, and an `:exports` map naming the pure
logic you want to drive from node. chat's `shadow-cljs.edn` is the canon
shape. Then put `shadow-cljs release testlib` back into the `test` script,
between the cljfmt check and `node --test`.
2. **Golden vectors** under `test/vectors/*.json` for every op builder and every
canonicalised object — byte-exact, because key order is the wire. Assert them
with `js/JSON.stringify` output, never `pr-str`.
3. **Order-independence pairs**: feed the same entry set to your projector in
two arrival orders and assert the two `JSON.stringify`s are equal. One line
per pair, and it catches fold divergences instantly.
4. **An i18n placeholder test** asserting that every `(t "key" {…})` call site
supplies exactly the placeholders its English string uses — no missing, no
extra. board's `source-hygiene.test.mjs` has the canon copy; board shipped a
raw `{cur}` on every returning user's screen for want of it.
5. **`test/orbit-address.test.mjs`** if you care that your room addresses are
deterministic. It boots the real stack, which needs the libdatachannel native
addon — that is what `scripts/ensure-native.mjs` is for; re-add it as the
`pretest` step (and note the point above about `ignore-scripts`).
`dev/docs/CLJS.md` — "Test-harness notes", "Crypto and wire compatibility" and
"Gates: write them, then try to break them" — is the canon for all of it. The
one rule worth repeating here: **probe every gate by deliberately breaking what
it guards.** An unprobed gate is a claim, not a check.