Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 7669700

Browse files
committed
Use async act
1 parent c3e3d90 commit 7669700

24 files changed

+716
-477
lines changed

‎jest.config.js‎

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@ module.exports = Object.assign(jestConfig, {
66
// Full coverage across the build matrix (React 18, 19) but not in a single job
77
// Ful coverage is checked via codecov
88
'./src/act-compat': {
9-
branches: 90,
9+
branches: 80,
1010
},
1111
'./src/pure': {
1212
// minimum coverage of jobs using React 18 and 19
13-
branches: 95,
14-
functions: 88,
15-
lines: 92,
16-
statements: 92,
13+
branches: 90,
14+
functions: 81,
15+
lines: 91,
16+
statements: 91,
1717
},
1818
},
1919
})

‎package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@
9090
"react/no-adjacent-inline-elements": "off",
9191
"import/no-unassigned-import": "off",
9292
"import/named": "off",
93+
"testing-library/no-await-sync-events": "off",
9394
"testing-library/no-container": "off",
9495
"testing-library/no-debugging-utils": "off",
9596
"testing-library/no-dom-import": "off",

‎src/__tests__/act-compat.js‎

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
import * as React from 'react'
2+
import {render, fireEvent, screen} from '../'
3+
import {actIfEnabled} from '../act-compat'
4+
5+
beforeEach(() => {
6+
global.IS_REACT_ACT_ENVIRONMENT = true
7+
})
8+
9+
test('render calls useEffect immediately', async () => {
10+
const effectCb = jest.fn()
11+
function MyUselessComponent() {
12+
React.useEffect(effectCb)
13+
return null
14+
}
15+
await render(<MyUselessComponent />)
16+
expect(effectCb).toHaveBeenCalledTimes(1)
17+
})
18+
19+
test('findByTestId returns the element', async () => {
20+
const ref = React.createRef()
21+
await render(<div ref={ref} data-testid="foo" />)
22+
expect(await screen.findByTestId('foo')).toBe(ref.current)
23+
})
24+
25+
test('fireEvent triggers useEffect calls', async () => {
26+
const effectCb = jest.fn()
27+
function Counter() {
28+
React.useEffect(effectCb)
29+
const [count, setCount] = React.useState(0)
30+
return <button onClick={() => setCount(count + 1)}>{count}</button>
31+
}
32+
const {
33+
container: {firstChild: buttonNode},
34+
} = await render(<Counter />)
35+
36+
effectCb.mockClear()
37+
// eslint-disable-next-line testing-library/no-await-sync-events -- TODO: Remove lint rule.
38+
await fireEvent.click(buttonNode)
39+
expect(buttonNode).toHaveTextContent('1')
40+
expect(effectCb).toHaveBeenCalledTimes(1)
41+
})
42+
43+
test('calls to hydrate will run useEffects', async () => {
44+
const effectCb = jest.fn()
45+
function MyUselessComponent() {
46+
React.useEffect(effectCb)
47+
return null
48+
}
49+
await render(<MyUselessComponent />, {hydrate: true})
50+
expect(effectCb).toHaveBeenCalledTimes(1)
51+
})
52+
53+
test('cleans up IS_REACT_ACT_ENVIRONMENT if its callback throws', async () => {
54+
global.IS_REACT_ACT_ENVIRONMENT = false
55+
56+
await expect(() =>
57+
actIfEnabled(() => {
58+
throw new Error('threw')
59+
}),
60+
).rejects.toThrow('threw')
61+
62+
expect(global.IS_REACT_ACT_ENVIRONMENT).toEqual(false)
63+
})
64+
65+
test('cleans up IS_REACT_ACT_ENVIRONMENT if its async callback throws', async () => {
66+
global.IS_REACT_ACT_ENVIRONMENT = false
67+
68+
await expect(() =>
69+
actIfEnabled(async () => {
70+
throw new Error('thenable threw')
71+
}),
72+
).rejects.toThrow('thenable threw')
73+
74+
expect(global.IS_REACT_ACT_ENVIRONMENT).toEqual(false)
75+
})
76+
77+
test('state update from microtask does not trigger "missing act" warning', async () => {
78+
let triggerStateUpdateFromMicrotask
79+
function App() {
80+
const [state, setState] = React.useState(0)
81+
triggerStateUpdateFromMicrotask = () => setState(1)
82+
React.useEffect(() => {
83+
// eslint-disable-next-line jest/no-conditional-in-test
84+
if (state === 1) {
85+
Promise.resolve().then(() => {
86+
setState(2)
87+
})
88+
}
89+
}, [state])
90+
return state
91+
}
92+
const {container} = await render(<App />)
93+
94+
await actIfEnabled(() => {
95+
triggerStateUpdateFromMicrotask()
96+
})
97+
98+
expect(container).toHaveTextContent('2')
99+
})

‎src/__tests__/act.js‎

Lines changed: 18 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,69 +1,26 @@
11
import * as React from 'react'
2-
import {act, render,fireEvent,screen} from '../'
2+
import {act, render} from '../'
33

