From 421bb350aafdc47e1d08196df266f40a2c0fdd87 Mon Sep 17 00:00:00 2001 From: Juho Teperi Date: Sat, 23 Nov 2024 13:40:04 +0200 Subject: [PATCH 1/4] Use new test utils (from reagent-next brach) with React 17 mode --- .clj-kondo/config.edn | 3 +- .clj-kondo/funcool/promesa/config.edn | 9 + project.clj | 1 + shadow-cljs.edn | 3 +- src/reagent/impl/component.cljs | 2 +- test/reagenttest/testreagent.cljs | 691 ++++++++++++-------------- test/reagenttest/testwrap.cljs | 171 +++---- test/reagenttest/utils.clj | 20 +- test/reagenttest/utils.cljs | 107 +++- 9 files changed, 504 insertions(+), 503 deletions(-) create mode 100644 .clj-kondo/funcool/promesa/config.edn diff --git a/.clj-kondo/config.edn b/.clj-kondo/config.edn index 4de750ff..97cf4dcc 100644 --- a/.clj-kondo/config.edn +++ b/.clj-kondo/config.edn @@ -1,5 +1,6 @@ {:lint-as {reagent.core/with-let clojure.core/let - reagenttest.utils/deftest clojure.test/deftest} + reagenttest.utils/deftest clojure.test/deftest + reagenttest.utils/with-render clojure.core/let} :linters {:unused-binding {:level :off} :missing-else-branch {:level :off} :unused-referred-var {:exclude {cljs.test [deftest testing is]}} diff --git a/.clj-kondo/funcool/promesa/config.edn b/.clj-kondo/funcool/promesa/config.edn new file mode 100644 index 00000000..1844f77c --- /dev/null +++ b/.clj-kondo/funcool/promesa/config.edn @@ -0,0 +1,9 @@ +{:lint-as {promesa.core/-> clojure.core/-> + promesa.core/->> clojure.core/->> + promesa.core/as-> clojure.core/as-> + promesa.core/let clojure.core/let + promesa.core/plet clojure.core/let + promesa.core/loop clojure.core/loop + promesa.core/recur clojure.core/recur + promesa.core/with-redefs clojure.core/with-redefs + promesa.core/doseq clojure.core/doseq}} diff --git a/project.clj b/project.clj index 1ef9d8a3..ff8784b7 100644 --- a/project.clj +++ b/project.clj @@ -23,6 +23,7 @@ [figwheel-sidecar "0.5.20"] [doo "0.1.11"] [cljsjs/prop-types "15.8.1-0"] + [funcool/promesa "11.0.678"] [cljsjs/react "18.3.1-1"] [cljsjs/react-dom "18.3.1-1"] diff --git a/shadow-cljs.edn b/shadow-cljs.edn index e0645c10..4d0002a3 100644 --- a/shadow-cljs.edn +++ b/shadow-cljs.edn @@ -14,4 +14,5 @@ :source-map true :compiler-options {:infer-externs :auto} :ns-regexp "(reagenttest\\.test.*|reagent\\..*-test)"}} - :dependencies [[doo "0.1.11"]]} + :dependencies [[doo "0.1.11"] + [funcool/promesa "11.0.678"]]} diff --git a/src/reagent/impl/component.cljs b/src/reagent/impl/component.cljs index f1a3770e..f54b17bc 100644 --- a/src/reagent/impl/component.cljs +++ b/src/reagent/impl/component.cljs @@ -46,7 +46,7 @@ (defn ^boolean reagent-class? [c] (and (fn? c) - (some? (some-> c (.-prototype) (.-reagentRender))))) + (some? (some-> c (.-prototype) ^clj (.-reagentRender))))) (defn ^boolean react-class? [c] (and (fn? c) diff --git a/test/reagenttest/testreagent.cljs b/test/reagenttest/testreagent.cljs index f5908f73..2b998f42 100644 --- a/test/reagenttest/testreagent.cljs +++ b/test/reagenttest/testreagent.cljs @@ -1,19 +1,19 @@ (ns reagenttest.testreagent - (:require [clojure.test :as t :refer-macros [is deftest testing]] + (:require [clojure.string :as string] + [clojure.test :as t :refer-macros [is deftest testing]] + [goog.object :as gobj] + [goog.string :as gstr] + [promesa.core :as p] [react :as react] - [reagent.ratom :as rv :refer [reaction]] - [reagent.debug :as debug :refer [dev?]] [reagent.core :as r] + [reagent.debug :as debug :refer [dev?]] [reagent.dom :as rdom] [reagent.dom.client :as rdomc] [reagent.dom.server :as server] [reagent.impl.component :as comp] [reagent.impl.template :as tmpl] - [reagenttest.utils :as u :refer [with-mounted-component as-string]] - [clojure.string :as string] - [goog.string :as gstr] - [goog.object :as gobj] - [prop-types :as prop-types])) + [reagent.ratom :as rv :refer [reaction]] + [reagenttest.utils :as u :refer [as-string]])) (t/use-fixtures :once {:before (fn [] @@ -26,8 +26,8 @@ really-simple (fn [] (swap! ran inc) [:div "div in really-simple"])] - (with-mounted-component [really-simple nil nil] - (fn [c div] + (u/async + (u/with-render [div [really-simple nil nil]] (is (= 1 @ran)) (is (= "div in really-simple" (.-innerText div))))))) @@ -44,8 +44,9 @@ (is (= 1 (count (r/children this)))) (swap! ran inc) [:div (str "hi " (:foo props) ".")]))})] - (with-mounted-component [comp {:foo "you"} 1] - (fn [C div] + (u/async + (u/with-render [div [comp {:foo "you"} 1]] + ;; FIXME: Why? (swap! ran inc) (is (= "hi you." (.-innerText div))) (is (= 3 @ran)))))) @@ -61,21 +62,18 @@ (reset! self this) (swap! ran inc) [:div (str "hi " (:foo (r/state this)))]))})] - (with-mounted-component [comp] - (fn [C div] + (u/async + (u/with-render [div [comp]] (swap! ran inc) (is (= "hi initial" (.-innerText div))) - (r/replace-state @self {:foo "there"}) - (r/state @self) + (u/act (r/replace-state @self {:foo "there"})) - (r/flush) (is (= "hi there" (.-innerText div))) - (r/set-state @self {:foo "you"}) - (r/flush) - (is (= "hi you" (.-innerText div))))) - (is (= 4 @ran)))) + (u/act (r/set-state @self {:foo "you"})) + (is (= "hi you" (.-innerText div)))) + (is (= 4 @ran))))) (u/deftest ^:dom test-ratom-change (let [compiler u/*test-compiler* @@ -83,51 +81,42 @@ runs (rv/running) val (r/atom 0) secval (r/atom 0) - v1-ran (atom 0) - v1 (reaction (swap! v1-ran inc) @val) + reaction-ran (atom 0) + v1 (reaction (swap! reaction-ran inc) @val) comp (fn [] (swap! ran inc) [:div (str "val " @v1 " " @val " " @secval)])] - (t/async done - (u/with-mounted-component-async [comp] - (fn [] - (r/next-tick - (fn [] - (r/next-tick - (fn [] - (is (= runs (rv/running))) - (is (= 2 @ran)) - (done)))))) - compiler - (fn [C div done] - (r/flush) - (is (not= runs (rv/running))) - (is (= "val 0 0 0" (.-innerText div))) - (is (= 1 @ran)) + (u/async + (u/with-render [div [comp]] + {:compiler compiler} + + (is (not= runs (rv/running))) + (is (= "val 0 0 0" (.-innerText div))) + (is (= 1 @ran)) + (u/act (reset! secval 1) (reset! secval 0) (reset! val 1) (reset! val 2) - (reset! val 1) - (is (= 1 @ran)) - (is (= 1 @v1-ran)) - (r/flush) - (is (= "val 1 1 0" (.-innerText div))) - (is (= 2 @ran) "ran once more") - (is (= 2 @v1-ran)) - - ;; should not be rendered - (reset! val 1) - (is (= 2 @v1-ran)) - (r/flush) - (is (= 2 @v1-ran)) - (is (= "val 1 1 0" (.-innerText div))) - (is (= 2 @ran) "did not run") - (done)))))) + (reset! val 1)) + + (is (= "val 1 1 0" (.-innerText div))) + (is (= 2 @ran) "ran once more") + (is (= 2 @reaction-ran)) + + ;; should not be rendered + (u/act (reset! val 1)) + (is (= 2 @reaction-ran)) + (is (= 2 @reaction-ran)) + (is (= "val 1 1 0" (.-innerText div))) + (is (= 2 @ran) "did not run")) + + (is (= runs (rv/running))) + (is (= 2 @ran))))) (u/deftest ^:dom batched-update-test [] - (let [ran (r/atom 0) + (let [ran (atom 0) v1 (r/atom 0) v2 (r/atom 0) c2 (fn [{val :val}] @@ -138,16 +127,14 @@ (swap! ran inc) [:div @v1 [c2 {:val @v1}]])] - (with-mounted-component [c1] - (fn [c div] - (r/flush) - (is (= 2 @ran)) - (swap! v2 inc) + (u/async + (u/with-render [div [c1]] (is (= 2 @ran)) - (r/flush) + (u/act (swap! v2 inc)) + ;; FIXME: ??? + ;; (is (= 2 @ran)) (is (= 3 @ran)) - (swap! v1 inc) - (r/flush) + (u/act (swap! v1 inc)) (is (= 5 @ran)) ;; TODO: Failing on optimized build ; (swap! v2 inc) @@ -170,15 +157,15 @@ (fn [] [:div (str "this is " (:foo (r/state this)))])))] - (with-mounted-component [really-simple nil nil] - (fn [c div] + (u/async + (u/with-render [div [really-simple nil nil]] (swap! ran inc) - (is (= "this is foobar" (.-innerText div))))) - (is (= 2 @ran)))) + (is (= "this is foobar" (.-innerText div)))) + (is (= 2 @ran))))) (u/deftest ^:dom should-update-test - (let [parent-ran (r/atom 0) - child-ran (r/atom 0) + (let [parent-ran (atom 0) + child-ran (atom 0) child-props (r/atom nil) f (fn []) f1 (fn []) @@ -188,70 +175,41 @@ parent (fn [] (swap! parent-ran inc) [:div "child-foo" [child @child-props]])] - (with-mounted-component [parent nil nil] - (fn [c div] - (r/flush) + (u/async + (u/with-render [div [parent nil nil]] (is (= 1 @child-ran)) (is (= "child-foo" (.-innerText div))) - (reset! child-props {:style {:display :none}}) - (r/flush) + (u/act (reset! child-props {:style {:display :none}})) (is (= 2 @child-ran)) - (reset! child-props {:style {:display :none}}) - (r/flush) + (u/act (reset! child-props {:style {:display :none}})) (is (= 2 @child-ran) "keyw is equal") - (reset! child-props {:class :foo}) (r/flush) - (r/flush) + (u/act (reset! child-props {:class :foo})) (r/flush) (is (= 3 @child-ran)) - (reset! child-props {:class :foo}) (r/flush) - (r/flush) + (u/act (reset! child-props {:class :foo})) (r/flush) (is (= 3 @child-ran)) - (reset! child-props {:class 'foo}) - (r/flush) + (u/act (reset! child-props {:class 'foo})) (is (= 4 @child-ran) "symbols are different from keyw") - (reset! child-props {:class 'foo}) - (r/flush) + (u/act (reset! child-props {:class 'foo})) (is (= 4 @child-ran) "symbols are equal") - (reset! child-props {:style {:color 'red}}) - (r/flush) + (u/act (reset! child-props {:style {:color 'red}})) (is (= 5 @child-ran)) - (reset! child-props {:on-change (r/partial f)}) - (r/flush) + (u/act (reset! child-props {:on-change (r/partial f)})) (is (= 6 @child-ran)) - (reset! child-props {:on-change (r/partial f)}) - (r/flush) + (u/act (reset! child-props {:on-change (r/partial f)})) (is (= 6 @child-ran)) - (reset! child-props {:on-change (r/partial f1)}) - (r/flush) + (u/act (reset! child-props {:on-change (r/partial f1)})) (is (= 7 @child-ran)))))) -(u/deftest ^:dom dirty-test - (let [ran (r/atom 0) - state (r/atom 0) - really-simple (fn [] - (swap! ran inc) - (if (= 1 @state) - (reset! state 3)) - [:div (str "state=" @state)])] - (with-mounted-component [really-simple nil nil] - (fn [c div] - (is (= 1 @ran)) - (is (= "state=0" (.-innerText div))) - (reset! state 1) - (r/flush) - (is (= 2 @ran)) - (is (= "state=3" (.-innerText div))))) - (is (= 2 @ran)))) - (u/deftest to-string-test [] (let [comp (fn [props] [:div (str "i am " (:foo props))])] @@ -357,8 +315,8 @@ {:__html "

foobar

"}}])))) (u/deftest ^:dom test-return-class - (let [ran (r/atom 0) - top-ran (r/atom 0) + (let [ran (atom 0) + top-ran (atom 0) comp (fn [] (swap! top-ran inc) (r/create-class @@ -374,15 +332,15 @@ [:div (str "hi " (:foo props) ".")]))})) prop (r/atom {:foo "you"}) parent (fn [] [comp @prop 1])] - (with-mounted-component [parent] - (fn [C div] + (u/async + (u/with-render [div [parent]] + ;; FIXME: Why? (swap! ran inc) (is (= "hi you." (.-innerText div))) (is (= 1 @top-ran)) (is (= 3 @ran)) - (swap! prop assoc :foo "me") - (r/flush) + (u/act (swap! prop assoc :foo "me")) (is (= "hi me." (.-innerText div))) (is (= 1 @top-ran)) (is (= 4 @ran)))))) @@ -401,15 +359,15 @@ [:div (str "hi " (:foo p) ".")])})) prop (r/atom {:foo "you"}) parent (fn [] [comp @prop 1])] - (with-mounted-component [parent] - (fn [C div] + (u/async + (u/with-render [div [parent]] + ;; FIXME: Why? (swap! ran inc) (is (= "hi you." (.-innerText div))) (is (= 1 @top-ran)) (is (= 3 @ran)) - (swap! prop assoc :foo "me") - (r/flush) + (u/act (swap! prop assoc :foo "me")) (is (= "hi me." (.-innerText div))) (is (= 1 @top-ran)) (is (= 4 @ran)))))) @@ -509,40 +467,42 @@ comp (fn [props] (reset! p props) (r/as-element [:div "a" (.-children props)]))] - (with-mounted-component [:r> comp #js {:foo {:bar "x"}} - [:p "bar"]] - (fn [c div] - (is (= {:bar "x"} (gobj/get @p "foo"))) + (u/async + (u/with-render [div [:r> comp #js {:foo {:bar "x"}} + [:p "bar"]]] + (is (= {:bar "x"} + (gobj/get @p "foo"))) (is (= "
a

bar

" (.-innerHTML div))))))) (deftest ^:dom shortcut-key-warning ;; TODO: Test create-element with key prop - (let [w (debug/track-warnings - #(with-mounted-component [:div - (list - [:> "div" {:key 1} "a"] - [:> "div" {:key 2} "b"])] - (fn [c div])))] + (u/async + (p/let [w (u/track-warnings-promise + #(u/with-render [div [:div + (list + [:> "div" {:key 1} "a"] + [:> "div" {:key 2} "b"])]] + nil))] (is (empty? (:warn w)))) - (let [w (debug/track-warnings - #(with-mounted-component [:div - (list - [:r> "div" #js {:key 1} "a"] - [:r> "div" #js {:key 2} "b"])] - (fn [c div])))] + (p/let [w (u/track-warnings-promise + #(u/with-render [div [:div + (list + [:r> "div" #js {:key 1} "a"] + [:r> "div" #js {:key 2} "b"])]] + nil))] (is (empty? (:warn w)))) - (let [f (fn [props c] - [:div props c]) - w (debug/track-warnings - #(with-mounted-component [:div - (list - [:f> f {:key 1} "a"] - [:f> f {:key 2} "b"])] - (fn [c div])))] - (is (empty? (:warn w))))) + (p/let [f (fn [props c] + [:div props c]) + w (u/track-warnings-promise + #(u/with-render [div [:div + (list + [:f> f {:key 1} "a"] + [:f> f {:key 2} "b"])]] + nil))] + (is (empty? (:warn w)))))) (u/deftest test-reactize-component (let [ae r/as-element @@ -572,31 +532,34 @@ (is (= nil @a)))) (u/deftest ^:dom test-keys - (let [a nil ;; (r/atom "a") - c (fn key-tester [] - [:div - (for [i (range 3)] - ^{:key i} [:p i (some-> a deref)]) - (for [i (range 3)] - [:p {:key i} i (some-> a deref)])]) - w (debug/track-warnings - #(with-mounted-component [c] - (fn [c div])))] - (is (empty? (:warn w)))) - - (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 - (u/wrap-capture-console-error - #(with-mounted-component [c] - (fn [c div]))))] - (if (dev?) - (is (re-find #"Warning: Every element in a seq should have a unique :key: \(\[:button \{:on-click #object\[Function\]\}\] \[:button \{:on-click #object\[Function\]\}\] \[:button \{:on-click #object\[Function\]\}\]\)\n \(in reagenttest.testreagent.key_tester\)" - (first (:warn w)))))))) + (u/async + (p/let [a nil ;; (r/atom "a") + c (fn key-tester [] + [:div + (for [i (range 3)] + ^{:key i} [:p i (some-> a deref)]) + (for [i (range 3)] + [:p {:key i} i (some-> a deref)])]) + w (u/track-warnings-promise + (fn [] + (u/with-render [_div [c]] + nil)))] + (is (empty? (:warn w)))) + + (testing "Check warning text can be produced even if hiccup contains function literals" + (p/let [c (fn key-tester [] + [:div + (for [i (range 3)] + ^{:key nil} + [:button {:on-click #(js/console.log %)}])]) + w (u/track-warnings-promise + (u/wrap-capture-console-error-promise + (fn [] + (u/with-render [_div [c]] + nil))))] + (if (dev?) + (is (re-find #"Warning: Every element in a seq should have a unique :key: \(\[:button \{:on-click #object\[Function\]\}\] \[:button \{:on-click #object\[Function\]\}\] \[:button \{:on-click #object\[Function\]\}\]\)\n \(in reagenttest.testreagent.key_tester\)" + (first (:warn w))))))))) (u/deftest test-extended-syntax (is (= "

foo

" @@ -615,8 +578,8 @@ [:div (for [k [1 2]] ^{:key k} [:div>div "a"])])] - (with-mounted-component [comp] - (fn [c div] + (u/async + (u/with-render [div [comp]] ;; Just make sure this doesn't print a debug message )))) @@ -670,24 +633,25 @@ c3 (fn [] (swap! comps assoc :c3 (r/current-component)) [:div "" (reset! spy @(r/track t1))])] - (with-mounted-component [c2] - (fn [c div] + (u/async + (u/with-render [div [c2]] (is (= {:v1 1 :v2 1} @v)) - (r/force-update (:c2 @comps)) + (u/act (r/force-update (:c2 @comps))) (is (= {:v1 1 :v2 2} @v)) - (r/force-update (:c1 @comps)) + (u/act (r/force-update (:c1 @comps))) (is (= {:v1 2 :v2 2} @v)) - (r/force-update (:c2 @comps) true) - (is (= {:v1 3 :v2 3} @v)))) - (with-mounted-component [c3] - (fn [c] + (u/act (r/force-update (:c2 @comps) true)) + (is (= {:v1 3 :v2 3} @v))) + + (u/with-render [div [c3]] (is (= 0 @spy)) + ;; swap! without act doesn't instantly trigger render. (swap! state inc) (is (= 0 @spy)) - (r/force-update (:c3 @comps)) + (u/act (r/force-update (:c3 @comps))) (is (= 1 @spy)))))) ;; Class component only @@ -698,8 +662,8 @@ (let [c (r/current-component)] (reset! a (comp/component-name c)) [:div]))})] - (with-mounted-component [tc] - (fn [c] + (u/async + (u/with-render [div [tc]] (is (seq @a)) (is (re-find #"atestcomponent" @a) "component-path should work"))))) @@ -723,26 +687,12 @@ [:div @val] (finally (swap! n3 inc))))] - ;; With functional components, - ;; effect cleanup (which calls ratom dispose) happens - ;; async after unmount. - (t/async done - (u/with-mounted-component-async [c] - (fn [] - (r/next-tick - (fn [] - (r/next-tick - (fn [] - (is (= [1 2 1] [@n1 @n2 @n3])) - (done)))))) - compiler - (fn [_ div done] - (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])) - (done)))))) + (u/async + (u/with-render [div [c]] + (is (= [1 1 0] [@n1 @n2 @n3])) + (u/act (swap! val inc)) + (is (= [1 2 0] [@n1 @n2 @n3]))) + (is (= [1 2 1] [@n1 @n2 @n3]))))) (u/deftest ^:dom with-let-destroy-only (let [compiler u/*test-compiler* @@ -754,21 +704,10 @@ [:div] (finally (swap! n2 inc))))] - (t/async done - (u/with-mounted-component-async [c] - ;; Wait 2 animation frames for - ;; useEffect cleanup to be called. - (fn [] - (r/next-tick - (fn [] - (r/next-tick - (fn [] - (is (= [1 1] [@n1 @n2])) - (done)))))) - compiler - (fn [_ div done] - (is (= [1 0] [@n1 @n2])) - (done)))))) + (u/async + (u/with-render [_div [c]] + (is (= [1 0] [@n1 @n2]))) + (is (= [1 1] [@n1 @n2]))))) (u/deftest ^:dom with-let-arg (let [a (atom 0) @@ -780,11 +719,10 @@ c (fn [] (r/with-let [] [f @s]))] - (with-mounted-component [c] - (fn [_ div] + (u/async + (u/with-render [div [c]] (is (= "foo" @a)) - (reset! s "bar") - (r/flush) + (u/act (reset! s "bar")) (is (= "bar" @a)))))) (u/deftest with-let-non-reactive @@ -868,8 +806,8 @@ (is (= {:at 4 :args [@t]} (:did-mount @res))) - (reset! arg ["a" "c"]) - (r/flush) + (u/act (reset! arg ["a" "c"])) + (is (= {:at 5 :args [@t [@comp "a" "c"]]} (:will-receive @res))) (is (= {:at 6 :args [@t [@comp "a" "b"] [@comp "a" "c"]]} @@ -880,17 +818,22 @@ (:render @res))) (is (= {:at 9 :args [@t [@comp "a" "b"] {:foo "bar"} nil]} (:did-update @res))))] - (with-mounted-component [c2] u/*test-compiler* check) - (is (= {:at 10 :args [@t]} - (:will-unmount @res))) + (u/async + (u/with-render [_div [c2]] + {:compiler u/*test-compiler*} + (check)) + (is (= {:at 10 :args [@t]} + (:will-unmount @res))) + + (reset! comp (with-meta render2 ls)) + (reset! arg defarg) + (reset! n1 0) - (reset! comp (with-meta render2 ls)) - (reset! arg defarg) - (reset! n1 0) - (with-mounted-component [c2] nil check) - (is (= {:at 10 :args [@t]} - (:will-unmount @res))))) + (u/with-render [_div [c2]] + (check)) + (is (= {:at 10 :args [@t]} + (:will-unmount @res)))))) (u/deftest ^:dom lifecycle-native (let [n1 (atom 0) @@ -967,8 +910,7 @@ (is (= {:at 4 :args [@t]} (:did-mount @res))) - (reset! arg [{:f "oo"} "a" "c"]) - (r/flush) + (u/act (reset! arg [{:f "oo"} "a" "c"])) (is (= {:at 5 :args [{:foo "bar"} "a" "b"]} (:will-receive @res))) @@ -991,9 +933,11 @@ [this oldv] :args} a] (is (= 9 at)) (is (= [@comp @oldprops] oldv))))] - (with-mounted-component [cnative] check) - (is (= {:at 10 :args [@t]} - (:will-unmount @res))))) + (u/async + (u/with-render [_div [cnative]] + (check)) + (is (= {:at 10 :args [@t]} + (:will-unmount @res)))))) (defn foo [] [:div]) @@ -1045,42 +989,52 @@ #js {:render (fn [])}) (gobj/extend cmp react/Component) cmp) - compiler u/*test-compiler* - rend (fn [x] - (with-mounted-component x compiler identity))] - - ;; Error is orginally caused by comp1, so only that is shown in the error - (let [e (debug/track-warnings - (u/wrap-capture-window-error - (u/wrap-capture-console-error - #(is (thrown-with-msg? - :default #"Invalid tag: 'div.' \(in reagenttest.testreagent.comp1\)" - (rend [comp2 [:div. "foo"]]))))))] - (is (re-find #"The above error occurred in the component:" - (first (:error e))))) - - (let [e (debug/track-warnings - (u/wrap-capture-window-error - (u/wrap-capture-console-error - #(is (thrown-with-msg? - :default #"Invalid tag: 'div.' \(in reagenttest.testreagent.comp1\)" - (rend [comp1 [:div. "foo"]]))))))] - (is (re-find #"The above error occurred in the component:" - (first (:error e))))) - - (let [e (debug/track-warnings #(r/as-element [nat] compiler))] - (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) compiler))] - (is (re-find #"Every element in a seq should have a unique :key" - (-> e :warn first))))))) + compiler u/*test-compiler*] + + (u/async + ;; Error is orginally caused by comp1, so only that is shown in the error + ;; FIXME: + #_ + (p/let [e (u/track-warnings-promise + (u/wrap-capture-window-error-promise + (u/wrap-capture-console-error-promise + (fn [] + (is (thrown-with-msg? + :default #"Invalid tag: 'div.' \(in reagenttest.testreagent.comp1\)" + (-> (u/with-render [div [comp2 [:div. "foo"]]] + nil) + (p/catch (fn [_e] nil)))))))))] + (is (re-find #"The above error occurred in the component:" + (first (:error e))))) + + ;; FIXME: + #_ + (p/let [e (u/track-warnings-promise + (u/wrap-capture-window-error-promise + (u/wrap-capture-console-error-promise + (fn [] + (is (thrown-with-msg? + :default #"Invalid tag: 'div.' \(in reagenttest.testreagent.comp1\)" + (-> (u/with-render [div [comp1 [:div. "foo"]]] + nil) + (p/catch (fn [_e] nil)))))))))] + (is (re-find #"The above error occurred in the component:" + (first (:error e))))) + + (let [e (debug/track-warnings #(r/as-element [nat] compiler))] + (is (re-find #"Using native React classes directly" + (-> e :warn first)))) + + (p/let [e (u/track-warnings-promise + #(u/with-render [div [comp3]] + nil))] + (is (re-find #"Reactive deref not supported" + (-> e :warn first)))) + + (let [e (debug/track-warnings + #(r/as-element (comp4) compiler))] + (is (re-find #"Every element in a seq should have a unique :key" + (-> e :warn first)))))))) (u/deftest ^:dom test-error-boundary (let [error (r/atom nil) @@ -1100,12 +1054,11 @@ (throw (js/Error. "Test error"))) comp2 (fn comp2 [] [comp1])] - (debug/track-warnings - (u/wrap-capture-window-error - (u/wrap-capture-console-error - #(with-mounted-component [error-boundary [comp2]] - (fn [c div] - (r/flush) + (u/async + (u/track-warnings-promise + (u/wrap-capture-window-error-promise + (u/wrap-capture-console-error-promise + #(u/with-render [div [error-boundary [comp2]]] (is (= "Test error" (.-message @error))) (is (re-find #"Something went wrong\." (.-innerHTML div))) (if (dev?) @@ -1128,8 +1081,8 @@ :component-did-mount (fn [this] (reset! node (rdom/dom-node this)))})] - (with-mounted-component [comp] - (fn [c div] + (u/async + (u/with-render [div [comp]] (is (= "foobar" (.-innerHTML @ref))) (is (= "foobar" (.-innerHTML @node))) (is (identical? @ref @node)))))) @@ -1159,9 +1112,10 @@ (is (= @spy old)) (is (= @exp @val)) [:div {:ref #(reset! node %)} @state]))] - (with-mounted-component [comp] - (fn [c div] - (r/flush) + (u/async + (u/with-render [div [comp]] + ;; FIXME: Rewrite this to use act? + (is (= 1 @spy)) (swap! state inc) (is (= 1 @spy)) @@ -1178,8 +1132,8 @@ ; (is (= 3 @spy)) ; (r/flush) ; (is (= 0 @spy)) - )) - (is (= nil @node)))) + ) + (is (= nil @node))))) (u/deftest style-property-names-are-camel-cased (is (= "
foo
" @@ -1203,10 +1157,9 @@ (as-string [:i (gstr/unescapeEntities " ")]))))) (def Foo (react/createContext)) -(def FooProvider (.-Provider Foo)) (defn context-wrapper [child] - [:r> FooProvider + [:r> (.-Provider Foo) #js {:value #js {:foo "bar"}} [:div "parent," @@ -1223,10 +1176,8 @@ ;; Class component only (deftest ^:dom context-test - (with-mounted-component - [context-wrapper [context-child]] - nil - (fn [c div] + (u/async + (u/with-render [div [context-wrapper [context-child]]] (is (= "parent,child,bar" (.-innerText div)))))) @@ -1313,20 +1264,20 @@ component (fn [] [component-class @prop])] - (when (dev?) - (let [e (debug/track-warnings - #(with-mounted-component [component] - (fn [c div] - (reset! prop (sorted-map 1 2)) - (try - (r/flush) - (catch :default e - (reset! error-thrown-after-updating-props true))) - - (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)))))))) + (u/async + (when (dev?) + (p/let [e (u/track-warnings-promise + #(u/with-render [div [component]] + (u/act (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))))))))) (u/deftest ^:dom get-derived-state-from-props-test (let [prop (r/atom 0) @@ -1344,11 +1295,10 @@ (r/as-element [:p "Value " (gobj/get (.-state this) "v")]))}) component (fn [] [pure-component {:value @prop}])] - (with-mounted-component [component] - (fn [c div] + (u/async + (u/with-render [div [component]] (is (= "Value foo" (.-innerText div))) - (swap! prop inc) - (r/flush) + (u/act (swap! prop inc)) (is (= "Value foo foo" (.-innerText div))))))) (u/deftest ^:dom get-derived-state-from-error-test @@ -1367,13 +1317,13 @@ (if (= 0 @prop) [:div "Ok"] (throw (js/Error. "foo"))))] - (u/wrap-capture-window-error - (u/wrap-capture-console-error - #(with-mounted-component [component [bad-component]] - (fn [c div] + ;; FIXME: These assertions aren't being run? + (u/async + (u/wrap-capture-window-error-promise + (u/wrap-capture-console-error-promise + #(u/with-render [div [component [bad-component]]] (is (= "Ok" (.-innerText div))) - (swap! prop inc) - (r/flush) + (u/act (swap! prop inc)) (is (= "Error" (.-innerText div))))))))) (u/deftest ^:dom get-snapshot-before-update-test @@ -1393,13 +1343,12 @@ "foo"]))}) component-2 (fn [] [component {:value @prop}])] - (with-mounted-component [component-2] - (fn [c div] + (u/async + (u/with-render [div [component-2]] ;; Attach to DOM to get real height value (.appendChild js/document.body div) (is (= "foo" (.-innerText div))) - (swap! prop inc) - (r/flush) + (u/act (swap! prop inc)) (is (= {:height 20} @did-update)) (.removeChild js/document.body div))))) @@ -1416,42 +1365,36 @@ c (fn issue-462-c [] ^{:key @val} [b])] - (with-mounted-component [c] - (fn [c div] + (u/async + (u/with-render [div [c]] (is (= 1 @render)) - (reset! val 1) - (r/flush) + (u/act (reset! val 1)) (is (= 2 @render)) - (reset! val 0) - (r/flush) + (u/act (reset! val 0)) (is (= 3 @render)))))) (deftest ^:dom functional-component-poc-simple (let [c (fn [x] [:span "Hello " x])] - (testing ":f>" - (with-mounted-component [:f> c "foo"] - u/class-compiler - (fn [c div] - (is (nil? c) "Render returns nil for stateless components") - (is (= "Hello foo" (.-innerText div)))))) - - (testing "compiler options" - (with-mounted-component [c "foo"] - u/fn-compiler - (fn [c div] - (is (nil? c) "Render returns nil for stateless components") - (is (= "Hello foo" (.-innerText div)))))) - - (testing "setting default compiler" - (try - (r/set-default-compiler! u/fn-compiler) - (with-mounted-component [c "foo"] nil - (fn [c div] - (is (nil? c) "Render returns nil for stateless components") - (is (= "Hello foo" (.-innerText div))))) - (finally - (r/set-default-compiler! nil)))))) + (u/async + (testing ":f>" + (u/with-render [div [:f> c "foo"]] + {:compiler u/class-compiler} + (is (= "Hello foo" (.-innerText div))))) + + (testing "compiler options" + (u/with-render [div [c "foo"]] + {:compiler u/fn-compiler} + (is (= "Hello foo" (.-innerText div))))) + + (testing "setting default compiler" + (try + (r/set-default-compiler! u/fn-compiler) + (u/with-render [div [c "foo"]] + {:compiler nil} + (is (= "Hello foo" (.-innerText div)))) + (finally + (r/set-default-compiler! nil))))))) (deftest ^:dom functional-component-poc-state-hook (let [;; Probably not the best idea to keep @@ -1462,29 +1405,25 @@ (let [[c set-count] (react/useState x)] (reset! set-count! set-count) [:span "Count " c]))] - (with-mounted-component [c 5] - u/fn-compiler - (fn [c div] - (is (nil? c) "Render returns nil for stateless components") + (u/async + (u/with-render [div [c 5]] + {:compiler u/fn-compiler} (is (= "Count 5" (.-innerText div))) - (@set-count! 6) + (u/act (@set-count! 6)) (is (= "Count 6" (.-innerText div))))))) (deftest ^:dom functional-component-poc-ratom (let [count (r/atom 5) c (fn [x] [:span "Count " @count])] - (with-mounted-component [c 5] - u/fn-compiler - (fn [c div] - (is (nil? c) "Render returns nil for stateless components") + (u/async + (u/with-render [div [c 5]] + {:compiler u/fn-compiler} (is (= "Count 5" (.-innerText div))) - (reset! count 6) - (r/flush) - (is (= "Count 6" (.-innerText div))) - ;; TODO: Test that component RAtom is disposed - )))) + (u/act (reset! count 6)) + ;; TODO: Test that component RAtom is disposed + (is (= "Count 6" (.-innerText div))))))) (deftest ^:dom functional-component-poc-ratom-state-hook (let [r-count (r/atom 3) @@ -1493,17 +1432,14 @@ (let [[c set-count] (react/useState x)] (reset! set-count! set-count) [:span "Counts " @r-count " " c]))] - (with-mounted-component [c 15] - u/fn-compiler - (fn [c div] - (is (nil? c) "Render returns nil for stateless components") + (u/async + (u/with-render [div [c 15]] + {:compiler u/fn-compiler} (is (= "Counts 3 15" (.-innerText div))) - (reset! r-count 6) - (r/flush) + (u/act (reset! r-count 6)) (is (= "Counts 6 15" (.-innerText div))) - (@set-count! 17) - (is (= "Counts 6 17" (.-innerText div))) - )))) + (u/act (@set-count! 17)) + (is (= "Counts 6 17" (.-innerText div))))))) (u/deftest ^:dom test-input-el-ref (let [ref-1 (atom nil) @@ -1522,11 +1458,10 @@ {:ref ref-2 :value nil :on-change (fn [_])}]])] - (with-mounted-component [c] - (fn [c div] + (u/async + (u/with-render [div [c]] (is (some? @ref-1)) - (is (some? (.-current ref-2))) - )))) + (is (some? (.-current ref-2))))))) (deftest test-element-key (is (= "0" (.-key (r/as-element [:div {:key 0}])))) diff --git a/test/reagenttest/testwrap.cljs b/test/reagenttest/testwrap.cljs index 979d4576..5a5c1990 100644 --- a/test/reagenttest/testwrap.cljs +++ b/test/reagenttest/testwrap.cljs @@ -1,7 +1,7 @@ (ns reagenttest.testwrap (:require [clojure.test :as t :refer-macros [is deftest]] [reagent.core :as r] - [reagenttest.utils :as u :refer [with-mounted-component]])) + [reagenttest.utils :as u])) (deftest test-wrap-basic (let [state (r/atom {:foo 1}) @@ -107,60 +107,56 @@ parent (fn [] [child (r/wrap (:foo @state) swap! state assoc :foo)])] - (t/async done - (u/with-mounted-component-async - [parent] - done - compiler - (fn [c div done] - (u/run-fns-after-render - (fn [] - (is (= 1 @ran)) - (is (= "value:1:" (.-innerText div))) - - (reset! @grand-state {:foobar 2}) - ;; Not sure why this fixes this. - (r/flush)) - (fn [] - (is (= {:foo {:bar {:foobar 2}}} @state)) - (is (= 2 @ran)) - (is (= "value:2:" (.-innerText div))) - - (swap! state update-in [:foo :bar] assoc :foobar 3)) - (fn [] - (is (= 3 @ran)) - (is (= "value:3:" (.-innerText div))) - (reset! state {:foo {:bar {:foobar 3}} - :foo1 {}})) - (fn [] - (is (= 3 @ran)) - (reset! @grand-state {:foobar 3})) - (fn [] - ; (is (= 3 @ran)) - (is (= "value:3:" (.-innerText div))) - - (reset! state {:foo {:bar {:foobar 2}} - :foo2 {}})) - (fn [] - ; (is (= 4 @ran)) - (is (= "value:2:" (.-innerText div))) - - (reset! @grand-state {:foobar 2})) - (fn [] - ; (is (= 5 @ran)) - (is (= "value:2:" (.-innerText div))) - - (reset! state {:foo {:bar {:foobar 4}}}) - (reset! @grand-state {:foobar 4})) - (fn [] - ; (is (= 6 @ran)) - (is (= "value:4:" (.-innerText div))) - - (reset! @grand-state {:foobar 4})) - (fn [] - ; (is (= 7 @ran)) - (is (= "value:4:" (.-innerText div)))) - done)))))) + (u/async + (u/with-render [div [parent]] + {:compiler compiler} + (is (= 1 @ran)) + (is (= "value:1:" (.-innerText div))) + + (u/act (reset! @grand-state {:foobar 2})) + + (is (= {:foo {:bar {:foobar 2}}} @state)) + (is (= 2 @ran)) + (is (= "value:2:" (.-innerText div))) + + (u/act (swap! state update-in [:foo :bar] assoc :foobar 3)) + + (is (= 3 @ran)) + (is (= "value:3:" (.-innerText div))) + + (u/act (reset! state {:foo {:bar {:foobar 3}} + :foo1 {}})) + + (is (= 3 @ran)) + + (u/act (reset! @grand-state {:foobar 3})) + + ; (is (= 3 @ran)) + (is (= "value:3:" (.-innerText div))) + + (u/act (reset! state {:foo {:bar {:foobar 2}} + :foo2 {}})) + + ; (is (= 4 @ran)) + (is (= "value:2:" (.-innerText div))) + + (u/act (reset! @grand-state {:foobar 2})) + + ; (is (= 5 @ran)) + (is (= "value:2:" (.-innerText div))) + + (u/act (reset! state {:foo {:bar {:foobar 4}}}) + (reset! @grand-state {:foobar 4})) + + ; (is (= 6 @ran)) + (is (= "value:4:" (.-innerText div))) + + (u/act (reset! @grand-state {:foobar 4})) + + ; (is (= 7 @ran)) + (is (= "value:4:" (.-innerText div))) + + )))) (u/deftest ^:dom test-cursor (let [compiler u/*test-compiler* @@ -175,34 +171,26 @@ [:div [derefer (r/cursor state [:a]) a-count] [derefer (r/cursor state [:b]) b-count]])] - (t/async done - (u/with-mounted-component-async - [comp] - done - compiler - (fn [c div done] - (u/run-fns-after-render - (fn [] - (is (= 1 @a-count)) - (is (= 1 @b-count)) - - - (swap! state update-in [:a :v] inc) - (is (= 1 @a-count))) - (fn [] - (is (= 2 @a-count)) - (is (= 1 @b-count)) - - (reset! state {:a {:v 2} :b {:v 2}})) - (fn [] - (is (= 2 @a-count)) - (is (= 1 @b-count)) - - (reset! state {:a {:v 3} :b {:v 2}})) - (fn [] - (is (= 3 @a-count)) - (is (= 1 @b-count))) - done)))))) + (u/async + (u/with-render [div [comp]] + {:compiler compiler} + (is (= 1 @a-count)) + (is (= 1 @b-count)) + + (u/act (swap! state update-in [:a :v] inc)) + + (is (= 2 @a-count)) + (is (= 1 @b-count)) + + (u/act (reset! state {:a {:v 2} :b {:v 2}})) + + (is (= 2 @a-count)) + (is (= 1 @b-count)) + + (u/act (reset! state {:a {:v 3} :b {:v 2}})) + + (is (= 3 @a-count)) + (is (= 1 @b-count)))))) (u/deftest ^:dom test-fn-cursor (let [state (r/atom {:a {:v 1} @@ -219,25 +207,22 @@ [:div [derefer ac] [derefer bc]])] - (with-mounted-component [comp] - (fn [c div] + (u/async + (u/with-render [div [comp]] (is (= 1 @a-count)) (is (= 1 @b-count)) - (swap! state update-in [:a :v] inc) - (is (= 1 @a-count)) - (is (= 1 @b-count)) + (u/act (swap! state update-in [:a :v] inc)) - (r/flush) (is (= 2 @a-count)) (is (= 2 @b-count)) - (reset! state {:a {:v 2} :b {:v 2}}) - (r/flush) + (u/act (reset! state {:a {:v 2} :b {:v 2}})) + (is (= 2 @a-count)) (is (= 2 @b-count)) - (reset! state {:a {:v 3} :b {:v 2}}) - (r/flush) + (u/act (reset! state {:a {:v 3} :b {:v 2}})) + (is (= 3 @a-count)) (is (= 3 @b-count)))))) diff --git a/test/reagenttest/utils.clj b/test/reagenttest/utils.clj index 9e787fe4..cac506ea 100644 --- a/test/reagenttest/utils.clj +++ b/test/reagenttest/utils.clj @@ -5,19 +5,19 @@ (cljs.test/deftest ~(with-meta (symbol (str (name test-name) "--class")) (meta test-name)) - (binding [*test-compiler* class-compiler - *test-compiler-name* "class"] + (binding [reagenttest.utils/*test-compiler* class-compiler + reagenttest.utils/*test-compiler-name* "class"] ~@body)) (cljs.test/deftest ~(with-meta (symbol (str (name test-name) "--fn")) (meta test-name)) - (binding [*test-compiler* fn-compiler - *test-compiler-name* "fn"] + (binding [reagenttest.utils/*test-compiler* fn-compiler + reagenttest.utils/*test-compiler-name* "fn"] ~@body)))) (defmacro act [& body] - `(act* (fn [] ~@body))) + `(reagenttest.utils/act* (fn [] ~@body))) ;; Inspired by ;; https://github.com/henryw374/Cljs-Async-Timeout-Tests/blob/master/src/widdindustries/timeout_test.cljc @@ -43,15 +43,23 @@ (done#)) ~timeout-ms)] (try - (-> (do ~@body) + (-> (promesa.core/do ~@body) (.then (fn [] (js/clearTimeout timeout#) (done#))) (.catch (fn [e#] (js/clearTimeout timeout#) + (js/console.error "Promise failed in async macro" e#) (cljs.test/is (not e#)) (done#)))) (catch js/Error e# (js/clearTimeout timeout#) + (js/console.error "Error in async macro" e#) (cljs.test/is (not e#)) (done#))))))) + +(defmacro with-render [[sym comp] & body] + (let [[opts body] (if (map? (first body)) + [(first body) (rest body)] + [nil body])] + `(reagenttest.utils/with-render* ~comp ~(:compiler opts) (fn [~sym] (promesa.core/do ~@body))))) diff --git a/test/reagenttest/utils.cljs b/test/reagenttest/utils.cljs index d7dac479..b09dc190 100644 --- a/test/reagenttest/utils.cljs +++ b/test/reagenttest/utils.cljs @@ -1,12 +1,16 @@ (ns reagenttest.utils (:require-macros reagenttest.utils) - (:require ["react-dom/test-utils" :as react-test] + (:require [promesa.core :as p] + [react :as react] [reagent.core :as r] + [reagent.debug :as debug :refer [dev?]] [reagent.dom :as rdom] [reagent.dom.server :as server] - [reagent.debug :as debug] [reagent.impl.template :as tmpl])) +;; Should be only set for tests.... +;; (set! (.-IS_REACT_ACT_ENVIRONMENT js/window) true) + ;; Silence ReactDOM.render warning (defonce original-console-error (.-error js/console)) @@ -70,6 +74,8 @@ (defn log-error [& f] (debug/error (apply str f))) +;; "Regular versions" + (defn wrap-capture-console-error [f] (fn [] (let [org js/console.error] @@ -100,29 +106,84 @@ (finally (.removeListener process "uncaughtException" l))))))) -;; FIXME: Not useful, this isn't usable with production React. +;; Promise versions + +(defn wrap-capture-console-error-promise [f] + (fn [] + (let [org js/console.error] + (set! js/console.error log-error) + (-> (f) + (p/finally + (fn [] + (set! js/console.error org))))))) + +(defn track-warnings-promise [f] + (set! debug/tracking true) + (reset! debug/warnings nil) + (-> (f) + (p/then (fn [] + @debug/warnings)) + (p/finally + (fn [] + (reset! debug/warnings nil) + (set! debug/tracking false))))) + +(defn wrap-capture-window-error-promise [f] + (if (exists? js/window) + (fn [] + (let [org js/console.onerror] + (set! js/window.onerror (fn [e] + (log-error e) + true)) + (-> (f) + (p/finally + (fn [] (set! js/window.onerror org)))))) + (fn [] + (let [process (js/require "process") + l (fn [e] + (log-error e))] + (.on process "uncaughtException" l) + (-> (f) + (p/finally + (fn [] (.removeListener process "uncaughtException" l)))))))) + (defn act* "Run f to trigger Reagent updates, will return Promise which will resolve after Reagent and React render." [f] - ;; async act doesn't return a real promise (with chainable then), - ;; so wrap it. - (js/Promise. - (fn [resolve reject] - (try - (.then (react-test/act - (fn reagent-act-callback [] - ;; React act callback should return something "thenable" to use - ;; async act. - (let [p (js/Promise. (fn [resolve _reject] - (r/after-render (fn reagent-act-after-reagent-flush [] - (js/console.log "after render") - (resolve)))))] - (js/console.log "act call") - (f) - p))) - resolve - reject) - (catch :default e - (reject e)))))) + (let [p (p/deferred)] + (f) + (r/flush) + (r/after-render (fn [] + (p/resolve! p))) + p)) + +(defn with-render* + "Render the given component to a DOM node, + after the the component is mounted to DOM, + run the given function and wait for the Promise + returned from the function to be resolved + before unmounting the component from DOM." + ([comp f] + (with-render* comp *test-compiler* f)) + ([comp compiler f] + (let [div (.createElement js/document "div") + p (p/deferred) + callback (fn [] + (p/resolve! p))] + (if compiler + (rdom/render comp div {:compiler compiler + :callback callback}) + (rdom/render comp div callback)) + (.then p + (fn [] + (-> (js/Promise.resolve (f div)) + (.then (fn [] + (rdom/unmount-component-at-node div) + ;; Need to wait for reagent tick after unmount + ;; for the ratom watches to be removed? + (let [p (p/deferred)] + (r/next-tick (fn [] + (p/resolve! p))) + p))))))))) From 761d4b80e497f85b2e5d615ac554d2c49238b411 Mon Sep 17 00:00:00 2001 From: Juho Teperi Date: Sat, 23 Nov 2024 13:42:17 +0200 Subject: [PATCH 2/4] Cleanup unused test utils --- test/reagenttest/utils.cljs | 57 +------------------------------------ 1 file changed, 1 insertion(+), 56 deletions(-) diff --git a/test/reagenttest/utils.cljs b/test/reagenttest/utils.cljs index b09dc190..356aa7d9 100644 --- a/test/reagenttest/utils.cljs +++ b/test/reagenttest/utils.cljs @@ -1,9 +1,8 @@ (ns reagenttest.utils (:require-macros reagenttest.utils) (:require [promesa.core :as p] - [react :as react] [reagent.core :as r] - [reagent.debug :as debug :refer [dev?]] + [reagent.debug :as debug] [reagent.dom :as rdom] [reagent.dom.server :as server] [reagent.impl.template :as tmpl])) @@ -36,39 +35,6 @@ (defn as-string [comp] (server/render-to-static-markup comp *test-compiler*)) -(defn with-mounted-component - ([comp f] - (with-mounted-component comp *test-compiler* f)) - ([comp compiler f] - (let [div (.createElement js/document "div")] - (try - (let [c (if compiler - (rdom/render comp div compiler) - (rdom/render comp div))] - (f c div)) - (finally - (rdom/unmount-component-at-node div) - (r/flush)))))) - -(defn with-mounted-component-async - [comp done compiler f] - (let [div (.createElement js/document "div") - c (if compiler - (rdom/render comp div compiler) - (rdom/render comp div))] - (f c div (fn [] - (rdom/unmount-component-at-node div) - (r/flush) - (done))))) - -(defn run-fns-after-render [& fs] - ((reduce (fn [cb f] - (fn [] - (r/after-render (fn [] - (f) - (cb))))) - (reverse fs)))) - ;; For testing logged errors and warnings (defn log-error [& f] @@ -85,27 +51,6 @@ (finally (set! js/console.error org)))))) -(defn wrap-capture-window-error [f] - (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))))))) - ;; Promise versions (defn wrap-capture-console-error-promise [f] From 299c1027968403dbc31c2a59f7e1be3f7dd3ddaf Mon Sep 17 00:00:00 2001 From: Juho Teperi Date: Sat, 23 Nov 2024 13:45:29 +0200 Subject: [PATCH 3/4] Add missing deps.edn deps --- deps.edn | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/deps.edn b/deps.edn index dddf3d95..468485a7 100644 --- a/deps.edn +++ b/deps.edn @@ -1,3 +1,4 @@ {:paths ["src" "test" "examples/todomvc/src" "examples/simple/src" "examples/geometry/src" "demo"] - :deps {org.clojure/clojurescript {:mvn/version "1.11.60"} - doo/doo {:mvn/version "0.1.11"}}} + :deps {org.clojure/clojurescript {:mvn/version "1.11.132"} + doo/doo {:mvn/version "0.1.11"} + funcool/promesa {:mvn/version "11.0.678"}}} From 490cf1185c9bae3df2cb53dece6ec335942741e2 Mon Sep 17 00:00:00 2001 From: Juho Teperi Date: Mon, 25 Nov 2024 16:38:30 +0200 Subject: [PATCH 4/4] Fix rest of error test cases --- test/reagenttest/testreagent.cljs | 254 ++++++++++++++---------------- test/reagenttest/testwrap.cljs | 4 +- test/reagenttest/utils.clj | 2 +- test/reagenttest/utils.cljs | 122 +++++++------- 4 files changed, 185 insertions(+), 197 deletions(-) diff --git a/test/reagenttest/testreagent.cljs b/test/reagenttest/testreagent.cljs index 2b998f42..e82ef76e 100644 --- a/test/reagenttest/testreagent.cljs +++ b/test/reagenttest/testreagent.cljs @@ -22,7 +22,7 @@ (set! rv/debug false))}) (u/deftest ^:dom really-simple-test - (let [ran (r/atom 0) + (let [ran (atom 0) really-simple (fn [] (swap! ran inc) [:div "div in really-simple"])] @@ -32,7 +32,7 @@ (is (= "div in really-simple" (.-innerText div))))))) (u/deftest ^:dom test-simple-callback - (let [ran (r/atom 0) + (let [ran (atom 0) comp (r/create-class {:component-did-mount #(swap! ran inc) :render @@ -46,13 +46,12 @@ [:div (str "hi " (:foo props) ".")]))})] (u/async (u/with-render [div [comp {:foo "you"} 1]] - ;; FIXME: Why? - (swap! ran inc) (is (= "hi you." (.-innerText div))) - (is (= 3 @ran)))))) + ;; 1 from render, 1 from component did mount + (is (= 2 @ran)))))) (u/deftest ^:dom test-state-change - (let [ran (r/atom 0) + (let [ran (atom 0) self (r/atom nil) comp (r/create-class {:get-initial-state (fn [] {:foo "initial"}) @@ -77,7 +76,7 @@ (u/deftest ^:dom test-ratom-change (let [compiler u/*test-compiler* - ran (r/atom 0) + ran (atom 0) runs (rv/running) val (r/atom 0) secval (r/atom 0) @@ -131,8 +130,6 @@ (u/with-render [div [c1]] (is (= 2 @ran)) (u/act (swap! v2 inc)) - ;; FIXME: ??? - ;; (is (= 2 @ran)) (is (= 3 @ran)) (u/act (swap! v1 inc)) (is (= 5 @ran)) @@ -149,7 +146,7 @@ )))) (u/deftest ^:dom init-state-test - (let [ran (r/atom 0) + (let [ran (atom 0) really-simple (fn [] (let [this (r/current-component)] (swap! ran inc) @@ -334,20 +331,18 @@ parent (fn [] [comp @prop 1])] (u/async (u/with-render [div [parent]] - ;; FIXME: Why? - (swap! ran inc) (is (= "hi you." (.-innerText div))) (is (= 1 @top-ran)) - (is (= 3 @ran)) + (is (= 2 @ran)) (u/act (swap! prop assoc :foo "me")) (is (= "hi me." (.-innerText div))) (is (= 1 @top-ran)) - (is (= 4 @ran)))))) + (is (= 3 @ran)))))) (u/deftest ^:dom test-return-class-fn - (let [ran (r/atom 0) - top-ran (r/atom 0) + (let [ran (atom 0) + top-ran (atom 0) comp (fn [] (swap! top-ran inc) (r/create-class @@ -361,16 +356,14 @@ parent (fn [] [comp @prop 1])] (u/async (u/with-render [div [parent]] - ;; FIXME: Why? - (swap! ran inc) (is (= "hi you." (.-innerText div))) (is (= 1 @top-ran)) - (is (= 3 @ran)) + (is (= 2 @ran)) (u/act (swap! prop assoc :foo "me")) (is (= "hi me." (.-innerText div))) (is (= 1 @top-ran)) - (is (= 4 @ran)))))) + (is (= 3 @ran)))))) (u/deftest test-create-element (let [ae r/as-element @@ -478,31 +471,28 @@ ;; TODO: Test create-element with key prop (u/async - (p/let [w (u/track-warnings-promise - #(u/with-render [div [:div - (list - [:> "div" {:key 1} "a"] - [:> "div" {:key 2} "b"])]] - nil))] - (is (empty? (:warn w)))) - - (p/let [w (u/track-warnings-promise - #(u/with-render [div [:div - (list - [:r> "div" #js {:key 1} "a"] - [:r> "div" #js {:key 2} "b"])]] - nil))] - (is (empty? (:warn w)))) - - (p/let [f (fn [props c] - [:div props c]) - w (u/track-warnings-promise - #(u/with-render [div [:div - (list - [:f> f {:key 1} "a"] - [:f> f {:key 2} "b"])]] - nil))] - (is (empty? (:warn w)))))) + (u/with-render [div [:div + (list + [:> "div" {:key 1} "a"] + [:> "div" {:key 2} "b"])]] + {:capture-errors true} + (is (empty? (:warn @reagent.debug/warnings)))) + + (u/with-render [div [:div + (list + [:r> "div" #js {:key 1} "a"] + [:r> "div" #js {:key 2} "b"])]] + {:capture-errors true} + (is (empty? (:warn @reagent.debug/warnings)))) + + (let [f (fn [props c] + [:div props c])] + (u/with-render [div [:div + (list + [:f> f {:key 1} "a"] + [:f> f {:key 2} "b"])]] + {:capture-errors true} + (is (empty? (:warn @reagent.debug/warnings))))))) (u/deftest test-reactize-component (let [ae r/as-element @@ -539,27 +529,22 @@ (for [i (range 3)] ^{:key i} [:p i (some-> a deref)]) (for [i (range 3)] - [:p {:key i} i (some-> a deref)])]) - w (u/track-warnings-promise - (fn [] - (u/with-render [_div [c]] - nil)))] - (is (empty? (:warn w)))) + [:p {:key i} i (some-> a deref)])])] + (u/with-render [_div [c]] + {:capture-errors true} + (is (empty? (:warn @reagent.debug/warnings))))) (testing "Check warning text can be produced even if hiccup contains function literals" (p/let [c (fn key-tester [] [:div (for [i (range 3)] ^{:key nil} - [:button {:on-click #(js/console.log %)}])]) - w (u/track-warnings-promise - (u/wrap-capture-console-error-promise - (fn [] - (u/with-render [_div [c]] - nil))))] - (if (dev?) - (is (re-find #"Warning: Every element in a seq should have a unique :key: \(\[:button \{:on-click #object\[Function\]\}\] \[:button \{:on-click #object\[Function\]\}\] \[:button \{:on-click #object\[Function\]\}\]\)\n \(in reagenttest.testreagent.key_tester\)" - (first (:warn w))))))))) + [:button {:on-click #(js/console.log %)}])])] + (u/with-render [_div [c]] + {:capture-errors true} + (when (dev?) + (is (re-find #"Warning: Every element in a seq should have a unique :key: \(\[:button \{:on-click #object\[Function\]\}\] \[:button \{:on-click #object\[Function\]\}\] \[:button \{:on-click #object\[Function\]\}\]\)\n \(in reagenttest.testreagent.key_tester\)" + (first (:warn @reagent.debug/warnings)))))))))) (u/deftest test-extended-syntax (is (= "

foo

" @@ -993,43 +978,29 @@ (u/async ;; Error is orginally caused by comp1, so only that is shown in the error - ;; FIXME: - #_ - (p/let [e (u/track-warnings-promise - (u/wrap-capture-window-error-promise - (u/wrap-capture-console-error-promise - (fn [] - (is (thrown-with-msg? - :default #"Invalid tag: 'div.' \(in reagenttest.testreagent.comp1\)" - (-> (u/with-render [div [comp2 [:div. "foo"]]] - nil) - (p/catch (fn [_e] nil)))))))))] + (u/with-render [div [comp2 [:div. "foo"]]] + {:capture-errors true} + ;; The render call itself throws the exception + (is (re-find #"Invalid tag: 'div.' \(in reagenttest.testreagent.comp1\)" (.-message u/*render-error*))) (is (re-find #"The above error occurred in the component:" - (first (:error e))))) - - ;; FIXME: - #_ - (p/let [e (u/track-warnings-promise - (u/wrap-capture-window-error-promise - (u/wrap-capture-console-error-promise - (fn [] - (is (thrown-with-msg? - :default #"Invalid tag: 'div.' \(in reagenttest.testreagent.comp1\)" - (-> (u/with-render [div [comp1 [:div. "foo"]]] - nil) - (p/catch (fn [_e] nil)))))))))] + (first (:error @reagent.debug/warnings)))) + nil) + + (u/with-render [div [comp1 [:div. "foo"]]] + {:capture-errors true} + (is (re-find #"Invalid tag: 'div.' \(in reagenttest.testreagent.comp1\)" (.-message u/*render-error*))) (is (re-find #"The above error occurred in the component:" - (first (:error e))))) + (first (:error @reagent.debug/warnings)))) + nil) (let [e (debug/track-warnings #(r/as-element [nat] compiler))] (is (re-find #"Using native React classes directly" (-> e :warn first)))) - (p/let [e (u/track-warnings-promise - #(u/with-render [div [comp3]] - nil))] + (u/with-render [div [comp3]] + {:capture-errors true} (is (re-find #"Reactive deref not supported" - (-> e :warn first)))) + (-> @reagent.debug/warnings :warn first)))) (let [e (debug/track-warnings #(r/as-element (comp4) compiler))] @@ -1055,21 +1026,19 @@ comp2 (fn comp2 [] [comp1])] (u/async - (u/track-warnings-promise - (u/wrap-capture-window-error-promise - (u/wrap-capture-console-error-promise - #(u/with-render [div [error-boundary [comp2]]] - (is (= "Test error" (.-message @error))) - (is (re-find #"Something went wrong\." (.-innerHTML div))) - (if (dev?) - ;; FIXME: Firefox formats the stack traces differently, and perhaps there is a real problem that - ;; the component function names aren't being used correctly, and all components are named "cmp"? - (when-not (.. js/navigator -userAgent toLowerCase (includes "firefox")) - (is (re-find #"^\n at reagenttest.testreagent.comp1 \([^)]*\)\n at reagenttest.testreagent.comp2 \([^)]*\)\n at reagent[0-9]+ \([^)]*\)\n at reagenttest.testreagent.error_boundary \([^)]*\)" - (.-componentStack ^js @info)))) - ;; Names are completely manged on adv compilation - (is (re-find #"^\n at .* \([^)]*\)\n at .* \([^)]*\)\n at .* \([^)]*\)\n at .+ \([^)]*\)" - (.-componentStack ^js @info))))))))))) + (u/with-render [div [error-boundary [comp2]]] + {:capture-errors true} + (is (= "Test error" (.-message @error))) + (is (re-find #"Something went wrong\." (.-innerHTML div))) + (if (dev?) + ;; FIXME: Firefox formats the stack traces differently, and perhaps there is a real problem that + ;; the component function names aren't being used correctly, and all components are named "cmp"? + (when-not (.. js/navigator -userAgent toLowerCase (includes "firefox")) + (is (re-find #"^\n at reagenttest.testreagent.comp1 \([^)]*\)\n at reagenttest.testreagent.comp2 \([^)]*\)\n at reagent[0-9]+ \([^)]*\)\n at reagenttest.testreagent.error_boundary \([^)]*\)" + (.-componentStack ^js @info)))) + ;; Names are completely manged on adv compilation + (is (re-find #"^\n at .* \([^)]*\)\n at .* \([^)]*\)\n at .* \([^)]*\)\n at .+ \([^)]*\)" + (.-componentStack ^js @info)))))))) #_{:clj-kondo/ignore [:deprecated-var]} (u/deftest ^:dom test-dom-node @@ -1103,7 +1072,9 @@ exp (atom 0) node (atom nil) state (r/atom 0) + component-instance (atom nil) comp (fn [] + (reset! component-instance (r/current-component)) (let [old @spy] (r/after-render (fn [] @@ -1111,28 +1082,39 @@ (swap! spy inc))) (is (= @spy old)) (is (= @exp @val)) - [:div {:ref #(reset! node %)} @state]))] + [:div + {:ref #(reset! node %)} + @state]))] (u/async (u/with-render [div [comp]] - ;; FIXME: Rewrite this to use act? - - (is (= 1 @spy)) - (swap! state inc) + ;; after-render was already called after the initial render (is (= 1 @spy)) + + (u/act (swap! state inc)) + + (is (= 2 @spy)) + + ;; Update some non reactive values (r/next-tick #(swap! val inc)) (reset! exp 1) - (is (= 0 @val)) - (r/flush) + + ;; After waiting for render, the next-tick update has also happened + (u/act nil) + (is (= 1 @val)) (is (= 2 @spy)) - ;; FIXME: c is nil because render call doesn't return anything - ; (r/force-update c) - ; (is (= 3 @spy)) - ; (r/next-tick #(reset! spy 0)) - ; (is (= 3 @spy)) - ; (r/flush) - ; (is (= 0 @spy)) - ) + + ;; Now if we force render directly, spy is also updated from after-render callback + (r/force-update @component-instance) + (is (= 3 @spy)) + + ;; next-tick callback isn't called right away, + (r/next-tick #(reset! spy 0)) + (is (= 3 @spy)) + + ;; the update is run when waiting for the component to finish rendering + (u/act nil) + (is (= 0 @spy))) (is (= nil @node))))) (u/deftest style-property-names-are-camel-cased @@ -1266,18 +1248,18 @@ (u/async (when (dev?) - (p/let [e (u/track-warnings-promise - #(u/with-render [div [component]] - (u/act (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))))] + (u/with-render [div [component]] + {:capture-errors true} + (u/act (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))))))))) + (first (:warn @reagent.debug/warnings))))))))) (u/deftest ^:dom get-derived-state-from-props-test (let [prop (r/atom 0) @@ -1317,14 +1299,12 @@ (if (= 0 @prop) [:div "Ok"] (throw (js/Error. "foo"))))] - ;; FIXME: These assertions aren't being run? (u/async - (u/wrap-capture-window-error-promise - (u/wrap-capture-console-error-promise - #(u/with-render [div [component [bad-component]]] - (is (= "Ok" (.-innerText div))) - (u/act (swap! prop inc)) - (is (= "Error" (.-innerText div))))))))) + (u/with-render [div [component [bad-component]]] + {:capture-errors true} + (is (= "Ok" (.-innerText div))) + (u/act (swap! prop inc)) + (is (= "Error" (.-innerText div))))))) (u/deftest ^:dom get-snapshot-before-update-test (let [ref (react/createRef) diff --git a/test/reagenttest/testwrap.cljs b/test/reagenttest/testwrap.cljs index 5a5c1990..2bdaf777 100644 --- a/test/reagenttest/testwrap.cljs +++ b/test/reagenttest/testwrap.cljs @@ -95,8 +95,8 @@ (u/deftest ^:dom test-wrap (let [compiler u/*test-compiler* state (r/atom {:foo {:bar {:foobar 1}}}) - ran (r/atom 0) - grand-state (clojure.core/atom nil) + ran (atom 0) + grand-state (atom nil) grand-child (fn [v] (swap! ran inc) (reset! grand-state v) diff --git a/test/reagenttest/utils.clj b/test/reagenttest/utils.clj index cac506ea..ccb2f83b 100644 --- a/test/reagenttest/utils.clj +++ b/test/reagenttest/utils.clj @@ -62,4 +62,4 @@ (let [[opts body] (if (map? (first body)) [(first body) (rest body)] [nil body])] - `(reagenttest.utils/with-render* ~comp ~(:compiler opts) (fn [~sym] (promesa.core/do ~@body))))) + `(reagenttest.utils/with-render* ~comp ~opts (fn [~sym] (promesa.core/do ~@body))))) diff --git a/test/reagenttest/utils.cljs b/test/reagenttest/utils.cljs index 356aa7d9..4cac15fb 100644 --- a/test/reagenttest/utils.cljs +++ b/test/reagenttest/utils.cljs @@ -51,46 +51,31 @@ (finally (set! js/console.error org)))))) -;; Promise versions - -(defn wrap-capture-console-error-promise [f] - (fn [] - (let [org js/console.error] - (set! js/console.error log-error) - (-> (f) - (p/finally - (fn [] - (set! js/console.error org))))))) - -(defn track-warnings-promise [f] - (set! debug/tracking true) - (reset! debug/warnings nil) - (-> (f) - (p/then (fn [] - @debug/warnings)) - (p/finally - (fn [] - (reset! debug/warnings nil) - (set! debug/tracking false))))) - -(defn wrap-capture-window-error-promise [f] - (if (exists? js/window) - (fn [] - (let [org js/console.onerror] - (set! js/window.onerror (fn [e] - (log-error e) - true)) - (-> (f) - (p/finally - (fn [] (set! js/window.onerror org)))))) +(defn init-capture [] + (let [org-console js/console.error + org-window js/window.onerror + l (fn [e] + (log-error e))] + ;; console.error + (set! js/console.error log-error) + ;; reagent.debug + (set! debug/tracking true) + (reset! debug/warnings nil) + ;; window.error + (if (exists? js/window) + (set! js/window.onerror (fn [e] + (log-error e) + true)) + (let [process (js/require "process")] + (.on process "uncaughtException" l))) (fn [] - (let [process (js/require "process") - l (fn [e] - (log-error e))] - (.on process "uncaughtException" l) - (-> (f) - (p/finally - (fn [] (.removeListener process "uncaughtException" l)))))))) + (set! js/console.error org-console) + (reset! debug/warnings nil) + (set! debug/tracking false) + (if (exists? js/window) + (set! js/window.onerror org-window) + (let [process (js/require "process")] + (.removeListener process "uncaughtException" l)))))) (defn act* "Run f to trigger Reagent updates, @@ -104,6 +89,8 @@ (p/resolve! p))) p)) +(def ^:dynamic *render-error* nil) + (defn with-render* "Render the given component to a DOM node, after the the component is mounted to DOM, @@ -112,23 +99,44 @@ before unmounting the component from DOM." ([comp f] (with-render* comp *test-compiler* f)) - ([comp compiler f] + ([comp options f] (let [div (.createElement js/document "div") - p (p/deferred) + first-render (p/deferred) callback (fn [] - (p/resolve! p))] - (if compiler - (rdom/render comp div {:compiler compiler - :callback callback}) - (rdom/render comp div callback)) - (.then p - (fn [] - (-> (js/Promise.resolve (f div)) - (.then (fn [] - (rdom/unmount-component-at-node div) - ;; Need to wait for reagent tick after unmount - ;; for the ratom watches to be removed? - (let [p (p/deferred)] - (r/next-tick (fn [] - (p/resolve! p))) - p))))))))) + (p/resolve! first-render)) + compiler (:compiler options) + restore-error-handlers (when (:capture-errors options) + (init-capture)) + ;; Magic setup to make exception from render available to the + ;; with-render body. + render-error (atom nil)] + (try + (if compiler + (rdom/render comp div {:compiler compiler + :callback callback}) + (rdom/render comp div callback)) + (catch :default e + (reset! render-error e) + nil)) + (-> first-render + ;; The callback is called even if render throws an error, + ;; so this is always resolved. + (p/then (fn [] + (p/do + (set! *render-error* @render-error) + (f div) + (set! *render-error* nil)))) + ;; If f throws more errors, just ignore them? + ;; Not sure if this makes sense. + (p/catch (fn [] nil)) + (p/then (fn [] + (rdom/unmount-component-at-node div) + ;; Need to wait for reagent tick after unmount + ;; for the ratom watches to be removed? + (let [ratoms-cleaned (p/deferred)] + (r/next-tick (fn [] + (p/resolve! ratoms-cleaned))) + ratoms-cleaned))) + (p/finally (fn [] + (when restore-error-handlers + (restore-error-handlers))))))))