Skip to content

Commit c53de03

Browse files
authored
Cljs test improvements (#804)
* run-tests: accept quoted ns symbols at the macro level + ns event The macro now converts (quote my.ns) at the call site to a plain "my.ns" string before emitting, so users can write the cljs.test idiom (run-tests 'my.ns) without depending on squint's quoted-symbol runtime support (which doesn't exist). The runtime fn keeps its existing signatures. run-vars-with-once-fixtures now brackets each ns's tests with :begin-test-ns / :end-test-ns reports — the events were declared in report's case but never fired. Reporters (and plain output) now print "Testing my.ns" before each ns's run, matching cljs.test. Smoke test gains a regression for the quoted-symbol macro path and asserts the Testing lines appear in output. CHANGELOG updated with the user-visible cljs.test entry. * Add (async done body) macro for cljs.test parity Adds core-async to the built-in test macros: (async done body) expands to (js/Promise. (fn [done] body)). The deftest body returns the Promise and test-var awaits it, so no ^:async marker on the outer fn is needed. Lets users copy-paste cljs.test code that uses the (async done ...) idiom without rewriting it. Also added to builtin-refer-is-macro? so :refer [async] doesn't emit a runtime import. Smoke test gets a regression. CHANGELOG and TODO entries updated (#8 closed).
1 parent a39ddd0 commit c53de03

7 files changed

Lines changed: 99 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44

55
## Unreleased
66

7+
- Add built-in `cljs.test` / `clojure.test` support: `deftest`, `is`, `testing`,
8+
`are`, `deftest-`, `use-fixtures`, `async`, `run-tests` (no-args defaults to
9+
current ns; also accepts quoted ns symbols, e.g. `(run-tests 'my.ns)`).
10+
Per-namespace fixtures, counter isolation per `run-tests` call, `^:async`
11+
test support, and `(async done body)` form for tests that resolve when
12+
`done` is called. Runtime ships at `squint-cljs/src/squint/test.mjs`.
13+
- `with-meta` now preserves callability when applied to a function (returns a
14+
forwarding wrapper instead of a plain object via `copy()`).
715
- Fix [#783](https://github.com/squint-cljs/squint/issues/783): auto-load macros from `.cljc` files via `:require` (no need for `:require-macros`)
816
- Fix [#783](https://github.com/squint-cljs/squint/issues/783): resolve qualified symbols from macro expansions
917
- Fix [#784](https://github.com/squint-cljs/squint/issues/784): resolve transitive macro deps and auto-import runtime deps from macro expansion

bb/tasks.clj

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,13 @@
8787
(shell "node" "node_cli.js" "compile" src)
8888
(let [out (:out (shell {:out :string} "node" out-file))]
8989
(fs/delete out-file)
90-
(assert (str/includes? out "Ran 8 tests containing 17 assertions") out)
91-
(assert (str/includes? out "1 failures, 0 errors") out))))
90+
(assert (str/includes? out "Ran 10 tests containing 20 assertions") out)
91+
(assert (str/includes? out "1 failures, 0 errors") out)
92+
;; :begin-test-ns must fire for each ns visited by run-tests
93+
(assert (str/includes? out "Testing ns.a") out)
94+
(assert (str/includes? out "Testing ns.b") out)
95+
;; (run-tests 'synthetic.ns) macro must compile to a string lookup
96+
(assert (str/includes? out "Testing synthetic.ns") out))))
9297

9398
(defn test-squint []
9499
(fs/create-dirs ".work")

doc/cljs-test-todo.md

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -70,16 +70,26 @@ everyone uses, but it's overridable). Same idea as `report`.
7070

7171
## Coverage gaps — features that exist but are partial
7272

73-
### 7. `:begin-test-ns` / `:end-test-ns` are never emitted
74-
We support the report types in `report`'s `case`, but `run-tests` never
73+
### 7. `:begin-test-ns` / `:end-test-ns` are never emitted ✅ DONE
74+
~~We support the report types in `report`'s `case`, but `run-tests` never
7575
fires them. Real `clojure.test` brackets each ns it processes with these
76-
events; reporters use them to group output.
76+
events; reporters use them to group output.~~
7777

78-
### 8. No `(t/async done body)` form
79-
Real `cljs.test` async tests use `(async done (do ... (done)))`. We use
78+
Fixed: `run-vars-with-once-fixtures` now brackets each ns's tests with
79+
`:begin-test-ns` / `:end-test-ns` reports, so `(t/run-tests 'my.ns)`
80+
prints `Testing my.ns` like cljs.test. Anonymous (nil-ns) groups are
81+
skipped. Smoke tests assert the lines appear.
82+
83+
### 8. No `(t/async done body)` form ✅ DONE
84+
~~Real `cljs.test` async tests use `(async done (do ... (done)))`. We use
8085
`^:async` on the deftest plus a Promise-returning body. Functionally
8186
equivalent but different surface — code copied from a CLJS project
82-
won't run.
87+
won't run.~~
88+
89+
Added: `core-async` macro expands `(async done body)` to
90+
`(js/Promise. (fn [done] body))`. `test-var` already awaits any
91+
Promise-returning test fn, so no additional plumbing was needed —
92+
the `^:async` form keeps working too. Regression tests in both repos.
8393

8494
### 9. Test discovery is registration-based, not metadata-based
8595
`clojure.test/run-tests` walks `ns-publics` and filters by

src/squint/compiler_common.cljc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,7 @@
316316
alias))))
317317

