Skip to content

Add built-in cljs.test/clojure.test support#802

Merged
borkdude merged 18 commits intomainfrom
cljs-test-builtin
Apr 17, 2026
Merged

Add built-in cljs.test/clojure.test support#802
borkdude merged 18 commits intomainfrom
cljs-test-builtin

Conversation

@borkdude
Copy link
Copy Markdown
Member

Makes squint recognize (:require [cljs.test :refer [deftest is testing are]]) and friends out of the box, so user code can use the standard cljs.test API without vendoring macros or a runtime namespace into their project.

  • src/squint/test.cljs: runtime (report, run-tests, test-var, fixtures, ^:async), pre-compiled to src/squint/test.mjs and shipped via package.json.
  • src/squint/internal/test.cljc: deftest/is/testing/are/deftest-/use-fixtures as compiler-built-in macros, registered through built-in-macro-nss.
  • compiler_common: resolve-ns and resolve-macro-ns learn to map cljs.test/clojure.test to the squint runtime; symbol emit falls back to the built-in ns->libname mapping so macro-generated qualified refs auto-import; :refer'd macro names are filtered out of runtime imports while still being tracked in :refers for lookup.
  • bb tasks: compile-test-runtime regenerates test.mjs on build/test; test-cljs-test compiles test-resources/cljs_test_smoke.cljs and asserts on the summary output, wired into test-squint for CI.

borkdude added 18 commits April 17, 2026 13:15
Makes squint recognize (:require [cljs.test :refer [deftest is testing are]])
and friends out of the box, so user code can use the standard cljs.test API
without vendoring macros or a runtime namespace into their project.

- src/squint/test.cljs: runtime (report, run-tests, test-var, fixtures,
  ^:async), pre-compiled to src/squint/test.mjs and shipped via package.json.
- src/squint/internal/test.cljc: deftest/is/testing/are/deftest-/use-fixtures
  as compiler-built-in macros, registered through built-in-macro-nss.
- compiler_common: resolve-ns and resolve-macro-ns learn to map
  cljs.test/clojure.test to the squint runtime; symbol emit falls back to the
  built-in ns->libname mapping so macro-generated qualified refs auto-import;
  :refer'd macro names are filtered out of runtime imports while still being
  tracked in :refers for lookup.
- bb tasks: compile-test-runtime regenerates test.mjs on build/test;
  test-cljs-test compiles test-resources/cljs_test_smoke.cljs and asserts on
  the summary output, wired into test-squint for CI.
When a macro expansion produces a qualified ref like clojure.test/report,
the symbol emitter generates an auto-import for the namespace. In REPL mode
this emitted `var X = await import(...)` but forgot the matching
`globalThis.<ns>.X = X` assignment that the regular ns form uses, so later
emissions of `globalThis.user.X.report(...)` blew up with "Cannot read
properties of undefined".

Also add test.mjs to the playground's runtime file copy list so prod
builds have the file available under public/public/src/squint/.
The auto-import can land before the ns form has emitted its own
`globalThis.<ns> = globalThis.<ns> || {}` initializer, so emit the
same idempotent guard alongside the assignment.
data: URLs aren't hierarchical, so relative import specifiers inside
the compiled module (e.g. ./squint-local/core.js in dev) fail to
resolve. A Blob URL inherits the document origin, letting those
relative imports resolve against the page URL.
Blob URLs have an opaque origin, so a ./squint-local/... import
inside the compiled module has nothing hierarchical to resolve
against. Compute an absolute URL once (relative to main_js.mjs)
and substitute it in — works under both blob and data scheme.
Under Vite, import.meta.url inside main_js.mjs can resolve to an
unexpected bundle path (e.g. .../index.js), which made the computed
localBase miss the trailing "squint-local/" segment entirely. The
result was concatenated URLs like .../index.jscore.js. Use the page
origin directly so the rewrite always targets the js/squint-local
symlink.
- deftest registers each test fn under its compile-time ns name.
- run-tests dispatches on arg type: strings look up ns's tests,
  fns run directly, no args defaults (via the new macro) to the
  current compile-time ns — matching cljs.test/run-tests.
