Skip to content
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

feat(waitFor): add complete and transparent support for fake timers #662

Merged
merged 1 commit into from
Jun 23, 2020

Conversation

kentcdodds
Copy link
Member

@kentcdodds kentcdodds commented Jun 23, 2020

What: feat(waitFor): add complete and transparent support for fake timers

Why: Closes #661

How:

Basically, we detect if we're in an environment that's faking out timers and if we are then we do a while loop that advances the jest fake timers by the interval until the callback passes. We do still support a timeout so it can time out eventually and won't loop forever.

Checklist:

- [ ] Documentation added to the docs site N/A

  • Tests
    - [ ] Typescript definitions updated N/A
  • Ready to be merged

@codesandbox-ci
Copy link

codesandbox-ci bot commented Jun 23, 2020

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 9aab321:

Sandbox Source
old-microservice-bp8u4 Configuration

@tannerlinsley
Copy link

Elegant :)

@kentcdodds
Copy link
Member Author

Build is busted thanks to: #663

I'm planning on doing a beta release of this locally so I can test it out.

Copy link
Member Author

@kentcdodds kentcdodds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm feeling pretty good about this, but I would still love someone else to give this a look.

Comment on lines -92 to -140
describe('timers', () => {
const expectElementToBeRemoved = async () => {
const importedWaitForElementToBeRemoved = importModule()

const {queryAllByTestId} = renderIntoDocument(`
<div data-testid="div"></div>
<div data-testid="div"></div>
`)
const divs = queryAllByTestId('div')
// first mutation
setTimeout(() => {
divs.forEach(d => d.setAttribute('id', 'mutated'))
})
// removal
setTimeout(() => {
divs.forEach(div => div.parentElement.removeChild(div))
}, 100)

const promise = importedWaitForElementToBeRemoved(
() => queryAllByTestId('div'),
{
timeout: 200,
},
)

if (setTimeout._isMockFunction) {
jest.advanceTimersByTime(110)
}

await promise
}

it('works with real timers', async () => {
jest.useRealTimers()
await expectElementToBeRemoved()
})
it('works with fake timers', async () => {
jest.useFakeTimers()
await expectElementToBeRemoved()
})
})

test("doesn't change jest's timers value when importing the module", () => {
jest.useFakeTimers()
importModule()

expect(window.setTimeout._isMockFunction).toEqual(true)
})

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need these tests because waitForElementToBeRemoved is built on top of waitFor and it has its own tests.

@@ -117,4 +125,5 @@ export {
setTimeoutFn as setTimeout,
runWithRealTimers,
checkContainerType,
jestFakeTimersAreEnabled,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pretty much all the changes in this file are refactors to facilitate this export.

@kentcdodds
Copy link
Member Author

This is published as @testing-library/[email protected]

@kentcdodds
Copy link
Member Author

And I published these changes in @testing-library/[email protected] to try it out in a practical situation.

@kentcdodds
Copy link
Member Author

Works like a charm! :chefs-kiss:

@kentcdodds
Copy link
Member Author

Dang it.

Ran 100000 timers, and there are still more! Assuming we've hit an infinite recursion and bailing out...

🤦‍♂️ investigating....

src/wait-for.js Outdated
// waiting or when we've timed out.
// eslint-disable-next-line no-unmodified-loop-condition
while (!finished) {
jest.runAllTimers()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Whoops! This was supposed to be jest.advanceTimersByTime(interval) 😅

@kentcdodds kentcdodds force-pushed the pr/support-fake-timers branch 2 times, most recently from 229dea2 to 93977ad Compare June 23, 2020 16:01
@kentcdodds
Copy link
Member Author

I think this is ready. I made a handful of changes to how we test some of these utilities (especially the deprecated ones).

@kentcdodds kentcdodds force-pushed the pr/support-fake-timers branch from 93977ad to abbfdbd Compare June 23, 2020 16:03
@codecov
Copy link

codecov bot commented Jun 23, 2020

Codecov Report

Merging #662 into master will not change coverage.
The diff coverage is 100.00%.

Impacted file tree graph

@@            Coverage Diff            @@
##            master      #662   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           24        24           
  Lines          583       593   +10     
  Branches       149       148    -1     
=========================================
+ Hits           583       593   +10     
Impacted Files Coverage Δ
src/helpers.js 100.00% <100.00%> (ø)
src/wait-for.js 100.00% <100.00%> (ø)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 260e1e8...9aab321. Read the comment docs.

@kentcdodds kentcdodds force-pushed the pr/support-fake-timers branch from abbfdbd to b32e2fd Compare June 23, 2020 16:07
@kentcdodds kentcdodds force-pushed the pr/support-fake-timers branch from b32e2fd to 9aab321 Compare June 23, 2020 16:07
Copy link
Member Author

@kentcdodds kentcdodds left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, this is ready to go as soon as the build passes.

Comment on lines -43 to -48
const {MutationObserver} = getWindowFromNode(container)
const observer = new MutationObserver(checkCallback)
runWithRealTimers(() =>
observer.observe(container, mutationObserverOptions),
)
checkCallback()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We no longer need to run this with real timers because we only run it when we're not using fake timers.

@kentcdodds
Copy link
Member Author

Note: I can't guarantee that these changes won't break people who had to work around the limitations in their tests. So if someone was using our async utilities with fake timers, it's possible that we'll break them depending on how they worked around things.

I'll add this to the release notes, but if you're here reading this because your tests broke as a result of this change, just know that you shouldn't have to worry about advancing the fake timers manually anymore.

@kentcdodds kentcdodds merged commit 5b2640a into master Jun 23, 2020
@kentcdodds kentcdodds deleted the pr/support-fake-timers branch June 23, 2020 16:14
@kentcdodds
Copy link
Member Author

🎉 This PR is included in version 7.17.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

waitFor should work with fake timers
2 participants