Use createRoot in ReactEmptyComponent-test (#28095)
Jack Pope committed
Jan 26, 2024 at 11:37 UTC
51c380d6ed303a54dad7b55258dd7aa8c6bb3fe1
1 file changed
+160
-132
packages/react-dom/src/__tests__/ReactEmptyComponent-test.js
+160
-132
@@ -11,10 +11,13 @@
11
12
let React;
13
let ReactDOM;
14
-let ReactTestUtils;
14
+let ReactDOMClient;
15
let TogglingComponent;
16
+let act;
17
+let Scheduler;
18
+let assertLog;
19
17
-let log;
20
+let container;
21
22
describe('ReactEmptyComponent', () => {
23
beforeEach(() => {
@@ -22,20 +25,24 @@ describe('ReactEmptyComponent', () => {
25
26
React = require('react');
27
ReactDOM = require('react-dom');
25
- ReactTestUtils = require('react-dom/test-utils');
28
+ ReactDOMClient = require('react-dom/client');
29
+ Scheduler = require('scheduler');
30
+ const InternalTestUtils = require('internal-test-utils');
31
+ act = InternalTestUtils.act;
32
+ assertLog = InternalTestUtils.assertLog;
33
27
- log = jest.fn();
34
+ container = document.createElement('div');
35
36
TogglingComponent = class extends React.Component {
37
state = {component: this.props.firstComponent};
38
39
componentDidMount() {
33
- log(ReactDOM.findDOMNode(this));
40
+ Scheduler.log('mount ' + ReactDOM.findDOMNode(this)?.nodeName);
41
this.setState({component: this.props.secondComponent});
42
}
43
44
componentDidUpdate() {
38
- log(ReactDOM.findDOMNode(this));
45
+ Scheduler.log('update ' + ReactDOM.findDOMNode(this)?.nodeName);
46
}
47
48
render() {
@@ -47,40 +54,44 @@ describe('ReactEmptyComponent', () => {
54
55
describe.each([null, undefined])('when %s', nullORUndefined => {
56
it('should not throw when rendering', () => {
50
- class Component extends React.Component {
51
- render() {
52
- return nullORUndefined;
53
- }
57
+ function EmptyComponent() {
58
+ return nullORUndefined;
59
}
60
56
- expect(function () {
57
- ReactTestUtils.renderIntoDocument(<Component />);
61
+ const root = ReactDOMClient.createRoot(container);
62
+
63
+ expect(() => {
64
+ ReactDOM.flushSync(() => {
65
+ root.render(<EmptyComponent />);
66
+ });
67
}).not.toThrowError();
68
});
69
61
- it('should not produce child DOM nodes for nullish and false', () => {
62
- class Component1 extends React.Component {
63
- render() {
64
- return nullORUndefined;
65
- }
70
+ it('should not produce child DOM nodes for nullish and false', async () => {
71
+ function Component1() {
72
+ return nullORUndefined;
73
}
74
68
- class Component2 extends React.Component {
69
- render() {
70
- return false;
71
- }
75
+ function Component2() {
76
+ return false;
77
}
78
79
const container1 = document.createElement('div');
75
- ReactDOM.render(<Component1 />, container1);
80
+ const root1 = ReactDOMClient.createRoot(container1);
81
+ await act(() => {
82
+ root1.render(<Component1 />);
83
+ });
84
expect(container1.children.length).toBe(0);
85
86
const container2 = document.createElement('div');
79
- ReactDOM.render(<Component2 />, container2);
87
+ const root2 = ReactDOMClient.createRoot(container2);
88
+ await act(() => {
89
+ root2.render(<Component2 />);
90
+ });
91
expect(container2.children.length).toBe(0);
92
});
93
83
- it('should be able to switch between rendering nullish and a normal tag', () => {
94
+ it('should be able to switch between rendering nullish and a normal tag', async () => {
95
const instance1 = (
96
<TogglingComponent
97
firstComponent={nullORUndefined}
@@ -94,23 +105,26 @@ describe('ReactEmptyComponent', () => {
105
/>
106
);
107
97
- ReactTestUtils.renderIntoDocument(instance1);
98
- ReactTestUtils.renderIntoDocument(instance2);
99
-
100
- expect(log).toHaveBeenCalledTimes(4);
101
- expect(log).toHaveBeenNthCalledWith(1, null);
102
- expect(log).toHaveBeenNthCalledWith(
103
- 2,
104
- expect.objectContaining({tagName: 'DIV'}),
105
- );
106
- expect(log).toHaveBeenNthCalledWith(
107
- 3,
108
- expect.objectContaining({tagName: 'DIV'}),
109
- );
110
- expect(log).toHaveBeenNthCalledWith(4, null);
108
+ const container2 = document.createElement('div');
109
+ const root1 = ReactDOMClient.createRoot(container);
110
+ await act(() => {
111
+ root1.render(instance1);
112
+ });
113
+
114
+ const root2 = ReactDOMClient.createRoot(container2);
115
+ await act(() => {
116
+ root2.render(instance2);
117
+ });
118
+
119
+ assertLog([
120
+ 'mount undefined',
121
+ 'update DIV',
122
+ 'mount DIV',
123
+ 'update undefined',
124
+ ]);
125
});
126
113
- it('should be able to switch in a list of children', () => {
127
+ it('should be able to switch in a list of children', async () => {
128
const instance1 = (
129
<TogglingComponent
130
firstComponent={nullORUndefined}
@@ -118,30 +132,25 @@ describe('ReactEmptyComponent', () => {
132
/>
133
);
134
121
- ReactTestUtils.renderIntoDocument(
122
- <div>
123
- {instance1}
124
- {instance1}
125
- {instance1}
126
- </div>,
127
- );
128
-
129
- expect(log).toHaveBeenCalledTimes(6);
130
- expect(log).toHaveBeenNthCalledWith(1, null);
131
- expect(log).toHaveBeenNthCalledWith(2, null);
132
- expect(log).toHaveBeenNthCalledWith(3, null);
133
- expect(log).toHaveBeenNthCalledWith(
134
- 4,
135
- expect.objectContaining({tagName: 'DIV'}),
136
- );
137
- expect(log).toHaveBeenNthCalledWith(
138
- 5,
139
- expect.objectContaining({tagName: 'DIV'}),
140
- );
141
- expect(log).toHaveBeenNthCalledWith(
142
- 6,
143
- expect.objectContaining({tagName: 'DIV'}),
144
- );
135
+ const root = ReactDOMClient.createRoot(container);
136
+ await act(() => {
137
+ root.render(
138
+ <div>
139
+ {instance1}
140
+ {instance1}
141
+ {instance1}
142
+ </div>,
143
+ );
144
+ });
145
+
146
+ assertLog([
147
+ 'mount undefined',
148
+ 'mount undefined',
149
+ 'mount undefined',
150
+ 'update DIV',
151
+ 'update DIV',
152
+ 'update DIV',
153
+ ]);
154
});
155
156
it('should distinguish between a script placeholder and an actual script tag', () => {
@@ -158,40 +167,39 @@ describe('ReactEmptyComponent', () => {
167
/>
168
);
169
161
- expect(function () {
162
- ReactTestUtils.renderIntoDocument(instance1);
170
+ const root1 = ReactDOMClient.createRoot(container);
171
+ expect(() => {
172
+ ReactDOM.flushSync(() => {
173
+ root1.render(instance1);
174
+ });
175
}).not.toThrow();
164
- expect(function () {
165
- ReactTestUtils.renderIntoDocument(instance2);
176
+
177
+ const container2 = document.createElement('div');
178
+ const root2 = ReactDOMClient.createRoot(container2);
179
+ expect(() => {
180
+ ReactDOM.flushSync(() => {
181
+ root2.render(instance2);
182
+ });
183
}).not.toThrow();
184
168
- expect(log).toHaveBeenCalledTimes(4);
169
- expect(log).toHaveBeenNthCalledWith(1, null);
170
- expect(log).toHaveBeenNthCalledWith(
171
- 2,
172
- expect.objectContaining({tagName: 'SCRIPT'}),
173
- );
174
- expect(log).toHaveBeenNthCalledWith(
175
- 3,
176
- expect.objectContaining({tagName: 'SCRIPT'}),
177
- );
178
- expect(log).toHaveBeenNthCalledWith(4, null);
185
+ assertLog([
186
+ 'mount undefined',
187
+ 'update SCRIPT',
188
+ 'mount SCRIPT',
189
+ 'update undefined',
190
+ ]);
191
});
192
193
it(
194
'should have findDOMNode return null when multiple layers of composite ' +
195
'components render to the same nullish placeholder',
196
() => {
185
- class GrandChild extends React.Component {
186
- render() {
187
- return nullORUndefined;
188
- }
197
+ function GrandChild() {
198
+ return nullORUndefined;
199
}
200
191
- class Child extends React.Component {
192
- render() {
193
- return <GrandChild />;
194
- }
201
+ function Child() {
202
+ return <GrandChild />;
203
}
204
205
const instance1 = (
@@ -201,29 +209,32 @@ describe('ReactEmptyComponent', () => {
209
<TogglingComponent firstComponent={Child} secondComponent={'div'} />
210
);
211
204
- expect(function () {
205
- ReactTestUtils.renderIntoDocument(instance1);
212
+ const root1 = ReactDOMClient.createRoot(container);
213
+ expect(() => {
214
+ ReactDOM.flushSync(() => {
215
+ root1.render(instance1);
216
+ });
217
}).not.toThrow();
207
- expect(function () {
208
- ReactTestUtils.renderIntoDocument(instance2);
218
+
219
+ const container2 = document.createElement('div');
220
+ const root2 = ReactDOMClient.createRoot(container2);
221
+ expect(() => {
222
+ ReactDOM.flushSync(() => {
223
+ root2.render(instance2);
224
+ });
225
}).not.toThrow();
226
211
- expect(log).toHaveBeenCalledTimes(4);
212
- expect(log).toHaveBeenNthCalledWith(
213
- 1,
214
- expect.objectContaining({tagName: 'DIV'}),
215
- );
216
- expect(log).toHaveBeenNthCalledWith(2, null);
217
- expect(log).toHaveBeenNthCalledWith(3, null);
218
- expect(log).toHaveBeenNthCalledWith(
219
- 4,
220
- expect.objectContaining({tagName: 'DIV'}),
221
- );
227
+ assertLog([
228
+ 'mount DIV',
229
+ 'update undefined',
230
+ 'mount undefined',
231
+ 'update DIV',
232
+ ]);
233
},
234
);
235
225
- it('works when switching components', () => {
226
- let assertions = 0;
236
+ it('works when switching components', async () => {
237
+ let innerRef;
238
239
class Inner extends React.Component {
240
render() {
@@ -234,44 +245,51 @@ describe('ReactEmptyComponent', () => {
245
// Make sure the DOM node resolves properly even if we're replacing a
246
// `null` component
247
expect(ReactDOM.findDOMNode(this)).not.toBe(null);
237
- assertions++;
248
}
249
250
componentWillUnmount() {
251
// Even though we're getting replaced by `null`, we haven't been
252
// replaced yet!
253
expect(ReactDOM.findDOMNode(this)).not.toBe(null);
244
- assertions++;
254
}
255
}
256
248
- class Wrapper extends React.Component {
249
- render() {
250
- return this.props.showInner ? <Inner /> : nullORUndefined;
251
- }
257
+ function Wrapper({showInner}) {
258
+ innerRef = React.createRef(null);
259
+ return showInner ? <Inner ref={innerRef} /> : nullORUndefined;
260
}
261
262
const el = document.createElement('div');
255
- let component;
263
264
// Render the <Inner /> component...
258
- component = ReactDOM.render(<Wrapper showInner={true} />, el);
259
- expect(ReactDOM.findDOMNode(component)).not.toBe(null);
265
+ const root = ReactDOMClient.createRoot(el);
266
+ await act(() => {
267
+ root.render(<Wrapper showInner={true} />);
268
+ });
269
+ expect(innerRef.current).not.toBe(null);
270
271
// Switch to null...
262
- component = ReactDOM.render(<Wrapper showInner={false} />, el);
263
- expect(ReactDOM.findDOMNode(component)).toBe(null);
272
+ await act(() => {
273
+ root.render(<Wrapper showInner={false} />);
274
+ });
275
+ expect(innerRef.current).toBe(null);
276
277
// ...then switch back.
266
- component = ReactDOM.render(<Wrapper showInner={true} />, el);
267
- expect(ReactDOM.findDOMNode(component)).not.toBe(null);
278
+ await act(() => {
279
+ root.render(<Wrapper showInner={true} />);
280
+ });
281
+ expect(innerRef.current).not.toBe(null);
282
269
- expect(assertions).toBe(3);
283
+ expect.assertions(6);
284
});
285
272
- it('can render nullish at the top level', () => {
286
+ it('can render nullish at the top level', async () => {
287
const div = document.createElement('div');
274
- ReactDOM.render(nullORUndefined, div);
288
+ const root = ReactDOMClient.createRoot(div);
289
+
290
+ await act(() => {
291
+ root.render(nullORUndefined);
292
+ });
293
expect(div.innerHTML).toBe('');
294
});
295
@@ -308,26 +326,30 @@ describe('ReactEmptyComponent', () => {
326
}
327
}
328
311
- expect(function () {
312
- ReactTestUtils.renderIntoDocument(<Parent />);
329
+ const root = ReactDOMClient.createRoot(container);
330
+ expect(() => {
331
+ ReactDOM.flushSync(() => {
332
+ root.render(<Parent />);
333
+ });
334
}).not.toThrow();
335
});
336
316
- it('preserves the dom node during updates', () => {
317
- class Empty extends React.Component {
318
- render() {
319
- return nullORUndefined;
320
- }
337
+ it('preserves the dom node during updates', async () => {
338
+ function Empty() {
339
+ return nullORUndefined;
340
}
341
323
- const container = document.createElement('div');
324
-
325
- ReactDOM.render(<Empty />, container);
342
+ const root = ReactDOMClient.createRoot(container);
343
+ await act(() => {
344
+ root.render(<Empty />);
345
+ });
346
const noscript1 = container.firstChild;
347
expect(noscript1).toBe(null);
348
349
// This update shouldn't create a DOM node
330
- ReactDOM.render(<Empty />, container);
350
+ await act(() => {
351
+ root.render(<Empty />);
352
+ });
353
const noscript2 = container.firstChild;
354
expect(noscript2).toBe(null);
355
});
@@ -338,8 +360,11 @@ describe('ReactEmptyComponent', () => {
360
};
361
const EmptyForwardRef = React.forwardRef(Empty);
362
363
+ const root = ReactDOMClient.createRoot(container);
364
expect(() => {
342
- ReactTestUtils.renderIntoDocument(<EmptyForwardRef />);
365
+ ReactDOM.flushSync(() => {
366
+ root.render(<EmptyForwardRef />);
367
+ });
368
}).not.toThrowError();
369
});
370
@@ -349,8 +374,11 @@ describe('ReactEmptyComponent', () => {
374
};
375
const EmptyMemo = React.memo(Empty);
376
377
+ const root = ReactDOMClient.createRoot(container);
378
expect(() => {
353
- ReactTestUtils.renderIntoDocument(<EmptyMemo />);
379
+ ReactDOM.flushSync(() => {
380
+ root.render(<EmptyMemo />);
381
+ });
382
}).not.toThrowError();
383
});
384
});