Convert ReactDOMTextComponent to createRoot (#28141)
Sebastian Silbermann committed
Jan 29, 2024 at 09:11 UTC
313e4d4129088b3ed99ab64d15dd8b6de175261b
1 file changed
+224
-174
packages/react-dom/src/__tests__/ReactDOMTextComponent-test.js
+224
-174
@@ -10,94 +10,99 @@
10
'use strict';
11
12
let React;
13
-let ReactDOM;
13
+let ReactDOMClient;
14
let ReactDOMServer;
15
-
16
-// In standard React, TextComponent keeps track of different Text templates
17
-// using comments. However, in React Fiber, those comments are not outputted due
18
-// to the way Fiber keeps track of the templates.
19
-// This function "Normalizes" childNodes lists to avoid the presence of comments
20
-// and make the child list identical in standard React and Fiber
21
-function filterOutComments(nodeList) {
22
- return [].slice.call(nodeList).filter(node => !(node instanceof Comment));
23
-}
15
+let act;
16
17
describe('ReactDOMTextComponent', () => {
18
beforeEach(() => {
19
React = require('react');
28
- ReactDOM = require('react-dom');
20
+ ReactDOMClient = require('react-dom/client');
21
ReactDOMServer = require('react-dom/server');
22
+ act = require('internal-test-utils').act;
23
});
24
32
- it('updates a mounted text component in place', () => {
33
- const el = document.createElement('div');
34
- let inst = ReactDOM.render(
35
- <div>
36
- <span />
37
- {'foo'}
38
- {'bar'}
39
- </div>,
40
- el,
41
- );
42
- let nodes = filterOutComments(inst.childNodes);
25
+ it('updates a mounted text component in place', async () => {
26
+ const container = document.createElement('div');
27
+ const root = ReactDOMClient.createRoot(container);
28
+ await act(() => {
29
+ root.render(
30
+ <div>
31
+ <span />
32
+ {'foo'}
33
+ {'bar'}
34
+ </div>,
35
+ );
36
+ });
37
+ let inst = container.firstChild;
38
+ let nodes = inst.childNodes;
39
40
const foo = nodes[1];
41
const bar = nodes[2];
42
expect(foo.data).toBe('foo');
43
expect(bar.data).toBe('bar');
44
49
- inst = ReactDOM.render(
50
- <div>
51
- <span />
52
- {'baz'}
53
- {'qux'}
54
- </div>,
55
- el,
56
- );
45
+ await act(() => {
46
+ root.render(
47
+ <div>
48
+ <span />
49
+ {'baz'}
50
+ {'qux'}
51
+ </div>,
52
+ );
53
+ });
54
+ inst = container.firstChild;
55
// After the update, the text nodes should have stayed in place (as opposed
56
// to getting unmounted and remounted)
59
- nodes = filterOutComments(inst.childNodes);
57
+ nodes = inst.childNodes;
58
expect(nodes[1]).toBe(foo);
59
expect(nodes[2]).toBe(bar);
60
expect(foo.data).toBe('baz');
61
expect(bar.data).toBe('qux');
62
});
63
66
- it('can be toggled in and out of the markup', () => {
67
- const el = document.createElement('div');
68
- let inst = ReactDOM.render(
69
- <div>
70
- {'foo'}
71
- <div />
72
- {'bar'}
73
- </div>,
74
- el,
75
- );
64
+ it('can be toggled in and out of the markup', async () => {
65
+ const container = document.createElement('div');
66
+ const root = ReactDOMClient.createRoot(container);
67
+ await act(() => {
68
+ root.render(
69
+ <div>
70
+ {'foo'}
71
+ <div />
72
+ {'bar'}
73
+ </div>,
74
+ );
75
+ });
76
+ let inst = container.firstChild;
77
77
- let childNodes = filterOutComments(inst.childNodes);
78
+ let childNodes = inst.childNodes;
79
const childDiv = childNodes[1];
80
80
- inst = ReactDOM.render(
81
- <div>
82
- {null}
83
- <div />
84
- {null}
85
- </div>,
86
- el,
87
- );
88
- childNodes = filterOutComments(inst.childNodes);
81
+ await act(() => {
82
+ root.render(
83
+ <div>
84
+ {null}
85
+ <div />
86
+ {null}
87
+ </div>,
88
+ );
89
+ });
90
+ inst = container.firstChild;
91
+ childNodes = inst.childNodes;
92
expect(childNodes.length).toBe(1);
93
expect(childNodes[0]).toBe(childDiv);
94
92
- inst = ReactDOM.render(
93
- <div>
94
- {'foo'}
95
- <div />
96
- {'bar'}
97
- </div>,
98
- el,
99
- );
100
- childNodes = filterOutComments(inst.childNodes);
95
+ await act(() => {
96
+ root.render(
97
+ <div>
98
+ {'foo'}
99
+ <div />
100
+ {'bar'}
101
+ </div>,
102
+ );
103
+ });
104
+ inst = container.firstChild;
105
+ childNodes = inst.childNodes;
106
expect(childNodes.length).toBe(3);
107
expect(childNodes[0].data).toBe('foo');
108
expect(childNodes[1]).toBe(childDiv);
@@ -106,101 +111,125 @@ describe('ReactDOMTextComponent', () => {
111
112
/**
113
* The following Node.normalize() tests are intentionally failing.
109
- * See #9836 tracking whether we'll need to fix this or if it's unnecessary.
114
+ * See https://github.com/facebook/react/issues/9836 tracking whether we'll need to fix this or if it's unnecessary.
115
*/
116
+ // @gate TODO
117
+ it('can reconcile text merged by Node.normalize() alongside other elements', async () => {
118
+ const container = document.createElement('div');
119
+ const root = ReactDOMClient.createRoot(container);
120
+ await act(() => {
121
+ root.render(
122
+ <div>
123
+ {'foo'}
124
+ {'bar'}
125
+ {'baz'}
126
+ <span />
127
+ {'qux'}
128
+ </div>,
129
+ );
130
+ });
131
112
- xit('can reconcile text merged by Node.normalize() alongside other elements', () => {
113
- const el = document.createElement('div');
114
- let inst = ReactDOM.render(
115
- <div>
116
- {'foo'}
117
- {'bar'}
118
- {'baz'}
119
- <span />
120
- {'qux'}
121
- </div>,
122
- el,
123
- );
132
+ const inst = container.firstChild;
133
134
inst.normalize();
135
127
- inst = ReactDOM.render(
128
- <div>
129
- {'bar'}
130
- {'baz'}
131
- {'qux'}
132
- <span />
133
- {'foo'}
134
- </div>,
135
- el,
136
- );
136
+ await act(() => {
137
+ root.render(
138
+ <div>
139
+ {'bar'}
140
+ {'baz'}
141
+ {'qux'}
142
+ <span />
143
+ {'foo'}
144
+ </div>,
145
+ container,
146
+ );
147
+ });
148
expect(inst.textContent).toBe('barbazquxfoo');
149
});
150
140
- xit('can reconcile text merged by Node.normalize()', () => {
141
- const el = document.createElement('div');
142
- let inst = ReactDOM.render(
143
- <div>
144
- {'foo'}
145
- {'bar'}
146
- {'baz'}
147
- </div>,
148
- el,
149
- );
151
+ // @gate TODO
152
+ it('can reconcile text merged by Node.normalize()', async () => {
153
+ const container = document.createElement('div');
154
+ const root = ReactDOMClient.createRoot(container);
155
+ await act(() => {
156
+ root.render(
157
+ <div>
158
+ {'foo'}
159
+ {'bar'}
160
+ {'baz'}
161
+ </div>,
162
+ );
163
+ });
164
+ let inst = container.firstChild;
165
166
inst.normalize();
167
153
- inst = ReactDOM.render(
154
- <div>
155
- {'bar'}
156
- {'baz'}
157
- {'qux'}
158
- </div>,
159
- el,
160
- );
168
+ await act(() => {
169
+ root.render(
170
+ <div>
171
+ {'bar'}
172
+ {'baz'}
173
+ {'qux'}
174
+ </div>,
175
+ container,
176
+ );
177
+ });
178
+ inst = container.firstChild;
179
expect(inst.textContent).toBe('barbazqux');
180
});
181
164
- it('can reconcile text from pre-rendered markup', () => {
165
- const el = document.createElement('div');
166
- let reactEl = (
182
+ it('can reconcile text from pre-rendered markup', async () => {
183
+ const container = document.createElement('div');
184
+ let children = (
185
<div>
186
{'foo'}
187
{'bar'}
188
{'baz'}
189
</div>
190
);
173
- el.innerHTML = ReactDOMServer.renderToString(reactEl);
191
+ container.innerHTML = ReactDOMServer.renderToString(children);
192
175
- ReactDOM.hydrate(reactEl, el);
176
- expect(el.textContent).toBe('foobarbaz');
193
+ const root = await act(() => {
194
+ return ReactDOMClient.hydrateRoot(container, children);
195
+ });
196
+ expect(container.textContent).toBe('foobarbaz');
197
178
- ReactDOM.unmountComponentAtNode(el);
198
+ await act(() => {
199
+ root.unmount();
200
+ });
201
180
- reactEl = (
202
+ children = (
203
<div>
204
{''}
205
{''}
206
{''}
207
</div>
208
);
187
- el.innerHTML = ReactDOMServer.renderToString(reactEl);
209
+ container.innerHTML = ReactDOMServer.renderToString(children);
210
189
- ReactDOM.hydrate(reactEl, el);
190
- expect(el.textContent).toBe('');
211
+ await act(() => {
212
+ ReactDOMClient.hydrateRoot(container, children);
213
+ });
214
+ expect(container.textContent).toBe('');
215
});
216
193
- xit('can reconcile text arbitrarily split into multiple nodes', () => {
194
- const el = document.createElement('div');
195
- let inst = ReactDOM.render(
196
- <div>
197
- <span />
198
- {'foobarbaz'}
199
- </div>,
200
- el,
201
- );
217
+ // @gate TODO
218
+ it('can reconcile text arbitrarily split into multiple nodes', async () => {
219
+ const container = document.createElement('div');
220
+ const root = ReactDOMClient.createRoot(container);
221
203
- const childNodes = filterOutComments(inst.childNodes);
222
+ await act(() => {
223
+ root.render(
224
+ <div>
225
+ <span />
226
+ {'foobarbaz'}
227
+ </div>,
228
+ );
229
+ });
230
+ let inst = container.firstChild;
231
+
232
+ const childNodes = inst.childNodes;
233
const textNode = childNodes[1];
234
textNode.textContent = 'foo';
235
inst.insertBefore(
@@ -212,32 +241,40 @@ describe('ReactDOMTextComponent', () => {
241
childNodes[1].nextSibling,
242
);
243
215
- inst = ReactDOM.render(
216
- <div>
217
- <span />
218
- {'barbazqux'}
219
- </div>,
220
- el,
221
- );
244
+ await act(() => {
245
+ root.render(
246
+ <div>
247
+ <span />
248
+ {'barbazqux'}
249
+ </div>,
250
+ container,
251
+ );
252
+ });
253
+ inst = container.firstChild;
254
expect(inst.textContent).toBe('barbazqux');
255
});
256
225
- xit('can reconcile text arbitrarily split into multiple nodes on some substitutions only', () => {
226
- const el = document.createElement('div');
227
- let inst = ReactDOM.render(
228
- <div>
229
- <span />
230
- {'bar'}
231
- <span />
232
- {'foobarbaz'}
233
- {'foo'}
234
- {'barfoo'}
235
- <span />
236
- </div>,
237
- el,
238
- );
257
+ // @gate TODO
258
+ it('can reconcile text arbitrarily split into multiple nodes on some substitutions only', async () => {
259
+ const container = document.createElement('div');
260
+ const root = ReactDOMClient.createRoot(container);
261
+ await act(() => {
262
+ root.render(
263
+ <div>
264
+ <span />
265
+ {'bar'}
266
+ <span />
267
+ {'foobarbaz'}
268
+ {'foo'}
269
+ {'barfoo'}
270
+ <span />
271
+ </div>,
272
+ );
273
+ });
274
240
- const childNodes = filterOutComments(inst.childNodes);
275
+ let inst = container.firstChild;
276
+
277
+ const childNodes = inst.childNodes;
278
const textNode = childNodes[3];
279
textNode.textContent = 'foo';
280
inst.insertBefore(
@@ -255,38 +292,48 @@ describe('ReactDOMTextComponent', () => {
292
childNodes[5].nextSibling,
293
);
294
258
- inst = ReactDOM.render(
259
- <div>
260
- <span />
261
- {'baz'}
262
- <span />
263
- {'barbazqux'}
264
- {'bar'}
265
- {'bazbar'}
266
- <span />
267
- </div>,
268
- el,
269
- );
295
+ await act(() => {
296
+ root.render(
297
+ <div>
298
+ <span />
299
+ {'baz'}
300
+ <span />
301
+ {'barbazqux'}
302
+ {'bar'}
303
+ {'bazbar'}
304
+ <span />
305
+ </div>,
306
+ container,
307
+ );
308
+ });
309
+ inst = container.firstChild;
310
expect(inst.textContent).toBe('bazbarbazquxbarbazbar');
311
});
312
273
- xit('can unmount normalized text nodes', () => {
274
- const el = document.createElement('div');
275
- ReactDOM.render(
276
- <div>
277
- {''}
278
- {'foo'}
279
- {'bar'}
280
- </div>,
281
- el,
282
- );
283
- el.normalize();
284
- ReactDOM.render(<div />, el);
285
- expect(el.innerHTML).toBe('<div></div>');
313
+ // @gate TODO
314
+ it('can unmount normalized text nodes', async () => {
315
+ const container = document.createElement('div');
316
+ const root = ReactDOMClient.createRoot(container);
317
+ await act(() => {
318
+ root.render(
319
+ <div>
320
+ {''}
321
+ {'foo'}
322
+ {'bar'}
323
+ </div>,
324
+ );
325
+ });
326
+
327
+ container.normalize();
328
+ await act(() => {
329
+ root.render(<div />);
330
+ });
331
+
332
+ expect(container.innerHTML).toBe('<div></div>');
333
});
334
288
- it('throws for Temporal-like text nodes', () => {
289
- const el = document.createElement('div');
335
+ it('throws for Temporal-like text nodes', async () => {
336
+ const container = document.createElement('div');
337
class TemporalLike {
338
valueOf() {
339
// Throwing here is the behavior of ECMAScript "Temporal" date/time API.
@@ -297,9 +344,12 @@ describe('ReactDOMTextComponent', () => {
344
return '2020-01-01';
345
}
346
}
300
- expect(() =>
301
- ReactDOM.render(<div>{new TemporalLike()}</div>, el),
302
- ).toThrowError(
347
+ const root = ReactDOMClient.createRoot(container);
348
+ await expect(
349
+ act(() => {
350
+ root.render(<div>{new TemporalLike()}</div>);
351
+ }),
352
+ ).rejects.toThrowError(
353
new Error(
354
'Objects are not valid as a React child (found: object with keys {}).' +
355
' If you meant to render a collection of children, use an array instead.',