@samitouri / QOS-React-1 / commits / a7c898d83a

[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