[assert helpers] react-reconciler (#31986)
Based off: https://github.com/facebook/react/pull/31984
Ricky committed
Jan 6, 2025 at 14:12 UTC
6b865330f4bc6c87dcd2c8cdf665895c8a190fc1
17 files changed
+1101
-638
packages/react-reconciler/src/__tests__/Activity-test.js
+10
-5
@@ -14,6 +14,7 @@ let startTransition;
14
let waitForPaint;
15
let waitFor;
16
let assertLog;
17
+let assertConsoleErrorDev;
18
19
describe('Activity', () => {
20
beforeEach(() => {
@@ -37,6 +38,7 @@ describe('Activity', () => {
38
waitForPaint = InternalTestUtils.waitForPaint;
39
waitFor = InternalTestUtils.waitFor;
40
assertLog = InternalTestUtils.assertLog;
41
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
42
});
43
44
function Text(props) {
@@ -784,11 +786,14 @@ describe('Activity', () => {
786
// would be null because it was nulled out when it was deleted, but there
787
// was no null check before we accessed it. A weird edge case but we must
788
// account for it.
787
- expect(() => {
788
- setState('Updated');
789
- }).toErrorDev(
790
- "Can't perform a React state update on a component that hasn't mounted yet",
791
- );
789
+ setState('Updated');
790
+ assertConsoleErrorDev([
791
+ "Can't perform a React state update on a component that hasn't mounted yet. " +
792
+ 'This indicates that you have a side-effect in your render function that ' +
793
+ 'asynchronously later calls tries to update the component. ' +
794
+ 'Move this work to useEffect instead.\n' +
795
+ ' in Child (at **)',
796
+ ]);
797
});
798
expect(root).toMatchRenderedOutput(null);
799
});
packages/react-reconciler/src/__tests__/ReactActWarnings-test.js
+120
-33
@@ -18,6 +18,7 @@ let Suspense;
18
let startTransition;
19
let getCacheForType;
20
let caches;
21
+let assertConsoleErrorDev;
22
23
// These tests are mostly concerned with concurrent roots. The legacy root
24
// behavior is covered by other older test suites and is unchanged from
@@ -38,6 +39,7 @@ describe('act warnings', () => {
39
const InternalTestUtils = require('internal-test-utils');
40
waitForAll = InternalTestUtils.waitForAll;
41
assertLog = InternalTestUtils.assertLog;
42
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
43
});
44
45
function createTextCache() {
@@ -171,9 +173,21 @@ describe('act warnings', () => {
173
174
// Flag is true. Warn.
175
await withActEnvironment(true, async () => {
174
- expect(() => setState(2)).toErrorDev(
175
- 'An update to App inside a test was not wrapped in act',
176
- );
176
+ setState(2);
177
+ assertConsoleErrorDev([
178
+ 'An update to App inside a test was not wrapped in act(...).\n' +
179
+ '\n' +
180
+ 'When testing, code that causes React state updates should be wrapped into act(...):\n' +
181
+ '\n' +
182
+ 'act(() => {\n' +
183
+ ' /* fire events that update state */\n' +
184
+ '});\n' +
185
+ '/* assert on the output */\n' +
186
+ '\n' +
187
+ "This ensures that you're testing the behavior the user would see in the browser. " +
188
+ 'Learn more at https://react.dev/link/wrap-tests-with-act\n' +
189
+ ' in App (at **)',
190
+ ]);
191
await waitForAll([2]);
192
expect(root).toMatchRenderedOutput('2');
193
});
@@ -202,12 +216,11 @@ describe('act warnings', () => {
216
217
// Default behavior. Flag is undefined. Warn.
218
expect(global.IS_REACT_ACT_ENVIRONMENT).toBe(undefined);
205
- expect(() => {
206
- act(() => {
207
- setState(1);
208
- });
209
- }).toErrorDev(
210
- 'The current testing environment is not configured to support act(...)',
219
+ act(() => {
220
+ setState(1);
221
+ });
222
+ assertConsoleErrorDev(
223
+ ['The current testing environment is not configured to support act(...)'],
224
{withoutStack: true},
225
);
226
assertLog([1]);
@@ -224,12 +237,13 @@ describe('act warnings', () => {
237
238
// Flag is false. Warn.
239
await withActEnvironment(false, () => {
227
- expect(() => {
228
- act(() => {
229
- setState(1);
230
- });
231
- }).toErrorDev(
232
- 'The current testing environment is not configured to support act(...)',
240
+ act(() => {
241
+ setState(1);
242
+ });
243
+ assertConsoleErrorDev(
244
+ [
245
+ 'The current testing environment is not configured to support act(...)',
246
+ ],
247
{withoutStack: true},
248
);
249
assertLog([1]);
@@ -240,10 +254,23 @@ describe('act warnings', () => {
254
it('warns if root update is not wrapped', async () => {
255
await withActEnvironment(true, () => {
256
const root = ReactNoop.createRoot();
243
- expect(() => root.render('Hi')).toErrorDev(
244
- // TODO: Better error message that doesn't make it look like "Root" is
245
- // the name of a custom component
246
- 'An update to Root inside a test was not wrapped in act(...)',
257
+ root.render('Hi');
258
+ assertConsoleErrorDev(
259
+ [
260
+ // TODO: Better error message that doesn't make it look like "Root" is
261
+ // the name of a custom component
262
+ 'An update to Root inside a test was not wrapped in act(...).\n' +
263
+ '\n' +
264
+ 'When testing, code that causes React state updates should be wrapped into act(...):\n' +
265
+ '\n' +
266
+ 'act(() => {\n' +
267
+ ' /* fire events that update state */\n' +
268
+ '});\n' +
269
+ '/* assert on the output */\n' +
270
+ '\n' +
271
+ "This ensures that you're testing the behavior the user would see in the browser. " +
272
+ 'Learn more at https://react.dev/link/wrap-tests-with-act',
273
+ ],
274
{withoutStack: true},
275
);
276
});
@@ -265,9 +292,21 @@ describe('act warnings', () => {
292
act(() => {
293
root.render(<App />);
294
});
268
- expect(() => app.setState({count: 1})).toErrorDev(
269
- 'An update to App inside a test was not wrapped in act(...)',
270
- );
295
+ app.setState({count: 1});
296
+ assertConsoleErrorDev([
297
+ 'An update to App inside a test was not wrapped in act(...).\n' +
298
+ '\n' +
299
+ 'When testing, code that causes React state updates should be wrapped into act(...):\n' +
300
+ '\n' +
301
+ 'act(() => {\n' +
302
+ ' /* fire events that update state */\n' +
303
+ '});\n' +
304
+ '/* assert on the output */\n' +
305
+ '\n' +
306
+ "This ensures that you're testing the behavior the user would see in the browser. " +
307
+ 'Learn more at https://react.dev/link/wrap-tests-with-act\n' +
308
+ ' in App (at **)',
309
+ ]);
310
});
311
});
312
@@ -288,9 +327,21 @@ describe('act warnings', () => {
327
328
// Even though this update is synchronous, we should still fire a warning,
329
// because it could have spawned additional asynchronous work
291
- expect(() => ReactNoop.flushSync(() => setState(1))).toErrorDev(
292
- 'An update to App inside a test was not wrapped in act(...)',
293
- );
330
+ ReactNoop.flushSync(() => setState(1));
331
+ assertConsoleErrorDev([
332
+ 'An update to App inside a test was not wrapped in act(...).\n' +
333
+ '\n' +
334
+ 'When testing, code that causes React state updates should be wrapped into act(...):\n' +
335
+ '\n' +
336
+ 'act(() => {\n' +
337
+ ' /* fire events that update state */\n' +
338
+ '});\n' +
339
+ '/* assert on the output */\n' +
340
+ '\n' +
341
+ "This ensures that you're testing the behavior the user would see in the browser. " +
342
+ 'Learn more at https://react.dev/link/wrap-tests-with-act\n' +
343
+ ' in App (at **)',
344
+ ]);
345
346
assertLog([1]);
347
expect(root).toMatchRenderedOutput('1');
@@ -322,12 +373,36 @@ describe('act warnings', () => {
373
expect(root).toMatchRenderedOutput('Loading...');
374
375
// This is a retry, not a ping, because we already showed a fallback.
325
- expect(() => resolveText('Async')).toErrorDev(
376
+ resolveText('Async');
377
+ assertConsoleErrorDev(
378
[
327
- 'A suspended resource finished loading inside a test, but the event ' +
328
- 'was not wrapped in act(...)',
329
-
330
- ...(gate('enableSiblingPrerendering') ? ['not wrapped in act'] : []),
379
+ 'A suspended resource finished loading inside a test, but the event was not wrapped in act(...).\n' +
380
+ '\n' +
381
+ 'When testing, code that resolves suspended data should be wrapped into act(...):\n' +
382
+ '\n' +
383
+ 'act(() => {\n' +
384
+ ' /* finish loading suspended data */\n' +
385
+ '});\n' +
386
+ '/* assert on the output */\n' +
387
+ '\n' +
388
+ "This ensures that you're testing the behavior the user would see in the browser. " +
389
+ 'Learn more at https://react.dev/link/wrap-tests-with-act',
390
+
391
+ ...(gate('enableSiblingPrerendering')
392
+ ? [
393
+ 'A suspended resource finished loading inside a test, but the event was not wrapped in act(...).\n' +
394
+ '\n' +
395
+ 'When testing, code that resolves suspended data should be wrapped into act(...):\n' +
396
+ '\n' +
397
+ 'act(() => {\n' +
398
+ ' /* finish loading suspended data */\n' +
399
+ '});\n' +
400
+ '/* assert on the output */\n' +
401
+ '\n' +
402
+ "This ensures that you're testing the behavior the user would see in the browser. " +
403
+ 'Learn more at https://react.dev/link/wrap-tests-with-act',
404
+ ]
405
+ : []),
406
],
407
408
{withoutStack: true},
@@ -363,9 +438,21 @@ describe('act warnings', () => {
438
expect(root).toMatchRenderedOutput('(empty)');
439
440
// This is a ping, not a retry, because no fallback is showing.
366
- expect(() => resolveText('Async')).toErrorDev(
367
- 'A suspended resource finished loading inside a test, but the event ' +
368
- 'was not wrapped in act(...)',
441
+ resolveText('Async');
442
+ assertConsoleErrorDev(
443
+ [
444
+ 'A suspended resource finished loading inside a test, but the event was not wrapped in act(...).\n' +
445
+ '\n' +
446
+ 'When testing, code that resolves suspended data should be wrapped into act(...):\n' +
447
+ '\n' +
448
+ 'act(() => {\n' +
449
+ ' /* finish loading suspended data */\n' +
450
+ '});\n' +
451
+ '/* assert on the output */\n' +
452
+ '\n' +
453
+ "This ensures that you're testing the behavior the user would see in the browser. " +
454
+ 'Learn more at https://react.dev/link/wrap-tests-with-act',
455
+ ],
456
{withoutStack: true},
457
);
458
});
packages/react-reconciler/src/__tests__/ReactAsyncActions-test.js
+13
-9
@@ -7,6 +7,7 @@ let useTransition;
7
let useState;
8
let useOptimistic;
9
let textCache;
10
+let assertConsoleErrorDev;
11
12
describe('ReactAsyncActions', () => {
13
beforeEach(() => {
@@ -21,6 +22,8 @@ describe('ReactAsyncActions', () => {
22
Scheduler = require('scheduler');
23
act = require('internal-test-utils').act;
24
assertLog = require('internal-test-utils').assertLog;
25
+ assertConsoleErrorDev =
26
+ require('internal-test-utils').assertConsoleErrorDev;
27
useTransition = React.useTransition;
28
useState = React.useState;
29
useOptimistic = React.useOptimistic;
@@ -1231,15 +1234,16 @@ describe('ReactAsyncActions', () => {
1234
assertLog(['A']);
1235
expect(root).toMatchRenderedOutput(<div>A</div>);
1236
1234
- await expect(async () => {
1235
- await act(() => {
1236
- setLoadingProgress('25%');
1237
- startTransition(() => setText('B'));
1238
- });
1239
- }).toErrorDev(
1240
- 'An optimistic state update occurred outside a transition or ' +
1241
- 'action. To fix, move the update to an action, or wrap ' +
1242
- 'with startTransition.',
1237
+ await act(() => {
1238
+ setLoadingProgress('25%');
1239
+ startTransition(() => setText('B'));
1240
+ });
1241
+ assertConsoleErrorDev(
1242
+ [
1243
+ 'An optimistic state update occurred outside a transition or ' +
1244
+ 'action. To fix, move the update to an action, or wrap ' +
1245
+ 'with startTransition.',
1246
+ ],
1247
{withoutStack: true},
1248
);
1249
assertLog(['Loading... (25%)', 'A', 'B']);
packages/react-reconciler/src/__tests__/ReactFragment-test.js
+28
-6
@@ -12,6 +12,7 @@
12
let React;
13
let ReactNoop;
14
let waitForAll;
15
+let assertConsoleErrorDev;
16
17
describe('ReactFragment', () => {
18
beforeEach(function () {
@@ -22,6 +23,7 @@ describe('ReactFragment', () => {
23
24
const InternalTestUtils = require('internal-test-utils');
25
waitForAll = InternalTestUtils.waitForAll;
26
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
27
});
28
29
it('should render a single child via noop renderer', async () => {
@@ -740,9 +742,22 @@ describe('ReactFragment', () => {
742
await waitForAll([]);
743
744
ReactNoop.render(<Foo condition={false} />);
743
- await expect(async () => await waitForAll([])).toErrorDev(
744
- 'Each child in a list should have a unique "key" prop.',
745
- );
745
+ await waitForAll([]);
746
+ assertConsoleErrorDev([
747
+ gate('enableOwnerStacks')
748
+ ? 'Each child in a list should have a unique "key" prop.\n' +
749
+ '\n' +
750
+ 'Check the render method of `div`. ' +
751
+ 'It was passed a child from Foo. ' +
752
+ 'See https://react.dev/link/warning-keys for more information.\n' +
753
+ ' in Foo (at **)'
754
+ : 'Each child in a list should have a unique "key" prop.\n' +
755
+ '\n' +
756
+ 'Check the render method of `Foo`. ' +
757
+ 'See https://react.dev/link/warning-keys for more information.\n' +
758
+ ' in Stateful (at **)\n' +
759
+ ' in Foo (at **)',
760
+ ]);
761
762
expect(ops).toEqual([]);
763
expect(ReactNoop).toMatchRenderedOutput(
@@ -937,9 +952,16 @@ describe('ReactFragment', () => {
952
}
953
954
ReactNoop.render(<Foo condition={true} />);
940
- await expect(async () => await waitForAll([])).toErrorDev(
941
- 'Each child in a list should have a unique "key" prop.',
942
- );
955
+ await waitForAll([]);
956
+ assertConsoleErrorDev([
957
+ 'Each child in a list should have a unique "key" prop.\n' +
958
+ '\n' +
959
+ 'Check the top-level render call using <Foo>. ' +
960
+ 'It was passed a child from Foo. ' +
961
+ 'See https://react.dev/link/warning-keys for more information.\n' +
962
+ ' in span (at **)\n' +
963
+ ' in Foo (at **)',
964
+ ]);
965
966
ReactNoop.render(<Foo condition={false} />);
967
// The key warning gets deduped because it's in the same component.
packages/react-reconciler/src/__tests__/ReactHooks-test.internal.js
+305
-217
@@ -239,17 +239,18 @@ describe('ReactHooks', () => {
239
await waitForAll(['Count: 0']);
240
expect(root).toMatchRenderedOutput('0');
241
242
- await expect(async () => {
243
- await act(() =>
244
- setCounter(1, () => {
245
- throw new Error('Expected to ignore the callback.');
246
- }),
247
- );
248
- }).toErrorDev(
249
- 'State updates from the useState() and useReducer() Hooks ' +
250
- "don't support the second callback argument. " +
251
- 'To execute a side effect after rendering, ' +
252
- 'declare it in the component body with useEffect().',
242
+ await act(() =>
243
+ setCounter(1, () => {
244
+ throw new Error('Expected to ignore the callback.');
245
+ }),
246
+ );
247
+ assertConsoleErrorDev(
248
+ [
249
+ 'State updates from the useState() and useReducer() Hooks ' +
250
+ "don't support the second callback argument. " +
251
+ 'To execute a side effect after rendering, ' +
252
+ 'declare it in the component body with useEffect().',
253
+ ],
254
{withoutStack: true},
255
);
256
assertLog(['Count: 1']);
@@ -273,17 +274,18 @@ describe('ReactHooks', () => {
274
await waitForAll(['Count: 0']);
275
expect(root).toMatchRenderedOutput('0');
276
276
- await expect(async () => {
277
- await act(() =>
278
- dispatch(1, () => {
279
- throw new Error('Expected to ignore the callback.');
280
- }),
281
- );
282
- }).toErrorDev(
283
- 'State updates from the useState() and useReducer() Hooks ' +
284
- "don't support the second callback argument. " +
285
- 'To execute a side effect after rendering, ' +
286
- 'declare it in the component body with useEffect().',
277
+ await act(() =>
278
+ dispatch(1, () => {
279
+ throw new Error('Expected to ignore the callback.');
280
+ }),
281
+ );
282
+ assertConsoleErrorDev(
283
+ [
284
+ 'State updates from the useState() and useReducer() Hooks ' +
285
+ "don't support the second callback argument. " +
286
+ 'To execute a side effect after rendering, ' +
287
+ 'declare it in the component body with useEffect().',
288
+ ],
289
{withoutStack: true},
290
);
291
assertLog(['Count: 1']);
@@ -581,16 +583,17 @@ describe('ReactHooks', () => {
583
});
584
});
585
assertLog(['Did commit: A']);
584
- await expect(async () => {
585
- await act(() => {
586
- root.update(<App dependencies={['A', 'B']} />);
587
- });
588
- }).toErrorDev([
586
+ await act(() => {
587
+ root.update(<App dependencies={['A', 'B']} />);
588
+ });
589
+ assertConsoleErrorDev([
590
'The final argument passed to useLayoutEffect changed size ' +
591
'between renders. The order and size of this array must remain ' +
591
- 'constant.\n\n' +
592
+ 'constant.\n' +
593
+ '\n' +
594
'Previous: [A]\n' +
593
- 'Incoming: [A, B]\n',
595
+ 'Incoming: [A, B]\n' +
596
+ ' in App (at **)',
597
]);
598
});
599
@@ -617,14 +620,14 @@ describe('ReactHooks', () => {
620
assertLog(['Compute']);
621
expect(root).toMatchRenderedOutput('HELLO');
622
620
- await expect(async () => {
621
- await act(() => {
622
- root.update(<App text="Hello" hasDeps={false} />);
623
- });
624
- }).toErrorDev([
623
+ await act(() => {
624
+ root.update(<App text="Hello" hasDeps={false} />);
625
+ });
626
+ assertConsoleErrorDev([
627
'useMemo received a final argument during this render, but ' +
628
'not during the previous render. Even though the final argument is ' +
627
- 'optional, its type cannot change between renders.',
629
+ 'optional, its type cannot change between renders.\n' +
630
+ ' in App (at **)',
631
]);
632
});
633
@@ -639,53 +642,62 @@ describe('ReactHooks', () => {
642
return null;
643
}
644
642
- await expect(async () => {
643
- await act(() => {
644
- ReactTestRenderer.create(<App deps={'hello'} />, {
645
- unstable_isConcurrent: true,
646
- });
645
+ await act(() => {
646
+ ReactTestRenderer.create(<App deps={'hello'} />, {
647
+ unstable_isConcurrent: true,
648
});
648
- }).toErrorDev([
649
+ });
650
+ assertConsoleErrorDev([
651
'useEffect received a final argument that is not an array (instead, received `string`). ' +
650
- 'When specified, the final argument must be an array.',
652
+ 'When specified, the final argument must be an array.\n' +
653
+ ' in App (at **)',
654
'useLayoutEffect received a final argument that is not an array (instead, received `string`). ' +
652
- 'When specified, the final argument must be an array.',
655
+ 'When specified, the final argument must be an array.\n' +
656
+ ' in App (at **)',
657
'useMemo received a final argument that is not an array (instead, received `string`). ' +
654
- 'When specified, the final argument must be an array.',
658
+ 'When specified, the final argument must be an array.\n' +
659
+ ' in App (at **)',
660
'useCallback received a final argument that is not an array (instead, received `string`). ' +
656
- 'When specified, the final argument must be an array.',
661
+ 'When specified, the final argument must be an array.\n' +
662
+ ' in App (at **)',
663
]);
658
- await expect(async () => {
659
- await act(() => {
660
- ReactTestRenderer.create(<App deps={100500} />, {
661
- unstable_isConcurrent: true,
662
- });
664
+ await act(() => {
665
+ ReactTestRenderer.create(<App deps={100500} />, {
666
+ unstable_isConcurrent: true,
667
});
664
- }).toErrorDev([
668
+ });
669
+ assertConsoleErrorDev([
670
'useEffect received a final argument that is not an array (instead, received `number`). ' +
666
- 'When specified, the final argument must be an array.',
671
+ 'When specified, the final argument must be an array.\n' +
672
+ ' in App (at **)',
673
'useLayoutEffect received a final argument that is not an array (instead, received `number`). ' +
668
- 'When specified, the final argument must be an array.',
674
+ 'When specified, the final argument must be an array.\n' +
675
+ ' in App (at **)',
676
'useMemo received a final argument that is not an array (instead, received `number`). ' +
670
- 'When specified, the final argument must be an array.',
677
+ 'When specified, the final argument must be an array.\n' +
678
+ ' in App (at **)',
679
'useCallback received a final argument that is not an array (instead, received `number`). ' +
672
- 'When specified, the final argument must be an array.',
680
+ 'When specified, the final argument must be an array.\n' +
681
+ ' in App (at **)',
682
]);
674
- await expect(async () => {
675
- await act(() => {
676
- ReactTestRenderer.create(<App deps={{}} />, {
677
- unstable_isConcurrent: true,
678
- });
683
+ await act(() => {
684
+ ReactTestRenderer.create(<App deps={{}} />, {
685
+ unstable_isConcurrent: true,
686
});
680
- }).toErrorDev([
687
+ });
688
+ assertConsoleErrorDev([
689
'useEffect received a final argument that is not an array (instead, received `object`). ' +
682
- 'When specified, the final argument must be an array.',
690
+ 'When specified, the final argument must be an array.\n' +
691
+ ' in App (at **)',
692
'useLayoutEffect received a final argument that is not an array (instead, received `object`). ' +
684
- 'When specified, the final argument must be an array.',
693
+ 'When specified, the final argument must be an array.\n' +
694
+ ' in App (at **)',
695
'useMemo received a final argument that is not an array (instead, received `object`). ' +
686
- 'When specified, the final argument must be an array.',
696
+ 'When specified, the final argument must be an array.\n' +
697
+ ' in App (at **)',
698
'useCallback received a final argument that is not an array (instead, received `object`). ' +
688
- 'When specified, the final argument must be an array.',
699
+ 'When specified, the final argument must be an array.\n' +
700
+ ' in App (at **)',
701
]);
702
703
await act(() => {
@@ -710,15 +722,15 @@ describe('ReactHooks', () => {
722
});
723
App.displayName = 'App';
724
713
- await expect(async () => {
714
- await act(() => {
715
- ReactTestRenderer.create(<App deps={'hello'} />, {
716
- unstable_isConcurrent: true,
717
- });
725
+ await act(() => {
726
+ ReactTestRenderer.create(<App deps={'hello'} />, {
727
+ unstable_isConcurrent: true,
728
});
719
- }).toErrorDev([
729
+ });
730
+ assertConsoleErrorDev([
731
'useImperativeHandle received a final argument that is not an array (instead, received `string`). ' +
721
- 'When specified, the final argument must be an array.',
732
+ 'When specified, the final argument must be an array.\n' +
733
+ ' in App (at **)',
734
]);
735
await act(() => {
736
ReactTestRenderer.create(<App deps={null} />, {
@@ -817,17 +829,18 @@ describe('ReactHooks', () => {
829
}
830
831
await expect(async () => {
820
- await expect(async () => {
821
- await act(() => {
822
- ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
823
- });
824
- }).rejects.toThrow('create is not a function');
825
- }).toErrorDev([
832
+ await act(() => {
833
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
834
+ });
835
+ }).rejects.toThrow('create is not a function');
836
+ assertConsoleErrorDev([
837
+ 'Expected useImperativeHandle() second argument to be a function ' +
838
+ 'that creates a handle. Instead received: undefined.\n' +
839
+ ' in App (at **)',
840
'Expected useImperativeHandle() first argument to either be a ' +
841
'ref callback or React.createRef() object. ' +
828
- 'Instead received: an object with keys {focus}.',
829
- 'Expected useImperativeHandle() second argument to be a function ' +
830
- 'that creates a handle. Instead received: undefined.',
842
+ 'Instead received: an object with keys {focus}.\n' +
843
+ ' in App (at **)',
844
]);
845
});
846
@@ -841,13 +854,13 @@ describe('ReactHooks', () => {
854
});
855
App.displayName = 'App';
856
844
- await expect(async () => {
845
- await act(() => {
846
- ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
847
- });
848
- }).toErrorDev([
857
+ await act(() => {
858
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
859
+ });
860
+ assertConsoleErrorDev([
861
'Expected useImperativeHandle() second argument to be a function ' +
850
- 'that creates a handle. Instead received: object.',
862
+ 'that creates a handle. Instead received: object.\n' +
863
+ ' in App (at **)',
864
]);
865
});
866
@@ -933,13 +946,15 @@ describe('ReactHooks', () => {
946
});
947
return null;
948
}
936
- await expect(async () => {
937
- await act(() => {
938
- ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
939
- });
940
- }).toErrorDev(
941
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks.',
942
- );
949
+ await act(() => {
950
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
951
+ });
952
+ assertConsoleErrorDev([
953
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
954
+ 'You can only call Hooks at the top level of your React function. ' +
955
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
956
+ ' in App (at **)',
957
+ ]);
958
});
959
960
it('warns when reading context inside useMemo', async () => {
@@ -954,11 +969,16 @@ describe('ReactHooks', () => {
969
}, []);
970
}
971
957
- await expect(async () => {
958
- await act(() => {
959
- ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
960
- });
961
- }).toErrorDev('Context can only be read while React is rendering');
972
+ await act(() => {
973
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
974
+ });
975
+ assertConsoleErrorDev([
976
+ 'Context can only be read while React is rendering. ' +
977
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
978
+ 'In function components, you can read it directly in the function body, ' +
979
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
980
+ ' in App (at **)',
981
+ ]);
982
});
983
984
it('warns when reading context inside useMemo after reading outside it', async () => {
@@ -977,11 +997,16 @@ describe('ReactHooks', () => {
997
}, []);
998
}
999
980
- await expect(async () => {
981
- await act(() => {
982
- ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
983
- });
984
- }).toErrorDev('Context can only be read while React is rendering');
1000
+ await act(() => {
1001
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
1002
+ });
1003
+ assertConsoleErrorDev([
1004
+ 'Context can only be read while React is rendering. ' +
1005
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
1006
+ 'In function components, you can read it directly in the function body, ' +
1007
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
1008
+ ' in App (at **)',
1009
+ ]);
1010
expect(firstRead).toBe('light');
1011
expect(secondRead).toBe('light');
1012
});
@@ -1048,11 +1073,16 @@ describe('ReactHooks', () => {
1073
return null;
1074
}
1075
1051
- await expect(async () => {
1052
- await act(() => {
1053
- ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
1054
- });
1055
- }).toErrorDev(['Context can only be read while React is rendering']);
1076
+ await act(() => {
1077
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
1078
+ });
1079
+ assertConsoleErrorDev([
1080
+ 'Context can only be read while React is rendering. ' +
1081
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
1082
+ 'In function components, you can read it directly in the function body, ' +
1083
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
1084
+ ' in App (at **)',
1085
+ ]);
1086
});
1087
1088
// Edge case.
@@ -1078,19 +1108,25 @@ describe('ReactHooks', () => {
1108
}
1109
}
1110
1081
- await expect(async () => {
1082
- await act(() => {
1083
- ReactTestRenderer.create(
1084
- <>
1085
- <Fn />
1086
- <Cls />
1087
- </>,
1088
- {unstable_isConcurrent: true},
1089
- );
1090
- });
1091
- }).toErrorDev([
1092
- 'Context can only be read while React is rendering',
1093
- 'Cannot update a component (`Fn`) while rendering a different component (`Cls`).',
1111
+ await act(() => {
1112
+ ReactTestRenderer.create(
1113
+ <>
1114
+ <Fn />
1115
+ <Cls />
1116
+ </>,
1117
+ {unstable_isConcurrent: true},
1118
+ );
1119
+ });
1120
+ assertConsoleErrorDev([
1121
+ 'Context can only be read while React is rendering. ' +
1122
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
1123
+ 'In function components, you can read it directly in the function body, ' +
1124
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
1125
+ ' in Cls (at **)',
1126
+ 'Cannot update a component (`Fn`) while rendering a different component (`Cls`). ' +
1127
+ 'To locate the bad setState() call inside `Cls`, ' +
1128
+ 'follow the stack trace as described in https://react.dev/link/setstate-in-render\n' +
1129
+ ' in Cls (at **)',
1130
]);
1131
});
1132
@@ -1110,24 +1146,32 @@ describe('ReactHooks', () => {
1146
}
1147
1148
await expect(async () => {
1113
- await expect(async () => {
1114
- await act(() => {
1115
- ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
1116
- });
1117
- }).rejects.toThrow(
1118
- 'Update hook called on initial render. This is likely a bug in React. Please file an issue.',
1119
- );
1120
- }).toErrorDev([
1121
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
1149
+ await act(() => {
1150
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
1151
+ });
1152
+ }).rejects.toThrow(
1153
+ 'Update hook called on initial render. This is likely a bug in React. Please file an issue.',
1154
+ );
1155
+ assertConsoleErrorDev([
1156
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
1157
+ 'You can only call Hooks at the top level of your React function. ' +
1158
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
1159
+ ' in App (at **)',
1160
'React has detected a change in the order of Hooks called by App. ' +
1161
'This will lead to bugs and errors if not fixed. For more information, ' +
1124
- 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
1162
+ 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n' +
1163
+ '\n' +
1164
' Previous render Next render\n' +
1165
' ------------------------------------------------------\n' +
1166
'1. useReducer useReducer\n' +
1167
'2. useState useRef\n' +
1129
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
1130
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
1168
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
1169
+ '\n' +
1170
+ ' in App (at **)',
1171
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
1172
+ 'You can only call Hooks at the top level of your React function. ' +
1173
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
1174
+ ' in App (at **)',
1175
]);
1176
});
1177
@@ -1140,13 +1184,15 @@ describe('ReactHooks', () => {
1184
});
1185
return null;
1186
}
1143
- await expect(async () => {
1144
- await act(() => {
1145
- ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
1146
- });
1147
- }).toErrorDev(
1148
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks.',
1149
- );
1187
+ await act(() => {
1188
+ ReactTestRenderer.create(<App />, {unstable_isConcurrent: true});
1189
+ });
1190
+ assertConsoleErrorDev([
1191
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
1192
+ 'You can only call Hooks at the top level of your React function. ' +
1193
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
1194
+ ' in App (at **)',
1195
+ ]);
1196
});
1197
1198
it('resets warning internal state when interrupted by an error', async () => {
@@ -1177,21 +1223,37 @@ describe('ReactHooks', () => {
1223
}
1224
}
1225
1180
- await expect(async () => {
1181
- await act(() => {
1182
- ReactTestRenderer.create(
1183
- <Boundary>
1184
- <App />
1185
- </Boundary>,
1186
- {unstable_isConcurrent: true},
1187
- );
1188
- });
1189
- }).toErrorDev([
1190
- 'Context can only be read while React is rendering',
1191
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
1192
-
1193
- 'Context can only be read while React is rendering',
1194
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
1226
+ await act(() => {
1227
+ ReactTestRenderer.create(
1228
+ <Boundary>
1229
+ <App />
1230
+ </Boundary>,
1231
+ {unstable_isConcurrent: true},
1232
+ );
1233
+ });
1234
+ assertConsoleErrorDev([
1235
+ 'Context can only be read while React is rendering. ' +
1236
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
1237
+ 'In function components, you can read it directly in the function body, ' +
1238
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
1239
+ ' in App (at **)' +
1240
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
1241
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
1242
+ 'You can only call Hooks at the top level of your React function. ' +
1243
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
1244
+ ' in App (at **)' +
1245
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
1246
+ 'Context can only be read while React is rendering. ' +
1247
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
1248
+ 'In function components, you can read it directly in the function body, ' +
1249
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
1250
+ ' in App (at **)' +
1251
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
1252
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
1253
+ 'You can only call Hooks at the top level of your React function. ' +
1254
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
1255
+ ' in App (at **)' +
1256
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
1257
]);
1258
1259
function Valid() {
@@ -1218,21 +1280,37 @@ describe('ReactHooks', () => {
1280
});
1281
1282
// Verify warnings don't get permanently disabled.
1221
- await expect(async () => {
1222
- await act(() => {
1223
- ReactTestRenderer.create(
1224
- <Boundary>
1225
- <App />
1226
- </Boundary>,
1227
- {unstable_isConcurrent: true},
1228
- );
1229
- });
1230
- }).toErrorDev([
1231
- 'Context can only be read while React is rendering',
1232
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
1233
-
1234
- 'Context can only be read while React is rendering',
1235
- 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks',
1283
+ await act(() => {
1284
+ ReactTestRenderer.create(
1285
+ <Boundary>
1286
+ <App />
1287
+ </Boundary>,
1288
+ {unstable_isConcurrent: true},
1289
+ );
1290
+ });
1291
+ assertConsoleErrorDev([
1292
+ 'Context can only be read while React is rendering. ' +
1293
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
1294
+ 'In function components, you can read it directly in the function body, ' +
1295
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
1296
+ ' in App (at **)' +
1297
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
1298
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
1299
+ 'You can only call Hooks at the top level of your React function. ' +
1300
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
1301
+ ' in App (at **)' +
1302
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
1303
+ 'Context can only be read while React is rendering. ' +
1304
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
1305
+ 'In function components, you can read it directly in the function body, ' +
1306
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
1307
+ ' in App (at **)' +
1308
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
1309
+ 'Do not call Hooks inside useEffect(...), useMemo(...), or other built-in Hooks. ' +
1310
+ 'You can only call Hooks at the top level of your React function. ' +
1311
+ 'For more information, see https://react.dev/link/rules-of-hooks\n' +
1312
+ ' in App (at **)' +
1313
+ (gate('enableOwnerStacks') ? '' : '\n in Boundary (at **)'),
1314
]);
1315
});
1316
@@ -1569,24 +1647,26 @@ describe('ReactHooks', () => {
1647
unstable_isConcurrent: true,
1648
});
1649
});
1572
- await expect(async () => {
1573
- try {
1574
- await act(() => {
1575
- root.update(<App update={true} />);
1576
- });
1577
- } catch (error) {
1578
- // Swapping certain types of hooks will cause runtime errors.
1579
- // This is okay as far as this test is concerned.
1580
- // We just want to verify that warnings are always logged.
1581
- }
1582
- }).toErrorDev([
1650
+ try {
1651
+ await act(() => {
1652
+ root.update(<App update={true} />);
1653
+ });
1654
+ } catch (error) {
1655
+ // Swapping certain types of hooks will cause runtime errors.
1656
+ // This is okay as far as this test is concerned.
1657
+ // We just want to verify that warnings are always logged.
1658
+ }
1659
+ assertConsoleErrorDev([
1660
'React has detected a change in the order of Hooks called by App. ' +
1661
'This will lead to bugs and errors if not fixed. For more information, ' +
1585
- 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
1662
+ 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n' +
1663
+ '\n' +
1664
' Previous render Next render\n' +
1665
' ------------------------------------------------------\n' +
1666
`1. ${formatHookNamesToMatchErrorMessage(hookNameA, hookNameB)}\n` +
1589
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
1667
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
1668
+ '\n' +
1669
+ ' in App (at **)',
1670
]);
1671
1672
// further warnings for this component are silenced
@@ -1618,25 +1698,27 @@ describe('ReactHooks', () => {
1698
});
1699
});
1700
1621
- await expect(async () => {
1622
- try {
1623
- await act(() => {
1624
- root.update(<App update={true} />);
1625
- });
1626
- } catch (error) {
1627
- // Swapping certain types of hooks will cause runtime errors.
1628
- // This is okay as far as this test is concerned.
1629
- // We just want to verify that warnings are always logged.
1630
- }
1631
- }).toErrorDev([
1701
+ try {
1702
+ await act(() => {
1703
+ root.update(<App update={true} />);
1704
+ });
1705
+ } catch (error) {
1706
+ // Swapping certain types of hooks will cause runtime errors.
1707
+ // This is okay as far as this test is concerned.
1708
+ // We just want to verify that warnings are always logged.
1709
+ }
1710
+ assertConsoleErrorDev([
1711
'React has detected a change in the order of Hooks called by App. ' +
1712
'This will lead to bugs and errors if not fixed. For more information, ' +
1634
- 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
1713
+ 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n' +
1714
+ '\n' +
1715
' Previous render Next render\n' +
1716
' ------------------------------------------------------\n' +
1717
`1. ${formatHookNamesToMatchErrorMessage(hookNameA, hookNameA)}\n` +
1718
`2. undefined use${hookNameB}\n` +
1639
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
1719
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
1720
+ '\n' +
1721
+ ' in App (at **)',
1722
]);
1723
});
1724
});
@@ -1701,24 +1783,26 @@ describe('ReactHooks', () => {
1783
unstable_isConcurrent: true,
1784
});
1785
});
1704
- await expect(async () => {
1705
- await act(() => {
1706
- root.update(<App update={true} />);
1707
- }).catch(e => {});
1708
- // Swapping certain types of hooks will cause runtime errors.
1709
- // This is okay as far as this test is concerned.
1710
- // We just want to verify that warnings are always logged.
1711
- }).toErrorDev([
1786
+ await act(() => {
1787
+ root.update(<App update={true} />);
1788
+ }).catch(e => {});
1789
+ // Swapping certain types of hooks will cause runtime errors.
1790
+ // This is okay as far as this test is concerned.
1791
+ // We just want to verify that warnings are always logged.
1792
+ assertConsoleErrorDev([
1793
'React has detected a change in the order of Hooks called by App. ' +
1794
'This will lead to bugs and errors if not fixed. For more information, ' +
1714
- 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
1795
+ 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n' +
1796
+ '\n' +
1797
' Previous render Next render\n' +
1798
' ------------------------------------------------------\n' +
1799
`1. ${formatHookNamesToMatchErrorMessage(
1800
'ImperativeHandle',
1801
'Memo',
1802
)}\n` +
1721
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
1803
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
1804
+ '\n' +
1805
+ ' in App (at **)',
1806
]);
1807
1808
// further warnings for this component are silenced
@@ -1751,19 +1835,21 @@ describe('ReactHooks', () => {
1835
});
1836
});
1837
await expect(async () => {
1754
- await expect(async () => {
1755
- await act(() => {
1756
- root.update(<App update={true} />);
1757
- });
1758
- }).rejects.toThrow('custom error');
1759
- }).toErrorDev([
1838
+ await act(() => {
1839
+ root.update(<App update={true} />);
1840
+ });
1841
+ }).rejects.toThrow('custom error');
1842
+ assertConsoleErrorDev([
1843
'React has detected a change in the order of Hooks called by App. ' +
1844
'This will lead to bugs and errors if not fixed. For more information, ' +
1762
- 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
1845
+ 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n' +
1846
+ '\n' +
1847
' Previous render Next render\n' +
1848
' ------------------------------------------------------\n' +
1849
'1. useReducer useState\n' +
1766
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
1850
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
1851
+ '\n' +
1852
+ ' in App (at **)',
1853
]);
1854
});
1855
});
@@ -1798,8 +1884,10 @@ describe('ReactHooks', () => {
1884
});
1885
}).rejects.toThrow('Hello');
1886
assertConsoleErrorDev([
1801
- 'Cannot update a component (`A`) while rendering ' +
1802
- 'a different component (`B`).',
1887
+ 'Cannot update a component (`A`) while rendering a different component (`B`). ' +
1888
+ 'To locate the bad setState() call inside `B`, ' +
1889
+ 'follow the stack trace as described in https://react.dev/link/setstate-in-render\n' +
1890
+ ' in B (at **)',
1891
]);
1892
});
1893
packages/react-reconciler/src/__tests__/ReactHooksWithNoopRenderer-test.js
+195
-143
@@ -42,6 +42,7 @@ let waitForThrow;
42
let waitForPaint;
43
let assertLog;
44
let useResourceEffect;
45
+let assertConsoleErrorDev;
46
47
describe('ReactHooksWithNoopRenderer', () => {
48
beforeEach(() => {
@@ -52,6 +53,8 @@ describe('ReactHooksWithNoopRenderer', () => {
53
ReactNoop = require('react-noop-renderer');
54
Scheduler = require('scheduler');
55
act = require('internal-test-utils').act;
56
+ assertConsoleErrorDev =
57
+ require('internal-test-utils').assertConsoleErrorDev;
58
useState = React.useState;
59
useReducer = React.useReducer;
60
useEffect = React.useEffect;
@@ -232,17 +235,18 @@ describe('ReactHooksWithNoopRenderer', () => {
235
});
236
237
it('throws when called outside the render phase', async () => {
235
- expect(() => {
236
- expect(() => useState(0)).toThrow(
237
- "Cannot read property 'useState' of null",
238
- );
239
- }).toErrorDev(
240
- 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
241
- ' one of the following reasons:\n' +
242
- '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
243
- '2. You might be breaking the Rules of Hooks\n' +
244
- '3. You might have more than one copy of React in the same app\n' +
245
- 'See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.',
238
+ expect(() => useState(0)).toThrow(
239
+ "Cannot read property 'useState' of null",
240
+ );
241
+ assertConsoleErrorDev(
242
+ [
243
+ 'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
244
+ ' one of the following reasons:\n' +
245
+ '1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
246
+ '2. You might be breaking the Rules of Hooks\n' +
247
+ '3. You might have more than one copy of React in the same app\n' +
248
+ 'See https://react.dev/link/invalid-hook-call for tips about how to debug and fix this problem.',
249
+ ],
250
{withoutStack: true},
251
);
252
});
@@ -459,11 +463,12 @@ describe('ReactHooksWithNoopRenderer', () => {
463
<Bar triggerUpdate={true} />
464
</>,
465
);
462
- await expect(
463
- async () => await waitForAll(['Foo [0]', 'Bar', 'Foo [1]']),
464
- ).toErrorDev([
465
- 'Cannot update a component (`Foo`) while rendering a ' +
466
- 'different component (`Bar`). To locate the bad setState() call inside `Bar`',
466
+ await waitForAll(['Foo [0]', 'Bar', 'Foo [1]']);
467
+ assertConsoleErrorDev([
468
+ 'Cannot update a component (`Foo`) while rendering a different component (`Bar`). ' +
469
+ 'To locate the bad setState() call inside `Bar`, ' +
470
+ 'follow the stack trace as described in https://react.dev/link/setstate-in-render\n' +
471
+ ' in Bar (at **)',
472
]);
473
474
// It should not warn again (deduplication).
@@ -1645,6 +1650,12 @@ describe('ReactHooksWithNoopRenderer', () => {
1650
updateCount(props.count);
1651
});
1652
assertLog([`Schedule update [${props.count}]`]);
1653
+ assertConsoleErrorDev([
1654
+ 'flushSync was called from inside a lifecycle method. ' +
1655
+ 'React cannot flush when React is already rendering. ' +
1656
+ 'Consider moving this call to a scheduler task or micro task.\n' +
1657
+ ' in Counter (at **)',
1658
+ ]);
1659
// This shouldn't flush synchronously.
1660
expect(ReactNoop).not.toMatchRenderedOutput(
1661
<span prop={`Count: ${props.count}`} />,
@@ -1652,17 +1663,14 @@ describe('ReactHooksWithNoopRenderer', () => {
1663
}, [props.count]);
1664
return <Text text={'Count: ' + count} />;
1665
}
1655
- await expect(async () => {
1656
- await act(async () => {
1657
- ReactNoop.render(<Counter count={0} />, () =>
1658
- Scheduler.log('Sync effect'),
1659
- );
1660
- await waitFor(['Count: (empty)', 'Sync effect']);
1661
- expect(ReactNoop).toMatchRenderedOutput(
1662
- <span prop="Count: (empty)" />,
1663
- );
1664
- });
1665
- }).toErrorDev('flushSync was called from inside a lifecycle method');
1666
+ await act(async () => {
1667
+ ReactNoop.render(<Counter count={0} />, () =>
1668
+ Scheduler.log('Sync effect'),
1669
+ );
1670
+ await waitFor(['Count: (empty)', 'Sync effect']);
1671
+ expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: (empty)" />);
1672
+ });
1673
+
1674
assertLog([`Count: 0`]);
1675
expect(ReactNoop).toMatchRenderedOutput(<span prop="Count: 0" />);
1676
});
@@ -2506,35 +2514,47 @@ describe('ReactHooksWithNoopRenderer', () => {
2514
}
2515
2516
const root1 = ReactNoop.createRoot();
2509
- await expect(async () => {
2510
- await act(() => {
2511
- root1.render(<App return={17} />);
2512
- });
2513
- }).toErrorDev([
2514
- 'useEffect must not return anything besides a ' +
2515
- 'function, which is used for clean-up. You returned: 17',
2517
+ await act(() => {
2518
+ root1.render(<App return={17} />);
2519
+ });
2520
+ assertConsoleErrorDev([
2521
+ 'useEffect must not return anything besides a function, ' +
2522
+ 'which is used for clean-up. You returned: 17\n' +
2523
+ ' in App (at **)',
2524
]);
2525
2526
const root2 = ReactNoop.createRoot();
2519
- await expect(async () => {
2520
- await act(() => {
2521
- root2.render(<App return={null} />);
2522
- });
2523
- }).toErrorDev([
2524
- 'useEffect must not return anything besides a ' +
2525
- 'function, which is used for clean-up. You returned null. If your ' +
2526
- 'effect does not require clean up, return undefined (or nothing).',
2527
+ await act(() => {
2528
+ root2.render(<App return={null} />);
2529
+ });
2530
+ assertConsoleErrorDev([
2531
+ 'useEffect must not return anything besides a function, ' +
2532
+ 'which is used for clean-up. You returned null. ' +
2533
+ 'If your effect does not require clean up, return undefined (or nothing).\n' +
2534
+ ' in App (at **)',
2535
]);
2536
2537
const root3 = ReactNoop.createRoot();
2530
- await expect(async () => {
2531
- await act(() => {
2532
- root3.render(<App return={Promise.resolve()} />);
2533
- });
2534
- }).toErrorDev([
2535
- 'useEffect must not return anything besides a ' +
2536
- 'function, which is used for clean-up.\n\n' +
2537
- 'It looks like you wrote useEffect(async () => ...) or returned a Promise.',
2538
+ await act(() => {
2539
+ root3.render(<App return={Promise.resolve()} />);
2540
+ });
2541
+ assertConsoleErrorDev([
2542
+ 'useEffect must not return anything besides a function, which is used for clean-up.\n' +
2543
+ '\n' +
2544
+ 'It looks like you wrote useEffect(async () => ...) or returned a Promise. ' +
2545
+ 'Instead, write the async function inside your effect and call it immediately:\n' +
2546
+ '\n' +
2547
+ 'useEffect(() => {\n' +
2548
+ ' async function fetchData() {\n' +
2549
+ ' // You can await here\n' +
2550
+ ' const response = await MyAPI.getData(someId);\n' +
2551
+ ' // ...\n' +
2552
+ ' }\n' +
2553
+ ' fetchData();\n' +
2554
+ "}, [someId]); // Or [] if effect doesn't need props or state\n" +
2555
+ '\n' +
2556
+ 'Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fetching\n' +
2557
+ ' in App (at **)',
2558
]);
2559
2560
// Error on unmount because React assumes the value is a function
@@ -2895,35 +2915,48 @@ describe('ReactHooksWithNoopRenderer', () => {
2915
}
2916
2917
const root1 = ReactNoop.createRoot();
2898
- await expect(async () => {
2899
- await act(() => {
2900
- root1.render(<App return={17} />);
2901
- });
2902
- }).toErrorDev([
2903
- 'useInsertionEffect must not return anything besides a ' +
2904
- 'function, which is used for clean-up. You returned: 17',
2918
+ await act(() => {
2919
+ root1.render(<App return={17} />);
2920
+ });
2921
+ assertConsoleErrorDev([
2922
+ 'useInsertionEffect must not return anything besides a function, ' +
2923
+ 'which is used for clean-up. You returned: 17\n' +
2924
+ ' in App (at **)',
2925
]);
2926
2927
const root2 = ReactNoop.createRoot();
2908
- await expect(async () => {
2909
- await act(() => {
2910
- root2.render(<App return={null} />);
2911
- });
2912
- }).toErrorDev([
2913
- 'useInsertionEffect must not return anything besides a ' +
2914
- 'function, which is used for clean-up. You returned null. If your ' +
2915
- 'effect does not require clean up, return undefined (or nothing).',
2928
+ await act(() => {
2929
+ root2.render(<App return={null} />);
2930
+ });
2931
+ assertConsoleErrorDev([
2932
+ 'useInsertionEffect must not return anything besides a function, ' +
2933
+ 'which is used for clean-up. You returned null. ' +
2934
+ 'If your effect does not require clean up, return undefined (or nothing).\n' +
2935
+ ' in App (at **)',
2936
]);
2937
2938
const root3 = ReactNoop.createRoot();
2919
- await expect(async () => {
2920
- await act(() => {
2921
- root3.render(<App return={Promise.resolve()} />);
2922
- });
2923
- }).toErrorDev([
2939
+ await act(() => {
2940
+ root3.render(<App return={Promise.resolve()} />);
2941
+ });
2942
+ assertConsoleErrorDev([
2943
'useInsertionEffect must not return anything besides a ' +
2925
- 'function, which is used for clean-up.\n\n' +
2926
- 'It looks like you wrote useInsertionEffect(async () => ...) or returned a Promise.',
2944
+ 'function, which is used for clean-up.\n' +
2945
+ '\n' +
2946
+ 'It looks like you wrote useInsertionEffect(async () => ...) or returned a Promise. ' +
2947
+ 'Instead, write the async function inside your effect and call it immediately:\n' +
2948
+ '\n' +
2949
+ 'useInsertionEffect(() => {\n' +
2950
+ ' async function fetchData() {\n' +
2951
+ ' // You can await here\n' +
2952
+ ' const response = await MyAPI.getData(someId);\n' +
2953
+ ' // ...\n' +
2954
+ ' }\n' +
2955
+ ' fetchData();\n' +
2956
+ "}, [someId]); // Or [] if effect doesn't need props or state\n" +
2957
+ '\n' +
2958
+ 'Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fetching\n' +
2959
+ ' in App (at **)',
2960
]);
2961
2962
// Error on unmount because React assumes the value is a function
@@ -2946,11 +2979,13 @@ describe('ReactHooksWithNoopRenderer', () => {
2979
}
2980
2981
const root = ReactNoop.createRoot();
2949
- await expect(async () => {
2950
- await act(() => {
2951
- root.render(<App />);
2952
- });
2953
- }).toErrorDev(['useInsertionEffect must not schedule updates.']);
2982
+ await act(() => {
2983
+ root.render(<App />);
2984
+ });
2985
+ assertConsoleErrorDev([
2986
+ 'useInsertionEffect must not schedule updates.\n' +
2987
+ ' in App (at **)',
2988
+ ]);
2989
2990
await act(async () => {
2991
root.render(<App throw={true} />);
@@ -2988,11 +3023,13 @@ describe('ReactHooksWithNoopRenderer', () => {
3023
await act(() => {
3024
root.render(<App foo="hello" />);
3025
});
2991
- await expect(async () => {
2992
- await act(() => {
2993
- root.render(<App foo="goodbye" />);
2994
- });
2995
- }).toErrorDev(['useInsertionEffect must not schedule updates.']);
3026
+ await act(() => {
3027
+ root.render(<App foo="goodbye" />);
3028
+ });
3029
+ assertConsoleErrorDev([
3030
+ 'useInsertionEffect must not schedule updates.\n' +
3031
+ ' in App (at **)',
3032
+ ]);
3033
3034
await act(async () => {
3035
root.render(<App throw={true} />);
@@ -3036,19 +3073,17 @@ describe('ReactHooksWithNoopRenderer', () => {
3073
);
3074
});
3075
3039
- if (gate(flags => flags.enableHiddenSubtreeInsertionEffectCleanup)) {
3040
- await expect(async () => {
3041
- await act(() => {
3042
- root.render(<Activity mode="hidden" />);
3043
- });
3044
- }).toErrorDev(['useInsertionEffect must not schedule updates.']);
3045
- } else {
3046
- await expect(async () => {
3047
- await act(() => {
3048
- root.render(<Activity mode="hidden" />);
3049
- });
3050
- }).toErrorDev([]);
3051
- }
3076
+ await act(() => {
3077
+ root.render(<Activity mode="hidden" />);
3078
+ });
3079
+ assertConsoleErrorDev(
3080
+ gate('enableHiddenSubtreeInsertionEffectCleanup')
3081
+ ? [
3082
+ 'useInsertionEffect must not schedule updates.\n' +
3083
+ ' in App (at **)',
3084
+ ]
3085
+ : [],
3086
+ );
3087
3088
// Should not warn for regular effects after throw.
3089
function NotInsertion() {
@@ -3225,35 +3260,47 @@ describe('ReactHooksWithNoopRenderer', () => {
3260
}
3261
3262
const root1 = ReactNoop.createRoot();
3228
- await expect(async () => {
3229
- await act(() => {
3230
- root1.render(<App return={17} />);
3231
- });
3232
- }).toErrorDev([
3263
+ await act(() => {
3264
+ root1.render(<App return={17} />);
3265
+ });
3266
+ assertConsoleErrorDev([
3267
'useLayoutEffect must not return anything besides a ' +
3234
- 'function, which is used for clean-up. You returned: 17',
3268
+ 'function, which is used for clean-up. You returned: 17\n' +
3269
+ ' in App (at **)',
3270
]);
3271
3272
const root2 = ReactNoop.createRoot();
3238
- await expect(async () => {
3239
- await act(() => {
3240
- root2.render(<App return={null} />);
3241
- });
3242
- }).toErrorDev([
3273
+ await act(() => {
3274
+ root2.render(<App return={null} />);
3275
+ });
3276
+ assertConsoleErrorDev([
3277
'useLayoutEffect must not return anything besides a ' +
3278
'function, which is used for clean-up. You returned null. If your ' +
3245
- 'effect does not require clean up, return undefined (or nothing).',
3279
+ 'effect does not require clean up, return undefined (or nothing).\n' +
3280
+ ' in App (at **)',
3281
]);
3282
3283
const root3 = ReactNoop.createRoot();
3249
- await expect(async () => {
3250
- await act(() => {
3251
- root3.render(<App return={Promise.resolve()} />);
3252
- });
3253
- }).toErrorDev([
3284
+ await act(() => {
3285
+ root3.render(<App return={Promise.resolve()} />);
3286
+ });
3287
+ assertConsoleErrorDev([
3288
'useLayoutEffect must not return anything besides a ' +
3289
'function, which is used for clean-up.\n\n' +
3256
- 'It looks like you wrote useLayoutEffect(async () => ...) or returned a Promise.',
3290
+ 'It looks like you wrote useLayoutEffect(async () => ...) or returned a Promise. ' +
3291
+ 'Instead, write the async function inside your effect and call it immediately:\n' +
3292
+ '\n' +
3293
+ 'useLayoutEffect(() => {\n' +
3294
+ ' async function fetchData() {\n' +
3295
+ ' // You can await here\n' +
3296
+ ' const response = await MyAPI.getData(someId);\n' +
3297
+ ' // ...\n' +
3298
+ ' }\n' +
3299
+ ' fetchData();\n' +
3300
+ "}, [someId]); // Or [] if effect doesn't need props or state\n" +
3301
+ '\n' +
3302
+ 'Learn more about data fetching with Hooks: https://react.dev/link/hooks-data-fetching\n' +
3303
+ ' in App (at **)',
3304
]);
3305
3306
// Error on unmount because React assumes the value is a function
@@ -3300,13 +3347,14 @@ describe('ReactHooksWithNoopRenderer', () => {
3347
return null;
3348
}
3349
3303
- await expect(async () => {
3304
- await act(() => {
3305
- ReactNoop.render(<App id={1} />);
3306
- });
3307
- }).toErrorDev(
3308
- 'useResourceEffect must provide a callback which returns a resource. ' +
3309
- 'If a managed resource is not needed here, use useEffect. Received undefined',
3350
+ await act(() => {
3351
+ ReactNoop.render(<App id={1} />);
3352
+ });
3353
+ assertConsoleErrorDev(
3354
+ [
3355
+ 'useResourceEffect must provide a callback which returns a resource. ' +
3356
+ 'If a managed resource is not needed here, use useEffect. Received undefined',
3357
+ ],
3358
{withoutStack: true},
3359
);
3360
});
@@ -3328,14 +3376,14 @@ describe('ReactHooksWithNoopRenderer', () => {
3376
return null;
3377
}
3378
3331
- await expect(async () => {
3332
- await act(() => {
3333
- ReactNoop.render(<App id={1} />);
3334
- });
3335
- }).toErrorDev(
3379
+ await act(() => {
3380
+ ReactNoop.render(<App id={1} />);
3381
+ });
3382
+ assertConsoleErrorDev([
3383
'useResourceEffect received a dependency array with no dependencies. ' +
3337
- 'When specified, the dependency array must have at least one dependency.',
3338
- );
3384
+ 'When specified, the dependency array must have at least one dependency.\n' +
3385
+ ' in App (at **)',
3386
+ ]);
3387
});
3388
3389
// @gate enableUseResourceEffectHook
@@ -4472,21 +4520,23 @@ describe('ReactHooksWithNoopRenderer', () => {
4520
);
4521
4522
ReactNoop.render(<App loadC={true} />);
4475
- await expect(async () => {
4476
- await waitForThrow(
4477
- 'Rendered more hooks than during the previous render.',
4478
- );
4479
- assertLog([]);
4480
- }).toErrorDev([
4523
+ await waitForThrow(
4524
+ 'Rendered more hooks than during the previous render.',
4525
+ );
4526
+ assertLog([]);
4527
+ assertConsoleErrorDev([
4528
'React has detected a change in the order of Hooks called by App. ' +
4529
'This will lead to bugs and errors if not fixed. For more information, ' +
4483
- 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
4530
+ 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n' +
4531
+ '\n' +
4532
' Previous render Next render\n' +
4533
' ------------------------------------------------------\n' +
4534
'1. useState useState\n' +
4535
'2. useState useState\n' +
4536
'3. undefined useState\n' +
4489
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
4537
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
4538
+ '\n' +
4539
+ ' in App (at **)',
4540
]);
4541
4542
// Uncomment if/when we support this again
@@ -4569,20 +4619,22 @@ describe('ReactHooksWithNoopRenderer', () => {
4619
4620
await act(async () => {
4621
ReactNoop.render(<App showMore={true} />);
4572
- await expect(async () => {
4573
- await waitForThrow(
4574
- 'Rendered more hooks than during the previous render.',
4575
- );
4576
- assertLog(['Unmount A']);
4577
- }).toErrorDev([
4622
+ await waitForThrow(
4623
+ 'Rendered more hooks than during the previous render.',
4624
+ );
4625
+ assertLog(['Unmount A']);
4626
+ assertConsoleErrorDev([
4627
'React has detected a change in the order of Hooks called by App. ' +
4628
'This will lead to bugs and errors if not fixed. For more information, ' +
4580
- 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n\n' +
4629
+ 'read the Rules of Hooks: https://react.dev/link/rules-of-hooks\n' +
4630
+ '\n' +
4631
' Previous render Next render\n' +
4632
' ------------------------------------------------------\n' +
4633
'1. useEffect useEffect\n' +
4634
'2. undefined useEffect\n' +
4585
- ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n',
4635
+ ' ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n' +
4636
+ '\n' +
4637
+ ' in App (at **)',
4638
]);
4639
});
4640
packages/react-reconciler/src/__tests__/ReactIncremental-test.js
+175
-38
@@ -1798,9 +1798,19 @@ describe('ReactIncremental', () => {
1798
'ShowBoth {"locale":"fr"}',
1799
]);
1800
assertConsoleErrorDev([
1801
- 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
1802
- 'ShowLocale uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
1803
- 'ShowBoth uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
1801
+ 'Intl uses the legacy childContextTypes API which will soon be removed. ' +
1802
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
1803
+ ' in Intl (at **)',
1804
+ 'ShowLocale uses the legacy contextTypes API which will soon be removed. ' +
1805
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
1806
+ ' in ShowLocale (at **)' +
1807
+ (gate('enableOwnerStacks') ? '' : '\n in Intl (at **)'),
1808
+ 'ShowBoth uses the legacy contextTypes API which will be removed soon. ' +
1809
+ 'Use React.createContext() with React.useContext() instead. (https://react.dev/link/legacy-context)\n' +
1810
+ ' in ShowBoth (at **)' +
1811
+ (gate('enableOwnerStacks')
1812
+ ? ''
1813
+ : '\n in div (at **)' + '\n in Intl (at **)'),
1814
]);
1815
1816
ReactNoop.render(
@@ -1853,8 +1863,18 @@ describe('ReactIncremental', () => {
1863
'ShowBoth {"locale":"en"}',
1864
]);
1865
assertConsoleErrorDev([
1856
- 'Router uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
1857
- 'ShowRoute uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
1866
+ 'Router uses the legacy childContextTypes API which will soon be removed. ' +
1867
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
1868
+ ' in Router (at **)' +
1869
+ (gate('enableOwnerStacks') ? '' : '\n in Intl (at **)'),
1870
+ 'ShowRoute uses the legacy contextTypes API which will soon be removed. ' +
1871
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
1872
+ (gate('enableOwnerStacks')
1873
+ ? ' in Indirection (at **)'
1874
+ : ' in ShowRoute (at **)\n' +
1875
+ ' in Indirection (at **)\n' +
1876
+ ' in Router (at **)\n' +
1877
+ ' in Intl (at **)'),
1878
]);
1879
});
1880
@@ -1890,8 +1910,12 @@ describe('ReactIncremental', () => {
1910
'Recurse {"n":0}',
1911
]);
1912
assertConsoleErrorDev([
1893
- 'Recurse uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
1894
- 'Recurse uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
1913
+ 'Recurse uses the legacy childContextTypes API which will soon be removed. ' +
1914
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
1915
+ ' in Recurse (at **)',
1916
+ 'Recurse uses the legacy contextTypes API which will soon be removed. ' +
1917
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
1918
+ ' in Recurse (at **)',
1919
]);
1920
});
1921
@@ -1943,8 +1967,13 @@ describe('ReactIncremental', () => {
1967
'ShowLocale {"locale":"fr"}',
1968
]);
1969
assertConsoleErrorDev([
1946
- 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
1947
- 'ShowLocale uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
1970
+ 'Intl uses the legacy childContextTypes API which will soon be removed. ' +
1971
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
1972
+ ' in Intl (at **)',
1973
+ 'ShowLocale uses the legacy contextTypes API which will soon be removed. ' +
1974
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
1975
+ ' in ShowLocale (at **)' +
1976
+ (gate('enableOwnerStacks') ? '' : '\n in Intl (at **)'),
1977
]);
1978
1979
await waitForAll([
@@ -2034,9 +2063,27 @@ describe('ReactIncremental', () => {
2063
'ShowLocaleFn:read {"locale":"fr"}',
2064
]);
2065
assertConsoleErrorDev([
2037
- 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2038
- 'ShowLocaleClass uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2039
- 'ShowLocaleFn uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
2066
+ 'Intl uses the legacy childContextTypes API which will soon be removed. ' +
2067
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
2068
+ ' in Intl (at **)',
2069
+ 'ShowLocaleClass uses the legacy contextTypes API which will soon be removed. ' +
2070
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
2071
+ ' in ShowLocaleClass (at **)' +
2072
+ (gate('enableOwnerStacks')
2073
+ ? ''
2074
+ : '\n in Stateful (at **)' +
2075
+ '\n in IndirectionClass (at **)' +
2076
+ '\n in IndirectionFn (at **)' +
2077
+ ' in Intl (at **)'),
2078
+ 'ShowLocaleFn uses the legacy contextTypes API which will be removed soon. ' +
2079
+ 'Use React.createContext() with React.useContext() instead. (https://react.dev/link/legacy-context)\n' +
2080
+ ' in ShowLocaleFn (at **)' +
2081
+ (gate('enableOwnerStacks')
2082
+ ? ''
2083
+ : '\n in Stateful (at **)' +
2084
+ '\n in IndirectionClass (at **)' +
2085
+ '\n in IndirectionFn (at **)' +
2086
+ ' in Intl (at **)'),
2087
]);
2088
2089
statefulInst.setState({x: 1});
@@ -2125,9 +2172,28 @@ describe('ReactIncremental', () => {
2172
]);
2173
2174
assertConsoleErrorDev([
2128
- 'Intl uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2129
- 'ShowLocaleClass uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2130
- 'ShowLocaleFn uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
2175
+ 'Intl uses the legacy childContextTypes API which will soon be removed. ' +
2176
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
2177
+ (gate('enableOwnerStacks') ? '' : ' in Intl (at **)\n') +
2178
+ ' in Stateful (at **)',
2179
+ 'ShowLocaleClass uses the legacy contextTypes API which will soon be removed. ' +
2180
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
2181
+ ' in ShowLocaleClass (at **)' +
2182
+ (gate('enableOwnerStacks')
2183
+ ? ''
2184
+ : '\n in IndirectionClass (at **)' +
2185
+ '\n in IndirectionFn (at **)' +
2186
+ '\n in Intl (at **)' +
2187
+ '\n in Stateful (at **)'),
2188
+ 'ShowLocaleFn uses the legacy contextTypes API which will be removed soon. ' +
2189
+ 'Use React.createContext() with React.useContext() instead. (https://react.dev/link/legacy-context)\n' +
2190
+ ' in ShowLocaleFn (at **)' +
2191
+ (gate('enableOwnerStacks')
2192
+ ? ''
2193
+ : '\n in IndirectionClass (at **)' +
2194
+ '\n in IndirectionFn (at **)' +
2195
+ '\n in Intl (at **)' +
2196
+ '\n in Stateful (at **)'),
2197
]);
2198
2199
statefulInst.setState({locale: 'gr'});
@@ -2187,7 +2253,11 @@ describe('ReactIncremental', () => {
2253
await waitForAll([]);
2254
2255
assertConsoleErrorDev([
2190
- 'Child uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2256
+ 'Child uses the legacy childContextTypes API which will soon be removed. ' +
2257
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
2258
+ (gate('enableOwnerStacks') ? '' : ' in Child (at **)\n') +
2259
+ ' in Middle (at **)\n' +
2260
+ ' in Root (at **)',
2261
]);
2262
2263
// Trigger an update in the middle of the tree
@@ -2235,8 +2305,12 @@ describe('ReactIncremental', () => {
2305
2306
// Init
2307
ReactNoop.render(<Root />);
2238
- await expect(async () => await waitForAll([])).toErrorDev([
2239
- 'ContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2308
+ await waitForAll([]);
2309
+ assertConsoleErrorDev([
2310
+ 'ContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
2311
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
2312
+ (gate('enableOwnerStacks') ? '' : ' in ContextProvider (at **)\n') +
2313
+ ' in Root (at **)',
2314
]);
2315
2316
// Trigger an update in the middle of the tree
@@ -2244,9 +2318,12 @@ describe('ReactIncremental', () => {
2318
instance.setState({
2319
throwError: true,
2320
});
2247
- await expect(async () => await waitForAll([])).toErrorDev(
2248
- 'Error boundaries should implement getDerivedStateFromError()',
2249
- );
2321
+ await waitForAll([]);
2322
+ assertConsoleErrorDev([
2323
+ 'Root: Error boundaries should implement getDerivedStateFromError(). ' +
2324
+ 'In that method, return a state update to display an error message or fallback UI.\n' +
2325
+ ' in Root (at **)',
2326
+ ]);
2327
});
2328
2329
// @gate !disableLegacyContext || !__DEV__
@@ -2292,7 +2369,9 @@ describe('ReactIncremental', () => {
2369
]);
2370
2371
assertConsoleErrorDev([
2295
- 'MyComponent uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2372
+ 'MyComponent uses the legacy contextTypes API which will soon be removed. ' +
2373
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
2374
+ ' in MyComponent (at **)',
2375
]);
2376
});
2377
@@ -2427,8 +2506,15 @@ describe('ReactIncremental', () => {
2506
2507
await waitForAll(['count:0']);
2508
assertConsoleErrorDev([
2430
- 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2431
- 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2509
+ 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
2510
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
2511
+ ' in TopContextProvider (at **)',
2512
+ 'Child uses the legacy contextTypes API which will soon be removed. ' +
2513
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
2514
+ ' in Child (at **)' +
2515
+ (gate('enableOwnerStacks')
2516
+ ? ''
2517
+ : '\n in Middle (at **)' + '\n in TopContextProvider (at **)'),
2518
]);
2519
instance.updateCount();
2520
await waitForAll(['count:1']);
@@ -2487,9 +2573,22 @@ describe('ReactIncremental', () => {
2573
2574
await waitForAll(['count:0']);
2575
assertConsoleErrorDev([
2490
- 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2491
- 'MiddleContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2492
- 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2576
+ 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
2577
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
2578
+ ' in TopContextProvider (at **)',
2579
+ 'MiddleContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
2580
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
2581
+ ' in MiddleContextProvider (at **)' +
2582
+ (gate('enableOwnerStacks')
2583
+ ? ''
2584
+ : '\n in TopContextProvider (at **)'),
2585
+ 'Child uses the legacy contextTypes API which will soon be removed. ' +
2586
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
2587
+ ' in Child (at **)' +
2588
+ (gate('enableOwnerStacks')
2589
+ ? ''
2590
+ : '\n in MiddleContextProvider (at **)' +
2591
+ '\n in TopContextProvider (at **)'),
2592
]);
2593
instance.updateCount();
2594
await waitForAll(['count:1']);
@@ -2557,9 +2656,24 @@ describe('ReactIncremental', () => {
2656
2657
await waitForAll(['count:0']);
2658
assertConsoleErrorDev([
2560
- 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2561
- 'MiddleContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2562
- 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2659
+ 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
2660
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
2661
+ ' in TopContextProvider (at **)',
2662
+ 'MiddleContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
2663
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
2664
+ ' in MiddleContextProvider (at **)' +
2665
+ (gate('enableOwnerStacks')
2666
+ ? ''
2667
+ : '\n in MiddleScu (at **)' +
2668
+ '\n in TopContextProvider (at **)'),
2669
+ 'Child uses the legacy contextTypes API which will soon be removed. ' +
2670
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
2671
+ ' in Child (at **)' +
2672
+ (gate('enableOwnerStacks')
2673
+ ? ''
2674
+ : '\n in MiddleContextProvider (at **)' +
2675
+ '\n in MiddleScu (at **)' +
2676
+ '\n in TopContextProvider (at **)'),
2677
]);
2678
instance.updateCount();
2679
await waitForAll([]);
@@ -2637,9 +2751,24 @@ describe('ReactIncremental', () => {
2751
2752
await waitForAll(['count:0, name:brian']);
2753
assertConsoleErrorDev([
2640
- 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2641
- 'MiddleContextProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
2642
- 'Child uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2754
+ 'TopContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
2755
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
2756
+ ' in TopContextProvider (at **)',
2757
+ 'MiddleContextProvider uses the legacy childContextTypes API which will soon be removed. ' +
2758
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
2759
+ ' in MiddleContextProvider (at **)' +
2760
+ (gate('enableOwnerStacks')
2761
+ ? ''
2762
+ : '\n in MiddleScu (at **)' +
2763
+ '\n in TopContextProvider (at **)'),
2764
+ 'Child uses the legacy contextTypes API which will soon be removed. ' +
2765
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
2766
+ ' in Child (at **)' +
2767
+ (gate('enableOwnerStacks')
2768
+ ? ''
2769
+ : '\n in MiddleContextProvider (at **)' +
2770
+ '\n in MiddleScu (at **)' +
2771
+ '\n in TopContextProvider (at **)'),
2772
]);
2773
topInstance.updateCount();
2774
await waitForAll([]);
@@ -2743,11 +2872,19 @@ describe('ReactIncremental', () => {
2872
<Boundary />
2873
</React.StrictMode>,
2874
);
2746
- await expect(async () => {
2747
- await waitForAll([]);
2748
- }).toErrorDev([
2749
- 'Boundary uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
2750
- 'Legacy context API has been detected within a strict-mode tree',
2875
+ await waitForAll([]);
2876
+ assertConsoleErrorDev([
2877
+ 'Boundary uses the legacy contextTypes API which will soon be removed. ' +
2878
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
2879
+ ' in Boundary (at **)',
2880
+ 'Legacy context API has been detected within a strict-mode tree.\n' +
2881
+ '\n' +
2882
+ 'The old API will be supported in all 16.x releases, but applications using it should migrate to the new version.\n' +
2883
+ '\n' +
2884
+ 'Please update the following components: Boundary\n' +
2885
+ '\n' +
2886
+ 'Learn more about this warning here: https://react.dev/link/legacy-context\n' +
2887
+ ' in Boundary (at **)',
2888
]);
2889
}
2890
packages/react-reconciler/src/__tests__/ReactIncrementalErrorHandling-test.internal.js
+42
-11
@@ -1212,12 +1212,18 @@ describe('ReactIncrementalErrorHandling', () => {
1212
</Provider>,
1213
);
1214
1215
- await expect(async () => {
1216
- await waitForAll([]);
1217
- }).toErrorDev([
1218
- 'Provider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
1219
- 'Provider uses the legacy contextTypes API which will soon be removed. Use React.createContext() with static contextType instead.',
1220
- 'Connector uses the legacy contextTypes API which will be removed soon. Use React.createContext() with React.useContext() instead.',
1215
+ await waitForAll([]);
1216
+ assertConsoleErrorDev([
1217
+ 'Provider uses the legacy childContextTypes API which will soon be removed. ' +
1218
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
1219
+ ' in Provider (at **)',
1220
+ 'Provider uses the legacy contextTypes API which will soon be removed. ' +
1221
+ 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
1222
+ ' in Provider (at **)',
1223
+ 'Connector uses the legacy contextTypes API which will be removed soon. ' +
1224
+ 'Use React.createContext() with React.useContext() instead. (https://react.dev/link/legacy-context)\n' +
1225
+ ' in Connector (at **)' +
1226
+ (gate('enableOwnerStacks') ? '' : '\n in Provider (at **)'),
1227
]);
1228
1229
// If the context stack does not unwind, span will get 'abcde'
@@ -1250,9 +1256,19 @@ describe('ReactIncrementalErrorHandling', () => {
1256
await waitForAll([]);
1257
if (gate(flags => !flags.enableOwnerStacks)) {
1258
assertConsoleErrorDev([
1253
- 'React.jsx: type is invalid -- expected a string',
1259
+ 'React.jsx: type is invalid -- expected a string (for built-in components) ' +
1260
+ 'or a class/function (for composite components) but got: undefined. ' +
1261
+ "You likely forgot to export your component from the file it's defined in, " +
1262
+ 'or you might have mixed up default and named imports.\n' +
1263
+ ' in BrokenRender (at **)\n' +
1264
+ ' in ErrorBoundary (at **)',
1265
// React retries once on error
1255
- 'React.jsx: type is invalid -- expected a string',
1266
+ 'React.jsx: type is invalid -- expected a string (for built-in components) ' +
1267
+ 'or a class/function (for composite components) but got: undefined. ' +
1268
+ "You likely forgot to export your component from the file it's defined in, " +
1269
+ 'or you might have mixed up default and named imports.\n' +
1270
+ ' in BrokenRender (at **)\n' +
1271
+ ' in ErrorBoundary (at **)',
1272
]);
1273
}
1274
@@ -1305,9 +1321,19 @@ describe('ReactIncrementalErrorHandling', () => {
1321
await waitForAll([]);
1322
if (gate(flags => !flags.enableOwnerStacks)) {
1323
assertConsoleErrorDev([
1308
- 'React.jsx: type is invalid -- expected a string',
1324
+ 'React.jsx: type is invalid -- expected a string (for built-in components) ' +
1325
+ 'or a class/function (for composite components) but got: undefined. ' +
1326
+ "You likely forgot to export your component from the file it's defined in, " +
1327
+ 'or you might have mixed up default and named imports.\n' +
1328
+ ' in BrokenRender (at **)\n' +
1329
+ ' in ErrorBoundary (at **)',
1330
// React retries once on error
1310
- 'React.jsx: type is invalid -- expected a string',
1331
+ 'React.jsx: type is invalid -- expected a string (for built-in components) ' +
1332
+ 'or a class/function (for composite components) but got: undefined. ' +
1333
+ "You likely forgot to export your component from the file it's defined in, " +
1334
+ 'or you might have mixed up default and named imports.\n' +
1335
+ ' in BrokenRender (at **)\n' +
1336
+ ' in ErrorBoundary (at **)',
1337
]);
1338
}
1339
expect(ReactNoop).toMatchRenderedOutput(
@@ -1330,7 +1356,12 @@ describe('ReactIncrementalErrorHandling', () => {
1356
ReactNoop.render(<InvalidType />);
1357
if (gate(flags => !flags.enableOwnerStacks)) {
1358
assertConsoleErrorDev(
1333
- ['React.jsx: type is invalid -- expected a string'],
1359
+ [
1360
+ 'React.jsx: type is invalid -- expected a string (for built-in components) ' +
1361
+ 'or a class/function (for composite components) but got: undefined. ' +
1362
+ "You likely forgot to export your component from the file it's defined in, " +
1363
+ 'or you might have mixed up default and named imports.',
1364
+ ],
1365
{withoutStack: true},
1366
);
1367
}
packages/react-reconciler/src/__tests__/ReactIncrementalErrorLogging-test.js
+1
-1
@@ -33,7 +33,7 @@ describe('ReactIncrementalErrorLogging', () => {
33
waitForAll = InternalTestUtils.waitForAll;
34
});
35
36
- // Note: in this test file we won't be using toErrorDev() matchers
36
+ // Note: in this test file we won't be using assertConsoleDev() matchers
37
// because they filter out precisely the messages we want to test for.
38
let oldConsoleWarn;
39
let oldConsoleError;
packages/react-reconciler/src/__tests__/ReactIncrementalUpdates-test.js
+14
-11
@@ -18,6 +18,7 @@ let act;
18
let waitForAll;
19
let waitFor;
20
let assertLog;
21
+let assertConsoleErrorDev;
22
23
describe('ReactIncrementalUpdates', () => {
24
beforeEach(() => {
@@ -34,6 +35,7 @@ describe('ReactIncrementalUpdates', () => {
35
waitForAll = InternalTestUtils.waitForAll;
36
waitFor = InternalTestUtils.waitFor;
37
assertLog = InternalTestUtils.assertLog;
38
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
39
});
40
41
function Text({text}) {
@@ -366,20 +368,21 @@ describe('ReactIncrementalUpdates', () => {
368
return {a: 'a'};
369
});
370
369
- await expect(
370
- async () =>
371
- await waitForAll([
372
- 'setState updater',
373
- // Updates in the render phase receive the currently rendering
374
- // lane, so the update flushes immediately in the same render.
375
- 'render',
376
- ]),
377
- ).toErrorDev(
371
+ await waitForAll([
372
+ 'setState updater',
373
+ // Updates in the render phase receive the currently rendering
374
+ // lane, so the update flushes immediately in the same render.
375
+ 'render',
376
+ ]);
377
+ assertConsoleErrorDev([
378
'An update (setState, replaceState, or forceUpdate) was scheduled ' +
379
'from inside an update function. Update functions should be pure, ' +
380
'with zero side-effects. Consider using componentDidUpdate or a ' +
381
- 'callback.\n\nPlease update the following component: Foo',
382
- );
381
+ 'callback.\n' +
382
+ '\n' +
383
+ 'Please update the following component: Foo\n' +
384
+ ' in Foo (at **)',
385
+ ]);
386
expect(instance.state).toEqual({a: 'a', b: 'b'});
387
388
// Test deduplication (no additional warnings expected)
packages/react-reconciler/src/__tests__/ReactLazy-test.internal.js
+48
-34
@@ -721,12 +721,13 @@ describe('ReactLazy', () => {
721
}
722
T.defaultProps = {inner: 'Hi'};
723
const LazyText = lazy(() => fakeImport(T));
724
- expect(() => {
725
- LazyText.defaultProps = {outer: 'Bye'};
726
- }).toErrorDev(
727
- 'It is not supported to assign `defaultProps` to ' +
728
- 'a lazy component import. Either specify them where the component ' +
729
- 'is defined, or create a wrapping component around it.',
724
+ LazyText.defaultProps = {outer: 'Bye'};
725
+ assertConsoleErrorDev(
726
+ [
727
+ 'It is not supported to assign `defaultProps` to ' +
728
+ 'a lazy component import. Either specify them where the component ' +
729
+ 'is defined, or create a wrapping component around it.',
730
+ ],
731
{withoutStack: true},
732
);
733
@@ -742,14 +743,15 @@ describe('ReactLazy', () => {
743
await waitForAll(['Loading...']);
744
expect(root).not.toMatchRenderedOutput('Hi Bye');
745
745
- await expect(async () => {
746
- await act(() => resolveFakeImport(T));
747
- assertLog(['Hi Bye']);
748
- }).toErrorDev(
746
+ await act(() => resolveFakeImport(T));
747
+ assertLog(['Hi Bye']);
748
+ assertConsoleErrorDev([
749
'T: Support for defaultProps ' +
750
'will be removed from function components in a future major ' +
751
- 'release. Use JavaScript default parameters instead.',
752
- );
751
+ 'release. Use JavaScript default parameters instead.\n' +
752
+ ' in T (at **)\n' +
753
+ ' in Suspense (at **)',
754
+ ]);
755
756
expect(root).toMatchRenderedOutput('Hi Bye');
757
@@ -1026,11 +1028,13 @@ describe('ReactLazy', () => {
1028
expect(root).not.toMatchRenderedOutput('Inner default text');
1029
1030
// Mount
1029
- await expect(async () => {
1030
- await act(() => resolveFakeImport(T));
1031
- assertLog(['Inner default text']);
1032
- }).toErrorDev([
1033
- 'T: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead.',
1031
+ await act(() => resolveFakeImport(T));
1032
+ assertLog(['Inner default text']);
1033
+ assertConsoleErrorDev([
1034
+ 'T: Support for defaultProps will be removed from function components in a future major release. ' +
1035
+ 'Use JavaScript default parameters instead.\n' +
1036
+ ' in T (at **)\n' +
1037
+ ' in Suspense (at **)',
1038
]);
1039
expect(root).toMatchRenderedOutput('Inner default text');
1040
@@ -1063,13 +1067,19 @@ describe('ReactLazy', () => {
1067
await waitForAll(['Started loading', 'Loading...']);
1068
expect(root).not.toMatchRenderedOutput(<div>AB</div>);
1069
1066
- await expect(async () => {
1067
- await act(() => resolveFakeImport(Foo));
1068
- assertLog(['A', 'B']);
1069
- }).toErrorDev(
1070
- (gate(flags => flags.enableOwnerStacks) ? '' : ' in Text (at **)\n') +
1071
- ' in Foo (at **)',
1072
- );
1070
+ await act(() => resolveFakeImport(Foo));
1071
+ assertLog(['A', 'B']);
1072
+ assertConsoleErrorDev([
1073
+ 'Each child in a list should have a unique "key" prop.\n' +
1074
+ '\n' +
1075
+ 'Check the render method of `Foo`. ' +
1076
+ 'See https://react.dev/link/warning-keys for more information.\n' +
1077
+ (gate(flags => flags.enableOwnerStacks)
1078
+ ? ' in Foo (at **)'
1079
+ : ' in Text (at **)\n' +
1080
+ ' in Foo (at **)\n' +
1081
+ ' in Suspense (at **)'),
1082
+ ]);
1083
expect(root).toMatchRenderedOutput(<div>AB</div>);
1084
});
1085
@@ -1143,11 +1153,12 @@ describe('ReactLazy', () => {
1153
expect(root).not.toMatchRenderedOutput('4');
1154
1155
// Mount
1146
- await expect(async () => {
1147
- await act(() => resolveFakeImport(Add));
1148
- }).toErrorDev(
1149
- 'Unknown: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
1150
- );
1156
+ await act(() => resolveFakeImport(Add));
1157
+ assertConsoleErrorDev([
1158
+ 'Unknown: Support for defaultProps will be removed from memo components in a future major release. ' +
1159
+ 'Use JavaScript default parameters instead.\n' +
1160
+ ' in Suspense (at **)',
1161
+ ]);
1162
expect(root).toMatchRenderedOutput('4');
1163
1164
// Update (shallowly equal)
@@ -1231,11 +1242,14 @@ describe('ReactLazy', () => {
1242
expect(root).not.toMatchRenderedOutput('4');
1243
1244
// Mount
1234
- await expect(async () => {
1235
- await act(() => resolveFakeImport(Add));
1236
- }).toErrorDev([
1237
- 'Memo: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
1238
- 'Unknown: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
1245
+ await act(() => resolveFakeImport(Add));
1246
+ assertConsoleErrorDev([
1247
+ 'Memo: Support for defaultProps will be removed from memo components in a future major release. ' +
1248
+ 'Use JavaScript default parameters instead.\n' +
1249
+ ' in Suspense (at **)',
1250
+ 'Unknown: Support for defaultProps will be removed from memo components in a future major release. ' +
1251
+ 'Use JavaScript default parameters instead.\n' +
1252
+ ' in Suspense (at **)',
1253
]);
1254
expect(root).toMatchRenderedOutput('4');
1255
packages/react-reconciler/src/__tests__/ReactMemo-test.js
+57
-48
@@ -19,6 +19,7 @@ let Scheduler;
19
let act;
20
let waitForAll;
21
let assertLog;
22
+let assertConsoleErrorDev;
23
24
describe('memo', () => {
25
beforeEach(() => {
@@ -33,6 +34,7 @@ describe('memo', () => {
34
const InternalTestUtils = require('internal-test-utils');
35
waitForAll = InternalTestUtils.waitForAll;
36
assertLog = InternalTestUtils.assertLog;
37
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
38
});
39
40
function Text(props) {
@@ -397,17 +399,19 @@ describe('memo', () => {
399
// The final layer uses memo() from test fixture (which might be lazy).
400
Counter = memo(Counter);
401
400
- await expect(async () => {
401
- await act(() => {
402
- ReactNoop.render(
403
- <Suspense fallback={<Text text="Loading..." />}>
404
- <Counter e={5} />
405
- </Suspense>,
406
- );
407
- });
408
- assertLog(['Loading...', 15]);
409
- }).toErrorDev([
410
- 'Counter: Support for defaultProps will be removed from memo components in a future major release. Use JavaScript default parameters instead.',
402
+ await act(() => {
403
+ ReactNoop.render(
404
+ <Suspense fallback={<Text text="Loading..." />}>
405
+ <Counter e={5} />
406
+ </Suspense>,
407
+ );
408
+ });
409
+ assertLog(['Loading...', 15]);
410
+ assertConsoleErrorDev([
411
+ 'Counter: Support for defaultProps will be removed from memo components in a future major release. ' +
412
+ 'Use JavaScript default parameters instead.\n' +
413
+ (label === 'lazy' ? '' : ' in Indirection (at **)\n') +
414
+ ' in Suspense (at **)',
415
]);
416
expect(ReactNoop).toMatchRenderedOutput(<span prop={15} />);
417
@@ -431,17 +435,23 @@ describe('memo', () => {
435
});
436
437
it('warns if the first argument is undefined', () => {
434
- expect(() => memo()).toErrorDev(
435
- 'memo: The first argument must be a component. Instead ' +
436
- 'received: undefined',
438
+ memo();
439
+ assertConsoleErrorDev(
440
+ [
441
+ 'memo: The first argument must be a component. Instead ' +
442
+ 'received: undefined',
443
+ ],
444
{withoutStack: true},
445
);
446
});
447
448
it('warns if the first argument is null', () => {
442
- expect(() => memo(null)).toErrorDev(
443
- 'memo: The first argument must be a component. Instead ' +
444
- 'received: null',
449
+ memo(null);
450
+ assertConsoleErrorDev(
451
+ [
452
+ 'memo: The first argument must be a component. Instead ' +
453
+ 'received: null',
454
+ ],
455
{withoutStack: true},
456
);
457
});
@@ -458,16 +468,18 @@ describe('memo', () => {
468
Outer.defaultProps = {outer: 100};
469
470
const root = ReactNoop.createRoot();
461
- await expect(async () => {
462
- await act(() => {
463
- root.render(
464
- <div>
465
- <Outer />
466
- </div>,
467
- );
468
- });
469
- }).toErrorDev([
470
- 'Support for defaultProps will be removed from memo component',
471
+ await act(() => {
472
+ root.render(
473
+ <div>
474
+ <Outer />
475
+ </div>,
476
+ );
477
+ });
478
+ assertConsoleErrorDev([
479
+ 'Inner: ' +
480
+ 'Support for defaultProps will be removed from memo components in a future major release. ' +
481
+ 'Use JavaScript default parameters instead.\n' +
482
+ ' in div (at **)',
483
]);
484
expect(root).toMatchRenderedOutput(<div>111</div>);
485
@@ -559,14 +571,15 @@ describe('memo', () => {
571
<MemoComponent />
572
</p>,
573
);
562
- await expect(async () => {
563
- await waitForAll([]);
564
- }).toErrorDev(
574
+ await waitForAll([]);
575
+ assertConsoleErrorDev([
576
'Each child in a list should have a unique "key" prop. ' +
577
'See https://react.dev/link/warning-keys for more information.\n' +
578
' in span (at **)\n' +
568
- ' in ',
569
- );
579
+ (gate('enableOwnerStacks')
580
+ ? ' in **/ReactMemo-test.js:**:** (at **)'
581
+ : ' in p (at **)'),
582
+ ]);
583
});
584
585
it('should use the inner function name for the stack', async () => {
@@ -578,16 +591,15 @@ describe('memo', () => {
591
<MemoComponent />
592
</p>,
593
);
581
- await expect(async () => {
582
- await waitForAll([]);
583
- }).toErrorDev(
594
+ await waitForAll([]);
595
+ assertConsoleErrorDev([
596
'Each child in a list should have a unique "key" prop.' +
597
'\n\nCheck the top-level render call using <Inner>. It was passed a child from Inner. ' +
598
'See https://react.dev/link/warning-keys for more information.\n' +
599
' in span (at **)\n' +
600
' in Inner (at **)' +
601
(gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'),
590
- );
602
+ ]);
603
});
604
605
it('should use the inner name in the stack', async () => {
@@ -601,16 +613,15 @@ describe('memo', () => {
613
<MemoComponent />
614
</p>,
615
);
604
- await expect(async () => {
605
- await waitForAll([]);
606
- }).toErrorDev(
616
+ await waitForAll([]);
617
+ assertConsoleErrorDev([
618
'Each child in a list should have a unique "key" prop.' +
619
'\n\nCheck the top-level render call using <Inner>. It was passed a child from Inner. ' +
620
'See https://react.dev/link/warning-keys for more information.\n' +
621
' in span (at **)\n' +
622
' in Inner (at **)' +
623
(gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'),
613
- );
624
+ ]);
625
});
626
627
it('can use the outer displayName in the stack', async () => {
@@ -623,16 +634,15 @@ describe('memo', () => {
634
<MemoComponent />
635
</p>,
636
);
626
- await expect(async () => {
627
- await waitForAll([]);
628
- }).toErrorDev(
637
+ await waitForAll([]);
638
+ assertConsoleErrorDev([
639
'Each child in a list should have a unique "key" prop.' +
640
'\n\nCheck the top-level render call using <Outer>. It was passed a child from Outer. ' +
641
'See https://react.dev/link/warning-keys for more information.\n' +
642
' in span (at **)\n' +
643
' in Outer (at **)' +
644
(gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'),
635
- );
645
+ ]);
646
});
647
648
it('should prefer the inner to the outer displayName in the stack', async () => {
@@ -647,16 +657,15 @@ describe('memo', () => {
657
<MemoComponent />
658
</p>,
659
);
650
- await expect(async () => {
651
- await waitForAll([]);
652
- }).toErrorDev(
660
+ await waitForAll([]);
661
+ assertConsoleErrorDev([
662
'Each child in a list should have a unique "key" prop.' +
663
'\n\nCheck the top-level render call using <Inner>. It was passed a child from Inner. ' +
664
'See https://react.dev/link/warning-keys for more information.\n' +
665
' in span (at **)\n' +
666
' in Inner (at **)' +
667
(gate(flags => flags.enableOwnerStacks) ? '' : '\n in p (at **)'),
659
- );
668
+ ]);
669
});
670
}
671
});
packages/react-reconciler/src/__tests__/ReactNewContext-test.js
+23
-9
@@ -861,8 +861,11 @@ describe('ReactNewContext', () => {
861
<Context.Provider anyPropNameOtherThanValue="value could be anything" />,
862
);
863
864
- await expect(async () => await waitForAll([])).toErrorDev(
865
- 'The `value` prop is required for the `<Context.Provider>`. Did you misspell it or forget to pass it?',
864
+ await waitForAll([]);
865
+ assertConsoleErrorDev(
866
+ [
867
+ 'The `value` prop is required for the `<Context.Provider>`. Did you misspell it or forget to pass it?',
868
+ ],
869
{
870
withoutStack: true,
871
},
@@ -1036,7 +1039,9 @@ describe('ReactNewContext', () => {
1039
);
1040
await waitForAll(['LegacyProvider', 'App', 'Child']);
1041
assertConsoleErrorDev([
1039
- 'LegacyProvider uses the legacy childContextTypes API which will soon be removed. Use React.createContext() instead.',
1042
+ 'LegacyProvider uses the legacy childContextTypes API which will soon be removed. ' +
1043
+ 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
1044
+ ' in LegacyProvider (at **)',
1045
]);
1046
expect(ReactNoop).toMatchRenderedOutput(<span prop="Child" />);
1047
@@ -1320,9 +1325,16 @@ describe('ReactNewContext', () => {
1325
}
1326
1327
ReactNoop.render(<Cls />);
1323
- await expect(async () => await waitForAll([])).toErrorDev([
1324
- 'Context can only be read while React is rendering',
1325
- 'Cannot update during an existing state transition',
1328
+ await waitForAll([]);
1329
+ assertConsoleErrorDev([
1330
+ 'Cannot update during an existing state transition (such as within `render`). ' +
1331
+ 'Render methods should be a pure function of props and state.\n' +
1332
+ ' in Cls (at **)',
1333
+ 'Context can only be read while React is rendering. ' +
1334
+ 'In classes, you can read it in the render method or getDerivedStateFromProps. ' +
1335
+ 'In function components, you can read it directly in the function body, ' +
1336
+ 'but not inside Hooks like useReducer() or useMemo().\n' +
1337
+ ' in Cls (at **)',
1338
]);
1339
});
1340
});
@@ -1353,10 +1365,12 @@ describe('ReactNewContext', () => {
1365
return useContext(Context.Consumer);
1366
}
1367
ReactNoop.render(<Foo />);
1356
- await expect(async () => await waitForAll([])).toErrorDev(
1368
+ await waitForAll([]);
1369
+ assertConsoleErrorDev([
1370
'Calling useContext(Context.Consumer) is not supported and will cause bugs. ' +
1358
- 'Did you mean to call useContext(Context) instead?',
1359
- );
1371
+ 'Did you mean to call useContext(Context) instead?\n' +
1372
+ ' in Foo (at **)',
1373
+ ]);
1374
});
1375
1376
// Context consumer bails out on propagating "deep" updates when `value` hasn't changed.
packages/react-reconciler/src/__tests__/ReactSuspenseCallback-test.js
+8
-5
@@ -13,6 +13,7 @@ let React;
13
let ReactNoop;
14
let waitForAll;
15
let act;
16
+let assertConsoleErrorDev;
17
18
describe('ReactSuspense', () => {
19
beforeEach(() => {
@@ -24,6 +25,7 @@ describe('ReactSuspense', () => {
25
const InternalTestUtils = require('internal-test-utils');
26
waitForAll = InternalTestUtils.waitForAll;
27
act = InternalTestUtils.act;
28
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
29
});
30
31
function createThenable() {
@@ -57,10 +59,10 @@ describe('ReactSuspense', () => {
59
);
60
61
ReactNoop.render(elementBadType);
60
- await expect(async () => await waitForAll([])).toErrorDev(
61
- ['Unexpected type for suspenseCallback.'],
62
- {withoutStack: true},
63
- );
62
+ await waitForAll([]);
63
+ assertConsoleErrorDev(['Unexpected type for suspenseCallback.'], {
64
+ withoutStack: true,
65
+ });
66
67
const elementMissingCallback = (
68
<React.Suspense fallback={'Waiting'}>
@@ -69,7 +71,8 @@ describe('ReactSuspense', () => {
71
);
72
73
ReactNoop.render(elementMissingCallback);
72
- await expect(async () => await waitForAll([])).toErrorDev([]);
74
+ await waitForAll([]);
75
+ assertConsoleErrorDev([]);
76
});
77
78
// @gate enableSuspenseCallback
packages/react-reconciler/src/__tests__/ReactSuspenseList-test.js
+38
-44
@@ -18,6 +18,7 @@ let SuspenseList;
18
let waitForAll;
19
let assertLog;
20
let waitFor;
21
+let assertConsoleErrorDev;
22
23
describe('ReactSuspenseList', () => {
24
beforeEach(() => {
@@ -37,6 +38,7 @@ describe('ReactSuspenseList', () => {
38
assertLog = InternalTestUtils.assertLog;
39
waitFor = InternalTestUtils.waitFor;
40
act = InternalTestUtils.act;
41
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
42
});
43
44
function Text(props) {
@@ -72,11 +74,10 @@ describe('ReactSuspenseList', () => {
74
);
75
}
76
75
- await expect(async () => {
76
- await act(() => {
77
- ReactNoop.render(<Foo />);
78
- });
79
- }).toErrorDev([
77
+ await act(() => {
78
+ ReactNoop.render(<Foo />);
79
+ });
80
+ assertConsoleErrorDev([
81
'"something" is not a supported revealOrder on ' +
82
'<SuspenseList />. Did you mean "together", "forwards" or "backwards"?' +
83
'\n in SuspenseList (at **)' +
@@ -94,11 +95,10 @@ describe('ReactSuspenseList', () => {
95
);
96
}
97
97
- await expect(async () => {
98
- await act(() => {
99
- ReactNoop.render(<Foo />);
100
- });
101
- }).toErrorDev([
98
+ await act(() => {
99
+ ReactNoop.render(<Foo />);
100
+ });
101
+ assertConsoleErrorDev([
102
'"TOGETHER" is not a valid value for revealOrder on ' +
103
'<SuspenseList />. Use lowercase "together" instead.' +
104
'\n in SuspenseList (at **)' +
@@ -116,11 +116,10 @@ describe('ReactSuspenseList', () => {
116
);
117
}
118
119
- await expect(async () => {
120
- await act(() => {
121
- ReactNoop.render(<Foo />);
122
- });
123
- }).toErrorDev([
119
+ await act(() => {
120
+ ReactNoop.render(<Foo />);
121
+ });
122
+ assertConsoleErrorDev([
123
'"forward" is not a valid value for revealOrder on ' +
124
'<SuspenseList />. React uses the -s suffix in the spelling. ' +
125
'Use "forwards" instead.' +
@@ -147,15 +146,14 @@ describe('ReactSuspenseList', () => {
146
// No warning
147
await waitForAll([]);
148
150
- await expect(async () => {
151
- await act(() => {
152
- ReactNoop.render(
153
- <Foo>
154
- <Suspense fallback="Loading">Child</Suspense>
155
- </Foo>,
156
- );
157
- });
158
- }).toErrorDev([
149
+ await act(() => {
150
+ ReactNoop.render(
151
+ <Foo>
152
+ <Suspense fallback="Loading">Child</Suspense>
153
+ </Foo>,
154
+ );
155
+ });
156
+ assertConsoleErrorDev([
157
'A single row was passed to a <SuspenseList revealOrder="forwards" />. ' +
158
'This is not useful since it needs multiple rows. ' +
159
'Did you mean to pass multiple children or an array?' +
@@ -174,11 +172,10 @@ describe('ReactSuspenseList', () => {
172
);
173
}
174
177
- await expect(async () => {
178
- await act(() => {
179
- ReactNoop.render(<Foo />);
180
- });
181
- }).toErrorDev([
175
+ await act(() => {
176
+ ReactNoop.render(<Foo />);
177
+ });
178
+ assertConsoleErrorDev([
179
'A single row was passed to a <SuspenseList revealOrder="backwards" />. ' +
180
'This is not useful since it needs multiple rows. ' +
181
'Did you mean to pass multiple children or an array?' +
@@ -202,11 +199,10 @@ describe('ReactSuspenseList', () => {
199
);
200
}
201
205
- await expect(async () => {
206
- await act(() => {
207
- ReactNoop.render(<Foo items={['A', 'B']} />);
208
- });
209
- }).toErrorDev([
202
+ await act(() => {
203
+ ReactNoop.render(<Foo items={['A', 'B']} />);
204
+ });
205
+ assertConsoleErrorDev([
206
'A nested array was passed to row #0 in <SuspenseList />. ' +
207
'Wrap it in an additional SuspenseList to configure its revealOrder: ' +
208
'<SuspenseList revealOrder=...> ... ' +
@@ -1555,11 +1551,10 @@ describe('ReactSuspenseList', () => {
1551
);
1552
}
1553
1558
- await expect(async () => {
1559
- await act(() => {
1560
- ReactNoop.render(<Foo />);
1561
- });
1562
- }).toErrorDev([
1554
+ await act(() => {
1555
+ ReactNoop.render(<Foo />);
1556
+ });
1557
+ assertConsoleErrorDev([
1558
'"collapse" is not a supported value for tail on ' +
1559
'<SuspenseList />. Did you mean "collapsed" or "hidden"?' +
1560
'\n in SuspenseList (at **)' +
@@ -1577,11 +1572,10 @@ describe('ReactSuspenseList', () => {
1572
);
1573
}
1574
1580
- await expect(async () => {
1581
- await act(() => {
1582
- ReactNoop.render(<Foo />);
1583
- });
1584
- }).toErrorDev([
1575
+ await act(() => {
1576
+ ReactNoop.render(<Foo />);
1577
+ });
1578
+ assertConsoleErrorDev([
1579
'<SuspenseList tail="collapsed" /> is only valid if ' +
1580
'revealOrder is "forwards" or "backwards". ' +
1581
'Did you mean to specify revealOrder="forwards"?' +
packages/react-reconciler/src/__tests__/ReactSuspenseWithNoopRenderer-test.js
+3
-3
@@ -1979,7 +1979,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
1979
ReactNoop.render(<App />);
1980
});
1981
1982
- // TODO: assert toErrorDev() when the warning is implemented again.
1982
+ // TODO: assertConsoleErrorDev() when the warning is implemented again.
1983
await act(() => {
1984
ReactNoop.flushSync(() => _setShow(true));
1985
});
@@ -2006,7 +2006,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2006
ReactNoop.render(<App />);
2007
});
2008
2009
- // TODO: assert toErrorDev() when the warning is implemented again.
2009
+ // TODO: assertConsoleErrorDev() when the warning is implemented again.
2010
await act(() => {
2011
ReactNoop.flushSync(() => show());
2012
});
@@ -2076,7 +2076,7 @@ describe('ReactSuspenseWithNoopRenderer', () => {
2076
ReactNoop.render(<App />);
2077
});
2078
2079
- // TODO: assert toErrorDev() when the warning is implemented again.
2079
+ // TODO: assertConsoleErrorDev() when the warning is implemented again.
2080
await act(() => {
2081
ReactNoop.flushSync(() => _setShow(true));
2082
});
packages/react-reconciler/src/__tests__/ReactTransitionTracing-test.js
+21
-21
@@ -14,6 +14,7 @@ let Scheduler;
14
let act;
15
let waitForAll;
16
let assertLog;
17
+let assertConsoleErrorDev;
18
19
let getCacheForType;
20
let useState;
@@ -43,11 +44,11 @@ describe('ReactInteractionTracing', () => {
44
ReactNoop = require('react-noop-renderer');
45
Scheduler = require('scheduler');
46
46
- act = require('internal-test-utils').act;
47
-
47
const InternalTestUtils = require('internal-test-utils');
48
+ act = InternalTestUtils.act;
49
waitForAll = InternalTestUtils.waitForAll;
50
assertLog = InternalTestUtils.assertLog;
51
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
52
53
useState = React.useState;
54
startTransition = React.startTransition;
@@ -1412,14 +1413,12 @@ describe('ReactInteractionTracing', () => {
1413
root.render(<App navigate={true} markerName="marker two" />);
1414
ReactNoop.expire(1000);
1415
await advanceTimers(1000);
1415
- await expect(
1416
- async () =>
1417
- await waitForAll([
1418
- 'Suspend [Page Two]',
1419
- 'Loading...',
1420
- 'onMarkerIncomplete(transition one, marker one, 1000, [{endTime: 3000, name: marker one, newName: marker two, type: marker}])',
1421
- ]),
1422
- ).toErrorDev('');
1416
+
1417
+ await waitForAll([
1418
+ 'Suspend [Page Two]',
1419
+ 'Loading...',
1420
+ 'onMarkerIncomplete(transition one, marker one, 1000, [{endTime: 3000, name: marker one, newName: marker two, type: marker}])',
1421
+ ]);
1422
1423
resolveText('Page Two');
1424
ReactNoop.expire(1000);
@@ -2220,17 +2219,18 @@ describe('ReactInteractionTracing', () => {
2219
);
2220
ReactNoop.expire(1000);
2221
await advanceTimers(1000);
2223
- await expect(async () => {
2224
- // onMarkerComplete shouldn't be called for transitions with
2225
- // new keys
2226
- await waitForAll([
2227
- 'two',
2228
- 'onTransitionStart(transition two, 1000)',
2229
- 'onTransitionComplete(transition two, 1000, 2000)',
2230
- ]);
2231
- }).toErrorDev(
2232
- 'Changing the name of a tracing marker after mount is not supported.',
2233
- );
2222
+ // onMarkerComplete shouldn't be called for transitions with
2223
+ // new keys
2224
+ await waitForAll([
2225
+ 'two',
2226
+ 'onTransitionStart(transition two, 1000)',
2227
+ 'onTransitionComplete(transition two, 1000, 2000)',
2228
+ ]);
2229
+ assertConsoleErrorDev([
2230
+ 'Changing the name of a tracing marker after mount is not supported. ' +
2231
+ 'To remount the tracing marker, pass it a new key.\n' +
2232
+ ' in App (at **)',
2233
+ ]);
2234
startTransition(
2235
() => root.render(<App markerName="three" markerKey="new key" />),
2236
{