Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests borrowed from Hiccup's test suite and make small tweaks to match Hiccup/Reagent behavior. #4

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,21 @@ or add the following to your `project.clj` ([Leiningen](https://leiningen.org/))
(h/render [:html [:body ...]])
```

## Differences between Lambda Island Hiccup and other implementation.

We try to minimize differences between Lambda Island Hiccup and Reagent, first
and foremost. Whenver possible, we try to minimize differences between between Lambda Island Hiccup and the original Hiccup as well.

There are no known semantic differences in the resulting HTML, but there are some minor differences if
you are comparing the output character-for-character and one runtime
difference:

* When attributes are specified in both the tag name and a map, attributes may appear in a different order.
* The CSS in `style` attributes has different whitespace.
* Illegal tags throw `clojure.lang.ExceptionInfo`, instead of
`IllegalArgumentException`, as in Hiccup, or `AssertionError`, as in Reagent.


<!-- opencollective -->
## Lambda Island Open Source

Expand Down
5 changes: 3 additions & 2 deletions src/lambdaisland/hiccup.clj
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
(= :<> tag)
(enlive/flatmap #(nodify % opts) more)

(keyword? tag)
(or (keyword? tag) (symbol? tag) (string? tag))
(let [[tag-name & segments] (.split (name tag) "(?=[#.])")
id (some (fn [^String seg]
(when (= \# (.charAt seg 0)) (subs seg 1))) segments)
Expand All @@ -107,7 +107,8 @@
(map (fn [[k v]]
[(convert-attribute k) v]))
(into {}))))
node (if id (assoc-in node [:attrs :id] id) node)
node (if (and id (not (contains? (:attrs node) :id)))
(assoc-in node [:attrs :id] id) node)
node (if (seq classes)
(update-in node
[:attrs "class"]
Expand Down
92 changes: 92 additions & 0 deletions test/lambdaisland/hiccup_test.clj
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,95 @@
(is (= (str "<div " expected "=\"baz\"></div>")
(hiccup/render [:div {input "baz"}]
{:doctype? false})))))))
;borrowed from Hiccup:

(defmacro html [& body]
`(hiccup/render ~@body {:doctype? false}))

(deftest tag-names
(testing "basic tags"
(is (= (str (html [:div])) "<div></div>"))
(is (= (str (html ["div"])) "<div></div>"))
(is (= (str (html ['div])) "<div></div>")))
(testing "tag syntax sugar"
(is (= (str (html [:div#foo])) "<div id=\"foo\"></div>"))
(is (= (str (html [:div.foo])) "<div class=\"foo\"></div>"))
(is (= (str (html [:div.foo (str "bar" "baz")]))
"<div class=\"foo\">barbaz</div>"))
(is (= (str (html [:div.a.b])) "<div class=\"a b\"></div>"))
(is (= (str (html [:div.a.b.c])) "<div class=\"a b c\"></div>"))
(is (= (str (html [:div#foo.bar.baz]))
"<div id=\"foo\" class=\"bar baz\"></div>"))))

(deftest tag-contents
(testing "empty tags"
(is (= (str (html [:div])) "<div></div>"))
(is (= (str (html [:h1])) "<h1></h1>"))
(is (= (str (html [:script])) "<script></script>"))
(is (= (str (html [:text])) "<text></text>"))
(is (= (str (html [:a])) "<a></a>"))
(is (= (str (html [:iframe])) "<iframe></iframe>"))
(is (= (str (html [:title])) "<title></title>"))
(is (= (str (html [:section])) "<section></section>"))
(is (= (str (html [:select])) "<select></select>"))
(is (= (str (html [:object])) "<object></object>"))
(is (= (str (html [:video])) "<video></video>")))
(testing "void tags"
(is (= (str (html [:br])) "<br />"))
(is (= (str (html [:link])) "<link />"))
(is (= (str (html [:colgroup {:span 2}])) "<colgroup span=\"2\"></colgroup>"))
(is (= (str (html [:colgroup [:col]])) "<colgroup><col /></colgroup>")))
(testing "tags containing text"
(is (= (str (html [:text "Lorem Ipsum"])) "<text>Lorem Ipsum</text>")))
(testing "contents are concatenated"
(is (= (str (html [:body "foo" "bar"])) "<body>foobar</body>"))
(is (= (str (html [:body [:p] [:br]])) "<body><p></p><br /></body>")))
(testing "seqs are expanded"
(is (= (str (html [:body (list "foo" "bar")])) "<body>foobar</body>"))
(is (= (str (html (list [:p "a"] [:p "b"]))) "<p>a</p><p>b</p>")))
(testing "vecs don't expand - error if vec doesn't have tag name"
(is (thrown? clojure.lang.ExceptionInfo
(html (vector [:p "a"] [:p "b"])))))
(testing "tags can contain tags"
(is (= (str (html [:div [:p]])) "<div><p></p></div>"))
(is (= (str (html [:div [:b]])) "<div><b></b></div>"))
(is (= (str (html [:p [:span [:a "foo"]]]))
"<p><span><a>foo</a></span></p>"))))

(deftest tag-attributes
(testing "tag with blank attribute map"
(is (= (str (html [:xml {}])) "<xml></xml>")))
(testing "tag with populated attribute map"
(is (= (str (html [:xml {:a "1", :b "2"}])) "<xml a=\"1\" b=\"2\"></xml>"))
(is (= (str (html [:img {"id" "foo"}])) "<img id=\"foo\" />"))
(is (= (str (html [:img {'id "foo"}])) "<img id=\"foo\" />"))
(is (= (str (html [:xml {:a "1", 'b "2", "c" "3"}]))
"<xml a=\"1\" b=\"2\" c=\"3\"></xml>")))
(testing "attribute values are escaped"
(is (= (str (html [:div {:id "\""}])) "<div id=\"&quot;\"></div>")))
(testing "boolean attributes"
#_(is (= (str (html [:input {:type "checkbox" :checked true}]))
"<input type=\"checkbox\" checked=\"checked\" />"))
(is (= (str (html [:input {:type "checkbox" :checked false}]))
"<input type=\"checkbox\" />")))
(testing "nil attributes"
(is (= (str (html [:span {:class nil} "foo"]))
"<span>foo</span>")))
(testing "vector attributes"
(is (= (str (html [:span {:class ["bar" "baz"]} "foo"]))
"<span class=\"bar baz\">foo</span>"))
(is (= (str (html [:span {:class ["baz"]} "foo"]))
"<span class=\"baz\">foo</span>"))
(is (= (str (html [:span {:class "baz bar"} "foo"]))
"<span class=\"baz bar\">foo</span>")))
(testing "map attributes"
(is (= (str (html [:span {:style {:background-color :blue, :color "red",
:line-width 1.2, :opacity "100%"}} "foo"]))
"<span style=\"background-color: blue;\n color: red;\n line-width: 1.2;\n opacity: 100%;\">foo</span>"))) ;format tweaked from original to match our format
(testing "resolving conflicts between attributes in the map and tag"
(is (= (str (html [:div.foo {:class "bar"} "baz"]))
"<div class=\"foo bar\">baz</div>"))
(is (= (str (html [:div.foo {:class ["bar"]} "baz"]))
"<div class=\"foo bar\">baz</div>"))
(is (= (str (html [:div#bar.foo {:id "baq"} "baz"]))
"<div id=\"baq\" class=\"foo\">baz</div>")))) ;swapped order from original test