Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@

## Unreleased

- Add built-in `cljs.test` / `clojure.test` support: `deftest`, `is`, `testing`,
`are`, `deftest-`, `use-fixtures`, `async`, `run-tests` (no-args defaults to
current ns; also accepts quoted ns symbols, e.g. `(run-tests 'my.ns)`).
Per-namespace fixtures, counter isolation per `run-tests` call, `^:async`
test support, and `(async done body)` form for tests that resolve when
`done` is called. Runtime ships at `squint-cljs/src/squint/test.mjs`.
- `with-meta` now preserves callability when applied to a function (returns a
forwarding wrapper instead of a plain object via `copy()`).
- Fix [#783](https://github.com/squint-cljs/squint/issues/783): auto-load macros from `.cljc` files via `:require` (no need for `:require-macros`)
- Fix [#783](https://github.com/squint-cljs/squint/issues/783): resolve qualified symbols from macro expansions
- Fix [#784](https://github.com/squint-cljs/squint/issues/784): resolve transitive macro deps and auto-import runtime deps from macro expansion
Expand Down
9 changes: 7 additions & 2 deletions bb/tasks.clj
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,13 @@
(shell "node" "node_cli.js" "compile" src)
(let [out (:out (shell {:out :string} "node" out-file))]
(fs/delete out-file)
(assert (str/includes? out "Ran 8 tests containing 17 assertions") out)
(assert (str/includes? out "1 failures, 0 errors") out))))
(assert (str/includes? out "Ran 10 tests containing 20 assertions") out)
(assert (str/includes? out "1 failures, 0 errors") out)
;; :begin-test-ns must fire for each ns visited by run-tests
(assert (str/includes? out "Testing ns.a") out)
(assert (str/includes? out "Testing ns.b") out)
;; (run-tests 'synthetic.ns) macro must compile to a string lookup
(assert (str/includes? out "Testing synthetic.ns") out))))

(defn test-squint []
(fs/create-dirs ".work")
Expand Down
22 changes: 16 additions & 6 deletions doc/cljs-test-todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,16 +70,26 @@ everyone uses, but it's overridable). Same idea as `report`.

## Coverage gaps — features that exist but are partial

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

### 8. No `(t/async done body)` form
Real `cljs.test` async tests use `(async done (do ... (done)))`. We use
Fixed: `run-vars-with-once-fixtures` now brackets each ns's tests with
`:begin-test-ns` / `:end-test-ns` reports, so `(t/run-tests 'my.ns)`
prints `Testing my.ns` like cljs.test. Anonymous (nil-ns) groups are
skipped. Smoke tests assert the lines appear.

### 8. No `(t/async done body)` form ✅ DONE
~~Real `cljs.test` async tests use `(async done (do ... (done)))`. We use
`^:async` on the deftest plus a Promise-returning body. Functionally
equivalent but different surface — code copied from a CLJS project
won't run.
won't run.~~

Added: `core-async` macro expands `(async done body)` to
`(js/Promise. (fn [done] body))`. `test-var` already awaits any
Promise-returning test fn, so no additional plumbing was needed —
the `^:async` form keeps working too. Regression tests in both repos.

### 9. Test discovery is registration-based, not metadata-based
`clojure.test/run-tests` walks `ns-publics` and filters by
Expand Down
2 changes: 1 addition & 1 deletion src/squint/compiler_common.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@
alias))))

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

(defn builtin-refer-is-macro?
"Returns true when a `:refer`'d symbol is known to be a compiler built-in
Expand Down
29 changes: 23 additions & 6 deletions src/squint/internal/test.cljc
Original file line number Diff line number Diff line change
Expand Up @@ -98,14 +98,30 @@
:once `(clojure.test/set-once-fixtures! ~ns-name [~@fns])
:each `(clojure.test/set-each-fixtures! ~ns-name [~@fns]))))

