diff --git a/README.md b/README.md index 7b070ad..2b4f388 100644 --- a/README.md +++ b/README.md @@ -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. + + ## Lambda Island Open Source diff --git a/src/lambdaisland/hiccup.clj b/src/lambdaisland/hiccup.clj index 007df5e..eda0bd5 100644 --- a/src/lambdaisland/hiccup.clj +++ b/src/lambdaisland/hiccup.clj @@ -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) @@ -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"] diff --git a/test/lambdaisland/hiccup_test.clj b/test/lambdaisland/hiccup_test.clj index 2abbaf1..3e93b61 100644 --- a/test/lambdaisland/hiccup_test.clj +++ b/test/lambdaisland/hiccup_test.clj @@ -76,3 +76,95 @@ (is (= (str "
") (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])) "
")) + (is (= (str (html ["div"])) "
")) + (is (= (str (html ['div])) "
"))) + (testing "tag syntax sugar" + (is (= (str (html [:div#foo])) "
")) + (is (= (str (html [:div.foo])) "
")) + (is (= (str (html [:div.foo (str "bar" "baz")])) + "
barbaz
")) + (is (= (str (html [:div.a.b])) "
")) + (is (= (str (html [:div.a.b.c])) "
")) + (is (= (str (html [:div#foo.bar.baz])) + "
")))) + +(deftest tag-contents + (testing "empty tags" + (is (= (str (html [:div])) "
")) + (is (= (str (html [:h1])) "

")) + (is (= (str (html [:script])) "")) + (is (= (str (html [:text])) "")) + (is (= (str (html [:a])) "")) + (is (= (str (html [:iframe])) "")) + (is (= (str (html [:title])) "")) + (is (= (str (html [:section])) "
")) + (is (= (str (html [:select])) "")) + (is (= (str (html [:object])) "")) + (is (= (str (html [:video])) ""))) + (testing "void tags" + (is (= (str (html [:br])) "
")) + (is (= (str (html [:link])) "")) + (is (= (str (html [:colgroup {:span 2}])) "")) + (is (= (str (html [:colgroup [:col]])) ""))) + (testing "tags containing text" + (is (= (str (html [:text "Lorem Ipsum"])) "Lorem Ipsum"))) + (testing "contents are concatenated" + (is (= (str (html [:body "foo" "bar"])) "foobar")) + (is (= (str (html [:body [:p] [:br]])) "


"))) + (testing "seqs are expanded" + (is (= (str (html [:body (list "foo" "bar")])) "foobar")) + (is (= (str (html (list [:p "a"] [:p "b"]))) "

a

b

"))) + (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]])) "

")) + (is (= (str (html [:div [:b]])) "
")) + (is (= (str (html [:p [:span [:a "foo"]]])) + "

foo

")))) + +(deftest tag-attributes + (testing "tag with blank attribute map" + (is (= (str (html [:xml {}])) ""))) + (testing "tag with populated attribute map" + (is (= (str (html [:xml {:a "1", :b "2"}])) "")) + (is (= (str (html [:img {"id" "foo"}])) "")) + (is (= (str (html [:img {'id "foo"}])) "")) + (is (= (str (html [:xml {:a "1", 'b "2", "c" "3"}])) + ""))) + (testing "attribute values are escaped" + (is (= (str (html [:div {:id "\""}])) "
"))) + (testing "boolean attributes" + #_(is (= (str (html [:input {:type "checkbox" :checked true}])) + "")) + (is (= (str (html [:input {:type "checkbox" :checked false}])) + ""))) + (testing "nil attributes" + (is (= (str (html [:span {:class nil} "foo"])) + "foo"))) + (testing "vector attributes" + (is (= (str (html [:span {:class ["bar" "baz"]} "foo"])) + "foo")) + (is (= (str (html [:span {:class ["baz"]} "foo"])) + "foo")) + (is (= (str (html [:span {:class "baz bar"} "foo"])) + "foo"))) + (testing "map attributes" + (is (= (str (html [:span {:style {:background-color :blue, :color "red", + :line-width 1.2, :opacity "100%"}} "foo"])) + "foo"))) ;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"])) + "
baz
")) + (is (= (str (html [:div.foo {:class ["bar"]} "baz"])) + "
baz
")) + (is (= (str (html [:div#bar.foo {:id "baq"} "baz"])) + "
baz
")))) ;swapped order from original test