2015-02-10 14:18:56 +01:00
(ns reagenttest.testreagent
2015-01-31 23:09:42 +01:00
(:require [cljs.test :as t :refer-macros [is deftest testing]]
2017-10-13 19:20:36 +03:00
[react :as react]
2015-09-24 10:19:30 +02:00
[reagent.ratom :as rv :refer-macros [reaction]]
2015-10-07 20:38:19 +02:00
[reagent.debug :as debug :refer-macros [dbg println log dev?]]
2015-10-08 15:52:18 +02:00
[reagent.core :as r]
2016-11-17 02:46:31 +02:00
[reagent.dom.server :as server]
2017-11-08 20:08:27 +02:00
[reagent.impl.util :as util]
2017-11-23 08:45:43 +02:00
[reagenttest.utils :as u :refer [with-mounted-component found-in]]
2019-08-14 10:35:59 +03:00
[clojure.string :as string]
2017-11-28 15:41:30 +02:00
[goog.string :as gstr]
[goog.object :as gobj]
[prop-types :as prop-types]))
2013-12-16 23:19:36 +01:00
2016-05-01 13:45:37 +02:00
(def tests-done (atom {}))
2017-11-08 20:08:27 +02:00
(t/use-fixtures :once
{:before (fn []
(set! rv/debug true))
:after (fn []
(set! rv/debug false))})
2015-09-30 08:56:06 +02:00
2013-12-16 23:19:36 +01:00
(defn running [] (rv/running))
2015-07-31 15:13:27 +02:00
(def isClient r/is-client)
2013-12-16 23:19:36 +01:00
2015-07-31 15:13:27 +02:00
(def rflush r/flush)
2014-01-27 16:17:37 +01:00
2018-03-13 21:49:48 +02:00
(defn rstr [react-elem]
(server/render-to-static-markup react-elem))
2019-10-02 15:37:56 +03: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 23:19:36 +01:00
(deftest really-simple-test
2016-05-01 13:45:37 +02:00
(when (and isClient
(not (:really-simple-test @tests-done)))
(swap! tests-done assoc :really-simple-test true)
2015-07-31 15:13:27 +02:00
(let [ran (r/atom 0)
2013-12-18 12:14:57 +01: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)
2014-12-09 12:58:30 +01:00
(is (found-in #"div in really-simple" div))
2015-07-31 15:13:27 +02:00
(r/flush)
2014-12-09 12:58:30 +01:00
(is (= 2 @ran))
2015-07-31 15:13:27 +02:00
(r/force-update-all)
2014-12-09 12:58:30 +01:00
(is (= 3 @ran))))
(is (= 3 @ran)))))
2013-12-16 23:19:36 +01:00
(deftest test-simple-callback
2013-12-18 12:14:57 +01:00
(when isClient
2015-07-31 15:13:27 +02:00
(let [ran (r/atom 0)
comp (r/create-class
2014-02-08 13:55:01 +01:00
{:component-did-mount #(swap! ran inc)
:render
(fn [this]
2015-07-31 15:13:27 +02:00
(let [props (r/props this)]
2014-02-14 11:27:45 +01:00
(is (map? props))
2015-07-31 15:13:27 +02:00
(is (= props ((r/argv this) 1)))
(is (= 1 (first (r/children this))))
(is (= 1 (count (r/children this))))
2014-02-08 13:55:01 +01:00
(swap! ran inc)
[:div (str "hi " (:foo props) ".")]))})]
2015-08-20 09:35:24 +02:00
(with-mounted-component [comp {:foo "you"} 1]
2013-12-18 12:14:57 +01:00
(fn [C div]
(swap! ran inc)
2014-02-10 09:31:25 +01:00
(is (found-in #"hi you" div))))
(is (= 3 @ran)))))
2013-12-16 23:19:36 +01:00
(deftest test-state-change
2013-12-18 12:14:57 +01:00
(when isClient
2015-07-31 15:13:27 +02:00
(let [ran (r/atom 0)
self (r/atom nil)
comp (r/create-class
2014-02-15 11:48:12 +01:00
{:get-initial-state (fn [] {:foo "initial"})
2015-02-04 22:45:39 +01:00
:reagent-render
2014-02-08 13:55:01 +01:00
(fn []
2015-07-31 15:13:27 +02:00
(let [this (r/current-component)]
2014-03-25 06:23:44 +01:00
(reset! self this)
2014-02-08 13:55:01 +01:00
(swap! ran inc)
2015-07-31 15:13:27 +02:00
[:div (str "hi " (:foo (r/state this)))]))})]
2015-08-20 09:35:24 +02:00
(with-mounted-component [comp]
2013-12-18 12:14:57 +01:00
(fn [C div]
(swap! ran inc)
2014-02-15 11:48:12 +01:00
(is (found-in #"hi initial" div))
2013-12-18 12:14:57 +01:00
2015-07-31 15:13:27 +02:00
(r/replace-state @self {:foo "there"})
(r/state @self)
2014-03-25 06:23:44 +01:00
2014-02-15 11:48:12 +01:00
(rflush)
2013-12-18 12:14:57 +01:00
(is (found-in #"hi there" div))
2015-07-31 15:13:27 +02:00
(r/set-state @self {:foo "you"})
2014-02-15 11:48:12 +01:00
(rflush)
2014-12-09 12:58:30 +01:00
(is (found-in #"hi you" div))))
2013-12-18 12:14:57 +01:00
(is (= 4 @ran)))))
2013-12-16 23:19:36 +01:00
(deftest test-ratom-change
2013-12-18 12:14:57 +01:00
(when isClient
2015-07-31 15:13:27 +02:00
(let [ran (r/atom 0)
2013-12-18 12:14:57 +01:00
runs (running)
2015-07-31 15:13:27 +02:00
val (r/atom 0)
secval (r/atom 0)
2015-09-11 15:06:42 +02:00
v1-ran (atom 0)
v1 (reaction (swap! v1-ran inc) @val)
2013-12-18 12:14:57 +01:00
comp (fn []
(swap! ran inc)
2014-01-27 13:37:59 +01:00
[:div (str "val " @v1 @val @secval)])]
2013-12-18 12:14:57 +01:00
(with-mounted-component [comp]
(fn [C div]
2015-07-31 15:13:27 +02:00
(r/flush)
2013-12-18 12:14:57 +01:00
(is (not= runs (running)))
(is (found-in #"val 0" div))
2014-01-27 13:37:59 +01:00
(is (= 1 @ran))
2013-12-18 12:14:57 +01:00
2014-01-27 13:37:59 +01:00
(reset! secval 1)
(reset! secval 0)
2013-12-18 12:14:57 +01:00
(reset! val 1)
2014-01-27 13:37:59 +01:00
(reset! val 2)
2013-12-18 12:14:57 +01:00
(reset! val 1)
2015-08-31 10:59:59 +02:00
(is (= 1 @ran))
2015-09-11 15:06:42 +02:00
(is (= 1 @v1-ran))
2015-07-31 15:13:27 +02:00
(r/flush)
2013-12-18 12:14:57 +01:00
(is (found-in #"val 1" div))
2015-08-31 11:36:42 +02:00
(is (= 2 @ran) "ran once more")
2015-09-11 15:06:42 +02:00
(is (= 2 @v1-ran))
2013-12-18 12:14:57 +01:00
;; should not be rendered
(reset! val 1)
2015-09-11 15:06:42 +02:00
(is (= 2 @v1-ran))
2015-07-31 15:13:27 +02:00
(r/flush)
2015-09-11 15:06:42 +02:00
(is (= 2 @v1-ran))
2013-12-18 12:14:57 +01:00
(is (found-in #"val 1" div))
2015-08-31 11:36:42 +02:00
(is (= 2 @ran) "did not run")))
2013-12-18 12:14:57 +01:00
(is (= runs (running)))
2014-01-27 13:37:59 +01:00
(is (= 2 @ran)))))
2013-12-16 23:19:36 +01:00
2014-01-27 16:17:37 +01:00
(deftest batched-update-test []
(when isClient
2015-07-31 15:13:27 +02:00
(let [ran (r/atom 0)
v1 (r/atom 0)
v2 (r/atom 0)
2014-01-27 16:17:37 +01:00
c2 (fn [{val :val}]
(swap! ran inc)
2014-02-14 11:27:45 +01:00
(is (= @v1 val))
2014-01-27 16:17:37 +01:00
[:div @v2])
c1 (fn []
(swap! ran inc)
[:div @v1
[c2 {:val @v1}]])]
(with-mounted-component [c1]
(fn [c div]
(rflush)
(is (= @ran 2))
(swap! v2 inc)
(is (= @ran 2))
(rflush)
(is (= @ran 3))
(swap! v1 inc)
(rflush)
(is (= @ran 5))
(swap! v2 inc)
(swap! v1 inc)
(rflush)
(is (= @ran 7))
(swap! v1 inc)
(swap! v1 inc)
(swap! v2 inc)
(rflush)
(is (= @ran 9)))))))
2013-12-16 23:19:36 +01:00
2013-12-18 09:13:16 +01:00
(deftest init-state-test
2013-12-18 12:14:57 +01:00
(when isClient
2015-07-31 15:13:27 +02:00
(let [ran (r/atom 0)
2014-02-08 13:55:01 +01:00
really-simple (fn []
2015-07-31 15:13:27 +02:00
(let [this (r/current-component)]
2014-02-08 13:55:01 +01:00
(swap! ran inc)
2015-07-31 15:13:27 +02:00
(r/set-state this {:foo "foobar"})
2014-02-08 13:55:01 +01:00
(fn []
[:div (str "this is "
2015-07-31 15:13:27 +02:00
(:foo (r/state this)))])))]
2013-12-18 12:14:57 +01:00
(with-mounted-component [really-simple nil nil]
(fn [c div]
(swap! ran inc)
(is (found-in #"this is foobar" div))))
(is (= 2 @ran)))))
2013-12-18 09:13:16 +01:00
2014-01-30 10:34:41 +01:00
(deftest shoud-update-test
2016-05-01 13:45:37 +02:00
(when (and isClient
(not (:should-update-test @tests-done)))
(swap! tests-done assoc :should-update-test true)
2015-07-31 15:13:27 +02:00
(let [parent-ran (r/atom 0)
child-ran (r/atom 0)
child-props (r/atom nil)
2014-01-30 10:34:41 +01: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]
(rflush)
(is (= @child-ran 1))
(is (found-in #"child-foo" div))
(do (reset! child-props {:style {:display :none}})
(rflush))
(is (= @child-ran 2))
(do (reset! child-props {:style {:display :none}})
(rflush))
(is (= @child-ran 2) "keyw is equal")
(do (reset! child-props {:class :foo}) (rflush))
(is (= @child-ran 3))
(do (reset! child-props {:class :foo}) (rflush))
(is (= @child-ran 3))
(do (reset! child-props {:class 'foo}) (rflush))
(is (= @child-ran 4) "symbols are different from keyw")
(do (reset! child-props {:class 'foo}) (rflush))
(is (= @child-ran 4) "symbols are equal")
(do (reset! child-props {:style {:color 'red}}) (rflush))
(is (= @child-ran 5))
2015-07-31 15:13:27 +02:00
(do (reset! child-props {:on-change (r/partial f)})
2014-01-30 10:34:41 +01:00
(rflush))
(is (= @child-ran 6))
2015-07-31 15:13:27 +02:00
(do (reset! child-props {:on-change (r/partial f)})
2014-01-30 10:34:41 +01:00
(rflush))
(is (= @child-ran 6))
2015-07-31 15:13:27 +02:00
(do (reset! child-props {:on-change (r/partial f1)})
2014-01-30 10:34:41 +01:00
(rflush))
2014-12-09 12:58:30 +01:00
(is (= @child-ran 7))
2015-07-31 15:13:27 +02:00
(r/force-update-all)
2014-12-09 12:58:30 +01:00
(is (= @child-ran 8)))))))
2014-01-30 10:34:41 +01:00
2014-02-03 13:58:31 +01:00
(deftest dirty-test
(when isClient
2015-07-31 15:13:27 +02:00
(let [ran (r/atom 0)
state (r/atom 0)
2014-02-10 23:08:20 +01:00
really-simple (fn []
2014-02-03 13:58:31 +01:00
(swap! ran inc)
(if (= @state 1)
(reset! state 3))
[:div (str "state=" @state)])]
(with-mounted-component [really-simple nil nil]
(fn [c div]
(is (= 1 @ran))
(is (found-in #"state=0" div))
(reset! state 1)
(rflush)
(is (= 2 @ran))
(is (found-in #"state=3" div))))
(is (= 2 @ran)))))
2014-01-30 10:34:41 +01:00
2014-01-25 13:21:14 +01:00
(defn as-string [comp]
2018-03-12 13:53:48 +02:00
(server/render-to-static-markup comp))
2014-01-25 13:21:14 +01:00
2013-12-18 09:13:16 +01:00
(deftest to-string-test []
(let [comp (fn [props]
[:div (str "i am " (:foo props))])]
(is (re-find #"i am foobar"
2014-01-25 13:21:14 +01:00
(as-string [comp {:foo "foobar"}])))))
(deftest data-aria-test []
(is (re-find #"data-foo"
(as-string [:div {:data-foo "x"}])))
2017-10-12 16:14:46 +03:00
(is (re-find #"aria-labelledby"
(as-string [:div {:aria-labelledby "x"}])))
2016-07-14 09:34:03 +02: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 11:18:48 +03: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 13:21:14 +01:00
(as-string [:div {"encType" "x"}]))
"Strings are passed through to React, and have to be camelcase.")
2017-10-13 11:18:48 +03:00
(is (re-find #"enc[tT]ype"
2014-01-25 13:21:14 +01:00
(as-string [:div {:enc-type "x"}]))
"Strings are passed through to React, and have to be camelcase."))
2014-01-28 18:00:15 +01:00
(deftest dynamic-id-class []
(is (re-find #"id=.foo"
(as-string [:div#foo {:class "bar"}])))
(is (re-find #"class=.foo bar"
(as-string [:div.foo {:class "bar"}])))
(is (re-find #"class=.foo bar"
(as-string [:div.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 (re-find #"id=.foo"
(as-string [:div#bar {:id "foo"}]))
"Dynamic id overwrites static"))
2014-02-11 11:36:58 +01:00
(deftest ifn-component []
2014-03-03 17:49:41 +02:00
(defmulti my-div :type)
(defmethod my-div :fooish [child] [:div.foo (:content child)])
(defmethod my-div :barish [child] [:div.bar (:content child)])
2014-02-11 11:36:58 +01:00
(let [comp {:foo [:div "foodiv"]
:bar [:div "bardiv"]}]
(is (re-find #"foodiv"
(as-string [:div [comp :foo]])))
(is (re-find #"bardiv"
2014-03-03 17:49:41 +02:00
(as-string [:div [comp :bar]])))
(is (re-find #"class=.foo"
(as-string [my-div {:type :fooish :content "inner"}])))))
2014-02-11 12:03:44 +01:00
(deftest symbol-string-tag []
(is (re-find #"foobar"
(as-string ['div "foobar"])))
(is (re-find #"foobar"
(as-string ["div" "foobar"])))
(is (re-find #"id=.foo"
(as-string ['div#foo "x"])))
(is (re-find #"id=.foo"
(as-string ["div#foo" "x"])))
(is (re-find #"class=.foo bar"
(as-string ['div.foo {:class "bar"}])))
(is (re-find #"class=.foo bar"
(as-string ["div.foo.bar"])))
(is (re-find #"id=.foo"
(as-string ['div#foo.foo.bar]))))
2014-02-14 11:27:45 +01:00
(deftest partial-test []
2015-07-31 15:13:27 +02:00
(let [p1 (r/partial vector 1 2)]
2014-02-14 11:27:45 +01:00
(is (= (p1 3) [1 2 3]))
2015-07-31 15:13:27 +02:00
(is (= p1 (r/partial vector 1 2)))
2014-02-14 11:27:45 +01:00
(is (ifn? p1))
2015-07-31 15:13:27 +02:00
(is (= (r/partial vector 1 2) p1))
2017-06-27 14:30:13 +03:00
(is (not= p1 (r/partial vector 1 3)))
(is (= (hash p1) (hash (r/partial vector 1 2))))))
2014-09-16 16:31:29 +02:00
(deftest test-null-component
(let [null-comp (fn [do-show]
(when do-show
[:div "div in test-null-component"]))]
(is (not (re-find #"test-null-component"
(as-string [null-comp false]))))
(is (re-find #"test-null-component"
(as-string [null-comp true])))))
2014-11-06 11:34:51 +01:00
(deftest test-static-markup
(is (= "<div>foo</div>"
2018-03-13 21:49:48 +02:00
(rstr [:div "foo"])))
2014-11-06 11:34:51 +01:00
(is (= "<div class=\"bar\"><p>foo</p></div>"
2018-03-13 21:49:48 +02:00
(rstr [:div.bar [:p "foo"]])))
2014-11-06 19:18:56 +01:00
(is (= "<div class=\"bar\"><p>foobar</p></div>"
2018-03-13 21:49:48 +02:00
(rstr [:div.bar {:dangerously-set-inner-HTML
{:__html "<p>foobar</p>"}} ]))))
2014-11-22 09:42:31 +01:00
(deftest test-return-class
(when isClient
2015-07-31 15:13:27 +02:00
(let [ran (r/atom 0)
top-ran (r/atom 0)
2014-11-22 09:42:31 +01:00
comp (fn []
(swap! top-ran inc)
2015-07-31 15:13:27 +02:00
(r/create-class
2014-11-22 09:42:31 +01:00
{:component-did-mount #(swap! ran inc)
:render
(fn [this]
2015-07-31 15:13:27 +02:00
(let [props (r/props this)]
2014-11-22 09:42:31 +01:00
(is (map? props))
2015-07-31 15:13:27 +02:00
(is (= props ((r/argv this) 1)))
(is (= 1 (first (r/children this))))
(is (= 1 (count (r/children this))))
2014-11-22 09:42:31 +01:00
(swap! ran inc)
[:div (str "hi " (:foo props) ".")]))}))
2015-07-31 15:13:27 +02:00
prop (r/atom {:foo "you"})
2014-11-22 09:42:31 +01:00
parent (fn [] [comp @prop 1])]
(with-mounted-component [parent]
(fn [C div]
(swap! ran inc)
(is (found-in #"hi you" div))
(is (= 1 @top-ran))
(is (= 3 @ran))
(swap! prop assoc :foo "me")
2015-07-31 15:13:27 +02:00
(r/flush)
2014-11-22 09:42:31 +01:00
(is (found-in #"hi me" div))
(is (= 1 @top-ran))
(is (= 4 @ran)))))))
(deftest test-return-class-fn
(when isClient
2015-07-31 15:13:27 +02:00
(let [ran (r/atom 0)
top-ran (r/atom 0)
2014-11-22 09:42:31 +01:00
comp (fn []
(swap! top-ran inc)
2015-07-31 15:13:27 +02:00
(r/create-class
2014-11-22 09:42:31 +01:00
{:component-did-mount #(swap! ran inc)
2018-12-31 14:38:15 +02:00
:reagent-render
2014-11-22 09:42:31 +01:00
(fn [p a]
(is (= 1 a))
(swap! ran inc)
[:div (str "hi " (:foo p) ".")])}))
2015-07-31 15:13:27 +02:00
prop (r/atom {:foo "you"})
2014-11-22 09:42:31 +01:00
parent (fn [] [comp @prop 1])]
(with-mounted-component [parent]
(fn [C div]
(swap! ran inc)
(is (found-in #"hi you" div))
(is (= 1 @top-ran))
(is (= 3 @ran))
(swap! prop assoc :foo "me")
2015-07-31 15:13:27 +02:00
(r/flush)
2014-11-22 09:42:31 +01:00
(is (found-in #"hi me" div))
(is (= 1 @top-ran))
(is (= 4 @ran)))))))
2014-12-02 11:43:35 +01:00
(deftest test-create-element
2015-07-31 15:13:27 +02:00
(let [ae r/as-element
ce r/create-element]
2014-12-07 20:26:29 +01:00
(is (= (rstr (ae [:div]))
(rstr (ce "div"))))
2014-12-02 11:43:35 +01:00
(is (= (rstr (ae [:div]))
(rstr (ce "div" nil))))
(is (= (rstr (ae [:div "foo"]))
(rstr (ce "div" nil "foo"))))
(is (= (rstr (ae [:div "foo" "bar"]))
(rstr (ce "div" nil "foo" "bar"))))
(is (= (rstr (ae [:div "foo" "bar" "foobar"]))
(rstr (ce "div" nil "foo" "bar" "foobar"))))
(is (= (rstr (ae [:div.foo "bar"]))
(rstr (ce "div" #js{:className "foo"} "bar"))))
(is (= (rstr (ae [:div [:div "foo"]]))
(rstr (ce "div" nil (ce "div" nil "foo")))))
(is (= (rstr (ae [:div [:div "foo"]]))
(rstr (ce "div" nil (ae [:div "foo"])))))
(is (= (rstr (ae [:div [:div "foo"]]))
(rstr (ae [:div (ce "div" nil "foo")]))))))
2018-12-31 10:37:43 +02: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 13:18:39 +02:00
"div" #js {:className (.. this -props -className)}
(.. this -props -children))))})
2018-12-31 10:37:43 +02:00
(gobj/extend cmp react/Component)
cmp))
2015-02-02 15:19:43 +01:00
(deftest test-adapt-class
2015-07-31 15:13:27 +02:00
(let [d1 (r/adapt-react-class ndiv)
d2 (r/adapt-react-class "div")]
2015-02-02 15:19:43 +01: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-08 00:01:31 +01:00
2015-10-06 14:27:44 +02: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-08 00:01:31 +01:00
(deftest test-reactize-component
2015-07-31 15:13:27 +02:00
(let [ae r/as-element
ce r/create-element
2015-10-06 12:49:47 +02:00
a (atom nil)
2015-10-08 14:14:34 +02:00
c1r (fn reactize [p & args]
2015-10-06 12:49:47 +02:00
(reset! a args)
2015-02-08 00:01:31 +01:00
[:p "p:" (:a p) (:children p)])
2015-07-31 15:13:27 +02:00
c1 (r/reactify-component c1r)]
2015-02-08 00:01:31 +01:00
(is (= (rstr [:p "p:a"])
(rstr (ce c1 #js{:a "a"}))))
2015-10-06 12:49:47 +02:00
(is (= @a nil))
2015-02-08 00:01:31 +01:00
(is (= (rstr [:p "p:"])
(rstr (ce c1 #js{:a nil}))))
(is (= (rstr [:p "p:"])
(rstr (ce c1 nil))))
(is (= (rstr [:p "p:a" [:b "b"]])
(rstr (ce c1 #js{:a "a"}
(ae [:b "b"])))))
2015-10-06 12:49:47 +02:00
(is (= @a nil))
2015-02-08 00:01:31 +01:00
(is (= (rstr [:p "p:a" [:b "b"] [:i "i"]])
(rstr (ce c1 #js{:a "a"}
(ae [:b "b"])
2015-10-06 12:49:47 +02:00
(ae [:i "i"])))))
(is (= @a nil))))
2015-02-08 12:24:52 +01:00
(deftest test-keys
2015-07-31 15:13:27 +02:00
(let [a nil ;; (r/atom "a")
2015-02-08 12:24:52 +01:00
c (fn key-tester []
[:div
(for [i (range 3)]
^{:key i} [:p i (some-> a deref)])
(for [i (range 3)]
2019-10-02 15:37:56 +03: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 20:26:16 +02:00
(deftest test-extended-syntax
(is (= (rstr [:p>b "foo"])
"<p><b>foo</b></p>"))
(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 14:59:13 +02:00
2017-11-28 18:51:37 +02: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 18:12:48 +02:00
(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" nil "b" false "c" nil]}])
(rstr [:p {:class "a b c"}])))
(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"}]))))
2018-05-04 16:10:41 +03:00
(deftest class-different-types
(testing "named values are supported"
(is (= (rstr [:p {:class :a}])
(rstr [:p {:class "a"}])))
(is (= (rstr [:p.a {:class :b}])
(rstr [:p {:class "a b"}])))
(is (= (rstr [:p.a {:class 'b}])
(rstr [:p {:class "a 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 "falsey values are filtered from collections"
2018-04-27 23:19:59 +03:00
(is (= (rstr [:p {:class [:a :b false nil]}])
(rstr [:p {:class "a b"}])))) )
2015-08-20 14:59:13 +02: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 09:34:03 +02:00
[:p "" (swap! v update-in [:v1] inc)])
2015-08-20 14:59:13 +02:00
c2 (fn []
(swap! comps assoc :c2 (r/current-component))
2016-07-14 09:34:03 +02:00
[:div "" (swap! v update-in [:v2] inc)
2016-04-30 14:24:18 +02: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 09:34:03 +02:00
[:div "" (reset! spy @(r/track t1))])]
2015-08-20 14:59:13 +02:00
(with-mounted-component [c2]
(fn [c div]
(is (= @v {:v1 1 :v2 1}))
(r/force-update (:c2 @comps))
(is (= @v {:v1 1 :v2 2}))
(r/force-update (:c1 @comps))
(is (= @v {:v1 2 :v2 2}))
(r/force-update (:c2 @comps) true)
2016-04-30 14:24:18 +02:00
(is (= @v {:v1 3 :v2 3}))))
(with-mounted-component [c3]
(fn [c]
(is (= @spy 0))
(swap! state inc)
(is (= @spy 0))
(r/force-update (:c3 @comps))
(is (= @spy 1))))))
2015-08-30 18:29:27 +02: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 08:18:45 +02:00
(deftest test-sorted-map-key
(let [c1 (fn [map]
[:div (map 1)])
c2 (fn []
[c1 (sorted-map 1 "foo" 2 "bar")])]
(is (= (rstr [c2]) "<div>foo</div>"))))
2015-09-23 10:39:49 +02:00
(deftest basic-with-let
2015-09-25 11:48:11 +02:00
(when isClient
(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 15:55:57 +02:00
(deftest with-let-destroy-only
2015-09-25 11:48:11 +02:00
(when isClient
(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 15:55:57 +02:00
2015-09-27 00:07:19 +02:00
(deftest with-let-arg
(when isClient
(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 (= @a "foo"))
(reset! s "bar")
(r/flush)
(is (= @a "bar")))))))
2015-09-23 15:55:57 +02:00
(deftest with-let-non-reactive
(let [n1 (atom 0)
n2 (atom 0)
n3 (atom 0)
c (fn []
2015-09-24 10:19:30 +02:00
(r/with-let [a (swap! n1 inc)]
2015-09-23 15:55:57 +02: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 13:44:10 +02: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 13:30:22 +02:00
(this-as c (is (= c (r/current-component))))
(add-args :render args)
2016-07-14 09:34:03 +02:00
[:div "" (first args)])
2015-10-11 13:30:22 +02:00
render2 (fn [& args]
2015-09-25 13:44:10 +02:00
(add-args :render args)
2016-07-14 09:34:03 +02:00
[:div "" (first args)])
2015-09-25 13:44:10 +02:00
ls {:get-initial-state
(fn [& args]
(reset! t (first args))
(add-args :initial-state args)
{:foo "bar"})
2019-08-13 09:14:40 +03:00
:UNSAFE_component-will-mount
2015-10-11 13:30:22 +02:00
(fn [& args]
(this-as c (is (= c (first args))))
(add-args :will-mount args))
2015-09-25 13:44:10 +02:00
:component-did-mount
2015-10-11 13:30:22 +02:00
(fn [& args]
(this-as c (is (= c (first args))))
(add-args :did-mount args))
2015-09-25 13:44:10 +02:00
:should-component-update
2015-10-11 13:30:22 +02:00
(fn [& args]
(this-as c (is (= c (first args))))
(add-args :should-update args) true)
2019-08-13 09:14:40 +03:00
:UNSAFE_component-will-receive-props
2015-10-11 13:30:22 +02:00
(fn [& args]
(this-as c (is (= c (first args))))
(add-args :will-receive args))
2019-08-13 09:14:40 +03:00
:UNSAFE_component-will-update
2015-10-11 13:30:22 +02:00
(fn [& args]
(this-as c (is (= c (first args))))
(add-args :will-update args))
2015-09-25 13:44:10 +02:00
:component-did-update
2015-10-11 13:30:22 +02:00
(fn [& args]
(this-as c (is (= c (first args))))
(add-args :did-update args))
2015-09-25 13:44:10 +02:00
:component-will-unmount
2015-10-11 13:30:22 +02:00
(fn [& args]
(this-as c (is (= c (first args))))
(add-args :will-unmount args))}
2015-09-25 13:44:10 +02: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 17:18:37 +02:00
cnative (fn []
(into [:> @comp] @arg))
2015-09-25 13:44:10 +02:00
check (fn []
(is (= (:initial-state @res)
{:at 1 :args [@t]}))
(is (= (:will-mount @res)
{:at 2 :args [@t]}))
(is (= (:render @res)
{:at 3 :args ["a" "b"]}))
(is (= (:did-mount @res)
{:at 4 :args [@t]}))
(reset! arg ["a" "c"])
(r/flush)
2015-10-07 19:58:00 +02:00
(is (= (:will-receive @res)
2015-10-12 17:18:37 +02:00
{:at 5 :args [@t [@comp "a" "c"]]}))
2015-09-25 13:44:10 +02:00
(is (= (:should-update @res)
2015-10-07 19:58:00 +02:00
{:at 6 :args [@t [@comp "a" "b"] [@comp "a" "c"]]}))
2015-09-25 13:44:10 +02:00
(is (= (:will-update @res)
2019-08-14 10:35:59 +03:00
{:at 7 :args [@t [@comp "a" "c"] {:foo "bar"}]}))
2015-09-25 13:44:10 +02:00
(is (= (:render @res)
2015-10-07 19:58:00 +02:00
{:at 8 :args ["a" "c"]}))
2015-09-25 13:44:10 +02:00
(is (= (:did-update @res)
2019-08-14 10:35:59 +03:00
{:at 9 :args [@t [@comp "a" "b"] {:foo "bar"} nil]})))]
2015-09-25 13:44:10 +02:00
(when isClient
(with-mounted-component [c2] check)
(is (= (:will-unmount @res)
2015-10-07 19:58:00 +02:00
{:at 10 :args [@t]}))
2015-09-25 13:44:10 +02:00
2015-10-11 13:30:22 +02:00
(reset! comp (with-meta render2 ls))
2015-09-25 13:44:10 +02:00
(reset! arg defarg)
(reset! n1 0)
(with-mounted-component [c2] check)
(is (= (:will-unmount @res)
2015-10-07 19:58:00 +02:00
{:at 10 :args [@t]})))))
2015-10-07 19:50:29 +02:00
2015-10-12 17:18:37 +02: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
(is (= @newprops) (first args))
(is (= @newprops) (r/props c)))
(is (= c (r/current-component)))
(is (= (first args) (r/props c)))
(add-args :render
{:children (r/children c)})
2016-07-14 09:34:03 +02:00
[:div "" (first args)]))
2015-10-12 17:18:37 +02: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 09:14:40 +03:00
:UNSAFE_component-will-mount
2015-10-12 17:18:37 +02: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 09:14:40 +03:00
:UNSAFE_component-will-receive-props
2015-10-12 17:18:37 +02: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 09:14:40 +03:00
:UNSAFE_component-will-update
2015-10-12 17:18:37 +02: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 []
(is (= (:initial-state @res)
{:at 1 :args [@t]}))
(is (= (:will-mount @res)
{:at 2 :args [@t]}))
(is (= (:render @res)
{:at 3 :args [[:children ["a" "b"]]]}))
(is (= (:did-mount @res)
{:at 4 :args [@t]}))
(reset! arg [{:f "oo"} "a" "c"])
(r/flush)
(is (= (:will-receive @res)
{:at 5 :args [{:foo "bar"} "a" "b"]}))
(let [a (:should-update @res)
{at :at
[this oldv newv] :args} a]
(is (= at 6))
(is (= (count (:args a)) 3))
2015-10-28 12:43:35 +01:00
(is (= (js->clj oldv) (js->clj [@comp @oldprops])))
(is (= newv [@comp @newprops])))
2015-10-12 17:18:37 +02:00
(let [a (:will-update @res)
{at :at
[this newv] :args} a]
(is (= at 7))
2015-10-28 12:43:35 +01:00
(is (= newv [@comp @newprops])))
2015-10-12 17:18:37 +02:00
(is (= (:render @res)
{:at 8 :args [[:children ["a" "c"]]]}))
(let [a (:did-update @res)
{at :at
[this oldv] :args} a]
(is (= at 9))
2015-10-28 12:43:35 +01:00
(is (= oldv [@comp @oldprops]))))]
2015-10-12 17:18:37 +02:00
(when isClient
(with-mounted-component [cnative] check)
(is (= (:will-unmount @res)
{:at 10 :args [@t]})))))
2015-10-07 19:50:29 +02:00
(defn foo []
[:div])
2017-10-13 19:57:46 +03:00
(defn wrap-capture-window-error [f]
2018-04-03 10:33:01 +03: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 19:57:46 +03:00
2015-10-07 19:50:29 +02: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 18:47:45 +03: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 18:58:00 +03:00
;; NOTE: browser-npm uses production cjs bundle for now which only shows
;; the minified error
2018-05-04 18:47:45 +03:00
(debug/track-warnings
(wrap-capture-console-error
#(is (thrown-with-msg?
2018-05-04 18:58:00 +03:00
:default #"(Element type is invalid:|Minified React error)"
2018-05-04 18:47:45 +03:00
(rstr [:> [:div]])))))
2015-10-07 19:50:29 +02:00
(is (thrown-with-msg?
:default #"Invalid tag: 'p.'"
2015-10-07 20:38:19 +02:00
(rstr [:p.])))
2016-05-27 13:14:35 +02: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 10:37:43 +02:00
nat (let [cmp (fn [])]
(gobj/extend
(.-prototype cmp)
(.-prototype react/Component)
#js {:render (fn [])})
(gobj/extend cmp react/Component)
cmp)
2016-05-27 13:14:35 +02: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 19:57:46 +03:00
(wrap-capture-window-error
(wrap-capture-console-error
#(is (thrown-with-msg?
:default (re "Invalid tag: 'div.' \\(" stack2 "\\)")
(rend [comp2 [:div. "foo"]]))))))]
(is (= (last (:error e))
(str "Error rendering component (" stack2 ")"))))
2016-05-27 13:14:35 +02:00
(let [e (debug/track-warnings
2017-10-13 19:57:46 +03:00
(wrap-capture-window-error
(wrap-capture-console-error
#(is (thrown-with-msg?
:default (re "Invalid tag: 'div.' \\(" stack1 "\\)")
(rend [comp1 [:div. "foo"]]))))))]
(is (= (last (:error e))
(str "Error rendering component (" stack1 ")"))))
2016-05-27 13:14:35 +02: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 14:14:34 +02:00
2017-10-13 19:20:36 +03:00
(deftest test-error-boundary
2018-04-03 10:33:01 +03:00
(let [error (r/atom nil)
error-boundary (fn error-boundary [comp]
(r/create-class
2019-10-02 15:37:21 +03:00
{:component-did-catch (fn [this e info])
:get-derived-state-from-error (fn [e]
(reset! error e)
2019-10-02 12:59:00 +03:00
#js {})
2018-04-03 10:33:01 +03:00
:reagent-render (fn [comp]
(if @error
[:div "Something went wrong."]
comp))}))
comp1 (fn comp1 []
2019-10-02 15:37:21 +03:00
(throw (js/Error. "Test error")))]
2018-04-03 10:33:01 +03: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 11:00:46 +03:00
(is (= "Test error" (.-message @error)))
(is (re-find #"Something went wrong\." (.-innerHTML div))))))))))
2017-10-13 19:20:36 +03:00
2015-10-08 14:14:34 +02: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]
(is (= (.-innerHTML @ref) "foobar"))
(is (= (.-innerHTML @node) "foobar"))
(is (identical? @ref @node))))))
2015-10-22 13:31:02 +02:00
(deftest test-empty-input
(is (= "<div><input/></div>"
(rstr [:div [:input]]))))
2016-04-29 09:13:55 +02: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 23:09:43 +02: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]
(is (nil? (r/after-render
(fn []
2017-11-18 20:04:52 +02:00
(is (= "DIV" (.-tagName @node)))
2016-05-31 23:09:43 +02:00
(swap! spy inc)))))
(is (= old @spy))
(is (= @exp @val))
[:div {:ref #(reset! node %)} @state]))]
(with-mounted-component [comp]
(fn [c div]
(is (= @spy 1))
(swap! state inc)
(is (= @spy 1))
(is (nil? (r/next-tick #(swap! val inc))))
(reset! exp 1)
(is (= @val 0))
(is (nil? (r/flush)))
(is (= @val 1))
(is (= @spy 2))
(is (nil? (r/force-update c)))
(is (= @spy 3))
(is (nil? (r/next-tick #(reset! spy 0))))
(is (= @spy 3))
(r/flush)
(is (= @spy 0))))
(is (= @node nil))))
2017-03-11 01:56:39 +02:00
(deftest style-property-names-are-camel-cased
2017-10-13 11:18:32 +03:00
(is (re-find #"<div style=\"text-align:center(;?)\">foo</div>"
2018-03-13 21:49:48 +02:00
(rstr [:div {:style {:text-align "center"}} "foo"]))))
2017-11-08 20:29:06 +02:00
(deftest custom-element-class-prop
(is (re-find #"<custom-element class=\"foobar\">foo</custom-element>"
2018-03-13 21:49:48 +02:00
(rstr [:custom-element {:class "foobar"} "foo"])))
2017-11-08 20:29:06 +02:00
(is (re-find #"<custom-element class=\"foobar\">foo</custom-element>"
2018-03-13 21:49:48 +02:00
(rstr [:custom-element.foobar "foo"]))))
2017-11-23 08:45:43 +02:00
(deftest html-entities
2017-11-28 17:13:10 +02:00
(testing "entity numbers can be unescaped always"
(is (= "<i> </i>"
2018-03-13 21:49:48 +02:00
(rstr [:i (gstr/unescapeEntities " ")]))))
2017-11-28 17:13:10 +02:00
(when r/is-client
(testing "When DOM is available, all named entities can be unescaped"
(is (= "<i> </i>"
2018-03-13 21:49:48 +02:00
(rstr [:i (gstr/unescapeEntities " ")]))))))
2017-11-28 15:41:30 +02: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 13:53:48 +02:00
(deftest test-fragments
2018-04-03 10:33:01 +03:00
(testing "Fragment as array"
2018-04-03 10:42:25 +03:00
(let [comp (fn comp1 []
2018-04-03 10:33:01 +03: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 10:42:25 +03:00
(let [comp (fn comp2 []
2018-04-03 10:33:01 +03: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 10:42:25 +03:00
;; But wont fail the test
(let [children (fn comp4 []
[:<>
[:div "foo"]])
comp (fn comp3 []
2018-04-03 10:33:01 +03:00
[:div
(list
[:<>
{:key 1}
[:div "hello"]
[:div "world"]]
^{:key 2}
2018-11-14 22:31:50 +02: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 10:33:01 +03:00
(as-string [comp]))))))
2018-05-04 17:49:56 +03: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 18:14:48 +03:00
Consumer #js {}
(fn [v]
(r/as-element [:div "Context: " v])))))))
2018-05-04 17:49:56 +03: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 02:21:05 +02: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 19:18:51 +03: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])]
2018-05-04 19:25:34 +03:00
(when (and isClient (dev?))
2018-05-04 19:18:51 +03: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 10:35:59 +03:00
(deftest get-derived-state-from-props-test
(when isClient
(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 10:53:05 +03:00
(r/as-element [:p "Value " (gobj/get (.-state this) "v")]))})
2019-08-14 10:35:59 +03:00
component (fn []
[pure-component {:value @prop}])]
(with-mounted-component [component]
(fn [c div]
(is (found-in #"Value foo" div))
(swap! prop inc)
(r/flush)
(is (found-in #"Value foo foo" div)))))))
(deftest get-derived-state-from-error-test
(when isClient
(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 10:25:43 +03:00
:render (fn [^js/React.Component this]
2019-08-14 10:35:59 +03: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]
(is (found-in #"Ok" div))
(swap! prop inc)
(r/flush)
(is (found-in #"Error" div)))))))))
(deftest get-snapshot-before-update-test
(when isClient
(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 (found-in #"foo" div))
(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 21:05:31 +02:00
(deftest issue-462-test
(when isClient
(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)))))))