fix: use flushPromise() instead of waitFor() #283
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
The current fireEvent() uses waitFor() after triggering event handler to ensure that the DOM is updated in a timely manner, passing in an empty function and awaiting it.
await waitFor (() => {})
returns a promise that resolves shortly after. Since Vue also uses promises or other microtasks to update the DOM, when the microtask generated by the promise returned by calling waitFor() is executed by the event loop, the microtask of updating the DOM can also be executed. Users only needs to await fireEvent() to ensure the update of the DOM.This seems very reasonable. But in some cases, such handling can cause some problems, causing the update of the DOM to be performed after the assertion written by the user.
The case is: some component libraries, like vant, encapsulate form that perform form validation before calling the event callback passed by the user when processing the submit event, and their function for validation will use nested promises to do check (usually a combination of promise and
Promise.all()
), which leads to a problem: when the microtask generated by waitFor() is executed, the microtask of Vue's update DOM has not been executed! So the user's assertion will be executed earlier than the task of updating the DOM, causing the test to fail.This situation can be reproduced as follows:
I also provide a reproduction here.
Obviously, the above situation is not in line with the design purpose of fireEvent() and the expectations of users. Users who do not know the reason may wonder whether the implementation of their source code is wrong or whether there is a problem with the test code, but will not think it's a problem with fireEvent() itself, because the docs they've seen tells them that the function is guaranteed to update the dom.
For users, one solution is to use waitFor() to wrap the assertion and wait for the dom to update. This can let the test to pass, but also adds redundant code. It also makes users lose confidence in writing test code, because they can't be sure that the dom will update immediately after they call and
await fireEvent()
.So I think it is necessary to modify the internal implementation of fireEvent(). I use the flushPromise() exported by vue/test-utils instead of waitFor(). In addition to using promises, flushPromise() calls setImmediate/setTimeout to generate a macroTask that calls the resolve() method. Since the macroTask will be executed after the microTask, this ensures that all microTasks generated after the fireEvent() call, including DOM updates, will be executed before the macroTask. So replacing waitFor() with flushPromises() guarantees that dom updates can be done before the user's assertion.