Hiccup conversion and utilities for headless testing.
This namespace provides:
Hiccup format: [:tag {:attr "value"} child1 child2 ...]
Example workflow:
(require '[com.fulcrologic.fulcro.headless :as h])
(require '[com.fulcrologic.fulcro.headless.hiccup :as hic])
;; Get the rendered tree from a frame
(let [frame (h/last-frame app)
hiccup (hic/rendered-tree->hiccup (:rendered frame))]
;; Inspect and interact with the hiccup tree
(hic/find-by-id hiccup "my-button")
(hic/click! (hic/find-by-id hiccup "submit-btn")))
Hiccup conversion and utilities for headless testing.
This namespace provides:
- Conversion from dom-server Element trees to hiccup format
- Utility functions for inspecting and traversing hiccup trees
- Functions operate on hiccup trees directly (not apps)
Hiccup format: [:tag {:attr "value"} child1 child2 ...]
Example workflow:
```clojure
(require '[com.fulcrologic.fulcro.headless :as h])
(require '[com.fulcrologic.fulcro.headless.hiccup :as hic])
;; Get the rendered tree from a frame
(let [frame (h/last-frame app)
hiccup (hic/rendered-tree->hiccup (:rendered frame))]
;; Inspect and interact with the hiccup tree
(hic/find-by-id hiccup "my-button")
(hic/click! (hic/find-by-id hiccup "submit-btn")))
```(attr-of tree id attr-key)Get an attribute value from the element with the given ID. Returns nil if element not found or attribute not present.
Example:
(attr-of tree "email-input" :type) => "email"
Get an attribute value from the element with the given ID. Returns nil if element not found or attribute not present. Example: ```clojure (attr-of tree "email-input" :type) => "email" ```
(classes-of tree id)Get the CSS classes of the element with the given ID as a set. Returns nil if element not found.
Example:
(classes-of tree "my-btn") => #{"btn" "btn-primary"}
Get the CSS classes of the element with the given ID as a set.
Returns nil if element not found.
Example:
```clojure
(classes-of tree "my-btn") => #{"btn" "btn-primary"}
```(click! elem)(click! elem event)Invoke the :onClick handler of an element. The element should have been found via find-by-id or similar.
The handler will have already closed over the app when the component rendered, so no app parameter is needed. Handlers are automatically wrapped to be arity-tolerant during hiccup conversion.
Returns the result of the handler, or nil if no handler.
Example:
(click! (find-by-id tree "submit-btn"))
(click! (find-by-id tree "checkbox") {:target {:checked true}})
Invoke the :onClick handler of an element.
The element should have been found via find-by-id or similar.
The handler will have already closed over the app when the component rendered,
so no app parameter is needed. Handlers are automatically wrapped to be
arity-tolerant during hiccup conversion.
Returns the result of the handler, or nil if no handler.
Example:
```clojure
(click! (find-by-id tree "submit-btn"))
(click! (find-by-id tree "checkbox") {:target {:checked true}})
```(click-bubbling! tree pattern)(click-bubbling! tree pattern n)Find an element by text and invoke onClick, bubbling up to ancestors if necessary. Returns the result of the handler, or nil if no clickable element found.
This implements event bubbling semantics: if the matched element doesn't have an onClick handler, we walk up the ancestor chain to find one.
The n parameter specifies which match to click (0-indexed, default 0).
Pattern can be a string, regex, or vector of patterns.
Example:
(click-bubbling! tree "View All")
(click-bubbling! tree "New" 1) ; click second 'New' link
(click-bubbling! tree #"Logout")
Find an element by text and invoke onClick, bubbling up to ancestors if necessary. Returns the result of the handler, or nil if no clickable element found. This implements event bubbling semantics: if the matched element doesn't have an onClick handler, we walk up the ancestor chain to find one. The `n` parameter specifies which match to click (0-indexed, default 0). Pattern can be a string, regex, or vector of patterns. Example: ```clojure (click-bubbling! tree "View All") (click-bubbling! tree "New" 1) ; click second 'New' link (click-bubbling! tree #"Logout") ```
(element-attr elem attr-key)Get an attribute value from an element.
Example:
(element-attr [:input {:type "email" :value "x@y.com"}] :type)
=> "email"
Get an attribute value from an element.
Example:
```clojure
(element-attr [:input {:type "email" :value "x@y.com"}] :type)
=> "email"
```(element-attrs elem)Get the attributes map from an element. Returns nil if not a valid element or has no attrs map.
Example:
(element-attrs [:div {:id "x" :className "foo"} "text"])
=> {:id "x" :className "foo"}
Get the attributes map from an element.
Returns nil if not a valid element or has no attrs map.
Example:
```clojure
(element-attrs [:div {:id "x" :className "foo"} "text"])
=> {:id "x" :className "foo"}
```(element-children elem)Get the children of an element (everything after tag and attrs map).
Example:
(element-children [:div {} "Hello" [:span {} "World"]])
=> ["Hello" [:span {} "World"]]
Get the children of an element (everything after tag and attrs map).
Example:
```clojure
(element-children [:div {} "Hello" [:span {} "World"]])
=> ["Hello" [:span {} "World"]]
```(element-classes elem)Get the CSS classes of an element as a set. Returns an empty set if no classes.
Example:
(element-classes [:div {:className "btn btn-primary"}])
=> #{"btn" "btn-primary"}
Get the CSS classes of an element as a set.
Returns an empty set if no classes.
Example:
```clojure
(element-classes [:div {:className "btn btn-primary"}])
=> #{"btn" "btn-primary"}
```(element-exists? tree id)Returns true if an element with the given ID exists in the tree.
Example:
(exists? tree "submit-btn") => true
Returns true if an element with the given ID exists in the tree. Example: ```clojure (exists? tree "submit-btn") => true ```
(element-tag elem)Get the tag keyword from an element.
Example:
(element-tag [:div {:id "x"} "text"]) => :div
Get the tag keyword from an element.
Example:
```clojure
(element-tag [:div {:id "x"} "text"]) => :div
```(element-text elem)Get the text content of an element, recursively extracting all text. Returns a string with all text content concatenated.
Example:
(element-text [:div {} "Hello " [:span {} "World"]])
=> "Hello World"
Get the text content of an element, recursively extracting all text.
Returns a string with all text content concatenated.
Example:
```clojure
(element-text [:div {} "Hello " [:span {} "World"]])
=> "Hello World"
```(element? x)Returns true if x is a hiccup element (a vector with a keyword tag). Text nodes (strings) and nil are not elements.
Example:
(element? [:div {} "text"]) => true
(element? "just text") => false
Returns true if x is a hiccup element (a vector with a keyword tag).
Text nodes (strings) and nil are not elements.
Example:
```clojure
(element? [:div {} "text"]) => true
(element? "just text") => false
```(find-all tree pred)Find all elements in the tree matching the predicate. The predicate receives each element (hiccup vector). Returns a vector of matching elements.
Example:
(find-all tree #(= :button (element-tag %)))
=> [[:button {...} "Click"] [:button {...} "Cancel"]]
Find all elements in the tree matching the predicate.
The predicate receives each element (hiccup vector).
Returns a vector of matching elements.
Example:
```clojure
(find-all tree #(= :button (element-tag %)))
=> [[:button {...} "Click"] [:button {...} "Cancel"]]
```(find-by-class tree class-name)Find all elements with the given CSS class.
Example:
(find-by-class tree "btn-primary")
=> [[:button {:className "btn btn-primary"} "Submit"]]
Find all elements with the given CSS class.
Example:
```clojure
(find-by-class tree "btn-primary")
=> [[:button {:className "btn btn-primary"} "Submit"]]
```(find-by-id tree id)Find an element in the tree by its :id attribute. Returns the first matching element or nil.
Example:
(find-by-id tree "submit-btn")
=> [:button {:id "submit-btn" :onClick #fn} "Submit"]
Find an element in the tree by its :id attribute.
Returns the first matching element or nil.
Example:
```clojure
(find-by-id tree "submit-btn")
=> [:button {:id "submit-btn" :onClick #fn} "Submit"]
```(find-by-tag tree tag)Find all elements with the given tag keyword.
Example:
(find-by-tag tree :input)
=> [[:input {:type "text"...}] [:input {:type "email"...}]]
Find all elements with the given tag keyword.
Example:
```clojure
(find-by-tag tree :input)
=> [[:input {:type "text"...}] [:input {:type "email"...}]]
```(find-by-text tree pattern)Find all elements whose text content matches the pattern. Pattern can be:
Also checks :text and :label attributes on elements.
Returns only the most specific matching elements - parent containers are excluded if they only match because their children contain the text.
Example:
(find-by-text tree "View All")
(find-by-text tree #"Account.*")
(find-by-text tree ["Account" "View"]) ; must contain both
Find all elements whose text content matches the pattern. Pattern can be: - A string (substring match) - A regex (pattern match) - A vector of patterns (all must match) Also checks :text and :label attributes on elements. Returns only the most specific matching elements - parent containers are excluded if they only match because their children contain the text. Example: ```clojure (find-by-text tree "View All") (find-by-text tree #"Account.*") (find-by-text tree ["Account" "View"]) ; must contain both ```
(find-first tree pred)Find the first element matching the predicate, or nil.
Example:
(find-first tree #(= :form (element-tag %)))
=> [:form {:onSubmit #fn} ...]
Find the first element matching the predicate, or nil.
Example:
```clojure
(find-first tree #(= :form (element-tag %)))
=> [:form {:onSubmit #fn} ...]
```(find-first-by-text tree pattern)Find the first element matching the text pattern, or nil.
See find-by-text for pattern options.
Find the first element matching the text pattern, or nil. See `find-by-text` for pattern options.
(find-input-for-label tree label-pattern)Find an input element associated with a label matching the pattern.
Searches for:
Pattern can be a string, regex, or vector of patterns.
Example:
(find-input-for-label tree "Username")
(find-input-for-label tree #"(?i)email")
Find an input element associated with a label matching the pattern. Searches for: 1. A container element that has both a label (or element with :label attr) matching the pattern AND an input element 2. Returns the input from the smallest (most deeply nested) such container Pattern can be a string, regex, or vector of patterns. Example: ```clojure (find-input-for-label tree "Username") (find-input-for-label tree #"(?i)email") ```
(find-nth-by-text tree pattern n)Find the nth element (0-indexed) matching the text pattern, or nil.
See find-by-text for pattern options.
Find the nth element (0-indexed) matching the text pattern, or nil. See `find-by-text` for pattern options.
(find-nth-input-for-label tree label-pattern n)Find the nth input (0-indexed) associated with labels matching the pattern.
Useful when multiple fields have similar labels.
Example:
;; Get the second 'Amount' field
(find-nth-input-for-label tree "Amount" 1)
Find the nth input (0-indexed) associated with labels matching the pattern. Useful when multiple fields have similar labels. Example: ```clojure ;; Get the second 'Amount' field (find-nth-input-for-label tree "Amount" 1) ```
(has-class? elem class-name)Returns true if the element has the given CSS class.
Example:
(has-class? [:div {:className "btn active"}] "active") => true
Returns true if the element has the given CSS class.
Example:
```clojure
(has-class? [:div {:className "btn active"}] "active") => true
```Protocol for converting dom-server elements to hiccup format.
Protocol for converting dom-server elements to hiccup format.
(to-hiccup* this)Convert this element to hiccup.
Convert this element to hiccup.
(invoke-handler! elem handler-key & args)Invoke a specific handler on an element. The handler key can be any attribute (e.g., :onClick, :onChange, :onSubmit).
Returns the result of the handler, or nil if no handler.
Any additional arguments are applied to the handler, so you can match the arity of the target.
Example:
(invoke-handler! (find-by-id tree "email") :onChange {:target {:value "x@y.com"}})
Invoke a specific handler on an element.
The handler key can be any attribute (e.g., :onClick, :onChange, :onSubmit).
Returns the result of the handler, or nil if no handler.
Any additional arguments are applied to the handler, so you can match the arity of the target.
Example:
```clojure
(invoke-handler! (find-by-id tree "email") :onChange {:target {:value "x@y.com"}})
```(rendered-tree->hiccup x)Convert a dom-server Element tree to hiccup format. Preserves all attributes including function handlers (unlike render-to-str which strips them for HTML output).
This is the primary conversion function for headless testing where you want to inspect the DOM structure and invoke event handlers.
Accepts:
Returns:
Example:
(rendered-tree->hiccup (doms/div {:id "test" :onClick my-handler} "Hello"))
;; => [:div {:id "test" :onClick #function} "Hello"]
Convert a dom-server Element tree to hiccup format.
Preserves all attributes including function handlers (unlike render-to-str
which strips them for HTML output).
This is the primary conversion function for headless testing where you want
to inspect the DOM structure and invoke event handlers.
Accepts:
- A dom-server Element record
- A vector of Elements
- nil
Returns:
- A hiccup vector [:tag {...attrs} ...children]
- Or a vector of hiccup vectors for fragments
- Or nil for empty elements
Example:
```clojure
(rendered-tree->hiccup (doms/div {:id "test" :onClick my-handler} "Hello"))
;; => [:div {:id "test" :onClick #function} "Hello"]
```(text-of tree id)Get the text content of the element with the given ID. Returns nil if not found.
Example:
(text-of tree "counter") => "42"
Get the text content of the element with the given ID. Returns nil if not found. Example: ```clojure (text-of tree "counter") => "42" ```
(type-text! elem value)Invoke the :onChange handler with a text value. Convenience for simulating typing into an input.
Example:
(type-text! (find-by-id tree "username") "john.doe")
Invoke the :onChange handler with a text value. Convenience for simulating typing into an input. Example: ```clojure (type-text! (find-by-id tree "username") "john.doe") ```
(type-text-into-labeled! tree label-pattern value)(type-text-into-labeled! tree label-pattern value n)Find an input by its label and type text into it. Invokes the input's :onChange handler with {:target {:value text}}.
The n parameter specifies which matching labeled field to use (0-indexed, default 0).
Example:
(type-text-into-labeled! tree "Username" "john.doe")
(type-text-into-labeled! tree "Password" "secret123")
(type-text-into-labeled! tree "Amount" "100.00" 1) ; second Amount field
Find an input by its label and type text into it.
Invokes the input's :onChange handler with {:target {:value text}}.
The `n` parameter specifies which matching labeled field to use (0-indexed, default 0).
Example:
```clojure
(type-text-into-labeled! tree "Username" "john.doe")
(type-text-into-labeled! tree "Password" "secret123")
(type-text-into-labeled! tree "Amount" "100.00" 1) ; second Amount field
```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 |