Skip to content

Commit 2d75d52

Browse files
committed
fix async act detection
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 2d75d52

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)