Skip to content

Commit abbfdbd

Browse files
committed
feat(waitFor): add complete and transparent support for fake timers
Closes #661
1 parent 260e1e8 commit abbfdbd

10 files changed

+166
-445
lines changed

src/__tests__/__snapshots__/wait-for-dom-change.js.snap

-37
This file was deleted.

src/__tests__/deprecation-warnings.js

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import {waitForElement, waitForDomChange, wait} from '..'
2+
3+
afterEach(() => {
4+
console.warn.mockClear()
5+
})
6+
7+
test('deprecation warnings only warn once', async () => {
8+
await wait(() => {}, {timeout: 1})
9+
await waitForElement(() => {}, {timeout: 1}).catch(e => e)
10+
await waitForDomChange({timeout: 1}).catch(e => e)
11+
expect(console.warn.mock.calls).toMatchInlineSnapshot(`
12+
Array [
13+
Array [
14+
"\`wait\` has been deprecated and replaced by \`waitFor\` instead. In most cases you should be able to find/replace \`wait\` with \`waitFor\`. Learn more: https://testing-library.com/docs/dom-testing-library/api-async#waitfor.",
15+
],
16+
Array [
17+
"\`waitForElement\` has been deprecated. Use a \`find*\` query (preferred: https://testing-library.com/docs/dom-testing-library/api-queries#findby) or use \`waitFor\` instead: https://testing-library.com/docs/dom-testing-library/api-async#waitfor",
18+
],
19+
Array [
20+
"\`waitForDomChange\` has been deprecated. Use \`waitFor\` instead: https://testing-library.com/docs/dom-testing-library/api-async#waitfor.",
21+
],
22+
]
23+
`)
24+
25+
console.warn.mockClear()
26+
await wait(() => {}, {timeout: 1})
27+
await waitForElement(() => {}, {timeout: 1}).catch(e => e)
28+
await waitForDomChange({timeout: 1}).catch(e => e)
29+
expect(console.warn).not.toHaveBeenCalled()
30+
})

src/__tests__/example.js

-92
This file was deleted.

src/__tests__/fake-timers.js

+44-120
Original file line numberDiff line numberDiff line change
@@ -1,139 +1,63 @@
1+
import {waitFor, waitForElementToBeRemoved} from '..'
12
import {render} from './helpers/test-utils'
23

