Thanks to the awesome tools in Ember ecosystem such as ember-cli-mirage, ember-qunit or ember-test-helpers writing majority of the tests is pretty straight-forward. Nevertheless, there are quite a few cases where simulating user’s interaction is not that simple. An example of such use case would be holding a button for particular period of time triggering some side effect.
Anatomy of The Problem
Imagine you are implementing a feature of destroying some records in your application, e.g. the todo items from the list. It would be a bit unfortunate to destroy any item if a user accidentally clicked on the destroy button, so it might be a good idea to somehow make it harder to execute such an action. A simple approach would be displaying some alert asking user to confirm whether this item should be removed or not. This approach would get our job done, but it doesn’t offer the best UX. What are the better options here?
A pretty cool solution to this problem would be making user hold a delete button for a particular period of time, e.g. for 3 seconds. Holding this button for less than 3 seconds wouldn’t destroy the item, so it would be impossible to accidentally delete anything.
There is an addon which solves exactly this problem: ember-hold-button, so there is no need to reinvent the wheel. Let’s add this to our application.
Adding Destroy Action
Let’s start by installing
and assume that we already have some component for displaying a single item with
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
and that the component was test-driven with the following test written before the actual implementation (TDD for FTW!):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
Basically this test verifies that the
destroyRecord method will be called on item after clicking the button.
hold-button which will trigger
destroy action after holding it for 3 seconds:
1 2 3 4
delay option will get the job done here to make it holdable for 3 seconds to trigger
The button is working great, but our test obviously is failing now! How can we simulate holding action in our integration tests?
Testing Holding Interaction
To solve that problem we should break the problem down into the single events. On desktop, pressing a button simply means triggering
mouseDown event and releasing means trigger
mouseUp event. On mobile that would be
touchEnd events accordingly.
Based on how
hold-button component works, we may suspect that there is some internal timer which starts counting time after triggering
touchStart) event or a scheduler which executes the action if it was held for required period of time and cancels it if it was released before that period of time, which would mean cancelling timer on
After checking the internals, it turns out this is exactly the case! Let’s rewrite our test by triggering these events. We will also need two extra things as we are dealing with asynchronous actions:
done()– To make sure QUnit will wait for an asynchronous operation to be finished we need to use
async()function. That way QUnit will wait until
done()is called. We will call
mouseUpevent. But we also need to wait until the action is executed. We will need
wait()helper for that.
wait()– it forces run loop to process all the pending events. That way we ensure that the asynchronous operation have been executed (like calling
destroyaction after 3 seconds).
Here’s our new test:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42
Nice! Our test is passing again. However, there is one serious problem: this test is quite slow as it waits 3 second for the action to finish. Can we make it somehow faster?
Making Our Test Faster
The answer is: yes. We just need to provide a way to make
delay configurable from the outside. This can be simply done by introducing
destroyActionDelay property with default value equal
3000 and allowing it to be modified. Let’s start with applying this little change to the test:
We don’t care about waiting for 3 seconds in the tests, we just want to test if it works and to make it fast.
0 sounds like the most reasonable value in such case.
And let’s change few things in our component:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
1 2 3 4
And that’s it! You can now enjoy the much faster test suite!
Testing holding a button for particular period of time doesn’t sound like an obvious thing to do. Fortunately, with proper design and understanding the interaction from the browser’s perspective, it isn’t that hard to do and doesn’t necessarily make your tests slower.
P.S. I’ve just started writing a book about test-driving Ember applications. If you found this article useful, you are going to love it :). Subscribe to my newsletter to get updates and promotion code once it’s released.