Liking cljdoc? Tell your friends :D

Lookup your hiccup

You wrote a function that returns hiccup, and now you want to test it.

But you don't care about all the bells and whistles,
only certain bits and pieces.

That's what lookup helps you do:
Find the parts that matter to you.


Lookup is a testing library for hiccup-like data, and it helps you:

Install

With tools.deps:

no.cjohansen/lookup {:mvn/version "2024.12.22"}

With Leiningen:

[no.cjohansen/lookup "2024.12.22"]

Find interesting bits with CSS selectors

lookup.core/select can be used to find hiccup nodes with a CSS selector. A selector can be a single expression, like 'div to find all divs, or a vector representing a hierarchy, e.g. '[div button.btn] finds all button elements with the class name "btn" that are descendants of a div element. The > character can be used to specify direct children, e.g. '[ul > li] will only match li elements with a direct ul parent.

(require '[lookup.core :as lookup])

(def hiccup
  [:div
   [:ul
    nil
    [:li "B1"]
    [:li.active
     [:a {:href "#"} "C"]]
    [:li "D1"]
    [:li "E"]]
   [:p "Paragraph 1"]
   [:h1 "Heading"]
   [:p "Paragraph 2"]])

(lookup/select '[ul > li a] hiccup)
;;=> [[:a {:href "#"} "C"]]

(lookup.core/select-one selector hiccup) returns the first element matching the selector.

Supported selector symbols:

  • 'a matches all anchor tags.
  • '[form input] matches all input tags nested inside a form.
  • '[form > input] matches all input tags that are direct children of a form.
  • '[h1 + p] matches all paragraphs that are direct siblings of an h1.
  • '[h1 ~ p] matches all paragraphs that are subsequent siblings of an h1.
  • 'div.foo matches all div tags with "foo" in its class name.
  • '.button matches all elements with the "button" class.
  • 'div#content matches the div with "content" as its id.
  • ':first-child matches any element that is the first child.
  • ':last-child matches any element that is the last child.
  • '"meta[property]" matches all meta tags with the property attribute.
  • '"meta[property=og:title]" matches all meta tags with the property attribute set to "og:title".
  • h1:has(a) matches all h1 elements that contain the provided selector

Additionally supports all attribute selector operators.

Normalize hiccup

Hiccup is a flexible format, and when built with code it will often contain noise such as nested lists of children, nils, classes in several places, etc. lookup.core/normalize-hiccup unifies all these things:

(require '[lookup.core :as lookup])

(lookup/normalize-hiccup
 [:div
  [:ul
   nil
   [:li {} "One"]
   [:li.active
    [:a {:href "#"} "Two"]]
   [:li "Three"]
   [:li "Four"]]
  [:p {:class "text-sm fg-red"} "Paragraph 1"]
  '([:h1 "Heading"]
   [:p "Paragraph 2"])])

;;=>
;; [:div {}
;;  [:ul {}
;;   [:li {} "One"]
;;   [:li {:class #{"active"}} [:a {:href "#"} "Two"]]
;;   [:li {} "Three"]
;;   [:li {} "Four"]]
;;  [:p {:class #{"fg-red" "text-sm"}}
;;   "Paragraph 1"]
;;  [:h1 {} "Heading"]
;;  [:p {} "Paragraph 2"]]

This form is ideal for further programmatic manipulation, as every node is guaranteed to have an attribute map and a flat list of children. If you want something that's better suited for human reading, employ :strip-empty-attrs?:

(require '[lookup.core :as lookup])

(lookup/normalize-hiccup
[:div
  [:ul
   nil
   [:li {} "One"]
   [:li.active
    [:a {:href "#"} "Two"]]
   [:li "Three"]
   [:li "Four"]]
  [:p {:class "text-sm fg-red"} "Paragraph 1"]
  '([:h1 "Heading"]
   [:p "Paragraph 2"])]
 {:strip-empty-attrs? true})

;;=>
;; [:div
;;  [:ul
;;   [:li "One"]
;;   [:li {:class #{"active"}}
;;    [:a {:href "#"} "Two"]]
;;   [:li "Three"]
;;   [:li "Four"]]
;;  [:p {:class #{"fg-red" "text-sm"}}
;;   "Paragraph 1"]
;;  [:h1 "Heading"]
;;  [:p "Paragraph 2"]]

To make assertions about the normalized hiccup, you can use lookup.core/attrs and lookup.core/children, which both take a hiccup form and returns the corresponding details:

(require '[lookup.core :as lookup])

(def hiccup
  [:p {:class "text-sm fg-red"}
   "Paragraph " [:strong "1"]])

(lookup/attrs hiccup)
;;=> {:class "text-sm fg-red"}

(lookup/children hiccup)
;;=> ("Paragraph " [:strong "1"])

Extract text content

lookup.core/text returns the text content of some hiccup:

(require '[lookup.core :as lookup])

(lookup/text [:h1 "Hello world"])
;;=> "Hello world"

(lookup/text
 [:div
  [:ul
   nil
   [:li {} "One"]
   [:li.active
    [:a {:href "#"} "Two"]]
   [:li "Three"]
   [:li "Four"]]
  [:p {:class "text-sm fg-red"} "Paragraph 1"]
  '([:h1 "Heading"]
    [:p "Paragraph 2"])])

;;=> "One Two Three Four Paragraph 1 Heading Paragraph 2"

text also works on collections of hiccup nodes, and can be used in conjunction with select:

(require '[lookup.core :as lookup])

(def hiccup
  [:div
   [:ul
    nil
    [:li "B1"]
    [:li.active
     [:a {:href "#"} "C"]]
    [:li "D1"]
    [:li "E"]]
   [:p "Paragraph 1"]
   [:h1 "Heading"]
   [:p "Paragraph 2"]])

(->> hiccup
     (lookup/select '[ul > li])
     text)
;;=> "B1 C D1 E"

Can you improve this documentation?Edit on GitHub

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close