Most of a component is testable without HTTP, a browser or Datastar: drive the pipeline in process
and look at :memory, :state and the rendered hiccup.
(require '[taia.ds.datastar.interface :as ds])
(ds/set-component-search-paths! ["my.app.components"])
(def req {:params {:logged-user {:user/id "tester"} :id "3"} ; event params = query params
:body-params {:todos1 {:todos [...] :state "displayed"}}}) ; the signal store
(let [{:keys [ctx html]} (ds/process-component-event req "todomvc.todomvc" "todos1" "toggle" nil)]
(:memory ctx) ; after the transition
(:state ctx)
html) ; hiccup, or an effects list
The examples base wraps exactly this in
test_support.clj: run builds the
request from {:memory :state :params :user}, step feeds one result into the next request the
way the browser does, and testid-text / extract-post-urls parse the rendered HTML with
hickory so assertions target data-testid attributes, not
strings. Copy the file; it is forty lines.
(def c0 (run "basics.counter" "counter1" {} "init"))
(def c1 (step "basics.counter" "counter1" c0 "increment"))
(:count (:memory c1)) := 1
(testid-text (:html c1) "count") := "1"
examples_test.clj is the complete
executable spec of the five examples and the best place to see the style.
taia.ds.state-machines.interface/transition is pure: (transition machine {:state :a :memory {}} [:GO {}] {:state-key :state}).
For machines whose invokes hit the world, make-machine-testable swaps named functions for their
vars so with-redefs can intercept them, and taia.ds.state-machines.test-helpers/test-machine
asserts targets and action traces declaratively without running invokes.
Call it with a hand-made ctx and parse the result with hickory. Two traps:
:memory.(:event ctx); the event is not in ctx.Datastar itself: data-bind writing into the store, morph behaviour, data-init firing, the SSE
stream and the reconnect watchdog. Cover those with an end-to-end suite against a real page and
watch the browser console: a thrown Datastar expression aborts the attribute pass for the whole
patched subtree without any server-side symptom.
bb e2e-test # all specs; needs Node >= 18
bb e2e-test counter # one spec file
bb e2e-test --headed
scripts/e2e.clj installs e2e/ deps, starts the examples app on port 7799 in its own JVM, runs
npx playwright test with E2E_BASE_URL set, and stops the JVM (log in
e2e/test-results/server.log). Against an already running bb dev:
E2E_BASE_URL=http://localhost:7788 npx playwright test from e2e/.
One spec per example in e2e/tests/. Every spec installs
console-guard.ts, which fails the test on any
pageerror or console.error (network aborts on navigation are ignored) — the only reliable
detector of an aborted attribute pass. live-counter.spec.ts opens two browser contexts as two
users and asserts a click in one is pushed into the other over SSE.
Tests are inline RCF (tests …) blocks (hyperfiddle.rcf), run by Kaocha with
-Dhyperfiddle.rcf.generate-tests=true (bb test). Values are bound with def so they can be
inspected in a REPL; assertions are top-level :=. Because the dev server enables RCF, a
lazily-loaded component namespace runs its tests on first request — keep test blocks free of
process-global side effects, or restore what they change.
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |