Fork me on GitHub
#re-frame
<
2016-11-17
>
tom01:11:26

Is there a good way to do end to end testing or testing UI elements with re-frame? Is there a way to do this with a library like doo?

elahti05:11:22

We're just using phantomjs and plain old js to run UI tests. Started with doo based version but it turned out very fast to be expensive.

elahti05:11:11

Would very much like to have some de-facto standard way of doing this stuff in clj. Also would like to have something better than phantomjs... But that's different story šŸ™‚

yenda07:11:08

@tom I don't do much testing yet. If you have schemas for all your subscriptions and no logic in the components like re-frame recommends, not much can go wrong there, as long as the schemas are good. I suppose testing would be a matter of dispatching tons of events and verify that the subs stay consistent with your schemas.

yenda07:11:19

if you mean simulating a user doing actions in the browser I use this https://github.com/semperos/clj-webdriver

lsnape09:11:17

We use clj-webdriver for testing our re-frame webapp. Be aware that the project hasnā€™t seen much love recently. It now requires ageing Selenium dependencies that have compatibility issues with latest versions of Chrome and Firefox when running locally.

lsnape09:11:05

Iā€™d be interested to hear if anyone has any browser testing success stories šŸ™‚

mping10:11:22

@lsnape @elahti check nightmare.js, it has a nice api and uses electron under the hood: http://www.nightmarejs.org/

mping10:11:40

js only, Iā€™m afraid

elahti10:11:57

@mping cool, will check it out. thanks! js isn't at all a problem.

vikeri11:11:02

I would like to have all my dispatches being dispatch-sync when Iā€™m running tests. Trying to re-register the :dispatch fx I get an error though saying that > You can't call dispatch-sync within an event handler. Does anyone else have a good way to test handlers that dispatch to other handlers? I would prefer not to use setTimeout but to do it synchonously.

joshkh13:11:30

@vikeri someone can correct me if i'm wrong, but i think the point of having reg-event-fx is so that your handlers remain as pure functions and can therefore be tested individually

joshkh13:11:30

so you could mock the data and test the function registered to the handler, not the handler itself

sandbags13:11:23

@joshkh i was just reading about this the other day and, yes, that jives with my understanding

joshkh13:11:23

or maybe you can leverage the async-flow-fx? https://github.com/Day8/re-frame-async-flow-fx

joshkh13:11:54

i used it to boot my application

(defn boot-flow
  []
  {:first-dispatch [:auth/fetch-anonymous-token {:root ""}]
   :rules          [{:when     :seen?
                     :events   [:auth/store-token]
                     :dispatch [:main/fetch-assets]}
                    {:when     :seen-all-of?
                     :events   [:main/save-summary-fields
                                :main/save-model]
                     :dispatch [:im-tables.main/run-query]}]})

joshkh13:11:38

or, you know, pass 100% of your tests by not writing any šŸ™‚

vikeri13:11:14

Yes Iā€™m using fx handlers, thatā€™s why I have a :dispatch key in the handler. I understand that there is a point in testing the handlerā€™s function as a unit test. But I would also like to have a more ā€œintegration testā€-like behaviour when I can fire a bunch of dispatches and check that my state is where it should be when everything has finished.

joshkh13:11:06

then yes, maybe async-flow can work? you could watch for the last event and then check the app's state

joshkh13:11:27

i've asked for help in here before but this is still doing my head in... i have a standalone re-frame application which is a fancy data table. now i want to include multiple instances of the data table in a different re-frame application. so far, i understand that: 1. All of the data table's events and get registered to the global event registry (so it's really important to namespace them). 2. Any changes to the data table's app-db are changing the parent app's db because they're now the same app-db. 3. Since there will be multiple data tables they each need to know where in app-db to store their state. so, when I construct the main data table, i can pass it a path: [data-table {:path [:tables :table-1]}], and when a data table fires an event it can say (dispatch [:save-some-state path {:results 123}]) and the :save-some-state handler will update-in db at the path provided. but there are some really painful parts of this process. 1. My app is already written and there are about 50 events and 30 subs. Each one will have to be updated to accept a path parameter. 2. The :path key supplied to the view constructor will have to be passed down to every single nested component that dispatches or subscribes to something.

joshkh13:11:23

so then i thought an interceptor might solve the problem:

(defn sandbox
  "Returns an interceptor factory that shrinks the world
   down to a subset of app-db. Changes are merged back into
   the real db when the handler finishes at a path supplied
   as the last argument in an event.
  (dispatch [:some-event some-data [:my :sandboxed :location])"
  []
  (re-frame.core/->interceptor
    :id :sandbox
    :before (fn [context]
              (if-let [path (last (get-in context [:coeffects :event]))]
                (update context :coeffects assoc
                        :old-db (get-in context [:coeffects :db])
                        :db (get-in context (concat [:coeffects :db] path)))
                context))
    :after (fn [context]
             (if-let [path (last (get-in context [:coeffects :event]))]
               (let [old-db (get-in context [:coeffects :old-db])
                     new-db (get-in context [:effects :db])]
                 (assoc-in context [:effects :db]
                           (assoc-in old-db path new-db)))
               context))))

joshkh13:11:51

so the functions in the handlers could be left alone and the interceptor would handle the location of the update

joshkh13:11:44

but that still requires that a path be passed to every single event from every single dispatch, and i still have to update every subscription to accept a path (as far as i know there's no support for subscription middleware beyond piping in other subscriptions)

joshkh13:11:07

can anyone offer some advice? i keep coding myself into a corner. šŸ˜‰

joshkh13:11:38

(also, i looked into the path middleware but the paths are constructed at compile time and i need them to be dynamic)

danielgrosse14:11:51

I have a function which subscribes to a event and then should call another function. When I call it in a component, it is always called, when the component is rendered. How should I load it to refresh only on the own subscription?

joshkh14:11:31

is the evaluation of (sender) changing the value of menu?

joshkh14:11:17

that would put you into a render loop

joshkh14:11:08

also, it looks to me like you're using an out dated version of re-frame

joshkh14:11:35

(just on a side note - upgrading won't fix the render loop)

danielgrosse14:11:46

Actually not. The menu is set after the first load, and not changed. I still use 0.7.0, as I didn't had the time to port the code to a newer one.

joshkh14:11:39

could you move the call to (sender) outside of the render function?

joshkh14:11:24

(defn component []
  (let [menu (subscribe [:menu])]
    (reagent/create-class
      {:component-did-mount sender
       :reagent-render (fn [] [sub-component @menu])})))

joshkh14:11:39

although you might be going about things a little backwards. the view should never really call functions directly (except for dispatching events). instead, somewhere in your handlers, you could dispatch another event when you think 'sender' should be called

joshkh14:11:34

this is side effect-y, but just an example

(register-handler
  :update-id
  [trim-v (when debug? debug)]
  (fn [db [id]]
    (sender)
    (assoc db :id id)))