318318
(def ^:private builtin-test-macro-names
319-
'#{deftest deftest- is testing are use-fixtures})
319+
'#{deftest deftest- is testing are async use-fixtures})
320320

321321
(defn builtin-refer-is-macro?
322322
"Returns true when a `:refer`'d symbol is known to be a compiler built-in

src/squint/internal/test.cljc

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,14 +98,30 @@
9898
:once `(clojure.test/set-once-fixtures! ~ns-name [~@fns])
9999
:each `(clojure.test/set-each-fixtures! ~ns-name [~@fns]))))
100100

101+
(defn core-async [_&form _&env done & body]
102+
;; (async done body...) — common cljs.test idiom for async tests.
103+
;; Expands to a Promise constructor whose `resolve` is bound to `done`
104+
;; in the body's scope. The deftest body returns this Promise and
105+
;; test-var awaits it; no ^:async marker needed on the outer fn.
106+
(assert (symbol? done) "first argument to async must be a symbol")
107+
`(js/Promise. (fn [~done] ~@body)))
108+
101109
(defn core-run-tests [_&form &env & args]
102110
;; Match cljs.test: with no args, default to the compile-time current ns.
103-
;; The :squint.compiler/skip-macro meta stops the emission from landing back
104-
;; in this macro's lookup (which shares the name with the runtime fn).
105-
(let [ns-name (some-> &env :ns :name str)
106-
args' (if (and ns-name (empty? args))
107-
[ns-name]
108-
args)]
111+
;; Quoted namespace symbols `'my.ns` get converted to plain strings at
112+
;; macro-expansion time so the emission doesn't depend on quoted-symbol
113+
;; runtime support (which squint doesn't have). Anything else passes
114+
;; through to the runtime fn unchanged. The :squint.compiler/skip-macro
115+
;; meta stops the emission from re-entering this macro's lookup.
116+
(let [args' (if (empty? args)
117+
[(some-> &env :ns :name str)]
118+
(mapv (fn [arg]
119+
(if (and (seq? arg)
120+
(= 'quote (first arg))
121+
(symbol? (second arg)))
122+
(str (second arg))
123+
arg))
124+
args))]
109125
(with-meta `(clojure.test/run-tests ~@args')
110126
{:squint.compiler/skip-macro true})))
111127

@@ -115,5 +131,6 @@
115131
'is core-is
116132
'testing core-testing
117133
'are core-are
134+
'async core-async
118135
'use-fixtures core-use-fixtures
119136
'run-tests core-run-tests})

src/squint/test.cljs

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,23 @@
175175
(.then chain (fn [_] (test-var v)))
176176
(test-var v)))
177177
nil
178-
vars))]
179-
(if (seq once-fixtures)
180-
((join-fixtures once-fixtures) run-all)
181-
(run-all))))
178+
vars))
179+
run-with-fixtures (fn []
180+
(if (seq once-fixtures)
181+
((join-fixtures once-fixtures) run-all)
182+
(run-all)))]
183+
;; Match cljs.test: bracket each ns's run with :begin/:end-test-ns
184+
;; events so reporters can group output. Anonymous (nil-ns) groups
185+
;; — explicit fns without :ns meta — are skipped so we don't print
186+
;; "Testing " with no name.
187+
(when ns-str (report {:type :begin-test-ns :ns ns-str}))
188+
(let [chain (run-with-fixtures)
189+
finish (fn [r]
190+
(when ns-str (report {:type :end-test-ns :ns ns-str}))
191+
r)]
192+
(if (async? chain)
193+
(.then chain finish)
194+
(finish chain)))))
182195

183196
(def ^:private fresh-counters {:test 0 :pass 0 :fail 0 :error 0})
184197

test-resources/cljs_test_smoke.cljs

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
(ns cljs-test-smoke
2-
(:require [cljs.test :as t :refer [deftest is testing are]]))
2+
(:require [cljs.test :as t :refer [deftest is testing are async]]))
33

44
(deftest math-test
55
(testing "basic math"
@@ -99,16 +99,42 @@
9999
(is (nil? (:begin-test-ns counters))
100100
"no bogus :begin-test-ns key added")))))
101101

102+
(deftest async-done-form-test
103+
;; (async done ...) is the cljs.test idiom — body runs, done resolves
104+
;; the wrapping promise, test-var awaits it.
105+
(async done
106+
(js/setTimeout
107+
(fn []
108+
(is (= 42 (* 6 7)))
109+
(done))
110+
5)))
111+
112+
(deftest run-tests-quoted-symbol-test
113+
(testing "(run-tests 'my.ns) macro converts quoted symbol to a string"
114+
(let [saved-env (t/get-current-env)]
115+
(t/register-test! "synthetic.ns"
116+
(with-meta (fn [] (is true))
117+
{:name "synthetic" :ns "synthetic.ns"}))
118+
(t/set-env! (t/empty-env))
119+
(let [result (t/run-tests 'synthetic.ns)]
120+
(t/set-env! saved-env)
121+
(is (= 1 (:test result))
122+
"quoted ns symbol must reach the runtime as a string and resolve")
123+
(is (= 1 (:pass result))
124+
"the inner test ran and its assertion passed")))))
125+
102126
(defn ^:async -main []
103127
(t/set-env! (t/empty-env))
104128
(t/test-var math-test)
105129
(t/test-var thrown-test)
106130
(t/test-var expected-failure-test)
107131
(js-await (t/test-var async-test))
132+
(js-await (t/test-var async-done-form-test))
108133
(t/test-var per-ns-each-fixtures-test)
109134
(t/test-var per-ns-once-fixtures-test)
110135
(t/test-var run-tests-counter-isolation-test)
111136
(t/test-var report-only-counts-pass-fail-error-test)
137+
(t/test-var run-tests-quoted-symbol-test)
112138
(t/report {:type :summary}))
113139

114140
(-main)

0 commit comments

Comments
 (0)