Skip to content

Commit 3ae2702

Browse files
authored
fix: Only use a single clock (#966)
BREAKING CHANGE: Remove deprecated `waitFormDOMChange` BREAKING CHANGE: The `timeout` in `waitFor(callback, { interval, timeout } )` now uses the same clock as `interval`. Previously `timeout` was always using the real clock while `interval` was using the global clock which could've been mocked out. For the old behavior I'd recommend `waitFor(callback, { interval, timeout: Number.PositiveInfinity })` and rely on your test runner to timeout considering real timers.
1 parent 86fb094 commit 3ae2702

10 files changed

+22
-333
lines changed

src/__tests__/deprecation-warnings.js

-30
This file was deleted.

src/__tests__/fake-timers.js

+6-7
Original file line numberDiff line numberDiff line change
@@ -50,12 +50,11 @@ test('times out after 1000ms by default', async () => {
5050
).rejects.toThrowErrorMatchingInlineSnapshot(
5151
`"Timed out in waitForElementToBeRemoved."`,
5252
)
53-
// NOTE: this assertion ensures that even when we have fake timers, the
54-
// timeout still takes the full 1000ms
55-
// unfortunately, timeout clocks aren't super accurate, so we simply verify
56-
// that it's greater than or equal to 900ms. That's enough to be confident
57-
// that we're using real timers.
58-
expect(performance.now() - start).toBeGreaterThanOrEqual(900)
53+
// NOTE: this assertion ensures that the timeout runs in the declared (fake) clock
54+
// while in real time the time was only a fraction since the real clock is only bound by the CPU
55+
// So 10ms is really just an approximation on how long the CPU needs to execute our code.
56+
// If people want to timeout in real time they should rely on their test runners.
57+
expect(performance.now() - start).toBeLessThanOrEqual(10)
5958
})
6059

6160
test('recursive timers do not cause issues', async () => {
@@ -68,7 +67,7 @@ test('recursive timers do not cause issues', async () => {
6867
}
6968

7069
startTimer()
71-
await runWaitFor({time: 800}, {timeout: 100})
70+
await runWaitFor({time: 800}, {timeout: 900})
7271

7372
recurse = false
7473
})

src/__tests__/helpers.js

+1-70
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
import {screen} from '../'
2-
import {
3-
getDocument,
4-
getWindowFromNode,
5-
checkContainerType,
6-
runWithRealTimers,
7-
} from '../helpers'
2+
import {getDocument, getWindowFromNode, checkContainerType} from '../helpers'
83

94
test('returns global document if exists', () => {
105
expect(getDocument()).toBe(document)
@@ -61,67 +56,3 @@ describe('query container validation throws when validation fails', () => {
6156
)
6257
})
6358
})
64-
65-
describe('run with real timers', () => {
66-
const realSetTimeout = global.setTimeout
67-
68-
afterEach(() => {
69-
// restore timers replaced by jest.useFakeTimers()
70-
jest.useRealTimers()
71-
// restore setTimeout replaced by assignment
72-
global.setTimeout = realSetTimeout
73-
})
74-
75-
test('use real timers when timers are faked with jest.useFakeTimers(legacy)', () => {
76-
// legacy timers use mocks and do not rely on a clock instance
77-
jest.useFakeTimers('legacy')
78-
runWithRealTimers(() => {
79-
expect(global.setTimeout).toBe(realSetTimeout)
80-
})
81-
expect(global.setTimeout._isMockFunction).toBe(true)
82-
expect(global.setTimeout.clock).toBeUndefined()
83-
})
84-
85-
test('use real timers when timers are faked with jest.useFakeTimers(modern)', () => {
86-
// modern timers use a clock instance instead of a mock
87-
jest.useFakeTimers('modern')
88-
runWithRealTimers(() => {
89-
expect(global.setTimeout).toBe(realSetTimeout)
90-
})
91-
expect(global.setTimeout._isMockFunction).toBeUndefined()
92-
expect(global.setTimeout.clock).toBeDefined()
93-
})
94-
95-
test('do not use real timers when timers are not faked with jest.useFakeTimers', () => {
96-
// useFakeTimers is not used, timers are faked in some other way
97-
const fakedSetTimeout = callback => {
98-
callback()
99-
}
100-
fakedSetTimeout.clock = jest.fn()
101-
global.setTimeout = fakedSetTimeout
102-
103-
runWithRealTimers(() => {
104-
expect(global.setTimeout).toBe(fakedSetTimeout)
105-
})
106-
expect(global.setTimeout).toBe(fakedSetTimeout)
107-
})
108-
109-
describe('run with setImmediate and clearImmediate deleted', () => {
110-
const setImmediate = global.setImmediate
111-
const clearImmediate = global.clearImmediate
112-
113-
beforeEach(() => {
114-
delete global.setImmediate
115-
delete global.clearImmediate
116-
})
117-
118-
afterEach(() => {
119-
global.setImmediate = setImmediate
120-
global.clearImmediate = clearImmediate
121-
})
122-
123-
test('safe check for setImmediate and clearImmediate', () => {
124-
expect(() => runWithRealTimers(() => {})).not.toThrow()
125-
})
126-
})
127-
})

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

