2015-02-10 13:18:56 +00:00
(ns reagenttest.testreagent
2019-12-17 12:32:45 +00:00
(:require [clojure.test :as t :refer-macros [is deftest testing]]
2017-10-13 16:20:36 +00:00
[react :as react]
2015-09-24 08:19:30 +00:00
[reagent.ratom :as rv :refer-macros [reaction]]
2019-12-17 12:32:45 +00:00
[reagent.debug :as debug :refer-macros [dev?]]
2015-10-08 13:52:18 +00:00
[reagent.core :as r]
2016-11-17 00:46:31 +00:00
[reagent.dom.server :as server]
2020-02-03 15:34:31 +00:00
[reagenttest.utils :as u :refer [with-mounted-component]]
2019-08-14 07:35:59 +00:00
[clojure.string :as string]
2017-11-28 13:41:30 +00:00
[goog.string :as gstr]
[goog.object :as gobj]
[prop-types :as prop-types]))
2013-12-16 22:19:36 +00:00
2017-11-08 18:08:27 +00:00
(t/use-fixtures :once
{:before (fn []
(set! rv/debug true))
:after (fn []
(set! rv/debug false))})
2015-09-30 06:56:06 +00:00
2018-03-13 19:49:48 +00:00
(defn rstr [react-elem]
(server/render-to-static-markup react-elem))
2019-10-02 12:37:56 +00:00
(defn log-error [& f]
(debug/error (apply str f)))
(defn wrap-capture-console-error [f]
(fn []
(let [org js/console.error]
(set! js/console.error log-error)
(try
(f)
(finally
(set! js/console.error org))))))
2013-12-16 22:19:36 +00:00
(deftest really-simple-test
2020-02-05 21:01:27 +00:00
(when r/is-client
2015-07-31 13:13:27 +00:00
(let [ran (r/atom 0)
2013-12-18 11:14:57 +00:00
really-simple (fn []
(swap! ran inc)
[:div "div in really-simple"])]
(with-mounted-component [really-simple nil nil]
(fn [c div]
(swap! ran inc)
2020-02-03 15:34:31 +00:00
(is (= "div in really-simple" (.-innerText div)))
2015-07-31 13:13:27 +00:00
(r/flush)
2014-12-09 11:58:30 +00:00
(is (= 2 @ran))
2015-07-31 13:13:27 +00:00
(r/force-update-all)
2014-12-09 11:58:30 +00:00
(is (= 3 @ran))))
(is (= 3 @ran)))))
2013-12-16 22:19:36 +00:00
(deftest test-simple-callback
2020-02-05 21:01:27 +00:00
(when r/is-client
2015-07-31 13:13:27 +00:00
(let [ran (r/atom 0)
comp (r/create-class
2014-02-08 12:55:01 +00:00
{:component-did-mount #(swap! ran inc)
:render
(fn [this]
2015-07-31 13:13:27 +00:00
(let [props (r/props this)]
2014-02-14 10:27:45 +00:00
(is (map? props))
2015-07-31 13:13:27 +00:00
(is (= props ((r/argv this) 1)))
(is (= 1 (first (r/children this))))
(is (= 1 (count (r/children this))))
2014-02-08 12:55:01 +00:00
(swap! ran inc)
[:div (str "hi " (:foo props) ".")]))})]
2015-08-20 07:35:24 +00:00
(with-mounted-component [comp {:foo "you"} 1]
2013-12-18 11:14:57 +00:00
(fn [C div]
(swap! ran inc)
2020-02-03 15:34:31 +00:00
(is (= "hi you." (.-innerText div)))))
2014-02-10 08:31:25 +00:00
(is (= 3 @ran)))))
2013-12-16 22:19:36 +00:00
(deftest test-state-change
2020-02-05 21:01:27 +00:00
(when r/is-client
2015-07-31 13:13:27 +00:00
(let [ran (r/atom 0)
self (r/atom nil)
comp (r/create-class
2014-02-15 10:48:12 +00:00
{:get-initial-state (fn [] {:foo "initial"})
2015-02-04 21:45:39 +00:00
:reagent-render
2014-02-08 12:55:01 +00:00
(fn []
2015-07-31 13:13:27 +00:00
(let [this (r/current-component)]
2014-03-25 05:23:44 +00:00
(reset! self this)
2014-02-08 12:55:01 +00:00
(swap! ran inc)
2015-07-31 13:13:27 +00:00
[:div (str "hi " (:foo (r/state this)))]))})]
2015-08-20 07:35:24 +00:00
(with-mounted-component [comp]
2013-12-18 11:14:57 +00:00
(fn [C div]
(swap! ran inc)
2020-02-03 15:34:31 +00:00
(is (= "hi initial" (.-innerText div)))
2013-12-18 11:14:57 +00:00
2015-07-31 13:13:27 +00:00
(r/replace-state @self {:foo "there"})
(r/state @self)
2014-03-25 05:23:44 +00:00
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 15:34:31 +00:00
(is (= "hi there" (.-innerText div)))
2013-12-18 11:14:57 +00:00
2015-07-31 13:13:27 +00:00
(r/set-state @self {:foo "you"})
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 15:34:31 +00:00
(is (= "hi you" (.-innerText div)))))
2013-12-18 11:14:57 +00:00
(is (= 4 @ran)))))
2013-12-16 22:19:36 +00:00
(deftest test-ratom-change
2020-02-05 21:01:27 +00:00
(when r/is-client
2015-07-31 13:13:27 +00:00
(let [ran (r/atom 0)
2020-02-05 21:01:27 +00:00
runs (rv/running)
2015-07-31 13:13:27 +00:00
val (r/atom 0)
secval (r/atom 0)
2015-09-11 13:06:42 +00:00
v1-ran (atom 0)
v1 (reaction (swap! v1-ran inc) @val)
2013-12-18 11:14:57 +00:00
comp (fn []
(swap! ran inc)
2020-02-03 15:34:31 +00:00
[:div (str "val " @v1 " " @val " " @secval)])]
2013-12-18 11:14:57 +00:00
(with-mounted-component [comp]
(fn [C div]
2015-07-31 13:13:27 +00:00
(r/flush)
2020-02-05 21:01:27 +00:00
(is (not= runs (rv/running)))
2020-02-03 15:34:31 +00:00
(is (= "val 0 0 0" (.-innerText div)))
2014-01-27 12:37:59 +00:00
(is (= 1 @ran))
2013-12-18 11:14:57 +00:00
2014-01-27 12:37:59 +00:00
(reset! secval 1)
(reset! secval 0)
2013-12-18 11:14:57 +00:00
(reset! val 1)
2014-01-27 12:37:59 +00:00
(reset! val 2)
2013-12-18 11:14:57 +00:00
(reset! val 1)
2015-08-31 08:59:59 +00:00
(is (= 1 @ran))
2015-09-11 13:06:42 +00:00
(is (= 1 @v1-ran))
2015-07-31 13:13:27 +00:00
(r/flush)
2020-02-03 15:34:31 +00:00
(is (= "val 1 1 0" (.-innerText div)))
2015-08-31 09:36:42 +00:00
(is (= 2 @ran) "ran once more")
2015-09-11 13:06:42 +00:00
(is (= 2 @v1-ran))
2013-12-18 11:14:57 +00:00
;; should not be rendered
(reset! val 1)
2015-09-11 13:06:42 +00:00
(is (= 2 @v1-ran))
2015-07-31 13:13:27 +00:00
(r/flush)
2015-09-11 13:06:42 +00:00
(is (= 2 @v1-ran))
2020-02-03 15:34:31 +00:00
(is (= "val 1 1 0" (.-innerText div)))
2015-08-31 09:36:42 +00:00
(is (= 2 @ran) "did not run")))
2020-02-05 21:01:27 +00:00
(is (= runs (rv/running)))
2014-01-27 12:37:59 +00:00
(is (= 2 @ran)))))
2013-12-16 22:19:36 +00:00
2014-01-27 15:17:37 +00:00
(deftest batched-update-test []
2020-02-05 21:01:27 +00:00
(when r/is-client
2015-07-31 13:13:27 +00:00
(let [ran (r/atom 0)
v1 (r/atom 0)
v2 (r/atom 0)
2014-01-27 15:17:37 +00:00
c2 (fn [{val :val}]
(swap! ran inc)
2020-02-03 16:02:32 +00:00
(is (= val @v1))
2014-01-27 15:17:37 +00:00
[:div @v2])
c1 (fn []
(swap! ran inc)
[:div @v1
[c2 {:val @v1}]])]
(with-mounted-component [c1]
(fn [c div]
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 2 @ran))
2014-01-27 15:17:37 +00:00
(swap! v2 inc)
2020-02-03 16:02:32 +00:00
(is (= 2 @ran))
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 3 @ran))
2014-01-27 15:17:37 +00:00
(swap! v1 inc)
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 5 @ran))
2014-01-27 15:17:37 +00:00
(swap! v2 inc)
(swap! v1 inc)
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 7 @ran))
2014-01-27 15:17:37 +00:00
(swap! v1 inc)
(swap! v1 inc)
(swap! v2 inc)
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 9 @ran)))))))
2013-12-16 22:19:36 +00:00
2013-12-18 08:13:16 +00:00
(deftest init-state-test
2020-02-05 21:01:27 +00:00
(when r/is-client
2015-07-31 13:13:27 +00:00
(let [ran (r/atom 0)
2014-02-08 12:55:01 +00:00
really-simple (fn []
2015-07-31 13:13:27 +00:00
(let [this (r/current-component)]
2014-02-08 12:55:01 +00:00
(swap! ran inc)
2015-07-31 13:13:27 +00:00
(r/set-state this {:foo "foobar"})
2014-02-08 12:55:01 +00:00
(fn []
[:div (str "this is "
2015-07-31 13:13:27 +00:00
(:foo (r/state this)))])))]
2013-12-18 11:14:57 +00:00
(with-mounted-component [really-simple nil nil]
(fn [c div]
(swap! ran inc)
2020-02-03 15:34:31 +00:00
(is (= "this is foobar" (.-innerText div)))))
2013-12-18 11:14:57 +00:00
(is (= 2 @ran)))))
2013-12-18 08:13:16 +00:00
2014-01-30 09:34:41 +00:00
(deftest shoud-update-test
2020-02-05 21:01:27 +00:00
(when r/is-client
2015-07-31 13:13:27 +00:00
(let [parent-ran (r/atom 0)
child-ran (r/atom 0)
child-props (r/atom nil)
2014-01-30 09:34:41 +00:00
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]
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 1 @child-ran))
2020-02-03 15:34:31 +00:00
(is (= "child-foo" (.-innerText div)))
2020-02-05 21:01:27 +00:00
2019-12-17 12:32:45 +00:00
(reset! child-props {:style {:display :none}})
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 2 @child-ran))
2020-02-05 21:01:27 +00:00
2019-12-17 12:32:45 +00:00
(reset! child-props {:style {:display :none}})
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 2 @child-ran) "keyw is equal")
2020-02-05 21:01:27 +00:00
(reset! child-props {:class :foo}) (r/flush)
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 3 @child-ran))
2020-02-05 21:01:27 +00:00
(reset! child-props {:class :foo}) (r/flush)
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 3 @child-ran))
2020-02-05 21:01:27 +00:00
2019-12-17 12:32:45 +00:00
(reset! child-props {:class 'foo})
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 4 @child-ran) "symbols are different from keyw")
2020-02-05 21:01:27 +00:00
2019-12-17 12:32:45 +00:00
(reset! child-props {:class 'foo})
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 4 @child-ran) "symbols are equal")
2020-02-05 21:01:27 +00:00
2019-12-17 12:32:45 +00:00
(reset! child-props {:style {:color 'red}})
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 5 @child-ran))
2020-02-05 21:01:27 +00:00
2019-12-17 12:32:45 +00:00
(reset! child-props {:on-change (r/partial f)})
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 6 @child-ran))
2020-02-05 21:01:27 +00:00
2019-12-17 12:32:45 +00:00
(reset! child-props {:on-change (r/partial f)})
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 6 @child-ran))
2020-02-05 21:01:27 +00:00
2019-12-17 12:32:45 +00:00
(reset! child-props {:on-change (r/partial f1)})
2020-02-05 21:01:27 +00:00
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 7 @child-ran))
2014-12-09 11:58:30 +00:00
2015-07-31 13:13:27 +00:00
(r/force-update-all)
2020-02-03 16:02:32 +00:00
(is (= 8 @child-ran)))))))
2014-01-30 09:34:41 +00:00
2014-02-03 12:58:31 +00:00
(deftest dirty-test
2020-02-05 21:01:27 +00:00
(when r/is-client
2015-07-31 13:13:27 +00:00
(let [ran (r/atom 0)
state (r/atom 0)
2014-02-10 22:08:20 +00:00
really-simple (fn []
2014-02-03 12:58:31 +00:00
(swap! ran inc)
2020-02-03 16:02:32 +00:00
(if (= 1 @state)
2014-02-03 12:58:31 +00:00
(reset! state 3))
[:div (str "state=" @state)])]
(with-mounted-component [really-simple nil nil]
(fn [c div]
(is (= 1 @ran))
2020-02-03 15:34:31 +00:00
(is (= "state=0" (.-innerText div)))
2014-02-03 12:58:31 +00:00
(reset! state 1)
2020-02-05 21:01:27 +00:00
(r/flush)
2014-02-03 12:58:31 +00:00
(is (= 2 @ran))
2020-02-03 15:34:31 +00:00
(is (= "state=3" (.-innerText div)))))
2014-02-03 12:58:31 +00:00
(is (= 2 @ran)))))
2014-01-30 09:34:41 +00:00
2014-01-25 12:21:14 +00:00
(defn as-string [comp]
2018-03-12 11:53:48 +00:00
(server/render-to-static-markup comp))
2014-01-25 12:21:14 +00:00
2013-12-18 08:13:16 +00:00
(deftest to-string-test []
(let [comp (fn [props]
[:div (str "i am " (:foo props))])]
2020-02-03 16:02:32 +00:00
(is (= "<div>i am foobar</div>" (as-string [comp {:foo "foobar"}])))))
2014-01-25 12:21:14 +00:00
(deftest data-aria-test []
2020-02-03 16:02:32 +00:00
(is (= "<div data-foo=\"x\"></div>"
(as-string [:div {:data-foo "x"}])))
(is (= "<div aria-labelledby=\"x\"></div>"
(as-string [:div {:aria-labelledby "x"}])))
2016-07-14 07:34:03 +00:00
;; Skip test: produces warning in new React
;; (is (not (re-find #"enctype"
;; (as-string [:div {"enc-type" "x"}])))
;; "Strings are passed through to React.")
2017-10-13 08:18:48 +00:00
;; FIXME: For some reason UMD module returns everything in
;; lowercase, and CommonJS with upper T
(is (re-find #"enc[tT]ype"
2014-01-25 12:21:14 +00:00
(as-string [:div {"encType" "x"}]))
"Strings are passed through to React, and have to be camelcase.")
2017-10-13 08:18:48 +00:00
(is (re-find #"enc[tT]ype"
2014-01-25 12:21:14 +00:00
(as-string [:div {:enc-type "x"}]))
"Strings are passed through to React, and have to be camelcase."))
2014-01-28 17:00:15 +00:00
(deftest dynamic-id-class []
(is (re-find #"id=.foo"
(as-string [:div#foo {:class "bar"}])))
2020-02-03 16:02:32 +00:00
(is (= "<div class=\"foo bar\"></div>"
(as-string [:div.foo {:class "bar"}])))
(is (= "<div class=\"foo bar\"></div>"
(as-string [:div.foo.bar])))
2014-01-28 17:00:15 +00:00
(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"}])))
2020-02-03 16:02:32 +00:00
(is (= "<div id=\"foo\"></div>"
(as-string [:div#bar {:id "foo"}]))
2014-01-28 17:00:15 +00:00
"Dynamic id overwrites static"))
2014-02-11 10:36:58 +00:00
2019-12-17 12:32:45 +00:00
(defmulti my-div :type)
(defmethod my-div :fooish [child] [:div.foo (:content child)])
(defmethod my-div :barish [child] [:div.bar (:content child)])
2014-03-03 15:49:41 +00:00
2019-12-17 12:32:45 +00:00
(deftest ifn-component []
2014-02-11 10:36:58 +00:00
(let [comp {:foo [:div "foodiv"]
:bar [:div "bardiv"]}]
2020-02-03 16:02:32 +00:00
(is (= "<div><div>foodiv</div></div>"
(as-string [:div [comp :foo]])))
(is (= "<div><div>bardiv</div></div>"
(as-string [:div [comp :bar]])))
(is (= "<div class=\"foo\">inner</div>"
(as-string [my-div {:type :fooish :content "inner"}])))))
2014-02-11 11:03:44 +00:00
(deftest symbol-string-tag []
2020-02-03 16:02:32 +00:00
(is (= "<div>foobar</div>" (as-string ['div "foobar"])))
(is (= "<div>foobar</div>" (as-string ["div" "foobar"])))
(is (= "<div id=\"foo\">x</div>" (as-string ['div#foo "x"])))
(is (= "<div id=\"foo\">x</div>" (as-string ["div#foo" "x"])))
(is (= "<div class=\"foo bar\"></div>" (as-string ['div.foo {:class "bar"}])))
(is (= "<div class=\"foo bar\"></div>" (as-string ["div.foo.bar"])))
2014-02-11 11:03:44 +00:00
(is (re-find #"id=.foo"
(as-string ['div#foo.foo.bar]))))
2014-02-14 10:27:45 +00:00
(deftest partial-test []
2015-07-31 13:13:27 +00:00
(let [p1 (r/partial vector 1 2)]
2020-02-03 16:02:32 +00:00
(is (= [1 2 3] (p1 3)))
2015-07-31 13:13:27 +00:00
(is (= p1 (r/partial vector 1 2)))
2014-02-14 10:27:45 +00:00
(is (ifn? p1))
2015-07-31 13:13:27 +00:00
(is (= (r/partial vector 1 2) p1))
2017-06-27 11:30:13 +00:00
(is (not= p1 (r/partial vector 1 3)))
(is (= (hash p1) (hash (r/partial vector 1 2))))))
2014-09-16 14:31:29 +00:00
(deftest test-null-component
(let [null-comp (fn [do-show]
(when do-show
[:div "div in test-null-component"]))]
2020-02-03 16:02:32 +00:00
(is (= ""
(as-string [null-comp false])))
(is (= "<div>div in test-null-component</div>"
(as-string [null-comp true])))))
2014-11-06 10:34:51 +00:00
(deftest test-static-markup
(is (= "<div>foo</div>"
2018-03-13 19:49:48 +00:00
(rstr [:div "foo"])))
2014-11-06 10:34:51 +00:00
(is (= "<div class=\"bar\"><p>foo</p></div>"
2018-03-13 19:49:48 +00:00
(rstr [:div.bar [:p "foo"]])))
2014-11-06 18:18:56 +00:00
(is (= "<div class=\"bar\"><p>foobar</p></div>"
2018-03-13 19:49:48 +00:00
(rstr [:div.bar {:dangerously-set-inner-HTML
{:__html "<p>foobar</p>"}} ]))))
2014-11-22 08:42:31 +00:00
(deftest test-return-class
2020-02-05 21:01:27 +00:00
(when r/is-client
2015-07-31 13:13:27 +00:00
(let [ran (r/atom 0)
top-ran (r/atom 0)
2014-11-22 08:42:31 +00:00
comp (fn []
(swap! top-ran inc)
2015-07-31 13:13:27 +00:00
(r/create-class
2014-11-22 08:42:31 +00:00
{:component-did-mount #(swap! ran inc)
:render
(fn [this]
2015-07-31 13:13:27 +00:00
(let [props (r/props this)]
2014-11-22 08:42:31 +00:00
(is (map? props))
2015-07-31 13:13:27 +00:00
(is (= props ((r/argv this) 1)))
(is (= 1 (first (r/children this))))
(is (= 1 (count (r/children this))))
2014-11-22 08:42:31 +00:00
(swap! ran inc)
[:div (str "hi " (:foo props) ".")]))}))
2015-07-31 13:13:27 +00:00
prop (r/atom {:foo "you"})
2014-11-22 08:42:31 +00:00
parent (fn [] [comp @prop 1])]
(with-mounted-component [parent]
(fn [C div]
(swap! ran inc)
2020-02-03 15:34:31 +00:00
(is (= "hi you." (.-innerText div)))
2014-11-22 08:42:31 +00:00
(is (= 1 @top-ran))
(is (= 3 @ran))
(swap! prop assoc :foo "me")
2015-07-31 13:13:27 +00:00
(r/flush)
2020-02-03 15:34:31 +00:00
(is (= "hi me." (.-innerText div)))
2014-11-22 08:42:31 +00:00
(is (= 1 @top-ran))
(is (= 4 @ran)))))))
(deftest test-return-class-fn
2020-02-05 21:01:27 +00:00
(when r/is-client
2015-07-31 13:13:27 +00:00
(let [ran (r/atom 0)
top-ran (r/atom 0)
2014-11-22 08:42:31 +00:00
comp (fn []
(swap! top-ran inc)
2015-07-31 13:13:27 +00:00
(r/create-class
2014-11-22 08:42:31 +00:00
{:component-did-mount #(swap! ran inc)
2018-12-31 12:38:15 +00:00
:reagent-render
2014-11-22 08:42:31 +00:00
(fn [p a]
(is (= 1 a))
(swap! ran inc)
[:div (str "hi " (:foo p) ".")])}))
2015-07-31 13:13:27 +00:00
prop (r/atom {:foo "you"})
2014-11-22 08:42:31 +00:00
parent (fn [] [comp @prop 1])]
(with-mounted-component [parent]
(fn [C div]
(swap! ran inc)
2020-02-03 15:34:31 +00:00
(is (= "hi you." (.-innerText div)))
2014-11-22 08:42:31 +00:00
(is (= 1 @top-ran))
(is (= 3 @ran))
(swap! prop assoc :foo "me")
2015-07-31 13:13:27 +00:00
(r/flush)
2020-02-03 15:34:31 +00:00
(is (= "hi me." (.-innerText div)))
2014-11-22 08:42:31 +00:00
(is (= 1 @top-ran))
(is (= 4 @ran)))))))
2014-12-02 10:43:35 +00:00
(deftest test-create-element
2015-07-31 13:13:27 +00:00
(let [ae r/as-element
ce r/create-element]
2020-02-03 16:02:32 +00:00
(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" 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"]]))))))
2014-12-02 10:43:35 +00:00
2018-12-31 08:37:43 +00:00
(def ndiv (let [cmp (fn [])]
(gobj/extend
(.-prototype cmp)
(.-prototype react/Component)
#js {:render (fn []
(this-as
this
(r/create-element
2018-12-31 11:18:39 +00:00
"div" #js {:className (.. this -props -className)}
(.. this -props -children))))})
2018-12-31 08:37:43 +00:00
(gobj/extend cmp react/Component)
cmp))
2015-02-02 14:19:43 +00:00
(deftest test-adapt-class
2015-07-31 13:13:27 +00:00
(let [d1 (r/adapt-react-class ndiv)
d2 (r/adapt-react-class "div")]
2015-02-02 14:19:43 +00:00
(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"]])))))
2015-02-07 23:01:31 +00:00
2015-10-06 12:27:44 +00:00
(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"]])))
(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"]])))))
2015-02-07 23:01:31 +00:00
(deftest test-reactize-component
2015-07-31 13:13:27 +00:00
(let [ae r/as-element
ce r/create-element
2015-10-06 10:49:47 +00:00
a (atom nil)
2015-10-08 12:14:34 +00:00
c1r (fn reactize [p & args]
2015-10-06 10:49:47 +00:00
(reset! a args)
2015-02-07 23:01:31 +00:00
[:p "p:" (:a p) (:children p)])
2015-07-31 13:13:27 +00:00
c1 (r/reactify-component c1r)]
2020-02-03 16:02:32 +00:00
(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"}
2015-02-07 23:01:31 +00:00
(ae [:b "b"])
2020-02-03 16:02:32 +00:00
(ae [:i "i"])))
(rstr [:p "p:a" [:b "b"] [:i "i"]])))
(is (= nil @a))))
2015-02-08 11:24:52 +00:00
(deftest test-keys
2015-07-31 13:13:27 +00:00
(let [a nil ;; (r/atom "a")
2015-02-08 11:24:52 +00:00
c (fn key-tester []
[:div
(for [i (range 3)]
^{:key i} [:p i (some-> a deref)])
(for [i (range 3)]
2019-10-02 12:37:56 +00:00
[:p {:key i} i (some-> a deref)])])
w (debug/track-warnings
#(with-mounted-component [c]
(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 (debug/dev?)
(is (= "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)))))))))
2015-07-30 18:26:16 +00:00
(deftest test-extended-syntax
2020-02-03 16:02:32 +00:00
(is (= "<p><b>foo</b></p>"
(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"]))))
2015-08-20 12:59:13 +00:00
2017-11-28 16:51:37 +00:00
(deftest extended-syntax-metadata
(when r/is-client
(let [comp (fn []
[:div
(for [k [1 2]]
^{:key k} [:div>div "a"])])]
(with-mounted-component [comp]
(fn [c div]
;; Just make sure this doesn't print a debug message
)))))
2015-07-02 16:12:48 +00:00
(deftest test-class-from-collection
2020-02-03 16:02:32 +00:00
(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"}}]))))
2015-07-02 16:12:48 +00:00
2018-05-04 13:10:41 +00:00
(deftest class-different-types
(testing "named values are supported"
2020-02-03 16:02:32 +00:00
(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]}]))))
2018-05-04 13:10:41 +00:00
(testing "non-named values like numbers"
2020-02-03 16:02:32 +00:00
(is (= (rstr [:p {:class "1 b"}])
(rstr [:p {:class [1 :b]}]))))
2018-05-04 13:10:41 +00:00
(testing "falsey values are filtered from collections"
2020-02-03 16:02:32 +00:00
(is (= (rstr [:p {:class "a b"}])
(rstr [:p {:class [:a :b false nil]}])))) )
2018-04-27 20:19:59 +00:00
2015-08-20 12:59:13 +00:00
(deftest test-force-update
(let [v (atom {:v1 0
:v2 0})
comps (atom {})
c1 (fn []
(swap! comps assoc :c1 (r/current-component))
2016-07-14 07:34:03 +00:00
[:p "" (swap! v update-in [:v1] inc)])
2015-08-20 12:59:13 +00:00
c2 (fn []
(swap! comps assoc :c2 (r/current-component))
2016-07-14 07:34:03 +00:00
[:div "" (swap! v update-in [:v2] inc)
2016-04-30 12:24:18 +00:00
[c1]])
state (r/atom 0)
spy (r/atom 0)
t (fn [] @state)
t1 (fn [] @(r/track t))
c3 (fn []
(swap! comps assoc :c3 (r/current-component))
2016-07-14 07:34:03 +00:00
[:div "" (reset! spy @(r/track t1))])]
2015-08-20 12:59:13 +00:00
(with-mounted-component [c2]
(fn [c div]
2020-02-03 16:02:32 +00:00
(is (= {:v1 1 :v2 1} @v))
2015-08-20 12:59:13 +00:00
(r/force-update (:c2 @comps))
2020-02-03 16:02:32 +00:00
(is (= {:v1 1 :v2 2} @v))
2015-08-20 12:59:13 +00:00
(r/force-update (:c1 @comps))
2020-02-03 16:02:32 +00:00
(is (= {:v1 2 :v2 2} @v))
2015-08-20 12:59:13 +00:00
(r/force-update (:c2 @comps) true)
2020-02-03 16:02:32 +00:00
(is (= {:v1 3 :v2 3} @v))))
2016-04-30 12:24:18 +00:00
(with-mounted-component [c3]
(fn [c]
2020-02-03 16:02:32 +00:00
(is (= 0 @spy))
2016-04-30 12:24:18 +00:00
(swap! state inc)
2020-02-03 16:02:32 +00:00
(is (= 0 @spy))
2016-04-30 12:24:18 +00:00
(r/force-update (:c3 @comps))
2020-02-03 16:02:32 +00:00
(is (= 1 @spy))))))
2015-08-30 16:29:27 +00:00
(deftest test-component-path
(let [a (atom nil)
tc (r/create-class {:display-name "atestcomponent"
:render (fn []
(let [c (r/current-component)]
(reset! a (r/component-path c))
[:div]))})]
(with-mounted-component [tc]
(fn [c]
(is (seq @a))
(is (re-find #"atestcomponent" @a) "component-path should work")))))
2015-08-31 06:18:45 +00:00
(deftest test-sorted-map-key
(let [c1 (fn [map]
[:div (map 1)])
c2 (fn []
[c1 (sorted-map 1 "foo" 2 "bar")])]
2020-02-03 16:02:32 +00:00
(is (= "<div>foo</div>" (rstr [c2])))))
2015-09-23 08:39:49 +00:00
(deftest basic-with-let
2020-02-05 21:01:27 +00:00
(when r/is-client
2015-09-25 09:48:11 +00:00
(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])))))
2015-09-23 13:55:57 +00:00
(deftest with-let-destroy-only
2020-02-05 21:01:27 +00:00
(when r/is-client
2015-09-25 09:48:11 +00:00
(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])))))
2015-09-23 13:55:57 +00:00
2015-09-26 22:07:19 +00:00
(deftest with-let-arg
2020-02-05 21:01:27 +00:00
(when r/is-client
2015-09-26 22:07:19 +00:00
(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]
2020-02-03 16:02:32 +00:00
(is (= "foo" @a))
2015-09-26 22:07:19 +00:00
(reset! s "bar")
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= "bar" @a)))))))
2015-09-26 22:07:19 +00:00
2015-09-23 13:55:57 +00:00
(deftest with-let-non-reactive
(let [n1 (atom 0)
n2 (atom 0)
n3 (atom 0)
c (fn []
2015-09-24 08:19:30 +00:00
(r/with-let [a (swap! n1 inc)]
2015-09-23 13:55:57 +00:00
(swap! n2 inc)
[:div a]
(finally
(swap! n3 inc))))]
(is (= (rstr [c]) (rstr [:div 1])))
(is (= [1 1 1] [@n1 @n2 @n3]))))
2015-09-25 11:44:10 +00:00
(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]
2015-10-11 11:30:22 +00:00
(this-as c (is (= c (r/current-component))))
(add-args :render args)
2016-07-14 07:34:03 +00:00
[:div "" (first args)])
2015-10-11 11:30:22 +00:00
render2 (fn [& args]
2015-09-25 11:44:10 +00:00
(add-args :render args)
2016-07-14 07:34:03 +00:00
[:div "" (first args)])
2015-09-25 11:44:10 +00:00
ls {:get-initial-state
(fn [& args]
(reset! t (first args))
(add-args :initial-state args)
{:foo "bar"})
2019-08-13 06:14:40 +00:00
:UNSAFE_component-will-mount
2015-10-11 11:30:22 +00:00
(fn [& args]
(this-as c (is (= c (first args))))
(add-args :will-mount args))
2015-09-25 11:44:10 +00:00
:component-did-mount
2015-10-11 11:30:22 +00:00
(fn [& args]
(this-as c (is (= c (first args))))
(add-args :did-mount args))
2015-09-25 11:44:10 +00:00
:should-component-update
2015-10-11 11:30:22 +00:00
(fn [& args]
(this-as c (is (= c (first args))))
(add-args :should-update args) true)
2019-08-13 06:14:40 +00:00
:UNSAFE_component-will-receive-props
2015-10-11 11:30:22 +00:00
(fn [& args]
(this-as c (is (= c (first args))))
(add-args :will-receive args))
2019-08-13 06:14:40 +00:00
:UNSAFE_component-will-update
2015-10-11 11:30:22 +00:00
(fn [& args]
(this-as c (is (= c (first args))))
(add-args :will-update args))
2015-09-25 11:44:10 +00:00
:component-did-update
2015-10-11 11:30:22 +00:00
(fn [& args]
(this-as c (is (= c (first args))))
(add-args :did-update args))
2015-09-25 11:44:10 +00:00
:component-will-unmount
2015-10-11 11:30:22 +00:00
(fn [& args]
(this-as c (is (= c (first args))))
(add-args :will-unmount args))}
2015-09-25 11:44:10 +00:00
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))
2015-10-12 15:18:37 +00:00
cnative (fn []
(into [:> @comp] @arg))
2015-09-25 11:44:10 +00:00
check (fn []
2020-02-03 16:02:32 +00:00
(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)))
2015-09-25 11:44:10 +00:00
(reset! arg ["a" "c"])
(r/flush)
2020-02-03 16:02:32 +00:00
(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))))]
2020-02-05 21:01:27 +00:00
(when r/is-client
2015-09-25 11:44:10 +00:00
(with-mounted-component [c2] check)
2020-02-03 16:02:32 +00:00
(is (= {:at 10 :args [@t]}
(:will-unmount @res)))
2015-09-25 11:44:10 +00:00
2015-10-11 11:30:22 +00:00
(reset! comp (with-meta render2 ls))
2015-09-25 11:44:10 +00:00
(reset! arg defarg)
(reset! n1 0)
(with-mounted-component [c2] check)
2020-02-03 16:02:32 +00:00
(is (= {:at 10 :args [@t]}
(:will-unmount @res))))))
2015-10-07 17:50:29 +00:00
2015-10-12 15:18:37 +00:00
(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
2020-02-03 16:02:32 +00:00
(is (= (first args) @newprops))
(is (= (r/props c) @newprops)))
2015-10-12 15:18:37 +00:00
(is (= c (r/current-component)))
(is (= (first args) (r/props c)))
(add-args :render
{:children (r/children c)})
2016-07-14 07:34:03 +00:00
[:div "" (first args)]))
2015-10-12 15:18:37 +00:00
ls {:get-initial-state
(fn [& args]
(reset! t (first args))
(reset! oldprops (-> args first r/props))
(add-args :initial-state args)
{:foo "bar"})
2019-08-13 06:14:40 +00:00
:UNSAFE_component-will-mount
2015-10-12 15:18:37 +00:00
(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)
2019-08-13 06:14:40 +00:00
:UNSAFE_component-will-receive-props
2015-10-12 15:18:37 +00:00
(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))))))
2019-08-13 06:14:40 +00:00
:UNSAFE_component-will-update
2015-10-12 15:18:37 +00:00
(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 []
2020-02-03 16:02:32 +00:00
(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)))
2015-10-12 15:18:37 +00:00
(reset! arg [{:f "oo"} "a" "c"])
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= {:at 5 :args [{:foo "bar"} "a" "b"]}
(:will-receive @res)))
2015-10-12 15:18:37 +00:00
(let [a (:should-update @res)
{at :at
[this oldv newv] :args} a]
2020-02-03 16:02:32 +00:00
(is (= 6 at))
(is (= 3 (count (:args a))))
(is (= (js->clj [@comp @oldprops]) (js->clj oldv)))
(is (= [@comp @newprops] newv)))
2015-10-12 15:18:37 +00:00
(let [a (:will-update @res)
{at :at
[this newv] :args} a]
2020-02-03 16:02:32 +00:00
(is (= 7 at))
(is (= [@comp @newprops] newv)))
(is (= {:at 8 :args [[:children ["a" "c"]]]}
(:render @res)))
2015-10-12 15:18:37 +00:00
(let [a (:did-update @res)
{at :at
[this oldv] :args} a]
2020-02-03 16:02:32 +00:00
(is (= 9 at))
(is (= [@comp @oldprops] oldv))))]
2020-02-05 21:01:27 +00:00
(when r/is-client
2015-10-12 15:18:37 +00:00
(with-mounted-component [cnative] check)
2020-02-03 16:02:32 +00:00
(is (= {:at 10 :args [@t]}
(:will-unmount @res))))))
2015-10-12 15:18:37 +00:00
2015-10-07 17:50:29 +00:00
(defn foo []
[:div])
2017-10-13 16:57:46 +00:00
(defn wrap-capture-window-error [f]
2018-04-03 07:33:01 +00:00
(if (exists? js/window)
(fn []
(let [org js/console.onerror]
(set! js/window.onerror (fn [e]
(log-error e)
true))
(try
(f)
(finally
(set! js/window.onerror org)))))
(fn []
(let [process (js/require "process")
l (fn [e]
(log-error e))]
(.on process "uncaughtException" l)
(try
(f)
(finally
(.removeListener process "uncaughtException" l)))))))
2017-10-13 16:57:46 +00:00
2015-10-07 17:50:29 +00:00
(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])))
2018-05-04 15:47:45 +00:00
;; 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
2018-05-04 15:58:00 +00:00
;; NOTE: browser-npm uses production cjs bundle for now which only shows
;; the minified error
2018-05-04 15:47:45 +00:00
(debug/track-warnings
(wrap-capture-console-error
#(is (thrown-with-msg?
2018-05-04 15:58:00 +00:00
:default #"(Element type is invalid:|Minified React error)"
2018-05-04 15:47:45 +00:00
(rstr [:> [:div]])))))
2015-10-07 17:50:29 +00:00
(is (thrown-with-msg?
:default #"Invalid tag: 'p.'"
2015-10-07 18:38:19 +00:00
(rstr [:p.])))
2016-05-27 11:14:35 +00:00
(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"]))
2018-12-31 08:37:43 +00:00
nat (let [cmp (fn [])]
(gobj/extend
(.-prototype cmp)
(.-prototype react/Component)
#js {:render (fn [])})
(gobj/extend cmp react/Component)
cmp)
2016-05-27 11:14:35 +00:00
pkg "reagenttest.testreagent."
stack1 (str "in " pkg "comp1")
stack2 (str "in " pkg "comp2 > " pkg "comp1")
re (fn [& s]
(re-pattern (apply str s)))
rend (fn [x]
(with-mounted-component x identity))]
(let [e (debug/track-warnings
2017-10-13 16:57:46 +00:00
(wrap-capture-window-error
(wrap-capture-console-error
#(is (thrown-with-msg?
:default (re "Invalid tag: 'div.' \\(" stack2 "\\)")
(rend [comp2 [:div. "foo"]]))))))]
2020-02-03 16:02:32 +00:00
(is (= (str "Error rendering component (" stack2 ")")
(last (:error e)))))
2016-05-27 11:14:35 +00:00
(let [e (debug/track-warnings
2017-10-13 16:57:46 +00:00
(wrap-capture-window-error
(wrap-capture-console-error
#(is (thrown-with-msg?
:default (re "Invalid tag: 'div.' \\(" stack1 "\\)")
(rend [comp1 [:div. "foo"]]))))))]
2020-02-03 16:02:32 +00:00
(is (= (str "Error rendering component (" stack1 ")")
(last (:error e)))))
2016-05-27 11:14:35 +00:00
(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
#(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))))))))
2015-10-08 12:14:34 +00:00
2017-10-13 16:20:36 +00:00
(deftest test-error-boundary
2018-04-03 07:33:01 +00:00
(let [error (r/atom nil)
error-boundary (fn error-boundary [comp]
(r/create-class
2019-10-02 12:37:21 +00:00
{:component-did-catch (fn [this e info])
:get-derived-state-from-error (fn [e]
(reset! error e)
2019-10-02 09:59:00 +00:00
#js {})
2018-04-03 07:33:01 +00:00
:reagent-render (fn [comp]
(if @error
[:div "Something went wrong."]
comp))}))
comp1 (fn comp1 []
2019-10-02 12:37:21 +00:00
(throw (js/Error. "Test error")))]
2018-04-03 07:33:01 +00:00
(debug/track-warnings
(wrap-capture-window-error
(wrap-capture-console-error
#(with-mounted-component [error-boundary [comp1]]
(fn [c div]
(r/flush)
2018-04-03 08:00:46 +00:00
(is (= "Test error" (.-message @error)))
(is (re-find #"Something went wrong\." (.-innerHTML div))))))))))
2017-10-13 16:20:36 +00:00
2015-10-08 12:14:34 +00:00
(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 (r/dom-node this)))})]
(with-mounted-component [comp]
(fn [c div]
2020-02-03 16:02:32 +00:00
(is (= "foobar" (.-innerHTML @ref)))
(is (= "foobar" (.-innerHTML @node)))
2015-10-08 12:14:34 +00:00
(is (identical? @ref @node))))))
2015-10-22 11:31:02 +00:00
(deftest test-empty-input
(is (= "<div><input/></div>"
(rstr [:div [:input]]))))
2016-04-29 07:13:55 +00:00
(deftest test-object-children
(is (= "<p>foo bar1</p>"
(rstr [:p 'foo " " :bar nil 1])))
(is (= "<p>#<Atom: 1></p>"
(rstr [:p (r/atom 1)]))))
2016-05-31 21:09:43 +00:00
(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]
2020-02-03 16:02:32 +00:00
(r/after-render
(fn []
(is (= "DIV" (.-tagName @node)))
(swap! spy inc)))
(is (= @spy old))
2016-05-31 21:09:43 +00:00
(is (= @exp @val))
[:div {:ref #(reset! node %)} @state]))]
(with-mounted-component [comp]
(fn [c div]
2020-02-03 16:02:32 +00:00
(is (= 1 @spy))
2016-05-31 21:09:43 +00:00
(swap! state inc)
2020-02-03 16:02:32 +00:00
(is (= 1 @spy))
(r/next-tick #(swap! val inc))
2016-05-31 21:09:43 +00:00
(reset! exp 1)
2020-02-03 16:02:32 +00:00
(is (= 0 @val))
(r/flush)
(is (= 1 @val))
(is (= 2 @spy))
(r/force-update c)
(is (= 3 @spy))
(r/next-tick #(reset! spy 0))
(is (= 3 @spy))
2016-05-31 21:09:43 +00:00
(r/flush)
2020-02-03 16:02:32 +00:00
(is (= 0 @spy))))
(is (= nil @node))))
2017-03-10 23:56:39 +00:00
(deftest style-property-names-are-camel-cased
2020-02-03 16:02:32 +00:00
(is (= "<div style=\"text-align:center\">foo</div>"
(rstr [:div {:style {:text-align "center"}} "foo"]))))
2017-11-08 18:29:06 +00:00
(deftest custom-element-class-prop
2020-02-03 16:02:32 +00:00
(is (= "<custom-element class=\"foobar\">foo</custom-element>"
(rstr [:custom-element {:class "foobar"} "foo"])))
2017-11-08 18:29:06 +00:00
2020-02-03 16:02:32 +00:00
(is (= "<custom-element class=\"foobar\">foo</custom-element>"
(rstr [:custom-element.foobar "foo"]))))
2017-11-23 06:45:43 +00:00
(deftest html-entities
2017-11-28 15:13:10 +00:00
(testing "entity numbers can be unescaped always"
(is (= "<i> </i>"
2018-03-13 19:49:48 +00:00
(rstr [:i (gstr/unescapeEntities " ")]))))
2017-11-28 15:13:10 +00:00
(when r/is-client
(testing "When DOM is available, all named entities can be unescaped"
(is (= "<i> </i>"
2018-03-13 19:49:48 +00:00
(rstr [:i (gstr/unescapeEntities " ")]))))))
2017-11-28 13:41:30 +00:00
(defn context-wrapper []
(r/create-class
{:get-child-context (fn []
(this-as this
#js {:foo "bar"}))
:child-context-types #js {:foo prop-types/string.isRequired}
:reagent-render (fn [child]
[:div
"parent,"
child])}))
(defn context-child []
(r/create-class
{:context-types #js {:foo prop-types/string.isRequired}
:reagent-render (fn []
(let [this (r/current-component)]
;; Context property name is not mangled, so need to use gobj/get to access property by string name
;; React extern handles context name.
[:div "child," (gobj/get (.-context this) "foo")]))}))
(deftest context-test
(with-mounted-component [context-wrapper [context-child]]
(fn [c div]
(is (= "parent,child,bar"
(.-innerText div))))))
2018-03-12 11:53:48 +00:00
(deftest test-fragments
2018-04-03 07:33:01 +00:00
(testing "Fragment as array"
2018-04-03 07:42:25 +00:00
(let [comp (fn comp1 []
2018-04-03 07:33:01 +00:00
#js [(r/as-element [:div "hello"])
(r/as-element [:div "world"])])]
(is (= "<div>hello</div><div>world</div>"
(as-string [comp])))))
(testing "Fragment element, :<>"
2018-04-03 07:42:25 +00:00
(let [comp (fn comp2 []
2018-04-03 07:33:01 +00:00
[:<>
[:div "hello"]
[:div "world"]
[:div "foo"] ])]
(is (= "<div>hello</div><div>world</div><div>foo</div>"
(as-string [comp])))))
(testing "Fragment key"
;; This would cause React warning if both fragements didn't have key set
2018-04-03 07:42:25 +00:00
;; But wont fail the test
(let [children (fn comp4 []
[:<>
[:div "foo"]])
comp (fn comp3 []
2018-04-03 07:33:01 +00:00
[:div
(list
[:<>
{:key 1}
[:div "hello"]
[:div "world"]]
^{:key 2}
2018-11-14 20:31:50 +00:00
[children]
^{:key 3}
[:<>
[:div "1"]
[:div "2"]])])]
(is (= "<div><div>hello</div><div>world</div><div>foo</div><div>1</div><div>2</div></div>"
2018-04-03 07:33:01 +00:00
(as-string [comp]))))))
2018-05-04 14:49:56 +00:00
(defonce my-context (react/createContext "default"))
(def Provider (.-Provider my-context))
(def Consumer (.-Consumer my-context))
(deftest new-context-test
(is (= "<div>Context: foo</div>"
(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 (= "<div>Context: default</div>"
(rstr (r/create-element
2018-05-04 15:14:48 +00:00
Consumer #js {}
(fn [v]
(r/as-element [:div "Context: " v])))))))
2018-05-04 14:49:56 +00:00
(testing "context works with adapt-react-class"
(let [provider (r/adapt-react-class Provider)
consumer (r/adapt-react-class Consumer)]
(is (= "<div>Context: bar</div>"
(rstr [provider {:value "bar"}
[consumer {}
(fn [v]
(r/as-element [:div "Context: " v]))]])))))
(testing "context works with :>"
(is (= "<div>Context: bar</div>"
(rstr [:> Provider {:value "bar"}
[:> Consumer {}
(fn [v]
2019-12-17 00:21:05 +00:00
(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 (= "<div>Context: default</div>"
(rstr [comp]))))))
2018-05-04 16:18:51 +00:00
(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])]
2020-02-05 21:01:27 +00:00
(when (and r/is-client (dev?))
2018-05-04 16:18:51 +00:00
(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)))
(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))))))))
2019-08-14 07:35:59 +00:00
(deftest get-derived-state-from-props-test
2020-02-05 21:01:27 +00:00
(when r/is-client
2019-08-14 07:35:59 +00:00
(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]
2019-08-14 07:53:05 +00:00
(r/as-element [:p "Value " (gobj/get (.-state this) "v")]))})
2019-08-14 07:35:59 +00:00
component (fn []
[pure-component {:value @prop}])]
(with-mounted-component [component]
(fn [c div]
2020-02-03 15:34:31 +00:00
(is (= "Value foo" (.-innerText div)))
2019-08-14 07:35:59 +00:00
(swap! prop inc)
(r/flush)
2020-02-03 15:34:31 +00:00
(is (= "Value foo foo" (.-innerText div))))))))
2019-08-14 07:35:59 +00:00
(deftest get-derived-state-from-error-test
2020-02-05 21:01:27 +00:00
(when r/is-client
2019-08-14 07:35:59 +00:00
(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])
2019-10-25 07:25:43 +00:00
:render (fn [^js/React.Component this]
2019-08-14 07:35:59 +00:00
(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]]
(fn [c div]
2020-02-03 15:34:31 +00:00
(is (= "Ok" (.-innerText div)))
2019-08-14 07:35:59 +00:00
(swap! prop inc)
(r/flush)
2020-02-03 15:34:31 +00:00
(is (= "Error" (.-innerText div))))))))))
2019-08-14 07:35:59 +00:00
(deftest get-snapshot-before-update-test
2020-02-05 21:01:27 +00:00
(when r/is-client
2019-08-14 07:35:59 +00:00
(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)
2020-02-03 15:34:31 +00:00
(is (= "foo" (.-innerText div)))
2019-08-14 07:35:59 +00:00
(swap! prop inc)
(r/flush)
(is (= {:height 20} @did-update))
(.removeChild js/document.body div))))))
Use component constructor to keep track of mount order
Previous change (35ff5d33dd) started using ComponentDidMount to keep
track of component mount order. This affected the order in which this
was called, previously ComponentWillMount was called the first for
parent components and then for children. ComponentDidMount was called
first for children etc. To work around this, the mount order was
reversed when updating components after ratom updates.
Problem with this is, that when only some components are rerendered,
they get new numbers, but their parents don't:
(given components c, b, a)
**0.8.1**
c 1 > b 2 > a 3
a rerendered
c 1 > b 2 > a 4
b rerendered
c 1 > b 5 > a 6
**35ff5d33dd**
c 3 > b 2 > a 1
a rerendered
c 3 > b 2 > a 4 (BROKEN)
b rerendered
c 3 > b 6 > a 5 (BROKEN)
Best way to fix this is to revert back to old way, where parents get the
smaller number, this was re-rendering children doesn't change the order.
To implement this the mount-order can be stored in component
constructor, which seems to work similarly to ComponentWillMount.
> The constructor for a React component is called before it is mounted.
> UNSAFE_componentWillMount()... Generally, we recommend using the constructor() instead for initializing state.
2019-12-16 19:05:31 +00:00
(deftest issue-462-test
2020-02-05 21:01:27 +00:00
(when r/is-client
Use component constructor to keep track of mount order
Previous change (35ff5d33dd) started using ComponentDidMount to keep
track of component mount order. This affected the order in which this
was called, previously ComponentWillMount was called the first for
parent components and then for children. ComponentDidMount was called
first for children etc. To work around this, the mount order was
reversed when updating components after ratom updates.
Problem with this is, that when only some components are rerendered,
they get new numbers, but their parents don't:
(given components c, b, a)
**0.8.1**
c 1 > b 2 > a 3
a rerendered
c 1 > b 2 > a 4
b rerendered
c 1 > b 5 > a 6
**35ff5d33dd**
c 3 > b 2 > a 1
a rerendered
c 3 > b 2 > a 4 (BROKEN)
b rerendered
c 3 > b 6 > a 5 (BROKEN)
Best way to fix this is to revert back to old way, where parents get the
smaller number, this was re-rendering children doesn't change the order.
To implement this the mount-order can be stored in component
constructor, which seems to work similarly to ComponentWillMount.
> The constructor for a React component is called before it is mounted.
> UNSAFE_componentWillMount()... Generally, we recommend using the constructor() instead for initializing state.
2019-12-16 19:05:31 +00:00
(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)))))))