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