From e3592712e33e0141b180140da002275c731ec681 Mon Sep 17 00:00:00 2001 From: Alys Brooks Date: Mon, 20 Mar 2023 11:37:59 -0500 Subject: [PATCH 1/8] WIP: Add tests borrowed from Hiccup's test suite. --- test/lambdaisland/hiccup_test.clj | 97 +++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/test/lambdaisland/hiccup_test.clj b/test/lambdaisland/hiccup_test.clj index 2abbaf1..cbe887c 100644 --- a/test/lambdaisland/hiccup_test.clj +++ b/test/lambdaisland/hiccup_test.clj @@ -76,3 +76,100 @@ (is (= (str "
") (hiccup/render [:div {input "baz"}] {:doctype? false}))))))) +;borrowed from Hiccup: + +(defmacro html [& body] + `(hiccup/render ~@body {:doctype? false}) + ) + +(macroexpand-1 '(html [:test])) + +(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 "keywords are turned into strings" + (is (= (str (html [:div :foo])) "
foo
"))) + (testing "vecs don't expand - error if vec doesn't have tag name" + (is (thrown? IllegalArgumentException + (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"))) + (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
")))) From 2df900d6e7c2c0a6185df93d636501913d9d111a Mon Sep 17 00:00:00 2001 From: Alys Brooks Date: Fri, 26 May 2023 15:11:11 -0500 Subject: [PATCH 2/8] Support symbols and strings as tag names. For example, `['div]` and `["div"] are now allowed. This ensures compatability with the original Hiccup and also Reagent --- src/lambdaisland/hiccup.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lambdaisland/hiccup.clj b/src/lambdaisland/hiccup.clj index 007df5e..36fbdf0 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) From c1f09580ef0919c03ad7bb7b4eed27d4b641a3bc Mon Sep 17 00:00:00 2001 From: Alys Brooks Date: Fri, 26 May 2023 15:22:02 -0500 Subject: [PATCH 3/8] Document discrepancies Document a few discrepancies I've noticed between our libraryy and others. --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 7b070ad..696e8d5 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,19 @@ 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, but there are some minor differences if +you are comparing the output character-for-character: + +* When the tag name has attributes and there's an attribute map, Attributes may appear in a different order. +* Illegal tags throw `clojure.lang.ExceptionInfo`, instead of + `IllegalArgumentException`, as in Hiccup, or AssertionError, as in Reagent. + + ## Lambda Island Open Source From eb458efb3ee98c9ce219b57af18caa71519d1782 Mon Sep 17 00:00:00 2001 From: Alys Brooks Date: Fri, 26 May 2023 15:23:39 -0500 Subject: [PATCH 4/8] Update test to check for correct exception. We throw a different exception than Hiccup does. --- test/lambdaisland/hiccup_test.clj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/lambdaisland/hiccup_test.clj b/test/lambdaisland/hiccup_test.clj index cbe887c..dce0a22 100644 --- a/test/lambdaisland/hiccup_test.clj +++ b/test/lambdaisland/hiccup_test.clj @@ -125,10 +125,8 @@ (testing "seqs are expanded" (is (= (str (html [:body (list "foo" "bar")])) "foobar")) (is (= (str (html (list [:p "a"] [:p "b"]))) "

a

b

"))) - (testing "keywords are turned into strings" - (is (= (str (html [:div :foo])) "
foo
"))) (testing "vecs don't expand - error if vec doesn't have tag name" - (is (thrown? IllegalArgumentException + (is (thrown? clojure.lang.ExceptionInfo (html (vector [:p "a"] [:p "b"]))))) (testing "tags can contain tags" (is (= (str (html [:div [:p]])) "

")) From a8576226387e658dee30ea7355484c877fb2eb98 Mon Sep 17 00:00:00 2001 From: Alys Brooks Date: Fri, 26 May 2023 15:52:50 -0500 Subject: [PATCH 5/8] Make the attribute id take priority over the tag. This matches the behavior of Reagent and the original Hiccup. For example, in `[:div#foo {:id "bar"}]`, the id would be bar and not foo. --- src/lambdaisland/hiccup.clj | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lambdaisland/hiccup.clj b/src/lambdaisland/hiccup.clj index 36fbdf0..eda0bd5 100644 --- a/src/lambdaisland/hiccup.clj +++ b/src/lambdaisland/hiccup.clj @@ -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"] From 70cd93efe7b5f6b6b45ccf421f7ea129fe73982d Mon Sep 17 00:00:00 2001 From: Alys Brooks Date: Fri, 26 May 2023 15:54:28 -0500 Subject: [PATCH 6/8] Tweak tests to account for non-semantic differences. Our output differs slightly in ways that don't make any semantic difference. --- test/lambdaisland/hiccup_test.clj | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/lambdaisland/hiccup_test.clj b/test/lambdaisland/hiccup_test.clj index dce0a22..71ab9c3 100644 --- a/test/lambdaisland/hiccup_test.clj +++ b/test/lambdaisland/hiccup_test.clj @@ -97,7 +97,7 @@ (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" @@ -163,11 +163,11 @@ (testing "map attributes" (is (= (str (html [:span {:style {:background-color :blue, :color "red", :line-width 1.2, :opacity "100%"}} "foo"])) - "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
")))) + "
baz
")))) ;swapped order from original test From fb9d086d542fa5675342b11e26e9dc89b2c9cc1b Mon Sep 17 00:00:00 2001 From: Alys Brooks Date: Fri, 26 May 2023 16:02:57 -0500 Subject: [PATCH 7/8] Improve explanation of implementation differences. Revise wording and add bullet about `style` attribute. --- README.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 696e8d5..2b4f388 100644 --- a/README.md +++ b/README.md @@ -47,12 +47,14 @@ or add the following to your `project.clj` ([Leiningen](https://leiningen.org/)) 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, but there are some minor differences if -you are comparing the output character-for-character: +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 the tag name has attributes and there's an attribute map, Attributes may appear in a different order. +* 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. + `IllegalArgumentException`, as in Hiccup, or `AssertionError`, as in Reagent. From c2dd8a514248cae575f87fd8584fc7238eca7d37 Mon Sep 17 00:00:00 2001 From: Alys Brooks Date: Fri, 26 May 2023 16:08:35 -0500 Subject: [PATCH 8/8] Clean up code Remove some issues that were in the original Hiccup tests. --- test/lambdaisland/hiccup_test.clj | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/test/lambdaisland/hiccup_test.clj b/test/lambdaisland/hiccup_test.clj index 71ab9c3..3e93b61 100644 --- a/test/lambdaisland/hiccup_test.clj +++ b/test/lambdaisland/hiccup_test.clj @@ -79,10 +79,7 @@ ;borrowed from Hiccup: (defmacro html [& body] - `(hiccup/render ~@body {:doctype? false}) - ) - -(macroexpand-1 '(html [:test])) + `(hiccup/render ~@body {:doctype? false})) (deftest tag-names (testing "basic tags"