Convert ReactComponent to createRoot (#28171)
Sebastian Silbermann committed
Feb 2, 2024 at 00:50 UTC
44952dc9840d335fe76bf1501919ec36f54591e9
1 file changed
+185
-82
packages/react-dom/src/__tests__/ReactComponent-test.js
+185
-82
@@ -11,8 +11,9 @@
11
12
let React;
13
let ReactDOM;
14
+let ReactDOMClient;
15
let ReactDOMServer;
15
-let ReactTestUtils;
16
+let act;
17
18
describe('ReactComponent', () => {
19
beforeEach(() => {
@@ -20,11 +21,12 @@ describe('ReactComponent', () => {
21
22
React = require('react');
23
ReactDOM = require('react-dom');
24
+ ReactDOMClient = require('react-dom/client');
25
ReactDOMServer = require('react-dom/server');
24
- ReactTestUtils = require('react-dom/test-utils');
26
+ act = require('internal-test-utils').act;
27
});
28
27
- it('should throw on invalid render targets', () => {
29
+ it('should throw on invalid render targets in legacy roots', () => {
30
const container = document.createElement('div');
31
// jQuery objects are basically arrays; people often pass them in by mistake
32
expect(function () {
@@ -36,40 +38,52 @@ describe('ReactComponent', () => {
38
}).toThrowError(/Target container is not a DOM element./);
39
});
40
39
- it('should throw when supplying a string ref outside of render method', () => {
40
- let instance = <div ref="badDiv" />;
41
- expect(function () {
42
- instance = ReactTestUtils.renderIntoDocument(instance);
43
- }).toThrow();
41
+ it('should throw when supplying a string ref outside of render method', async () => {
42
+ const container = document.createElement('div');
43
+ const root = ReactDOMClient.createRoot(container);
44
+ await expect(
45
+ act(() => {
46
+ root.render(<div ref="badDiv" />);
47
+ }),
48
+ ).rejects.toThrow();
49
});
50
46
- it('should throw (in dev) when children are mutated during render', () => {
51
+ it('should throw (in dev) when children are mutated during render', async () => {
52
function Wrapper(props) {
53
props.children[1] = <p key={1} />; // Mutation is illegal
54
return <div>{props.children}</div>;
55
}
56
if (__DEV__) {
52
- expect(() => {
53
- ReactTestUtils.renderIntoDocument(
57
+ const container = document.createElement('div');
58
+ const root = ReactDOMClient.createRoot(container);
59
+ await expect(
60
+ act(() => {
61
+ root.render(
62
+ <Wrapper>
63
+ <span key={0} />
64
+ <span key={1} />
65
+ <span key={2} />
66
+ </Wrapper>,
67
+ );
68
+ }),
69
+ ).rejects.toThrowError(/Cannot assign to read only property.*/);
70
+ } else {
71
+ const container = document.createElement('div');
72
+ const root = ReactDOMClient.createRoot(container);
73
+
74
+ await act(() => {
75
+ root.render(
76
<Wrapper>
77
<span key={0} />
78
<span key={1} />
79
<span key={2} />
80
</Wrapper>,
81
);
60
- }).toThrowError(/Cannot assign to read only property.*/);
61
- } else {
62
- ReactTestUtils.renderIntoDocument(
63
- <Wrapper>
64
- <span key={0} />
65
- <span key={1} />
66
- <span key={2} />
67
- </Wrapper>,
68
- );
82
+ });
83
}
84
});
85
72
- it('should throw (in dev) when children are mutated during update', () => {
86
+ it('should throw (in dev) when children are mutated during update', async () => {
87
class Wrapper extends React.Component {
88
componentDidMount() {
89
this.props.children[1] = <p key={1} />; // Mutation is illegal
@@ -82,27 +96,36 @@ describe('ReactComponent', () => {
96
}
97
98
if (__DEV__) {
85
- expect(() => {
86
- ReactTestUtils.renderIntoDocument(
99
+ const container = document.createElement('div');
100
+ const root = ReactDOMClient.createRoot(container);
101
+ await expect(
102
+ act(() => {
103
+ root.render(
104
+ <Wrapper>
105
+ <span key={0} />
106
+ <span key={1} />
107
+ <span key={2} />
108
+ </Wrapper>,
109
+ );
110
+ }),
111
+ ).rejects.toThrowError(/Cannot assign to read only property.*/);
112
+ } else {
113
+ const container = document.createElement('div');
114
+ const root = ReactDOMClient.createRoot(container);
115
+
116
+ await act(() => {
117
+ root.render(
118
<Wrapper>
119
<span key={0} />
120
<span key={1} />
121
<span key={2} />
122
</Wrapper>,
123
);
93
- }).toThrowError(/Cannot assign to read only property.*/);
94
- } else {
95
- ReactTestUtils.renderIntoDocument(
96
- <Wrapper>
97
- <span key={0} />
98
- <span key={1} />
99
- <span key={2} />
100
- </Wrapper>,
101
- );
124
+ });
125
}
126
});
127
105
- it('should support string refs on owned components', () => {
128
+ it('should support string refs on owned components', async () => {
129
const innerObj = {};
130
const outerObj = {};
131
@@ -133,8 +156,12 @@ describe('ReactComponent', () => {
156
}
157
}
158
136
- expect(() => {
137
- ReactTestUtils.renderIntoDocument(<Component />);
159
+ const container = document.createElement('div');
160
+ const root = ReactDOMClient.createRoot(container);
161
+ await expect(async () => {
162
+ await act(() => {
163
+ root.render(<Component />);
164
+ });
165
}).toErrorDev([
166
'Warning: Component "div" contains the string ref "inner". ' +
167
'Support for string refs will be removed in a future major release. ' +
@@ -151,7 +178,7 @@ describe('ReactComponent', () => {
178
]);
179
});
180
154
- it('should not have string refs on unmounted components', () => {
181
+ it('should not have string refs on unmounted components', async () => {
182
class Parent extends React.Component {
183
render() {
184
return (
@@ -172,10 +199,14 @@ describe('ReactComponent', () => {
199
}
200
}
201
175
- ReactTestUtils.renderIntoDocument(<Parent child={<span />} />);
202
+ const container = document.createElement('div');
203
+ const root = ReactDOMClient.createRoot(container);
204
+ await act(() => {
205
+ root.render(<Parent child={<span />} />);
206
+ });
207
});
208
178
- it('should support callback-style refs', () => {
209
+ it('should support callback-style refs', async () => {
210
const innerObj = {};
211
const outerObj = {};
212
@@ -211,11 +242,16 @@ describe('ReactComponent', () => {
242
}
243
}
244
214
- ReactTestUtils.renderIntoDocument(<Component />);
245
+ const container = document.createElement('div');
246
+ const root = ReactDOMClient.createRoot(container);
247
+ await act(() => {
248
+ root.render(<Component />);
249
+ });
250
+
251
expect(mounted).toBe(true);
252
});
253
218
- it('should support object-style refs', () => {
254
+ it('should support object-style refs', async () => {
255
const innerObj = {};
256
const outerObj = {};
257
@@ -254,11 +290,16 @@ describe('ReactComponent', () => {
290
}
291
}
292
257
- ReactTestUtils.renderIntoDocument(<Component />);
293
+ const container = document.createElement('div');
294
+ const root = ReactDOMClient.createRoot(container);
295
+ await act(() => {
296
+ root.render(<Component />);
297
+ });
298
+
299
expect(mounted).toBe(true);
300
});
301
261
- it('should support new-style refs with mixed-up owners', () => {
302
+ it('should support new-style refs with mixed-up owners', async () => {
303
class Wrapper extends React.Component {
304
getTitle = () => {
305
return this.props.title;
@@ -296,11 +337,17 @@ describe('ReactComponent', () => {
337
}
338
}
339
299
- ReactTestUtils.renderIntoDocument(<Component />);
340
+ const container = document.createElement('div');
341
+ const root = ReactDOMClient.createRoot(container);
342
+
343
+ await act(() => {
344
+ root.render(<Component />);
345
+ });
346
+
347
expect(mounted).toBe(true);
348
});
349
303
- it('should call refs at the correct time', () => {
350
+ it('should call refs at the correct time', async () => {
351
const log = [];
352
353
class Inner extends React.Component {
@@ -358,11 +405,18 @@ describe('ReactComponent', () => {
405
// mount, update, unmount
406
const el = document.createElement('div');
407
log.push('start mount');
361
- ReactDOM.render(<Outer />, el);
408
+ const root = ReactDOMClient.createRoot(el);
409
+ await act(() => {
410
+ root.render(<Outer />);
411
+ });
412
log.push('start update');
363
- ReactDOM.render(<Outer />, el);
413
+ await act(() => {
414
+ root.render(<Outer />);
415
+ });
416
log.push('start unmount');
365
- ReactDOM.unmountComponentAtNode(el);
417
+ await act(() => {
418
+ root.unmount();
419
+ });
420
421
/* eslint-disable indent */
422
expect(log).toEqual([
@@ -396,7 +450,7 @@ describe('ReactComponent', () => {
450
/* eslint-enable indent */
451
});
452
399
- it('fires the callback after a component is rendered', () => {
453
+ it('fires the callback after a component is rendered in legacy roots', () => {
454
const callback = jest.fn();
455
const container = document.createElement('div');
456
ReactDOM.render(<div />, container, callback);
@@ -407,14 +461,20 @@ describe('ReactComponent', () => {
461
expect(callback).toHaveBeenCalledTimes(3);
462
});
463
410
- it('throws usefully when rendering badly-typed elements', () => {
464
+ it('throws usefully when rendering badly-typed elements', async () => {
465
const X = undefined;
412
- expect(() => {
413
- expect(() => ReactTestUtils.renderIntoDocument(<X />)).toErrorDev(
466
+ let container = document.createElement('div');
467
+ let root = ReactDOMClient.createRoot(container);
468
+ await expect(
469
+ expect(async () => {
470
+ await act(() => {
471
+ root.render(<X />);
472
+ });
473
+ }).toErrorDev(
474
'React.createElement: type is invalid -- expected a string (for built-in components) ' +
475
'or a class/function (for composite components) but got: undefined.',
416
- );
417
- }).toThrowError(
476
+ ),
477
+ ).rejects.toThrowError(
478
'Element type is invalid: expected a string (for built-in components) ' +
479
'or a class/function (for composite components) but got: undefined.' +
480
(__DEV__
@@ -424,18 +484,24 @@ describe('ReactComponent', () => {
484
);
485
486
const Y = null;
427
- expect(() => {
428
- expect(() => ReactTestUtils.renderIntoDocument(<Y />)).toErrorDev(
487
+ container = document.createElement('div');
488
+ root = ReactDOMClient.createRoot(container);
489
+ await expect(
490
+ expect(async () => {
491
+ await act(() => {
492
+ root.render(<Y />);
493
+ });
494
+ }).toErrorDev(
495
'React.createElement: type is invalid -- expected a string (for built-in components) ' +
496
'or a class/function (for composite components) but got: null.',
431
- );
432
- }).toThrowError(
497
+ ),
498
+ ).rejects.toThrowError(
499
'Element type is invalid: expected a string (for built-in components) ' +
500
'or a class/function (for composite components) but got: null.',
501
);
502
});
503
438
- it('includes owner name in the error about badly-typed elements', () => {
504
+ it('includes owner name in the error about badly-typed elements', async () => {
505
const X = undefined;
506
507
function Indirection(props) {
@@ -454,12 +520,18 @@ describe('ReactComponent', () => {
520
return <Bar />;
521
}
522
457
- expect(() => {
458
- expect(() => ReactTestUtils.renderIntoDocument(<Foo />)).toErrorDev(
523
+ const container = document.createElement('div');
524
+ const root = ReactDOMClient.createRoot(container);
525
+ await expect(
526
+ expect(async () => {
527
+ await act(() => {
528
+ root.render(<Foo />);
529
+ });
530
+ }).toErrorDev(
531
'React.createElement: type is invalid -- expected a string (for built-in components) ' +
532
'or a class/function (for composite components) but got: undefined.',
461
- );
462
- }).toThrowError(
533
+ ),
534
+ ).rejects.toThrowError(
535
'Element type is invalid: expected a string (for built-in components) ' +
536
'or a class/function (for composite components) but got: undefined.' +
537
(__DEV__
@@ -470,7 +542,7 @@ describe('ReactComponent', () => {
542
);
543
});
544
473
- it('throws if a plain object is used as a child', () => {
545
+ it('throws if a plain object is used as a child', async () => {
546
const children = {
547
x: <span />,
548
y: <span />,
@@ -478,15 +550,18 @@ describe('ReactComponent', () => {
550
};
551
const element = <div>{[children]}</div>;
552
const container = document.createElement('div');
481
- expect(() => {
482
- ReactDOM.render(element, container);
483
- }).toThrowError(
553
+ const root = ReactDOMClient.createRoot(container);
554
+ await expect(
555
+ act(() => {
556
+ root.render(element);
557
+ }),
558
+ ).rejects.toThrowError(
559
'Objects are not valid as a React child (found: object with keys {x, y, z}). ' +
560
'If you meant to render a collection of children, use an array instead.',
561
);
562
});
563
489
- it('throws if a plain object even if it is in an owner', () => {
564
+ it('throws if a plain object even if it is in an owner', async () => {
565
class Foo extends React.Component {
566
render() {
567
const children = {
@@ -498,9 +573,12 @@ describe('ReactComponent', () => {
573
}
574
}
575
const container = document.createElement('div');
501
- expect(() => {
502
- ReactDOM.render(<Foo />, container);
503
- }).toThrowError(
576
+ const root = ReactDOMClient.createRoot(container);
577
+ await expect(
578
+ act(() => {
579
+ root.render(<Foo />);
580
+ }),
581
+ ).rejects.toThrowError(
582
'Objects are not valid as a React child (found: object with keys {a, b, c}).' +
583
' If you meant to render a collection of children, use an array ' +
584
'instead.',
@@ -545,12 +623,17 @@ describe('ReactComponent', () => {
623
});
624
625
describe('with new features', () => {
548
- it('warns on function as a return value from a function', () => {
626
+ it('warns on function as a return value from a function', async () => {
627
function Foo() {
628
return Foo;
629
}
630
const container = document.createElement('div');
553
- expect(() => ReactDOM.render(<Foo />, container)).toErrorDev(
631
+ const root = ReactDOMClient.createRoot(container);
632
+ await expect(async () => {
633
+ await act(() => {
634
+ root.render(<Foo />);
635
+ });
636
+ }).toErrorDev(
637
'Warning: Functions are not valid as a React child. This may happen if ' +
638
'you return a Component instead of <Component /> from render. ' +
639
'Or maybe you meant to call this function rather than return it.\n' +
@@ -558,14 +641,20 @@ describe('ReactComponent', () => {
641
);
642
});
643
561
- it('warns on function as a return value from a class', () => {
644
+ it('warns on function as a return value from a class', async () => {
645
class Foo extends React.Component {
646
render() {
647
return Foo;
648
}
649
}
650
const container = document.createElement('div');
568
- expect(() => ReactDOM.render(<Foo />, container)).toErrorDev(
651
+ await expect(async () => {
652
+ const root = ReactDOMClient.createRoot(container);
653
+
654
+ await act(() => {
655
+ root.render(<Foo />);
656
+ });
657
+ }).toErrorDev(
658
'Warning: Functions are not valid as a React child. This may happen if ' +
659
'you return a Component instead of <Component /> from render. ' +
660
'Or maybe you meant to call this function rather than return it.\n' +
@@ -573,7 +662,7 @@ describe('ReactComponent', () => {
662
);
663
});
664
576
- it('warns on function as a child to host component', () => {
665
+ it('warns on function as a child to host component', async () => {
666
function Foo() {
667
return (
668
<div>
@@ -582,7 +671,12 @@ describe('ReactComponent', () => {
671
);
672
}
673
const container = document.createElement('div');
585
- expect(() => ReactDOM.render(<Foo />, container)).toErrorDev(
674
+ const root = ReactDOMClient.createRoot(container);
675
+ await expect(async () => {
676
+ await act(() => {
677
+ root.render(<Foo />);
678
+ });
679
+ }).toErrorDev(
680
'Warning: Functions are not valid as a React child. This may happen if ' +
681
'you return a Component instead of <Component /> from render. ' +
682
'Or maybe you meant to call this function rather than return it.\n' +
@@ -592,7 +686,7 @@ describe('ReactComponent', () => {
686
);
687
});
688
595
- it('does not warn for function-as-a-child that gets resolved', () => {
689
+ it('does not warn for function-as-a-child that gets resolved', async () => {
690
function Bar(props) {
691
return props.children();
692
}
@@ -600,11 +694,15 @@ describe('ReactComponent', () => {
694
return <Bar>{() => 'Hello'}</Bar>;
695
}
696
const container = document.createElement('div');
603
- ReactDOM.render(<Foo />, container);
697
+ const root = ReactDOMClient.createRoot(container);
698
+ await act(() => {
699
+ root.render(<Foo />);
700
+ });
701
+
702
expect(container.innerHTML).toBe('Hello');
703
});
704
607
- it('deduplicates function type warnings based on component type', () => {
705
+ it('deduplicates function type warnings based on component type', async () => {
706
class Foo extends React.PureComponent {
707
constructor() {
708
super();
@@ -624,9 +722,12 @@ describe('ReactComponent', () => {
722
}
723
}
724
const container = document.createElement('div');
725
+ const root = ReactDOMClient.createRoot(container);
726
let component;
628
- expect(() => {
629
- component = ReactDOM.render(<Foo />, container);
727
+ await expect(async () => {
728
+ await act(() => {
729
+ root.render(<Foo ref={current => (component = current)} />);
730
+ });
731
}).toErrorDev([
732
'Warning: Functions are not valid as a React child. This may happen if ' +
733
'you return a Component instead of <Component /> from render. ' +
@@ -640,7 +741,9 @@ describe('ReactComponent', () => {
741
' in div (at **)\n' +
742
' in Foo (at **)',
743
]);
643
- component.setState({type: 'portobello mushrooms'});
744
+ await act(() => {
745
+ component.setState({type: 'portobello mushrooms'});
746
+ });
747
});
748
});
749
});