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))))))))