Skip to content

Commit e6a0473

Browse files
author
Sunil Pai
authored
Warn when rendering tests in concurrent/batched mode without a mocked scheduler (#16207)
Concurrent/Batched mode tests should always be run with a mocked scheduler (v17 or not). This PR adds a warning for the same. I'll put up a separate PR to the docs with a page detailing how to mock the scheduler.
1 parent e276a5e commit e6a0473

18 files changed

+441
-319
lines changed
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @emails react-core
8+
*/
9+
10+
let React;
11+
let TestUtils;
12+
let TestRenderer;
13+
14+
global.__DEV__ = process.env.NODE_ENV !== 'production';
15+
16+
expect.extend(require('../toWarnDev'));
17+
18+
describe('unmocked scheduler', () => {
19+
beforeEach(() => {
20+
jest.resetModules();
21+
React = require('react');
22+
TestUtils = require('react-dom/test-utils');
23+
TestRenderer = require('react-test-renderer');
24+
});
25+
26+
it('flushes work only outside the outermost act() corresponding to its own renderer', () => {
27+
let log = [];
28+
function Effecty() {
29+
React.useEffect(() => {
30+
log.push('called');
31+
}, []);
32+
return null;
33+
}
34+
// in legacy mode, this tests whether an act only flushes its own effects
35+
TestRenderer.act(() => {
36+
TestUtils.act(() => {
37+
TestRenderer.create(<Effecty />);
38+
});
39+
expect(log).toEqual([]);
40+
});
41+
expect(log).toEqual(['called']);
42+
43+
log = [];
44+
// for doublechecking, we flip it inside out, and assert on the outermost
45+
TestUtils.act(() => {
46+
TestRenderer.act(() => {
47+
TestRenderer.create(<Effecty />);
48+
});
49+
expect(log).toEqual(['called']);
50+
});
51+
expect(log).toEqual(['called']);
52+
});
53+
});
54+
55+
describe('mocked scheduler', () => {
56+
beforeEach(() => {
57+
jest.resetModules();
58+
jest.mock('scheduler', () =>
59+
require.requireActual('scheduler/unstable_mock')
60+
);
61+
React = require('react');
62+
TestUtils = require('react-dom/test-utils');
63+
TestRenderer = require('react-test-renderer');
64+
});
65+
66+
afterEach(() => {
67+
jest.unmock('scheduler');
68+
});
69+
70+
it('flushes work only outside the outermost act()', () => {
71+
let log = [];
72+
function Effecty() {
73+
React.useEffect(() => {
74+
log.push('called');
75+
}, []);
76+
return null;
77+
}
78+
// with a mocked scheduler, this tests whether it flushes all work only on the outermost act
79+
TestRenderer.act(() => {
80+
TestUtils.act(() => {
81+
TestRenderer.create(<Effecty />);
82+
});
83+
expect(log).toEqual([]);
84+
});
85+
expect(log).toEqual(['called']);
86+
87+
log = [];
88+
// for doublechecking, we flip it inside out, and assert on the outermost
89+
TestUtils.act(() => {
90+
TestRenderer.act(() => {
91+
TestRenderer.create(<Effecty />);
92+
});
93+
expect(log).toEqual([]);
94+
});
95+
expect(log).toEqual(['called']);
96+
});
97+
});
Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @emails react-core
8+
*/
9+
10+
let React;
11+
let ReactDOM;
12+
let ReactART;
13+
let ARTSVGMode;
14+
let ARTCurrentMode;
15+
let TestUtils;
16+
let TestRenderer;
17+
let ARTTest;
18+
19+
global.__DEV__ = process.env.NODE_ENV !== 'production';
20+
21+
expect.extend(require('../toWarnDev'));
22+
23+
function App(props) {
24+
return 'hello world';
25+
}
26+
27+
beforeEach(() => {
28+
jest.resetModules();
29+
React = require('react');
30+
ReactDOM = require('react-dom');
31+
ReactART = require('react-art');
32+
ARTSVGMode = require('art/modes/svg');
33+
ARTCurrentMode = require('art/modes/current');
34+
TestUtils = require('react-dom/test-utils');
35+
TestRenderer = require('react-test-renderer');
36+
37+
ARTCurrentMode.setCurrent(ARTSVGMode);
38+
39+
ARTTest = function ARTTestComponent(props) {
40+
return (
41+
<ReactART.Surface width={150} height={200}>
42+
<ReactART.Group>
43+
<ReactART.Shape
44+
d="M0,0l50,0l0,50l-50,0z"
45+
fill={new ReactART.LinearGradient(['black', 'white'])}
46+
key="a"
47+
width={50}
48+
height={50}
49+
x={50}
50+
y={50}
51+
opacity={0.1}
52+
/>
53+
<ReactART.Shape
54+
fill="#3C5A99"
55+
key="b"
56+
scale={0.5}
57+
x={50}
58+
y={50}
59+
title="This is an F"
60+
cursor="pointer">
61+
M64.564,38.583H54l0.008-5.834c0-3.035,0.293-4.666,4.657-4.666
62+
h5.833V16.429h-9.33c-11.213,0-15.159,5.654-15.159,15.16v6.994
63+
h-6.99v11.652h6.99v33.815H54V50.235h9.331L64.564,38.583z
64+
</ReactART.Shape>
65+
</ReactART.Group>
66+
</ReactART.Surface>
67+
);
68+
};
69+
});
70+
71+
it("doesn't warn when you use the right act + renderer: dom", () => {
72+
TestUtils.act(() => {
73+
TestUtils.renderIntoDocument(<App />);
74+
});
75+
});
76+
77+
it("doesn't warn when you use the right act + renderer: test", () => {
78+
TestRenderer.act(() => {
79+
TestRenderer.create(<App />);
80+
});
81+
});
82+
83+
it('resets correctly across renderers', () => {
84+
function Effecty() {
85+
React.useEffect(() => {}, []);
86+
return null;
87+
}
88+
TestUtils.act(() => {
89+
TestRenderer.act(() => {});
90+
expect(() => {
91+
TestRenderer.create(<Effecty />);
92+
}).toWarnDev(["It looks like you're using the wrong act()"], {
93+
withoutStack: true,
94+
});
95+
});
96+
});
97+
98+
it('warns when using the wrong act version - test + dom: render', () => {
99+
expect(() => {
100+
TestRenderer.act(() => {
101+
TestUtils.renderIntoDocument(<App />);
102+
});
103+
}).toWarnDev(["It looks like you're using the wrong act()"], {
104+
withoutStack: true,
105+
});
106+
});
107+
108+
it('warns when using the wrong act version - test + dom: updates', () => {
109+
let setCtr;
110+
function Counter(props) {
111+
const [ctr, _setCtr] = React.useState(0);
112+
setCtr = _setCtr;
113+
return ctr;
114+
}
115+
TestUtils.renderIntoDocument(<Counter />);
116+
expect(() => {
117+
TestRenderer.act(() => {
118+
setCtr(1);
119+
});
120+
}).toWarnDev(["It looks like you're using the wrong act()"]);
121+
});
122+
123+
it('warns when using the wrong act version - dom + test: .create()', () => {
124+
expect(() => {
125+
TestUtils.act(() => {
126+
TestRenderer.create(<App />);
127+
});
128+
}).toWarnDev(["It looks like you're using the wrong act()"], {
129+
withoutStack: true,
130+
});
131+
});
132+
133+
it('warns when using the wrong act version - dom + test: .update()', () => {
134+
const root = TestRenderer.create(<App key="one" />);
135+
expect(() => {
136+
TestUtils.act(() => {
137+
root.update(<App key="two" />);
138+
});
139+
}).toWarnDev(["It looks like you're using the wrong act()"], {
140+
withoutStack: true,
141+
});
142+
});
143+
144+
it('warns when using the wrong act version - dom + test: updates', () => {
145+
let setCtr;
146+
function Counter(props) {
147+
const [ctr, _setCtr] = React.useState(0);
148+
setCtr = _setCtr;
149+
return ctr;
150+
}
151+
TestRenderer.create(<Counter />);
152+
expect(() => {
153+
TestUtils.act(() => {
154+
setCtr(1);
155+
});
156+
}).toWarnDev(["It looks like you're using the wrong act()"]);
157+
});
158+
159+
it('does not warn when nesting react-act inside react-dom', () => {
160+
TestUtils.act(() => {
161+
TestUtils.renderIntoDocument(<ARTTest />);
162+
});
163+
});
164+
165+
it('does not warn when nesting react-act inside react-test-renderer', () => {
166+
TestRenderer.act(() => {
167+
TestRenderer.create(<ARTTest />);
168+
});
169+
});
170+
171+
it("doesn't warn if you use nested acts from different renderers", () => {
172+
TestRenderer.act(() => {
173+
TestUtils.act(() => {
174+
TestRenderer.create(<App />);
175+
});
176+
});
177+
});
178+
179+
it('warns when using createRoot() + .render', () => {
180+
const root = ReactDOM.unstable_createRoot(document.createElement('div'));
181+
expect(() => {
182+
TestRenderer.act(() => {
183+
root.render(<App />);
184+
});
185+
}).toWarnDev(
186+
[
187+
'In Concurrent or Sync modes, the "scheduler" module needs to be mocked',
188+
"It looks like you're using the wrong act()",
189+
],
190+
{
191+
withoutStack: true,
192+
}
193+
);
194+
});

0 commit comments

Comments
 (0)