-
Notifications
You must be signed in to change notification settings - Fork 467
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
fix: Stop calling waitFor
callback after timeout
#1271
fix: Stop calling waitFor
callback after timeout
#1271
Conversation
This pull request is automatically built and testable in CodeSandbox. To see build info of the built libraries, click here or the icon next to each commit SHA. Latest deployment of this branch, based on commit 10a1ac7:
|
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## alpha #1271 +/- ##
=========================================
Coverage 100.00% 100.00%
=========================================
Files 24 24
Lines 1038 1041 +3
Branches 346 347 +1
=========================================
+ Hits 1038 1041 +3
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. |
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.
Thanks for working on the fix. However, we should rather make sure that callback
is not being invoked again. The proposed fix just hides the issue.
Also: Can you provide a minimal repro against which we can test this fix? Ideally we could extract a unit test from it.
73bbd53
to
a0bb005
Compare
@eps1lon I changed the solution implementation. You can find a reproduction example on this repository. Output example: It's been a while since I have used Don't hesitate if you have any other questions 🙇 |
Uh oh! @KevinBon, the image you shared is missing helpful alt text. Check #1271 (comment). Alt text is an invisible description that helps screen readers describe images to blind or low-vision users. If you are using markdown to display images, add your alt text inside the brackets of the markdown image. Learn more about alt text at Basic writing and formatting syntax: images on GitHub Docs. |
I created a branch to show-case that applying this patch will fix this behavior: KevinBon/jasmine-tl-waitfor-leak#1 |
src/wait-for.js
Outdated
@@ -160,6 +163,9 @@ function waitFor( | |||
} | |||
|
|||
function handleTimeout() { | |||
if (finished) { |
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.
Shouldn't we cancel the timeout instead when we finish?
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.
When we finish, onDone
should have been called with clearTimeout
(src), therefore I don't really think this one is needed, but it's more for extra-safety.
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.
Amended in 6b41483
src/wait-for.js
Outdated
@@ -134,7 +137,7 @@ function waitFor( | |||
} | |||
|
|||
function checkCallback() { | |||
if (promiseStatus === 'pending') return | |||
if (finished || promiseStatus === 'pending') return |
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.
Can we cancel whatever is calling checkCallback
instead?
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 would love to, but I'm afraid it might introduce breaking changes.
We'll need to move the if(finished)
before checkCallback
here: https://github.com/testing-library/dom-testing-library/pull/1271/files#diff-68584107d5f50eb95702a47b23bb6e0f6c2d78f9bab7456a1a4150c79229db78R84-R92
But the following comment is daunting:
dom-testing-library/src/wait-for.js
Lines 84 to 87 in a0bb005
// It's really important that checkCallback is run *before* we flush | |
// in-flight promises. To be honest, I'm not sure why, and I can't quite | |
// think of a way to reproduce the problem in a test, but I spent | |
// an entire day banging my head against a wall on this. |
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.
Actually https://github.com/testing-library/dom-testing-library/pull/1271/files#diff-68584107d5f50eb95702a47b23bb6e0f6c2d78f9bab7456a1a4150c79229db78R84-R92 is un-related, so I should move the if(finished)
before checkCallback
👍
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.
Amended in 6b41483
@@ -81,15 +81,15 @@ function waitFor( | |||
jest.advanceTimersByTime(interval) | |||
}) | |||
|
|||
// Could have timed-out | |||
if (finished) { |
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.
With this factoring, I wonder if we could simplify further by moving checkCallback
before the advanceTimers
. That way the loop condition makes sure we exit as soon as we're finished
. And we can remove another checkCallback
on L54.
At this point I would remove the "It's really important that checkCallback [...]" comment. We might have fixed the issue along the way. And since I'd rather ship this with the next major, we have a good opportunity to resurface the bug so that we can at least link a repro. It doesn't have to be a Jest test but somehow the reordering fixed "something". That "something" needs to be linked a t least. Doesn't need to be an automated test but it needs to be something. Otherwise we're chasing ghosts.
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.
If we move checkCallback
before the advanceTimers
, the waitFor
callback
will be called twice before we advance the clock, instead of once right now.
Which will break this fix: https://github.com/testing-library/dom-testing-library/pull/1073/files
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.
the waitFor callback will be called twice before we advance the clock, instead of once right now.
but not if we remove the checkCallback
call earlier from L54, no?
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.
There will be a change of behavior in
).rejects.toMatchInlineSnapshot(`[Error: always throws]`) |
timeout
smaller than the interval
, it will instead be rejected with [Error: Timed out in waitFor.]
@@ -292,12 +292,11 @@ test('the real timers => fake timers error shows the original stack trace when c | |||
|
|||
test('does not work after it resolves', async () => { | |||
jest.useFakeTimers('modern') | |||
let context = 'initial' | |||
const contextStack = [] |
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.
Or
const contextStack = [] | |
const contextCallStack = [] |
|
||
expect(context).toEqual('initial') | ||
test(`when fake timer is installed, on waitFor timeout, it doesn't call the callback afterward`, async () => { |
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 test ensures that the fix is working
@@ -292,12 +292,11 @@ test('the real timers => fake timers error shows the original stack trace when c | |||
|
|||
test('does not work after it resolves', async () => { |
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 made some changes on this test to help understand the outcome: everything that starts needs to end
waitfor
callback leakwaitFor
callback leak on waitFor
timeout
@eps1lon let me know if you need additional information or are unhappy with my changes 🙇 |
@eps1lon bumping, let me know if you need anything from me. Thank you! |
Thanks for the patience. Checking this out now so that we can land it ASAP. |
waitFor
callback leak on waitFor
timeoutwaitFor
callback leak on waitFor
timeout
waitFor
callback leak on waitFor
timeoutwaitFor
callback after timeout
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.
Looks great, thank you. I verified the tests are actually failing on main
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.
Looks great, thank you. I verified the tests are actually failing on main
@all-contributors add @KevinBon for code, bugs |
I've put up a pull request to add @KevinBon! 🎉 |
What:
Prevent
waitFor
callback from being invoked even after it resolved, onwaitFor
timeout.Why:
More context can be found in this issue, but with that context in mind, it's preventing test side-effects:
afterEach
should be enough to "clean" the previous test from any side-effect. However, because of the callback leak issue,window.variable
will remain to be123
even after being set back to1
for a short period of time.How:
When the clock is mocked, only call
checkCallback
whenfinished
isfalse
Checklist:
docs site: N/A
resolves #1270