diff --git a/src/reagent/core.cljs b/src/reagent/core.cljs index b6b8828..215d278 100644 --- a/src/reagent/core.cljs +++ b/src/reagent/core.cljs @@ -46,8 +46,8 @@ (defn as-element "Turns a vector of Hiccup syntax into a React element. Returns form unchanged if it is not a vector." - [form] - (tmpl/as-element form)) + ([form] (tmpl/as-element form nil)) + ([form opts] (tmpl/as-element form opts))) (defn adapt-react-class "Returns an adapter for a native React class, that may be used @@ -60,9 +60,10 @@ "Returns an adapter for a Reagent component, that may be used from React, for example in JSX. A single argument, props, is passed to the component, converted to a map." - [c] - (assert-some c "Component") - (comp/reactify-component c)) + ([c] (reactify-component c nil)) + ([c opts] + (assert-some c "Component") + (comp/reactify-component c opts))) (defn render "Render a Reagent component into the DOM. The first argument may be diff --git a/src/reagent/dom.cljs b/src/reagent/dom.cljs index a9ebc27..6827be8 100644 --- a/src/reagent/dom.cljs +++ b/src/reagent/dom.cljs @@ -34,10 +34,13 @@ Returns the mounted component instance." ([comp container] (render comp container nil)) - ([comp container callback] + ([comp container callback-or-opts] (ratom/flush!) - (let [f (fn [] - (tmpl/as-element (if (fn? comp) (comp) comp)))] + (let [[opts callback] (if (fn? callback-or-opts) + [nil callback-or-opts] + [callback-or-opts (:callback callback-or-opts)]) + f (fn [] + (tmpl/as-element (if (fn? comp) (comp) comp) opts))] (render-comp f container callback)))) (defn unmount-component-at-node diff --git a/src/reagent/dom/server.cljs b/src/reagent/dom/server.cljs index f538b53..da877bd 100644 --- a/src/reagent/dom/server.cljs +++ b/src/reagent/dom/server.cljs @@ -6,14 +6,18 @@ (defn render-to-string "Turns a component into an HTML string." - [component] - (ratom/flush!) - (binding [util/*non-reactive* true] - (dom-server/renderToString (tmpl/as-element component)))) + ([component] + (render-to-string component nil)) + ([component opts] + (ratom/flush!) + (binding [util/*non-reactive* true] + (dom-server/renderToString (tmpl/as-element component opts))))) (defn render-to-static-markup "Turns a component into an HTML string, without data-react-id attributes, etc." - [component] - (ratom/flush!) + ([component] + (render-to-static-markup component nil)) + ([component opts] + (ratom/flush!) (binding [util/*non-reactive* true] - (dom-server/renderToStaticMarkup (tmpl/as-element component)))) + (dom-server/renderToStaticMarkup (tmpl/as-element component opts))))) diff --git a/src/reagent/impl/component.cljs b/src/reagent/impl/component.cljs index 77ef97d..915e2ad 100644 --- a/src/reagent/impl/component.cljs +++ b/src/reagent/impl/component.cljs @@ -111,10 +111,11 @@ 5 (.call f c (nth v 1) (nth v 2) (nth v 3) (nth v 4)) (.apply f c (.slice (into-array v) 1)))))] (cond - (vector? res) (as-element res) + ;; FIXME: Opts + (vector? res) (as-element res nil) (ifn? res) (let [f (if (reagent-class? res) (fn [& args] - (as-element (apply vector res args))) + (as-element (apply vector res args) nil)) res)] (set! (.-reagentRender c) f) (recur c)) @@ -373,7 +374,7 @@ cmp)) -(defn fn-to-class [f] +(defn fn-to-class [f opts] (assert-callable f) (warn-unless (not (and (react-class? f) (not (reagent-class? f)))) @@ -389,15 +390,16 @@ res (create-class withrender)] (cache-react-class f res)))) -(defn as-class [tag] +(defn as-class [tag opts] + ;; TODO: Cache per opts (if-some [cached-class (cached-react-class tag)] cached-class - (fn-to-class tag))) + (fn-to-class tag opts))) -(defn reactify-component [comp] +(defn reactify-component [comp opts] (if (react-class? comp) comp - (as-class comp))) + (as-class comp opts))) (defn functional-wrap-render [c] @@ -406,10 +408,10 @@ argv (.-argv c) res (apply f argv)] (cond - (vector? res) (as-element res) + (vector? res) (as-element res (.-opts c)) (ifn? res) (let [f (if (reagent-class? res) (fn [& args] - (as-element (apply vector res args))) + (as-element (apply vector res args) (.-opts c))) res)] (set! (.-reagentRender c) f) (recur c)) @@ -501,6 +503,7 @@ original Reagent component." [tag] ;; TODO: Could be disabled for optimized builds? + ;; TODO: Need to cache per opts? (or (gobj/get fun-components tag) (let [f (fn [jsprops] (functional-render jsprops)) _ (set! (.-displayName f) (util/fun-name tag)) diff --git a/src/reagent/impl/template.cljs b/src/reagent/impl/template.cljs index 15333b6..db38bee 100644 --- a/src/reagent/impl/template.cljs +++ b/src/reagent/impl/template.cljs @@ -246,10 +246,10 @@ :component-did-update input-component-set-value :component-will-unmount input-unmount :reagent-render - (fn [argv component jsprops first-child] + (fn [argv component jsprops first-child opts] (let [this comp/*current-component*] (input-render-setup this jsprops) - (make-element argv component jsprops first-child)))}) + (make-element argv component jsprops first-child opts)))}) (defn reagent-input [] @@ -293,13 +293,21 @@ (if (= :> (nth v 0 nil)) (get-key (nth v 2 nil)))))) -(defn reag-element [tag v] +(defn reag-element [tag v opts] + (let [c (comp/as-class tag opts) + jsprops #js {}] + (set! (.-argv jsprops) v) + (when-some [key (key-from-vec v)] + (set! (.-key jsprops) key)) + (react/createElement c jsprops))) + +(defn functional-reag-element [tag v opts] (if (or (comp/react-class? tag) ;; TODO: Should check others for real comptibility, this fixes tests ;; TODO: Drop support for fn + meta for Class component methods? (:should-component-update (meta tag))) ;; as-class unncessary later as tag is always class - (let [c (comp/as-class tag) + (let [c (comp/as-class tag opts) jsprops #js {}] (set! (.-argv jsprops) v) (when-some [key (key-from-vec v)] @@ -308,6 +316,7 @@ (let [jsprops #js {}] (set! (.-reagentRender jsprops) tag) (set! (.-argv jsprops) (subvec v 1)) + (set! (.-opts jsprops) opts) (when-some [key (key-from-vec v)] (set! (.-key jsprops) key)) (react/createElement (comp/funtional-render-fn tag) jsprops)))) @@ -335,7 +344,7 @@ (gobj/set tag-name-cache x v) v))) -(defn native-element [parsed argv first] +(defn native-element [parsed argv first opts] (let [component (.-tag parsed) props (nth argv first nil) hasprops (or (nil? props) (map? props)) @@ -343,13 +352,13 @@ #js {}) first-child (+ first (if hasprops 1 0))] (if (input-component? component) - (-> [(reagent-input) argv component jsprops first-child] + (-> [(reagent-input) argv component jsprops first-child opts] (with-meta (meta argv)) - as-element) + (as-element opts)) (do (when-some [key (-> (meta argv) get-key)] (set! (.-key jsprops) key)) - (make-element argv component jsprops first-child))))) + (make-element argv component jsprops first-child opts))))) (defn str-coll [coll] (if (dev?) @@ -365,7 +374,7 @@ (defn hiccup-err [v & msg] (str (apply str msg) ": " (str-coll v) "\n" (comp/comp-name))) -(defn vec-to-elem [v] +(defn vec-to-elem [v opts] (assert (pos? (count v)) (hiccup-err v "Hiccup form should not be empty")) (let [tag (nth v 0 nil)] (assert (valid-tag? tag) (hiccup-err v "Invalid Hiccup form")) @@ -377,53 +386,56 @@ (let [n (name tag) pos (.indexOf n ">")] (case pos - -1 (native-element (cached-parse n) v 1) + -1 (native-element (cached-parse n) v 1 opts) 0 (let [component (nth v 1 nil)] ;; Support [:> component ...] (assert (= ">" n) (hiccup-err v "Invalid Hiccup tag")) - (native-element (->HiccupTag component nil nil nil) v 2)) + (native-element (->HiccupTag component nil nil nil) v 2 opts)) ;; Support extended hiccup syntax, i.e :div.bar>a.foo ;; Apply metadata (e.g. :key) to the outermost element. ;; Metadata is probably used only with sequeneces, and in that case ;; only the key of the outermost element matters. (recur (with-meta [(subs n 0 pos) (assoc (with-meta v nil) 0 (subs n (inc pos)))] - (meta v))))) + (meta v)) + opts))) (instance? NativeWrapper tag) - (native-element tag v 1) + (native-element tag v 1 opts) - :else (reag-element tag v)))) + :else (if (:functional-reag-elements? opts) + (functional-reag-element tag v opts) + (reag-element tag v opts))))) (declare expand-seq) (declare expand-seq-check) -(defn as-element [x] +(defn as-element [x opts] (cond (js-val? x) x - (vector? x) (vec-to-elem x) + (vector? x) (vec-to-elem x opts) (seq? x) (if (dev?) - (expand-seq-check x) - (expand-seq x)) + (expand-seq-check x opts) + (expand-seq x opts)) (named? x) (name x) (satisfies? IPrintWithWriter x) (pr-str x) :else x)) (set! comp/as-element as-element) -(defn expand-seq [s] +(defn expand-seq [s opts] (into-array (map as-element s))) -(defn expand-seq-dev [s ^clj o] +(defn expand-seq-dev [s ^clj o opts] (into-array (map (fn [val] (when (and (vector? val) (nil? (key-from-vec val))) (set! (.-no-key o) true)) - (as-element val)) + (as-element val opts)) s))) -(defn expand-seq-check [x] +(defn expand-seq-check [x opts] (let [ctx #js{} - [res derefed] (ratom/check-derefs #(expand-seq-dev x ctx))] + [res derefed] (ratom/check-derefs #(expand-seq-dev x ctx opts))] (when derefed (warn (hiccup-err x "Reactive deref not supported in lazy seq, " "it should be wrapped in doall"))) @@ -431,17 +443,17 @@ (warn (hiccup-err x "Every element in a seq should have a unique :key"))) res)) -(defn make-element [argv component jsprops first-child] +(defn make-element [argv component jsprops first-child opts] (case (- (count argv) first-child) ;; Optimize cases of zero or one child 0 (react/createElement component jsprops) 1 (react/createElement component jsprops - (as-element (nth argv first-child nil))) + (as-element (nth argv first-child nil) opts)) (.apply react/createElement nil (reduce-kv (fn [a k v] (when (>= k first-child) - (.push a (as-element v))) + (.push a (as-element v opts))) a) #js[component jsprops] argv)))) diff --git a/test/reagenttest/performancetest.cljs b/test/reagenttest/performancetest.cljs index e129b08..78e8303 100644 --- a/test/reagenttest/performancetest.cljs +++ b/test/reagenttest/performancetest.cljs @@ -10,14 +10,14 @@ (js/performance.mark "functional-start") ; (simple-benchmark [x [hello-world-component]] (tmpl/vec-to-elem x) 100000) (dotimes [i 100000] - (tmpl/vec-to-elem [hello-world-component])) + (tmpl/vec-to-elem [hello-world-component] nil)) (js/performance.mark "functional-end") (js/performance.measure "functional" "functional-start" "functional-end") (js/performance.mark "class-start") ; (simple-benchmark [x [^:class-component hello-world-component]] (tmpl/vec-to-elem x) 100000) (dotimes [i 100000] - (tmpl/vec-to-elem [^:class-component hello-world-component])) + (tmpl/vec-to-elem [^:class-component hello-world-component] nil)) (js/performance.mark "class-end") (js/performance.measure "class" "class-start" "class-end") ) diff --git a/test/reagenttest/testreagent.cljs b/test/reagenttest/testreagent.cljs index 9dc01fc..15827a1 100644 --- a/test/reagenttest/testreagent.cljs +++ b/test/reagenttest/testreagent.cljs @@ -19,8 +19,8 @@ :after (fn [] (set! rv/debug false))}) -(defn rstr [react-elem] - (server/render-to-static-markup react-elem)) +(defn rstr [react-elem opts] + (server/render-to-static-markup react-elem opts)) (defn log-error [& f] (debug/error (apply str f))) @@ -34,315 +34,342 @@ (finally (set! js/console.error org)))))) +;; Different set of options to try for most test cases + +(def test-options + [nil + {:functional-reag-elements? true}] ) + (deftest really-simple-test (when r/is-client - (let [ran (r/atom 0) - really-simple (fn [] - (swap! ran inc) - [:div "div in really-simple"])] - (with-mounted-component [really-simple nil nil] - (fn [c div] - (swap! ran inc) - (is (= "div in really-simple" (.-innerText div))) - (r/flush) - (is (= 2 @ran)) - (rdom/force-update-all) - (is (= 3 @ran)))) - (is (= 3 @ran))))) + (doseq [opts test-options] + (let [ran (r/atom 0) + really-simple (fn [] + (swap! ran inc) + [:div "div in really-simple"])] + (with-mounted-component [really-simple nil nil] + opts + (fn [c div] + (swap! ran inc) + (is (= "div in really-simple" (.-innerText div))) + (r/flush) + (is (= 2 @ran)) + (rdom/force-update-all) + (is (= 3 @ran)))) + (is (= 3 @ran)))))) (deftest test-simple-callback (when r/is-client - (let [ran (r/atom 0) - comp (r/create-class - {:component-did-mount #(swap! ran inc) - :render - (fn [this] - (let [props (r/props this)] - (is (map? props)) - (is (= props ((r/argv this) 1))) - (is (= 1 (first (r/children this)))) - (is (= 1 (count (r/children this)))) - (swap! ran inc) - [:div (str "hi " (:foo props) ".")]))})] - (with-mounted-component [comp {:foo "you"} 1] - (fn [C div] - (swap! ran inc) - (is (= "hi you." (.-innerText div))))) - (is (= 3 @ran))))) + (doseq [opts test-options] + (let [ran (r/atom 0) + comp (r/create-class + {:component-did-mount #(swap! ran inc) + :render + (fn [this] + (let [props (r/props this)] + (is (map? props)) + (is (= props ((r/argv this) 1))) + (is (= 1 (first (r/children this)))) + (is (= 1 (count (r/children this)))) + (swap! ran inc) + [:div (str "hi " (:foo props) ".")]))})] + (with-mounted-component [comp {:foo "you"} 1] + opts + (fn [C div] + (swap! ran inc) + (is (= "hi you." (.-innerText div))))) + (is (= 3 @ran)))))) (deftest test-state-change (when r/is-client - (let [ran (r/atom 0) - self (r/atom nil) - comp (r/create-class - {:get-initial-state (fn [] {:foo "initial"}) - :reagent-render - (fn [] - (let [this (r/current-component)] - (reset! self this) - (swap! ran inc) - [:div (str "hi " (:foo (r/state this)))]))})] - (with-mounted-component [comp] - (fn [C div] - (swap! ran inc) - (is (= "hi initial" (.-innerText div))) + (doseq [opts test-options] + (let [ran (r/atom 0) + self (r/atom nil) + comp (r/create-class + {:get-initial-state (fn [] {:foo "initial"}) + :reagent-render + (fn [] + (let [this (r/current-component)] + (reset! self this) + (swap! ran inc) + [:div (str "hi " (:foo (r/state this)))]))})] + (with-mounted-component [comp] + opts + (fn [C div] + (swap! ran inc) + (is (= "hi initial" (.-innerText div))) - (r/replace-state @self {:foo "there"}) - (r/state @self) + (r/replace-state @self {:foo "there"}) + (r/state @self) - (r/flush) - (is (= "hi there" (.-innerText div))) + (r/flush) + (is (= "hi there" (.-innerText div))) - (r/set-state @self {:foo "you"}) - (r/flush) - (is (= "hi you" (.-innerText div))))) - (is (= 4 @ran))))) + (r/set-state @self {:foo "you"}) + (r/flush) + (is (= "hi you" (.-innerText div))))) + (is (= 4 @ran)))))) (deftest test-ratom-change (when r/is-client - (let [ran (r/atom 0) - runs (rv/running) - val (r/atom 0) - secval (r/atom 0) - v1-ran (atom 0) - v1 (reaction (swap! v1-ran inc) @val) - comp (fn [] - (swap! ran inc) - [:div (str "val " @v1 " " @val " " @secval)])] - (with-mounted-component [comp] - (fn [C div] - (r/flush) - (is (not= runs (rv/running))) - (is (= "val 0 0 0" (.-innerText div))) - (is (= 1 @ran)) + (doseq [opts test-options] + (let [ran (r/atom 0) + runs (rv/running) + val (r/atom 0) + secval (r/atom 0) + v1-ran (atom 0) + v1 (reaction (swap! v1-ran inc) @val) + comp (fn [] + (swap! ran inc) + [:div (str "val " @v1 " " @val " " @secval)])] + (with-mounted-component [comp] + opts + (fn [C div] + (r/flush) + (is (not= runs (rv/running))) + (is (= "val 0 0 0" (.-innerText div))) + (is (= 1 @ran)) - (reset! secval 1) - (reset! secval 0) - (reset! val 1) - (reset! val 2) - (reset! val 1) - (is (= 1 @ran)) - (is (= 1 @v1-ran)) - (r/flush) - (is (= "val 1 1 0" (.-innerText div))) - (is (= 2 @ran) "ran once more") - (is (= 2 @v1-ran)) + (reset! secval 1) + (reset! secval 0) + (reset! val 1) + (reset! val 2) + (reset! val 1) + (is (= 1 @ran)) + (is (= 1 @v1-ran)) + (r/flush) + (is (= "val 1 1 0" (.-innerText div))) + (is (= 2 @ran) "ran once more") + (is (= 2 @v1-ran)) - ;; should not be rendered - (reset! val 1) - (is (= 2 @v1-ran)) - (r/flush) - (is (= 2 @v1-ran)) - (is (= "val 1 1 0" (.-innerText div))) - (is (= 2 @ran) "did not run"))) - (is (= runs (rv/running))) - (is (= 2 @ran))))) + ;; should not be rendered + (reset! val 1) + (is (= 2 @v1-ran)) + (r/flush) + (is (= 2 @v1-ran)) + (is (= "val 1 1 0" (.-innerText div))) + (is (= 2 @ran) "did not run"))) + (is (= runs (rv/running))) + (is (= 2 @ran)))))) (deftest batched-update-test [] (when r/is-client - (let [ran (r/atom 0) - v1 (r/atom 0) - v2 (r/atom 0) - c2 (fn [{val :val}] - (swap! ran inc) - (is (= val @v1)) - [:div @v2]) - c1 (fn [] - (swap! ran inc) - [:div @v1 - [c2 {:val @v1}]])] - (with-mounted-component [c1] - (fn [c div] - (r/flush) - (is (= 2 @ran)) - (swap! v2 inc) - (is (= 2 @ran)) - (r/flush) - (is (= 3 @ran)) - (swap! v1 inc) - (r/flush) - (is (= 5 @ran)) - ;; TODO: Failing on optimized build - ; (swap! v2 inc) - ; (swap! v1 inc) - ; (r/flush) - ; (is (= 7 @ran)) - ; (swap! v1 inc) - ; (swap! v1 inc) - ; (swap! v2 inc) - ; (r/flush) - ; (is (= 9 @ran)) - ))))) + (doseq [opts test-options] + (let [ran (r/atom 0) + v1 (r/atom 0) + v2 (r/atom 0) + c2 (fn [{val :val}] + (swap! ran inc) + (is (= val @v1)) + [:div @v2]) + c1 (fn [] + (swap! ran inc) + [:div @v1 + [c2 {:val @v1}]])] + (with-mounted-component [c1] + opts + (fn [c div] + (r/flush) + (is (= 2 @ran)) + (swap! v2 inc) + (is (= 2 @ran)) + (r/flush) + (is (= 3 @ran)) + (swap! v1 inc) + (r/flush) + (is (= 5 @ran)) + ;; TODO: Failing on optimized build + ; (swap! v2 inc) + ; (swap! v1 inc) + ; (r/flush) + ; (is (= 7 @ran)) + ; (swap! v1 inc) + ; (swap! v1 inc) + ; (swap! v2 inc) + ; (r/flush) + ; (is (= 9 @ran)) + )))))) (deftest init-state-test (when r/is-client - (let [ran (r/atom 0) - really-simple (fn [] - (let [this (r/current-component)] - (swap! ran inc) - (r/set-state this {:foo "foobar"}) - (fn [] - [:div (str "this is " - (:foo (r/state this)))])))] - (with-mounted-component [really-simple nil nil] - (fn [c div] - (swap! ran inc) - (is (= "this is foobar" (.-innerText div))))) - (is (= 2 @ran))))) + (doseq [opts test-options] + (let [ran (r/atom 0) + really-simple (fn [] + (let [this (r/current-component)] + (swap! ran inc) + (r/set-state this {:foo "foobar"}) + (fn [] + [:div (str "this is " + (:foo (r/state this)))])))] + (with-mounted-component [really-simple nil nil] + opts + (fn [c div] + (swap! ran inc) + (is (= "this is foobar" (.-innerText div))))) + (is (= 2 @ran)))))) (deftest should-update-test (when r/is-client - (let [parent-ran (r/atom 0) - child-ran (r/atom 0) - child-props (r/atom nil) - f (fn []) - f1 (fn []) - child (fn [p] - (swap! child-ran inc) - [:div (:val p)]) - parent (fn [] - (swap! parent-ran inc) - [:div "child-foo" [child @child-props]])] - (with-mounted-component [parent nil nil] - (fn [c div] - (r/flush) - (is (= 1 @child-ran)) - (is (= "child-foo" (.-innerText div))) + (doseq [opts test-options] + (let [parent-ran (r/atom 0) + child-ran (r/atom 0) + child-props (r/atom nil) + f (fn []) + f1 (fn []) + child (fn [p] + (swap! child-ran inc) + [:div (:val p)]) + parent (fn [] + (swap! parent-ran inc) + [:div "child-foo" [child @child-props]])] + (with-mounted-component [parent nil nil] + opts + (fn [c div] + (r/flush) + (is (= 1 @child-ran)) + (is (= "child-foo" (.-innerText div))) - (reset! child-props {:style {:display :none}}) - (r/flush) - (is (= 2 @child-ran)) + (reset! child-props {:style {:display :none}}) + (r/flush) + (is (= 2 @child-ran)) - (reset! child-props {:style {:display :none}}) - (r/flush) - (is (= 2 @child-ran) "keyw is equal") + (reset! child-props {:style {:display :none}}) + (r/flush) + (is (= 2 @child-ran) "keyw is equal") - (reset! child-props {:class :foo}) (r/flush) - (r/flush) - (is (= 3 @child-ran)) + (reset! child-props {:class :foo}) (r/flush) + (r/flush) + (is (= 3 @child-ran)) - (reset! child-props {:class :foo}) (r/flush) - (r/flush) - (is (= 3 @child-ran)) + (reset! child-props {:class :foo}) (r/flush) + (r/flush) + (is (= 3 @child-ran)) - (reset! child-props {:class 'foo}) - (r/flush) - (is (= 4 @child-ran) "symbols are different from keyw") + (reset! child-props {:class 'foo}) + (r/flush) + (is (= 4 @child-ran) "symbols are different from keyw") - (reset! child-props {:class 'foo}) - (r/flush) - (is (= 4 @child-ran) "symbols are equal") + (reset! child-props {:class 'foo}) + (r/flush) + (is (= 4 @child-ran) "symbols are equal") - (reset! child-props {:style {:color 'red}}) - (r/flush) - (is (= 5 @child-ran)) + (reset! child-props {:style {:color 'red}}) + (r/flush) + (is (= 5 @child-ran)) - (reset! child-props {:on-change (r/partial f)}) - (r/flush) - (is (= 6 @child-ran)) + (reset! child-props {:on-change (r/partial f)}) + (r/flush) + (is (= 6 @child-ran)) - (reset! child-props {:on-change (r/partial f)}) - (r/flush) - (is (= 6 @child-ran)) + (reset! child-props {:on-change (r/partial f)}) + (r/flush) + (is (= 6 @child-ran)) - (reset! child-props {:on-change (r/partial f1)}) - (r/flush) - (is (= 7 @child-ran)) + (reset! child-props {:on-change (r/partial f1)}) + (r/flush) + (is (= 7 @child-ran)) - (rdom/force-update-all) - (is (= 8 @child-ran))))))) + (rdom/force-update-all) + (is (= 8 @child-ran)))))))) (deftest dirty-test (when r/is-client - (let [ran (r/atom 0) - state (r/atom 0) - really-simple (fn [] - (swap! ran inc) - (if (= 1 @state) - (reset! state 3)) - [:div (str "state=" @state)])] - (with-mounted-component [really-simple nil nil] - (fn [c div] - (is (= 1 @ran)) - (is (= "state=0" (.-innerText div))) - (reset! state 1) - (r/flush) - (is (= 2 @ran)) - (is (= "state=3" (.-innerText div))))) - (is (= 2 @ran))))) + (doseq [opts test-options] + (let [ran (r/atom 0) + state (r/atom 0) + really-simple (fn [] + (swap! ran inc) + (if (= 1 @state) + (reset! state 3)) + [:div (str "state=" @state)])] + (with-mounted-component [really-simple nil nil] + opts + (fn [c div] + (is (= 1 @ran)) + (is (= "state=0" (.-innerText div))) + (reset! state 1) + (r/flush) + (is (= 2 @ran)) + (is (= "state=3" (.-innerText div))))) + (is (= 2 @ran)))))) -(defn as-string [comp] - (server/render-to-static-markup comp)) +(defn as-string [comp opts] + (server/render-to-static-markup comp opts)) (deftest to-string-test [] - (let [comp (fn [props] - [:div (str "i am " (:foo props))])] - (is (= "
i am foobar
" (as-string [comp {:foo "foobar"}]))))) + (doseq [opts test-options] + (let [comp (fn [props] + [:div (str "i am " (:foo props))])] + (is (= "
i am foobar
" (as-string [comp {:foo "foobar"}] opts)))))) (deftest data-aria-test [] - (is (= "
" - (as-string [:div {:data-foo "x"}]))) - (is (= "
" - (as-string [:div {:aria-labelledby "x"}]))) - ;; Skip test: produces warning in new React - ;; (is (not (re-find #"enctype" - ;; (as-string [:div {"enc-type" "x"}]))) - ;; "Strings are passed through to React.") - ;; FIXME: For some reason UMD module returns everything in - ;; lowercase, and CommonJS with upper T - (is (re-find #"enc[tT]ype" - (as-string [:div {"encType" "x"}])) - "Strings are passed through to React, and have to be camelcase.") - (is (re-find #"enc[tT]ype" - (as-string [:div {:enc-type "x"}])) - "Strings are passed through to React, and have to be camelcase.")) + (doseq [opts test-options] + (is (= "
" + (as-string [:div {:data-foo "x"}] opts))) + (is (= "
" + (as-string [:div {:aria-labelledby "x"}] opts))) + ;; Skip test: produces warning in new React + ;; (is (not (re-find #"enctype" + ;; (as-string [:div {"enc-type" "x"}]))) + ;; "Strings are passed through to React.") + ;; FIXME: For some reason UMD module returns everything in + ;; lowercase, and CommonJS with upper T + (is (re-find #"enc[tT]ype" + (as-string [:div {"encType" "x"}] opts)) + "Strings are passed through to React, and have to be camelcase.") + (is (re-find #"enc[tT]ype" + (as-string [:div {:enc-type "x"}] opts)) + "Strings are passed through to React, and have to be camelcase."))) (deftest dynamic-id-class [] - (is (re-find #"id=.foo" - (as-string [:div#foo {:class "bar"}]))) - (is (= "
" - (as-string [:div.foo {:class "bar"}]))) - (is (= "
" - (as-string [:div.foo.bar]))) - (is (= "
" - (as-string [:div.foo {:className "bar"}]))) - (is (= "
" - (as-string [:div {:className "foo bar"}]))) - (is (re-find #"id=.foo" - (as-string [:div#foo.foo.bar]))) - (is (re-find #"class=.xxx bar" - (as-string [:div#foo.xxx.bar]))) - (is (re-find #"id=.foo" - (as-string [:div.bar {:id "foo"}]))) - (is (re-find #"id=.foo" - (as-string [:div.bar.xxx {:id "foo"}]))) - (is (= "
" - (as-string [:div#bar {:id "foo"}])) - "Dynamic id overwrites static")) + (doseq [opts test-options] + (is (re-find #"id=.foo" + (as-string [:div#foo {:class "bar"}] opts))) + (is (= "
" + (as-string [:div.foo {:class "bar"}] opts))) + (is (= "
" + (as-string [:div.foo.bar] opts))) + (is (= "
" + (as-string [:div.foo {:className "bar"}] opts))) + (is (= "
" + (as-string [:div {:className "foo bar"}] opts))) + (is (re-find #"id=.foo" + (as-string [:div#foo.foo.bar] opts))) + (is (re-find #"class=.xxx bar" + (as-string [:div#foo.xxx.bar] opts))) + (is (re-find #"id=.foo" + (as-string [:div.bar {:id "foo"}] opts))) + (is (re-find #"id=.foo" + (as-string [:div.bar.xxx {:id "foo"}] opts))) + (is (= "
" + (as-string [:div#bar {:id "foo"}] opts)) + "Dynamic id overwrites static"))) (defmulti my-div :type) (defmethod my-div :fooish [child] [:div.foo (:content child)]) (defmethod my-div :barish [child] [:div.bar (:content child)]) (deftest ifn-component [] - (let [comp {:foo [:div "foodiv"] - :bar [:div "bardiv"]}] - (is (= "
foodiv
" - (as-string [:div [comp :foo]]))) - (is (= "
bardiv
" - (as-string [:div [comp :bar]]))) - (is (= "
inner
" - (as-string [my-div {:type :fooish :content "inner"}]))))) + (doseq [opts test-options] + (let [comp {:foo [:div "foodiv"] + :bar [:div "bardiv"]}] + (is (= "
foodiv
" + (as-string [:div [comp :foo]] opts))) + (is (= "
bardiv
" + (as-string [:div [comp :bar]] opts))) + (is (= "
inner
" + (as-string [my-div {:type :fooish :content "inner"}] opts)))))) (deftest symbol-string-tag [] - (is (= "
foobar
" (as-string ['div "foobar"]))) - (is (= "
foobar
" (as-string ["div" "foobar"]))) - (is (= "
x
" (as-string ['div#foo "x"]))) - (is (= "
x
" (as-string ["div#foo" "x"]))) - (is (= "
" (as-string ['div.foo {:class "bar"}]))) - (is (= "
" (as-string ["div.foo.bar"]))) - (is (re-find #"id=.foo" - (as-string ['div#foo.foo.bar])))) + (doseq [opts test-options] + (is (= "
foobar
" (as-string ['div "foobar"] opts))) + (is (= "
foobar
" (as-string ["div" "foobar"] opts))) + (is (= "
x
" (as-string ['div#foo "x"] opts))) + (is (= "
x
" (as-string ["div#foo" "x"] opts))) + (is (= "
" (as-string ['div.foo {:class "bar"}] opts))) + (is (= "
" (as-string ["div.foo.bar"] opts))) + (is (re-find #"id=.foo" + (as-string ['div#foo.foo.bar] opts))))) (deftest partial-test [] (let [p1 (r/partial vector 1 2)] @@ -354,64 +381,70 @@ (is (= (hash p1) (hash (r/partial vector 1 2)))))) (deftest test-null-component - (let [null-comp (fn [do-show] - (when do-show - [:div "div in test-null-component"]))] - (is (= "" - (as-string [null-comp false]))) - (is (= "
div in test-null-component
" - (as-string [null-comp true]))))) + (doseq [opts test-options] + (let [null-comp (fn [do-show] + (when do-show + [:div "div in test-null-component"]))] + (is (= "" + (as-string [null-comp false] opts))) + (is (= "
div in test-null-component
" + (as-string [null-comp true] opts)))))) (deftest test-string - (is (= "
foo
" - (server/render-to-string [:div "foo"]))) + (doseq [opts test-options] + (is (= "
foo
" + (server/render-to-string [:div "foo"] opts))) - (is (= "

foo

" - (server/render-to-string [:div [:p "foo"]])))) + (is (= "

foo

" + (server/render-to-string [:div [:p "foo"]] opts))))) (deftest test-static-markup - (is (= "
foo
" - (rstr [:div "foo"]))) - (is (= "

foo

" - (rstr [:div.bar [:p "foo"]]))) - (is (= "

foobar

" - (rstr [:div.bar {:dangerously-set-inner-HTML - {:__html "

foobar

"}} ])))) + (doseq [opts test-options] + (is (= "
foo
" + (rstr [:div "foo"] opts))) + (is (= "

foo

" + (rstr [:div.bar [:p "foo"]] opts))) + (is (= "

foobar

" + (rstr [:div.bar {:dangerously-set-inner-HTML + {:__html "

foobar

"}}] opts))))) (deftest test-return-class (when r/is-client - (let [ran (r/atom 0) - top-ran (r/atom 0) - comp (fn [] - (swap! top-ran inc) - (r/create-class - {:component-did-mount #(swap! ran inc) - :render - (fn [this] - (let [props (r/props this)] - (is (map? props)) - (is (= props ((r/argv this) 1))) - (is (= 1 (first (r/children this)))) - (is (= 1 (count (r/children this)))) - (swap! ran inc) - [:div (str "hi " (:foo props) ".")]))})) - prop (r/atom {:foo "you"}) - parent (fn [] [comp @prop 1])] - (with-mounted-component [parent] - (fn [C div] - (swap! ran inc) - (is (= "hi you." (.-innerText div))) - (is (= 1 @top-ran)) - (is (= 3 @ran)) + (doseq [opts test-options] + (let [ran (r/atom 0) + top-ran (r/atom 0) + comp (fn [] + (swap! top-ran inc) + (r/create-class + {:component-did-mount #(swap! ran inc) + :render + (fn [this] + (let [props (r/props this)] + (is (map? props)) + (is (= props ((r/argv this) 1))) + (is (= 1 (first (r/children this)))) + (is (= 1 (count (r/children this)))) + (swap! ran inc) + [:div (str "hi " (:foo props) ".")]))})) + prop (r/atom {:foo "you"}) + parent (fn [] [comp @prop 1])] + (with-mounted-component [parent] + opts + (fn [C div] + (swap! ran inc) + (is (= "hi you." (.-innerText div))) + (is (= 1 @top-ran)) + (is (= 3 @ran)) - (swap! prop assoc :foo "me") - (r/flush) - (is (= "hi me." (.-innerText div))) - (is (= 1 @top-ran)) - (is (= 4 @ran))))))) + (swap! prop assoc :foo "me") + (r/flush) + (is (= "hi me." (.-innerText div))) + (is (= 1 @top-ran)) + (is (= 4 @ran)))))))) (deftest test-return-class-fn (when r/is-client + (doseq [opts test-options] (let [ran (r/atom 0) top-ran (r/atom 0) comp (fn [] @@ -426,6 +459,7 @@ prop (r/atom {:foo "you"}) parent (fn [] [comp @prop 1])] (with-mounted-component [parent] + opts (fn [C div] (swap! ran inc) (is (= "hi you." (.-innerText div))) @@ -436,31 +470,33 @@ (r/flush) (is (= "hi me." (.-innerText div))) (is (= 1 @top-ran)) - (is (= 4 @ran))))))) + (is (= 4 @ran)))))))) (deftest test-create-element - (let [ae r/as-element - ce r/create-element] - (is (= (rstr (ce "div")) - (rstr (ae [:div])))) - (is (= (rstr (ce "div" nil)) - (rstr (ae [:div])))) - (is (= (rstr (ce "div" nil "foo")) - (rstr (ae [:div "foo"])))) - (is (= (rstr (ce "div" nil "foo" "bar")) - (rstr (ae [:div "foo" "bar"])))) - (is (= (rstr (ce "div" nil "foo" "bar" "foobar")) - (rstr (ae [:div "foo" "bar" "foobar"])))) + (doseq [opts test-options] + (let [ae r/as-element + ce r/create-element + rstr #(rstr % opts)] + (is (= (rstr (ce "div")) + (rstr (ae [:div])))) + (is (= (rstr (ce "div" nil)) + (rstr (ae [:div])))) + (is (= (rstr (ce "div" nil "foo")) + (rstr (ae [:div "foo"])))) + (is (= (rstr (ce "div" nil "foo" "bar")) + (rstr (ae [:div "foo" "bar"])))) + (is (= (rstr (ce "div" nil "foo" "bar" "foobar")) + (rstr (ae [:div "foo" "bar" "foobar"])))) - (is (= (rstr (ce "div" #js{:className "foo"} "bar")) - (rstr (ae [:div.foo "bar"])))) + (is (= (rstr (ce "div" #js{:className "foo"} "bar")) + (rstr (ae [:div.foo "bar"])))) - (is (= (rstr (ce "div" nil (ce "div" nil "foo"))) - (rstr (ae [:div [:div "foo"]])))) - (is (= (rstr (ce "div" nil (ae [:div "foo"]))) - (rstr (ae [:div [:div "foo"]])))) - (is (= (rstr (ae [:div (ce "div" nil "foo")])) - (rstr (ae [:div [:div "foo"]])))))) + (is (= (rstr (ce "div" nil (ce "div" nil "foo"))) + (rstr (ae [:div [:div "foo"]])))) + (is (= (rstr (ce "div" nil (ae [:div "foo"]))) + (rstr (ae [:div [:div "foo"]])))) + (is (= (rstr (ae [:div (ce "div" nil "foo")])) + (rstr (ae [:div [:div "foo"]]))))))) (def ndiv (let [cmp (fn [])] (gobj/extend @@ -476,54 +512,58 @@ cmp)) (deftest test-adapt-class - (let [d1 (r/adapt-react-class ndiv) - d2 (r/adapt-react-class "div")] - (is (= (rstr [:div]) - (rstr [d1]))) - (is (= (rstr [:div "a"]) - (rstr [d1 "a"]))) - (is (= (rstr [:div "a" "b"]) - (rstr [d1 "a" "b"]))) - (is (= (rstr [:div.foo "a"]) - (rstr [d1 {:class "foo"} "a"]))) - (is (= (rstr [:div "a" "b" [:div "c"]]) - (rstr [d1 "a" "b" [:div "c"]]))) + (doseq [opts test-options] + (let [d1 (r/adapt-react-class ndiv) + d2 (r/adapt-react-class "div") + rstr #(rstr % opts)] + (is (= (rstr [:div]) + (rstr [d1]))) + (is (= (rstr [:div "a"]) + (rstr [d1 "a"]))) + (is (= (rstr [:div "a" "b"]) + (rstr [d1 "a" "b"]))) + (is (= (rstr [:div.foo "a"]) + (rstr [d1 {:class "foo"} "a"]))) + (is (= (rstr [:div "a" "b" [:div "c"]]) + (rstr [d1 "a" "b" [:div "c"]]))) - (is (= (rstr [:div]) - (rstr [d2]))) - (is (= (rstr [:div "a"]) - (rstr [d2 "a"]))) - (is (= (rstr [:div "a" "b"]) - (rstr [d2 "a" "b"]))) - (is (= (rstr [:div.foo "a"]) - (rstr [d2 {:class "foo"} "a"]))) - (is (= (rstr [:div "a" "b" [:div "c"]]) - (rstr [d2 "a" "b" [:div "c"]]))))) + (is (= (rstr [:div]) + (rstr [d2]))) + (is (= (rstr [:div "a"]) + (rstr [d2 "a"]))) + (is (= (rstr [:div "a" "b"]) + (rstr [d2 "a" "b"]))) + (is (= (rstr [:div.foo "a"]) + (rstr [d2 {:class "foo"} "a"]))) + (is (= (rstr [:div "a" "b" [:div "c"]]) + (rstr [d2 "a" "b" [:div "c"]])))))) (deftest test-adapt-class-2 - (let [d1 ndiv - d2 "div"] - (is (= (rstr [:div]) - (rstr [:> d1]))) - (is (= (rstr [:div "a"]) - (rstr [:> d1 "a"]))) - (is (= (rstr [:div "a" "b"]) - (rstr [:> d1 "a" "b"]))) - (is (= (rstr [:div.foo "a"]) - (rstr [:> d1 {:class "foo"} "a"]))) - (is (= (rstr [:div "a" "b" [:div "c"]]) - (rstr [:> d1 "a" "b" [:div "c"]]))) + (doseq [opts test-options] + (let [d1 ndiv + d2 "div" + rstr #(rstr % opts)] + (is (= (rstr [:div]) + (rstr [:> d1]))) + (is (= (rstr [:div "a"]) + (rstr [:> d1 "a"]))) + (is (= (rstr [:div "a" "b"]) + (rstr [:> d1 "a" "b"]))) + (is (= (rstr [:div.foo "a"]) + (rstr [:> d1 {:class "foo"} "a"]))) + (is (= (rstr [:div "a" "b" [:div "c"]]) + (rstr [:> d1 "a" "b" [:div "c"]]))) - (is (= (rstr [:div]) - (rstr [:> d2]))) - (is (= (rstr [:div "a"]) - (rstr [:> d2 "a"]))) - (is (= (rstr [:div "a" "b"]) - (rstr [:> d2 "a" "b"]))) - (is (= (rstr [:div.foo "a"]) - (rstr [:> d2 {:class "foo"} "a"]))) - (is (= (rstr [:div "a" "b" [:div "c"]]) - (rstr [:> d2 "a" "b" [:div "c"]]))))) + (is (= (rstr [:div]) + (rstr [:> d2]))) + (is (= (rstr [:div "a"]) + (rstr [:> d2 "a"]))) + (is (= (rstr [:div "a" "b"]) + (rstr [:> d2 "a" "b"]))) + (is (= (rstr [:div.foo "a"]) + (rstr [:> d2 {:class "foo"} "a"]))) + (is (= (rstr [:div "a" "b" [:div "c"]]) + (rstr [:> d2 "a" "b" [:div "c"]])))))) (deftest adapt-react-class-shortcut-key-warning (let [w (debug/track-warnings @@ -535,70 +575,77 @@ (is (empty? (:warn w))))) (deftest test-reactize-component - (let [ae r/as-element - ce r/create-element - a (atom nil) - c1r (fn reactize [p & args] - (reset! a args) - [:p "p:" (:a p) (:children p)]) - c1 (r/reactify-component c1r)] - (is (= (rstr (ce c1 #js{:a "a"})) - (rstr [:p "p:a"]))) - (is (= nil @a)) - (is (= (rstr (ce c1 #js{:a nil})) - (rstr [:p "p:"]))) - (is (= (rstr (ce c1 nil)) - (rstr [:p "p:"]))) + (doseq [opts test-options] + (let [ae r/as-element + ce r/create-element + rstr #(rstr % opts) + a (atom nil) + c1r (fn reactize [p & args] + (reset! a args) + [:p "p:" (:a p) (:children p)]) + c1 (r/reactify-component c1r)] + (is (= (rstr (ce c1 #js{:a "a"})) + (rstr [:p "p:a"]))) + (is (= nil @a)) + (is (= (rstr (ce c1 #js{:a nil})) + (rstr [:p "p:"]))) + (is (= (rstr (ce c1 nil)) + (rstr [:p "p:"]))) - (is (= (rstr (ce c1 #js{:a "a"} - (ae [:b "b"]))) - (rstr [:p "p:a" [:b "b"]]))) - (is (= nil @a)) - (is (= (rstr (ce c1 #js{:a "a"} - (ae [:b "b"]) - (ae [:i "i"]))) - (rstr [:p "p:a" [:b "b"] [:i "i"]]))) - (is (= nil @a)))) + (is (= (rstr (ce c1 #js{:a "a"} + (ae [:b "b"]))) + (rstr [:p "p:a" [:b "b"]]))) + (is (= nil @a)) + (is (= (rstr (ce c1 #js{:a "a"} + (ae [:b "b"]) + (ae [:i "i"]))) + (rstr [:p "p:a" [:b "b"] [:i "i"]]))) + (is (= nil @a))))) (deftest test-keys - (let [a nil ;; (r/atom "a") - c (fn key-tester [] - [:div - (for [i (range 3)] - ^{:key i} [:p i (some-> a deref)]) - (for [i (range 3)] - [:p {:key i} i (some-> a deref)])]) - w (debug/track-warnings - #(with-mounted-component [c] - (fn [c div])))] - (is (empty? (:warn w)))) + (doseq [opts test-options] + (let [a nil ;; (r/atom "a") + c (fn key-tester [] + [:div + (for [i (range 3)] + ^{:key i} [:p i (some-> a deref)]) + (for [i (range 3)] + [:p {:key i} i (some-> a deref)])]) + w (debug/track-warnings + #(with-mounted-component [c] + opts + (fn [c div])))] + (is (empty? (:warn w)))) - (when r/is-client - (testing "Check warning text can be produced even if hiccup contains function literals" - (let [c (fn key-tester [] - [:div - (for [i (range 3)] - ^{:key nil} - [:button {:on-click #(js/console.log %)}])]) - w (debug/track-warnings - (wrap-capture-console-error - #(with-mounted-component [c] - (fn [c div]))))] - (if (dev?) - (is (re-find #"Warning: Every element in a seq should have a unique :key: \(\[:button \{:on-click #object\[Function\]\}\] \[:button \{:on-click #object\[Function\]\}\] \[:button \{:on-click #object\[Function\]\}\]\)\n \(in reagenttest.testreagent.key_tester\)" - (first (:warn w))))))))) + (when r/is-client + (testing "Check warning text can be produced even if hiccup contains function literals" + (let [c (fn key-tester [] + [:div + (for [i (range 3)] + ^{:key nil} + [:button {:on-click #(js/console.log %)}])]) + w (debug/track-warnings + (wrap-capture-console-error + #(with-mounted-component [c] + opts + (fn [c div]))))] + (if (dev?) + (is (re-find #"Warning: Every element in a seq should have a unique :key: \(\[:button \{:on-click #object\[Function\]\}\] \[:button \{:on-click #object\[Function\]\}\] \[:button \{:on-click #object\[Function\]\}\]\)\n \(in reagenttest.testreagent.key_tester\)" + (first (:warn w)))))))))) (deftest test-extended-syntax - (is (= "

foo

" - (rstr [:p>b "foo"]))) - (is (= (rstr [:p.foo [:b "x"]]) - (rstr [:p.foo>b "x"]))) - (is (= (rstr [:div.foo [:p.bar.foo [:b.foobar "xy"]]]) - (rstr [:div.foo>p.bar.foo>b.foobar "xy"]))) - (is (= (rstr [:div.foo [:p.bar.foo [:b.foobar "xy"]]]) - (rstr [:div.foo>p.bar.foo>b.foobar {} "xy"]))) - (is (= (rstr [:div [:p.bar.foo [:a.foobar {:href "href"} "xy"]]]) - (rstr [:div>p.bar.foo>a.foobar {:href "href"} "xy"])))) + (doseq [opts test-options + :let [rstr #(rstr % opts)]] + (is (= "

foo

" + (rstr [:p>b "foo"]))) + (is (= (rstr [:p.foo [:b "x"]]) + (rstr [:p.foo>b "x"]))) + (is (= (rstr [:div.foo [:p.bar.foo [:b.foobar "xy"]]]) + (rstr [:div.foo>p.bar.foo>b.foobar "xy"]))) + (is (= (rstr [:div.foo [:p.bar.foo [:b.foobar "xy"]]]) + (rstr [:div.foo>p.bar.foo>b.foobar {} "xy"]))) + (is (= (rstr [:div [:p.bar.foo [:a.foobar {:href "href"} "xy"]]]) + (rstr [:div>p.bar.foo>a.foobar {:href "href"} "xy"]))))) (deftest extended-syntax-metadata (when r/is-client @@ -612,35 +659,39 @@ ))))) (deftest test-class-from-collection - (is (= (rstr [:p {:class "a b c d"}]) - (rstr [:p {:class ["a" "b" "c" "d"]}]))) - (is (= (rstr [:p {:class "a b c"}]) - (rstr [:p {:class ["a" nil "b" false "c" nil]}]))) - (is (= (rstr [:p {:class "a b c"}]) - (rstr [:p {:class '("a" "b" "c")}]))) - (is (= (rstr [:p {:class "a b c"}]) - (rstr [:p {:class #{"a" "b" "c"}}])))) + (doseq [opts test-options + :let [rstr #(rstr % opts)]] + (is (= (rstr [:p {:class "a b c d"}]) + (rstr [:p {:class ["a" "b" "c" "d"]}]))) + (is (= (rstr [:p {:class "a b c"}]) + (rstr [:p {:class ["a" nil "b" false "c" nil]}]))) + (is (= (rstr [:p {:class "a b c"}]) + (rstr [:p {:class '("a" "b" "c")}]))) + (is (= (rstr [:p {:class "a b c"}]) + (rstr [:p {:class #{"a" "b" "c"}}]))))) (deftest class-different-types - (testing "named values are supported" - (is (= (rstr [:p {:class "a"}]) - (rstr [:p {:class :a}]))) - (is (= (rstr [:p {:class "a b"}]) - (rstr [:p.a {:class :b}]))) - (is (= (rstr [:p {:class "a b"}]) - (rstr [:p.a {:class 'b}]))) - (is (= (rstr [:p {:class "a b"}]) - (rstr [:p {:class [:a :b]}]))) - (is (= (rstr [:p {:class "a b"}]) - (rstr [:p {:class ['a :b]}])))) + (doseq [opts test-options + :let [rstr #(rstr % opts)]] + (testing "named values are supported" + (is (= (rstr [:p {:class "a"}]) + (rstr [:p {:class :a}]))) + (is (= (rstr [:p {:class "a b"}]) + (rstr [:p.a {:class :b}]))) + (is (= (rstr [:p {:class "a b"}]) + (rstr [:p.a {:class 'b}]))) + (is (= (rstr [:p {:class "a b"}]) + (rstr [:p {:class [:a :b]}]))) + (is (= (rstr [:p {:class "a b"}]) + (rstr [:p {:class ['a :b]}])))) - (testing "non-named values like numbers" - (is (= (rstr [:p {:class "1 b"}]) - (rstr [:p {:class [1 :b]}])))) + (testing "non-named values like numbers" + (is (= (rstr [:p {:class "1 b"}]) + (rstr [:p {:class [1 :b]}])))) - (testing "falsey values are filtered from collections" - (is (= (rstr [:p {:class "a b"}]) - (rstr [:p {:class [:a :b false nil]}])))) ) + (testing "falsey values are filtered from collections" + (is (= (rstr [:p {:class "a b"}]) + (rstr [:p {:class [:a :b false nil]}])))))) (deftest test-force-update (let [v (atom {:v1 0 @@ -693,274 +744,286 @@ (is (re-find #"atestcomponent" @a) "component-path should work"))))) (deftest test-sorted-map-key - (let [c1 (fn [map] - [:div (map 1)]) - c2 (fn [] - [c1 (sorted-map 1 "foo" 2 "bar")])] - (is (= "
foo
" (rstr [c2]))))) + (doseq [opts test-options + :let [rstr #(rstr % opts)]] + (let [c1 (fn [map] + [:div (map 1)]) + c2 (fn [] + [c1 (sorted-map 1 "foo" 2 "bar")])] + (is (= "
foo
" (rstr [c2])))))) (deftest basic-with-let - (when r/is-client - (let [n1 (atom 0) - n2 (atom 0) - n3 (atom 0) - val (r/atom 0) - c (fn [] - (r/with-let [v (swap! n1 inc)] - (swap! n2 inc) - [:div @val] - (finally - (swap! n3 inc))))] - (with-mounted-component [c] - (fn [_ div] - (is (= [1 1 0] [@n1 @n2 @n3])) - (swap! val inc) - (is (= [1 1 0] [@n1 @n2 @n3])) - (r/flush) - (is (= [1 2 0] [@n1 @n2 @n3])))) - (is (= [1 2 1] [@n1 @n2 @n3]))))) + (doseq [opts test-options] + (when r/is-client + (let [n1 (atom 0) + n2 (atom 0) + n3 (atom 0) + val (r/atom 0) + c (fn [] + (r/with-let [v (swap! n1 inc)] + (swap! n2 inc) + [:div @val] + (finally + (swap! n3 inc))))] + (with-mounted-component [c] + opts + (fn [_ div] + (is (= [1 1 0] [@n1 @n2 @n3])) + (swap! val inc) + (is (= [1 1 0] [@n1 @n2 @n3])) + (r/flush) + (is (= [1 2 0] [@n1 @n2 @n3])))) + (is (= [1 2 1] [@n1 @n2 @n3])))))) (deftest with-let-destroy-only (when r/is-client - (let [n1 (atom 0) - n2 (atom 0) - c (fn [] - (r/with-let [] - (swap! n1 inc) - [:div] - (finally - (swap! n2 inc))))] - (with-mounted-component [c] - (fn [_ div] - (is (= [1 0] [@n1 @n2])))) - (is (= [1 1] [@n1 @n2]))))) + (doseq [opts test-options] + (let [n1 (atom 0) + n2 (atom 0) + c (fn [] + (r/with-let [] + (swap! n1 inc) + [:div] + (finally + (swap! n2 inc))))] + (with-mounted-component [c] + opts + (fn [_ div] + (is (= [1 0] [@n1 @n2])))) + (is (= [1 1] [@n1 @n2])))))) (deftest with-let-arg (when r/is-client - (let [a (atom 0) - s (r/atom "foo") - f (fn [x] - (r/with-let [] - (reset! a x) - [:div x])) - c (fn [] - (r/with-let [] - [f @s]))] - (with-mounted-component [c] - (fn [_ div] - (is (= "foo" @a)) - (reset! s "bar") - (r/flush) - (is (= "bar" @a))))))) + (doseq [opts test-options] + (let [a (atom 0) + s (r/atom "foo") + f (fn [x] + (r/with-let [] + (reset! a x) + [:div x])) + c (fn [] + (r/with-let [] + [f @s]))] + (with-mounted-component [c] + opts + (fn [_ div] + (is (= "foo" @a)) + (reset! s "bar") + (r/flush) + (is (= "bar" @a)))))))) (deftest with-let-non-reactive - (let [n1 (atom 0) - n2 (atom 0) - n3 (atom 0) - c (fn [] - (r/with-let [a (swap! n1 inc)] - (swap! n2 inc) - [:div a] - (finally - (swap! n3 inc))))] - (is (= (rstr [c]) (rstr [:div 1]))) - (is (= [1 1 1] [@n1 @n2 @n3])))) + (doseq [opts test-options] + (let [n1 (atom 0) + n2 (atom 0) + n3 (atom 0) + rstr #(rstr % opts) + c (fn [] + (r/with-let [a (swap! n1 inc)] + (swap! n2 inc) + [:div a] + (finally + (swap! n3 inc))))] + (is (= (rstr [c]) (rstr [:div 1]))) + (is (= [1 1 1] [@n1 @n2 @n3]))))) (deftest lifecycle - (let [n1 (atom 0) - t (atom 0) - res (atom {}) - add-args (fn [key args] - (swap! res assoc key - {:at (swap! n1 inc) - :args (vec args)})) - render (fn [& args] - (this-as c (is (= c (r/current-component)))) - (add-args :render args) - [:div "" (first args)]) - render2 (fn [& args] - (add-args :render args) - [:div "" (first args)]) - ls {:get-initial-state - (fn [& args] - (reset! t (first args)) - (add-args :initial-state args) - {:foo "bar"}) - :UNSAFE_component-will-mount - (fn [& args] - (this-as c (is (= c (first args)))) - (add-args :will-mount args)) - :component-did-mount - (fn [& args] - (this-as c (is (= c (first args)))) - (add-args :did-mount args)) - :should-component-update - (fn [& args] - (this-as c (is (= c (first args)))) - (add-args :should-update args) true) - :UNSAFE_component-will-receive-props - (fn [& args] - (this-as c (is (= c (first args)))) - (add-args :will-receive args)) - :UNSAFE_component-will-update - (fn [& args] - (this-as c (is (= c (first args)))) - (add-args :will-update args)) - :component-did-update - (fn [& args] - (this-as c (is (= c (first args)))) - (add-args :did-update args)) - :component-will-unmount - (fn [& args] - (this-as c (is (= c (first args)))) - (add-args :will-unmount args))} - c1 (r/create-class - (assoc ls :reagent-render render)) - defarg ["a" "b"] - arg (r/atom defarg) - comp (atom c1) - c2 (fn [] - (apply vector @comp @arg)) - cnative (fn [] - (into [:> @comp] @arg)) - check (fn [] - (is (= {:at 1 :args [@t]} - (:initial-state @res))) - (is (= {:at 2 :args [@t]} - (:will-mount @res))) - (is (= {:at 3 :args ["a" "b"]} - (:render @res))) - (is (= {:at 4 :args [@t]} - (:did-mount @res))) + (doseq [opts test-options] + (let [n1 (atom 0) + t (atom 0) + res (atom {}) + add-args (fn [key args] + (swap! res assoc key + {:at (swap! n1 inc) + :args (vec args)})) + render (fn [& args] + (this-as c (is (= c (r/current-component)))) + (add-args :render args) + [:div "" (first args)]) + render2 (fn [& args] + (add-args :render args) + [:div "" (first args)]) + ls {:get-initial-state + (fn [& args] + (reset! t (first args)) + (add-args :initial-state args) + {:foo "bar"}) + :UNSAFE_component-will-mount + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :will-mount args)) + :component-did-mount + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :did-mount args)) + :should-component-update + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :should-update args) true) + :UNSAFE_component-will-receive-props + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :will-receive args)) + :UNSAFE_component-will-update + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :will-update args)) + :component-did-update + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :did-update args)) + :component-will-unmount + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :will-unmount args))} + c1 (r/create-class + (assoc ls :reagent-render render)) + defarg ["a" "b"] + arg (r/atom defarg) + comp (atom c1) + c2 (fn [] + (apply vector @comp @arg)) + cnative (fn [] + (into [:> @comp] @arg)) + check (fn [] + (is (= {:at 1 :args [@t]} + (:initial-state @res))) + (is (= {:at 2 :args [@t]} + (:will-mount @res))) + (is (= {:at 3 :args ["a" "b"]} + (:render @res))) + (is (= {:at 4 :args [@t]} + (:did-mount @res))) - (reset! arg ["a" "c"]) - (r/flush) - (is (= {:at 5 :args [@t [@comp "a" "c"]]} - (:will-receive @res))) - (is (= {:at 6 :args [@t [@comp "a" "b"] [@comp "a" "c"]]} - (:should-update @res))) - (is (= {:at 7 :args [@t [@comp "a" "c"] {:foo "bar"}]} - (:will-update @res))) - (is (= {:at 8 :args ["a" "c"]} - (:render @res))) - (is (= {:at 9 :args [@t [@comp "a" "b"] {:foo "bar"} nil]} - (:did-update @res))))] - (when r/is-client - (with-mounted-component [c2] check) - (is (= {:at 10 :args [@t]} - (:will-unmount @res))) + (reset! arg ["a" "c"]) + (r/flush) + (is (= {:at 5 :args [@t [@comp "a" "c"]]} + (:will-receive @res))) + (is (= {:at 6 :args [@t [@comp "a" "b"] [@comp "a" "c"]]} + (:should-update @res))) + (is (= {:at 7 :args [@t [@comp "a" "c"] {:foo "bar"}]} + (:will-update @res))) + (is (= {:at 8 :args ["a" "c"]} + (:render @res))) + (is (= {:at 9 :args [@t [@comp "a" "b"] {:foo "bar"} nil]} + (:did-update @res))))] + (when r/is-client + (with-mounted-component [c2] opts check) + (is (= {:at 10 :args [@t]} + (:will-unmount @res))) - (reset! comp (with-meta render2 ls)) - (reset! arg defarg) - (reset! n1 0) - (with-mounted-component [c2] check) - (is (= {:at 10 :args [@t]} - (:will-unmount @res)))))) + (reset! comp (with-meta render2 ls)) + (reset! arg defarg) + (reset! n1 0) + (with-mounted-component [c2] check) + (is (= {:at 10 :args [@t]} + (:will-unmount @res))))))) (deftest lifecycle-native - (let [n1 (atom 0) - t (atom 0) - res (atom {}) - oldprops (atom nil) - newprops (atom nil) - add-args (fn [key args] - (swap! res assoc key - {:at (swap! n1 inc) - :args (vec args)})) - render (fn [& args] - (this-as - c - (when @newprops - (is (= (first args) @newprops)) - (is (= (r/props c) @newprops))) - (is (= c (r/current-component))) - (is (= (first args) (r/props c))) - (add-args :render - {:children (r/children c)}) - [:div "" (first args)])) - ls {:get-initial-state - (fn [& args] - (reset! t (first args)) - (reset! oldprops (-> args first r/props)) - (add-args :initial-state args) - {:foo "bar"}) - :UNSAFE_component-will-mount - (fn [& args] - (this-as c (is (= c (first args)))) - (add-args :will-mount args)) - :component-did-mount - (fn [& args] - (this-as c (is (= c (first args)))) - (add-args :did-mount args)) - :should-component-update - (fn [& args] - (this-as c (is (= c (first args)))) - (add-args :should-update args) true) - :UNSAFE_component-will-receive-props - (fn [& args] - (reset! newprops (-> args second second)) - (this-as c - (is (= c (first args))) - (add-args :will-receive (into [(dissoc (r/props c) :children)] - (:children (r/props c)))))) - :UNSAFE_component-will-update - (fn [& args] - (this-as c (is (= c (first args)))) - (add-args :will-update args)) - :component-did-update - (fn [& args] - (this-as c (is (= c (first args)))) - (add-args :did-update args)) - :component-will-unmount - (fn [& args] - (this-as c (is (= c (first args)))) - (add-args :will-unmount args))} - c1 (r/create-class - (assoc ls :reagent-render render)) - defarg [{:foo "bar"} "a" "b"] - arg (r/atom defarg) - comp (atom c1) - cnative (fn [] - (into [:> @comp] @arg)) - check (fn [] - (is (= {:at 1 :args [@t]} - (:initial-state @res))) - (is (= {:at 2 :args [@t]} - (:will-mount @res))) - (is (= {:at 3 :args [[:children ["a" "b"]]]} - (:render @res))) - (is (= {:at 4 :args [@t]} - (:did-mount @res))) + (doseq [opts test-options] + (let [n1 (atom 0) + t (atom 0) + res (atom {}) + oldprops (atom nil) + newprops (atom nil) + add-args (fn [key args] + (swap! res assoc key + {:at (swap! n1 inc) + :args (vec args)})) + render (fn [& args] + (this-as + c + (when @newprops + (is (= (first args) @newprops)) + (is (= (r/props c) @newprops))) + (is (= c (r/current-component))) + (is (= (first args) (r/props c))) + (add-args :render + {:children (r/children c)}) + [:div "" (first args)])) + ls {:get-initial-state + (fn [& args] + (reset! t (first args)) + (reset! oldprops (-> args first r/props)) + (add-args :initial-state args) + {:foo "bar"}) + :UNSAFE_component-will-mount + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :will-mount args)) + :component-did-mount + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :did-mount args)) + :should-component-update + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :should-update args) true) + :UNSAFE_component-will-receive-props + (fn [& args] + (reset! newprops (-> args second second)) + (this-as c + (is (= c (first args))) + (add-args :will-receive (into [(dissoc (r/props c) :children)] + (:children (r/props c)))))) + :UNSAFE_component-will-update + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :will-update args)) + :component-did-update + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :did-update args)) + :component-will-unmount + (fn [& args] + (this-as c (is (= c (first args)))) + (add-args :will-unmount args))} + c1 (r/create-class + (assoc ls :reagent-render render)) + defarg [{:foo "bar"} "a" "b"] + arg (r/atom defarg) + comp (atom c1) + cnative (fn [] + (into [:> @comp] @arg)) + check (fn [] + (is (= {:at 1 :args [@t]} + (:initial-state @res))) + (is (= {:at 2 :args [@t]} + (:will-mount @res))) + (is (= {:at 3 :args [[:children ["a" "b"]]]} + (:render @res))) + (is (= {:at 4 :args [@t]} + (:did-mount @res))) - (reset! arg [{:f "oo"} "a" "c"]) - (r/flush) + (reset! arg [{:f "oo"} "a" "c"]) + (r/flush) - (is (= {:at 5 :args [{:foo "bar"} "a" "b"]} - (:will-receive @res))) - (let [a (:should-update @res) - {at :at - [this oldv newv] :args} a] - (is (= 6 at)) - (is (= 3 (count (:args a)))) - (is (= (js->clj [@comp @oldprops]) (js->clj oldv))) - (is (= [@comp @newprops] newv))) - (let [a (:will-update @res) - {at :at - [this newv] :args} a] - (is (= 7 at)) - (is (= [@comp @newprops] newv))) - (is (= {:at 8 :args [[:children ["a" "c"]]]} - (:render @res))) - (let [a (:did-update @res) - {at :at - [this oldv] :args} a] - (is (= 9 at)) - (is (= [@comp @oldprops] oldv))))] - (when r/is-client - (with-mounted-component [cnative] check) - (is (= {:at 10 :args [@t]} - (:will-unmount @res)))))) + (is (= {:at 5 :args [{:foo "bar"} "a" "b"]} + (:will-receive @res))) + (let [a (:should-update @res) + {at :at + [this oldv newv] :args} a] + (is (= 6 at)) + (is (= 3 (count (:args a)))) + (is (= (js->clj [@comp @oldprops]) (js->clj oldv))) + (is (= [@comp @newprops] newv))) + (let [a (:will-update @res) + {at :at + [this newv] :args} a] + (is (= 7 at)) + (is (= [@comp @newprops] newv))) + (is (= {:at 8 :args [[:children ["a" "c"]]]} + (:render @res))) + (let [a (:did-update @res) + {at :at + [this oldv] :args} a] + (is (= 9 at)) + (is (= [@comp @oldprops] oldv))))] + (when r/is-client + (with-mounted-component [cnative] opts check) + (is (= {:at 10 :args [@t]} + (:will-unmount @res))))))) (defn foo [] [:div]) @@ -988,203 +1051,221 @@ (deftest test-err-messages (when (dev?) - (is (thrown-with-msg? - :default #"Hiccup form should not be empty: \[]" - (rstr []))) - (is (thrown-with-msg? - :default #"Invalid Hiccup tag: \[:>div \[reagenttest.testreagent.foo]]" - (rstr [:>div [foo]]))) - (is (thrown-with-msg? - :default #"Invalid Hiccup form: \[23]" - (rstr [23]))) - ;; This used to be asserted by Reagent, but because it is hard to validate - ;; components, now we just trust React will validate elements. - ; (is (thrown-with-msg? - ; :default #"Expected React component in: \[:> \[:div]]" - ; (rstr [:> [:div]]))) - ;; This is from React.createElement - ;; NOTE: browser-npm uses production cjs bundle for now which only shows - ;; the minified error - (debug/track-warnings - (wrap-capture-console-error - #(is (thrown-with-msg? - :default #"(Element type is invalid:|Minified React error)" - (rstr [:> [:div]]))))) - (is (thrown-with-msg? - :default #"Invalid tag: 'p.'" - (rstr [:p.]))) - (when r/is-client - (let [comp1 (fn comp1 [x] - x) - comp2 (fn comp2 [x] - [comp1 x]) - comp3 (fn comp3 [] - (r/with-let [a (r/atom "foo")] - [:div (for [i (range 0 1)] - ^{:key i} [:p @a])])) - comp4 (fn comp4 [] - (for [i (range 0 1)] - [:p "foo"])) - nat (let [cmp (fn [])] - (gobj/extend - (.-prototype cmp) - (.-prototype react/Component) - #js {:render (fn [])}) - (gobj/extend cmp react/Component) - cmp) - pkg "reagenttest.testreagent." - stack1 (str "in " pkg "comp1") - rend (fn [x] - (with-mounted-component x identity))] + (doseq [opts test-options + :let [rstr #(rstr % opts)]] + (is (thrown-with-msg? + :default #"Hiccup form should not be empty: \[]" + (rstr []))) + (is (thrown-with-msg? + :default #"Invalid Hiccup tag: \[:>div \[reagenttest.testreagent.foo]]" + (rstr [:>div [foo]]))) + (is (thrown-with-msg? + :default #"Invalid Hiccup form: \[23]" + (rstr [23]))) + ;; This used to be asserted by Reagent, but because it is hard to validate + ;; components, now we just trust React will validate elements. + ; (is (thrown-with-msg? + ; :default #"Expected React component in: \[:> \[:div]]" + ; (rstr [:> [:div]]))) + ;; This is from React.createElement + ;; NOTE: browser-npm uses production cjs bundle for now which only shows + ;; the minified error + (debug/track-warnings + (wrap-capture-console-error + #(is (thrown-with-msg? + :default #"(Element type is invalid:|Minified React error)" + (rstr [:> [:div]]))))) + (is (thrown-with-msg? + :default #"Invalid tag: 'p.'" + (rstr [:p.]))) + (when r/is-client + (let [comp1 (fn comp1 [x] + x) + comp2 (fn comp2 [x] + [comp1 x]) + comp3 (fn comp3 [] + (r/with-let [a (r/atom "foo")] + [:div (for [i (range 0 1)] + ^{:key i} [:p @a])])) + comp4 (fn comp4 [] + (for [i (range 0 1)] + [:p "foo"])) + nat (let [cmp (fn [])] + (gobj/extend + (.-prototype cmp) + (.-prototype react/Component) + #js {:render (fn [])}) + (gobj/extend cmp react/Component) + cmp) + pkg "reagenttest.testreagent." + stack1 (str "in " pkg "comp1") + rend (fn [x] + (with-mounted-component x opts identity))] - ;; Error is orginally caused by comp1, so only that is shown in the error - (let [e (debug/track-warnings - (wrap-capture-window-error - (wrap-capture-console-error - #(is (thrown-with-msg? - :default #"Invalid tag: 'div.' \(in reagenttest.testreagent.comp1\)" - (rend [comp2 [:div. "foo"]]))))))] - (is (re-find #"Error rendering component \(in reagenttest.testreagent.comp1\)" - (last (:error e))))) + ;; Error is orginally caused by comp1, so only that is shown in the error + (let [e (debug/track-warnings + (wrap-capture-window-error + (wrap-capture-console-error + #(is (thrown-with-msg? + :default #"Invalid tag: 'div.' \(in reagenttest.testreagent.comp1\)" + (rend [comp2 [:div. "foo"]]))))))] + (is (re-find #"Error rendering component \(in reagenttest.testreagent.comp1\)" + (last (:error e))))) - (let [e (debug/track-warnings - (wrap-capture-window-error - (wrap-capture-console-error - #(is (thrown-with-msg? - :default #"Invalid tag: 'div.' \(in reagenttest.testreagent.comp1\)" - (rend [comp1 [:div. "foo"]]))))))] - (is (re-find #"Error rendering component \(in reagenttest.testreagent.comp1\)" - (last (:error e))))) + (let [e (debug/track-warnings + (wrap-capture-window-error + (wrap-capture-console-error + #(is (thrown-with-msg? + :default #"Invalid tag: 'div.' \(in reagenttest.testreagent.comp1\)" + (rend [comp1 [:div. "foo"]]))))))] + (is (re-find #"Error rendering component \(in reagenttest.testreagent.comp1\)" + (last (:error e))))) - (let [e (debug/track-warnings #(r/as-element [nat]))] - (is (re-find #"Using native React classes directly" - (-> e :warn first)))) + (let [e (debug/track-warnings #(r/as-element [nat] opts))] + (is (re-find #"Using native React classes directly" + (-> e :warn first)))) - (let [e (debug/track-warnings - #(rend [comp3]))] - (is (re-find #"Reactive deref not supported" - (-> e :warn first)))) + (let [e (debug/track-warnings + #(rend [comp3]))] + (is (re-find #"Reactive deref not supported" + (-> e :warn first)))) - (let [e (debug/track-warnings - #(r/as-element (comp4)))] - (is (re-find #"Every element in a seq should have a unique :key" - (-> e :warn first)))))))) + (let [e (debug/track-warnings + #(r/as-element (comp4) opts))] + (is (re-find #"Every element in a seq should have a unique :key" + (-> e :warn first))))))))) (deftest test-error-boundary - (let [error (r/atom nil) - info (r/atom nil) - error-boundary (fn error-boundary [comp] - (r/create-class - {:component-did-catch (fn [this e i] - (reset! info i)) - :get-derived-state-from-error (fn [e] - (reset! error e) - #js {}) - :reagent-render (fn [comp] - (if @error - [:div "Something went wrong."] - comp))})) - comp1 (fn comp1 [] - (throw (js/Error. "Test error"))) - comp2 (fn comp2 [] - [comp1])] - (debug/track-warnings - (wrap-capture-window-error - (wrap-capture-console-error - #(with-mounted-component [error-boundary [comp2]] - (fn [c div] - (r/flush) - (is (= "Test error" (.-message @error))) - (is (re-find #"Something went wrong\." (.-innerHTML div))) - ;; FIXME: React.memo messes this up - #_ - (if (dev?) - (is (re-find #"\n in reagenttest.testreagent.comp1 \(created by reagenttest.testreagent.comp2\)\n in reagenttest.testreagent.comp2 \(created by reagent[0-9]+\)\n in reagent[0-9]+ \(created by reagenttest.testreagent.error_boundary\)\n in reagenttest.testreagent.error_boundary" - (.-componentStack ^js @info))) - (is (re-find #"\n in .+\n in .+\n in reagent[0-9]+\n in .+" - (.-componentStack ^js @info))) )))))))) + (doseq [opts test-options] + (let [error (r/atom nil) + info (r/atom nil) + error-boundary (fn error-boundary [comp] + (r/create-class + {:component-did-catch (fn [this e i] + (reset! info i)) + :get-derived-state-from-error (fn [e] + (reset! error e) + #js {}) + :reagent-render (fn [comp] + (if @error + [:div "Something went wrong."] + comp))})) + comp1 (fn comp1 [] + (throw (js/Error. "Test error"))) + comp2 (fn comp2 [] + [comp1])] + (debug/track-warnings + (wrap-capture-window-error + (wrap-capture-console-error + #(with-mounted-component [error-boundary [comp2]] + opts + (fn [c div] + (r/flush) + (is (= "Test error" (.-message @error))) + (is (re-find #"Something went wrong\." (.-innerHTML div))) + ;; FIXME: React.memo messes this up + #_ + (if (dev?) + (is (re-find #"\n in reagenttest.testreagent.comp1 \(created by reagenttest.testreagent.comp2\)\n in reagenttest.testreagent.comp2 \(created by reagent[0-9]+\)\n in reagent[0-9]+ \(created by reagenttest.testreagent.error_boundary\)\n in reagenttest.testreagent.error_boundary" + (.-componentStack ^js @info))) + (is (re-find #"\n in .+\n in .+\n in reagent[0-9]+\n in .+" + (.-componentStack ^js @info)))))))))))) (deftest test-dom-node - (let [node (atom nil) - ref (atom nil) - comp (r/create-class - {:reagent-render (fn test-dom [] - [:div {:ref #(reset! ref %)} "foobar"]) - :component-did-mount - (fn [this] - (reset! node (rdom/dom-node this)))})] - (with-mounted-component [comp] - (fn [c div] - (is (= "foobar" (.-innerHTML @ref))) - (is (= "foobar" (.-innerHTML @node))) - (is (identical? @ref @node)))))) + (doseq [opts test-options] + (let [node (atom nil) + ref (atom nil) + comp (r/create-class + {:reagent-render (fn test-dom [] + [:div {:ref #(reset! ref %)} "foobar"]) + :component-did-mount + (fn [this] + (reset! node (rdom/dom-node this)))})] + (with-mounted-component [comp] + opts + (fn [c div] + (is (= "foobar" (.-innerHTML @ref))) + (is (= "foobar" (.-innerHTML @node))) + (is (identical? @ref @node))))))) (deftest test-empty-input - (is (= "
" - (rstr [:div [:input]])))) + (doseq [opts test-options + :let [rstr #(rstr % opts)]] + (is (= "
" + (rstr [:div [:input]]))))) (deftest test-object-children - (is (= "

foo bar1

" - (rstr [:p 'foo " " :bar nil 1]))) - (is (= "

#object[reagent.ratom.RAtom {:val 1}]

" - (rstr [:p (r/atom 1)])))) + (doseq [opts test-options + :let [rstr #(rstr % opts)]] + (is (= "

foo bar1

" + (rstr [:p 'foo " " :bar nil 1]))) + (is (= "

#object[reagent.ratom.RAtom {:val 1}]

" + (rstr [:p (r/atom 1)]))))) (deftest test-after-render - (let [spy (atom 0) - val (atom 0) - exp (atom 0) - node (atom nil) - state (r/atom 0) - comp (fn [] - (let [old @spy] - (r/after-render - (fn [] - (is (= "DIV" (.-tagName @node))) - (swap! spy inc))) - (is (= @spy old)) - (is (= @exp @val)) - [:div {:ref #(reset! node %)} @state]))] - (with-mounted-component [comp] - (fn [c div] - (is (= 1 @spy)) - (swap! state inc) - (is (= 1 @spy)) - (r/next-tick #(swap! val inc)) - (reset! exp 1) - (is (= 0 @val)) - (r/flush) - (is (= 1 @val)) - (is (= 2 @spy)) - ;; FIXME: c is nil because render call doesn't return anything - ; (r/force-update c) - ; (is (= 3 @spy)) - ; (r/next-tick #(reset! spy 0)) - ; (is (= 3 @spy)) - ; (r/flush) - ; (is (= 0 @spy)) - )) - (is (= nil @node)))) + (doseq [opts test-options] + (let [spy (atom 0) + val (atom 0) + exp (atom 0) + node (atom nil) + state (r/atom 0) + comp (fn [] + (let [old @spy] + (r/after-render + (fn [] + (is (= "DIV" (.-tagName @node))) + (swap! spy inc))) + (is (= @spy old)) + (is (= @exp @val)) + [:div {:ref #(reset! node %)} @state]))] + (with-mounted-component [comp] + opts + (fn [c div] + (is (= 1 @spy)) + (swap! state inc) + (is (= 1 @spy)) + (r/next-tick #(swap! val inc)) + (reset! exp 1) + (is (= 0 @val)) + (r/flush) + (is (= 1 @val)) + (is (= 2 @spy)) + ;; FIXME: c is nil because render call doesn't return anything + ; (r/force-update c) + ; (is (= 3 @spy)) + ; (r/next-tick #(reset! spy 0)) + ; (is (= 3 @spy)) + ; (r/flush) + ; (is (= 0 @spy)) + )) + (is (= nil @node))))) (deftest style-property-names-are-camel-cased - (is (= "
foo
" - (rstr [:div {:style {:text-align "center"}} "foo"])))) + (doseq [opts test-options + :let [rstr #(rstr % opts)]] + (is (= "
foo
" + (rstr [:div {:style {:text-align "center"}} "foo"]))))) (deftest custom-element-class-prop - (is (= "foo" - (rstr [:custom-element {:class "foobar"} "foo"]))) + (doseq [opts test-options + :let [rstr #(rstr % opts)]] + (is (= "foo" + (rstr [:custom-element {:class "foobar"} "foo"]))) - (is (= "foo" - (rstr [:custom-element.foobar "foo"])))) + (is (= "foo" + (rstr [:custom-element.foobar "foo"]))))) (deftest html-entities - (testing "entity numbers can be unescaped always" - (is (= " " - (rstr [:i (gstr/unescapeEntities " ")])))) - - (when r/is-client - (testing "When DOM is available, all named entities can be unescaped" + (doseq [opts test-options + :let [rstr #(rstr % opts)]] + (testing "entity numbers can be unescaped always" (is (= " " - (rstr [:i (gstr/unescapeEntities " ")])))))) + (rstr [:i (gstr/unescapeEntities " ")])))) + + (when r/is-client + (testing "When DOM is available, all named entities can be unescaped" + (is (= " " + (rstr [:i (gstr/unescapeEntities " ")]))))))) (defn context-wrapper [] (r/create-class @@ -1214,43 +1295,44 @@ (deftest test-fragments - (testing "Fragment as array" - (let [comp (fn comp1 [] - #js [(r/as-element [:div "hello"]) - (r/as-element [:div "world"])])] - (is (= "
hello
world
" - (as-string [comp]))))) + (doseq [opts test-options] + (testing "Fragment as array" + (let [comp (fn comp1 [] + #js [(r/as-element [:div "hello"] opts) + (r/as-element [:div "world"] opts)])] + (is (= "
hello
world
" + (as-string [comp] opts))))) - (testing "Fragment element, :<>" - (let [comp (fn comp2 [] - [:<> - [:div "hello"] - [:div "world"] - [:div "foo"] ])] - (is (= "
hello
world
foo
" - (as-string [comp]))))) + (testing "Fragment element, :<>" + (let [comp (fn comp2 [] + [:<> + [:div "hello"] + [:div "world"] + [:div "foo"] ])] + (is (= "
hello
world
foo
" + (as-string [comp] opts))))) - (testing "Fragment key" - ;; This would cause React warning if both fragements didn't have key set - ;; But wont fail the test - (let [children (fn comp4 [] - [:<> - [:div "foo"]]) - comp (fn comp3 [] - [:div - (list - [:<> - {:key 1} - [:div "hello"] - [:div "world"]] - ^{:key 2} - [children] - ^{:key 3} - [:<> - [:div "1"] - [:div "2"]])])] - (is (= "
hello
world
foo
1
2
" - (as-string [comp])))))) + (testing "Fragment key" + ;; This would cause React warning if both fragements didn't have key set + ;; But wont fail the test + (let [children (fn comp4 [] + [:<> + [:div "foo"]]) + comp (fn comp3 [] + [:div + (list + [:<> + {:key 1} + [:div "hello"] + [:div "world"]] + ^{:key 2} + [children] + ^{:key 3} + [:<> + [:div "1"] + [:div "2"]])])] + (is (= "
hello
world
foo
1
2
" + (as-string [comp] opts))))))) ;; In bundle version, the names aren't optimized. ;; In node module processed versions, names probably are optimized. @@ -1260,179 +1342,192 @@ (def Consumer (.-Consumer my-context)) (deftest new-context-test - (is (= "
Context: foo
" - (rstr (r/create-element - Provider #js {:value "foo"} - (r/create-element - Consumer #js {} - (fn [v] - (r/as-element [:div "Context: " v]))))))) - - (testing "context default value works" - (is (= "
Context: default
" + (doseq [opts test-options + :let [rstr #(rstr % opts)]] + (is (= "
Context: foo
" (rstr (r/create-element - Consumer #js {} - (fn [v] - (r/as-element [:div "Context: " v]))))))) - - (testing "context works with adapt-react-class" - (let [provider (r/adapt-react-class Provider) - consumer (r/adapt-react-class Consumer)] - (is (= "
Context: bar
" - (rstr [provider {:value "bar"} - [consumer {} + Provider #js {:value "foo"} + (r/create-element + Consumer #js {} (fn [v] - (r/as-element [:div "Context: " v]))]]))))) + (r/as-element [:div "Context: " v]))))))) - (testing "context works with :>" - (is (= "
Context: bar
" - (rstr [:> Provider {:value "bar"} - [:> Consumer {} - (fn [v] - (r/as-element [:div "Context: " v]))]])))) - - (testing "static contextType" - (let [comp (r/create-class - {:context-type my-context - :reagent-render (fn [] - (this-as this - (r/as-element [:div "Context: " (.-context this)])))})] + (testing "context default value works" (is (= "
Context: default
" - (rstr [comp])))))) + (rstr (r/create-element + Consumer #js {} + (fn [v] + (r/as-element [:div "Context: " v]))))))) + + (testing "context works with adapt-react-class" + (let [provider (r/adapt-react-class Provider) + consumer (r/adapt-react-class Consumer)] + (is (= "
Context: bar
" + (rstr [provider {:value "bar"} + [consumer {} + (fn [v] + (r/as-element [:div "Context: " v]))]]))))) + + (testing "context works with :>" + (is (= "
Context: bar
" + (rstr [:> Provider {:value "bar"} + [:> Consumer {} + (fn [v] + (r/as-element [:div "Context: " v]))]])))) + + (testing "static contextType" + (let [comp (r/create-class + {:context-type my-context + :reagent-render (fn [] + (this-as this + (r/as-element [:div "Context: " (.-context this)])))})] + (is (= "
Context: default
" + (rstr [comp]))))))) (deftest on-failed-prop-comparison-in-should-update-swallow-exception-and-do-not-update-component - (let [prop (r/atom {:todos 1}) - component-was-updated (atom false) - error-thrown-after-updating-props (atom false) - component-class (r/create-class {:reagent-render (fn [& args] - [:div (str (first args))]) - :component-did-update (fn [& args] - (reset! component-was-updated true))}) - component (fn [] - [component-class @prop])] + (doseq [opts test-options] + (let [prop (r/atom {:todos 1}) + component-was-updated (atom false) + error-thrown-after-updating-props (atom false) + component-class (r/create-class {:reagent-render (fn [& args] + [:div (str (first args))]) + :component-did-update (fn [& args] + (reset! component-was-updated true))}) + component (fn [] + [component-class @prop])] - (when (and r/is-client (dev?)) - (let [e (debug/track-warnings - #(with-mounted-component [component] - (fn [c div] - (reset! prop (sorted-map 1 2)) - (try - (r/flush) - (catch :default e - (reset! error-thrown-after-updating-props true))) + (when (and r/is-client (dev?)) + (let [e (debug/track-warnings + #(with-mounted-component [component] + opts + (fn [c div] + (reset! prop (sorted-map 1 2)) + (try + (r/flush) + (catch :default e + (reset! error-thrown-after-updating-props true))) - (is (not @component-was-updated)) - (is (not @error-thrown-after-updating-props)))))] - (is (re-find #"Warning: Exception thrown while comparing argv's in shouldComponentUpdate:" - (first (:warn e)))))))) + (is (not @component-was-updated)) + (is (not @error-thrown-after-updating-props)))))] + (is (re-find #"Warning: Exception thrown while comparing argv's in shouldComponentUpdate:" + (first (:warn e))))))))) (deftest get-derived-state-from-props-test (when r/is-client - (let [prop (r/atom 0) - ;; Usually one can use Cljs object as React state. However, - ;; getDerivedStateFromProps implementation in React uses - ;; Object.assign to merge current state and partial state returned - ;; from the method, so the state has to be plain old object. - pure-component (r/create-class - {:constructor (fn [this] - (set! (.-state this) #js {})) - :get-derived-state-from-props (fn [props state] - ;; "Expensive" calculation based on the props - #js {:v (string/join " " (repeat (inc (:value props)) "foo"))}) - :render (fn [this] - (r/as-element [:p "Value " (gobj/get (.-state this) "v")]))}) - component (fn [] - [pure-component {:value @prop}])] - (with-mounted-component [component] - (fn [c div] - (is (= "Value foo" (.-innerText div))) - (swap! prop inc) - (r/flush) - (is (= "Value foo foo" (.-innerText div)))))))) + (doseq [opts test-options] + (let [prop (r/atom 0) + ;; Usually one can use Cljs object as React state. However, + ;; getDerivedStateFromProps implementation in React uses + ;; Object.assign to merge current state and partial state returned + ;; from the method, so the state has to be plain old object. + pure-component (r/create-class + {:constructor (fn [this] + (set! (.-state this) #js {})) + :get-derived-state-from-props (fn [props state] + ;; "Expensive" calculation based on the props + #js {:v (string/join " " (repeat (inc (:value props)) "foo"))}) + :render (fn [this] + (r/as-element [:p "Value " (gobj/get (.-state this) "v")]))}) + component (fn [] + [pure-component {:value @prop}])] + (with-mounted-component [component] + opts + (fn [c div] + (is (= "Value foo" (.-innerText div))) + (swap! prop inc) + (r/flush) + (is (= "Value foo foo" (.-innerText div))))))))) (deftest get-derived-state-from-error-test (when r/is-client - (let [prop (r/atom 0) - component (r/create-class - {:constructor (fn [this props] - (set! (.-state this) #js {:hasError false})) - :get-derived-state-from-error (fn [error] - #js {:hasError true}) - :component-did-catch (fn [this e info]) - :render (fn [^js/React.Component this] - (r/as-element (if (.-hasError (.-state this)) - [:p "Error"] - (into [:<>] (r/children this)))))}) - bad-component (fn [] - (if (= 0 @prop) - [:div "Ok"] - (throw (js/Error. "foo"))))] - (wrap-capture-window-error - (wrap-capture-console-error + (doseq [opts test-options] + (let [prop (r/atom 0) + component (r/create-class + {:constructor (fn [this props] + (set! (.-state this) #js {:hasError false})) + :get-derived-state-from-error (fn [error] + #js {:hasError true}) + :component-did-catch (fn [this e info]) + :render (fn [^js/React.Component this] + (r/as-element (if (.-hasError (.-state this)) + [:p "Error"] + (into [:<>] (r/children this)))))}) + bad-component (fn [] + (if (= 0 @prop) + [:div "Ok"] + (throw (js/Error. "foo"))))] + (wrap-capture-window-error + (wrap-capture-console-error #(with-mounted-component [component [bad-component]] + opts (fn [c div] (is (= "Ok" (.-innerText div))) (swap! prop inc) (r/flush) - (is (= "Error" (.-innerText div)))))))))) + (is (= "Error" (.-innerText div))))))))))) (deftest get-snapshot-before-update-test (when r/is-client - (let [ref (react/createRef) - prop (r/atom 0) - did-update (atom nil) - component (r/create-class - {:get-snapshot-before-update (fn [this [_ prev-props] prev-state] - {:height (.. ref -current -scrollHeight)}) - :component-did-update (fn [this [_ prev-props] prev-state snapshot] - (reset! did-update snapshot)) - :render (fn [this] - (r/as-element - [:div - {:ref ref - :style {:height "20px"}} - "foo"]))}) - component-2 (fn [] - [component {:value @prop}])] - (with-mounted-component [component-2] - (fn [c div] - ;; Attach to DOM to get real height value - (.appendChild js/document.body div) - (is (= "foo" (.-innerText div))) - (swap! prop inc) - (r/flush) - (is (= {:height 20} @did-update)) - (.removeChild js/document.body div)))))) + (doseq [opts test-options] + (let [ref (react/createRef) + prop (r/atom 0) + did-update (atom nil) + component (r/create-class + {:get-snapshot-before-update (fn [this [_ prev-props] prev-state] + {:height (.. ref -current -scrollHeight)}) + :component-did-update (fn [this [_ prev-props] prev-state snapshot] + (reset! did-update snapshot)) + :render (fn [this] + (r/as-element + [:div + {:ref ref + :style {:height "20px"}} + "foo"]))}) + component-2 (fn [] + [component {:value @prop}])] + (with-mounted-component [component-2] + opts + (fn [c div] + ;; Attach to DOM to get real height value + (.appendChild js/document.body div) + (is (= "foo" (.-innerText div))) + (swap! prop inc) + (r/flush) + (is (= {:height 20} @did-update)) + (.removeChild js/document.body div))))))) (deftest issue-462-test (when r/is-client - (let [val (r/atom 0) - render (atom 0) - a (fn issue-462-a [nr] - (swap! render inc) - [:h1 "Value " nr]) - b (fn issue-462-b [] - [:div - ^{:key @val} - [a @val]]) - c (fn issue-462-c [] - ^{:key @val} - [b])] - (with-mounted-component [c] - (fn [c div] - (is (= 1 @render)) - (reset! val 1) - (r/flush) - (is (= 2 @render)) - (reset! val 0) - (r/flush) - (is (= 3 @render))))))) + (doseq [opts test-options] + (let [val (r/atom 0) + render (atom 0) + a (fn issue-462-a [nr] + (swap! render inc) + [:h1 "Value " nr]) + b (fn issue-462-b [] + [:div + ^{:key @val} + [a @val]]) + c (fn issue-462-c [] + ^{:key @val} + [b])] + (with-mounted-component [c] + opts + (fn [c div] + (is (= 1 @render)) + (reset! val 1) + (r/flush) + (is (= 2 @render)) + (reset! val 0) + (r/flush) + (is (= 3 @render)))))))) (deftest functional-component-poc-simple (when r/is-client (let [c (fn [x] [:span "Hello " x])] (with-mounted-component [c "foo"] + {:functional-reag-elements? true} (fn [c div] (is (nil? c) "Render returns nil for stateless components") (is (= "Hello foo" (.-innerText div)))))))) @@ -1448,6 +1543,7 @@ (reset! set-count! set-count) [:span "Count " c]))] (with-mounted-component [c 5] + {:functional-reag-elements? true} (fn [c div] (is (nil? c) "Render returns nil for stateless components") (is (= "Count 5" (.-innerText div))) @@ -1460,6 +1556,7 @@ c (fn [x] [:span "Count " @count])] (with-mounted-component [c 5] + {:functional-reag-elements? true} (fn [c div] (is (nil? c) "Render returns nil for stateless components") (is (= "Count 5" (.-innerText div))) @@ -1479,6 +1576,7 @@ (reset! set-count! set-count) [:span "Counts " @r-count " " c]))] (with-mounted-component [c 15] + {:functional-reag-elements? true} (fn [c div] (is (nil? c) "Render returns nil for stateless components") (is (= "Counts 3 15" (.-innerText div))) diff --git a/test/reagenttest/utils.cljs b/test/reagenttest/utils.cljs index 205cb3b..e1b5867 100644 --- a/test/reagenttest/utils.cljs +++ b/test/reagenttest/utils.cljs @@ -2,15 +2,18 @@ (:require [reagent.core :as r] [reagent.dom :as rdom])) -(defn with-mounted-component [comp f] - (when r/is-client - (let [div (.createElement js/document "div")] - (try - (let [c (rdom/render comp div)] - (f c div)) - (finally - (rdom/unmount-component-at-node div) - (r/flush)))))) +(defn with-mounted-component + ([comp f] + (with-mounted-component comp nil f)) + ([comp opts f] + (when r/is-client + (let [div (.createElement js/document "div")] + (try + (let [c (rdom/render comp div opts)] + (f c div)) + (finally + (rdom/unmount-component-at-node div) + (r/flush))))))) (defn with-mounted-component-async [comp done f] (when r/is-client