user-event: userEvent.type's delay hangs forever
Up front… This issue probably belongs in angular-testing-library, jest-preset-angular, or maybe jest-dom, but I can’t say where at the moment, and the problem is manifest in user-event . Also, there is may be a better title depending on where this belongs. Please feel free to modify any of this or redirect me as appropriate.
-
@testing-library/user-eventversion: 12.7.3 -
Testing Framework and version: jest: 26.6.3 angular: 11.2.2 node: 12.18.4 jest-dom: 5.11.9 angular-testing-library: 10.3.2 jest-preset-angular: 8.3.2
Relevant code or config
My jest test simulates the user clicking an input, and clearing it’s existing text:
await userEvent.type(inputElem, '{selectall}{backspace}', {delay: 10, skipClick: false});
What happened:
If delay is set to zero the test passes as expected.
If delay is greater than zero, the test hangs until jest times out (longer jest timeout does not help).
What you did: user-event/src/type.js currently contains the following code:
async function runCallbacks(callbacks) {
...
for (const callback of callbacks) {
if (delay > 0) await wait(delay)
if (!currentElement().disabled) {
...
A breakpoint on if (delay > 0) is always hit.
A breakpoint on if (!currentElement().disabled) is never hit (assuming you call with delay > 0).
What I tried: Disabling the zone.js patch of setTimeout allows the test to pass (although obviously not a real solution).
declare var window;
(window as any).__Zone_disable_timers = true;
Problem description: This issue seems like an interaction problem between packages in the testing-library ecosystem. I’ve followed each packages installation and setup guides, and believe my import ordering and configurations are correct, but obviously something was missed somewhere.
Any suggestions?
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Reactions: 1
- Comments: 17 (5 by maintainers)
Commits related to this issue
- test(frontend): fix issue with userEvent timing out test cases More info https://github.com/testing-library/user-event/issues/565#issuecomment-1064579531 — committed to opengovsg/postmangovsg by stanleynguyen 2 years ago
- ci(github-actions): run all tests on PR to develop (#1476) * ci(github-actions): run all tests on PR to develop * test(frontend): fix outdated frontend test * test(frontend): fix issue with use... — committed to opengovsg/postmangovsg by stanleynguyen 2 years ago
I solve the issue by setting real timers of jest before the type. Hope it helps 😄
I’m glad your problem is solved. To add some more information about relationship of timers and
userEvent.typewith delay…https://github.com/testing-library/user-event/blob/7a5c51e7f89c4ab0592174eff591b9e9275ff81c/src/type.js#L124
Each block of events for a character is delayed per
setTimeout. You don’t need to use real timers. You just can’tawaitthe typing in your test since you have to wind your fake timers while the type implementation is running.If you want to use real timers for some part of your code, there is also this solution by
@testing-library/dom. It’s just an internal, but it won’t go away in foreseeable future:If you don’t like to use an internal but also don’t want to repeat the code, open an issue there. Maybe adding this to the exports of the main module is worth a discussion. 😃
If I could get back all the time I’ve wasted chasing defects caused by jest’s replacement of standard Javascript functions…
That solved my issue as well. Thanks!
We push
resolve()onto the event loop persetTimeoutand wait for it to be called. If you’re using fake timers this won’t happen afterdelaymicroseconds, but when you wind your fake timer.advanceTimersallows you to provide a callback to do this.https://github.com/testing-library/user-event/blob/ee062e762f9ac185d982dbf990387e97e05b3c9d/src/utils/misc/wait.ts#L3-L12
E.g. with Jest’s fake timers:
@ph-fritsche - Would it be possible to add an
advanceoption to type? The typeImpl function could then call that with the delay beforeawaiting on the promise.As an example of the approach, I’m using this “wrapper” around type (it only works for plaintext):
So instead of above, I could just call
userEvent.type(input, 'some text', { delay: 20, advance: jest.advanceTimersByTime });advanceTimersoption was introduced in #907 . It is included in v14.1.0Actually this would be really cool, to advance timers. It could remove instabilities that could appear for waiting the type function.
Please note that
userEvent.pasteprovides a completely different abstraction thanuserEvent.type. If you expect the user totypesome input, usinguserEvent.pasteis no better than to callfireEvent.inputdirectly.If you have mocked the timer functions by other means than
jest.useFakeTimers,jest.useRealTimerswill have no effect.Yeah, that’s what I should have done in the first place. In a small sample I might even find my configuration mistake 😃
Will reply with an update once I know one way or the other.