Skip to content

Commit ffe2b79

Browse files
Sunil PaiKent C. Dodds
Sunil Pai
authored and
Kent C. Dodds
committed
fix: async act detection (#407)
The previous version of async act detection left an open hanging act scope, which broke tests and expectations. This PR delays the detection until it's been called at least once.
1 parent 4aa0c56 commit ffe2b79

File tree

4 files changed

+325
-59
lines changed

4 files changed

+325
-59
lines changed

src/__tests__/new-act.js

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
let asyncAct
2+
3+
jest.mock('react-dom/test-utils', () => ({
4+
act: cb => {
5+
return cb()
6+
},
7+
}))
8+
9+
beforeEach(() => {
10+
jest.resetModules()
11+
asyncAct = require('../act-compat').asyncAct
12+
jest.spyOn(console, 'error').mockImplementation(() => {})
13+
})
14+
15+
afterEach(() => {
16+
console.error.mockRestore()
17+
})
18+
19+
test('async act works when it does not exist (older versions of react)', async () => {
20+
const callback = jest.fn()
21+
await asyncAct(async () => {
22+
await Promise.resolve()
23+
await callback()
24+
})
25+
expect(console.error).toHaveBeenCalledTimes(0)
26+
expect(callback).toHaveBeenCalledTimes(1)
27+
28+
callback.mockClear()
29+
console.error.mockClear()
30+
31+
await asyncAct(async () => {
32+
await Promise.resolve()
33+
await callback()
34+
})
35+
expect(console.error).toHaveBeenCalledTimes(0)
36+
expect(callback).toHaveBeenCalledTimes(1)
37+
})
38+
39+
test('async act recovers from errors', async () => {
40+
try {
41+
await asyncAct(async () => {
42+
await null
43+
throw new Error('test error')
44+
})
45+
} catch (err) {
46+
console.error('call console.error')
47+
}
48+
expect(console.error).toHaveBeenCalledTimes(1)
49+
expect(console.error.mock.calls).toMatchInlineSnapshot(`
50+
Array [
51+
Array [
52+
"call console.error",
53+
],
54+
]
55+
`)
56+
})
57+
58+
test('async act recovers from sync errors', async () => {
59+
try {
60+
await asyncAct(() => {
61+
throw new Error('test error')
62+
})
63+
} catch (err) {
64+
console.error('call console.error')
65+
}
66+
expect(console.error).toHaveBeenCalledTimes(1)
67+
expect(console.error.mock.calls).toMatchInlineSnapshot(`
68+
Array [
69+
Array [
70+
"call console.error",
71+
],
72+
]
73+
`)
74+
})
75+
76+
/* eslint no-console:0 */

src/__tests__/no-act.js

+72-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,80 @@
1-
import {act} from '..'
1+
let act, asyncAct
2+
3+
beforeEach(() => {
4+
jest.resetModules()
5+
act = require('..').act
6+
asyncAct = require('../act-compat').asyncAct
7+
jest.spyOn(console, 'error').mockImplementation(() => {})
8+
})
9+
10+
afterEach(() => {
11+
console.error.mockRestore()
12+
})
213

314
jest.mock('react-dom/test-utils', () => ({}))
415

516
test('act works even when there is no act from test utils', () => {
617
const callback = jest.fn()
718
act(callback)
819
expect(callback).toHaveBeenCalledTimes(1)
20+
expect(console.error).toHaveBeenCalledTimes(0)
21+
})
22+
23+
test('async act works when it does not exist (older versions of react)', async () => {
24+
const callback = jest.fn()
25+
await asyncAct(async () => {
26+
await Promise.resolve()
27+
await callback()
28+
})
29+
expect(console.error).toHaveBeenCalledTimes(0)
30+
expect(callback).toHaveBeenCalledTimes(1)
31+
32+
callback.mockClear()
33+
console.error.mockClear()
34+
35+
await asyncAct(async () => {
36+
await Promise.resolve()
37+
await callback()
38+
})
39+
expect(console.error).toHaveBeenCalledTimes(0)
40+
expect(callback).toHaveBeenCalledTimes(1)
41+
})
42+
43+
test('async act recovers from errors', async () => {
44+
try {
45+
await asyncAct(async () => {
46+
await null
47+
throw new Error('test error')
48+
})
49+
} catch (err) {
50+
console.error('call console.error')
51+
}
52+
expect(console.error).toHaveBeenCalledTimes(1)
53+
expect(console.error.mock.calls).toMatchInlineSnapshot(`
54+
Array [
55+
Array [
56+
"call console.error",
57+
],
58+
]
59+
`)
60+
})
61+
62+
test('async act recovers from sync errors', async () => {
63+
try {
64+
await asyncAct(() => {
65+
throw new Error('test error')
66+
})
67+
} catch (err) {
68+
console.error('call console.error')
69+
}
70+
expect(console.error).toHaveBeenCalledTimes(1)
71+
expect(console.error.mock.calls).toMatchInlineSnapshot(`
72+
Array [
73+
Array [
74+
"call console.error",
75+
],
76+
]
77+
`)
978
})
79+
80+
/* eslint no-console:0 */

src/__tests__/old-act.js

+70-12
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,55 @@
1-
import {asyncAct} from '../act-compat'
1+
let asyncAct
2+
3+
beforeEach(() => {
4+
jest.resetModules()
5+
asyncAct = require('../act-compat').asyncAct
6+
jest.spyOn(console, 'error').mockImplementation(() => {})
7+
})
8+
9+
afterEach(() => {
10+
console.error.mockRestore()
11+
})
212

313
jest.mock('../react-dom-16.9.0-is-released', () => ({
414
reactDomSixteenPointNineIsReleased: true,
515
}))
616

717
jest.mock('react-dom/test-utils', () => ({
818
act: cb => {
9-
const promise = cb()
19+
cb()
1020
return {
1121
then() {
12-
console.error('blah, do not do this')
13-
return promise
22+
console.error(
23+
'Warning: Do not await the result of calling ReactTestUtils.act(...), it is not a Promise.',
24+
)
1425
},
1526
}
1627
},
1728
}))
1829

1930
test('async act works even when the act is an old one', async () => {
20-
jest.spyOn(console, 'error').mockImplementation(() => {})
2131
const callback = jest.fn()
2232
await asyncAct(async () => {
33+
console.error('sigil')
2334
await Promise.resolve()
2435
await callback()
36+
console.error('sigil')
2537
})
2638
expect(console.error.mock.calls).toMatchInlineSnapshot(`
27-
Array [
28-
Array [
29-
"It looks like you're using a version of react-dom that supports the \\"act\\" function, but not an awaitable version of \\"act\\" which you will need. Please upgrade to at least [email protected] to remove this warning.",
30-
],
31-
]
32-
`)
39+
Array [
40+
Array [
41+
Array [
42+
"sigil",
43+
],
44+
],
45+
Array [
46+
"It looks like you're using a version of react-dom that supports the \\"act\\" function, but not an awaitable version of \\"act\\" which you will need. Please upgrade to at least [email protected] to remove this warning.",
47+
],
48+
Array [
49+
"sigil",
50+
],
51+
]
52+
`)
3353
expect(callback).toHaveBeenCalledTimes(1)
3454

3555
// and it doesn't warn you twice
@@ -42,8 +62,46 @@ Array [
4262
})
4363
expect(console.error).toHaveBeenCalledTimes(0)
4464
expect(callback).toHaveBeenCalledTimes(1)
65+
})
4566

46-
console.error.mockRestore()
67+
test('async act recovers from async errors', async () => {
68+
try {
69+
await asyncAct(async () => {
70+
await null
71+
throw new Error('test error')
72+
})
73+
} catch (err) {
74+
console.error('call console.error')
75+
}
76+
expect(console.error).toHaveBeenCalledTimes(2)
77+
expect(console.error.mock.calls).toMatchInlineSnapshot(`
78+
Array [
79+
Array [
80+
"It looks like you're using a version of react-dom that supports the \\"act\\" function, but not an awaitable version of \\"act\\" which you will need. Please upgrade to at least [email protected] to remove this warning.",
81+
],
82+
Array [
83+
"call console.error",
84+
],
85+
]
86+
`)
87+
})
88+
89+
test('async act recovers from sync errors', async () => {
90+
try {
91+
await asyncAct(() => {
92+
throw new Error('test error')
93+
})
94+
} catch (err) {
95+
console.error('call console.error')
96+
}
97+
expect(console.error).toHaveBeenCalledTimes(1)
98+
expect(console.error.mock.calls).toMatchInlineSnapshot(`
99+
Array [
100+
Array [
101+
"call console.error",
102+
],
103+
]
104+
`)
47105
})
48106

49107
/* eslint no-console:0 */

0 commit comments

Comments
 (0)