(defn core-async [_&form _&env done & body]
;; (async done body...) — common cljs.test idiom for async tests.
;; Expands to a Promise constructor whose `resolve` is bound to `done`
;; in the body's scope. The deftest body returns this Promise and
;; test-var awaits it; no ^:async marker needed on the outer fn.
(assert (symbol? done) "first argument to async must be a symbol")
`(js/Promise. (fn [~done] ~@body)))

(defn core-run-tests [_&form &env & args]
;; Match cljs.test: with no args, default to the compile-time current ns.
;; The :squint.compiler/skip-macro meta stops the emission from landing back
;; in this macro's lookup (which shares the name with the runtime fn).
(let [ns-name (some-> &env :ns :name str)
args' (if (and ns-name (empty? args))
[ns-name]
args)]
;; Quoted namespace symbols `'my.ns` get converted to plain strings at
;; macro-expansion time so the emission doesn't depend on quoted-symbol
;; runtime support (which squint doesn't have). Anything else passes
;; through to the runtime fn unchanged. The :squint.compiler/skip-macro
;; meta stops the emission from re-entering this macro's lookup.
(let [args' (if (empty? args)
[(some-> &env :ns :name str)]
(mapv (fn [arg]
(if (and (seq? arg)
(= 'quote (first arg))
(symbol? (second arg)))
(str (second arg))
arg))
args))]
(with-meta `(clojure.test/run-tests ~@args')
{:squint.compiler/skip-macro true})))

Expand All @@ -115,5 +131,6 @@
'is core-is
'testing core-testing
'are core-are
'async core-async
'use-fixtures core-use-fixtures
'run-tests core-run-tests})
21 changes: 17 additions & 4 deletions src/squint/test.cljs
Original file line number Diff line number Diff line change
Expand Up @@ -175,10 +175,23 @@
(.then chain (fn [_] (test-var v)))
(test-var v)))
nil
vars))]
(if (seq once-fixtures)
((join-fixtures once-fixtures) run-all)
(run-all))))
vars))
run-with-fixtures (fn []
(if (seq once-fixtures)
((join-fixtures once-fixtures) run-all)
(run-all)))]
;; Match cljs.test: bracket each ns's run with :begin/:end-test-ns
;; events so reporters can group output. Anonymous (nil-ns) groups
;; — explicit fns without :ns meta — are skipped so we don't print
;; "Testing " with no name.
(when ns-str (report {:type :begin-test-ns :ns ns-str}))
(let [chain (run-with-fixtures)
finish (fn [r]
(when ns-str (report {:type :end-test-ns :ns ns-str}))
r)]
(if (async? chain)
(.then chain finish)
(finish chain)))))

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

Expand Down
28 changes: 27 additions & 1 deletion test-resources/cljs_test_smoke.cljs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
(ns cljs-test-smoke
(:require [cljs.test :as t :refer [deftest is testing are]]))
(:require [cljs.test :as t :refer [deftest is testing are async]]))

(deftest math-test
(testing "basic math"
Expand Down Expand Up @@ -99,16 +99,42 @@
(is (nil? (:begin-test-ns counters))
"no bogus :begin-test-ns key added")))))

(deftest async-done-form-test
;; (async done ...) is the cljs.test idiom — body runs, done resolves
;; the wrapping promise, test-var awaits it.
(async done
(js/setTimeout
(fn []
(is (= 42 (* 6 7)))
(done))
5)))

(deftest run-tests-quoted-symbol-test
(testing "(run-tests 'my.ns) macro converts quoted symbol to a string"
(let [saved-env (t/get-current-env)]
(t/register-test! "synthetic.ns"
(with-meta (fn [] (is true))
{:name "synthetic" :ns "synthetic.ns"}))
(t/set-env! (t/empty-env))
(let [result (t/run-tests 'synthetic.ns)]
(t/set-env! saved-env)
(is (= 1 (:test result))
"quoted ns symbol must reach the runtime as a string and resolve")
(is (= 1 (:pass result))
"the inner test ran and its assertion passed")))))

(defn ^:async -main []
(t/set-env! (t/empty-env))
(t/test-var math-test)
(t/test-var thrown-test)
(t/test-var expected-failure-test)
(js-await (t/test-var async-test))
(js-await (t/test-var async-done-form-test))
(t/test-var per-ns-each-fixtures-test)
(t/test-var per-ns-once-fixtures-test)
(t/test-var run-tests-counter-isolation-test)
(t/test-var report-only-counts-pass-fail-error-test)
(t/test-var run-tests-quoted-symbol-test)
(t/report {:type :summary}))

(-main)
Loading