2015-02-10 13:18:56 +00:00
(ns reagenttest.testreagent
2015-01-31 22:09:42 +00:00
(:require [cljs.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]]
2015-10-07 18:38:19 +00:00
[reagent.debug :as debug :refer-macros [dbg println log 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]
2017-11-08 18:08:27 +00:00
[reagent.impl.util :as util]
2017-11-23 06:45:43 +00:00
[reagenttest.utils :as u :refer [with-mounted-component found-in]]
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
2016-05-01 11:45:37 +00:00
(def tests-done (atom {}))
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
2013-12-16 22:19:36 +00:00
(defn running [] (rv/running))
2015-07-31 13:13:27 +00:00
(def isClient r/is-client)
2013-12-16 22:19:36 +00:00
2015-07-31 13:13:27 +00:00
(def rflush r/flush)
2014-01-27 15:17:37 +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
2016-05-01 11:45:37 +00:00
(when (and isClient
(not (:really-simple-test @tests-done)))
(swap! tests-done assoc :really-simple-test true)
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)
2014-12-09 11:58:30 +00:00
(is (found-in #"div in really-simple" 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
2013-12-18 11:14:57 +00:00
(when isClient
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)
2014-02-10 08:31:25 +00:00
(is (found-in #"hi you" div))))
(is (= 3 @ran)))))
2013-12-16 22:19:36 +00:00
(deftest test-state-change
2013-12-18 11:14:57 +00:00
(when isClient
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)
2014-02-15 10:48:12 +00:00
(is (found-in #"hi initial" 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
2014-02-15 10:48:12 +00:00
(rflush)
2013-12-18 11:14:57 +00:00
(is (found-in #"hi there" div))
2015-07-31 13:13:27 +00:00
(r/set-state @self {:foo "you"})
2014-02-15 10:48:12 +00:00
(rflush)
2014-12-09 11:58:30 +00:00
(is (found-in #"hi you" div))))
2013-12-18 11:14:57 +00:00
(is (= 4 @ran)))))
2013-12-16 22:19:36 +00:00
(deftest test-ratom-change
2013-12-18 11:14:57 +00:00
(when isClient
2015-07-31 13:13:27 +00:00
(let [ran (r/atom 0)
2013-12-18 11:14:57 +00:00
runs (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)
2014-01-27 12:37:59 +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)
2013-12-18 11:14:57 +00:00
(is (not= runs (running)))
(is (found-in #"val 0" 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)
2013-12-18 11:14:57 +00:00
(is (found-in #"val 1" 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))
2013-12-18 11:14:57 +00:00
(is (found-in #"val 1" div))
2015-08-31 09:36:42 +00:00
(is (= 2 @ran) "did not run")))
2013-12-18 11:14:57 +00:00
(is (= runs (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 []
(when isClient
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)
2014-02-14 10:27:45 +00:00
(is (= @v1 val))
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]
(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 22:19:36 +00:00
2013-12-18 08:13:16 +00:00
(deftest init-state-test
2013-12-18 11:14:57 +00:00
(when isClient
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)
(is (found-in #"this is foobar" div))))
(is (= 2 @ran)))))
2013-12-18 08:13:16 +00:00
2014-01-30 09:34:41 +00:00
(deftest shoud-update-test
2016-05-01 11:45:37 +00:00
(when (and isClient
(not (:should-update-test @tests-done)))
(swap! tests-done assoc :should-update-test true)
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]
(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 13:13:27 +00:00
(do (reset! child-props {:on-change (r/partial f)})
2014-01-30 09:34:41 +00:00
(rflush))
(is (= @child-ran 6))
2015-07-31 13:13:27 +00:00
(do (reset! child-props {:on-change (r/partial f)})
2014-01-30 09:34:41 +00:00
(rflush))
(is (= @child-ran 6))
2015-07-31 13:13:27 +00:00
(do (reset! child-props {:on-change (r/partial f1)})
2014-01-30 09:34:41 +00:00
(rflush))
2014-12-09 11:58:30 +00:00
(is (= @child-ran 7))
2015-07-31 13:13:27 +00:00
(r/force-update-all)
2014-12-09 11:58:30 +00:00
(is (= @child-ran 8)))))))
2014-01-30 09:34:41 +00:00
2014-02-03 12:58:31 +00:00
(deftest dirty-test
(when isClient
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)
(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 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))])]
(is (re-find #"i am foobar"
2014-01-25 12:21:14 +00:00
(as-string [comp {:foo "foobar"}])))))
(deftest data-aria-test []
(is (re-find #"data-foo"
(as-string [:div {:data-foo "x"}])))
2017-10-12 13:14:46 +00:00
(is (re-find #"aria-labelledby"
(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"}])))
(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 10:36:58 +00:00
(deftest ifn-component []
2014-03-03 15:49:41 +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-02-11 10:36:58 +00: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 15:49:41 +00:00
(as-string [:div [comp :bar]])))
(is (re-find #"class=.foo"
(as-string [my-div {:type :fooish :content "inner"}])))))
2014-02-11 11:03:44 +00: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 10:27:45 +00:00
(deftest partial-test []
2015-07-31 13:13:27 +00:00
(let [p1 (r/partial vector 1 2)]
2014-02-14 10:27:45 +00:00
(is (= (p1 3) [1 2 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"]))]
(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 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
(when isClient
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)
(is (found-in #"hi you" div))
(is (= 1 @top-ran))
(is (= 3 @ran))
(swap! prop assoc :foo "me")
2015-07-31 13:13:27 +00:00
(r/flush)
2014-11-22 08:42:31 +00:00
(is (found-in #"hi me" div))
(is (= 1 @top-ran))
(is (= 4 @ran)))))))
(deftest test-return-class-fn
(when isClient
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)
(is (found-in #"hi you" div))
(is (= 1 @top-ran))
(is (= 3 @ran))
(swap! prop assoc :foo "me")
2015-07-31 13:13:27 +00:00
(r/flush)
2014-11-22 08:42:31 +00:00
(is (found-in #"hi me" div))
(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]
2014-12-07 19:26:29 +00:00
(is (= (rstr (ae [:div]))
(rstr (ce "div"))))
2014-12-02 10:43:35 +00: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 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)]
2015-02-07 23:01:31 +00:00
(is (= (rstr [:p "p:a"])
(rstr (ce c1 #js{:a "a"}))))
2015-10-06 10:49:47 +00:00
(is (= @a nil))
2015-02-07 23:01:31 +00: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 10:49:47 +00:00
(is (= @a nil))
2015-02-07 23:01:31 +00:00
(is (= (rstr [:p "p:a" [:b "b"] [:i "i"]])
(rstr (ce c1 #js{:a "a"}
(ae [:b "b"])
2015-10-06 10:49:47 +00:00
(ae [:i "i"])))))
(is (= @a nil))))
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
(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 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
(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 13:10:41 +00: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 20:19:59 +00:00
(is (= (rstr [:p {:class [:a :b false nil]}])
(rstr [:p {:class "a b"}])))) )
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]
(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 12:24:18 +00: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 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")])]
(is (= (rstr [c2]) "<div>foo</div>"))))
2015-09-23 08:39:49 +00:00
(deftest basic-with-let
2015-09-25 09:48:11 +00: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 13:55:57 +00:00
(deftest with-let-destroy-only
2015-09-25 09:48:11 +00: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 13:55:57 +00:00
2015-09-26 22:07:19 +00: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 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 []
(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 17:58:00 +00:00
(is (= (:will-receive @res)
2015-10-12 15:18:37 +00:00
{:at 5 :args [@t [@comp "a" "c"]]}))
2015-09-25 11:44:10 +00:00
(is (= (:should-update @res)
2015-10-07 17:58:00 +00:00
{:at 6 :args [@t [@comp "a" "b"] [@comp "a" "c"]]}))
2015-09-25 11:44:10 +00:00
(is (= (:will-update @res)
2019-08-14 07:35:59 +00:00
{:at 7 :args [@t [@comp "a" "c"] {:foo "bar"}]}))
2015-09-25 11:44:10 +00:00
(is (= (:render @res)
2015-10-07 17:58:00 +00:00
{:at 8 :args ["a" "c"]}))
2015-09-25 11:44:10 +00:00
(is (= (:did-update @res)
2019-08-14 07:35:59 +00:00
{:at 9 :args [@t [@comp "a" "b"] {:foo "bar"} nil]})))]
2015-09-25 11:44:10 +00:00
(when isClient
(with-mounted-component [c2] check)
(is (= (:will-unmount @res)
2015-10-07 17:58:00 +00:00
{:at 10 :args [@t]}))
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)
(is (= (:will-unmount @res)
2015-10-07 17:58:00 +00:00
{:at 10 :args [@t]})))))
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
(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 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 []
(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 11:43:35 +00:00
(is (= (js->clj oldv) (js->clj [@comp @oldprops])))
(is (= newv [@comp @newprops])))
2015-10-12 15:18:37 +00:00
(let [a (:will-update @res)
{at :at
[this newv] :args} a]
(is (= at 7))
2015-10-28 11:43:35 +00:00
(is (= newv [@comp @newprops])))
2015-10-12 15:18:37 +00: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 11:43:35 +00:00
(is (= oldv [@comp @oldprops]))))]
2015-10-12 15:18:37 +00:00
(when isClient
(with-mounted-component [cnative] check)
(is (= (:will-unmount @res)
{:at 10 :args [@t]})))))
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"]]))))))]
(is (= (last (:error e))
(str "Error rendering component (" stack2 ")"))))
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"]]))))))]
(is (= (last (:error e))
(str "Error rendering component (" stack1 ")"))))
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]
(is (= (.-innerHTML @ref) "foobar"))
(is (= (.-innerHTML @node) "foobar"))
(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]
(is (nil? (r/after-render
(fn []
2017-11-18 18:04:52 +00:00
(is (= "DIV" (.-tagName @node)))
2016-05-31 21:09:43 +00: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-10 23:56:39 +00:00
(deftest style-property-names-are-camel-cased
2017-10-13 08:18:32 +00:00
(is (re-find #"<div style=\"text-align:center(;?)\">foo</div>"
2018-03-13 19:49:48 +00:00
(rstr [:div {:style {:text-align "center"}} "foo"]))))
2017-11-08 18:29:06 +00:00
(deftest custom-element-class-prop
(is (re-find #"<custom-element class=\"foobar\">foo</custom-element>"
2018-03-13 19:49:48 +00:00
(rstr [:custom-element {:class "foobar"} "foo"])))
2017-11-08 18:29:06 +00:00
(is (re-find #"<custom-element class=\"foobar\">foo</custom-element>"
2018-03-13 19:49:48 +00:00
(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]
(r/as-element [:div "Context: " v]))]])))))
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])]
2018-05-04 16:25:34 +00:00
(when (and isClient (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
(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 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]
(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 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]
(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))))))