Public API of the stube framework.
Most users only need this namespace. It re-exports the small set of
functions that are intended to be called from application code; the
internals live in dev.zeko.stube.kernel, dev.zeko.stube.conversation,
dev.zeko.stube.registry, dev.zeko.stube.render, dev.zeko.stube.runtime,
dev.zeko.stube.http and dev.zeko.stube.server. Hosts embedding stube
in their own Ring app reach for dev.zeko.stube.embed and
dev.zeko.stube.adapter.ring.
(require '[dev.zeko.stube.core :as s])
(s/defcomponent :demo/prompt-number
:init (fn [{:keys [text]}] {:text text :answer ""})
:keep #{:answer}
:render (fn [self]
[:form (s/root-attrs self (s/on self :submit))
[:label (:text self)]
[:input (merge {:name "answer"} (s/bind :answer))]
[:button "OK"]])
:handle (fn [self _]
[(s/answer (parse-long (:answer self)))]))
(s/defcomponent :demo/guess
:init (fn [_] {:target (rand-int 100)})
:handle (fn [_ _]
[(s/call :demo/prompt-number {:text "Guess 1–100"} :on-guess)])
:on-guess (fn [self n]
(cond
(< n (:target self))
[(s/call :demo/prompt-number {:text "Too low"} :on-guess)]
:else
[(s/end {:winner true})])))
(s/mount! "/guess" :demo/guess)
(s/start! {:port 8080})
Everything in this namespace is intended to remain stable across
framework versions. Host-framework integration also has a stable
surface in dev.zeko.stube.embed / dev.zeko.stube.adapter.ring.
Other namespaces are internal until the framework reaches 1.0.
Public API of the stube framework.
Most users only need this namespace. It re-exports the small set of
functions that are intended to be called from application code; the
internals live in [[dev.zeko.stube.kernel]], [[dev.zeko.stube.conversation]],
[[dev.zeko.stube.registry]], [[dev.zeko.stube.render]], [[dev.zeko.stube.runtime]],
[[dev.zeko.stube.http]] and [[dev.zeko.stube.server]]. Hosts embedding stube
in their own Ring app reach for [[dev.zeko.stube.embed]] and
[[dev.zeko.stube.adapter.ring]].
----------------------------------------------------------------------
At a glance
----------------------------------------------------------------------
(require '[dev.zeko.stube.core :as s])
(s/defcomponent :demo/prompt-number
:init (fn [{:keys [text]}] {:text text :answer ""})
:keep #{:answer}
:render (fn [self]
[:form (s/root-attrs self (s/on self :submit))
[:label (:text self)]
[:input (merge {:name "answer"} (s/bind :answer))]
[:button "OK"]])
:handle (fn [self _]
[(s/answer (parse-long (:answer self)))]))
(s/defcomponent :demo/guess
:init (fn [_] {:target (rand-int 100)})
:handle (fn [_ _]
[(s/call :demo/prompt-number {:text "Guess 1–100"} :on-guess)])
:on-guess (fn [self n]
(cond
(< n (:target self))
[(s/call :demo/prompt-number {:text "Too low"} :on-guess)]
:else
[(s/end {:winner true})])))
(s/mount! "/guess" :demo/guess)
(s/start! {:port 8080})
----------------------------------------------------------------------
Stability of this surface
----------------------------------------------------------------------
Everything in this namespace is intended to remain stable across
framework versions. Host-framework integration also has a stable
surface in [[dev.zeko.stube.embed]] / [[dev.zeko.stube.adapter.ring]].
Other namespaces are internal until the framework reaches 1.0.(active-conversations)See [[dev.zeko.stube.server/active-conversations]].
(after delay-ms route-event)Effect: dispatch route-event to the current instance after
delay-ms.
[self [(s/after 1000 :tick)]]
If the conversation or instance is gone when the timer fires, the
event is dropped. route-event accepts the same keyword/vector
shape as (s/on self :click :as route-event).
Effect: dispatch `route-event` to the current instance after
`delay-ms`.
[self [(s/after 1000 :tick)]]
If the conversation or instance is gone when the timer fires, the
event is dropped. `route-event` accepts the same keyword/vector
shape as `(s/on self :click :as route-event)`.(answer value)Pop this frame; deliver value to the parent under its resume key.
See dev.zeko.stube.effects/answer.
Pop this frame; deliver `value` to the parent under its resume key. See [[dev.zeko.stube.effects/answer]].
(answer-error ex)Pop this frame and route the exception through the parent's
:on-error-<key> resume handler instead of :on-<key>.
If the parent declares only :on-<key>, the kernel falls back to
it with a wrapped value [:error ex] and logs a one-time
deprecation warning per component. If neither is declared, the
parent surfaces a default error banner just like an intra-component
throw. See dev.zeko.stube.effects/answer-error.
Pop this frame and route the exception through the parent's `:on-error-<key>` resume handler instead of `:on-<key>`. If the parent declares only `:on-<key>`, the kernel falls back to it with a wrapped value `[:error ex]` and logs a one-time deprecation warning per component. If neither is declared, the parent surfaces a default error banner just like an intra-component throw. See [[dev.zeko.stube.effects/answer-error]].
(app)Return the opaque host-app value the embedder attached to the kernel
via :app. Typically a small map of dependencies such as
{:db ds :auth-fn (fn [request] ...)}.
Returns nil outside a runtime dispatch/render (e.g. when component
code is exercised through core/replay in a unit test). Component
tests that need a stand-in can wrap the call site with
with-app.
Return the opaque host-app value the embedder attached to the kernel
via `:app`. Typically a small map of dependencies such as
`{:db ds :auth-fn (fn [request] ...)}`.
Returns `nil` outside a runtime dispatch/render (e.g. when component
code is exercised through `core/replay` in a unit test). Component
tests that need a stand-in can wrap the call site with
[[with-app]].(back)Construct a [:back] effect that walks one step backward through
the conversation's :conv/history. Use it from a handler to wire
a "Back" button, or render (s/back-button "Back") for the
stock conversation-level button.
[(s/back)] ; from inside a handler
When emitted from a top-level handler, it pops the most recent
conversation snapshot off :conv/history and re-renders the
restored top frame. No-op if the history is empty.
s/back is a zero-arity function, matching every other effect
constructor (s/end, s/after, etc.). Always call it with parens.
Construct a `[:back]` effect that walks one step backward through
the conversation's `:conv/history`. Use it from a handler to wire
a "Back" button, or render `(s/back-button "Back")` for the
stock conversation-level button.
[(s/back)] ; from inside a handler
When emitted from a top-level handler, it pops the most recent
conversation snapshot off `:conv/history` and re-renders the
restored top frame. No-op if the history is empty.
`s/back` is a zero-arity function, matching every other effect
constructor (`s/end`, `s/after`, etc.). Always call it with parens.(back-button label)(back-button label attrs)See [[dev.zeko.stube.render/back-button]].
(become component-id-or-embed)(become component-id-or-embed args)Pop the current frame and push another in its place (Seaside
become:). The replacement inherits the original parent linkage
and resume key, so an eventual :answer still flows back to whoever
called the original frame.
(s/become :wizard/step-2)
(s/become :wizard/step-2 {:from data})
(s/become (s/embed :wizard/step-2 {:from data}))
Named become instead of replace to avoid shadowing
clojure.core/replace.
Pop the current frame and push another in its place (Seaside
`become:`). The replacement inherits the original parent linkage
and resume key, so an eventual `:answer` still flows back to whoever
called the original frame.
(s/become :wizard/step-2)
(s/become :wizard/step-2 {:from data})
(s/become (s/embed :wizard/step-2 {:from data}))
Named `become` instead of `replace` to avoid shadowing
`clojure.core/replace`.(behavior self behavior-id)(behavior self behavior-id args)See [[dev.zeko.stube.render/behavior]].
(boot flow-id)(boot flow-id init-args)Pure boot effects for a flow — see dev.zeko.stube.kernel/boot.
Pure boot effects for a flow — see [[dev.zeko.stube.kernel/boot]].
(call component-id-or-embed)(call component-id-or-embed args-or-resume)(call component-id-or-embed args resume)Push a child onto the stack. On :answer, the parent's resume key
is invoked with the answered value.
(s/call :ui/prompt) ; no args, no resume
(s/call :ui/prompt :on-pick) ; no args, resume :on-pick
(s/call :ui/prompt {:text "Hi"}) ; args, no resume
(s/call :ui/prompt {:text "Hi"} :on-pick) ; args + resume
(s/call (s/prompt "Name?") :on-pick) ; existing embed spec
Wraps component ids with embed internally. Passing an existing
embed spec is useful with stock UI helpers and defflow-style helper
functions.
Push a child onto the stack. On `:answer`, the parent's resume key
is invoked with the answered value.
(s/call :ui/prompt) ; no args, no resume
(s/call :ui/prompt :on-pick) ; no args, resume :on-pick
(s/call :ui/prompt {:text "Hi"}) ; args, no resume
(s/call :ui/prompt {:text "Hi"} :on-pick) ; args + resume
(s/call (s/prompt "Name?") :on-pick) ; existing embed spec
Wraps component ids with [[embed]] internally. Passing an existing
embed spec is useful with stock UI helpers and `defflow`-style helper
functions.(call-in-slot slot component-id-or-embed)(call-in-slot slot component-id-or-embed args-or-resume)(call-in-slot slot component-id-or-embed args resume)Temporarily swap an embedded slot's child; the new child answers back to the parent without taking over the page.
(s/call-in-slot :slot/main :feature/edit)
(s/call-in-slot :slot/main :feature/edit {:id 7} :on-saved)
(s/call-in-slot :slot/main (s/embed :feature/edit {:id 7}) :on-saved)
Temporarily swap an embedded slot's child; the new child answers
back to the parent without taking over the page.
(s/call-in-slot :slot/main :feature/edit)
(s/call-in-slot :slot/main :feature/edit {:id 7} :on-saved)
(s/call-in-slot :slot/main (s/embed :feature/edit {:id 7}) :on-saved)Sentinel returned by cancellable stock UI components.
Sentinel returned by cancellable stock UI components.
(choose options)(choose options caption)Return an embed spec for the stock one-of-N choice component.
Return an embed spec for the stock one-of-N choice component.
(confirm question)Return an embed spec for the stock yes/no confirmation component.
Return an embed spec for the stock yes/no confirmation component.
(context self)Return adapter/application context injected into this conversation.
Embedders pass :context-fn to dev.zeko.stube.embed/make-kernel;
handlers and lifecycle hooks can then call (s/context self) to reach
request-scoped dependencies such as a database handle.
Return adapter/application context injected into this conversation. Embedders pass `:context-fn` to `dev.zeko.stube.embed/make-kernel`; handlers and lifecycle hooks can then call `(s/context self)` to reach request-scoped dependencies such as a database handle.
(conv-history cid)Summarise :conv/history for live conversation cid.
Summarise `:conv/history` for live conversation `cid`.
(decorate base-cdef overrides)Build a new component definition by overriding keys of base-cdef.
overrides is either:
:render / :handle etc.).Returns a fresh map; the caller is responsible for giving it a fresh
:component/id and registering it.
(def site-header
(s/decorate (s/registry-lookup :booking/wizard)
(fn [base]
{:component/id :booking/wizard-with-banner
:component/render
(fn [self]
[:div {:id (:instance/id self)}
[:header.banner "Welcome"]
((:component/render base) self)])})))
No new runtime concept: this is just merge lifted into the
framework's vocabulary so the call site reads like Seaside-style
behavioural composition.
See decorate! for the register-on-the-way variant.
Build a new component definition by overriding keys of `base-cdef`.
`overrides` is either:
* a **map** that replaces the corresponding entries verbatim, or
* a **function** of the base cdef returning such a map (so the
override can call into the original `:render` / `:handle` etc.).
Returns a fresh map; the caller is responsible for giving it a fresh
`:component/id` and registering it.
(def site-header
(s/decorate (s/registry-lookup :booking/wizard)
(fn [base]
{:component/id :booking/wizard-with-banner
:component/render
(fn [self]
[:div {:id (:instance/id self)}
[:header.banner "Welcome"]
((:component/render base) self)])})))
No new runtime concept: this is just `merge` lifted into the
framework's vocabulary so the call site reads like Seaside-style
behavioural composition.
See [[decorate!]] for the register-on-the-way variant.(decorate! base-cdef overrides)Like decorate but also registers the resulting cdef and returns
the registered map. Convenient for the common case:
(s/decorate! (s/registry-lookup :booking/wizard)
{:component/id :booking/wizard-with-banner
:component/render
(fn [self]
[:div (s/root-attrs self)
[:header.banner "Welcome"]
((:component/render (s/registry-lookup :booking/wizard)) self)])})
Use decorate when you want to inspect or further modify the cdef
before deciding whether to register it.
Like [[decorate]] but also registers the resulting cdef and returns
the registered map. Convenient for the common case:
(s/decorate! (s/registry-lookup :booking/wizard)
{:component/id :booking/wizard-with-banner
:component/render
(fn [self]
[:div (s/root-attrs self)
[:header.banner "Welcome"]
((:component/render (s/registry-lookup :booking/wizard)) self)])})
Use [[decorate]] when you want to inspect or further modify the cdef
before deciding whether to register it.(defcomponent id & opts)Define a component and add it to the global registry.
(s/defcomponent :auth/login
:doc "Prompt for credentials."
:init (fn [args] state-map)
:keep #{:signal-keys} ;; optional
:render (fn [self] hiccup)
:handle (fn [self event] ;; event is {:event …, :signals …}
[self' effects])
:on-foo (fn [self answer-value] ;; resume keys, optional
[self' effects]))
The macro captures the call-site :file/:line so the halos dev
tool can jump to the definition. Use register-component! from
data-driven code that prefers a function shape.
Define a component and add it to the global registry.
(s/defcomponent :auth/login
:doc "Prompt for credentials."
:init (fn [args] state-map)
:keep #{:signal-keys} ;; optional
:render (fn [self] hiccup)
:handle (fn [self event] ;; event is {:event …, :signals …}
[self' effects])
:on-foo (fn [self answer-value] ;; resume keys, optional
[self' effects]))
The macro captures the call-site `:file`/`:line` so the halos dev
tool can jump to the definition. Use [[register-component!]] from
data-driven code that prefers a function shape.(defflow id bindings & body)Define a linear flow component. See dev.zeko.stube.flow/defflow for the
full docstring; this is a thin re-export so application code only ever
needs [dev.zeko.stube.core :as s].
Define a linear flow component. See [[dev.zeko.stube.flow/defflow]] for the full docstring; this is a thin re-export so application code only ever needs `[dev.zeko.stube.core :as s]`.
(dispatch conv {:keys [instance-id event payload signals] :as ev})Pure event dispatch — (dispatch conv event) → [conv' fragments].
Useful from the REPL or for tests; production code goes through the
http layer.
Pure event dispatch — `(dispatch conv event) → [conv' fragments]`. Useful from the REPL or for tests; production code goes through the http layer.
(end value)Terminate the conversation with a final value. After this the SSE
channel closes and the conversation is forgotten. See
dev.zeko.stube.effects/end.
Terminate the conversation with a final value. After this the SSE channel closes and the conversation is forgotten. See [[dev.zeko.stube.effects/end]].
(event-url target route-event)See [[dev.zeko.stube.render/event-url]].
(execute-script js)Run literal JS in the browser. Last-resort escape hatch; prefer
Datastar attributes for most needs. See
dev.zeko.stube.effects/execute-script.
Run literal JS in the browser. Last-resort escape hatch; prefer Datastar attributes for most needs. See [[dev.zeko.stube.effects/execute-script]].
(help id)Return a registered component's docstring, or nil.
Return a registered component's docstring, or nil.
(history mode url)Sync the browser URL without a page reload.
(s/history :replace "/notes?id=42")
(s/history :push "/notes/42")
:replace calls history.replaceState; :push calls
history.pushState. Use :replace for in-place state mutations
(e.g. filter changes) and :push for navigations the user should
be able to Back-button out of.
Sync the browser URL without a page reload.
(s/history :replace "/notes?id=42")
(s/history :push "/notes/42")
`:replace` calls `history.replaceState`; `:push` calls
`history.pushState`. Use `:replace` for in-place state mutations
(e.g. filter changes) and `:push` for navigations the user should
be able to Back-button out of.
See [[dev.zeko.stube.effects/history]].(in-memory-store)See [[dev.zeko.stube.store/in-memory-store]].
(info text)Return an embed spec for the stock informational OK dialog.
Return an embed spec for the stock informational OK dialog.
(instance cid iid)Return the instance map for iid in live conversation cid.
Return the instance map for `iid` in live conversation `cid`.
(io thunk)Ask the active runtime to run (thunk) off the request thread,
fire-and-forget. Pure dispatch/replay leave this effect inert
unless a runtime hook is bound. See dev.zeko.stube.effects/io.
Ask the active runtime to run `(thunk)` off the request thread, fire-and-forget. Pure `dispatch`/`replay` leave this effect inert unless a runtime hook is bound. See [[dev.zeko.stube.effects/io]].
(keyed-children self slot)Render the keyed-children container for slot on self. Returns
hiccup [:div {:id …} child-hiccup…] where each child instance's
:render is inlined in the declared order.
:handle (fn [self _]
[self [(s/set-keyed-children
:slot/cols
(map (fn [id] [id (s/embed :demo/counter)])
(:col-ids self)))]])
:render (fn [self]
[:section (s/root-attrs self)
(s/keyed-children self :slot/cols)])
Render the keyed-children container for `slot` on `self`. Returns
hiccup `[:div {:id …} child-hiccup…]` where each child instance's
`:render` is inlined in the declared order.
:handle (fn [self _]
[self [(s/set-keyed-children
:slot/cols
(map (fn [id] [id (s/embed :demo/counter)])
(:col-ids self)))]])
:render (fn [self]
[:section (s/root-attrs self)
(s/keyed-children self :slot/cols)])(local-signal self signal)See [[dev.zeko.stube.conversation/local-signal]].
(mount! path flow-id)(mount! path flow-id opts)See dev.zeko.stube.server/mount!. Accepts an optional opts map
with :init-args-fn to seed component state from GET request
params.
See [[dev.zeko.stube.server/mount!]]. Accepts an optional opts map with `:init-args-fn` to seed component state from GET request params.
(on self dom-event)(on self dom-event as-kw route-event)(on self dom-event as-kw route-event modifiers)See [[dev.zeko.stube.render/on]].
(on-parent self dom-event)(on-parent self dom-event as-kw route-event)(on-parent self dom-event as-kw route-event modifiers)See [[dev.zeko.stube.render/on-parent]].
(on-target target dom-event)(on-target target dom-event as-kw route-event)(on-target target dom-event as-kw route-event modifiers)See [[dev.zeko.stube.render/on-target]].
(on-unmount self label expr)See [[dev.zeko.stube.render/on-unmount]].
(patch hiccup)Emit an extra DOM patch without changing the stack.
See dev.zeko.stube.effects/patch.
Emit an extra DOM patch without changing the stack. See [[dev.zeko.stube.effects/patch]].
(patch-signals m)Push a Datastar signal patch (writes signal values back to the
browser). See dev.zeko.stube.effects/patch-signals.
Push a Datastar signal patch (writes signal values back to the browser). See [[dev.zeko.stube.effects/patch-signals]].
(principal)Return the authenticated principal the embedder stamped onto this
conversation via :principal-fn at mint time.
Returns nil for anonymous conversations. The principal is fixed
for the life of the conversation — to refresh it (post-login, after
account switching), end the conversation and re-mint.
Tests that need a stand-in can wrap the call site with
with-principal.
Return the authenticated principal the embedder stamped onto this conversation via `:principal-fn` at mint time. Returns `nil` for anonymous conversations. The principal is fixed for the life of the conversation — to refresh it (post-login, after account switching), end the conversation and re-mint. Tests that need a stand-in can wrap the call site with [[with-principal]].
(prompt label)(prompt label default)Return an embed spec for the stock text prompt component.
Return an embed spec for the stock text prompt component.
(publish! topic msg)Publish msg to every live instance subscribed to topic.
Delivery is asynchronous and cid/iid-scoped; stale subscribers are
ignored. From component code this targets the active runtime
kernel; outside a dispatch it targets the standalone server kernel.
Returns the number of subscribers targeted.
Publish `msg` to every live instance subscribed to `topic`. Delivery is asynchronous and cid/iid-scoped; stale subscribers are ignored. From component code this targets the active runtime kernel; outside a dispatch it targets the standalone server kernel. Returns the number of subscribers targeted.
(query-value {:keys [query-string]} param-name)Return the decoded value of query-param param-name from a Ring
request, or nil if absent. Useful in :init-args-fn callbacks:
(s/mount! "/counter" :demo/counter
{:init-args-fn (fn [req]
{:n (parse-long (or (s/query-value req "n") "0"))})})
Return the decoded value of query-param `param-name` from a Ring
request, or nil if absent. Useful in `:init-args-fn` callbacks:
(s/mount! "/counter" :demo/counter
{:init-args-fn (fn [req]
{:n (parse-long (or (s/query-value req "n") "0"))})})
See [[dev.zeko.stube.http/query-value]].(register-component! id opts)(register-component! id opts source)Plain function form of defcomponent. Two arities:
(register-component! id opts)
(register-component! id opts source-map)
source-map is {:file ... :line ...} to be attached under
:component/source (so the halos dev tool can jump to a definition).
The macro supplies it from call-site &form meta; hand-rolled
data-driven callers can pass nil.
registry/register! lifts colocated author keys (:init, :render,
:handle, :keep, :doc, :state, :start, :stop, :wakeup,
:children, :url, :styles, :modules) to :component/<name> so
cdefs are uniform regardless of which entry point produced them.
Plain function form of [[defcomponent]]. Two arities:
(register-component! id opts)
(register-component! id opts source-map)
`source-map` is `{:file ... :line ...}` to be attached under
`:component/source` (so the halos dev tool can jump to a definition).
The macro supplies it from call-site `&form` meta; hand-rolled
data-driven callers can pass nil.
`registry/register!` lifts colocated author keys (`:init`, `:render`,
`:handle`, `:keep`, `:doc`, `:state`, `:start`, `:stop`, `:wakeup`,
`:children`, `:url`, `:styles`, `:modules`) to `:component/<name>` so
cdefs are uniform regardless of which entry point produced them.(registry-lookup id)Look up a registered component definition by id (or nil).
Look up a registered component definition by id (or nil).
(render-slot self slot-key)(render-slot self slot-key lookup!)See [[dev.zeko.stube.render/render-slot]].
(replay events)(replay baseline events)Replay events against a baseline conversation or root flow id.
Returns [conv fragments], matching dispatch / boot. When
the baseline is a flow id, replay boots a fresh conversation first.
Event maps may omit :instance-id to target the current top frame and
may omit :signals to use {}. An event may also be a function of
the current conversation returning such a map.
Replay `events` against a baseline conversation or root flow id.
Returns `[conv fragments]`, matching [[dispatch]] / [[boot]]. When
the baseline is a flow id, replay boots a fresh conversation first.
Event maps may omit `:instance-id` to target the current top frame and
may omit `:signals` to use `{}`. An event may also be a function of
the current conversation returning such a map.(root-attrs self & attr-maps)See [[dev.zeko.stube.render/root-attrs]].
(set-keyed-children slot pairs)Effect: reconcile keyed children for a slot. See
dev.zeko.stube.effects/set-keyed-children and the diff semantics
documented in dev.zeko.stube.keyed.
Effect: reconcile keyed children for a slot. See [[dev.zeko.stube.effects/set-keyed-children]] and the diff semantics documented in [[dev.zeko.stube.keyed]].
(start!)(start! {:keys [port store ui-css? halos? app principal-fn conversation-ttl
reaper-interval]
:or {port 8080 ui-css? true halos? false}})See [[dev.zeko.stube.server/start!]].
(subscribe topic route-event)Effect: subscribe the current instance to topic.
Published messages arrive as route-event with the published value
in :payload. Re-emit this from :wakeup for components that
should resubscribe after crash-resume.
Effect: subscribe the current instance to `topic`. Published messages arrive as `route-event` with the published value in `:payload`. Re-emit this from `:wakeup` for components that should resubscribe after crash-resume.
(tree cid)Pretty-print the component tree for live conversation cid and
return the tree data. nil if the conversation is unknown.
Pretty-print the component tree for live conversation `cid` and return the tree data. nil if the conversation is unknown.
(unsubscribe)(unsubscribe topic)Effect: remove this instance's topic subscription(s).
Effect: remove this instance's topic subscription(s).
(where type-kw)Return the source location captured for component type-kw at
defcomponent time, or nil.
Return the source location captured for component `type-kw` at `defcomponent` time, or nil.
(with-app app-value & body)Run body with (s/app) returning app-value. Intended for
component-author tests that exercise handler/render code through
replay or dispatch without a running runtime.
(s/with-app {:db stub-conn}
(s/replay :my/component events))
Run `body` with `(s/app)` returning `app-value`. Intended for
component-author tests that exercise handler/render code through
[[replay]] or [[dispatch]] without a running runtime.
(s/with-app {:db stub-conn}
(s/replay :my/component events))(with-principal principal-value & body)Run body with (s/principal) returning principal-value. Pairs
with with-app for tests that need a stand-in principal without
minting a real conversation.
Run `body` with `(s/principal)` returning `principal-value`. Pairs with [[with-app]] for tests that need a stand-in principal without minting a real conversation.
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 |