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