3-
// Because we're using fake timers here and I don't want these tests to run
4-
// for the actual length of the test (because it's waiting for a timeout error)
5-
// we'll mock the setTimeout, clearTimeout, and setImmediate to be the ones
6-
// that jest will mock for us.
7-
jest.mock('../helpers', () => {
8-
const actualHelpers = jest.requireActual('../helpers')
9-
return {
10-
...actualHelpers,
11-
setTimeout,
12-
clearTimeout,
13-
setImmediate,
14-
}
4+
beforeAll(() => {
5+
jest.useFakeTimers()
156
})
167

17-
jest.useFakeTimers()
18-
19-
// Because of the way jest mocking works here's the order of things (and no, the order of the code above doesn't make a difference):
20-
// 1. Just mocks '../helpers' and setTimeout/clearTimeout/setImmediate are set to their "correct" values
21-
// 2. We tell Jest to use fake timers
22-
// 3. We reset the modules and we mock '../helpers' again so now setTimeout/clearTimeout/setImmediate are set to their mocked values
23-
// We're only doing this because want to mock those values so this test doesn't take 4501ms to run.
24-
jest.resetModules()
25-
26-
const {
27-
wait,
28-
waitForElement,
29-
waitForDomChange,
30-
waitForElementToBeRemoved,
31-
} = require('../')
32-
33-
test('waitForElementToBeRemoved: times out after 4500ms by default', () => {
34-
const {container} = render(`<div></div>`)
35-
// there's a bug with this rule here...
36-
// eslint-disable-next-line jest/valid-expect
37-
const promise = expect(
38-
waitForElementToBeRemoved(() => container),
39-
).rejects.toThrowErrorMatchingInlineSnapshot(
40-
`"Timed out in waitForElementToBeRemoved."`,
41-
)
42-
jest.advanceTimersByTime(4501)
43-
return promise
8+
afterAll(() => {
9+
jest.useRealTimers()
4410
})
4511

46-
test('wait: can time out', async () => {
47-
const promise = wait(() => {
48-
// eslint-disable-next-line no-throw-literal
49-
throw undefined
50-
})
51-
jest.advanceTimersByTime(4600)
52-
await expect(promise).rejects.toThrow(/timed out/i)
53-
})
54-
55-
test('waitForElement: can time out', async () => {
56-
const promise = waitForElement(() => {})
57-
jest.advanceTimersByTime(4600)
58-
await expect(promise).rejects.toThrow(/timed out/i)
59-
})
12+
async function runWaitFor() {
13+
const response = 'data'
14+
const doAsyncThing = () =>
15+
new Promise(r => setTimeout(() => r(response), 300))
16+
let result
17+
doAsyncThing().then(r => (result = r))
6018

61-
test('waitForElement: can specify our own timeout time', async () => {
62-
const promise = waitForElement(() => {}, {timeout: 4700})
63-
const handler = jest.fn()
64-
promise.then(handler, handler)
65-
// advance beyond the default
66-
jest.advanceTimersByTime(4600)
67-
// promise was neither rejected nor resolved
68-
expect(handler).toHaveBeenCalledTimes(0)
19+
await waitFor(() => expect(result).toBe(response))
20+
}
6921

70-
// advance beyond our specified timeout
71-
jest.advanceTimersByTime(150)
72-
73-
// timed out
74-
await expect(promise).rejects.toThrow(/timed out/i)
22+
test('real timers', async () => {
23+
// the only difference when not using fake timers is this test will
24+
// have to wait the full length of the timeout
25+
await runWaitFor()
7526
})
7627

77-
test('waitForDomChange: can time out', async () => {
78-
const promise = waitForDomChange()
79-
jest.advanceTimersByTime(4600)
80-
await expect(promise).rejects.toThrow(/timed out/i)
28+
test('legacy', async () => {
29+
jest.useFakeTimers('legacy')
30+
await runWaitFor()
8131
})
8232

83-
test('waitForDomChange: can specify our own timeout time', async () => {
84-
const promise = waitForDomChange({timeout: 4700})
85-
const handler = jest.fn()
86-
promise.then(handler, handler)
87-
// advance beyond the default
88-
jest.advanceTimersByTime(4600)
89-
// promise was neither rejected nor resolved
90-
expect(handler).toHaveBeenCalledTimes(0)
91-
92-
// advance beyond our specified timeout
93-
jest.advanceTimersByTime(150)
94-
95-
// timed out
96-
await expect(promise).rejects.toThrow(/timed out/i)
33+
test('modern', async () => {
34+
jest.useFakeTimers()
35+
await runWaitFor()
9736
})
9837

99-
test('wait: ensures the interval is greater than 0', async () => {
100-
// Arrange
101-
const spy = jest.fn()
102-
spy.mockImplementationOnce(() => {
103-
throw new Error('first time does not work')
104-
})
105-
const promise = wait(spy, {interval: 0})
106-
expect(spy).toHaveBeenCalledTimes(1)
107-
spy.mockClear()
108-
109-
// Act
110-
// this line will throw an error if wait does not make the interval 1 instead of 0
111-
// which is why it does that!
112-
jest.advanceTimersByTime(0)
113-
114-
// Assert
115-
expect(spy).toHaveBeenCalledTimes(0)
116-
spy.mockImplementationOnce(() => 'second time does work')
117-
118-
// Act
119-
jest.advanceTimersByTime(1)
120-
await promise
121-
122-
// Assert
123-
expect(spy).toHaveBeenCalledTimes(1)
38+
test('fake timer timeout', async () => {
39+
jest.useFakeTimers()
40+
await expect(
41+
waitFor(
42+
() => {
43+
throw new Error('always throws')
44+
},
45+
{timeout: 10},
46+
),
47+
).rejects.toMatchInlineSnapshot(`[Error: always throws]`)
12448
})
12549

126-
test('wait: times out if it runs out of attempts', () => {
127-
const spy = jest.fn(() => {
128-
throw new Error('example error')
129-
})
50+
test('times out after 1000ms by default', async () => {
51+
const {container} = render(`<div></div>`)
52+
const start = performance.now()
13053
// there's a bug with this rule here...
13154
// eslint-disable-next-line jest/valid-expect
132-
const promise = expect(
133-
wait(spy, {interval: 1, timeout: 3}),
134-
).rejects.toThrowErrorMatchingInlineSnapshot(`"example error"`)
135-
jest.advanceTimersByTime(1)
136-
jest.advanceTimersByTime(1)
137-
jest.advanceTimersByTime(1)
138-
return promise
55+
await expect(
56+
waitForElementToBeRemoved(() => container),
57+
).rejects.toThrowErrorMatchingInlineSnapshot(
58+
`"Timed out in waitForElementToBeRemoved."`,
59+
)
60+
// NOTE: this assertion ensures that even when we have fake timers, the
61+
// timeout still takes the full 1000ms
62+
expect(performance.now() - start).toBeGreaterThan(1000)
13963
})

0 commit comments

Comments
 (0)