Skip to content

Commit ac9a6b7

Browse files
authored
fix: remove side-effect from runWithRealTimers (#887)
1 parent 5bc9364 commit ac9a6b7

File tree

2 files changed

+60
-69
lines changed

2 files changed

+60
-69
lines changed

src/__tests__/helpers.js

Lines changed: 35 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,6 @@ import {
55
runWithRealTimers,
66
} from '../helpers'
77

8-
const globalObj = typeof window === 'undefined' ? global : window
9-
10-
afterEach(() => jest.useRealTimers())
11-
128
test('returns global document if exists', () => {
139
expect(getDocument()).toBe(document)
1410
})
@@ -53,42 +49,47 @@ describe('query container validation throws when validation fails', () => {
5349
})
5450
})
5551

56-
test('should always use realTimers before using callback when timers are faked with useFakeTimers', () => {
57-
const originalSetTimeout = globalObj.setTimeout
52+
describe('run with real timers', () => {
53+
const realSetTimeout = global.setTimeout
5854

59-
// legacy timers use mocks and do not rely on a clock instance
60-
jest.useFakeTimers('legacy')
61-
runWithRealTimers(() => {
62-
expect(originalSetTimeout).toEqual(globalObj.setTimeout)
55+
afterEach(() => {
56+
// restore timers replaced by jest.useFakeTimers()
57+
jest.useRealTimers()
58+
// restore setTimeout replaced by assignment
59+
global.setTimeout = realSetTimeout
6360
})
64-
expect(globalObj.setTimeout._isMockFunction).toBe(true)
65-
expect(globalObj.setTimeout.clock).toBeUndefined()
6661

67-
jest.useRealTimers()
68-
69-
// modern timers use a clock instance instead of a mock
70-
jest.useFakeTimers('modern')
71-
runWithRealTimers(() => {
72-
expect(originalSetTimeout).toEqual(globalObj.setTimeout)
62+
test('use real timers when timers are faked with jest.useFakeTimers(legacy)', () => {
63+
// legacy timers use mocks and do not rely on a clock instance
64+
jest.useFakeTimers('legacy')
65+
runWithRealTimers(() => {
66+
expect(global.setTimeout).toBe(realSetTimeout)
67+
})
68+
expect(global.setTimeout._isMockFunction).toBe(true)
69+
expect(global.setTimeout.clock).toBeUndefined()
7370
})
74-
expect(globalObj.setTimeout._isMockFunction).toBeUndefined()
75-
expect(globalObj.setTimeout.clock).toBeDefined()
76-
})
7771

78-
test('should not use realTimers when timers are not faked with useFakeTimers', () => {
79-
const originalSetTimeout = globalObj.setTimeout
80-
81-
// useFakeTimers is not used, timers are faked in some other way
82-
const fakedSetTimeout = callback => {
83-
callback()
84-
}
85-
fakedSetTimeout.clock = jest.fn()
72+
test('use real timers when timers are faked with jest.useFakeTimers(modern)', () => {
73+
// modern timers use a clock instance instead of a mock
74+
jest.useFakeTimers('modern')
75+
runWithRealTimers(() => {
76+
expect(global.setTimeout).toBe(realSetTimeout)
77+
})
78+
expect(global.setTimeout._isMockFunction).toBeUndefined()
79+
expect(global.setTimeout.clock).toBeDefined()
80+
})
8681

87-
globalObj.setTimeout = fakedSetTimeout
82+
test('do not use real timers when timers are not faked with jest.useFakeTimers', () => {
83+
// useFakeTimers is not used, timers are faked in some other way
84+
const fakedSetTimeout = callback => {
85+
callback()
86+
}
87+
fakedSetTimeout.clock = jest.fn()
88+
global.setTimeout = fakedSetTimeout
8889

89-
runWithRealTimers(() => {
90-
expect(fakedSetTimeout).toEqual(globalObj.setTimeout)
90+
runWithRealTimers(() => {
91+
expect(global.setTimeout).toBe(fakedSetTimeout)
92+
})
93+
expect(global.setTimeout).toBe(fakedSetTimeout)
9194
})
92-
93-
globalObj.setTimeout = originalSetTimeout
9495
})

src/helpers.js

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -5,52 +5,42 @@ const TEXT_NODE = 3
55

66
// Currently this fn only supports jest timers, but it could support other test runners in the future.
77
function runWithRealTimers(callback) {
8-
const fakeTimersType = getJestFakeTimersType()
9-
if (fakeTimersType) {
8+
return _runWithRealTimers(callback).callbackReturnValue
9+
}
10+
11+
function _runWithRealTimers(callback) {
12+
const timerAPI = {
13+
clearImmediate,
14+
clearInterval,
15+
clearTimeout,
16+
setImmediate,
17+
setInterval,
18+
setTimeout,
19+
}
20+
21+
// istanbul ignore else
22+
if (typeof jest !== 'undefined') {
1023
jest.useRealTimers()
1124
}
1225

1326
const callbackReturnValue = callback()
1427

15-
if (fakeTimersType) {
16-
jest.useFakeTimers(fakeTimersType)
17-
}
28+
const usedJestFakeTimers = Object.entries(timerAPI).some(
29+
([name, func]) => func !== globalObj[name],
30+
)
1831

19-
return callbackReturnValue
20-
}
21-
22-
function getJestFakeTimersType() {
23-
// istanbul ignore if
24-
if (
25-
typeof jest === 'undefined' ||
26-
typeof globalObj.setTimeout === 'undefined'
27-
) {
28-
return null
32+
if (usedJestFakeTimers) {
33+
jest.useFakeTimers(timerAPI.setTimeout?.clock ? 'modern' : 'legacy')
2934
}
3035

31-
if (
32-
typeof globalObj.setTimeout._isMockFunction !== 'undefined' &&
33-
globalObj.setTimeout._isMockFunction
34-
) {
35-
return 'legacy'
36-
}
37-
38-
if (
39-
typeof globalObj.setTimeout.clock !== 'undefined' &&
40-
typeof jest.getRealSystemTime !== 'undefined'
41-
) {
42-
try {
43-
// jest.getRealSystemTime is only supported for Jest's `modern` fake timers and otherwise throws
44-
jest.getRealSystemTime()
45-
return 'modern'
46-
} catch {
47-
// not using Jest's modern fake timers
48-
}
36+
return {
37+
callbackReturnValue,
38+
usedJestFakeTimers,
4939
}
50-
return null
5140
}
5241

53-
const jestFakeTimersAreEnabled = () => Boolean(getJestFakeTimersType())
42+
const jestFakeTimersAreEnabled = () =>
43+
Boolean(_runWithRealTimers(() => {}).usedJestFakeTimers)
5444

5545
// we only run our tests in node, and setImmediate is supported in node.
5646
// istanbul ignore next

0 commit comments

Comments
 (0)