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

Convert ReactDOMComponent-test to createRoot (#28034)

Convert ReactDOMComponent-test to createRoot

Jan Kassens committed Jan 23, 2024 at 10:04 UTC b2d637128c89d450355622379e97c6c6b1c79435
1 file changed +957 -538
packages/react-dom/src/__tests__/ReactDOMComponent-test.js
+957 -538
@@ -13,15 +13,20 @@ describe('ReactDOMComponent', () => {
13 let React;
14 let ReactTestUtils;
15 let ReactDOM;
16 + let ReactDOMClient;
17 let ReactDOMServer;
18 const ReactFeatureFlags = require('shared/ReactFeatureFlags');
19
20 + let act;
21 +
22 beforeEach(() => {
23 jest.resetModules();
24 React = require('react');
25 ReactDOM = require('react-dom');
26 + ReactDOMClient = require('react-dom/client');
27 ReactDOMServer = require('react-dom/server');
28 ReactTestUtils = require('react-dom/test-utils');
29 + act = require('internal-test-utils').act;
30 });
31
32 afterEach(() => {
@@ -29,21 +34,33 @@ describe('ReactDOMComponent', () => {
34 });
35
36 describe('updateDOM', () => {
32 - it('should handle className', () => {
37 + it('should handle className', async () => {
38 const container = document.createElement('div');
34 - ReactDOM.render(<div style={{}} />, container);
39 + const root = ReactDOMClient.createRoot(container);
40 + await act(() => {
41 + root.render(<div style={{}} />);
42 + });
43
36 - ReactDOM.render(<div className={'foo'} />, container);
44 + await act(() => {
45 + root.render(<div className={'foo'} />);
46 + });
47 expect(container.firstChild.className).toEqual('foo');
38 - ReactDOM.render(<div className={'bar'} />, container);
48 + await act(() => {
49 + root.render(<div className={'bar'} />);
50 + });
51 expect(container.firstChild.className).toEqual('bar');
40 - ReactDOM.render(<div className={null} />, container);
52 + await act(() => {
53 + root.render(<div className={null} />);
54 + });
55 expect(container.firstChild.className).toEqual('');
56 });
57
44 - it('should gracefully handle various style value types', () => {
58 + it('should gracefully handle various style value types', async () => {
59 const container = document.createElement('div');
46 - ReactDOM.render(<div style={{}} />, container);
60 + const root = ReactDOMClient.createRoot(container);
61 + await act(() => {
62 + root.render(<div style={{}} />);
63 + });
64 const stubStyle = container.firstChild.style;
65
66 // set initial style
@@ -53,7 +70,9 @@ describe('ReactDOMComponent', () => {
70 top: 2,
71 fontFamily: 'Arial',
72 };
56 - ReactDOM.render(<div style={setup} />, container);
73 + await act(() => {
74 + root.render(<div style={setup} />);
75 + });
76 expect(stubStyle.display).toEqual('block');
77 expect(stubStyle.left).toEqual('1px');
78 expect(stubStyle.top).toEqual('2px');
@@ -61,14 +80,16 @@ describe('ReactDOMComponent', () => {
80
81 // reset the style to their default state
82 const reset = {display: '', left: null, top: false, fontFamily: true};
64 - ReactDOM.render(<div style={reset} />, container);
83 + await act(() => {
84 + root.render(<div style={reset} />);
85 + });
86 expect(stubStyle.display).toEqual('');
87 expect(stubStyle.left).toEqual('');
88 expect(stubStyle.top).toEqual('');
89 expect(stubStyle.fontFamily).toEqual('');
90 });
91
71 - it('should not update styles when mutating a proxy style object', () => {
92 + it('should not update styles when mutating a proxy style object', async () => {
93 const styleStore = {
94 display: 'none',
95 fontFamily: 'Arial',
@@ -97,7 +118,10 @@ describe('ReactDOMComponent', () => {
118 },
119 };
120 const container = document.createElement('div');
100 - ReactDOM.render(<div style={styles} />, container);
121 + const root = ReactDOMClient.createRoot(container);
122 + await act(() => {
123 + root.render(<div style={styles} />);
124 + });
125
126 const stubStyle = container.firstChild.style;
127 stubStyle.display = styles.display;
@@ -105,26 +129,34 @@ describe('ReactDOMComponent', () => {
129
130 styles.display = 'block';
131
108 - ReactDOM.render(<div style={styles} />, container);
132 + await act(() => {
133 + root.render(<div style={styles} />);
134 + });
135 expect(stubStyle.display).toEqual('none');
136 expect(stubStyle.fontFamily).toEqual('Arial');
137 expect(stubStyle.lineHeight).toEqual('1.2');
138
139 styles.fontFamily = 'Helvetica';
140
115 - ReactDOM.render(<div style={styles} />, container);
141 + await act(() => {
142 + root.render(<div style={styles} />);
143 + });
144 expect(stubStyle.display).toEqual('none');
145 expect(stubStyle.fontFamily).toEqual('Arial');
146 expect(stubStyle.lineHeight).toEqual('1.2');
147
148 styles.lineHeight = 0.5;
149
122 - ReactDOM.render(<div style={styles} />, container);
150 + await act(() => {
151 + root.render(<div style={styles} />);
152 + });
153 expect(stubStyle.display).toEqual('none');
154 expect(stubStyle.fontFamily).toEqual('Arial');
155 expect(stubStyle.lineHeight).toEqual('1.2');
156
127 - ReactDOM.render(<div style={undefined} />, container);
157 + await act(() => {
158 + root.render(<div style={undefined} />);
159 + });
160 expect(stubStyle.display).toBe('');
161 expect(stubStyle.fontFamily).toBe('');
162 expect(stubStyle.lineHeight).toBe('');
@@ -147,11 +179,14 @@ describe('ReactDOMComponent', () => {
179 }
180 });
181
150 - it('should warn for unknown prop', () => {
182 + it('should warn for unknown prop', async () => {
183 const container = document.createElement('div');
152 - expect(() =>
153 - ReactDOM.render(<div foo={() => {}} />, container),
154 - ).toErrorDev(
184 + const root = ReactDOMClient.createRoot(container);
185 + await expect(async () => {
186 + await act(() => {
187 + root.render(<div foo={() => {}} />);
188 + });
189 + }).toErrorDev(
190 'Warning: Invalid value for prop `foo` on <div> tag. Either remove it ' +
191 'from the element, or pass a string or number value to keep ' +
192 'it in the DOM. For details, see https://reactjs.org/link/attribute-behavior ' +
@@ -159,11 +194,14 @@ describe('ReactDOMComponent', () => {
194 );
195 });
196
162 - it('should group multiple unknown prop warnings together', () => {
197 + it('should group multiple unknown prop warnings together', async () => {
198 const container = document.createElement('div');
164 - expect(() =>
165 - ReactDOM.render(<div foo={() => {}} baz={() => {}} />, container),
166 - ).toErrorDev(
199 + const root = ReactDOMClient.createRoot(container);
200 + await expect(async () => {
201 + await act(() => {
202 + root.render(<div foo={() => {}} baz={() => {}} />);
203 + });
204 + }).toErrorDev(
205 'Warning: Invalid values for props `foo`, `baz` on <div> tag. Either remove ' +
206 'them from the element, or pass a string or number value to keep ' +
207 'them in the DOM. For details, see https://reactjs.org/link/attribute-behavior ' +
@@ -171,68 +209,90 @@ describe('ReactDOMComponent', () => {
209 );
210 });
211
174 - it('should warn for onDblClick prop', () => {
212 + it('should warn for onDblClick prop', async () => {
213 const container = document.createElement('div');
176 - expect(() =>
177 - ReactDOM.render(<div onDblClick={() => {}} />, container),
178 - ).toErrorDev(
214 + const root = ReactDOMClient.createRoot(container);
215 + await expect(async () => {
216 + await act(() => {
217 + root.render(<div onDblClick={() => {}} />);
218 + });
219 + }).toErrorDev(
220 'Warning: Invalid event handler property `onDblClick`. Did you mean `onDoubleClick`?\n in div (at **)',
221 );
222 });
223
183 - it('should warn for unknown string event handlers', () => {
224 + it('should warn for unknown string event handlers', async () => {
225 const container = document.createElement('div');
185 - expect(() =>
186 - ReactDOM.render(<div onUnknown='alert("hack")' />, container),
187 - ).toErrorDev(
226 + const root = ReactDOMClient.createRoot(container);
227 + await expect(async () => {
228 + await act(() => {
229 + root.render(<div onUnknown='alert("hack")' />);
230 + });
231 + }).toErrorDev(
232 'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)',
233 );
234 expect(container.firstChild.hasAttribute('onUnknown')).toBe(false);
235 expect(container.firstChild.onUnknown).toBe(undefined);
192 - expect(() =>
193 - ReactDOM.render(<div onunknown='alert("hack")' />, container),
194 - ).toErrorDev(
236 + await expect(async () => {
237 + await act(() => {
238 + root.render(<div onunknown='alert("hack")' />);
239 + });
240 + }).toErrorDev(
241 'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)',
242 );
243 expect(container.firstChild.hasAttribute('onunknown')).toBe(false);
244 expect(container.firstChild.onunknown).toBe(undefined);
199 - expect(() =>
200 - ReactDOM.render(<div on-unknown='alert("hack")' />, container),
201 - ).toErrorDev(
245 + await expect(async () => {
246 + await act(() => {
247 + root.render(<div on-unknown='alert("hack")' />);
248 + });
249 + }).toErrorDev(
250 'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)',
251 );
252 expect(container.firstChild.hasAttribute('on-unknown')).toBe(false);
253 expect(container.firstChild['on-unknown']).toBe(undefined);
254 });
255
208 - it('should warn for unknown function event handlers', () => {
256 + it('should warn for unknown function event handlers', async () => {
257 const container = document.createElement('div');
210 - expect(() =>
211 - ReactDOM.render(<div onUnknown={function () {}} />, container),
212 - ).toErrorDev(
258 + const root = ReactDOMClient.createRoot(container);
259 + await expect(async () => {
260 + await act(() => {
261 + root.render(<div onUnknown={function () {}} />);
262 + });
263 + }).toErrorDev(
264 'Warning: Unknown event handler property `onUnknown`. It will be ignored.\n in div (at **)',
265 );
266 expect(container.firstChild.hasAttribute('onUnknown')).toBe(false);
267 expect(container.firstChild.onUnknown).toBe(undefined);
217 - expect(() =>
218 - ReactDOM.render(<div onunknown={function () {}} />, container),
219 - ).toErrorDev(
268 + await expect(async () => {
269 + await act(() => {
270 + root.render(<div onunknown={function () {}} />);
271 + });
272 + }).toErrorDev(
273 'Warning: Unknown event handler property `onunknown`. It will be ignored.\n in div (at **)',
274 );
275 expect(container.firstChild.hasAttribute('onunknown')).toBe(false);
276 expect(container.firstChild.onunknown).toBe(undefined);
224 - expect(() =>
225 - ReactDOM.render(<div on-unknown={function () {}} />, container),
226 - ).toErrorDev(
277 + await expect(async () => {
278 + await act(() => {
279 + root.render(<div on-unknown={function () {}} />);
280 + });
281 + }).toErrorDev(
282 'Warning: Unknown event handler property `on-unknown`. It will be ignored.\n in div (at **)',
283 );
284 expect(container.firstChild.hasAttribute('on-unknown')).toBe(false);
285 expect(container.firstChild['on-unknown']).toBe(undefined);
286 });
287
233 - it('should warn for badly cased React attributes', () => {
288 + it('should warn for badly cased React attributes', async () => {
289 const container = document.createElement('div');
235 - expect(() => ReactDOM.render(<div CHILDREN="5" />, container)).toErrorDev(
290 + const root = ReactDOMClient.createRoot(container);
291 + await expect(async () => {
292 + await act(() => {
293 + root.render(<div CHILDREN="5" />);
294 + });
295 + }).toErrorDev(
296 'Warning: Invalid DOM property `CHILDREN`. Did you mean `children`?\n in div (at **)',
297 );
298 expect(container.firstChild.getAttribute('CHILDREN')).toBe('5');
@@ -248,14 +308,21 @@ describe('ReactDOMComponent', () => {
308 ReactTestUtils.renderIntoDocument(<Component />);
309 });
310
251 - it('should warn nicely about NaN in style', () => {
311 + it('should warn nicely about NaN in style', async () => {
312 const style = {fontSize: NaN};
313 const div = document.createElement('div');
254 - expect(() => ReactDOM.render(<span style={style} />, div)).toErrorDev(
314 + const root = ReactDOMClient.createRoot(div);
315 + await expect(async () => {
316 + await act(() => {
317 + root.render(<span style={style} />);
318 + });
319 + }).toErrorDev(
320 'Warning: `NaN` is an invalid value for the `fontSize` css style property.' +
321 '\n in span (at **)',
322 );
258 - ReactDOM.render(<span style={style} />, div);
323 + await act(() => {
324 + root.render(<span style={style} />);
325 + });
326 });
327
328 it('throws with Temporal-like objects as style values', () => {
@@ -280,50 +347,68 @@ describe('ReactDOMComponent', () => {
347 );
348 });
349
283 - it('should update styles if initially null', () => {
350 + it('should update styles if initially null', async () => {
351 let styles = null;
352 const container = document.createElement('div');
286 - ReactDOM.render(<div style={styles} />, container);
353 + const root = ReactDOMClient.createRoot(container);
354 + await act(() => {
355 + root.render(<div style={styles} />);
356 + });
357
358 const stubStyle = container.firstChild.style;
359
360 styles = {display: 'block'};
361
292 - ReactDOM.render(<div style={styles} />, container);
362 + await act(() => {
363 + root.render(<div style={styles} />);
364 + });
365 expect(stubStyle.display).toEqual('block');
366 });
367
296 - it('should update styles if updated to null multiple times', () => {
368 + it('should update styles if updated to null multiple times', async () => {
369 let styles = null;
370 const container = document.createElement('div');
299 - ReactDOM.render(<div style={styles} />, container);
371 + const root = ReactDOMClient.createRoot(container);
372 + await act(() => {
373 + root.render(<div style={styles} />);
374 + });
375
376 styles = {display: 'block'};
377 const stubStyle = container.firstChild.style;
378
304 - ReactDOM.render(<div style={styles} />, container);
379 + await act(() => {
380 + root.render(<div style={styles} />);
381 + });
382 expect(stubStyle.display).toEqual('block');
383
307 - ReactDOM.render(<div style={null} />, container);
384 + await act(() => {
385 + root.render(<div style={null} />);
386 + });
387 expect(stubStyle.display).toEqual('');
388
310 - ReactDOM.render(<div style={styles} />, container);
389 + await act(() => {
390 + root.render(<div style={styles} />);
391 + });
392 expect(stubStyle.display).toEqual('block');
393
313 - ReactDOM.render(<div style={null} />, container);
394 + await act(() => {
395 + root.render(<div style={null} />);
396 + });
397 expect(stubStyle.display).toEqual('');
398 });
399
317 - it('should allow named slot projection on both web components and regular DOM elements', () => {
400 + it('should allow named slot projection on both web components and regular DOM elements', async () => {
401 const container = document.createElement('div');
402 + const root = ReactDOMClient.createRoot(container);
403
320 - ReactDOM.render(
321 - <my-component>
322 - <my-second-component slot="first" />
323 - <button slot="second">Hello</button>
324 - </my-component>,
325 - container,
326 - );
404 + await act(() => {
405 + root.render(
406 + <my-component>
407 + <my-second-component slot="first" />
408 + <button slot="second">Hello</button>
409 + </my-component>,
410 + );
411 + });
412
413 const lightDOM = container.firstChild.childNodes;
414
@@ -331,17 +416,19 @@ describe('ReactDOMComponent', () => {
416 expect(lightDOM[1].getAttribute('slot')).toBe('second');
417 });
418
334 - it('should skip reserved props on web components', () => {
419 + it('should skip reserved props on web components', async () => {
420 const container = document.createElement('div');
421 + const root = ReactDOMClient.createRoot(container);
422
337 - ReactDOM.render(
338 - <my-component
339 - children={['foo']}
340 - suppressContentEditableWarning={true}
341 - suppressHydrationWarning={true}
342 - />,
343 - container,
344 - );
423 + await act(() => {
424 + root.render(
425 + <my-component
426 + children={['foo']}
427 + suppressContentEditableWarning={true}
428 + suppressHydrationWarning={true}
429 + />,
430 + );
431 + });
432 expect(container.firstChild.hasAttribute('children')).toBe(false);
433 expect(
434 container.firstChild.hasAttribute('suppressContentEditableWarning'),
@@ -350,14 +437,15 @@ describe('ReactDOMComponent', () => {
437 container.firstChild.hasAttribute('suppressHydrationWarning'),
438 ).toBe(false);
439
353 - ReactDOM.render(
354 - <my-component
355 - children={['bar']}
356 - suppressContentEditableWarning={false}
357 - suppressHydrationWarning={false}
358 - />,
359 - container,
360 - );
440 + await act(() => {
441 + root.render(
442 + <my-component
443 + children={['bar']}
444 + suppressContentEditableWarning={false}
445 + suppressHydrationWarning={false}
446 + />,
447 + );
448 + });
449 expect(container.firstChild.hasAttribute('children')).toBe(false);
450 expect(
451 container.firstChild.hasAttribute('suppressContentEditableWarning'),
@@ -367,110 +455,133 @@ describe('ReactDOMComponent', () => {
455 ).toBe(false);
456 });
457
370 - it('should skip dangerouslySetInnerHTML on web components', () => {
458 + it('should skip dangerouslySetInnerHTML on web components', async () => {
459 const container = document.createElement('div');
460 + const root = ReactDOMClient.createRoot(container);
461
373 - ReactDOM.render(
374 - <my-component dangerouslySetInnerHTML={{__html: 'hi'}} />,
375 - container,
376 - );
462 + await act(() => {
463 + root.render(<my-component dangerouslySetInnerHTML={{__html: 'hi'}} />);
464 + });
465 expect(container.firstChild.hasAttribute('dangerouslySetInnerHTML')).toBe(
466 false,
467 );
468
381 - ReactDOM.render(
382 - <my-component dangerouslySetInnerHTML={{__html: 'bye'}} />,
383 - container,
384 - );
469 + await act(() => {
470 + root.render(<my-component dangerouslySetInnerHTML={{__html: 'bye'}} />);
471 + });
472 expect(container.firstChild.hasAttribute('dangerouslySetInnerHTML')).toBe(
473 false,
474 );
475 });
476
390 - it('should render null and undefined as empty but print other falsy values', () => {
477 + it('should render null and undefined as empty but print other falsy values', async () => {
478 const container = document.createElement('div');
479 + const root = ReactDOMClient.createRoot(container);
480
393 - ReactDOM.render(
394 - <div dangerouslySetInnerHTML={{__html: 'textContent'}} />,
395 - container,
396 - );
481 + await act(() => {
482 + root.render(<div dangerouslySetInnerHTML={{__html: 'textContent'}} />);
483 + });
484 expect(container.textContent).toEqual('textContent');
485
399 - ReactDOM.render(<div dangerouslySetInnerHTML={{__html: 0}} />, container);
486 + await act(() => {
487 + root.render(<div dangerouslySetInnerHTML={{__html: 0}} />);
488 + });
489 expect(container.textContent).toEqual('0');
490
402 - ReactDOM.render(
403 - <div dangerouslySetInnerHTML={{__html: false}} />,
404 - container,
405 - );
491 + await act(() => {
492 + root.render(<div dangerouslySetInnerHTML={{__html: false}} />);
493 + });
494 expect(container.textContent).toEqual('false');
495
408 - ReactDOM.render(
409 - <div dangerouslySetInnerHTML={{__html: ''}} />,
410 - container,
411 - );
496 + await act(() => {
497 + root.render(<div dangerouslySetInnerHTML={{__html: ''}} />);
498 + });
499 expect(container.textContent).toEqual('');
500
414 - ReactDOM.render(
415 - <div dangerouslySetInnerHTML={{__html: null}} />,
416 - container,
417 - );
501 + await act(() => {
502 + root.render(<div dangerouslySetInnerHTML={{__html: null}} />);
503 + });
504 expect(container.textContent).toEqual('');
505
420 - ReactDOM.render(
421 - <div dangerouslySetInnerHTML={{__html: undefined}} />,
422 - container,
423 - );
506 + await act(() => {
507 + root.render(<div dangerouslySetInnerHTML={{__html: undefined}} />);
508 + });
509 expect(container.textContent).toEqual('');
510 });
511
427 - it('should remove attributes', () => {
512 + it('should remove attributes', async () => {
513 const container = document.createElement('div');
429 - ReactDOM.render(<img height="17" />, container);
514 + const root = ReactDOMClient.createRoot(container);
515 + await act(() => {
516 + root.render(<img height="17" />);
517 + });
518
519 expect(container.firstChild.hasAttribute('height')).toBe(true);
432 - ReactDOM.render(<img />, container);
520 + await act(() => {
521 + root.render(<img />);
522 + });
523 expect(container.firstChild.hasAttribute('height')).toBe(false);
524 });
525
436 - it('should remove properties', () => {
526 + it('should remove properties', async () => {
527 const container = document.createElement('div');
438 - ReactDOM.render(<div className="monkey" />, container);
528 + const root = ReactDOMClient.createRoot(container);
529 + await act(() => {
530 + root.render(<div className="monkey" />);
531 + });
532
533 expect(container.firstChild.className).toEqual('monkey');
441 - ReactDOM.render(<div />, container);
534 + await act(() => {
535 + root.render(<div />);
536 + });
537 expect(container.firstChild.className).toEqual('');
538 });
539
445 - it('should not set null/undefined attributes', () => {
540 + it('should not set null/undefined attributes', async () => {
541 const container = document.createElement('div');
542 + const root = ReactDOMClient.createRoot(container);
543 // Initial render.
448 - ReactDOM.render(<img src={null} data-foo={undefined} />, container);
544 + await act(() => {
545 + root.render(<img src={null} data-foo={undefined} />);
546 + });
547 const node = container.firstChild;
548 expect(node.hasAttribute('src')).toBe(false);
549 expect(node.hasAttribute('data-foo')).toBe(false);
550 // Update in one direction.
453 - ReactDOM.render(<img src={undefined} data-foo={null} />, container);
551 + await act(() => {
552 + root.render(<img src={undefined} data-foo={null} />);
553 + });
554 expect(node.hasAttribute('src')).toBe(false);
555 expect(node.hasAttribute('data-foo')).toBe(false);
556 // Update in another direction.
457 - ReactDOM.render(<img src={null} data-foo={undefined} />, container);
557 + await act(() => {
558 + root.render(<img src={null} data-foo={undefined} />);
559 + });
560 expect(node.hasAttribute('src')).toBe(false);
561 expect(node.hasAttribute('data-foo')).toBe(false);
562 // Removal.
461 - ReactDOM.render(<img />, container);
563 + await act(() => {
564 + root.render(<img />);
565 + });
566 expect(node.hasAttribute('src')).toBe(false);
567 expect(node.hasAttribute('data-foo')).toBe(false);
568 // Addition.
465 - ReactDOM.render(<img src={undefined} data-foo={null} />, container);
569 + await act(() => {
570 + root.render(<img src={undefined} data-foo={null} />);
571 + });
572 expect(node.hasAttribute('src')).toBe(false);
573 expect(node.hasAttribute('data-foo')).toBe(false);
574 });
575
576 if (ReactFeatureFlags.enableFilterEmptyStringAttributesDOM) {
471 - it('should not add an empty src attribute', () => {
577 + it('should not add an empty src attribute', async () => {
578 const container = document.createElement('div');
473 - expect(() => ReactDOM.render(<img src="" />, container)).toErrorDev(
579 + const root = ReactDOMClient.createRoot(container);
580 + await expect(async () => {
581 + await act(() => {
582 + root.render(<img src="" />);
583 + });
584 + }).toErrorDev(
585 'An empty string ("") was passed to the src attribute. ' +
586 'This may cause the browser to download the whole page again over the network. ' +
587 'To fix this, either do not render the element at all ' +
@@ -479,10 +590,16 @@ describe('ReactDOMComponent', () => {
590 const node = container.firstChild;
591 expect(node.hasAttribute('src')).toBe(false);
592
482 - ReactDOM.render(<img src="abc" />, container);
593 + await act(() => {
594 + root.render(<img src="abc" />);
595 + });
596 expect(node.hasAttribute('src')).toBe(true);
597
485 - expect(() => ReactDOM.render(<img src="" />, container)).toErrorDev(
598 + await expect(async () => {
599 + await act(() => {
600 + root.render(<img src="" />);
601 + });
602 + }).toErrorDev(
603 'An empty string ("") was passed to the src attribute. ' +
604 'This may cause the browser to download the whole page again over the network. ' +
605 'To fix this, either do not render the element at all ' +
@@ -491,9 +608,14 @@ describe('ReactDOMComponent', () => {
608 expect(node.hasAttribute('src')).toBe(false);
609 });
610
494 - it('should not add an empty href attribute', () => {
611 + it('should not add an empty href attribute', async () => {
612 const container = document.createElement('div');
496 - expect(() => ReactDOM.render(<link href="" />, container)).toErrorDev(
613 + const root = ReactDOMClient.createRoot(container);
614 + await expect(async () => {
615 + await act(() => {
616 + root.render(<link href="" />);
617 + });
618 + }).toErrorDev(
619 'An empty string ("") was passed to the href attribute. ' +
620 'To fix this, either do not render the element at all ' +
621 'or pass null to href instead of an empty string.',
@@ -501,10 +623,16 @@ describe('ReactDOMComponent', () => {
623 const node = container.firstChild;
624 expect(node.hasAttribute('href')).toBe(false);
625
504 - ReactDOM.render(<link href="abc" />, container);
626 + await act(() => {
627 + root.render(<link href="abc" />);
628 + });
629 expect(node.hasAttribute('href')).toBe(true);
630
507 - expect(() => ReactDOM.render(<link href="" />, container)).toErrorDev(
631 + await expect(async () => {
632 + await act(() => {
633 + root.render(<link href="" />);
634 + });
635 + }).toErrorDev(
636 'An empty string ("") was passed to the href attribute. ' +
637 'To fix this, either do not render the element at all ' +
638 'or pass null to href instead of an empty string.',
@@ -512,38 +640,49 @@ describe('ReactDOMComponent', () => {
640 expect(node.hasAttribute('href')).toBe(false);
641 });
642
515 - it('should allow an empty action attribute', () => {
643 + it('should allow an empty action attribute', async () => {
644 const container = document.createElement('div');
517 - ReactDOM.render(<form action="" />, container);
645 + const root = ReactDOMClient.createRoot(container);
646 + await act(() => {
647 + root.render(<form action="" />);
648 + });
649 const node = container.firstChild;
650 expect(node.getAttribute('action')).toBe('');
651
521 - ReactDOM.render(<form action="abc" />, container);
652 + await act(() => {
653 + root.render(<form action="abc" />);
654 + });
655 expect(node.hasAttribute('action')).toBe(true);
656
524 - ReactDOM.render(<form action="" />, container);
657 + await act(() => {
658 + root.render(<form action="" />);
659 + });
660 expect(node.getAttribute('action')).toBe('');
661 });
662
528 - it('allows empty string of a formAction to override the default of a parent', () => {
663 + it('allows empty string of a formAction to override the default of a parent', async () => {
664 const container = document.createElement('div');
530 - ReactDOM.render(
531 - <form action="hello">
532 - <button formAction="" />,
533 - </form>,
534 - container,
535 - );
665 + const root = ReactDOMClient.createRoot(container);
666 + await act(() => {
667 + root.render(
668 + <form action="hello">
669 + <button formAction="" />,
670 + </form>,
671 + );
672 + });
673 const node = container.firstChild.firstChild;
674 expect(node.hasAttribute('formaction')).toBe(true);
675 expect(node.getAttribute('formaction')).toBe('');
676 });
677
541 - it('should not filter attributes for custom elements', () => {
678 + it('should not filter attributes for custom elements', async () => {
679 const container = document.createElement('div');
543 - ReactDOM.render(
544 - <some-custom-element action="" formAction="" href="" src="" />,
545 - container,
546 - );
680 + const root = ReactDOMClient.createRoot(container);
681 + await act(() => {
682 + root.render(
683 + <some-custom-element action="" formAction="" href="" src="" />,
684 + );
685 + });
686 const node = container.firstChild;
687 expect(node.hasAttribute('action')).toBe(true);
688 expect(node.hasAttribute('formAction')).toBe(true);
@@ -552,93 +691,138 @@ describe('ReactDOMComponent', () => {
691 });
692 }
693
555 - it('should apply React-specific aliases to HTML elements', () => {
694 + it('should apply React-specific aliases to HTML elements', async () => {
695 const container = document.createElement('div');
557 - ReactDOM.render(<form acceptCharset="foo" />, container);
696 + const root = ReactDOMClient.createRoot(container);
697 + await act(() => {
698 + root.render(<form acceptCharset="foo" />);
699 + });
700 const node = container.firstChild;
701 // Test attribute initialization.
702 expect(node.getAttribute('accept-charset')).toBe('foo');
703 expect(node.hasAttribute('acceptCharset')).toBe(false);
704 // Test attribute update.
563 - ReactDOM.render(<form acceptCharset="boo" />, container);
705 + await act(() => {
706 + root.render(<form acceptCharset="boo" />);
707 + });
708 expect(node.getAttribute('accept-charset')).toBe('boo');
709 expect(node.hasAttribute('acceptCharset')).toBe(false);
710 // Test attribute removal by setting to null.
567 - ReactDOM.render(<form acceptCharset={null} />, container);
711 + await act(() => {
712 + root.render(<form acceptCharset={null} />);
713 + });
714 expect(node.hasAttribute('accept-charset')).toBe(false);
715 expect(node.hasAttribute('acceptCharset')).toBe(false);
716 // Restore.
571 - ReactDOM.render(<form acceptCharset="foo" />, container);
717 + await act(() => {
718 + root.render(<form acceptCharset="foo" />);
719 + });
720 expect(node.getAttribute('accept-charset')).toBe('foo');
721 expect(node.hasAttribute('acceptCharset')).toBe(false);
722 // Test attribute removal by setting to undefined.
575 - ReactDOM.render(<form acceptCharset={undefined} />, container);
723 + await act(() => {
724 + root.render(<form acceptCharset={undefined} />);
725 + });
726 expect(node.hasAttribute('accept-charset')).toBe(false);
727 expect(node.hasAttribute('acceptCharset')).toBe(false);
728 // Restore.
579 - ReactDOM.render(<form acceptCharset="foo" />, container);
729 + await act(() => {
730 + root.render(<form acceptCharset="foo" />);
731 + });
732 expect(node.getAttribute('accept-charset')).toBe('foo');
733 expect(node.hasAttribute('acceptCharset')).toBe(false);
734 // Test attribute removal.
583 - ReactDOM.render(<form />, container);
735 + await act(() => {
736 + root.render(<form />);
737 + });
738 expect(node.hasAttribute('accept-charset')).toBe(false);
739 expect(node.hasAttribute('acceptCharset')).toBe(false);
740 });
741
588 - it('should apply React-specific aliases to SVG elements', () => {
742 + it('should apply React-specific aliases to SVG elements', async () => {
743 const container = document.createElement('div');
590 - ReactDOM.render(<svg arabicForm="foo" />, container);
744 + const root = ReactDOMClient.createRoot(container);
745 + await act(() => {
746 + root.render(<svg arabicForm="foo" />);
747 + });
748 const node = container.firstChild;
749 // Test attribute initialization.
750 expect(node.getAttribute('arabic-form')).toBe('foo');
751 expect(node.hasAttribute('arabicForm')).toBe(false);
752 // Test attribute update.
596 - ReactDOM.render(<svg arabicForm="boo" />, container);
753 + await act(() => {
754 + root.render(<svg arabicForm="boo" />);
755 + });
756 expect(node.getAttribute('arabic-form')).toBe('boo');
757 expect(node.hasAttribute('arabicForm')).toBe(false);
758 // Test attribute removal by setting to null.
600 - ReactDOM.render(<svg arabicForm={null} />, container);
759 + await act(() => {
760 + root.render(<svg arabicForm={null} />);
761 + });
762 expect(node.hasAttribute('arabic-form')).toBe(false);
763 expect(node.hasAttribute('arabicForm')).toBe(false);
764 // Restore.
604 - ReactDOM.render(<svg arabicForm="foo" />, container);
765 + await act(() => {
766 + root.render(<svg arabicForm="foo" />);
767 + });
768 expect(node.getAttribute('arabic-form')).toBe('foo');
769 expect(node.hasAttribute('arabicForm')).toBe(false);
770 // Test attribute removal by setting to undefined.
608 - ReactDOM.render(<svg arabicForm={undefined} />, container);
771 + await act(() => {
772 + root.render(<svg arabicForm={undefined} />);
773 + });
774 expect(node.hasAttribute('arabic-form')).toBe(false);
775 expect(node.hasAttribute('arabicForm')).toBe(false);
776 // Restore.
612 - ReactDOM.render(<svg arabicForm="foo" />, container);
777 + await act(() => {
778 + root.render(<svg arabicForm="foo" />);
779 + });
780 expect(node.getAttribute('arabic-form')).toBe('foo');
781 expect(node.hasAttribute('arabicForm')).toBe(false);
782 // Test attribute removal.
616 - ReactDOM.render(<svg />, container);
783 + await act(() => {
784 + root.render(<svg />);
785 + });
786 expect(node.hasAttribute('arabic-form')).toBe(false);
787 expect(node.hasAttribute('arabicForm')).toBe(false);
788 });
789
621 - it('should properly update custom attributes on custom elements', () => {
790 + it('should properly update custom attributes on custom elements', async () => {
791 const container = document.createElement('div');
623 - ReactDOM.render(<some-custom-element foo="bar" />, container);
624 - ReactDOM.render(<some-custom-element bar="buzz" />, container);
792 + const root = ReactDOMClient.createRoot(container);
793 + await act(() => {
794 + root.render(<some-custom-element foo="bar" />);
795 + });
796 + expect(container.firstChild.getAttribute('foo')).toBe('bar');
797 + await act(() => {
798 + root.render(<some-custom-element bar="buzz" />);
799 + });
800 + expect(container.firstChild.hasAttribute('foo')).toBe(false);
801 + expect(container.firstChild.getAttribute('bar')).toBe('buzz');
802 const node = container.firstChild;
803 expect(node.hasAttribute('foo')).toBe(false);
804 expect(node.getAttribute('bar')).toBe('buzz');
805 });
806
630 - it('should not apply React-specific aliases to custom elements', () => {
807 + it('should not apply React-specific aliases to custom elements', async () => {
808 const container = document.createElement('div');
632 - ReactDOM.render(<some-custom-element arabicForm="foo" />, container);
809 + const root = ReactDOMClient.createRoot(container);
810 + await act(() => {
811 + root.render(<some-custom-element arabicForm="foo" />);
812 + });
813 const node = container.firstChild;
814 // Should not get transformed to arabic-form as SVG would be.
815 expect(node.getAttribute('arabicForm')).toBe('foo');
816 expect(node.hasAttribute('arabic-form')).toBe(false);
817 // Test attribute update.
638 - ReactDOM.render(<some-custom-element arabicForm="boo" />, container);
818 + await act(() => {
819 + root.render(<some-custom-element arabicForm="boo" />);
820 + });
821 expect(node.getAttribute('arabicForm')).toBe('boo');
822 // Test attribute removal and addition.
641 - ReactDOM.render(<some-custom-element acceptCharset="buzz" />, container);
823 + await act(() => {
824 + root.render(<some-custom-element acceptCharset="buzz" />);
825 + });
826 // Verify the previous attribute was removed.
827 expect(node.hasAttribute('arabicForm')).toBe(false);
828 // Should not get transformed to accept-charset as HTML would be.
@@ -646,15 +830,20 @@ describe('ReactDOMComponent', () => {
830 expect(node.hasAttribute('accept-charset')).toBe(false);
831 });
832
649 - it('should clear a single style prop when changing `style`', () => {
833 + it('should clear a single style prop when changing `style`', async () => {
834 let styles = {display: 'none', color: 'red'};
835 const container = document.createElement('div');
652 - ReactDOM.render(<div style={styles} />, container);
836 + const root = ReactDOMClient.createRoot(container);
837 + await act(() => {
838 + root.render(<div style={styles} />);
839 + });
840
841 const stubStyle = container.firstChild.style;
842
843 styles = {color: 'green'};
657 - ReactDOM.render(<div style={styles} />, container);
844 + await act(() => {
845 + root.render(<div style={styles} />);
846 + });
847 expect(stubStyle.display).toEqual('');
848 expect(stubStyle.color).toEqual('green');
849 });
@@ -829,144 +1018,189 @@ describe('ReactDOMComponent', () => {
1018 ]);
1019 });
1020
832 - it('should update arbitrary attributes for tags containing dashes', () => {
1021 + it('should update arbitrary attributes for tags containing dashes', async () => {
1022 const container = document.createElement('div');
1023 + const root = ReactDOMClient.createRoot(container);
1024
1025 const beforeUpdate = React.createElement('x-foo-component', {}, null);
836 - ReactDOM.render(beforeUpdate, container);
1026 + await act(() => {
1027 + root.render(beforeUpdate);
1028 + });
1029
1030 const afterUpdate = <x-foo-component myattr="myval" />;
839 - ReactDOM.render(afterUpdate, container);
1031 + await act(() => {
1032 + root.render(afterUpdate);
1033 + });
1034
1035 expect(container.childNodes[0].getAttribute('myattr')).toBe('myval');
1036 });
1037
844 - it('should clear all the styles when removing `style`', () => {
1038 + it('should clear all the styles when removing `style`', async () => {
1039 const styles = {display: 'none', color: 'red'};
1040 const container = document.createElement('div');
847 - ReactDOM.render(<div style={styles} />, container);
1041 + const root = ReactDOMClient.createRoot(container);
1042 + await act(() => {
1043 + root.render(<div style={styles} />);
1044 + });
1045
1046 const stubStyle = container.firstChild.style;
1047
851 - ReactDOM.render(<div />, container);
1048 + await act(() => {
1049 + root.render(<div />);
1050 + });
1051 expect(stubStyle.display).toEqual('');
1052 expect(stubStyle.color).toEqual('');
1053 });
1054
856 - it('should update styles when `style` changes from null to object', () => {
1055 + it('should update styles when `style` changes from null to object', async () => {
1056 const container = document.createElement('div');
1057 + const root = ReactDOMClient.createRoot(container);
1058 const styles = {color: 'red'};
859 - ReactDOM.render(<div style={styles} />, container);
860 - ReactDOM.render(<div />, container);
861 - ReactDOM.render(<div style={styles} />, container);
862 -
1059 + await act(() => {
1060 + root.render(<div style={styles} />);
1061 + });
1062 const stubStyle = container.firstChild.style;
864 - expect(stubStyle.color).toEqual('red');
1063 + expect(stubStyle.color).toBe('red');
1064 + await act(() => {
1065 + root.render(<div />);
1066 + });
1067 + expect(stubStyle.color).toBe('');
1068 + await act(() => {
1069 + root.render(<div style={styles} />);
1070 + });
1071 +
1072 + expect(stubStyle.color).toBe('red');
1073 });
1074
867 - it('should not reset innerHTML for when children is null', () => {
1075 + it('should not reset innerHTML for when children is null', async () => {
1076 const container = document.createElement('div');
869 - ReactDOM.render(<div />, container);
1077 + const root = ReactDOMClient.createRoot(container);
1078 + await act(() => {
1079 + root.render(<div />);
1080 + });
1081 container.firstChild.innerHTML = 'bonjour';
1082 expect(container.firstChild.innerHTML).toEqual('bonjour');
1083
873 - ReactDOM.render(<div />, container);
1084 + await act(() => {
1085 + root.render(<div />);
1086 + });
1087 expect(container.firstChild.innerHTML).toEqual('bonjour');
1088 });
1089
877 - it('should reset innerHTML when switching from a direct text child to an empty child', () => {
1090 + it('should reset innerHTML when switching from a direct text child to an empty child', async () => {
1091 const transitionToValues = [null, undefined, false];
879 - transitionToValues.forEach(transitionToValue => {
1092 + // eslint-disable-next-line no-for-of-loops/no-for-of-loops
1093 + for (const transitionToValue of transitionToValues) {
1094 const container = document.createElement('div');
881 - ReactDOM.render(<div>bonjour</div>, container);
1095 + const root = ReactDOMClient.createRoot(container);
1096 + await act(() => {
1097 + root.render(<div>bonjour</div>);
1098 + });
1099 expect(container.firstChild.innerHTML).toEqual('bonjour');
1100
884 - ReactDOM.render(<div>{transitionToValue}</div>, container);
1101 + await act(() => {
1102 + root.render(<div>{transitionToValue}</div>);
1103 + });
1104 expect(container.firstChild.innerHTML).toEqual('');
886 - });
1105 + }
1106 });
1107
889 - it('should empty element when removing innerHTML', () => {
1108 + it('should empty element when removing innerHTML', async () => {
1109 const container = document.createElement('div');
891 - ReactDOM.render(
892 - <div dangerouslySetInnerHTML={{__html: ':)'}} />,
893 - container,
894 - );
1110 + const root = ReactDOMClient.createRoot(container);
1111 + await act(() => {
1112 + root.render(<div dangerouslySetInnerHTML={{__html: ':)'}} />);
1113 + });
1114
1115 expect(container.firstChild.innerHTML).toEqual(':)');
897 - ReactDOM.render(<div />, container);
1116 + await act(() => {
1117 + root.render(<div />);
1118 + });
1119 expect(container.firstChild.innerHTML).toEqual('');
1120 });
1121
901 - it('should transition from string content to innerHTML', () => {
1122 + it('should transition from string content to innerHTML', async () => {
1123 const container = document.createElement('div');
903 - ReactDOM.render(<div>hello</div>, container);
1124 + const root = ReactDOMClient.createRoot(container);
1125 + await act(() => {
1126 + root.render(<div>hello</div>);
1127 + });
1128
1129 expect(container.firstChild.innerHTML).toEqual('hello');
906 - ReactDOM.render(
907 - <div dangerouslySetInnerHTML={{__html: 'goodbye'}} />,
908 - container,
909 - );
1130 + await act(() => {
1131 + root.render(<div dangerouslySetInnerHTML={{__html: 'goodbye'}} />);
1132 + });
1133 expect(container.firstChild.innerHTML).toEqual('goodbye');
1134 });
1135
913 - it('should transition from innerHTML to string content', () => {
1136 + it('should transition from innerHTML to string content', async () => {
1137 const container = document.createElement('div');
915 - ReactDOM.render(
916 - <div dangerouslySetInnerHTML={{__html: 'bonjour'}} />,
917 - container,
918 - );
1138 + const root = ReactDOMClient.createRoot(container);
1139 + await act(() => {
1140 + root.render(<div dangerouslySetInnerHTML={{__html: 'bonjour'}} />);
1141 + });
1142
1143 expect(container.firstChild.innerHTML).toEqual('bonjour');
921 - ReactDOM.render(<div>adieu</div>, container);
1144 + await act(() => {
1145 + root.render(<div>adieu</div>);
1146 + });
1147 expect(container.firstChild.innerHTML).toEqual('adieu');
1148 });
1149
925 - it('should transition from innerHTML to children in nested el', () => {
1150 + it('should transition from innerHTML to children in nested el', async () => {
1151 const container = document.createElement('div');
927 - ReactDOM.render(
928 - <div>
929 - <div dangerouslySetInnerHTML={{__html: 'bonjour'}} />
930 - </div>,
931 - container,
932 - );
1152 + const root = ReactDOMClient.createRoot(container);
1153 + await act(() => {
1154 + root.render(
1155 + <div>
1156 + <div dangerouslySetInnerHTML={{__html: 'bonjour'}} />
1157 + </div>,
1158 + );
1159 + });
1160
1161 expect(container.textContent).toEqual('bonjour');
935 - ReactDOM.render(
936 - <div>
1162 + await act(() => {
1163 + root.render(
1164 <div>
938 - <span>adieu</span>
939 - </div>
940 - </div>,
941 - container,
942 - );
1165 + <div>
1166 + <span>adieu</span>
1167 + </div>
1168 + </div>,
1169 + );
1170 + });
1171 expect(container.textContent).toEqual('adieu');
1172 });
1173
946 - it('should transition from children to innerHTML in nested el', () => {
1174 + it('should transition from children to innerHTML in nested el', async () => {
1175 const container = document.createElement('div');
948 - ReactDOM.render(
949 - <div>
1176 + const root = ReactDOMClient.createRoot(container);
1177 + await act(() => {
1178 + root.render(
1179 <div>
951 - <span>adieu</span>
952 - </div>
953 - </div>,
954 - container,
955 - );
1180 + <div>
1181 + <span>adieu</span>
1182 + </div>
1183 + </div>,
1184 + );
1185 + });
1186
1187 expect(container.textContent).toEqual('adieu');
958 - ReactDOM.render(
959 - <div>
960 - <div dangerouslySetInnerHTML={{__html: 'bonjour'}} />
961 - </div>,
962 - container,
963 - );
1188 + await act(() => {
1189 + root.render(
1190 + <div>
1191 + <div dangerouslySetInnerHTML={{__html: 'bonjour'}} />
1192 + </div>,
1193 + );
1194 + });
1195 expect(container.textContent).toEqual('bonjour');
1196 });
1197
967 - it('should not incur unnecessary DOM mutations for attributes', () => {
1198 + it('should not incur unnecessary DOM mutations for attributes', async () => {
1199 const container = document.createElement('div');
969 - ReactDOM.render(<div id="" />, container);
1200 + const root = ReactDOMClient.createRoot(container);
1201 + await act(() => {
1202 + root.render(<div id="" />);
1203 + });
1204
1205 const node = container.firstChild;
1206 const nodeSetAttribute = node.setAttribute;
@@ -977,34 +1211,49 @@ describe('ReactDOMComponent', () => {
1211 node.removeAttribute = jest.fn();
1212 node.removeAttribute.mockImplementation(nodeRemoveAttribute);
1213
980 - ReactDOM.render(<div id="" />, container);
1214 + await act(() => {
1215 + root.render(<div id="" />);
1216 + });
1217 expect(node.setAttribute).toHaveBeenCalledTimes(0);
1218 expect(node.removeAttribute).toHaveBeenCalledTimes(0);
1219
984 - ReactDOM.render(<div id="foo" />, container);
1220 + await act(() => {
1221 + root.render(<div id="foo" />);
1222 + });
1223 expect(node.setAttribute).toHaveBeenCalledTimes(1);
1224 expect(node.removeAttribute).toHaveBeenCalledTimes(0);
1225
988 - ReactDOM.render(<div id="foo" />, container);
1226 + await act(() => {
1227 + root.render(<div id="foo" />);
1228 + });
1229 expect(node.setAttribute).toHaveBeenCalledTimes(1);
1230 expect(node.removeAttribute).toHaveBeenCalledTimes(0);
1231
992 - ReactDOM.render(<div />, container);
1232 + await act(() => {
1233 + root.render(<div />);
1234 + });
1235 expect(node.setAttribute).toHaveBeenCalledTimes(1);
1236 expect(node.removeAttribute).toHaveBeenCalledTimes(1);
1237
996 - ReactDOM.render(<div id="" />, container);
1238 + await act(() => {
1239 + root.render(<div id="" />);
1240 + });
1241 expect(node.setAttribute).toHaveBeenCalledTimes(2);
1242 expect(node.removeAttribute).toHaveBeenCalledTimes(1);
1243
1000 - ReactDOM.render(<div />, container);
1244 + await act(() => {
1245 + root.render(<div />);
1246 + });
1247 expect(node.setAttribute).toHaveBeenCalledTimes(2);
1248 expect(node.removeAttribute).toHaveBeenCalledTimes(2);
1249 });
1250
1005 - it('should not incur unnecessary DOM mutations for string properties', () => {
1251 + it('should not incur unnecessary DOM mutations for string properties', async () => {
1252 const container = document.createElement('div');
1007 - ReactDOM.render(<div value="" />, container);
1253 + const root = ReactDOMClient.createRoot(container);
1254 + await act(() => {
1255 + root.render(<div value="" />);
1256 + });
1257
1258 const node = container.firstChild;
1259
@@ -1016,29 +1265,44 @@ describe('ReactDOMComponent', () => {
1265 nodeValueSetter(key, value);
1266 };
1267
1019 - ReactDOM.render(<div value="foo" />, container);
1268 + await act(() => {
1269 + root.render(<div value="foo" />);
1270 + });
1271 expect(nodeValueSetter).toHaveBeenCalledTimes(1);
1272
1022 - ReactDOM.render(<div value="foo" />, container);
1273 + await act(() => {
1274 + root.render(<div value="foo" />);
1275 + });
1276 expect(nodeValueSetter).toHaveBeenCalledTimes(1);
1277
1025 - ReactDOM.render(<div />, container);
1278 + await act(() => {
1279 + root.render(<div />);
1280 + });
1281 expect(nodeValueSetter).toHaveBeenCalledTimes(1);
1282
1028 - ReactDOM.render(<div value={null} />, container);
1283 + await act(() => {
1284 + root.render(<div value={null} />);
1285 + });
1286 expect(nodeValueSetter).toHaveBeenCalledTimes(1);
1287
1031 - ReactDOM.render(<div value="" />, container);
1288 + await act(() => {
1289 + root.render(<div value="" />);
1290 + });
1291 expect(nodeValueSetter).toHaveBeenCalledTimes(2);
1292
1034 - ReactDOM.render(<div />, container);
1293 + await act(() => {
1294 + root.render(<div />);
1295 + });
1296 expect(nodeValueSetter).toHaveBeenCalledTimes(2);
1297 });
1298
1038 - it('should not incur unnecessary DOM mutations for controlled string properties', () => {
1299 + it('should not incur unnecessary DOM mutations for controlled string properties', async () => {
1300 function onChange() {}
1301 const container = document.createElement('div');
1041 - ReactDOM.render(<input value="" onChange={onChange} />, container);
1302 + const root = ReactDOMClient.createRoot(container);
1303 + await act(() => {
1304 + root.render(<input value="" onChange={onChange} />);
1305 + });
1306
1307 const node = container.firstChild;
1308
@@ -1053,17 +1317,22 @@ describe('ReactDOMComponent', () => {
1317 }),
1318 });
1319
1056 - ReactDOM.render(<input value="foo" onChange={onChange} />, container);
1320 + await act(() => {
1321 + root.render(<input value="foo" onChange={onChange} />);
1322 + });
1323 expect(nodeValueSetter).toHaveBeenCalledTimes(1);
1324
1059 - ReactDOM.render(
1060 - <input value="foo" data-unrelated={true} onChange={onChange} />,
1061 - container,
1062 - );
1325 + await act(() => {
1326 + root.render(
1327 + <input value="foo" data-unrelated={true} onChange={onChange} />,
1328 + );
1329 + });
1330 expect(nodeValueSetter).toHaveBeenCalledTimes(1);
1331
1065 - expect(() => {
1066 - ReactDOM.render(<input onChange={onChange} />, container);
1332 + await expect(async () => {
1333 + await act(() => {
1334 + root.render(<input onChange={onChange} />);
1335 + });
1336 }).toErrorDev(
1337 'A component is changing a controlled input to be uncontrolled. This is likely caused by ' +
1338 'the value changing from a defined to undefined, which should not happen. Decide between ' +
@@ -1071,16 +1340,20 @@ describe('ReactDOMComponent', () => {
1340 );
1341 expect(nodeValueSetter).toHaveBeenCalledTimes(1);
1342
1074 - expect(() => {
1075 - ReactDOM.render(<input value={null} onChange={onChange} />, container);
1343 + await expect(async () => {
1344 + await act(() => {
1345 + root.render(<input value={null} onChange={onChange} />);
1346 + });
1347 }).toErrorDev(
1348 'value` prop on `input` should not be null. Consider using an empty string to clear the ' +
1349 'component or `undefined` for uncontrolled components.',
1350 );
1351 expect(nodeValueSetter).toHaveBeenCalledTimes(1);
1352
1082 - expect(() => {
1083 - ReactDOM.render(<input value="" onChange={onChange} />, container);
1353 + await expect(async () => {
1354 + await act(() => {
1355 + root.render(<input value="" onChange={onChange} />);
1356 + });
1357 }).toErrorDev(
1358 ' A component is changing an uncontrolled input to be controlled. This is likely caused by ' +
1359 'the value changing from undefined to a defined value, which should not happen. Decide between ' +
@@ -1088,13 +1361,18 @@ describe('ReactDOMComponent', () => {
1361 );
1362 expect(nodeValueSetter).toHaveBeenCalledTimes(2);
1363
1091 - ReactDOM.render(<input onChange={onChange} />, container);
1364 + await act(() => {
1365 + root.render(<input onChange={onChange} />);
1366 + });
1367 expect(nodeValueSetter).toHaveBeenCalledTimes(2);
1368 });
1369
1095 - it('should not incur unnecessary DOM mutations for boolean properties', () => {
1370 + it('should not incur unnecessary DOM mutations for boolean properties', async () => {
1371 const container = document.createElement('div');
1097 - ReactDOM.render(<audio muted={true} />, container);
1372 + const root = ReactDOMClient.createRoot(container);
1373 + await act(() => {
1374 + root.render(<audio muted={true} />);
1375 + });
1376
1377 const node = container.firstChild;
1378 let nodeValue = true;
@@ -1108,78 +1386,102 @@ describe('ReactDOMComponent', () => {
1386 }),
1387 });
1388
1111 - ReactDOM.render(<audio muted={true} data-unrelated="yes" />, container);
1389 + await act(() => {
1390 + root.render(<audio muted={true} data-unrelated="yes" />);
1391 + });
1392 expect(nodeValueSetter).toHaveBeenCalledTimes(0);
1393
1114 - ReactDOM.render(<audio muted={false} data-unrelated="ok" />, container);
1394 + await act(() => {
1395 + root.render(<audio muted={false} data-unrelated="ok" />);
1396 + });
1397 expect(nodeValueSetter).toHaveBeenCalledTimes(1);
1398 });
1399
1118 - it('should ignore attribute list for elements with the "is" attribute', () => {
1400 + it('should ignore attribute list for elements with the "is" attribute', async () => {
1401 const container = document.createElement('div');
1120 - ReactDOM.render(<button is="test" cowabunga="chevynova" />, container);
1402 + const root = ReactDOMClient.createRoot(container);
1403 + await act(() => {
1404 + root.render(<button is="test" cowabunga="chevynova" />);
1405 + });
1406 expect(container.firstChild.hasAttribute('cowabunga')).toBe(true);
1407 });
1408
1124 - it('should warn about non-string "is" attribute', () => {
1409 + it('should warn about non-string "is" attribute', async () => {
1410 const container = document.createElement('div');
1126 - expect(() =>
1127 - ReactDOM.render(<button is={function () {}} />, container),
1128 - ).toErrorDev(
1411 + const root = ReactDOMClient.createRoot(container);
1412 + await expect(async () => {
1413 + await act(() => {
1414 + root.render(<button is={function () {}} />);
1415 + });
1416 + }).toErrorDev(
1417 'Received a `function` for a string attribute `is`. If this is expected, cast ' +
1418 'the value to a string.',
1419 );
1420 });
1421
1134 - it('should not update when switching between null/undefined', () => {
1422 + it('should not update when switching between null/undefined', async () => {
1423 const container = document.createElement('div');
1136 - const node = ReactDOM.render(<div />, container);
1424 + const root = ReactDOMClient.createRoot(container);
1425 + await act(() => {
1426 + root.render(<div />);
1427 + });
1428
1429 const setter = jest.fn();
1139 - node.setAttribute = setter;
1430 + container.firstChild.setAttribute = setter;
1431
1141 - ReactDOM.render(<div dir={null} />, container);
1142 - ReactDOM.render(<div dir={undefined} />, container);
1143 - ReactDOM.render(<div />, container);
1432 + await act(() => {
1433 + root.render(<div dir={null} />);
1434 + });
1435 + await act(() => {
1436 + root.render(<div dir={undefined} />);
1437 + });
1438 + await act(() => {
1439 + root.render(<div />);
1440 + });
1441 expect(setter).toHaveBeenCalledTimes(0);
1145 - ReactDOM.render(<div dir="ltr" />, container);
1442 + await act(() => {
1443 + root.render(<div dir="ltr" />);
1444 + });
1445 expect(setter).toHaveBeenCalledTimes(1);
1446 });
1447
1149 - it('handles multiple child updates without interference', () => {
1448 + it('handles multiple child updates without interference', async () => {
1449 // This test might look like it's just testing ReactMultiChild but the
1450 // last bug in this was actually in DOMChildrenOperations so this test
1451 // needs to be in some DOM-specific test file.
1452 const container = document.createElement('div');
1453 + const root = ReactDOMClient.createRoot(container);
1454
1455 // ABCD
1156 - ReactDOM.render(
1157 - <div>
1158 - <div key="one">
1159 - <div key="A">A</div>
1160 - <div key="B">B</div>
1161 - </div>
1162 - <div key="two">
1163 - <div key="C">C</div>
1164 - <div key="D">D</div>
1165 - </div>
1166 - </div>,
1167 - container,
1168 - );
1456 + await act(() => {
1457 + root.render(
1458 + <div>
1459 + <div key="one">
1460 + <div key="A">A</div>
1461 + <div key="B">B</div>
1462 + </div>
1463 + <div key="two">
1464 + <div key="C">C</div>
1465 + <div key="D">D</div>
1466 + </div>
1467 + </div>,
1468 + );
1469 + });
1470 // BADC
1170 - ReactDOM.render(
1171 - <div>
1172 - <div key="one">
1173 - <div key="B">B</div>
1174 - <div key="A">A</div>
1175 - </div>
1176 - <div key="two">
1177 - <div key="D">D</div>
1178 - <div key="C">C</div>
1179 - </div>
1180 - </div>,
1181 - container,
1182 - );
1471 + await act(() => {
1472 + root.render(
1473 + <div>
1474 + <div key="one">
1475 + <div key="B">B</div>
1476 + <div key="A">A</div>
1477 + </div>
1478 + <div key="two">
1479 + <div key="D">D</div>
1480 + <div key="C">C</div>
1481 + </div>
1482 + </div>,
1483 + );
1484 + });
1485
1486 expect(container.textContent).toBe('BADC');
1487 });
@@ -1248,25 +1550,30 @@ describe('ReactDOMComponent', () => {
1550 let mountComponent;
1551
1552 beforeEach(() => {
1251 - mountComponent = function (props) {
1553 + mountComponent = async props => {
1554 const container = document.createElement('div');
1253 - ReactDOM.render(<div {...props} />, container);
1555 + const root = ReactDOMClient.createRoot(container);
1556 + await act(() => {
1557 + root.render(<div {...props} />);
1558 + });
1559 };
1560 });
1561
1257 - it('should work error event on <source> element', () => {
1562 + it('should work error event on <source> element', async () => {
1563 spyOnDevAndProd(console, 'log');
1564 const container = document.createElement('div');
1260 - ReactDOM.render(
1261 - <video>
1262 - <source
1263 - src="http://example.org/video"
1264 - type="video/mp4"
1265 - onError={e => console.log('onError called')}
1266 - />
1267 - </video>,
1268 - container,
1269 - );
1565 + const root = ReactDOMClient.createRoot(container);
1566 + await act(() => {
1567 + root.render(
1568 + <video>
1569 + <source
1570 + src="http://example.org/video"
1571 + type="video/mp4"
1572 + onError={e => console.log('onError called')}
1573 + />
1574 + </video>,
1575 + );
1576 + });
1577
1578 const errorEvent = document.createEvent('Event');
1579 errorEvent.initEvent('error', false, false);
@@ -1358,32 +1665,36 @@ describe('ReactDOMComponent', () => {
1665 }
1666 });
1667
1361 - it('should throw on children for void elements', () => {
1668 + it('should throw on children for void elements', async () => {
1669 const container = document.createElement('div');
1363 - expect(() => {
1364 - ReactDOM.render(<input>children</input>, container);
1365 - }).toThrowError(
1670 + const root = ReactDOMClient.createRoot(container);
1671 + await expect(async () => {
1672 + await act(() => {
1673 + root.render(<input>children</input>);
1674 + });
1675 + }).rejects.toThrowError(
1676 'input is a void element tag and must neither have `children` nor ' +
1677 'use `dangerouslySetInnerHTML`.',
1678 );
1679 });
1680
1371 - it('should throw on dangerouslySetInnerHTML for void elements', () => {
1681 + it('should throw on dangerouslySetInnerHTML for void elements', async () => {
1682 const container = document.createElement('div');
1373 - expect(() => {
1374 - ReactDOM.render(
1375 - <input dangerouslySetInnerHTML={{__html: 'content'}} />,
1376 - container,
1377 - );
1378 - }).toThrowError(
1683 + const root = ReactDOMClient.createRoot(container);
1684 + await expect(async () => {
1685 + await act(() => {
1686 + root.render(<input dangerouslySetInnerHTML={{__html: 'content'}} />);
1687 + });
1688 + }).rejects.toThrowError(
1689 'input is a void element tag and must neither have `children` nor ' +
1690 'use `dangerouslySetInnerHTML`.',
1691 );
1692 });
1693
1384 - it('should treat menuitem as a void element but still create the closing tag', () => {
1694 + it('should treat menuitem as a void element but still create the closing tag', async () => {
1695 // menuitem is not implemented in jsdom, so this triggers the unknown warning error
1696 const container = document.createElement('div');
1697 + const root = ReactDOMClient.createRoot(container);
1698
1699 const returnedValue = ReactDOMServer.renderToString(
1700 <menu>
@@ -1393,70 +1704,71 @@ describe('ReactDOMComponent', () => {
1704
1705 expect(returnedValue).toContain('</menuitem>');
1706
1396 - expect(function () {
1397 - expect(() => {
1398 - ReactDOM.render(
1399 - <menu>
1400 - <menuitem>children</menuitem>
1401 - </menu>,
1402 - container,
1403 - );
1707 + await expect(async () => {
1708 + await expect(async () => {
1709 + await act(() => {
1710 + root.render(
1711 + <menu>
1712 + <menuitem>children</menuitem>
1713 + </menu>,
1714 + );
1715 + });
1716 }).toErrorDev('The tag <menuitem> is unrecognized in this browser.');
1405 - }).toThrowError(
1717 + }).rejects.toThrowError(
1718 'menuitem is a void element tag and must neither have `children` nor use ' +
1719 '`dangerouslySetInnerHTML`.',
1720 );
1721 });
1722
1411 - it('should validate against multiple children props', () => {
1412 - expect(function () {
1413 - mountComponent({children: '', dangerouslySetInnerHTML: ''});
1414 - }).toThrowError(
1723 + it('should validate against multiple children props', async () => {
1724 + await expect(async () => {
1725 + await mountComponent({children: '', dangerouslySetInnerHTML: ''});
1726 + }).rejects.toThrowError(
1727 '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +
1728 'Please visit https://reactjs.org/link/dangerously-set-inner-html for more information.',
1729 );
1730 });
1731
1420 - it('should validate against use of innerHTML', () => {
1421 - expect(() =>
1422 - mountComponent({innerHTML: '<span>Hi Jim!</span>'}),
1423 - ).toErrorDev('Directly setting property `innerHTML` is not permitted. ');
1732 + it('should validate against use of innerHTML', async () => {
1733 + await expect(async () => {
1734 + await mountComponent({innerHTML: '<span>Hi Jim!</span>'});
1735 + }).toErrorDev('Directly setting property `innerHTML` is not permitted. ');
1736 });
1737
1426 - it('should validate against use of innerHTML without case sensitivity', () => {
1427 - expect(() =>
1428 - mountComponent({innerhtml: '<span>Hi Jim!</span>'}),
1429 - ).toErrorDev('Directly setting property `innerHTML` is not permitted. ');
1738 + it('should validate against use of innerHTML without case sensitivity', async () => {
1739 + await expect(async () => {
1740 + await mountComponent({innerhtml: '<span>Hi Jim!</span>'});
1741 + }).toErrorDev('Directly setting property `innerHTML` is not permitted. ');
1742 });
1743
1432 - it('should validate use of dangerouslySetInnerHTML', () => {
1433 - expect(function () {
1434 - mountComponent({dangerouslySetInnerHTML: '<span>Hi Jim!</span>'});
1435 - }).toThrowError(
1744 + it('should validate use of dangerouslySetInnerHTML', async () => {
1745 + await expect(async () => {
1746 + await mountComponent({dangerouslySetInnerHTML: '<span>Hi Jim!</span>'});
1747 + }).rejects.toThrowError(
1748 '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +
1749 'Please visit https://reactjs.org/link/dangerously-set-inner-html for more information.',
1750 );
1751 });
1752
1441 - it('should validate use of dangerouslySetInnerHTML', () => {
1442 - expect(function () {
1443 - mountComponent({dangerouslySetInnerHTML: {foo: 'bar'}});
1444 - }).toThrowError(
1753 + it('should validate use of dangerouslySetInnerHTML', async () => {
1754 + await expect(async () => {
1755 + await mountComponent({dangerouslySetInnerHTML: {foo: 'bar'}});
1756 + }).rejects.toThrowError(
1757 '`props.dangerouslySetInnerHTML` must be in the form `{__html: ...}`. ' +
1758 'Please visit https://reactjs.org/link/dangerously-set-inner-html for more information.',
1759 );
1760 });
1761
1450 - it('should allow {__html: null}', () => {
1451 - expect(function () {
1452 - mountComponent({dangerouslySetInnerHTML: {__html: null}});
1762 + it('should allow {__html: null}', async () => {
1763 + await expect(async () => {
1764 + await mountComponent({dangerouslySetInnerHTML: {__html: null}});
1765 }).not.toThrow();
1766 });
1767
1456 - it('should warn about contentEditable and children', () => {
1457 - expect(() =>
1458 - mountComponent({contentEditable: true, children: ''}),
1459 - ).toErrorDev(
1768 + it('should warn about contentEditable and children', async () => {
1769 + await expect(async () => {
1770 + await mountComponent({contentEditable: true, children: ''});
1771 + }).toErrorDev(
1772 'Warning: A component is `contentEditable` and contains `children` ' +
1773 'managed by React. It is now your responsibility to guarantee that ' +
1774 'none of those nodes are unexpectedly modified or duplicated. This ' +
@@ -1464,25 +1776,25 @@ describe('ReactDOMComponent', () => {
1776 );
1777 });
1778
1467 - it('should respect suppressContentEditableWarning', () => {
1468 - mountComponent({
1779 + it('should respect suppressContentEditableWarning', async () => {
1780 + await mountComponent({
1781 contentEditable: true,
1782 children: '',
1783 suppressContentEditableWarning: true,
1784 });
1785 });
1786
1475 - it('should validate against invalid styles', () => {
1476 - expect(function () {
1477 - mountComponent({style: 'display: none'});
1478 - }).toThrowError(
1787 + it('should validate against invalid styles', async () => {
1788 + await expect(async () => {
1789 + await mountComponent({style: 'display: none'});
1790 + }).rejects.toThrowError(
1791 'The `style` prop expects a mapping from style properties to values, ' +
1792 "not a string. For example, style={{marginRight: spacing + 'em'}} " +
1793 'when using JSX.',
1794 );
1795 });
1796
1485 - it('should throw for children on void elements', () => {
1797 + it('should throw for children on void elements', async () => {
1798 class X extends React.Component {
1799 render() {
1800 return <input>moo</input>;
@@ -1490,36 +1802,44 @@ describe('ReactDOMComponent', () => {
1802 }
1803
1804 const container = document.createElement('div');
1493 - expect(() => {
1494 - ReactDOM.render(<X />, container);
1495 - }).toThrowError(
1805 + const root = ReactDOMClient.createRoot(container);
1806 + await expect(async () => {
1807 + await act(() => {
1808 + root.render(<X />);
1809 + });
1810 + }).rejects.toThrowError(
1811 'input is a void element tag and must neither have `children` ' +
1812 'nor use `dangerouslySetInnerHTML`.',
1813 );
1814 });
1815
1501 - it('should support custom elements which extend native elements', () => {
1816 + it('should support custom elements which extend native elements', async () => {
1817 const container = document.createElement('div');
1818 + const root = ReactDOMClient.createRoot(container);
1819 spyOnDevAndProd(document, 'createElement');
1504 - ReactDOM.render(<div is="custom-div" />, container);
1820 + await act(() => {
1821 + root.render(<div is="custom-div" />);
1822 + });
1823 expect(document.createElement).toHaveBeenCalledWith('div', {
1824 is: 'custom-div',
1825 });
1826 });
1827
1510 - it('should work load and error events on <image> element in SVG', () => {
1828 + it('should work load and error events on <image> element in SVG', async () => {
1829 spyOnDevAndProd(console, 'log');
1830 const container = document.createElement('div');
1513 - ReactDOM.render(
1514 - <svg>
1515 - <image
1516 - xlinkHref="http://example.org/image"
1517 - onError={e => console.log('onError called')}
1518 - onLoad={e => console.log('onLoad called')}
1519 - />
1520 - </svg>,
1521 - container,
1522 - );
1831 + const root = ReactDOMClient.createRoot(container);
1832 + await act(() => {
1833 + root.render(
1834 + <svg>
1835 + <image
1836 + xlinkHref="http://example.org/image"
1837 + onError={e => console.log('onError called')}
1838 + onLoad={e => console.log('onLoad called')}
1839 + />
1840 + </svg>,
1841 + );
1842 + });
1843
1844 const loadEvent = document.createEvent('Event');
1845 const errorEvent = document.createEvent('Event');
@@ -1537,14 +1857,14 @@ describe('ReactDOMComponent', () => {
1857 }
1858 });
1859
1540 - it('should receive a load event on <link> elements', () => {
1860 + it('should receive a load event on <link> elements', async () => {
1861 const container = document.createElement('div');
1862 + const root = ReactDOMClient.createRoot(container);
1863 const onLoad = jest.fn();
1864
1544 - ReactDOM.render(
1545 - <link href="http://example.org/link" onLoad={onLoad} />,
1546 - container,
1547 - );
1865 + await act(() => {
1866 + root.render(<link href="http://example.org/link" onLoad={onLoad} />);
1867 + });
1868
1869 const loadEvent = document.createEvent('Event');
1870 const link = container.getElementsByTagName('link')[0];
@@ -1555,14 +1875,14 @@ describe('ReactDOMComponent', () => {
1875 expect(onLoad).toHaveBeenCalledTimes(1);
1876 });
1877
1558 - it('should receive an error event on <link> elements', () => {
1878 + it('should receive an error event on <link> elements', async () => {
1879 const container = document.createElement('div');
1880 + const root = ReactDOMClient.createRoot(container);
1881 const onError = jest.fn();
1882
1562 - ReactDOM.render(
1563 - <link href="http://example.org/link" onError={onError} />,
1564 - container,
1565 - );
1883 + await act(() => {
1884 + root.render(<link href="http://example.org/link" onError={onError} />);
1885 + });
1886
1887 const errorEvent = document.createEvent('Event');
1888 const link = container.getElementsByTagName('link')[0];
@@ -1576,82 +1896,99 @@ describe('ReactDOMComponent', () => {
1896
1897 describe('updateComponent', () => {
1898 let container;
1899 + let root;
1900
1901 beforeEach(() => {
1902 container = document.createElement('div');
1903 + root = ReactDOMClient.createRoot(container);
1904 });
1905
1584 - it('should warn against children for void elements', () => {
1585 - ReactDOM.render(<input />, container);
1906 + it('should warn against children for void elements', async () => {
1907 + await act(() => {
1908 + root.render(<input />);
1909 + });
1910
1587 - expect(function () {
1588 - ReactDOM.render(<input>children</input>, container);
1589 - }).toThrowError(
1911 + await expect(async () => {
1912 + await act(() => {
1913 + root.render(<input>children</input>);
1914 + });
1915 + }).rejects.toThrowError(
1916 'input is a void element tag and must neither have `children` nor use ' +
1917 '`dangerouslySetInnerHTML`.',
1918 );
1919 });
1920
1595 - it('should warn against dangerouslySetInnerHTML for void elements', () => {
1596 - ReactDOM.render(<input />, container);
1921 + it('should warn against dangerouslySetInnerHTML for void elements', async () => {
1922 + await act(() => {
1923 + root.render(<input />);
1924 + });
1925
1598 - expect(function () {
1599 - ReactDOM.render(
1600 - <input dangerouslySetInnerHTML={{__html: 'content'}} />,
1601 - container,
1602 - );
1603 - }).toThrowError(
1926 + await expect(async () => {
1927 + await act(() => {
1928 + root.render(<input dangerouslySetInnerHTML={{__html: 'content'}} />);
1929 + });
1930 + }).rejects.toThrowError(
1931 'input is a void element tag and must neither have `children` nor use ' +
1932 '`dangerouslySetInnerHTML`.',
1933 );
1934 });
1935
1609 - it('should validate against multiple children props', () => {
1610 - ReactDOM.render(<div />, container);
1936 + it('should validate against multiple children props', async () => {
1937 + await act(() => {
1938 + root.render(<div />);
1939 + });
1940
1612 - expect(function () {
1613 - ReactDOM.render(
1614 - <div children="" dangerouslySetInnerHTML={{__html: ''}} />,
1615 - container,
1616 - );
1617 - }).toThrowError(
1941 + await expect(async () => {
1942 + await act(() => {
1943 + root.render(
1944 + <div children="" dangerouslySetInnerHTML={{__html: ''}} />,
1945 + );
1946 + });
1947 + }).rejects.toThrowError(
1948 'Can only set one of `children` or `props.dangerouslySetInnerHTML`.',
1949 );
1950 });
1951
1622 - it('should warn about contentEditable and children', () => {
1623 - expect(() => {
1624 - ReactDOM.render(
1625 - <div contentEditable={true}>
1626 - <div />
1627 - </div>,
1628 - container,
1629 - );
1952 + it('should warn about contentEditable and children', async () => {
1953 + await expect(async () => {
1954 + await act(() => {
1955 + root.render(
1956 + <div contentEditable={true}>
1957 + <div />
1958 + </div>,
1959 + );
1960 + });
1961 }).toErrorDev('contentEditable');
1962 });
1963
1633 - it('should validate against invalid styles', () => {
1634 - ReactDOM.render(<div />, container);
1964 + it('should validate against invalid styles', async () => {
1965 + await act(() => {
1966 + root.render(<div />);
1967 + });
1968
1636 - expect(function () {
1637 - ReactDOM.render(<div style={1} />, container);
1638 - }).toThrowError(
1969 + await expect(async () => {
1970 + await act(() => {
1971 + root.render(<div style={1} />);
1972 + });
1973 + }).rejects.toThrowError(
1974 'The `style` prop expects a mapping from style properties to values, ' +
1975 "not a string. For example, style={{marginRight: spacing + 'em'}} " +
1976 'when using JSX.',
1977 );
1978 });
1979
1645 - it('should report component containing invalid styles', () => {
1980 + it('should report component containing invalid styles', async () => {
1981 class Animal extends React.Component {
1982 render() {
1983 return <div style={1} />;
1984 }
1985 }
1986
1652 - expect(() => {
1653 - ReactDOM.render(<Animal />, container);
1654 - }).toThrowError(
1987 + await expect(async () => {
1988 + await act(() => {
1989 + root.render(<Animal />);
1990 + });
1991 + }).rejects.toThrowError(
1992 'The `style` prop expects a mapping from style properties to values, ' +
1993 "not a string. For example, style={{marginRight: spacing + 'em'}} " +
1994 'when using JSX.',
@@ -1681,7 +2018,7 @@ describe('ReactDOMComponent', () => {
2018 });
2019
2020 describe('unmountComponent', () => {
1684 - it('unmounts children before unsetting DOM node info', () => {
2021 + it('unmounts children before unsetting DOM node info', async () => {
2022 class Inner extends React.Component {
2023 render() {
2024 return <span />;
@@ -1693,14 +2030,17 @@ describe('ReactDOMComponent', () => {
2030 }
2031 }
2032
1696 - const container = document.createElement('div');
1697 - ReactDOM.render(
1698 - <div>
1699 - <Inner />
1700 - </div>,
1701 - container,
1702 - );
1703 - ReactDOM.unmountComponentAtNode(container);
2033 + const root = ReactDOMClient.createRoot(document.createElement('div'));
2034 + await act(() => {
2035 + root.render(
2036 + <div>
2037 + <Inner />
2038 + </div>,
2039 + );
2040 + });
2041 + await act(() => {
2042 + root.unmount();
2043 + });
2044 });
2045 });
2046
@@ -1747,16 +2087,18 @@ describe('ReactDOMComponent', () => {
2087 ]);
2088 });
2089
1750 - it('warns on invalid nesting at root', () => {
2090 + it('warns on invalid nesting at root', async () => {
2091 const p = document.createElement('p');
1752 -
1753 - expect(() => {
1754 - ReactDOM.render(
1755 - <span>
1756 - <p />
1757 - </span>,
1758 - p,
1759 - );
2092 + const root = ReactDOMClient.createRoot(p);
2093 +
2094 + await expect(async () => {
2095 + await act(() => {
2096 + root.render(
2097 + <span>
2098 + <p />
2099 + </span>,
2100 + );
2101 + });
2102 }).toErrorDev(
2103 'Warning: validateDOMNesting(...): <p> cannot appear as a descendant ' +
2104 'of <p>.' +
@@ -1805,8 +2147,8 @@ describe('ReactDOMComponent', () => {
2147 ]);
2148 });
2149
1808 - it('warns nicely for updating table rows to use text', () => {
1809 - const container = document.createElement('div');
2150 + it('warns nicely for updating table rows to use text', async () => {
2151 + const root = ReactDOMClient.createRoot(document.createElement('div'));
2152
2153 function Row({children}) {
2154 return <tr>{children}</tr>;
@@ -1817,9 +2159,15 @@ describe('ReactDOMComponent', () => {
2159 }
2160
2161 // First is fine.
1820 - ReactDOM.render(<Foo />, container);
2162 + await act(() => {
2163 + root.render(<Foo />);
2164 + });
2165
1822 - expect(() => ReactDOM.render(<Foo> </Foo>, container)).toErrorDev([
2166 + await expect(async () => {
2167 + await act(() => {
2168 + root.render(<Foo> </Foo>);
2169 + });
2170 + }).toErrorDev([
2171 'Warning: validateDOMNesting(...): Whitespace text nodes cannot ' +
2172 "appear as a child of <table>. Make sure you don't have any extra " +
2173 'whitespace between tags on each line of your source code.' +
@@ -1827,25 +2175,27 @@ describe('ReactDOMComponent', () => {
2175 '\n in Foo (at **)',
2176 ]);
2177
1830 - ReactDOM.render(
1831 - <Foo>
1832 - <tbody>
1833 - <Row />
1834 - </tbody>
1835 - </Foo>,
1836 - container,
1837 - );
1838 -
1839 - expect(() =>
1840 - ReactDOM.render(
2178 + await act(() => {
2179 + root.render(
2180 <Foo>
2181 <tbody>
1843 - <Row>text</Row>
2182 + <Row />
2183 </tbody>
2184 </Foo>,
1846 - container,
1847 - ),
1848 - ).toErrorDev([
2185 + );
2186 + });
2187 +
2188 + await expect(async () => {
2189 + await act(() => {
2190 + root.render(
2191 + <Foo>
2192 + <tbody>
2193 + <Row>text</Row>
2194 + </tbody>
2195 + </Foo>,
2196 + );
2197 + });
2198 + }).toErrorDev([
2199 'Warning: validateDOMNesting(...): Text nodes cannot appear as a ' +
2200 'child of <tr>.' +
2201 '\n in tr (at **)' +
@@ -2369,24 +2719,33 @@ describe('ReactDOMComponent', () => {
2719 });
2720
2721 describe('whitespace', () => {
2372 - it('renders innerHTML and preserves whitespace', () => {
2722 + it('renders innerHTML and preserves whitespace', async () => {
2723 const container = document.createElement('div');
2724 + const root = ReactDOMClient.createRoot(container);
2725 +
2726 const html = '\n \t <span> \n testContent \t </span> \n \t';
2727 const elem = <div dangerouslySetInnerHTML={{__html: html}} />;
2728
2377 - ReactDOM.render(elem, container);
2729 + await act(() => {
2730 + root.render(elem);
2731 + });
2732 expect(container.firstChild.innerHTML).toBe(html);
2733 });
2734
2381 - it('render and then updates innerHTML and preserves whitespace', () => {
2735 + it('render and then updates innerHTML and preserves whitespace', async () => {
2736 const container = document.createElement('div');
2737 + const root = ReactDOMClient.createRoot(container);
2738 const html = '\n \t <span> \n testContent1 \t </span> \n \t';
2739 const elem = <div dangerouslySetInnerHTML={{__html: html}} />;
2385 - ReactDOM.render(elem, container);
2740 + await act(() => {
2741 + root.render(elem);
2742 + });
2743
2744 const html2 = '\n \t <div> \n testContent2 \t </div> \n \t';
2745 const elem2 = <div dangerouslySetInnerHTML={{__html: html2}} />;
2389 - ReactDOM.render(elem2, container);
2746 + await act(() => {
2747 + root.render(elem2);
2748 + });
2749
2750 expect(container.firstChild.innerHTML).toBe(html2);
2751 });
@@ -2447,29 +2806,39 @@ describe('ReactDOMComponent', () => {
2806 expect(el.getAttribute('class')).toBe('test');
2807 });
2808
2450 - it('updates aliased attributes on custom elements', function () {
2809 + it('updates aliased attributes on custom elements', async () => {
2810 const container = document.createElement('div');
2452 - ReactDOM.render(<div is="custom-element" class="foo" />, container);
2453 - ReactDOM.render(<div is="custom-element" class="bar" />, container);
2811 + const root = ReactDOMClient.createRoot(container);
2812 + await act(() => {
2813 + root.render(<div is="custom-element" class="foo" />);
2814 + });
2815 + await act(() => {
2816 + root.render(<div is="custom-element" class="bar" />);
2817 + });
2818
2819 expect(container.firstChild.getAttribute('class')).toBe('bar');
2820 });
2821 });
2822
2823 describe('Custom attributes', function () {
2460 - it('allows assignment of custom attributes with string values', function () {
2824 + it('allows assignment of custom attributes with string values', () => {
2825 const el = ReactTestUtils.renderIntoDocument(<div whatever="30" />);
2826
2827 expect(el.getAttribute('whatever')).toBe('30');
2828 });
2829
2466 - it('removes custom attributes', function () {
2830 + it('removes custom attributes', async () => {
2831 const container = document.createElement('div');
2468 - ReactDOM.render(<div whatever="30" />, container);
2832 + const root = ReactDOMClient.createRoot(container);
2833 + await act(() => {
2834 + root.render(<div whatever="30" />);
2835 + });
2836
2837 expect(container.firstChild.getAttribute('whatever')).toBe('30');
2838
2472 - ReactDOM.render(<div whatever={null} />, container);
2839 + await act(() => {
2840 + root.render(<div whatever={null} />);
2841 + });
2842
2843 expect(container.firstChild.hasAttribute('whatever')).toBe(false);
2844 });
@@ -2521,7 +2890,7 @@ describe('ReactDOMComponent', () => {
2890 expect(el.getAttribute('whatever')).toBe('[object Object]');
2891 });
2892
2524 - it('allows Temporal-like objects as HTML (they are not coerced to strings first)', function () {
2893 + it('allows Temporal-like objects as HTML (they are not coerced to strings first)', async () => {
2894 class TemporalLike {
2895 valueOf() {
2896 // Throwing here is the behavior of ECMAScript "Temporal" date/time API.
@@ -2536,14 +2905,16 @@ describe('ReactDOMComponent', () => {
2905 // `dangerouslySetInnerHTML` is never coerced to a string, so won't throw
2906 // even with a Temporal-like object.
2907 const container = document.createElement('div');
2539 - ReactDOM.render(
2540 - <div dangerouslySetInnerHTML={{__html: new TemporalLike()}} />,
2541 - container,
2542 - );
2908 + const root = ReactDOMClient.createRoot(container);
2909 + await act(() => {
2910 + root.render(
2911 + <div dangerouslySetInnerHTML={{__html: new TemporalLike()}} />,
2912 + );
2913 + });
2914 expect(container.firstChild.innerHTML).toEqual('2020-01-01');
2915 });
2916
2546 - it('allows cased data attributes', function () {
2917 + it('allows cased data attributes', () => {
2918 let el;
2919 expect(() => {
2920 el = ReactTestUtils.renderIntoDocument(<div data-fooBar="true" />);
@@ -2558,7 +2929,7 @@ describe('ReactDOMComponent', () => {
2929 expect(el.getAttribute('data-foobar')).toBe('true');
2930 });
2931
2561 - it('allows cased custom attributes', function () {
2932 + it('allows cased custom attributes', () => {
2933 let el;
2934 expect(() => {
2935 el = ReactTestUtils.renderIntoDocument(<div fooBar="true" />);
@@ -2573,7 +2944,7 @@ describe('ReactDOMComponent', () => {
2944 expect(el.getAttribute('foobar')).toBe('true');
2945 });
2946
2576 - it('warns on NaN attributes', function () {
2947 + it('warns on NaN attributes', () => {
2948 let el;
2949 expect(() => {
2950 el = ReactTestUtils.renderIntoDocument(<div whatever={NaN} />);
@@ -2585,12 +2956,17 @@ describe('ReactDOMComponent', () => {
2956 expect(el.getAttribute('whatever')).toBe('NaN');
2957 });
2958
2588 - it('removes a property when it becomes invalid', function () {
2959 + it('removes a property when it becomes invalid', async () => {
2960 const container = document.createElement('div');
2590 - ReactDOM.render(<div whatever={0} />, container);
2591 - expect(() =>
2592 - ReactDOM.render(<div whatever={() => {}} />, container),
2593 - ).toErrorDev('Warning: Invalid value for prop `whatever` on <div> tag.');
2961 + const root = ReactDOMClient.createRoot(container);
2962 + await act(() => {
2963 + root.render(<div whatever={0} />);
2964 + });
2965 + await expect(async () => {
2966 + await act(() => {
2967 + root.render(<div whatever={() => {}} />);
2968 + });
2969 + }).toErrorDev('Warning: Invalid value for prop `whatever` on <div> tag.');
2970 const el = container.firstChild;
2971 expect(el.hasAttribute('whatever')).toBe(false);
2972 });
@@ -2613,39 +2989,52 @@ describe('ReactDOMComponent', () => {
2989 expect(el.getAttribute('accept-charset')).toBe('[object Object]');
2990 });
2991
2616 - it('should pass objects as attributes if they define toString', () => {
2992 + it('should pass objects as attributes if they define toString', async () => {
2993 const obj = {
2994 toString() {
2995 return 'hello';
2996 },
2997 };
2998 const container = document.createElement('div');
2999 + const root = ReactDOMClient.createRoot(container);
3000
2624 - ReactDOM.render(<img src={obj} />, container);
3001 + await act(() => {
3002 + root.render(<img src={obj} />);
3003 + });
3004 expect(container.firstChild.src).toBe('http://localhost/hello');
3005
2627 - ReactDOM.render(<svg arabicForm={obj} />, container);
3006 + await act(() => {
3007 + root.render(<svg arabicForm={obj} />);
3008 + });
3009 expect(container.firstChild.getAttribute('arabic-form')).toBe('hello');
3010
2630 - ReactDOM.render(<div unknown={obj} />, container);
3011 + await act(() => {
3012 + root.render(<div unknown={obj} />);
3013 + });
3014 expect(container.firstChild.getAttribute('unknown')).toBe('hello');
3015 });
3016
2634 - it('passes objects on known SVG attributes if they do not define toString', () => {
3017 + it('passes objects on known SVG attributes if they do not define toString', async () => {
3018 const obj = {};
3019 const container = document.createElement('div');
3020 + const root = ReactDOMClient.createRoot(container);
3021
2638 - ReactDOM.render(<svg arabicForm={obj} />, container);
3022 + await act(() => {
3023 + root.render(<svg arabicForm={obj} />);
3024 + });
3025 expect(container.firstChild.getAttribute('arabic-form')).toBe(
3026 '[object Object]',
3027 );
3028 });
3029
2644 - it('passes objects on custom attributes if they do not define toString', () => {
3030 + it('passes objects on custom attributes if they do not define toString', async () => {
3031 const obj = {};
3032 const container = document.createElement('div');
3033 + const root = ReactDOMClient.createRoot(container);
3034
2648 - ReactDOM.render(<div unknown={obj} />, container);
3035 + await act(() => {
3036 + root.render(<div unknown={obj} />);
3037 + });
3038 expect(container.firstChild.getAttribute('unknown')).toBe(
3039 '[object Object]',
3040 );
@@ -2772,38 +3161,56 @@ describe('ReactDOMComponent', () => {
3161 // These tests mostly verify the existing behavior.
3162 // It may not always makes sense but we can't change it in minors.
3163 describe('Custom elements', () => {
2775 - it('does not strip unknown boolean attributes', () => {
3164 + it('does not strip unknown boolean attributes', async () => {
3165 const container = document.createElement('div');
2777 - ReactDOM.render(<some-custom-element foo={true} />, container);
3166 + const root = ReactDOMClient.createRoot(container);
3167 + await act(() => {
3168 + root.render(<some-custom-element foo={true} />);
3169 + });
3170 const node = container.firstChild;
3171 expect(node.getAttribute('foo')).toBe(
3172 ReactFeatureFlags.enableCustomElementPropertySupport ? '' : 'true',
3173 );
2782 - ReactDOM.render(<some-custom-element foo={false} />, container);
3174 + await act(() => {
3175 + root.render(<some-custom-element foo={false} />);
3176 + });
3177 expect(node.getAttribute('foo')).toBe(
3178 ReactFeatureFlags.enableCustomElementPropertySupport ? null : 'false',
3179 );
2786 - ReactDOM.render(<some-custom-element />, container);
3180 + await act(() => {
3181 + root.render(<some-custom-element />);
3182 + });
3183 expect(node.hasAttribute('foo')).toBe(false);
2788 - ReactDOM.render(<some-custom-element foo={true} />, container);
3184 + await act(() => {
3185 + root.render(<some-custom-element foo={true} />);
3186 + });
3187 expect(node.hasAttribute('foo')).toBe(true);
3188 });
3189
2792 - it('does not strip the on* attributes', () => {
3190 + it('does not strip the on* attributes', async () => {
3191 const container = document.createElement('div');
2794 - ReactDOM.render(<some-custom-element onx="bar" />, container);
3192 + const root = ReactDOMClient.createRoot(container);
3193 + await act(() => {
3194 + root.render(<some-custom-element onx="bar" />);
3195 + });
3196 const node = container.firstChild;
3197 expect(node.getAttribute('onx')).toBe('bar');
2797 - ReactDOM.render(<some-custom-element onx="buzz" />, container);
3198 + await act(() => {
3199 + root.render(<some-custom-element onx="buzz" />);
3200 + });
3201 expect(node.getAttribute('onx')).toBe('buzz');
2799 - ReactDOM.render(<some-custom-element />, container);
3202 + await act(() => {
3203 + root.render(<some-custom-element />);
3204 + });
3205 expect(node.hasAttribute('onx')).toBe(false);
2801 - ReactDOM.render(<some-custom-element onx="bar" />, container);
3206 + await act(() => {
3207 + root.render(<some-custom-element onx="bar" />);
3208 + });
3209 expect(node.getAttribute('onx')).toBe('bar');
3210 });
3211 });
3212
2806 - it('receives events in specific order', () => {
3213 + it('receives events in specific order', async () => {
3214 const eventOrder = [];
3215 const track = tag => () => eventOrder.push(tag);
3216 const outerRef = React.createRef();
@@ -2830,11 +3237,17 @@ describe('ReactDOMComponent', () => {
3237 }
3238
3239 const container = document.createElement('div');
3240 + const root = ReactDOMClient.createRoot(container);
3241 document.body.appendChild(container);
3242
3243 try {
2836 - ReactDOM.render(<OuterReactApp />, container);
2837 - ReactDOM.render(<InnerReactApp />, outerRef.current);
3244 + await act(() => {
3245 + root.render(<OuterReactApp />);
3246 + });
3247 + const innerRoot = ReactDOMClient.createRoot(outerRef.current);
3248 + await act(() => {
3249 + innerRoot.render(<InnerReactApp />);
3250 + });
3251
3252 document.addEventListener('click', track('document bubble'));
3253 document.addEventListener('click', track('document capture'), true);
@@ -2868,20 +3281,24 @@ describe('ReactDOMComponent', () => {
3281 });
3282
3283 describe('iOS Tap Highlight', () => {
2871 - it('adds onclick handler to elements with onClick prop', () => {
3284 + it('adds onclick handler to elements with onClick prop', async () => {
3285 const container = document.createElement('div');
3286 + const root = ReactDOMClient.createRoot(container);
3287
3288 const elementRef = React.createRef();
3289 function Component() {
3290 return <div ref={elementRef} onClick={() => {}} />;
3291 }
3292
2879 - ReactDOM.render(<Component />, container);
3293 + await act(() => {
3294 + root.render(<Component />);
3295 + });
3296 expect(typeof elementRef.current.onclick).toBe('function');
3297 });
3298
2883 - it('adds onclick handler to a portal root', () => {
3299 + it('adds onclick handler to a portal root', async () => {
3300 const container = document.createElement('div');
3301 + const root = ReactDOMClient.createRoot(container);
3302 const portalContainer = document.createElement('div');
3303
3304 function Component() {
@@ -2891,7 +3308,9 @@ describe('ReactDOMComponent', () => {
3308 );
3309 }
3310
2894 - ReactDOM.render(<Component />, container);
3311 + await act(() => {
3312 + root.render(<Component />);
3313 + });
3314 expect(typeof portalContainer.onclick).toBe('function');
3315 });
3316