4-
test('render calls useEffect immediately', () => {
5-
const effectCb = jest.fn()
6-
function MyUselessComponent() {
7-
React.useEffect(effectCb)
8-
return null
9-
}
10-
render(<MyUselessComponent />)
11-
expect(effectCb).toHaveBeenCalledTimes(1)
12-
})
13-
14-
test('findByTestId returns the element', async () => {
15-
const ref = React.createRef()
16-
render(<div ref={ref} data-testid="foo" />)
17-
expect(await screen.findByTestId('foo')).toBe(ref.current)
18-
})
19-
20-
test('fireEvent triggers useEffect calls', () => {
21-
const effectCb = jest.fn()
22-
function Counter() {
23-
React.useEffect(effectCb)
24-
const [count, setCount] = React.useState(0)
25-
return <button onClick={() => setCount(count + 1)}>{count}</button>
26-
}
27-
const {
28-
container: {firstChild: buttonNode},
29-
} = render(<Counter />)
30-
31-
effectCb.mockClear()
32-
fireEvent.click(buttonNode)
33-
expect(buttonNode).toHaveTextContent('1')
34-
expect(effectCb).toHaveBeenCalledTimes(1)
4+
beforeEach(() => {
5+
global.IS_REACT_ACT_ENVIRONMENT = true
356
})
367

37-
test('calls to hydrate will run useEffects', () => {
38-
const effectCb = jest.fn()
39-
function MyUselessComponent() {
40-
React.useEffect(effectCb)
41-
return null
8+
test('does not work outside IS_REACT_ENVIRONMENT like React.act', async () => {
9+
let setState
10+
function Component() {
11+
const [state, _setState] = React.useState(0)
12+
setState = _setState
13+
return state
4214
}
43-
render(<MyUselessComponent />, {hydrate: true})
44-
expect(effectCb).toHaveBeenCalledTimes(1)
45-
})
15+
await render(<Component />)
4616

47-
test('cleans up IS_REACT_ACT_ENVIRONMENT if its callback throws', () => {
4817
global.IS_REACT_ACT_ENVIRONMENT = false
49-
50-
expect(() =>
51-
act(() => {
52-
throw new Error('threw')
53-
}),
54-
).toThrow('threw')
55-
56-
expect(global.IS_REACT_ACT_ENVIRONMENT).toEqual(false)
57-
})
58-
59-
test('cleans up IS_REACT_ACT_ENVIRONMENT if its async callback throws', async () => {
60-
global.IS_REACT_ACT_ENVIRONMENT = false
61-
62-
await expect(() =>
63-
act(async () => {
64-
throw new Error('thenable threw')
65-
}),
66-
).rejects.toThrow('thenable threw')
67-
68-
expect(global.IS_REACT_ACT_ENVIRONMENT).toEqual(false)
18+
await expect(async () => {
19+
await act(() => {
20+
setState(1)
21+
})
22+
}).toErrorDev(
23+
'The current testing environment is not configured to support act(...)',
24+
{withoutStack: true},
25+
)
6926
})

‎src/__tests__/auto-cleanup-skip.js‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,15 @@ import * as React from 'react'
33
let render
44
beforeAll(() => {
55
process.env.RTL_SKIP_AUTO_CLEANUP = 'true'
6+
globalThis.IS_REACT_ACT_ENVIRONMENT = true
67
const rtl = require('../')
78
render = rtl.render
89
})
910

1011
// This one verifies that if RTL_SKIP_AUTO_CLEANUP is set
1112
// then we DON'T auto-wire up the afterEach for folks
12-
test('first', () => {
13-
render(<div>hi</div>)
13+
test('first', async() => {
14+
awaitrender(<div>hi</div>)
1415
})
1516

1617
test('second', () => {

‎src/__tests__/auto-cleanup.js‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import {render} from '../'
44
// This just verifies that by importing RTL in an
55
// environment which supports afterEach (like jest)
66
// we'll get automatic cleanup between tests.
7-
test('first', () => {
8-
render(<div>hi</div>)
7+
test('first', async() => {
8+
awaitrender(<div>hi</div>)
99
})
1010

1111
test('second', () => {

‎src/__tests__/cleanup.js‎

Lines changed: 32 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import * as React from 'react'
22
import {render, cleanup} from '../'
33

4-
test('cleans up the document', () => {
4+
test('cleans up the document', async() => {
55
const spy = jest.fn()
66
const divId = 'my-div'
77

@@ -16,18 +16,18 @@ test('cleans up the document', () => {
1616
}
1717
}
1818

19-
render(<Test />)
20-
cleanup()
19+
awaitrender(<Test />)
20+
awaitcleanup()
2121
expect(document.body).toBeEmptyDOMElement()
2222
expect(spy).toHaveBeenCalledTimes(1)
2323
})
2424

25-
test('cleanup does not error when an element is not a child', () => {
26-
render(<div />, {container: document.createElement('div')})
27-
cleanup()
25+
test('cleanup does not error when an element is not a child', async() => {
26+
awaitrender(<div />, {container: document.createElement('div')})
27+
awaitcleanup()
2828
})
2929

30-
test('cleanup runs effect cleanup functions', () => {
30+
test('cleanup runs effect cleanup functions', async() => {
3131
const spy = jest.fn()
3232

3333
const Test = () => {
@@ -36,11 +36,23 @@ test('cleanup runs effect cleanup functions', () => {
3636
return null
3737
}
3838

39-
render(<Test />)
40-
cleanup()
39+
awaitrender(<Test />)
40+
awaitcleanup()
4141
expect(spy).toHaveBeenCalledTimes(1)
4242
})
4343

44+
test('cleanup cleans up every root and disconnects containers', async () => {
45+
const {container: container1} = await render(<div />)
46+
const {container: container2} = await render(<span />)
47+
48+
await cleanup()
49+
50+
expect(container1).toBeEmptyDOMElement()
51+
expect(container1.isConnected).toBe(false)
52+
expect(container2).toBeEmptyDOMElement()
53+
expect(container2.isConnected).toBe(false)
54+
})
55+
4456
describe('fake timers and missing act warnings', () => {
4557
beforeEach(() => {
4658
jest.resetAllMocks()
@@ -55,7 +67,7 @@ describe('fake timers and missing act warnings', () => {
5567
jest.useRealTimers()
5668
})
5769

58-
test('cleanup does not flush microtasks', () => {
70+
test('cleanup does flush microtasks',async () => {
5971
const microTaskSpy = jest.fn()
6072
function Test() {
6173
const counter = 1
@@ -72,22 +84,25 @@ describe('fake timers and missing act warnings', () => {
7284

7385
return () => {
7486
cancelled = true
87+
Promise.resolve().then(() => {
88+
microTaskSpy()
89+
})
7590
}
7691
}, [counter])
7792

7893
return null
7994
}
80-
render(<Test />)
81-
82-
cleanup()
95+
await render(<Test />)
96+
expect(microTaskSpy).toHaveBeenCalledTimes(1)
8397

84-
expect(microTaskSpy).toHaveBeenCalledTimes(0)
98+
await cleanup()
99+
expect(microTaskSpy).toHaveBeenCalledTimes(2)
85100
// console.error is mocked
86101
// eslint-disable-next-line no-console
87102
expect(console.error).toHaveBeenCalledTimes(0)
88103
})
89104

90-
test('cleanup does not swallow missing act warnings', () => {
105+
test('cleanup does not swallow missing act warnings', async() => {
91106
const deferredStateUpdateSpy = jest.fn()
92107
function Test() {
93108
const counter = 1
@@ -109,10 +124,10 @@ describe('fake timers and missing act warnings', () => {
109124

110125
return null
111126
}
112-
render(<Test />)
127+
awaitrender(<Test />)
113128

114129
jest.runAllTimers()
115-
cleanup()
130+
awaitcleanup()
116131

117132
expect(deferredStateUpdateSpy).toHaveBeenCalledTimes(1)
118133
// console.error is mocked

‎src/__tests__/debug.js‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,24 @@ afterEach(() => {
99
console.log.mockRestore()
1010
})
1111

12-
test('debug pretty prints the container', () => {
12+
test('debug pretty prints the container', async() => {
1313
const HelloWorld = () => <h1>Hello World</h1>
14-
const {debug} = render(<HelloWorld />)
14+
const {debug} = awaitrender(<HelloWorld />)
1515
debug()
1616
expect(console.log).toHaveBeenCalledTimes(1)
1717
expect(console.log).toHaveBeenCalledWith(
1818
expect.stringContaining('Hello World'),
1919
)
2020
})
2121

22-
test('debug pretty prints multiple containers', () => {
22+
test('debug pretty prints multiple containers', async() => {
2323
const HelloWorld = () => (
2424
<>
2525
<h1 data-testid="testId">Hello World</h1>
2626
<h1 data-testid="testId">Hello World</h1>
2727
</>
2828
)
29-
const {debug} = render(<HelloWorld />)
29+
const {debug} = awaitrender(<HelloWorld />)
3030
const multipleElements = screen.getAllByTestId('testId')
3131
debug(multipleElements)
3232

@@ -36,9 +36,9 @@ test('debug pretty prints multiple containers', () => {
3636
)
3737
})
3838

39-
test('allows same arguments as prettyDOM', () => {
39+
test('allows same arguments as prettyDOM', async() => {
4040
const HelloWorld = () => <h1>Hello World</h1>
41-
const {debug, container} = render(<HelloWorld />)
41+
const {debug, container} = awaitrender(<HelloWorld />)
4242
debug(container, 6, {highlight: false})
4343
expect(console.log).toHaveBeenCalledTimes(1)
4444
expect(console.log.mock.calls[0]).toMatchInlineSnapshot(`

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /