I've only written this from a cljs point of view.
code is untested
Callback version:
(defn wait-for-event
"Waits for an event to occur and then calls 'callback-fn'.
'ids' can be a single event id, or a collection of ids.
'callback-fn' is called when any one of the nominated event ids occurs.
'callback-fn' is called with one arguement: the full triggering event.
If one of the failure ids is seen, then cause the test to fail.
This is a one-off wait. The callback-fn will be called at most once."
[ids failure-ids callback-fn]
(let [ok-set (if (coll? ids) (set ids) #{ids})
fail-set (if (coll? failure-ids) (set failure-ids) #{failure-ids})]
(re-frame.core.add-post-event-callback
;; this listner is called post event processing.
;; the two arguments are: (1) the full event (2) the current queue
(fn listner
[new-event _]
(let [new-id (first new-event)]
;; if the new event has a failure id, then fail the test
(when (fail-set new-id)
(re-frame.core.remove-post-event-callback listner)
(#?(:cljs cljs.test/do-report :clj clojure.test/do-report)
{:type :fail
:message "wait-for-event: didn't get expected event."
:expected ok-set
:actual new-event}))
;; if the new event is one we are waiting for, then call callback-fn
(when (ok-set new-id)
(re-frame.core.remove-post-event-callback listner)
(callback-fn new-event)))))))
Example Use:
(deftest test-wait-for-event
(async ;; <-- we'll neeed to write a version of this macro for JVM
done ;; <-- this is the function to call when async test in finished
(dispatch-sync [:boot])
(is (= true @(subscribe [:booted?])))
(dispatch-sync [:something-else])
(dispatch-sync [:get-user])
(wait-for-event
:got-user ;; waiting for this event to happen
:failed-user ;; this event means failure
(fn [_]
;; the event has happened, so continue on now we containue on with test
(dispatch [:next-step])
(is (= :this @(subscribe [:that])))
(done))))) ;; must call 'done' when the test is finished
go block Version
(defn <!wait-for-event
"works like wait-for-event but without a callback-fn. Instead
it returns a core.asysnc channel through which the waited-for
event is returned.
Without callbacks the test flow looks more sequential, at the expense
of introducing a go block."
[ids failure-ids]
(let [ch (chan) ]
(wait-for-event
ids
failure-ids
(fn [new-event]
(>!! ch new-event)))
ch))
Example use:
(deftest test-with-go-block
(async
done
(go
(dispatch-sync [:boot])
(is (= true @(subscribe [:booted?])))
(dispatch-sync [:something-else])
(dispatch-sync [:get-user])
(<! (<!wait-for-event :got-user :failed-user))
(dispatch [:next-step])
(is (= :this @(subscribe [:that])))
(done)))) ;; must call this
Notes:
-
No built-in notion of timeouts. I figure that features under test will handle this. For example, the :get-user process will look after timeouts and and we'll see a :fail-user event in the case of a timeout, and that will be one of the failure events we can look for.
Of course, adding timeouts to the go block version is easy enough because we have full power of core async available
-
How do we write the JVM version of 'async' Name it: re-frame.async-test. Make it cross platform.
-
Still need a way to "push the state of re-frame" and then "pop the state of reframe" via a fixture.
pretty rough draft^