-54
This file was deleted.

src/helpers.js

+10-78
Original file line numberDiff line numberDiff line change
@@ -1,85 +1,21 @@
1-
const globalObj = typeof window === 'undefined' ? global : window
21
// Constant node.nodeType for text nodes, see:
32
// https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType#Node_type_constants
43
const TEXT_NODE = 3
54

6-
// Currently this fn only supports jest timers, but it could support other test runners in the future.
7-
function runWithRealTimers(callback) {
8-
return hasJestTimers()
9-
? runWithJestRealTimers(callback).callbackReturnValue
10-
: // istanbul ignore next
11-
callback()
12-
}
13-
14-
function hasJestTimers() {
15-
return (
16-
typeof jest !== 'undefined' &&
17-
jest !== null &&
18-
typeof jest.useRealTimers === 'function'
19-
)
20-
}
21-
22-
function runWithJestRealTimers(callback) {
23-
const timerAPI = {
24-
clearInterval,
25-
clearTimeout,
26-
setInterval,
27-
setTimeout,
28-
}
29-
30-
// For more on why we have the check here,
31-
// checkout https://github.com/testing-library/dom-testing-library/issues/914
32-
if (typeof setImmediate === 'function') {
33-
timerAPI.setImmediate = setImmediate
34-
}
35-
if (typeof clearImmediate === 'function') {
36-
timerAPI.clearImmediate = clearImmediate
37-
}
38-
39-
jest.useRealTimers()
40-
41-
const callbackReturnValue = callback()
42-
43-
const usedFakeTimers = Object.entries(timerAPI).some(
44-
([name, func]) => func !== globalObj[name],
45-
)
46-
47-
if (usedFakeTimers) {
48-
jest.useFakeTimers(timerAPI.setTimeout?.clock ? 'modern' : 'legacy')
49-
}
50-
51-
return {
52-
callbackReturnValue,
53-
usedFakeTimers,
54-
}
55-
}
56-
575
function jestFakeTimersAreEnabled() {
58-
return hasJestTimers()
59-
? runWithJestRealTimers(() => {}).usedFakeTimers
60-
: // istanbul ignore next
61-
false
62-
}
63-
64-
// we only run our tests in node, and setImmediate is supported in node.
65-
// istanbul ignore next
66-
function setImmediatePolyfill(fn) {
67-
return globalObj.setTimeout(fn, 0)
68-
}
69-
70-
function getTimeFunctions() {
71-
// istanbul ignore next
72-
return {
73-
clearTimeoutFn: globalObj.clearTimeout,
74-
setImmediateFn: globalObj.setImmediate || setImmediatePolyfill,
75-
setTimeoutFn: globalObj.setTimeout,
6+
/* istanbul ignore else */
7+
if (typeof jest !== 'undefined' && jest !== null) {
8+
return (
9+
// legacy timers
10+
setTimeout._isMockFunction === true ||
11+
// modern timers
12+
Object.prototype.hasOwnProperty.call(setTimeout, 'clock')
13+
)
7614
}
15+
// istanbul ignore next
16+
return false
7717
}
7818

79-
const {clearTimeoutFn, setImmediateFn, setTimeoutFn} = runWithRealTimers(
80-
getTimeFunctions,
81-
)
82-
8319
function getDocument() {
8420
/* istanbul ignore if */
8521
if (typeof window === 'undefined') {
@@ -144,10 +80,6 @@ function checkContainerType(container) {
14480
export {
14581
getWindowFromNode,
14682
getDocument,
147-
clearTimeoutFn as clearTimeout,
148-
setImmediateFn as setImmediate,
149-
setTimeoutFn as setTimeout,
150-
runWithRealTimers,
15183
checkContainerType,
15284
jestFakeTimersAreEnabled,
15385
TEXT_NODE,

src/index.js

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ export * from './queries'
66
export * from './wait-for'
77
export * from './wait-for-element'
88
export * from './wait-for-element-to-be-removed'
9-
export * from './wait-for-dom-change'
109
export {getDefaultNormalizer} from './matches'
1110
export * from './get-node-text'
1211
export * from './events'

src/wait-for-dom-change.js

-64
This file was deleted.

0 commit comments

Comments
 (0)