[assert helpers] react-dom (pt 1) (#31897)
Converts ~half of react-dom tests
Ricky committed
Jan 2, 2025 at 15:28 UTC
a7c898d83a991c48f3981fcc65d969f1d90d80a1
13 files changed
+781
-573
packages/react-dom/src/__tests__/CSSPropertyOperations-test.js
+29
-33
@@ -13,6 +13,8 @@ const React = require('react');
13
const ReactDOMClient = require('react-dom/client');
14
const ReactDOMServer = require('react-dom/server');
15
const act = require('internal-test-utils').act;
16
+const assertConsoleErrorDev =
17
+ require('internal-test-utils').assertConsoleErrorDev;
18
19
describe('CSSPropertyOperations', () => {
20
it('should automatically append `px` to relevant styles', () => {
@@ -103,15 +105,14 @@ describe('CSSPropertyOperations', () => {
105
106
const container = document.createElement('div');
107
const root = ReactDOMClient.createRoot(container);
106
- await expect(async () => {
107
- await act(() => {
108
- root.render(<Comp />);
109
- });
110
- }).toErrorDev(
108
+ await act(() => {
109
+ root.render(<Comp />);
110
+ });
111
+ assertConsoleErrorDev([
112
'Unsupported style property background-color. Did you mean backgroundColor?' +
113
'\n in div (at **)' +
114
'\n in Comp (at **)',
114
- );
115
+ ]);
116
});
117
118
it('should warn when updating hyphenated style names', async () => {
@@ -132,11 +133,10 @@ describe('CSSPropertyOperations', () => {
133
await act(() => {
134
root.render(<Comp />);
135
});
135
- await expect(async () => {
136
- await act(() => {
137
- root.render(<Comp style={styles} />);
138
- });
139
- }).toErrorDev([
136
+ await act(() => {
137
+ root.render(<Comp style={styles} />);
138
+ });
139
+ assertConsoleErrorDev([
140
'Unsupported style property -ms-transform. Did you mean msTransform?' +
141
'\n in div (at **)' +
142
'\n in Comp (at **)',
@@ -165,11 +165,10 @@ describe('CSSPropertyOperations', () => {
165
166
const container = document.createElement('div');
167
const root = ReactDOMClient.createRoot(container);
168
- await expect(async () => {
169
- await act(() => {
170
- root.render(<Comp />);
171
- });
172
- }).toErrorDev([
168
+ await act(() => {
169
+ root.render(<Comp />);
170
+ });
171
+ assertConsoleErrorDev([
172
// msTransform is correct already and shouldn't warn
173
'Unsupported vendor-prefixed style property oTransform. ' +
174
'Did you mean OTransform?' +
@@ -202,11 +201,10 @@ describe('CSSPropertyOperations', () => {
201
202
const container = document.createElement('div');
203
const root = ReactDOMClient.createRoot(container);
205
- await expect(async () => {
206
- await act(() => {
207
- root.render(<Comp />);
208
- });
209
- }).toErrorDev([
204
+ await act(() => {
205
+ root.render(<Comp />);
206
+ });
207
+ assertConsoleErrorDev([
208
"Style property values shouldn't contain a semicolon. " +
209
'Try "backgroundColor: blue" instead.' +
210
'\n in div (at **)' +
@@ -229,15 +227,14 @@ describe('CSSPropertyOperations', () => {
227
228
const container = document.createElement('div');
229
const root = ReactDOMClient.createRoot(container);
232
- await expect(async () => {
233
- await act(() => {
234
- root.render(<Comp />);
235
- });
236
- }).toErrorDev(
230
+ await act(() => {
231
+ root.render(<Comp />);
232
+ });
233
+ assertConsoleErrorDev([
234
'`NaN` is an invalid value for the `fontSize` css style property.' +
235
'\n in div (at **)' +
236
'\n in Comp (at **)',
240
- );
237
+ ]);
238
});
239
240
it('should not warn when setting CSS custom properties', async () => {
@@ -265,15 +262,14 @@ describe('CSSPropertyOperations', () => {
262
263
const container = document.createElement('div');
264
const root = ReactDOMClient.createRoot(container);
268
- await expect(async () => {
269
- await act(() => {
270
- root.render(<Comp />);
271
- });
272
- }).toErrorDev(
265
+ await act(() => {
266
+ root.render(<Comp />);
267
+ });
268
+ assertConsoleErrorDev([
269
'`Infinity` is an invalid value for the `fontSize` css style property.' +
270
'\n in div (at **)' +
271
'\n in Comp (at **)',
276
- );
272
+ ]);
273
});
274
275
it('should not add units to CSS custom properties', async () => {
packages/react-dom/src/__tests__/DOMPropertyOperations-test.js
+13
-8
@@ -1333,7 +1333,8 @@ describe('DOMPropertyOperations', () => {
1333
});
1334
1335
assertConsoleErrorDev([
1336
- 'The `popoverTarget` prop expects the ID of an Element as a string. Received HTMLDivElement {} instead.',
1336
+ 'The `popoverTarget` prop expects the ID of an Element as a string. Received HTMLDivElement {} instead.\n' +
1337
+ ' in button (at **)',
1338
]);
1339
1340
// Dedupe warning
@@ -1375,13 +1376,17 @@ describe('DOMPropertyOperations', () => {
1376
expect(container.firstChild.getAttribute('value')).toBe('foo');
1377
}
1378
expect(container.firstChild.value).toBe('foo');
1378
- await expect(async () => {
1379
- await act(() => {
1380
- root.render(<input type="text" onChange={function () {}} />);
1381
- });
1382
- }).toErrorDev(
1383
- 'A component is changing a controlled input to be uncontrolled',
1384
- );
1379
+ await act(() => {
1380
+ root.render(<input type="text" onChange={function () {}} />);
1381
+ });
1382
+ assertConsoleErrorDev([
1383
+ 'A component is changing a controlled input to be uncontrolled. ' +
1384
+ 'This is likely caused by the value changing from a defined to undefined, ' +
1385
+ 'which should not happen. Decide between using a controlled or uncontrolled ' +
1386
+ 'input element for the lifetime of the component. ' +
1387
+ 'More info: https://react.dev/link/controlled-components\n' +
1388
+ ' in input (at **)',
1389
+ ]);
1390
if (disableInputAttributeSyncing) {
1391
expect(container.firstChild.hasAttribute('value')).toBe(false);
1392
} else {
packages/react-dom/src/__tests__/InvalidEventListeners-test.js
+9
-8
@@ -15,13 +15,14 @@ describe('InvalidEventListeners', () => {
15
let React;
16
let ReactDOMClient;
17
let act;
18
+ let assertConsoleErrorDev;
19
let container;
20
21
beforeEach(() => {
22
jest.resetModules();
23
React = require('react');
24
ReactDOMClient = require('react-dom/client');
24
- act = require('internal-test-utils').act;
25
+ ({act, assertConsoleErrorDev} = require('internal-test-utils'));
26
27
container = document.createElement('div');
28
document.body.appendChild(container);
@@ -34,13 +35,13 @@ describe('InvalidEventListeners', () => {
35
36
it('should prevent non-function listeners, at dispatch', async () => {
37
const root = ReactDOMClient.createRoot(container);
37
- await expect(async () => {
38
- await act(() => {
39
- root.render(<div onClick="not a function" />);
40
- });
41
- }).toErrorDev(
42
- 'Expected `onClick` listener to be a function, instead got a value of `string` type.',
43
- );
38
+ await act(() => {
39
+ root.render(<div onClick="not a function" />);
40
+ });
41
+ assertConsoleErrorDev([
42
+ 'Expected `onClick` listener to be a function, instead got a value of `string` type.\n' +
43
+ ' in div (at **)',
44
+ ]);
45
const node = container.firstChild;
46
47
console.error = jest.fn();
packages/react-dom/src/__tests__/ReactChildReconciler-test.js
+50
-40
@@ -15,6 +15,7 @@
15
let React;
16
let ReactDOMClient;
17
let act;
18
+let assertConsoleErrorDev;
19
20
describe('ReactChildReconciler', () => {
21
beforeEach(() => {
@@ -22,7 +23,7 @@ describe('ReactChildReconciler', () => {
23
24
React = require('react');
25
ReactDOMClient = require('react-dom/client');
25
- act = require('internal-test-utils').act;
26
+ ({act, assertConsoleErrorDev} = require('internal-test-utils'));
27
});
28
29
function createIterable(array) {
@@ -62,15 +63,21 @@ describe('ReactChildReconciler', () => {
63
64
const container = document.createElement('div');
65
const root = ReactDOMClient.createRoot(container);
65
- await expect(async () => {
66
- await act(() => {
67
- root.render(
68
- <div>
69
- <h1>{iterableFunction}</h1>
70
- </div>,
71
- );
72
- });
73
- }).toErrorDev('Functions are not valid as a React child');
66
+ await act(() => {
67
+ root.render(
68
+ <div>
69
+ <h1>{iterableFunction}</h1>
70
+ </div>,
71
+ );
72
+ });
73
+ assertConsoleErrorDev([
74
+ 'Functions are not valid as a React child. ' +
75
+ 'This may happen if you return fn instead of <fn /> from render. ' +
76
+ 'Or maybe you meant to call this function rather than return it.\n' +
77
+ ' <h1>{fn}</h1>\n' +
78
+ ' in h1 (at **)' +
79
+ (gate('enableOwnerStacks') ? '' : '\n in div (at **)'),
80
+ ]);
81
const node = container.firstChild;
82
83
expect(node.innerHTML).toContain(''); // h1
@@ -85,16 +92,18 @@ describe('ReactChildReconciler', () => {
92
93
const container = document.createElement('div');
94
const root = ReactDOMClient.createRoot(container);
88
- await expect(async () => {
89
- await act(() => {
90
- root.render(<Component />);
91
- });
92
- }).toErrorDev(
93
- 'Keys should be unique so that components maintain their identity ' +
94
- 'across updates. Non-unique keys may cause children to be ' +
95
- 'duplicated and/or omitted — the behavior is unsupported and ' +
96
- 'could change in a future version.',
97
- );
95
+ await act(() => {
96
+ root.render(<Component />);
97
+ });
98
+ assertConsoleErrorDev([
99
+ 'Encountered two children with the same key, `1`. ' +
100
+ 'Keys should be unique so that components maintain their identity across updates. ' +
101
+ 'Non-unique keys may cause children to be duplicated and/or omitted — ' +
102
+ 'the behavior is unsupported and could change in a future version.\n' +
103
+ (gate('enableOwnerStacks') ? '' : ' in div (at **)\n') +
104
+ ' in div (at **)\n' +
105
+ ' in Component (at **)',
106
+ ]);
107
});
108
109
it('warns for duplicated array keys with component stack info', async () => {
@@ -118,11 +127,10 @@ describe('ReactChildReconciler', () => {
127
128
const container = document.createElement('div');
129
const root = ReactDOMClient.createRoot(container);
121
- await expect(async () => {
122
- await act(() => {
123
- root.render(<GrandParent />);
124
- });
125
- }).toErrorDev(
130
+ await act(() => {
131
+ root.render(<GrandParent />);
132
+ });
133
+ assertConsoleErrorDev([
134
'Encountered two children with the same key, `1`. ' +
135
'Keys should be unique so that components maintain their identity ' +
136
'across updates. Non-unique keys may cause children to be ' +
@@ -135,7 +143,7 @@ describe('ReactChildReconciler', () => {
143
? ''
144
: ' in Parent (at **)\n') +
145
' in GrandParent (at **)',
138
- );
146
+ ]);
147
});
148
149
it('warns for duplicated iterable keys', async () => {
@@ -147,16 +155,19 @@ describe('ReactChildReconciler', () => {
155
156
const container = document.createElement('div');
157
const root = ReactDOMClient.createRoot(container);
150
- await expect(async () => {
151
- await act(() => {
152
- root.render(<Component />);
153
- });
154
- }).toErrorDev(
155
- 'Keys should be unique so that components maintain their identity ' +
158
+ await act(() => {
159
+ root.render(<Component />);
160
+ });
161
+ assertConsoleErrorDev([
162
+ 'Encountered two children with the same key, `1`. ' +
163
+ 'Keys should be unique so that components maintain their identity ' +
164
'across updates. Non-unique keys may cause children to be ' +
165
'duplicated and/or omitted — the behavior is unsupported and ' +
158
- 'could change in a future version.',
159
- );
166
+ 'could change in a future version.\n' +
167
+ ' in div (at **)\n' +
168
+ (gate(flags => flags.enableOwnerStacks) ? '' : ' in div (at **)\n') +
169
+ ' in Component (at **)',
170
+ ]);
171
});
172
173
it('warns for duplicated iterable keys with component stack info', async () => {
@@ -180,11 +191,10 @@ describe('ReactChildReconciler', () => {
191
192
const container = document.createElement('div');
193
const root = ReactDOMClient.createRoot(container);
183
- await expect(async () => {
184
- await act(() => {
185
- root.render(<GrandParent />);
186
- });
187
- }).toErrorDev(
194
+ await act(() => {
195
+ root.render(<GrandParent />);
196
+ });
197
+ assertConsoleErrorDev([
198
'Encountered two children with the same key, `1`. ' +
199
'Keys should be unique so that components maintain their identity ' +
200
'across updates. Non-unique keys may cause children to be ' +
@@ -197,6 +207,6 @@ describe('ReactChildReconciler', () => {
207
? ''
208
: ' in Parent (at **)\n') +
209
' in GrandParent (at **)',
200
- );
210
+ ]);
211
});
212
});
packages/react-dom/src/__tests__/ReactComponent-test.js
+48
-35
@@ -411,7 +411,9 @@ describe('ReactComponent', () => {
411
assertConsoleErrorDev(
412
[
413
'React.jsx: type is invalid -- expected a string (for built-in components) ' +
414
- 'or a class/function (for composite components) but got: undefined.',
414
+ 'or a class/function (for composite components) but got: undefined. ' +
415
+ "You likely forgot to export your component from the file it's defined in, " +
416
+ 'or you might have mixed up default and named imports.',
417
],
418
{withoutStack: true},
419
);
@@ -491,16 +493,11 @@ describe('ReactComponent', () => {
493
494
const container = document.createElement('div');
495
const root = ReactDOMClient.createRoot(container);
494
- await expect(
495
- expect(async () => {
496
- await act(() => {
497
- root.render(<Foo />);
498
- });
499
- }).toErrorDev(
500
- 'React.jsx: type is invalid -- expected a string (for built-in components) ' +
501
- 'or a class/function (for composite components) but got: undefined.',
502
- ),
503
- ).rejects.toThrowError(
496
+ await expect(async () => {
497
+ await act(() => {
498
+ root.render(<Foo />);
499
+ });
500
+ }).rejects.toThrowError(
501
'Element type is invalid: expected a string (for built-in components) ' +
502
'or a class/function (for composite components) but got: undefined.' +
503
(__DEV__
@@ -509,6 +506,26 @@ describe('ReactComponent', () => {
506
'\n\nCheck the render method of `Bar`.'
507
: ''),
508
);
509
+ if (!gate('enableOwnerStacks')) {
510
+ assertConsoleErrorDev([
511
+ 'React.jsx: type is invalid -- expected a string (for built-in components) ' +
512
+ 'or a class/function (for composite components) but got: undefined.' +
513
+ (__DEV__
514
+ ? " You likely forgot to export your component from the file it's defined in, " +
515
+ 'or you might have mixed up default and named imports.\n' +
516
+ ' in Bar (at **)\n' +
517
+ ' in Foo (at **)'
518
+ : ''),
519
+ 'React.jsx: type is invalid -- expected a string (for built-in components) ' +
520
+ 'or a class/function (for composite components) but got: undefined.' +
521
+ (__DEV__
522
+ ? " You likely forgot to export your component from the file it's defined in, " +
523
+ 'or you might have mixed up default and named imports.\n' +
524
+ ' in Bar (at **)\n' +
525
+ ' in Foo (at **)'
526
+ : ''),
527
+ ]);
528
+ }
529
});
530
531
it('throws if a plain object is used as a child', async () => {
@@ -624,17 +641,16 @@ describe('ReactComponent', () => {
641
}
642
const container = document.createElement('div');
643
const root = ReactDOMClient.createRoot(container);
627
- await expect(async () => {
628
- await act(() => {
629
- root.render(<Foo />);
630
- });
631
- }).toErrorDev(
644
+ await act(() => {
645
+ root.render(<Foo />);
646
+ });
647
+ assertConsoleErrorDev([
648
'Functions are not valid as a React child. This may happen if ' +
649
'you return Foo instead of <Foo /> from render. ' +
650
'Or maybe you meant to call this function rather than return it.\n' +
651
' <Foo>{Foo}</Foo>\n' +
652
' in Foo (at **)',
637
- );
653
+ ]);
654
});
655
656
it('warns on function as a return value from a class', async () => {
@@ -644,19 +660,18 @@ describe('ReactComponent', () => {
660
}
661
}
662
const container = document.createElement('div');
647
- await expect(async () => {
648
- const root = ReactDOMClient.createRoot(container);
663
+ const root = ReactDOMClient.createRoot(container);
664
650
- await act(() => {
651
- root.render(<Foo />);
652
- });
653
- }).toErrorDev(
665
+ await act(() => {
666
+ root.render(<Foo />);
667
+ });
668
+ assertConsoleErrorDev([
669
'Functions are not valid as a React child. This may happen if ' +
670
'you return Foo instead of <Foo /> from render. ' +
671
'Or maybe you meant to call this function rather than return it.\n' +
672
' <Foo>{Foo}</Foo>\n' +
673
' in Foo (at **)',
659
- );
674
+ ]);
675
});
676
677
it('warns on function as a child to host component', async () => {
@@ -669,11 +684,10 @@ describe('ReactComponent', () => {
684
}
685
const container = document.createElement('div');
686
const root = ReactDOMClient.createRoot(container);
672
- await expect(async () => {
673
- await act(() => {
674
- root.render(<Foo />);
675
- });
676
- }).toErrorDev(
687
+ await act(() => {
688
+ root.render(<Foo />);
689
+ });
690
+ assertConsoleErrorDev([
691
'Functions are not valid as a React child. This may happen if ' +
692
'you return Foo instead of <Foo /> from render. ' +
693
'Or maybe you meant to call this function rather than return it.\n' +
@@ -683,7 +697,7 @@ describe('ReactComponent', () => {
697
? ''
698
: ' in div (at **)\n') +
699
' in Foo (at **)',
686
- );
700
+ ]);
701
});
702
703
it('does not warn for function-as-a-child that gets resolved', async () => {
@@ -724,11 +738,10 @@ describe('ReactComponent', () => {
738
const container = document.createElement('div');
739
const root = ReactDOMClient.createRoot(container);
740
let component;
727
- await expect(async () => {
728
- await act(() => {
729
- root.render(<Foo ref={current => (component = current)} />);
730
- });
731
- }).toErrorDev([
741
+ await act(() => {
742
+ root.render(<Foo ref={current => (component = current)} />);
743
+ });
744
+ assertConsoleErrorDev([
745
'Functions are not valid as a React child. This may happen if ' +
746
'you return Foo instead of <Foo /> from render. ' +
747
'Or maybe you meant to call this function rather than return it.\n' +
packages/react-dom/src/__tests__/ReactComponentLifeCycle-test.js
+386
-219
@@ -14,6 +14,8 @@ let act;
14
let React;
15
let ReactDOM;
16
let ReactDOMClient;
17
+let assertConsoleErrorDev;
18
+let assertConsoleWarnDev;
19
20
const clone = function (o) {
21
return JSON.parse(JSON.stringify(o));
@@ -90,7 +92,11 @@ describe('ReactComponentLifeCycle', () => {
92
beforeEach(() => {
93
jest.resetModules();
94
93
- act = require('internal-test-utils').act;
95
+ ({
96
+ act,
97
+ assertConsoleErrorDev,
98
+ assertConsoleWarnDev,
99
+ } = require('internal-test-utils'));
100
101
React = require('react');
102
ReactDOM = require('react-dom');
@@ -239,15 +245,15 @@ describe('ReactComponentLifeCycle', () => {
245
246
const container = document.createElement('div');
247
const root = ReactDOMClient.createRoot(container);
242
- await expect(async () => {
243
- await act(() => {
244
- root.render(<StatefulComponent />);
245
- });
246
- }).toErrorDev(
248
+ await act(() => {
249
+ root.render(<StatefulComponent />);
250
+ });
251
+ assertConsoleErrorDev([
252
'StatefulComponent: It is not recommended to assign props directly to state ' +
253
"because updates to props won't be reflected in state. " +
249
- 'In most cases, it is better to use props directly.',
250
- );
254
+ 'In most cases, it is better to use props directly.\n' +
255
+ ' in StatefulComponent (at **)',
256
+ ]);
257
});
258
259
it('should not allow update state inside of getInitialState', async () => {
@@ -266,16 +272,16 @@ describe('ReactComponentLifeCycle', () => {
272
273
let container = document.createElement('div');
274
let root = ReactDOMClient.createRoot(container);
269
- await expect(async () => {
270
- await act(() => {
271
- root.render(<StatefulComponent />);
272
- });
273
- }).toErrorDev(
275
+ await act(() => {
276
+ root.render(<StatefulComponent />);
277
+ });
278
+ assertConsoleErrorDev([
279
"Can't call setState on a component that is not yet mounted. " +
280
'This is a no-op, but it might indicate a bug in your application. ' +
281
'Instead, assign to `this.state` directly or define a `state = {};` ' +
277
- 'class property with the desired state in the StatefulComponent component.',
278
- );
282
+ 'class property with the desired state in the StatefulComponent component.\n' +
283
+ ' in StatefulComponent (at **)',
284
+ ]);
285
286
container = document.createElement('div');
287
root = ReactDOMClient.createRoot(container);
@@ -308,11 +314,17 @@ describe('ReactComponentLifeCycle', () => {
314
315
const container = document.createElement('div');
316
const root = ReactDOMClient.createRoot(container);
311
- await expect(async () => {
312
- await act(() => {
313
- root.render(element);
314
- });
315
- }).toErrorDev('Component is accessing isMounted inside its render()');
317
+ await act(() => {
318
+ root.render(element);
319
+ });
320
+ assertConsoleErrorDev([
321
+ 'Component is accessing isMounted inside its render() function. ' +
322
+ 'render() should be a pure function of props and state. ' +
323
+ 'It should never access something that requires stale data ' +
324
+ 'from the previous render, such as refs. ' +
325
+ 'Move this logic to componentDidMount and componentDidUpdate instead.\n' +
326
+ ' in Component (at **)',
327
+ ]);
328
expect(instance._isMounted()).toBeTruthy();
329
});
330
@@ -340,11 +352,17 @@ describe('ReactComponentLifeCycle', () => {
352
353
const container = document.createElement('div');
354
const root = ReactDOMClient.createRoot(container);
343
- await expect(async () => {
344
- await act(() => {
345
- root.render(element);
346
- });
347
- }).toErrorDev('Component is accessing isMounted inside its render()');
355
+ await act(() => {
356
+ root.render(element);
357
+ });
358
+ assertConsoleErrorDev([
359
+ 'Component is accessing isMounted inside its render() function. ' +
360
+ 'render() should be a pure function of props and state. ' +
361
+ 'It should never access something that requires stale data ' +
362
+ 'from the previous render, such as refs. ' +
363
+ 'Move this logic to componentDidMount and componentDidUpdate instead.\n' +
364
+ ' in Component (at **)',
365
+ ]);
366
expect(instance._isMounted()).toBeTruthy();
367
});
368
@@ -390,11 +408,17 @@ describe('ReactComponentLifeCycle', () => {
408
409
const container = document.createElement('div');
410
const root = ReactDOMClient.createRoot(container);
393
- await expect(async () => {
394
- await act(() => {
395
- root.render(<Component />);
396
- });
397
- }).toErrorDev('Component is accessing findDOMNode inside its render()');
411
+ await act(() => {
412
+ root.render(<Component />);
413
+ });
414
+ assertConsoleErrorDev([
415
+ 'Component is accessing findDOMNode inside its render(). ' +
416
+ 'render() should be a pure function of props and state. ' +
417
+ 'It should never access something that requires stale data ' +
418
+ 'from the previous render, such as refs. ' +
419
+ 'Move this logic to componentDidMount and componentDidUpdate instead.\n' +
420
+ ' in Component (at **)',
421
+ ]);
422
});
423
424
it('should carry through each of the phases of setup', async () => {
@@ -453,13 +477,17 @@ describe('ReactComponentLifeCycle', () => {
477
const root = ReactDOMClient.createRoot(document.createElement('div'));
478
479
const instanceRef = React.createRef();
456
- await expect(async () => {
457
- await act(() => {
458
- root.render(<LifeCycleComponent ref={instanceRef} />);
459
- });
460
- }).toErrorDev(
461
- 'LifeCycleComponent is accessing isMounted inside its render() function',
462
- );
480
+ await act(() => {
481
+ root.render(<LifeCycleComponent ref={instanceRef} />);
482
+ });
483
+ assertConsoleErrorDev([
484
+ 'LifeCycleComponent is accessing isMounted inside its render() function. ' +
485
+ 'render() should be a pure function of props and state. ' +
486
+ 'It should never access something that requires stale data ' +
487
+ 'from the previous render, such as refs. ' +
488
+ 'Move this logic to componentDidMount and componentDidUpdate instead.\n' +
489
+ ' in LifeCycleComponent (at **)',
490
+ ]);
491
const instance = instanceRef.current;
492
493
// getInitialState
@@ -781,19 +809,45 @@ describe('ReactComponentLifeCycle', () => {
809
}
810
811
const root = ReactDOMClient.createRoot(document.createElement('div'));
784
- await expect(async () => {
785
- await expect(async () => {
786
- await act(() => {
787
- root.render(<Component />);
788
- });
789
- }).toErrorDev(
790
- 'Unsafe legacy lifecycles will not be called for components using new component APIs.',
791
- );
792
- }).toWarnDev(
812
+ await act(() => {
813
+ root.render(<Component />);
814
+ });
815
+ assertConsoleErrorDev([
816
+ 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
817
+ 'Component uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
818
+ ' componentWillMount\n' +
819
+ ' componentWillReceiveProps\n' +
820
+ ' componentWillUpdate\n\n' +
821
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
822
+ 'https://react.dev/link/unsafe-component-lifecycles\n' +
823
+ ' in Component (at **)',
824
+ ]);
825
+ assertConsoleWarnDev(
826
[
794
- 'componentWillMount has been renamed',
795
- 'componentWillReceiveProps has been renamed',
796
- 'componentWillUpdate has been renamed',
827
+ 'componentWillMount has been renamed, and is not recommended for use. ' +
828
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
829
+ '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
830
+ '* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. ' +
831
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
832
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
833
+ 'Please update the following components: Component',
834
+ 'componentWillReceiveProps has been renamed, and is not recommended for use. ' +
835
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
836
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
837
+ "* If you're updating state whenever props change, refactor your code to use " +
838
+ 'memoization techniques or move it to static getDerivedStateFromProps. ' +
839
+ 'Learn more at: https://react.dev/link/derived-state\n' +
840
+ '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. ' +
841
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
842
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
843
+ 'Please update the following components: Component',
844
+ 'componentWillUpdate has been renamed, and is not recommended for use. ' +
845
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
846
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
847
+ '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. ' +
848
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
849
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
850
+ 'Please update the following components: Component',
851
],
852
{withoutStack: true},
853
);
@@ -821,20 +875,45 @@ describe('ReactComponentLifeCycle', () => {
875
}
876
877
const root = ReactDOMClient.createRoot(document.createElement('div'));
824
- await expect(async () => {
825
- await expect(
826
- async () =>
827
- await act(() => {
828
- root.render(<Component value={1} />);
829
- }),
830
- ).toErrorDev(
831
- 'Unsafe legacy lifecycles will not be called for components using new component APIs.',
832
- );
833
- }).toWarnDev(
878
+ await act(() => {
879
+ root.render(<Component value={1} />);
880
+ });
881
+ assertConsoleErrorDev([
882
+ 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
883
+ 'Component uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
884
+ ' componentWillMount\n' +
885
+ ' componentWillReceiveProps\n' +
886
+ ' componentWillUpdate\n\n' +
887
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
888
+ 'https://react.dev/link/unsafe-component-lifecycles\n' +
889
+ ' in Component (at **)',
890
+ ]);
891
+ assertConsoleWarnDev(
892
[
835
- 'componentWillMount has been renamed',
836
- 'componentWillReceiveProps has been renamed',
837
- 'componentWillUpdate has been renamed',
893
+ 'componentWillMount has been renamed, and is not recommended for use. ' +
894
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
895
+ '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
896
+ '* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. ' +
897
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
898
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
899
+ 'Please update the following components: Component',
900
+ 'componentWillReceiveProps has been renamed, and is not recommended for use. ' +
901
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
902
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
903
+ "* If you're updating state whenever props change, refactor your code to use " +
904
+ 'memoization techniques or move it to static getDerivedStateFromProps. ' +
905
+ 'Learn more at: https://react.dev/link/derived-state\n' +
906
+ '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. ' +
907
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
908
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
909
+ 'Please update the following components: Component',
910
+ 'componentWillUpdate has been renamed, and is not recommended for use. ' +
911
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
912
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
913
+ '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. ' +
914
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
915
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
916
+ 'Please update the following components: Component',
917
],
918
{withoutStack: true},
919
);
@@ -865,14 +944,19 @@ describe('ReactComponentLifeCycle', () => {
944
}
945
946
const root = ReactDOMClient.createRoot(document.createElement('div'));
868
- await expect(
869
- async () =>
870
- await act(() => {
871
- root.render(<Component value={1} />);
872
- }),
873
- ).toErrorDev(
874
- 'Unsafe legacy lifecycles will not be called for components using new component APIs.',
875
- );
947
+ await act(() => {
948
+ root.render(<Component value={1} />);
949
+ });
950
+ assertConsoleErrorDev([
951
+ 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
952
+ 'Component uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
953
+ ' UNSAFE_componentWillMount\n' +
954
+ ' UNSAFE_componentWillReceiveProps\n' +
955
+ ' UNSAFE_componentWillUpdate\n\n' +
956
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
957
+ 'https://react.dev/link/unsafe-component-lifecycles\n' +
958
+ ' in Component (at **)',
959
+ ]);
960
await act(() => {
961
root.render(<Component value={2} />);
962
});
@@ -893,24 +977,35 @@ describe('ReactComponentLifeCycle', () => {
977
}
978
979
const root = ReactDOMClient.createRoot(document.createElement('div'));
896
- await expect(async () => {
897
- await expect(async () => {
898
- await act(() => {
899
- root.render(<AllLegacyLifecycles />);
900
- });
901
- }).toErrorDev(
902
- 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
903
- 'AllLegacyLifecycles uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
904
- ' componentWillMount\n' +
905
- ' UNSAFE_componentWillReceiveProps\n' +
906
- ' componentWillUpdate\n\n' +
907
- 'The above lifecycles should be removed. Learn more about this warning here:\n' +
908
- 'https://react.dev/link/unsafe-component-lifecycles',
909
- );
910
- }).toWarnDev(
980
+ await act(() => {
981
+ root.render(<AllLegacyLifecycles />);
982
+ });
983
+ assertConsoleErrorDev([
984
+ 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
985
+ 'AllLegacyLifecycles uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
986
+ ' componentWillMount\n' +
987
+ ' UNSAFE_componentWillReceiveProps\n' +
988
+ ' componentWillUpdate\n\n' +
989
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
990
+ 'https://react.dev/link/unsafe-component-lifecycles\n' +
991
+ ' in AllLegacyLifecycles (at **)',
992
+ ]);
993
+ assertConsoleWarnDev(
994
[
912
- 'componentWillMount has been renamed',
913
- 'componentWillUpdate has been renamed',
995
+ 'componentWillMount has been renamed, and is not recommended for use. ' +
996
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
997
+ '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
998
+ '* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. ' +
999
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
1000
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
1001
+ 'Please update the following components: AllLegacyLifecycles',
1002
+ 'componentWillUpdate has been renamed, and is not recommended for use. ' +
1003
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
1004
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
1005
+ '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. ' +
1006
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
1007
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
1008
+ 'Please update the following components: AllLegacyLifecycles',
1009
],
1010
{withoutStack: true},
1011
);
@@ -926,17 +1021,17 @@ describe('ReactComponentLifeCycle', () => {
1021
}
1022
}
1023
929
- await expect(async () => {
930
- await act(() => {
931
- root.render(<WillMount />);
932
- });
933
- }).toErrorDev(
1024
+ await act(() => {
1025
+ root.render(<WillMount />);
1026
+ });
1027
+ assertConsoleErrorDev([
1028
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
1029
'WillMount uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
1030
' UNSAFE_componentWillMount\n\n' +
1031
'The above lifecycles should be removed. Learn more about this warning here:\n' +
938
- 'https://react.dev/link/unsafe-component-lifecycles',
939
- );
1032
+ 'https://react.dev/link/unsafe-component-lifecycles\n' +
1033
+ ' in WillMount (at **)',
1034
+ ]);
1035
1036
class WillMountAndUpdate extends React.Component {
1037
state = {};
@@ -950,23 +1045,30 @@ describe('ReactComponentLifeCycle', () => {
1045
}
1046
}
1047
953
- await expect(async () => {
954
- await expect(
955
- async () =>
956
- await act(() => {
957
- root.render(<WillMountAndUpdate />);
958
- }),
959
- ).toErrorDev(
960
- 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
961
- 'WillMountAndUpdate uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
962
- ' componentWillMount\n' +
963
- ' UNSAFE_componentWillUpdate\n\n' +
964
- 'The above lifecycles should be removed. Learn more about this warning here:\n' +
965
- 'https://react.dev/link/unsafe-component-lifecycles',
966
- );
967
- }).toWarnDev(['componentWillMount has been renamed'], {
968
- withoutStack: true,
1048
+ await act(() => {
1049
+ root.render(<WillMountAndUpdate />);
1050
});
1051
+ assertConsoleErrorDev([
1052
+ 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
1053
+ 'WillMountAndUpdate uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
1054
+ ' componentWillMount\n' +
1055
+ ' UNSAFE_componentWillUpdate\n\n' +
1056
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
1057
+ 'https://react.dev/link/unsafe-component-lifecycles\n' +
1058
+ ' in WillMountAndUpdate (at **)',
1059
+ ]);
1060
+ assertConsoleWarnDev(
1061
+ [
1062
+ 'componentWillMount has been renamed, and is not recommended for use. ' +
1063
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
1064
+ '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
1065
+ '* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. ' +
1066
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
1067
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
1068
+ 'Please update the following components: WillMountAndUpdate',
1069
+ ],
1070
+ {withoutStack: true},
1071
+ );
1072
1073
class WillReceiveProps extends React.Component {
1074
state = {};
@@ -979,21 +1081,34 @@ describe('ReactComponentLifeCycle', () => {
1081
}
1082
}
1083
982
- await expect(async () => {
983
- await expect(async () => {
984
- await act(() => {
985
- root.render(<WillReceiveProps />);
986
- });
987
- }).toErrorDev(
988
- 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
989
- 'WillReceiveProps uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
990
- ' componentWillReceiveProps\n\n' +
991
- 'The above lifecycles should be removed. Learn more about this warning here:\n' +
992
- 'https://react.dev/link/unsafe-component-lifecycles',
993
- );
994
- }).toWarnDev(['componentWillReceiveProps has been renamed'], {
995
- withoutStack: true,
1084
+ await act(() => {
1085
+ root.render(<WillReceiveProps />);
1086
});
1087
+ assertConsoleErrorDev([
1088
+ 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
1089
+ 'WillReceiveProps uses getDerivedStateFromProps() but also contains the following legacy lifecycles:\n' +
1090
+ ' componentWillReceiveProps\n\n' +
1091
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
1092
+ 'https://react.dev/link/unsafe-component-lifecycles\n' +
1093
+ ' in WillReceiveProps (at **)',
1094
+ ]);
1095
+ assertConsoleWarnDev(
1096
+ [
1097
+ 'componentWillReceiveProps has been renamed, and is not recommended for use. ' +
1098
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
1099
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
1100
+ "* If you're updating state whenever props change, refactor your code to use " +
1101
+ 'memoization techniques or move it to static getDerivedStateFromProps. ' +
1102
+ 'Learn more at: https://react.dev/link/derived-state\n' +
1103
+ '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. ' +
1104
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
1105
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
1106
+ 'Please update the following components: WillReceiveProps',
1107
+ ],
1108
+ {
1109
+ withoutStack: true,
1110
+ },
1111
+ );
1112
});
1113
1114
it('should warn about deprecated lifecycles (cWM/cWRP/cWU) if new getSnapshotBeforeUpdate is present', async () => {
@@ -1010,24 +1125,35 @@ describe('ReactComponentLifeCycle', () => {
1125
}
1126
1127
const root = ReactDOMClient.createRoot(document.createElement('div'));
1013
- await expect(async () => {
1014
- await expect(async () => {
1015
- await act(() => {
1016
- root.render(<AllLegacyLifecycles />);
1017
- });
1018
- }).toErrorDev(
1019
- 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
1020
- 'AllLegacyLifecycles uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
1021
- ' componentWillMount\n' +
1022
- ' UNSAFE_componentWillReceiveProps\n' +
1023
- ' componentWillUpdate\n\n' +
1024
- 'The above lifecycles should be removed. Learn more about this warning here:\n' +
1025
- 'https://react.dev/link/unsafe-component-lifecycles',
1026
- );
1027
- }).toWarnDev(
1128
+ await act(() => {
1129
+ root.render(<AllLegacyLifecycles />);
1130
+ });
1131
+ assertConsoleErrorDev([
1132
+ 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
1133
+ 'AllLegacyLifecycles uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
1134
+ ' componentWillMount\n' +
1135
+ ' UNSAFE_componentWillReceiveProps\n' +
1136
+ ' componentWillUpdate\n\n' +
1137
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
1138
+ 'https://react.dev/link/unsafe-component-lifecycles\n' +
1139
+ ' in AllLegacyLifecycles (at **)',
1140
+ ]);
1141
+ assertConsoleWarnDev(
1142
[
1029
- 'componentWillMount has been renamed',
1030
- 'componentWillUpdate has been renamed',
1143
+ 'componentWillMount has been renamed, and is not recommended for use. ' +
1144
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
1145
+ '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
1146
+ '* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. ' +
1147
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
1148
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
1149
+ 'Please update the following components: AllLegacyLifecycles',
1150
+ 'componentWillUpdate has been renamed, and is not recommended for use. ' +
1151
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
1152
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
1153
+ '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. ' +
1154
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
1155
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
1156
+ 'Please update the following components: AllLegacyLifecycles',
1157
],
1158
{withoutStack: true},
1159
);
@@ -1042,17 +1168,17 @@ describe('ReactComponentLifeCycle', () => {
1168
}
1169
}
1170
1045
- await expect(async () => {
1046
- await act(() => {
1047
- root.render(<WillMount />);
1048
- });
1049
- }).toErrorDev(
1171
+ await act(() => {
1172
+ root.render(<WillMount />);
1173
+ });
1174
+ assertConsoleErrorDev([
1175
'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
1176
'WillMount uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
1177
' UNSAFE_componentWillMount\n\n' +
1178
'The above lifecycles should be removed. Learn more about this warning here:\n' +
1054
- 'https://react.dev/link/unsafe-component-lifecycles',
1055
- );
1179
+ 'https://react.dev/link/unsafe-component-lifecycles\n' +
1180
+ ' in WillMount (at **)',
1181
+ ]);
1182
1183
class WillMountAndUpdate extends React.Component {
1184
state = {};
@@ -1065,22 +1191,32 @@ describe('ReactComponentLifeCycle', () => {
1191
}
1192
}
1193
1068
- await expect(async () => {
1069
- await expect(async () => {
1070
- await act(() => {
1071
- root.render(<WillMountAndUpdate />);
1072
- });
1073
- }).toErrorDev(
1074
- 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
1075
- 'WillMountAndUpdate uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
1076
- ' componentWillMount\n' +
1077
- ' UNSAFE_componentWillUpdate\n\n' +
1078
- 'The above lifecycles should be removed. Learn more about this warning here:\n' +
1079
- 'https://react.dev/link/unsafe-component-lifecycles',
1080
- );
1081
- }).toWarnDev(['componentWillMount has been renamed'], {
1082
- withoutStack: true,
1194
+ await act(() => {
1195
+ root.render(<WillMountAndUpdate />);
1196
});
1197
+ assertConsoleErrorDev([
1198
+ 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
1199
+ 'WillMountAndUpdate uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
1200
+ ' componentWillMount\n' +
1201
+ ' UNSAFE_componentWillUpdate\n\n' +
1202
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
1203
+ 'https://react.dev/link/unsafe-component-lifecycles\n' +
1204
+ ' in WillMountAndUpdate (at **)',
1205
+ ]);
1206
+ assertConsoleWarnDev(
1207
+ [
1208
+ 'componentWillMount has been renamed, and is not recommended for use. ' +
1209
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
1210
+ '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
1211
+ '* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. ' +
1212
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
1213
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
1214
+ 'Please update the following components: WillMountAndUpdate',
1215
+ ],
1216
+ {
1217
+ withoutStack: true,
1218
+ },
1219
+ );
1220
1221
class WillReceiveProps extends React.Component {
1222
state = {};
@@ -1092,22 +1228,34 @@ describe('ReactComponentLifeCycle', () => {
1228
}
1229
}
1230
1095
- await expect(async () => {
1096
- await expect(
1097
- async () =>
1098
- await act(() => {
1099
- root.render(<WillReceiveProps />);
1100
- }),
1101
- ).toErrorDev(
1102
- 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
1103
- 'WillReceiveProps uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
1104
- ' componentWillReceiveProps\n\n' +
1105
- 'The above lifecycles should be removed. Learn more about this warning here:\n' +
1106
- 'https://react.dev/link/unsafe-component-lifecycles',
1107
- );
1108
- }).toWarnDev(['componentWillReceiveProps has been renamed'], {
1109
- withoutStack: true,
1231
+ await act(() => {
1232
+ root.render(<WillReceiveProps />);
1233
});
1234
+ assertConsoleErrorDev([
1235
+ 'Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n' +
1236
+ 'WillReceiveProps uses getSnapshotBeforeUpdate() but also contains the following legacy lifecycles:\n' +
1237
+ ' componentWillReceiveProps\n\n' +
1238
+ 'The above lifecycles should be removed. Learn more about this warning here:\n' +
1239
+ 'https://react.dev/link/unsafe-component-lifecycles\n' +
1240
+ ' in WillReceiveProps (at **)',
1241
+ ]);
1242
+ assertConsoleWarnDev(
1243
+ [
1244
+ 'componentWillReceiveProps has been renamed, and is not recommended for use. ' +
1245
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
1246
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
1247
+ "* If you're updating state whenever props change, refactor your code to use " +
1248
+ 'memoization techniques or move it to static getDerivedStateFromProps. ' +
1249
+ 'Learn more at: https://react.dev/link/derived-state\n' +
1250
+ '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. ' +
1251
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
1252
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
1253
+ 'Please update the following components: WillReceiveProps',
1254
+ ],
1255
+ {
1256
+ withoutStack: true,
1257
+ },
1258
+ );
1259
});
1260
1261
it('should warn if getDerivedStateFromProps returns undefined', async () => {
@@ -1120,14 +1268,14 @@ describe('ReactComponentLifeCycle', () => {
1268
}
1269
1270
const root = ReactDOMClient.createRoot(document.createElement('div'));
1123
- await expect(async () => {
1124
- await act(() => {
1125
- root.render(<MyComponent />);
1126
- });
1127
- }).toErrorDev(
1271
+ await act(() => {
1272
+ root.render(<MyComponent />);
1273
+ });
1274
+ assertConsoleErrorDev([
1275
'MyComponent.getDerivedStateFromProps(): A valid state object (or null) must ' +
1129
- 'be returned. You have returned undefined.',
1130
- );
1276
+ 'be returned. You have returned undefined.\n' +
1277
+ ' in MyComponent (at **)',
1278
+ ]);
1279
1280
// De-duped
1281
await act(() => {
@@ -1146,16 +1294,16 @@ describe('ReactComponentLifeCycle', () => {
1294
}
1295
1296
const root = ReactDOMClient.createRoot(document.createElement('div'));
1149
- await expect(async () => {
1150
- await act(() => {
1151
- root.render(<MyComponent />);
1152
- });
1153
- }).toErrorDev(
1297
+ await act(() => {
1298
+ root.render(<MyComponent />);
1299
+ });
1300
+ assertConsoleErrorDev([
1301
'`MyComponent` uses `getDerivedStateFromProps` but its initial state is ' +
1302
'undefined. This is not recommended. Instead, define the initial state by ' +
1303
'assigning an object to `this.state` in the constructor of `MyComponent`. ' +
1157
- 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.',
1158
- );
1304
+ 'This ensures that `getDerivedStateFromProps` arguments have a consistent shape.\n' +
1305
+ ' in MyComponent (at **)',
1306
+ ]);
1307
1308
// De-duped
1309
await act(() => {
@@ -1191,15 +1339,35 @@ describe('ReactComponentLifeCycle', () => {
1339
}
1340
1341
const root = ReactDOMClient.createRoot(document.createElement('div'));
1194
- await expect(async () => {
1195
- await act(() => {
1196
- root.render(<MyComponent foo="bar" />);
1197
- });
1198
- }).toWarnDev(
1342
+ await act(() => {
1343
+ root.render(<MyComponent foo="bar" />);
1344
+ });
1345
+ assertConsoleWarnDev(
1346
[
1200
- 'componentWillMount has been renamed',
1201
- 'componentWillReceiveProps has been renamed',
1202
- 'componentWillUpdate has been renamed',
1347
+ 'componentWillMount has been renamed, and is not recommended for use. ' +
1348
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
1349
+ '* Move code with side effects to componentDidMount, and set initial state in the constructor.\n' +
1350
+ '* Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode. ' +
1351
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
1352
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
1353
+ 'Please update the following components: MyComponent',
1354
+ 'componentWillReceiveProps has been renamed, and is not recommended for use. ' +
1355
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
1356
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
1357
+ "* If you're updating state whenever props change, refactor your code to use " +
1358
+ 'memoization techniques or move it to static getDerivedStateFromProps. ' +
1359
+ 'Learn more at: https://react.dev/link/derived-state\n' +
1360
+ '* Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. ' +
1361
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
1362
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
1363
+ 'Please update the following components: MyComponent',
1364
+ 'componentWillUpdate has been renamed, and is not recommended for use. ' +
1365
+ 'See https://react.dev/link/unsafe-component-lifecycles for details.\n\n' +
1366
+ '* Move data fetching code or side effects to componentDidUpdate.\n' +
1367
+ '* Rename componentWillUpdate to UNSAFE_componentWillUpdate to suppress this warning in non-strict mode. ' +
1368
+ 'In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, ' +
1369
+ 'you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.\n\n' +
1370
+ 'Please update the following components: MyComponent',
1371
],
1372
{withoutStack: true},
1373
);
@@ -1444,14 +1612,14 @@ describe('ReactComponentLifeCycle', () => {
1612
root.render(<MyComponent value="foo" />);
1613
});
1614
1447
- await expect(async () => {
1448
- await act(() => {
1449
- root.render(<MyComponent value="bar" />);
1450
- });
1451
- }).toErrorDev(
1615
+ await act(() => {
1616
+ root.render(<MyComponent value="bar" />);
1617
+ });
1618
+ assertConsoleErrorDev([
1619
'MyComponent.getSnapshotBeforeUpdate(): A snapshot value (or null) must ' +
1453
- 'be returned. You have returned undefined.',
1454
- );
1620
+ 'be returned. You have returned undefined.\n' +
1621
+ ' in MyComponent (at **)',
1622
+ ]);
1623
1624
// De-duped
1625
await act(() => {
@@ -1470,14 +1638,14 @@ describe('ReactComponentLifeCycle', () => {
1638
}
1639
1640
const root = ReactDOMClient.createRoot(document.createElement('div'));
1473
- await expect(async () => {
1474
- await act(() => {
1475
- root.render(<MyComponent />);
1476
- });
1477
- }).toErrorDev(
1641
+ await act(() => {
1642
+ root.render(<MyComponent />);
1643
+ });
1644
+ assertConsoleErrorDev([
1645
'MyComponent: getSnapshotBeforeUpdate() should be used with componentDidUpdate(). ' +
1479
- 'This component defines getSnapshotBeforeUpdate() only.',
1480
- );
1646
+ 'This component defines getSnapshotBeforeUpdate() only.\n' +
1647
+ ' in MyComponent (at **)',
1648
+ ]);
1649
1650
// De-duped
1651
await act(() => {
@@ -1497,11 +1665,10 @@ describe('ReactComponentLifeCycle', () => {
1665
1666
const root = ReactDOMClient.createRoot(document.createElement('div'));
1667
1500
- await expect(async () => {
1501
- await act(() => {
1502
- root.render(<MyComponent x={1} />);
1503
- });
1504
- }).toWarnDev(
1668
+ await act(() => {
1669
+ root.render(<MyComponent x={1} />);
1670
+ });
1671
+ assertConsoleWarnDev(
1672
[
1673
`componentWillMount has been renamed, and is not recommended for use. See https://react.dev/link/unsafe-component-lifecycles for details.
1674
packages/react-dom/src/__tests__/ReactCompositeComponent-test.js
+111
-103
@@ -18,6 +18,7 @@ let ReactSharedInternals;
18
let Scheduler;
19
let assertLog;
20
let act;
21
+let assertConsoleErrorDev;
22
23
describe('ReactCompositeComponent', () => {
24
const hasOwnProperty = Object.prototype.hasOwnProperty;
@@ -71,7 +72,7 @@ describe('ReactCompositeComponent', () => {
72
require('react').__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE;
73
Scheduler = require('scheduler');
74
assertLog = require('internal-test-utils').assertLog;
74
- act = require('internal-test-utils').act;
75
+ ({act, assertConsoleErrorDev} = require('internal-test-utils'));
76
});
77
78
describe('MorphingComponent', () => {
@@ -308,16 +309,16 @@ describe('ReactCompositeComponent', () => {
309
310
const container = document.createElement('div');
311
const root = ReactDOMClient.createRoot(container);
311
- expect(() => {
312
- ReactDOM.flushSync(() => {
313
- root.render(<MyComponent />);
314
- });
315
- }).toErrorDev(
312
+ ReactDOM.flushSync(() => {
313
+ root.render(<MyComponent />);
314
+ });
315
+ assertConsoleErrorDev([
316
"Can't call forceUpdate on a component that is not yet mounted. " +
317
'This is a no-op, but it might indicate a bug in your application. ' +
318
'Instead, assign to `this.state` directly or define a `state = {};` ' +
319
- 'class property with the desired state in the MyComponent component.',
320
- );
319
+ 'class property with the desired state in the MyComponent component.\n' +
320
+ ' in MyComponent (at **)',
321
+ ]);
322
323
// No additional warning should be recorded
324
const container2 = document.createElement('div');
@@ -342,16 +343,16 @@ describe('ReactCompositeComponent', () => {
343
const container = document.createElement('div');
344
const root = ReactDOMClient.createRoot(container);
345
345
- expect(() => {
346
- ReactDOM.flushSync(() => {
347
- root.render(<MyComponent />);
348
- });
349
- }).toErrorDev(
346
+ ReactDOM.flushSync(() => {
347
+ root.render(<MyComponent />);
348
+ });
349
+ assertConsoleErrorDev([
350
"Can't call setState on a component that is not yet mounted. " +
351
'This is a no-op, but it might indicate a bug in your application. ' +
352
'Instead, assign to `this.state` directly or define a `state = {};` ' +
353
- 'class property with the desired state in the MyComponent component.',
354
- );
353
+ 'class property with the desired state in the MyComponent component.\n' +
354
+ ' in MyComponent (at **)',
355
+ ]);
356
357
// No additional warning should be recorded
358
const container2 = document.createElement('div');
@@ -478,16 +479,16 @@ describe('ReactCompositeComponent', () => {
479
}
480
const root = ReactDOMClient.createRoot(container);
481
await expect(async () => {
481
- await expect(async () => {
482
- await act(() => {
483
- root.render(<ClassWithRenderNotExtended />);
484
- });
485
- }).rejects.toThrow(TypeError);
486
- }).toErrorDev(
482
+ await act(() => {
483
+ root.render(<ClassWithRenderNotExtended />);
484
+ });
485
+ }).rejects.toThrow(TypeError);
486
+ assertConsoleErrorDev([
487
'The <ClassWithRenderNotExtended /> component appears to have a render method, ' +
488
"but doesn't extend React.Component. This is likely to cause errors. " +
489
- 'Change ClassWithRenderNotExtended to extend React.Component instead.',
490
- );
489
+ 'Change ClassWithRenderNotExtended to extend React.Component instead.\n' +
490
+ ' in ClassWithRenderNotExtended (at **)',
491
+ ]);
492
493
// Test deduplication
494
await expect(async () => {
@@ -514,14 +515,14 @@ describe('ReactCompositeComponent', () => {
515
516
let instance;
517
const root = ReactDOMClient.createRoot(container);
517
- expect(() => {
518
- ReactDOM.flushSync(() => {
519
- root.render(<Component ref={ref => (instance = ref)} />);
520
- });
521
- }).toErrorDev(
518
+ ReactDOM.flushSync(() => {
519
+ root.render(<Component ref={ref => (instance = ref)} />);
520
+ });
521
+ assertConsoleErrorDev([
522
'Cannot update during an existing state transition (such as within ' +
523
- '`render`). Render methods should be a pure function of props and state.',
524
- );
523
+ '`render`). Render methods should be a pure function of props and state.\n' +
524
+ ' in Component (at **)',
525
+ ]);
526
527
// The setState call is queued and then executed as a second pass. This
528
// behavior is undefined though so we're free to change it to suit the
@@ -618,14 +619,14 @@ describe('ReactCompositeComponent', () => {
619
root.render(<ClassComponent ref={ref => (instance = ref)} />);
620
});
621
621
- expect(() => {
622
- ReactDOM.flushSync(() => {
623
- instance.setState({bogus: true});
624
- });
625
- }).toErrorDev(
622
+ ReactDOM.flushSync(() => {
623
+ instance.setState({bogus: true});
624
+ });
625
+ assertConsoleErrorDev([
626
'ClassComponent.shouldComponentUpdate(): Returned undefined instead of a ' +
627
- 'boolean value. Make sure to return true or false.',
628
- );
627
+ 'boolean value. Make sure to return true or false.\n' +
628
+ ' in ClassComponent (at **)',
629
+ ]);
630
});
631
632
it('should warn when componentDidUnmount method is defined', async () => {
@@ -638,15 +639,15 @@ describe('ReactCompositeComponent', () => {
639
}
640
641
const root = ReactDOMClient.createRoot(document.createElement('div'));
641
- expect(() => {
642
- ReactDOM.flushSync(() => {
643
- root.render(<Component />);
644
- });
645
- }).toErrorDev(
642
+ ReactDOM.flushSync(() => {
643
+ root.render(<Component />);
644
+ });
645
+ assertConsoleErrorDev([
646
'Component has a method called ' +
647
'componentDidUnmount(). But there is no such lifecycle method. ' +
648
- 'Did you mean componentWillUnmount()?',
649
- );
648
+ 'Did you mean componentWillUnmount()?\n' +
649
+ ' in Component (at **)',
650
+ ]);
651
});
652
653
it('should warn when componentDidReceiveProps method is defined', () => {
@@ -660,17 +661,17 @@ describe('ReactCompositeComponent', () => {
661
662
const root = ReactDOMClient.createRoot(document.createElement('div'));
663
663
- expect(() => {
664
- ReactDOM.flushSync(() => {
665
- root.render(<Component />);
666
- });
667
- }).toErrorDev(
664
+ ReactDOM.flushSync(() => {
665
+ root.render(<Component />);
666
+ });
667
+ assertConsoleErrorDev([
668
'Component has a method called ' +
669
'componentDidReceiveProps(). But there is no such lifecycle method. ' +
670
'If you meant to update the state in response to changing props, ' +
671
'use componentWillReceiveProps(). If you meant to fetch data or ' +
672
- 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().',
673
- );
672
+ 'run side-effects or mutations after React has updated the UI, use componentDidUpdate().\n' +
673
+ ' in Component (at **)',
674
+ ]);
675
});
676
677
it('should warn when defaultProps was defined as an instance property', () => {
@@ -686,14 +687,14 @@ describe('ReactCompositeComponent', () => {
687
}
688
const root = ReactDOMClient.createRoot(document.createElement('div'));
689
689
- expect(() => {
690
- ReactDOM.flushSync(() => {
691
- root.render(<Component />);
692
- });
693
- }).toErrorDev(
690
+ ReactDOM.flushSync(() => {
691
+ root.render(<Component />);
692
+ });
693
+ assertConsoleErrorDev([
694
'Setting defaultProps as an instance property on Component is not supported ' +
695
- 'and will be ignored. Instead, define defaultProps as a static property on Component.',
696
- );
695
+ 'and will be ignored. Instead, define defaultProps as a static property on Component.\n' +
696
+ ' in Component (at **)',
697
+ ]);
698
});
699
700
it('should skip update when rerendering element in container', async () => {
@@ -739,16 +740,16 @@ describe('ReactCompositeComponent', () => {
740
}
741
}
742
742
- expect(() => {
743
- ReactDOM.flushSync(() => {
744
- root.render(<Outer />);
745
- });
746
- }).toErrorDev(
743
+ ReactDOM.flushSync(() => {
744
+ root.render(<Outer />);
745
+ });
746
+ assertConsoleErrorDev([
747
'Render methods should be a pure function of props and state; ' +
748
'triggering nested component updates from render is not allowed. If ' +
749
- 'necessary, trigger nested updates in componentDidUpdate.\n\nCheck the ' +
750
- 'render method of Outer.',
751
- );
749
+ 'necessary, trigger nested updates in componentDidUpdate.\n\n' +
750
+ 'Check the render method of Outer.\n' +
751
+ ' in Outer (at **)',
752
+ ]);
753
});
754
755
it('only renders once if updated in componentWillReceiveProps', async () => {
@@ -836,14 +837,14 @@ describe('ReactCompositeComponent', () => {
837
}
838
839
const root = ReactDOMClient.createRoot(container);
839
- expect(() => {
840
- ReactDOM.flushSync(() => {
841
- root.render(<Foo idx="qwe" />);
842
- });
843
- }).toErrorDev(
840
+ ReactDOM.flushSync(() => {
841
+ root.render(<Foo idx="qwe" />);
842
+ });
843
+ assertConsoleErrorDev([
844
'When calling super() in `Foo`, make sure to pass ' +
845
- "up the same props that your component's constructor was passed.",
846
- );
845
+ "up the same props that your component's constructor was passed.\n" +
846
+ ' in Foo (at **)',
847
+ ]);
848
});
849
850
it('should only call componentWillUnmount once', async () => {
@@ -1185,16 +1186,17 @@ describe('ReactCompositeComponent', () => {
1186
1187
const root = ReactDOMClient.createRoot(document.createElement('div'));
1188
await expect(async () => {
1188
- await expect(async () => {
1189
- await act(() => {
1190
- root.render(<RenderTextInvalidConstructor />);
1191
- });
1192
- }).rejects.toThrow();
1193
- }).toErrorDev([
1189
+ await act(() => {
1190
+ root.render(<RenderTextInvalidConstructor />);
1191
+ });
1192
+ }).rejects.toThrow();
1193
+ assertConsoleErrorDev([
1194
'No `render` method found on the RenderTextInvalidConstructor instance: ' +
1195
- 'did you accidentally return an object from the constructor?',
1195
+ 'did you accidentally return an object from the constructor?\n' +
1196
+ ' in RenderTextInvalidConstructor (at **)',
1197
'No `render` method found on the RenderTextInvalidConstructor instance: ' +
1197
- 'did you accidentally return an object from the constructor?',
1198
+ 'did you accidentally return an object from the constructor?\n' +
1199
+ ' in RenderTextInvalidConstructor (at **)',
1200
]);
1201
});
1202
@@ -1210,14 +1212,14 @@ describe('ReactCompositeComponent', () => {
1212
1213
const container = document.createElement('div');
1214
const root = ReactDOMClient.createRoot(container);
1213
- expect(() => {
1214
- ReactDOM.flushSync(() => {
1215
- root.render(<Bad />);
1216
- });
1217
- }).toErrorDev(
1215
+ ReactDOM.flushSync(() => {
1216
+ root.render(<Bad />);
1217
+ });
1218
+ assertConsoleErrorDev([
1219
'It looks like Bad is reassigning its own `this.props` while rendering. ' +
1219
- 'This is not supported and can lead to confusing bugs.',
1220
- );
1220
+ 'This is not supported and can lead to confusing bugs.\n' +
1221
+ ' in Bad (at **)',
1222
+ ]);
1223
});
1224
1225
it('should return error if render is not defined', async () => {
@@ -1225,16 +1227,17 @@ describe('ReactCompositeComponent', () => {
1227
1228
const root = ReactDOMClient.createRoot(document.createElement('div'));
1229
await expect(async () => {
1228
- await expect(async () => {
1229
- await act(() => {
1230
- root.render(<RenderTestUndefinedRender />);
1231
- });
1232
- }).rejects.toThrow();
1233
- }).toErrorDev([
1230
+ await act(() => {
1231
+ root.render(<RenderTestUndefinedRender />);
1232
+ });
1233
+ }).rejects.toThrow();
1234
+ assertConsoleErrorDev([
1235
'No `render` method found on the RenderTestUndefinedRender instance: ' +
1235
- 'you may have forgotten to define `render`.',
1236
+ 'you may have forgotten to define `render`.\n' +
1237
+ ' in RenderTestUndefinedRender (at **)',
1238
'No `render` method found on the RenderTestUndefinedRender instance: ' +
1237
- 'you may have forgotten to define `render`.',
1239
+ 'you may have forgotten to define `render`.\n' +
1240
+ ' in RenderTestUndefinedRender (at **)',
1241
]);
1242
});
1243
@@ -1386,13 +1389,18 @@ describe('ReactCompositeComponent', () => {
1389
}
1390
const container = document.createElement('div');
1391
const root = ReactDOMClient.createRoot(container);
1389
- expect(() => {
1390
- ReactDOM.flushSync(() => {
1391
- root.render(<Parent />);
1392
- });
1393
- }).toErrorDev(
1394
- 'Cannot update a component (`A`) while rendering a different component (`B`)',
1395
- );
1392
+ ReactDOM.flushSync(() => {
1393
+ root.render(<Parent />);
1394
+ });
1395
+ assertConsoleErrorDev([
1396
+ 'Cannot update a component (`A`) while rendering a different component (`B`). ' +
1397
+ 'To locate the bad setState() call inside `B`, ' +
1398
+ 'follow the stack trace as described in https://react.dev/link/setstate-in-render\n' +
1399
+ (gate('enableOwnerStacks')
1400
+ ? ''
1401
+ : ' in B (at **)\n' + ' in div (at **)\n') +
1402
+ ' in Parent (at **)',
1403
+ ]);
1404
1405
// We error, but still update the state.
1406
expect(ref.textContent).toBe('1');
packages/react-dom/src/__tests__/ReactCompositeComponentState-test.js
+26
-22
@@ -17,13 +17,14 @@ let Scheduler;
17
let assertLog;
18
let TestComponent;
19
let testComponentInstance;
20
+let assertConsoleErrorDev;
21
22
describe('ReactCompositeComponent-state', () => {
23
beforeEach(() => {
24
React = require('react');
25
ReactDOM = require('react-dom');
26
ReactDOMClient = require('react-dom/client');
26
- act = require('internal-test-utils').act;
27
+ ({act, assertConsoleErrorDev} = require('internal-test-utils'));
28
Scheduler = require('scheduler');
29
30
const InternalTestUtils = require('internal-test-utils');
@@ -469,15 +470,15 @@ describe('ReactCompositeComponent-state', () => {
470
root.render(<Test />);
471
});
472
// Update
472
- expect(() => {
473
- ReactDOM.flushSync(() => {
474
- root.render(<Test />);
475
- });
476
- }).toErrorDev(
473
+ ReactDOM.flushSync(() => {
474
+ root.render(<Test />);
475
+ });
476
+ assertConsoleErrorDev([
477
'Test.componentWillReceiveProps(): Assigning directly to ' +
478
"this.state is deprecated (except inside a component's constructor). " +
479
- 'Use setState instead.',
480
- );
479
+ 'Use setState instead.\n' +
480
+ ' in Test (at **)',
481
+ ]);
482
483
assertLog([
484
'render -- step: 1, extra: true',
@@ -518,15 +519,15 @@ describe('ReactCompositeComponent-state', () => {
519
// Mount
520
const container = document.createElement('div');
521
const root = ReactDOMClient.createRoot(container);
521
- expect(() => {
522
- ReactDOM.flushSync(() => {
523
- root.render(<Test />);
524
- });
525
- }).toErrorDev(
522
+ ReactDOM.flushSync(() => {
523
+ root.render(<Test />);
524
+ });
525
+ assertConsoleErrorDev([
526
'Test.componentWillMount(): Assigning directly to ' +
527
"this.state is deprecated (except inside a component's constructor). " +
528
- 'Use setState instead.',
529
- );
528
+ 'Use setState instead.\n' +
529
+ ' in Test (at **)',
530
+ ]);
531
532
assertLog([
533
'render -- step: 3, extra: false',
@@ -566,13 +567,16 @@ describe('ReactCompositeComponent-state', () => {
567
});
568
expect(el.textContent).toBe('A');
569
569
- expect(() => {
570
- ReactDOM.flushSync(() => {
571
- root.render(<B />);
572
- });
573
- }).toErrorDev(
574
- "Can't perform a React state update on a component that hasn't mounted yet",
575
- );
570
+ ReactDOM.flushSync(() => {
571
+ root.render(<B />);
572
+ });
573
+ assertConsoleErrorDev([
574
+ "Can't perform a React state update on a component that hasn't mounted yet. " +
575
+ 'This indicates that you have a side-effect in your render function that ' +
576
+ 'asynchronously later calls tries to update the component. ' +
577
+ 'Move this work to useEffect instead.\n' +
578
+ ' in B (at **)',
579
+ ]);
580
});
581
582
// @gate !disableLegacyMode
packages/react-dom/src/__tests__/ReactDOM-test.js
+54
-61
@@ -14,7 +14,7 @@ let ReactDOM;
14
let findDOMNode;
15
let ReactDOMClient;
16
let ReactDOMServer;
17
-
17
+let assertConsoleErrorDev;
18
let act;
19
20
describe('ReactDOM', () => {
@@ -28,7 +28,7 @@ describe('ReactDOM', () => {
28
ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
29
.findDOMNode;
30
31
- act = require('internal-test-utils').act;
31
+ ({act, assertConsoleErrorDev} = require('internal-test-utils'));
32
});
33
34
it('should bubble onSubmit', async () => {
@@ -188,15 +188,14 @@ describe('ReactDOM', () => {
188
189
const myDiv = document.createElement('div');
190
await expect(async () => {
191
- await expect(async () => {
192
- await act(() => {
193
- ReactDOM.render(<A />, myDiv, 'no');
194
- });
195
- }).rejects.toThrowError(
196
- 'Invalid argument passed as callback. Expected a function. Instead ' +
197
- 'received: no',
198
- );
199
- }).toErrorDev(
191
+ await act(() => {
192
+ ReactDOM.render(<A />, myDiv, 'no');
193
+ });
194
+ }).rejects.toThrowError(
195
+ 'Invalid argument passed as callback. Expected a function. Instead ' +
196
+ 'received: no',
197
+ );
198
+ assertConsoleErrorDev(
199
[
200
'Expected the last optional `callback` argument to be a function. Instead received: no.',
201
'Expected the last optional `callback` argument to be a function. Instead received: no.',
@@ -205,15 +204,14 @@ describe('ReactDOM', () => {
204
);
205
206
await expect(async () => {
208
- await expect(async () => {
209
- await act(() => {
210
- ReactDOM.render(<A />, myDiv, {foo: 'bar'});
211
- });
212
- }).rejects.toThrowError(
213
- 'Invalid argument passed as callback. Expected a function. Instead ' +
214
- 'received: [object Object]',
215
- );
216
- }).toErrorDev(
207
+ await act(() => {
208
+ ReactDOM.render(<A />, myDiv, {foo: 'bar'});
209
+ });
210
+ }).rejects.toThrowError(
211
+ 'Invalid argument passed as callback. Expected a function. Instead ' +
212
+ 'received: [object Object]',
213
+ );
214
+ assertConsoleErrorDev(
215
[
216
"Expected the last optional `callback` argument to be a function. Instead received: { foo: 'bar' }",
217
"Expected the last optional `callback` argument to be a function. Instead received: { foo: 'bar' }.",
@@ -222,15 +220,14 @@ describe('ReactDOM', () => {
220
);
221
222
await expect(async () => {
225
- await expect(async () => {
226
- await act(() => {
227
- ReactDOM.render(<A />, myDiv, new Foo());
228
- });
229
- }).rejects.toThrowError(
230
- 'Invalid argument passed as callback. Expected a function. Instead ' +
231
- 'received: [object Object]',
232
- );
233
- }).toErrorDev(
223
+ await act(() => {
224
+ ReactDOM.render(<A />, myDiv, new Foo());
225
+ });
226
+ }).rejects.toThrowError(
227
+ 'Invalid argument passed as callback. Expected a function. Instead ' +
228
+ 'received: [object Object]',
229
+ );
230
+ assertConsoleErrorDev(
231
[
232
'Expected the last optional `callback` argument to be a function. Instead received: Foo { a: 1, b: 2 }.',
233
'Expected the last optional `callback` argument to be a function. Instead received: Foo { a: 1, b: 2 }.',
@@ -257,15 +254,14 @@ describe('ReactDOM', () => {
254
const myDiv = document.createElement('div');
255
ReactDOM.render(<A />, myDiv);
256
await expect(async () => {
260
- await expect(async () => {
261
- await act(() => {
262
- ReactDOM.render(<A />, myDiv, 'no');
263
- });
264
- }).rejects.toThrowError(
265
- 'Invalid argument passed as callback. Expected a function. Instead ' +
266
- 'received: no',
267
- );
268
- }).toErrorDev(
257
+ await act(() => {
258
+ ReactDOM.render(<A />, myDiv, 'no');
259
+ });
260
+ }).rejects.toThrowError(
261
+ 'Invalid argument passed as callback. Expected a function. Instead ' +
262
+ 'received: no',
263
+ );
264
+ assertConsoleErrorDev(
265
[
266
'Expected the last optional `callback` argument to be a function. Instead received: no.',
267
'Expected the last optional `callback` argument to be a function. Instead received: no.',
@@ -275,15 +271,14 @@ describe('ReactDOM', () => {
271
272
ReactDOM.render(<A />, myDiv); // Re-mount
273
await expect(async () => {
278
- await expect(async () => {
279
- await act(() => {
280
- ReactDOM.render(<A />, myDiv, {foo: 'bar'});
281
- });
282
- }).rejects.toThrowError(
283
- 'Invalid argument passed as callback. Expected a function. Instead ' +
284
- 'received: [object Object]',
285
- );
286
- }).toErrorDev(
274
+ await act(() => {
275
+ ReactDOM.render(<A />, myDiv, {foo: 'bar'});
276
+ });
277
+ }).rejects.toThrowError(
278
+ 'Invalid argument passed as callback. Expected a function. Instead ' +
279
+ 'received: [object Object]',
280
+ );
281
+ assertConsoleErrorDev(
282
[
283
"Expected the last optional `callback` argument to be a function. Instead received: { foo: 'bar' }.",
284
"Expected the last optional `callback` argument to be a function. Instead received: { foo: 'bar' }.",
@@ -293,15 +288,14 @@ describe('ReactDOM', () => {
288
289
ReactDOM.render(<A />, myDiv); // Re-mount
290
await expect(async () => {
296
- await expect(async () => {
297
- await act(() => {
298
- ReactDOM.render(<A />, myDiv, new Foo());
299
- });
300
- }).rejects.toThrowError(
301
- 'Invalid argument passed as callback. Expected a function. Instead ' +
302
- 'received: [object Object]',
303
- );
304
- }).toErrorDev(
291
+ await act(() => {
292
+ ReactDOM.render(<A />, myDiv, new Foo());
293
+ });
294
+ }).rejects.toThrowError(
295
+ 'Invalid argument passed as callback. Expected a function. Instead ' +
296
+ 'received: [object Object]',
297
+ );
298
+ assertConsoleErrorDev(
299
[
300
'Expected the last optional `callback` argument to be a function. Instead received: Foo { a: 1, b: 2 }.',
301
'Expected the last optional `callback` argument to be a function. Instead received: Foo { a: 1, b: 2 }.',
@@ -544,11 +538,10 @@ describe('ReactDOM', () => {
538
}
539
540
const root = ReactDOMClient.createRoot(document.createElement('div'));
547
- await expect(async () => {
548
- await act(() => {
549
- root.render(<App />);
550
- });
551
- }).toErrorDev([
541
+ await act(() => {
542
+ root.render(<App />);
543
+ });
544
+ assertConsoleErrorDev([
545
// ReactDOM(App > div > span)
546
'Invalid ARIA attribute `ariaTypo`. ARIA attributes follow the pattern aria-* and must be lowercase.\n' +
547
' in span (at **)\n' +
packages/react-dom/src/__tests__/ReactDOMAttribute-test.js
+36
-33
@@ -13,12 +13,15 @@ describe('ReactDOM unknown attribute', () => {
13
let React;
14
let ReactDOMClient;
15
let act;
16
+ let assertConsoleErrorDev;
17
18
beforeEach(() => {
19
jest.resetModules();
20
React = require('react');
21
ReactDOMClient = require('react-dom/client');
22
act = require('internal-test-utils').act;
23
+ assertConsoleErrorDev =
24
+ require('internal-test-utils').assertConsoleErrorDev;
25
});
26
27
async function testUnknownAttributeRemoval(givenValue) {
@@ -62,12 +65,13 @@ describe('ReactDOM unknown attribute', () => {
65
});
66
67
it('changes values true, false to null, and also warns once', async () => {
65
- await expect(() => testUnknownAttributeAssignment(true, null)).toErrorDev(
68
+ await testUnknownAttributeAssignment(true, null);
69
+ assertConsoleErrorDev([
70
'Received `true` for a non-boolean attribute `unknown`.\n\n' +
71
'If you want to write it to the DOM, pass a string instead: ' +
72
'unknown="true" or unknown={value.toString()}.\n' +
73
' in div (at **)',
70
- );
74
+ ]);
75
await testUnknownAttributeAssignment(false, null);
76
});
77
@@ -92,11 +96,9 @@ describe('ReactDOM unknown attribute', () => {
96
const el = document.createElement('div');
97
const root = ReactDOMClient.createRoot(el);
98
95
- await expect(async () => {
96
- await act(() => {
97
- root.render(<div inert={true} />);
98
- });
99
- }).toErrorDev([]);
99
+ await act(() => {
100
+ root.render(<div inert={true} />);
101
+ });
102
103
expect(el.firstChild.getAttribute('inert')).toBe(true ? '' : null);
104
});
@@ -105,15 +107,15 @@ describe('ReactDOM unknown attribute', () => {
107
const el = document.createElement('div');
108
const root = ReactDOMClient.createRoot(el);
109
108
- await expect(async () => {
109
- await act(() => {
110
- root.render(<div inert="" />);
111
- });
112
- }).toErrorDev([
110
+ await act(() => {
111
+ root.render(<div inert="" />);
112
+ });
113
+ assertConsoleErrorDev([
114
'Received an empty string for a boolean attribute `inert`. ' +
115
'This will treat the attribute as if it were false. ' +
116
'Either pass `false` to silence this warning, or ' +
116
- 'pass `true` if you used an empty string in earlier versions of React to indicate this attribute is true.',
117
+ 'pass `true` if you used an empty string in earlier versions of React to indicate this attribute is true.\n' +
118
+ ' in div (at **)',
119
]);
120
121
expect(el.firstChild.getAttribute('inert')).toBe(true ? null : '');
@@ -136,11 +138,12 @@ describe('ReactDOM unknown attribute', () => {
138
});
139
140
it('coerces NaN to strings and warns', async () => {
139
- await expect(() => testUnknownAttributeAssignment(NaN, 'NaN')).toErrorDev(
141
+ await testUnknownAttributeAssignment(NaN, 'NaN');
142
+ assertConsoleErrorDev([
143
'Received NaN for the `unknown` attribute. ' +
144
'If this is expected, cast the value to a string.\n' +
145
' in div (at **)',
143
- );
146
+ ]);
147
});
148
149
it('coerces objects to strings and warns', async () => {
@@ -167,52 +170,52 @@ describe('ReactDOM unknown attribute', () => {
170
}
171
const test = () =>
172
testUnknownAttributeAssignment(new TemporalLike(), null);
170
- await expect(() =>
171
- expect(test).rejects.toThrowError(new TypeError('prod message')),
172
- ).toErrorDev(
173
+
174
+ await expect(test).rejects.toThrowError(new TypeError('prod message'));
175
+ assertConsoleErrorDev([
176
'The provided `unknown` attribute is an unsupported type TemporalLike.' +
174
- ' This value must be coerced to a string before using it here.',
175
- );
177
+ ' This value must be coerced to a string before using it here.\n' +
178
+ ' in div (at **)',
179
+ ]);
180
});
181
182
it('removes symbols and warns', async () => {
179
- await expect(() => testUnknownAttributeRemoval(Symbol('foo'))).toErrorDev(
183
+ await testUnknownAttributeRemoval(Symbol('foo'));
184
+ assertConsoleErrorDev([
185
'Invalid value for prop `unknown` on <div> tag. Either remove it ' +
186
'from the element, or pass a string or number value to keep it ' +
187
'in the DOM. For details, see https://react.dev/link/attribute-behavior \n' +
188
' in div (at **)',
184
- );
189
+ ]);
190
});
191
192
it('removes functions and warns', async () => {
188
- await expect(() =>
189
- testUnknownAttributeRemoval(function someFunction() {}),
190
- ).toErrorDev(
193
+ await testUnknownAttributeRemoval(function someFunction() {});
194
+ assertConsoleErrorDev([
195
'Invalid value for prop `unknown` on <div> tag. Either remove ' +
196
'it from the element, or pass a string or number value to ' +
197
'keep it in the DOM. For details, see ' +
198
'https://react.dev/link/attribute-behavior \n' +
199
' in div (at **)',
196
- );
200
+ ]);
201
});
202
203
it('allows camelCase unknown attributes and warns', async () => {
204
const el = document.createElement('div');
205
202
- await expect(async () => {
203
- const root = ReactDOMClient.createRoot(el);
206
+ const root = ReactDOMClient.createRoot(el);
207
205
- await act(() => {
206
- root.render(<div helloWorld="something" />);
207
- });
208
- }).toErrorDev(
208
+ await act(() => {
209
+ root.render(<div helloWorld="something" />);
210
+ });
211
+ assertConsoleErrorDev([
212
'React does not recognize the `helloWorld` prop on a DOM element. ' +
213
'If you intentionally want it to appear in the DOM as a custom ' +
214
'attribute, spell it as lowercase `helloworld` instead. ' +
215
'If you accidentally passed it from a parent component, remove ' +
216
'it from the DOM element.\n' +
217
' in div (at **)',
215
- );
218
+ ]);
219
220
expect(el.firstChild.getAttribute('helloworld')).toBe('something');
221
});
packages/react-dom/src/__tests__/ReactDeprecationWarnings-test.js
+12
-6
@@ -12,6 +12,7 @@
12
let React;
13
let ReactNoop;
14
let waitForAll;
15
+let assertConsoleErrorDev;
16
17
describe('ReactDeprecationWarnings', () => {
18
beforeEach(() => {
@@ -20,6 +21,7 @@ describe('ReactDeprecationWarnings', () => {
21
ReactNoop = require('react-noop-renderer');
22
const InternalTestUtils = require('internal-test-utils');
23
waitForAll = InternalTestUtils.waitForAll;
24
+ assertConsoleErrorDev = InternalTestUtils.assertConsoleErrorDev;
25
});
26
27
// @gate !disableDefaultPropsExceptForClasses || !__DEV__
@@ -33,11 +35,13 @@ describe('ReactDeprecationWarnings', () => {
35
};
36
37
ReactNoop.render(<FunctionalComponent />);
36
- await expect(async () => await waitForAll([])).toErrorDev(
38
+ await waitForAll([]);
39
+ assertConsoleErrorDev([
40
'FunctionalComponent: Support for defaultProps ' +
41
'will be removed from function components in a future major ' +
39
- 'release. Use JavaScript default parameters instead.',
40
- );
42
+ 'release. Use JavaScript default parameters instead.\n' +
43
+ ' in FunctionalComponent (at **)',
44
+ ]);
45
});
46
47
// @gate !disableDefaultPropsExceptForClasses || !__DEV__
@@ -55,10 +59,12 @@ describe('ReactDeprecationWarnings', () => {
59
<MemoComponent />
60
</div>,
61
);
58
- await expect(async () => await waitForAll([])).toErrorDev(
62
+ await waitForAll([]);
63
+ assertConsoleErrorDev([
64
'FunctionalComponent: Support for defaultProps ' +
65
'will be removed from memo components in a future major ' +
61
- 'release. Use JavaScript default parameters instead.',
62
- );
66
+ 'release. Use JavaScript default parameters instead.\n' +
67
+ ' in div (at **)',
68
+ ]);
69
});
70
});
packages/react-dom/src/__tests__/findDOMNodeFB-test.js
+6
-4
@@ -12,6 +12,8 @@
12
const React = require('react');
13
const ReactDOM = require('react-dom');
14
const StrictMode = React.StrictMode;
15
+const assertConsoleErrorDev =
16
+ require('internal-test-utils').assertConsoleErrorDev;
17
18
describe('findDOMNode', () => {
19
// @gate www && classic
@@ -128,8 +130,8 @@ describe('findDOMNode', () => {
130
container,
131
);
132
131
- let match;
132
- expect(() => (match = ReactDOM.findDOMNode(parent))).toErrorDev([
133
+ const match = ReactDOM.findDOMNode(parent);
134
+ assertConsoleErrorDev([
135
'findDOMNode is deprecated in StrictMode. ' +
136
'findDOMNode was passed an instance of ContainsStrictModeChild which renders StrictMode children. ' +
137
'Instead, add a ref directly to the element you want to reference. ' +
@@ -161,8 +163,8 @@ describe('findDOMNode', () => {
163
container,
164
);
165
164
- let match;
165
- expect(() => (match = ReactDOM.findDOMNode(parent))).toErrorDev([
166
+ const match = ReactDOM.findDOMNode(parent);
167
+ assertConsoleErrorDev([
168
'findDOMNode is deprecated in StrictMode. ' +
169
'findDOMNode was passed an instance of IsInStrictMode which is inside StrictMode. ' +
170
'Instead, add a ref directly to the element you want to reference. ' +
packages/react-dom/src/__tests__/utils/ReactDOMServerIntegrationTestUtils.js
+1
-1
@@ -87,7 +87,7 @@ module.exports = function (initModules) {
87
console.error.mockClear();
88
} else {
89
// TODO: Rewrite tests that use this helper to enumerate expected errors.
90
- // This will enable the helper to use the .toErrorDev() matcher instead of spying.
90
+ // This will enable the helper to use the assertConsoleErrorDev instead of spying.
91
spyOnDev(console, 'error').mockImplementation(() => {});
92
}
93