@samitouri / QOS-React-2 / commits / e6a0473c3c

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.

Sunil Pai committed Jul 30, 2019 at 19:00 UTC e6a0473c3c6f501dbe291f60b9ee35760ab99eed
18 files changed +441 -319
fixtures/dom/src/__tests__/nested-act-test.js new
+97
@@ -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 +});
fixtures/dom/src/__tests__/wrong-act-test.js new
+194
@@ -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 +});
fixtures/dom/src/index.test.js deleted
-233
@@ -1,233 +0,0 @@
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 -describe('legacy mode', () => {
28 - runTests();
29 -});
30 -
31 -describe('mocked scheduler', () => {
32 - beforeEach(() => {
33 - jest.mock('scheduler', () =>
34 - require.requireActual('scheduler/unstable_mock')
35 - );
36 - });
37 - afterEach(() => {
38 - jest.unmock('scheduler');
39 - });
40 - runTests();
41 -});
42 -
43 -function runTests() {
44 - beforeEach(() => {
45 - jest.resetModules();
46 - React = require('react');
47 - ReactDOM = require('react-dom');
48 - ReactART = require('react-art');
49 - ARTSVGMode = require('art/modes/svg');
50 - ARTCurrentMode = require('art/modes/current');
51 - TestUtils = require('react-dom/test-utils');
52 - TestRenderer = require('react-test-renderer');
53 -
54 - ARTCurrentMode.setCurrent(ARTSVGMode);
55 -
56 - ARTTest = function ARTTest(props) {
57 - return (
58 - <ReactART.Surface width={150} height={200}>
59 - <ReactART.Group>
60 - <ReactART.Shape
61 - d="M0,0l50,0l0,50l-50,0z"
62 - fill={new ReactART.LinearGradient(['black', 'white'])}
63 - key="a"
64 - width={50}
65 - height={50}
66 - x={50}
67 - y={50}
68 - opacity={0.1}
69 - />
70 - <ReactART.Shape
71 - fill="#3C5A99"
72 - key="b"
73 - scale={0.5}
74 - x={50}
75 - y={50}
76 - title="This is an F"
77 - cursor="pointer">
78 - M64.564,38.583H54l0.008-5.834c0-3.035,0.293-4.666,4.657-4.666
79 - h5.833V16.429h-9.33c-11.213,0-15.159,5.654-15.159,15.16v6.994
80 - h-6.99v11.652h6.99v33.815H54V50.235h9.331L64.564,38.583z
81 - </ReactART.Shape>
82 - </ReactART.Group>
83 - </ReactART.Surface>
84 - );
85 - };
86 - });
87 - it("doesn't warn when you use the right act + renderer: dom", () => {
88 - TestUtils.act(() => {
89 - TestUtils.renderIntoDocument(<App />);
90 - });
91 - });
92 -
93 - it("doesn't warn when you use the right act + renderer: test", () => {
94 - TestRenderer.act(() => {
95 - TestRenderer.create(<App />);
96 - });
97 - });
98 -
99 - it('resets correctly across renderers', () => {
100 - function Effecty() {
101 - React.useEffect(() => {}, []);
102 - return null;
103 - }
104 - TestUtils.act(() => {
105 - TestRenderer.act(() => {});
106 - expect(() => {
107 - TestRenderer.create(<Effecty />);
108 - }).toWarnDev(["It looks like you're using the wrong act()"], {
109 - withoutStack: true,
110 - });
111 - });
112 - });
113 -
114 - it('warns when using createRoot() + .render', () => {
115 - const root = ReactDOM.unstable_createRoot(document.createElement('div'));
116 - expect(() => {
117 - TestRenderer.act(() => {
118 - root.render(<App />);
119 - });
120 - }).toWarnDev(["It looks like you're using the wrong act()"], {
121 - withoutStack: true,
122 - });
123 - });
124 -
125 - it('warns when using the wrong act version - test + dom: render', () => {
126 - expect(() => {
127 - TestRenderer.act(() => {
128 - TestUtils.renderIntoDocument(<App />);
129 - });
130 - }).toWarnDev(["It looks like you're using the wrong act()"], {
131 - withoutStack: true,
132 - });
133 - });
134 -
135 - it('warns when using the wrong act version - test + dom: updates', () => {
136 - let setCtr;
137 - function Counter(props) {
138 - const [ctr, _setCtr] = React.useState(0);
139 - setCtr = _setCtr;
140 - return ctr;
141 - }
142 - TestUtils.renderIntoDocument(<Counter />);
143 - expect(() => {
144 - TestRenderer.act(() => {
145 - setCtr(1);
146 - });
147 - }).toWarnDev(["It looks like you're using the wrong act()"]);
148 - });
149 -
150 - it('warns when using the wrong act version - dom + test: .create()', () => {
151 - expect(() => {
152 - TestUtils.act(() => {
153 - TestRenderer.create(<App />);
154 - });
155 - }).toWarnDev(["It looks like you're using the wrong act()"], {
156 - withoutStack: true,
157 - });
158 - });
159 -
160 - it('warns when using the wrong act version - dom + test: .update()', () => {
161 - const root = TestRenderer.create(<App key="one" />);
162 - expect(() => {
163 - TestUtils.act(() => {
164 - root.update(<App key="two" />);
165 - });
166 - }).toWarnDev(["It looks like you're using the wrong act()"], {
167 - withoutStack: true,
168 - });
169 - });
170 -
171 - it('warns when using the wrong act version - dom + test: updates', () => {
172 - let setCtr;
173 - function Counter(props) {
174 - const [ctr, _setCtr] = React.useState(0);
175 - setCtr = _setCtr;
176 - return ctr;
177 - }
178 - const root = TestRenderer.create(<Counter />);
179 - expect(() => {
180 - TestUtils.act(() => {
181 - setCtr(1);
182 - });
183 - }).toWarnDev(["It looks like you're using the wrong act()"]);
184 - });
185 -
186 - it('does not warn when nesting react-act inside react-dom', () => {
187 - TestUtils.act(() => {
188 - TestUtils.renderIntoDocument(<ARTTest />);
189 - });
190 - });
191 -
192 - it('does not warn when nesting react-act inside react-test-renderer', () => {
193 - TestRenderer.act(() => {
194 - TestRenderer.create(<ARTTest />);
195 - });
196 - });
197 -
198 - it("doesn't warn if you use nested acts from different renderers", () => {
199 - TestRenderer.act(() => {
200 - TestUtils.act(() => {
201 - TestRenderer.create(<App />);
202 - });
203 - });
204 - });
205 -
206 - it('flushes work only outside the outermost act(), even when nested from different renderers', () => {
207 - const log = [];
208 - function Effecty() {
209 - React.useEffect(() => {
210 - log.push('called');
211 - }, []);
212 - return null;
213 - }
214 - // in legacy mode, this tests whether an act only flushes its own effects
215 - // with a mocked scheduler, this tests whether it flushes all work only on the outermost act
216 - TestRenderer.act(() => {
217 - TestUtils.act(() => {
218 - TestRenderer.create(<Effecty />);
219 - });
220 - expect(log).toEqual([]);
221 - });
222 - expect(log).toEqual(['called']);
223 -
224 - log.splice(0);
225 - // for doublechecking, we flip it inside out, and assert on the outermost
226 - TestUtils.act(() => {
227 - TestRenderer.act(() => {
228 - TestRenderer.create(<Effecty />);
229 - });
230 - });
231 - expect(log).toEqual(['called']);
232 - });
233 -}
packages/react-dom/src/__tests__/ReactTestUtilsAct-test.internal.js deleted
-28
@@ -1,28 +0,0 @@
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 ReactFeatureFlags;
11 -let act;
12 -describe('mocked scheduler', () => {
13 - beforeEach(() => {
14 - jest.resetModules();
15 - ReactFeatureFlags = require('shared/ReactFeatureFlags');
16 - ReactFeatureFlags.warnAboutMissingMockScheduler = true;
17 - jest.unmock('scheduler');
18 - act = require('react-dom/test-utils').act;
19 - });
20 - it("should warn when the scheduler isn't mocked", () => {
21 - expect(() => act(() => {})).toWarnDev(
22 - [
23 - 'Starting from React v17, the "scheduler" module will need to be mocked',
24 - ],
25 - {withoutStack: true},
26 - );
27 - });
28 -});
packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.internal.js new
+42
@@ -0,0 +1,42 @@
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 ReactFeatureFlags;
13 +
14 +function App() {
15 + return null;
16 +}
17 +
18 +beforeEach(() => {
19 + jest.resetModules();
20 + jest.unmock('scheduler');
21 + React = require('react');
22 + ReactDOM = require('react-dom');
23 + ReactFeatureFlags = require('shared/ReactFeatureFlags');
24 + ReactFeatureFlags.warnAboutUnmockedScheduler = true;
25 +});
26 +
27 +afterEach(() => {
28 + ReactFeatureFlags.warnAboutUnmockedScheduler = false;
29 +});
30 +
31 +it('should warn in sync mode', () => {
32 + expect(() => {
33 + ReactDOM.render(<App />, document.createElement('div'));
34 + }).toWarnDev(
35 + ['Starting from React v17, the "scheduler" module will need to be mocked'],
36 + {withoutStack: true},
37 + );
38 + // does not warn twice
39 + expect(() => {
40 + ReactDOM.render(<App />, document.createElement('div'));
41 + }).toWarnDev([]);
42 +});
packages/react-dom/src/__tests__/ReactUnmockedSchedulerWarning-test.js new
+60
@@ -0,0 +1,60 @@
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 +
13 +function App() {
14 + return null;
15 +}
16 +
17 +beforeEach(() => {
18 + jest.resetModules();
19 + jest.unmock('scheduler');
20 + React = require('react');
21 + ReactDOM = require('react-dom');
22 +});
23 +
24 +it('does not warn when rendering in sync mode', () => {
25 + expect(() => {
26 + ReactDOM.render(<App />, document.createElement('div'));
27 + }).toWarnDev([]);
28 +});
29 +
30 +it('should warn when rendering in concurrent mode', () => {
31 + expect(() => {
32 + ReactDOM.unstable_createRoot(document.createElement('div')).render(<App />);
33 + }).toWarnDev(
34 + 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
35 + 'to guarantee consistent behaviour across tests and browsers.',
36 + {withoutStack: true},
37 + );
38 + // does not warn twice
39 + expect(() => {
40 + ReactDOM.unstable_createRoot(document.createElement('div')).render(<App />);
41 + }).toWarnDev([]);
42 +});
43 +
44 +it('should warn when rendering in batched mode', () => {
45 + expect(() => {
46 + ReactDOM.unstable_createSyncRoot(document.createElement('div')).render(
47 + <App />,
48 + );
49 + }).toWarnDev(
50 + 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
51 + 'to guarantee consistent behaviour across tests and browsers.',
52 + {withoutStack: true},
53 + );
54 + // does not warn twice
55 + expect(() => {
56 + ReactDOM.unstable_createSyncRoot(document.createElement('div')).render(
57 + <App />,
58 + );
59 + }).toWarnDev([]);
60 +});
packages/react-dom/src/test-utils/ReactTestUtilsAct.js
-17
@@ -12,7 +12,6 @@ import type {Thenable} from 'react-reconciler/src/ReactFiberWorkLoop';
12 import warningWithoutStack from 'shared/warningWithoutStack';
13 import ReactDOM from 'react-dom';
14 import ReactSharedInternals from 'shared/ReactSharedInternals';
15 -import {warnAboutMissingMockScheduler} from 'shared/ReactFeatureFlags';
15 import enqueueTask from 'shared/enqueueTask';
16 import * as Scheduler from 'scheduler';
17
@@ -43,27 +42,11 @@ const {IsSomeRendererActing} = ReactSharedInternals;
42 // this implementation should be exactly the same in
43 // ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
44
46 -let hasWarnedAboutMissingMockScheduler = false;
45 const isSchedulerMocked =
46 typeof Scheduler.unstable_flushAllWithoutAsserting === 'function';
47 const flushWork =
48 Scheduler.unstable_flushAllWithoutAsserting ||
49 function() {
52 - if (warnAboutMissingMockScheduler === true) {
53 - if (hasWarnedAboutMissingMockScheduler === false) {
54 - warningWithoutStack(
55 - null,
56 - 'Starting from React v17, the "scheduler" module will need to be mocked ' +
57 - 'to guarantee consistent behaviour across tests and browsers. To fix this, add the following ' +
58 - "to the top of your tests, or in your framework's global config file -\n\n" +
59 - 'As an example, for jest - \n' +
60 - "jest.mock('scheduler', () => require.requireActual('scheduler/unstable_mock'));\n\n" +
61 - 'For more info, visit https://fb.me/react-mock-scheduler',
62 - );
63 - hasWarnedAboutMissingMockScheduler = true;
64 - }
65 - }
66 -
50 let didFlushWork = false;
51 while (flushPassiveEffects()) {
52 didFlushWork = true;
packages/react-noop-renderer/src/createReactNoop.js
-17
@@ -27,7 +27,6 @@ import {REACT_FRAGMENT_TYPE, REACT_ELEMENT_TYPE} from 'shared/ReactSymbols';
27 import enqueueTask from 'shared/enqueueTask';
28 import ReactSharedInternals from 'shared/ReactSharedInternals';
29 import warningWithoutStack from 'shared/warningWithoutStack';
30 -import {warnAboutMissingMockScheduler} from 'shared/ReactFeatureFlags';
30 import {ConcurrentRoot, BatchedRoot, LegacyRoot} from 'shared/ReactRootTags';
31
32 type Container = {
@@ -599,27 +598,11 @@ function createReactNoop(reconciler: Function, useMutation: boolean) {
598 // this act() implementation should be exactly the same in
599 // ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
600
602 - let hasWarnedAboutMissingMockScheduler = false;
601 const isSchedulerMocked =
602 typeof Scheduler.unstable_flushAllWithoutAsserting === 'function';
603 const flushWork =
604 Scheduler.unstable_flushAllWithoutAsserting ||
605 function() {
608 - if (warnAboutMissingMockScheduler === true) {
609 - if (hasWarnedAboutMissingMockScheduler === false) {
610 - warningWithoutStack(
611 - null,
612 - 'Starting from React v17, the "scheduler" module will need to be mocked ' +
613 - 'to guarantee consistent behaviour across tests and browsers. To fix this, add the following ' +
614 - "to the top of your tests, or in your framework's global config file -\n\n" +
615 - 'As an example, for jest - \n' +
616 - "jest.mock('scheduler', () => require.requireActual('scheduler/unstable_mock'));\n\n" +
617 - 'For more info, visit https://fb.me/react-mock-scheduler',
618 - );
619 - hasWarnedAboutMissingMockScheduler = true;
620 - }
621 - }
622 -
606 let didFlushWork = false;
607 while (flushPassiveEffects()) {
608 didFlushWork = true;
packages/react-reconciler/src/ReactFiberReconciler.js
+2
@@ -58,6 +58,7 @@ import {
58 flushDiscreteUpdates,
59 flushPassiveEffects,
60 warnIfNotScopedWithMatchingAct,
61 + warnIfUnmockedScheduler,
62 IsThisRendererActing,
63 } from './ReactFiberWorkLoop';
64 import {createUpdate, enqueueUpdate} from './ReactUpdateQueue';
@@ -314,6 +315,7 @@ export function updateContainer(
315 if (__DEV__) {
316 // $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
317 if ('undefined' !== typeof jest) {
318 + warnIfUnmockedScheduler(current);
319 warnIfNotScopedWithMatchingAct(current);
320 }
321 }
packages/react-reconciler/src/ReactFiberWorkLoop.js
+39
@@ -25,6 +25,7 @@ import {
25 enableProfilerTimer,
26 enableSchedulerTracing,
27 revertPassiveEffectsChange,
28 + warnAboutUnmockedScheduler,
29 } from 'shared/ReactFeatureFlags';
30 import ReactSharedInternals from 'shared/ReactSharedInternals';
31 import invariant from 'shared/invariant';
@@ -47,6 +48,9 @@ import {
48 scheduleSyncCallback,
49 } from './SchedulerWithReactIntegration';
50
51 +// The scheduler is imported here *only* to detect whether it's been mocked
52 +import * as Scheduler from 'scheduler';
53 +
54 import {__interactionsRef, __subscriberRef} from 'scheduler/tracing';
55
56 import {
@@ -2522,6 +2526,41 @@ function warnIfNotCurrentlyActingUpdatesInDEV(fiber: Fiber): void {
2526
2527 export const warnIfNotCurrentlyActingUpdatesInDev = warnIfNotCurrentlyActingUpdatesInDEV;
2528
2529 +// In tests, we want to enforce a mocked scheduler.
2530 +let didWarnAboutUnmockedScheduler = false;
2531 +// TODO Before we release concurrent mode, revisit this and decide whether a mocked
2532 +// scheduler is the actual recommendation. The alternative could be a testing build,
2533 +// a new lib, or whatever; we dunno just yet. This message is for early adopters
2534 +// to get their tests right.
2535 +
2536 +export function warnIfUnmockedScheduler(fiber: Fiber) {
2537 + if (__DEV__) {
2538 + if (didWarnAboutUnmockedScheduler === false) {
2539 + if (fiber.mode & BatchedMode || fiber.mode & ConcurrentMode) {
2540 + didWarnAboutUnmockedScheduler = true;
2541 + warningWithoutStack(
2542 + Scheduler.unstable_flushAllWithoutAsserting !== undefined,
2543 + 'In Concurrent or Sync modes, the "scheduler" module needs to be mocked ' +
2544 + 'to guarantee consistent behaviour across tests and browsers. ' +
2545 + 'For example, with jest: \n' +
2546 + "jest.mock('scheduler', () => require('scheduler/unstable_mock'));\n\n" +
2547 + 'For more info, visit https://fb.me/react-mock-scheduler',
2548 + );
2549 + } else if (warnAboutUnmockedScheduler === true) {
2550 + didWarnAboutUnmockedScheduler = true;
2551 + warningWithoutStack(
2552 + null,
2553 + 'Starting from React v17, the "scheduler" module will need to be mocked ' +
2554 + 'to guarantee consistent behaviour across tests and browsers. ' +
2555 + 'For example, with jest: \n' +
2556 + "jest.mock('scheduler', () => require('scheduler/unstable_mock'));\n\n" +
2557 + 'For more info, visit https://fb.me/react-mock-scheduler',
2558 + );
2559 + }
2560 + }
2561 + }
2562 +}
2563 +
2564 let componentsThatSuspendedAtHighPri = null;
2565 let componentsThatTriggeredHighPriSuspend = null;
2566 export function checkForWrongSuspensePriorityInDEV(sourceFiber: Fiber) {
packages/react-test-renderer/src/ReactTestRendererAct.js
-17
@@ -15,7 +15,6 @@ import {
15 } from 'react-reconciler/inline.test';
16 import ReactSharedInternals from 'shared/ReactSharedInternals';
17 import warningWithoutStack from 'shared/warningWithoutStack';
18 -import {warnAboutMissingMockScheduler} from 'shared/ReactFeatureFlags';
18 import enqueueTask from 'shared/enqueueTask';
19 import * as Scheduler from 'scheduler';
20
@@ -24,27 +23,11 @@ const {IsSomeRendererActing} = ReactSharedInternals;
23 // this implementation should be exactly the same in
24 // ReactTestUtilsAct.js, ReactTestRendererAct.js, createReactNoop.js
25
27 -let hasWarnedAboutMissingMockScheduler = false;
26 const isSchedulerMocked =
27 typeof Scheduler.unstable_flushAllWithoutAsserting === 'function';
28 const flushWork =
29 Scheduler.unstable_flushAllWithoutAsserting ||
30 function() {
33 - if (warnAboutMissingMockScheduler === true) {
34 - if (hasWarnedAboutMissingMockScheduler === false) {
35 - warningWithoutStack(
36 - null,
37 - 'Starting from React v17, the "scheduler" module will need to be mocked ' +
38 - 'to guarantee consistent behaviour across tests and browsers. To fix this, add the following ' +
39 - "to the top of your tests, or in your framework's global config file -\n\n" +
40 - 'As an example, for jest - \n' +
41 - "jest.mock('scheduler', () => require.requireActual('scheduler/unstable_mock'));\n\n" +
42 - 'For more info, visit https://fb.me/react-mock-scheduler',
43 - );
44 - hasWarnedAboutMissingMockScheduler = true;
45 - }
46 - }
47 -
31 let didFlushWork = false;
32 while (flushPassiveEffects()) {
33 didFlushWork = true;
packages/shared/ReactFeatureFlags.js
+1 -1
@@ -70,7 +70,7 @@ export const enableJSXTransformAPI = false;
70
71 // We will enforce mocking scheduler with scheduler/unstable_mock at some point. (v17?)
72 // Till then, we warn about the missing mock, but still fallback to a sync mode compatible version
73 -export const warnAboutMissingMockScheduler = false;
73 +export const warnAboutUnmockedScheduler = false;
74 // Temporary flag to revert the fix in #15650
75 export const revertPassiveEffectsChange = false;
76
packages/shared/forks/ReactFeatureFlags.native-fb.js
+1 -1
@@ -34,7 +34,7 @@ export const warnAboutDeprecatedSetNativeProps = true;
34 export const enableFlareAPI = false;
35 export const enableFundamentalAPI = false;
36 export const enableJSXTransformAPI = false;
37 -export const warnAboutMissingMockScheduler = true;
37 +export const warnAboutUnmockedScheduler = true;
38 export const revertPassiveEffectsChange = false;
39 export const enableUserBlockingEvents = false;
40 export const enableSuspenseCallback = false;
packages/shared/forks/ReactFeatureFlags.native-oss.js
+1 -1
@@ -29,7 +29,7 @@ export const warnAboutDeprecatedSetNativeProps = false;
29 export const enableFlareAPI = false;
30 export const enableFundamentalAPI = false;
31 export const enableJSXTransformAPI = false;
32 -export const warnAboutMissingMockScheduler = false;
32 +export const warnAboutUnmockedScheduler = false;
33 export const revertPassiveEffectsChange = false;
34 export const enableUserBlockingEvents = false;
35 export const enableSuspenseCallback = false;
packages/shared/forks/ReactFeatureFlags.persistent.js
+1 -1
@@ -29,7 +29,7 @@ export const warnAboutDeprecatedSetNativeProps = false;
29 export const enableFlareAPI = false;
30 export const enableFundamentalAPI = false;
31 export const enableJSXTransformAPI = false;
32 -export const warnAboutMissingMockScheduler = true;
32 +export const warnAboutUnmockedScheduler = true;
33 export const revertPassiveEffectsChange = false;
34 export const enableUserBlockingEvents = false;
35 export const enableSuspenseCallback = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.js
+1 -1
@@ -29,7 +29,7 @@ export const warnAboutDeprecatedSetNativeProps = false;
29 export const enableFlareAPI = false;
30 export const enableFundamentalAPI = false;
31 export const enableJSXTransformAPI = false;
32 -export const warnAboutMissingMockScheduler = false;
32 +export const warnAboutUnmockedScheduler = false;
33 export const revertPassiveEffectsChange = false;
34 export const enableUserBlockingEvents = false;
35 export const enableSuspenseCallback = false;
packages/shared/forks/ReactFeatureFlags.test-renderer.www.js
+1 -1
@@ -30,7 +30,7 @@ export const disableJavaScriptURLs = false;
30 export const enableFlareAPI = true;
31 export const enableFundamentalAPI = false;
32 export const enableJSXTransformAPI = true;
33 -export const warnAboutMissingMockScheduler = true;
33 +export const warnAboutUnmockedScheduler = true;
34 export const enableUserBlockingEvents = false;
35 export const enableSuspenseCallback = true;
36 export const warnAboutDefaultPropsOnFunctionComponents = false;
packages/shared/forks/ReactFeatureFlags.www.js
+1 -1
@@ -74,7 +74,7 @@ export const enableFundamentalAPI = false;
74
75 export const enableJSXTransformAPI = true;
76
77 -export const warnAboutMissingMockScheduler = true;
77 +export const warnAboutUnmockedScheduler = true;
78
79 export const enableSuspenseCallback = true;
80