Skip to content

Commit 69b474b

Browse files
author
Sebastian Silbermann
committed
Port tests from waitFor DOM
1 parent 221ad91 commit 69b474b

File tree

2 files changed

+254
-0
lines changed

2 files changed

+254
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`waitFor DOM reference implementation using fake legacy timers timeout 1`] = `Not done`;
4+
5+
exports[`waitFor DOM reference implementation using fake modern timers timeout 1`] = `Not done`;
6+
7+
exports[`waitFor DOM reference implementation using real timers timeout 1`] = `Not done`;

src/__tests__/waitFor.test.js

+247
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
/**
2+
* @jest-environment node
3+
*/
4+
5+
import * as prettyFormat from 'pretty-format'
6+
import {waitFor} from '../'
7+
8+
function deferred() {
9+
let resolve, reject
10+
const promise = new Promise((res, rej) => {
11+
resolve = res
12+
reject = rej
13+
})
14+
return {promise, resolve, reject}
15+
}
16+
17+
beforeEach(() => {
18+
jest.useRealTimers()
19+
})
20+
21+
test('waits callback to not throw an error', async () => {
22+
const spy = jest.fn()
23+
// we are using random timeout here to simulate a real-time example
24+
// of an async operation calling a callback at a non-deterministic time
25+
const randomTimeout = Math.floor(Math.random() * 60)
26+
setTimeout(spy, randomTimeout)
27+
28+
await waitFor(() => expect(spy).toHaveBeenCalledTimes(1))
29+
expect(spy).toHaveBeenCalledWith()
30+
})
31+
32+
// we used to have a limitation where we had to set an interval of 0 to 1
33+
// otherwise there would be problems. I don't think this limitation exists
34+
// anymore, but we'll keep this test around to make sure a problem doesn't
35+
// crop up.
36+
test('can accept an interval of 0', () => waitFor(() => {}, {interval: 0}))
37+
38+
test('can timeout after the given timeout time', async () => {
39+
const error = new Error('throws every time')
40+
const result = await waitFor(
41+
() => {
42+
throw error
43+
},
44+
{timeout: 8, interval: 5},
45+
).catch(e => e)
46+
expect(result).toBe(error)
47+
})
48+
49+
test('if no error is thrown then throws a timeout error', async () => {
50+
const result = await waitFor(
51+
() => {
52+
// eslint-disable-next-line no-throw-literal
53+
throw undefined
54+
},
55+
{timeout: 8, interval: 5, onTimeout: e => e},
56+
).catch(e => e)
57+
expect(result).toMatchInlineSnapshot(`[Error: Timed out in waitFor.]`)
58+
})
59+
60+
test('if showOriginalStackTrace on a timeout error then the stack trace does not include this file', async () => {
61+
const result = await waitFor(
62+
() => {
63+
// eslint-disable-next-line no-throw-literal
64+
throw undefined
65+
},
66+
{timeout: 8, interval: 5, showOriginalStackTrace: true},
67+
).catch(e => e)
68+
expect(result.stack).not.toMatch(__dirname)
69+
})
70+
71+
test('uses full stack error trace when showOriginalStackTrace present', async () => {
72+
const error = new Error('Throws the full stack trace')
73+
// even if the error is a TestingLibraryElementError
74+
error.name = 'TestingLibraryElementError'
75+
const originalStackTrace = error.stack
76+
const result = await waitFor(
77+
() => {
78+
throw error
79+
},
80+
{timeout: 8, interval: 5, showOriginalStackTrace: true},
81+
).catch(e => e)
82+
expect(result.stack).toBe(originalStackTrace)
83+
})
84+
85+
test('throws nice error if provided callback is not a function', () => {
86+
const someElement = {}
87+
expect(() => waitFor(someElement)).toThrow(
88+
'Received `callback` arg must be a function',
89+
)
90+
})
91+
92+
test('when a promise is returned, it does not call the callback again until that promise rejects', async () => {
93+
const sleep = t => new Promise(r => setTimeout(r, t))
94+
const p1 = deferred()
95+
const waitForCb = jest.fn(() => p1.promise)
96+
const waitForPromise = waitFor(waitForCb, {interval: 1})
97+
expect(waitForCb).toHaveBeenCalledTimes(1)
98+
waitForCb.mockClear()
99+
await sleep(50)
100+
expect(waitForCb).toHaveBeenCalledTimes(0)
101+
102+
const p2 = deferred()
103+
waitForCb.mockImplementation(() => p2.promise)
104+
105+
p1.reject('p1 rejection (should not fail this test)')
106+
await sleep(50)
107+
108+
expect(waitForCb).toHaveBeenCalledTimes(1)
109+
p2.resolve()
110+
111+
await waitForPromise
112+
})
113+
114+
test('when a promise is returned, if that is not resolved within the timeout, then waitFor is rejected', async () => {
115+
const sleep = t => new Promise(r => setTimeout(r, t))
116+
const {promise} = deferred()
117+
const waitForError = waitFor(() => promise, {timeout: 1}).catch(e => e)
118+
await sleep(5)
119+
120+
expect((await waitForError).message).toMatchInlineSnapshot(
121+
`Timed out in waitFor.`,
122+
)
123+
})
124+
125+
test('does not work after it resolves', async () => {
126+
jest.useFakeTimers('modern')
127+
let context = 'initial'
128+
129+
/** @type {import('../').FakeClock} */
130+
const clock = {
131+
// @testing-library/react usage to ensure `IS_REACT_ACT_ENVIRONMENT` is set when acting.
132+
advanceTimersByTime: async timeoutMS => {
133+
const originalContext = context
134+
context = 'act'
135+
try {
136+
jest.advanceTimersByTime(timeoutMS)
137+
} finally {
138+
context = originalContext
139+
}
140+
},
141+
flushPromises: async () => {
142+
const originalContext = context
143+
context = 'no-act'
144+
try {
145+
await await new Promise(r => {
146+
setTimeout(r, 0)
147+
jest.advanceTimersByTime(0)
148+
})
149+
} finally {
150+
context = originalContext
151+
}
152+
},
153+
}
154+
155+
let data = null
156+
setTimeout(() => {
157+
data = 'resolved'
158+
}, 100)
159+
160+
await waitFor(
161+
() => {
162+
// eslint-disable-next-line jest/no-conditional-in-test -- false-positive
163+
if (data === null) {
164+
throw new Error('not found')
165+
}
166+
},
167+
{clock, interval: 50},
168+
)
169+
170+
expect(context).toEqual('initial')
171+
172+
await Promise.resolve()
173+
174+
expect(context).toEqual('initial')
175+
})
176+
177+
/** @type {import('../').FakeClock} */
178+
const jestFakeClock = {
179+
advanceTimersByTime: timeoutMS => {
180+
jest.advanceTimersByTime(timeoutMS)
181+
},
182+
flushPromises: () => {
183+
return new Promise(r => {
184+
setTimeout(r, 0)
185+
jest.advanceTimersByTime(0)
186+
})
187+
},
188+
}
189+
describe.each([
190+
['real timers', {useTimers: () => jest.useRealTimers(), clock: undefined}],
191+
[
192+
'fake legacy timers',
193+
{useTimers: () => jest.useFakeTimers('legacy'), clock: jestFakeClock},
194+
],
195+
[
196+
'fake modern timers',
197+
{useTimers: () => jest.useFakeTimers('modern'), clock: jestFakeClock},
198+
],
199+
])(
200+
'waitFor DOM reference implementation using %s',
201+
(label, {useTimers, clock}) => {
202+
beforeEach(() => {
203+
useTimers()
204+
})
205+
206+
afterEach(() => {
207+
jest.useRealTimers()
208+
})
209+
210+
test('void callback', async () => {
211+
await expect(waitFor(() => {}, {clock})).resolves.toBeUndefined()
212+
})
213+
214+
test('callback passes after timeout', async () => {
215+
let state = 'pending'
216+
setTimeout(() => {
217+
state = 'done'
218+
}, 10)
219+
220+
await expect(
221+
waitFor(
222+
() => {
223+
if (state !== 'done') {
224+
throw new Error('Not done')
225+
}
226+
},
227+
{clock, interval: 5},
228+
),
229+
).resolves.toBeUndefined()
230+
})
231+
232+
test('timeout', async () => {
233+
const state = 'pending'
234+
235+
await expect(
236+
waitFor(
237+
() => {
238+
if (state !== 'done') {
239+
throw new Error('Not done')
240+
}
241+
},
242+
{clock, timeout: 10},
243+
),
244+
).rejects.toThrowErrorMatchingSnapshot()
245+
})
246+
},
247+
)

0 commit comments

Comments
 (0)