- Auto-initialise the env and emit :summary, returning counters.
- Macro output carries :squint.compiler/skip-macro so the generated
  runtime call doesn't re-enter macro dispatch.
with_meta previously copied through an object spread, which lost
callability for functions. Introduce IWithMeta__withMeta, following
the existing IApply__apply pattern, and give functions a default
implementation that wraps in a new callable carrying meta — keeping
fn? true and avoiding mutation.

With that in place the squint deftest macro can use plain with-meta
like cherry's (no _squintTestName side channel). :name is stored as
a string so neither compiler has to emit a quoted symbol for it.
The single-arg form still reads *target*, but call sites that live
inside a Promise chain (e.g. cherry's scan-macros) can't rely on
the dynamic binding — it's already unwound by the time .then fires.
Pass the target explicitly there.
(cljs.test/foo) with no require would silently dispatch to the
built-in test macros. Require the user's :aliases to actually
contain the alias (bare or alias-munged) before consulting the
fallback. Reordered so the contains? checks only run when there's
a built-in candidate to gate.
The Symbol emit method needs resolve-ns; rather than carrying a
forward (declare resolve-ns), define resolve-import-map,
resolve-macro-ns, builtin-refer-is-macro? and resolve-ns ahead of
the emit method. Pure code reorganisation — no behaviour change.
No type implements it; with_meta only ever ran the type-dispatched
default branches. Keep the function-wrap fix (the original reason
for touching with_meta) but skip the symbol-protocol indirection
until there's an actual consumer.
Regression coverage for the with_meta fn-wrap path: fn? must stay
true, (meta ...) must round-trip the attached map, calling the
result must forward to the original, and the original must not be
mutated.
Inventory of behavior our built-in cljs.test diverges from canonical
clojure.test/cljs.test. Grouped by impact (correctness, extensibility,
coverage, polish) with a fix sketch per item so they can be tackled
incrementally.
Each-fixtures and once-fixtures are now keyed by namespace string
in the env, matching cljs.test semantics.

- core-deftest stashes :ns alongside :name on the fn's meta so
  test-var can find the right fixture vector for a fn alone.
- core-use-fixtures captures the compile-time ns and emits the
  ns-aware setter call.
- set-each-fixtures!/set-once-fixtures! and their getters get a
  2-arity (ns-str, fixtures); the 1-arity stays as a back-compat
  shim that targets the nil-keyed bucket so existing direct callers
  keep working.
- test-var prefers per-ns each-fixtures, falling back to the nil
  bucket; run-tests groups its test-vars by ns so each ns's
  once-fixtures wrap only that ns's tests.
- Smoke test grows two regression tests: per-ns each-fixtures fire
  in isolation, and per-ns once-fixtures wrap their group only.
Previously an inner (run-tests ...) inflated the outer caller's
counters and emitted a summary showing cumulative totals. Match
clojure.test by saving the caller's :report-counters on entry,
running the tests against fresh counters, then restoring the saved
counters before returning. The returned counters map reflects only
this run's tests.

Smoke test gets a regression: inner run-tests must not change outer
counters; its returned summary must equal its own scope.
Closes #1 (per-namespace fixtures) and #2 (run-tests counter
scoping); #11 (double-summary noise) closes as a side-effect of #2.
#10 (successful? globals) was a misread — successful? already takes
a counters map, so dropped.
`(report {:type X})` was unconditionally incrementing
`[:report-counters X]`, leaving bogus keys like `:summary` and
`:begin-test-ns` in the counters map. Match clojure.test by
restricting the increment to result types.

Smoke test gets a regression: after a few non-result reports, only
the result keys exist on `:report-counters`.
@borkdude borkdude merged commit a39ddd0 into main Apr 17, 2026
6 checks passed
@borkdude borkdude deleted the cljs-test-builtin branch April 17, 2026 17:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant