Skip to content

Commit 28abcd0

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 28abcd0

File tree

4 files changed

+328
-57
lines changed

4 files changed

+328
-57
lines changed

src/__tests__/new-act.js

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

src/__tests__/no-act.js

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
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+
})
28

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

@@ -7,3 +13,69 @@ test('act works even when there is no act from test utils', () => {
713
act(callback)
814
expect(callback).toHaveBeenCalledTimes(1)
915
})
16+
17+
test('async act works when it does not exist (older versions of react)', async () => {
18+
jest.spyOn(console, 'error').mockImplementation(() => {})
19+
const callback = jest.fn()
20+
await asyncAct(async () => {
21+
await Promise.resolve()
22+
await callback()
23+
})
24+
expect(console.error).toHaveBeenCalledTimes(0)
25+
expect(callback).toHaveBeenCalledTimes(1)
26+
27+
callback.mockClear()
28+
console.error.mockClear()
29+
30+
await asyncAct(async () => {
31+
await Promise.resolve()
32+
await callback()
33+
})
34+
expect(console.error).toHaveBeenCalledTimes(0)
35+
expect(callback).toHaveBeenCalledTimes(1)
36+
37+
console.error.mockRestore()
38+
})
39+
40+
test('async act recovers from errors', async () => {
41+
jest.spyOn(console, 'error').mockImplementation(() => {})
42+
try {
43+
await asyncAct(async () => {
44+
await null
45+
throw new Error('test error')
46+
})
47+
} catch (err) {
48+
console.error('call console.error')
49+
}
50+
expect(console.error).toHaveBeenCalledTimes(1)
51+
expect(console.error.mock.calls).toMatchInlineSnapshot(`
52+
Array [
53+
Array [
54+
"call console.error",
55+
],
56+
]
57+
`)
58+
console.error.mockRestore()
59+
})
60+
61+
test('async act recovers from sync errors', async () => {
62+
jest.spyOn(console, 'error').mockImplementation(() => {})
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+
`)
78+
console.error.mockRestore()
79+
})
80+
81+
/* eslint no-console:0 */

src/__tests__/old-act.js

Lines changed: 70 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
1-
import {asyncAct} from '../act-compat'
1+
let asyncAct
2+
3+
beforeEach(() => {
4+
jest.resetModules()
5+
asyncAct = require('../act-compat').asyncAct
6+
})
27

38
jest.mock('../react-dom-16.9.0-is-released', () => ({
49
reactDomSixteenPointNineIsReleased: true,
510
}))
611

712
jest.mock('react-dom/test-utils', () => ({
813
act: cb => {
9-
const promise = cb()
14+
cb()
1015
return {
1116
then() {
12-
console.error('blah, do not do this')
13-
return promise
17+
console.error(
18+
'Warning: Do not await the result of calling ReactTestUtils.act(...), it is not a Promise.',
19+
)
1420
},
1521
}
1622
},
@@ -20,16 +26,26 @@ test('async act works even when the act is an old one', async () => {
2026
jest.spyOn(console, 'error').mockImplementation(() => {})
2127
const callback = jest.fn()
2228
await asyncAct(async () => {
29+
console.error('sigil')
2330
await Promise.resolve()
2431
await callback()
32+
console.error('sigil')
2533
})
2634
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-
`)
35+
Array [
36+
Array [
37+
Array [
38+
"sigil",
39+
],
40+
],
41+
Array [
42+
"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.",
43+
],
44+
Array [
45+
"sigil",
46+
],
47+
]
48+
`)
3349
expect(callback).toHaveBeenCalledTimes(1)
3450

3551
// and it doesn't warn you twice
@@ -46,4 +62,48 @@ Array [
4662
console.error.mockRestore()
4763
})
4864

65+
test('async act recovers from async errors', async () => {
66+
jest.spyOn(console, 'error').mockImplementation(() => {})
67+
try {
68+
await asyncAct(async () => {
69+
await null
70+
throw new Error('test error')
71+
})
72+
} catch (err) {
73+
console.error('call console.error')
74+
}
75+
expect(console.error).toHaveBeenCalledTimes(2)
76+
expect(console.error.mock.calls).toMatchInlineSnapshot(`
77+
Array [
78+
Array [
79+
"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.",
80+
],
81+
Array [
82+
"call console.error",
83+
],
84+
]
85+
`)
86+
console.error.mockRestore()
87+
})
88+
89+
test('async act recovers from sync errors', async () => {
90+
jest.spyOn(console, 'error').mockImplementation(() => {})
91+
try {
92+
await asyncAct(() => {
93+
throw new Error('test error')
94+
})
95+
} catch (err) {
96+
console.error('call console.error')
97+
}
98+
expect(console.error).toHaveBeenCalledTimes(1)
99+
expect(console.error.mock.calls).toMatchInlineSnapshot(`
100+
Array [
101+
Array [
102+
"call console.error",
103+
],
104+
]
105+
`)
106+
console.error.mockRestore()
107+
})
108+
49109
/* eslint no-console:0 */

0 commit comments

Comments
 (0)