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.
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Flush all pending microtasks and updates before returning from waitFor #1366
Flush all pending microtasks and updates before returning from waitFor #1366
Changes from all commits
70edb16
1c11074
70fc3cd
c229766
ac9597b
File filter
Filter by extension
Conversations
Jump to
There are no files selected for viewing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a weird thenable because the
then
function doesn't return anything and is therefore not chainable. Also, thesetImmediate
timer starts running at the time when the promise chain is constructed, not when it runs.should run there
setImmediate
s after each other, but instead it will crash, and even after fixing the crash, it will run all three timers in parallel.The way how
await
works doesn't expose these bugs, but outsideawait
, the helper won't work.My intuition is that the really correct version will look differently 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My bad, I've missed that comment.
So when used this in instead of inline
Promise
it passed the test correctly. @jsnajdr would you be able to submit a PR with better implementation. You seem to now the Promise stuff very well, so help in this area would be very welcomed.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jsnajdr Should the implementation be just:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add #1374 so that we have a proper place for discussion
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can see in the reference PR that there is
Do you know why this is not needed here?
Did your added test failed with both fake and real timers?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes.
Yes and it's an interesting story 🙂 The
setImmediate
function that is being called here, it's imported from the./helpers/timers
module, and it's the real Node.js function, not the fake one provided by Jest. Therefore, fake timers don't need to be advanced.We could use
global.setImmediate
, and then we'd need to advance the timers, otherwise the promise would never resolve. Like this:But, if you really do this, the test will start failing with fake timers! The scheduled effects won't be executed. That's because there is a bug in React: facebook/react#25889. When the
react-dom
module is loaded, it will save the current value ofwindow.setImmediate
to a local variable, and will use it later to schedule effects. If your test suite callsjest.useFakeTimers()
later, that's too late. React is already using real timers.With fake
setImmediate
used in the promise above, Jest doesn't wait long enough for the realsetImmediate
timer to fire. After returning fromwaitFor
, the effect has not been executed yet.To conclude, the "
waitFor
+ fake timers" scenario is currently broken and is not going to work reliably. I think I know how to break@testing-library/react
even with the testing-library/react-testing-library#1137 patch applied. I'll try it out.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, that's a really good catch, this
setImmediate
problem explains the issue we've had in #1347, it makes sense to me.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So I had a closer look at the question whether we should call
jest.advanceTimersByTime(0)
and my conclusion is no, it's not needed.Our issue is that when a component like this is rerendered:
then React, when committing the rerender (the
commitRootImpl
function), writes the newtext
to the DOM (commitMutationEffects
), and schedules a callback to flush the effects (scheduleCallback(flushPassiveEffects)
).A
waitFor
likelooks at the DOM, and can be satisfied and return immediately after the DOM update. But at that time, the effects are not yet flushed, the
sideEffectWith
function hasn't yet been called with the newtext
value. And that's the problem, both effects (mutation and passive) belong logically together.The effects are scheduled with the
unstable_scheduleCallback
function from thescheduler
package, which uses:window.postMessage
in a real browsersetImmediate
in Node.jssetTimeout(0)
in JSDOM environment, which supports neitherwindow.MessageChannel
norsetImmediate
React Native tests usually run in
jest-environment-node
, not injest-environment-jsdom
, so the callback is scheduled withsetImmediate
. So thewaitFor
implementation in@testing-library/react-native
needs to wait for the nextsetImmediate
. And the@testing-library/react
implementation needs to wait for the nextsetTimeout(0)
.Because of facebook/react#25889, the callback is always scheduled using real timers. We don't need to advance any fake timers. We're waiting solely for things scheduled in React internals, never for any user-space callback.
The extra
jest.advanceTimersByTime(0)
call could even be considered a bug: if there's user-space code that schedules a fakesetTimeout(0)
, then it's the user's, i.e., the test writer's, responsibility to advance the timers accordingly, whenever the user wants the timers to be advanced. The library shouldn't do it for them, at a time that's outside user's control.