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

Convert ReactDOMTextarea to createRoot (#28126)

Also removes usage of `ReactTestUtils`

Sebastian Silbermann committed Jan 29, 2024 at 17:02 UTC cdfaae73c4768ddcce724872b1aa45130b39c85f
1 file changed +546 -278
packages/react-dom/src/__tests__/ReactDOMTextarea-test.js
+546 -278
@@ -13,9 +13,9 @@ function emptyFunction() {}
13
14 describe('ReactDOMTextarea', () => {
15 let React;
16 - let ReactDOM;
16 + let ReactDOMClient;
17 let ReactDOMServer;
18 - let ReactTestUtils;
18 + let act;
19
20 let renderTextarea;
21
@@ -25,15 +25,16 @@ describe('ReactDOMTextarea', () => {
25 jest.resetModules();
26
27 React = require('react');
28 - ReactDOM = require('react-dom');
28 + ReactDOMClient = require('react-dom/client');
29 ReactDOMServer = require('react-dom/server');
30 - ReactTestUtils = require('react-dom/test-utils');
30 + act = require('internal-test-utils').act;
31
32 - renderTextarea = function (component, container) {
33 - if (!container) {
34 - container = document.createElement('div');
35 - }
36 - const node = ReactDOM.render(component, container);
32 + renderTextarea = async function (component, container, root) {
33 + await act(() => {
34 + root.render(component);
35 + });
36 +
37 + const node = container.firstChild;
38
39 // Fixing jsdom's quirky behavior -- in reality, the parser should strip
40 // off the leading newline but we need to do it by hand here.
@@ -46,93 +47,141 @@ describe('ReactDOMTextarea', () => {
47 jest.restoreAllMocks();
48 });
49
49 - it('should allow setting `defaultValue`', () => {
50 + it('should allow setting `defaultValue`', async () => {
51 const container = document.createElement('div');
51 - const node = renderTextarea(<textarea defaultValue="giraffe" />, container);
52 + const root = ReactDOMClient.createRoot(container);
53 + const node = await renderTextarea(
54 + <textarea defaultValue="giraffe" />,
55 + container,
56 + root,
57 + );
58
59 expect(node.value).toBe('giraffe');
60
61 // Changing `defaultValue` should do nothing.
56 - renderTextarea(<textarea defaultValue="gorilla" />, container);
62 + await renderTextarea(<textarea defaultValue="gorilla" />, container, root);
63 expect(node.value).toEqual('giraffe');
64
65 node.value = 'cat';
66
61 - renderTextarea(<textarea defaultValue="monkey" />, container);
67 + await renderTextarea(<textarea defaultValue="monkey" />, container, root);
68 expect(node.value).toEqual('cat');
69 });
70
65 - it('should display `defaultValue` of number 0', () => {
66 - const stub = <textarea defaultValue={0} />;
67 - const node = renderTextarea(stub);
71 + it('should display `defaultValue` of number 0', async () => {
72 + const container = document.createElement('div');
73 + const root = ReactDOMClient.createRoot(container);
74 + const node = await renderTextarea(
75 + <textarea defaultValue={0} />,
76 + container,
77 + root,
78 + );
79
80 expect(node.value).toBe('0');
81 });
82
72 - it('should display "false" for `defaultValue` of `false`', () => {
73 - const stub = <textarea defaultValue={false} />;
74 - const node = renderTextarea(stub);
83 + it('should display "false" for `defaultValue` of `false`', async () => {
84 + const container = document.createElement('div');
85 + const root = ReactDOMClient.createRoot(container);
86 + const node = await renderTextarea(
87 + <textarea defaultValue={false} />,
88 + container,
89 + root,
90 + );
91
92 expect(node.value).toBe('false');
93 });
94
79 - it('should display "foobar" for `defaultValue` of `objToString`', () => {
95 + it('should display "foobar" for `defaultValue` of `objToString`', async () => {
96 + const container = document.createElement('div');
97 + const root = ReactDOMClient.createRoot(container);
98 const objToString = {
99 toString: function () {
100 return 'foobar';
101 },
102 };
85 -
86 - const stub = <textarea defaultValue={objToString} />;
87 - const node = renderTextarea(stub);
103 + const node = await renderTextarea(
104 + <textarea defaultValue={objToString} />,
105 + container,
106 + root,
107 + );
108
109 expect(node.value).toBe('foobar');
110 });
111
92 - it('should set defaultValue', () => {
112 + it('should set defaultValue', async () => {
113 const container = document.createElement('div');
94 - ReactDOM.render(<textarea defaultValue="foo" />, container);
95 - ReactDOM.render(<textarea defaultValue="bar" />, container);
96 - ReactDOM.render(<textarea defaultValue="noise" />, container);
114 + const root = ReactDOMClient.createRoot(container);
115 + await act(() => {
116 + root.render(<textarea defaultValue="foo" />);
117 + });
118 + await act(() => {
119 + root.render(<textarea defaultValue="bar" />);
120 + });
121 + await act(() => {
122 + root.render(<textarea defaultValue="noise" />);
123 + });
124 +
125 expect(container.firstChild.defaultValue).toBe('noise');
126 });
127
100 - it('should not render value as an attribute', () => {
101 - const stub = <textarea value="giraffe" onChange={emptyFunction} />;
102 - const node = renderTextarea(stub);
128 + it('should not render value as an attribute', async () => {
129 + const container = document.createElement('div');
130 + const root = ReactDOMClient.createRoot(container);
131 + const node = await renderTextarea(
132 + <textarea value="giraffe" onChange={emptyFunction} />,
133 + container,
134 + root,
135 + );
136
137 expect(node.getAttribute('value')).toBe(null);
138 });
139
107 - it('should display `value` of number 0', () => {
108 - const stub = <textarea value={0} onChange={emptyFunction} />;
109 - const node = renderTextarea(stub);
140 + it('should display `value` of number 0', async () => {
141 + const container = document.createElement('div');
142 + const root = ReactDOMClient.createRoot(container);
143 + const node = await renderTextarea(
144 + <textarea value={0} onChange={emptyFunction} />,
145 + container,
146 + root,
147 + );
148
149 expect(node.value).toBe('0');
150 });
151
114 - it('should update defaultValue to empty string', () => {
152 + it('should update defaultValue to empty string', async () => {
153 const container = document.createElement('div');
116 - ReactDOM.render(<textarea defaultValue={'foo'} />, container);
117 - ReactDOM.render(<textarea defaultValue={''} />, container);
154 + const root = ReactDOMClient.createRoot(container);
155 + await act(() => {
156 + root.render(<textarea defaultValue={'foo'} />);
157 + });
158 +
159 + await act(() => {
160 + root.render(<textarea defaultValue={''} />);
161 + });
162 +
163 expect(container.firstChild.defaultValue).toBe('');
164 });
165
121 - it('should allow setting `value` to `giraffe`', () => {
166 + it('should allow setting `value` to `giraffe`', async () => {
167 const container = document.createElement('div');
123 - let stub = <textarea value="giraffe" onChange={emptyFunction} />;
124 - const node = renderTextarea(stub, container);
168 + const root = ReactDOMClient.createRoot(container);
169 + const node = await renderTextarea(
170 + <textarea value="giraffe" onChange={emptyFunction} />,
171 + container,
172 + root,
173 + );
174
175 expect(node.value).toBe('giraffe');
176
128 - stub = ReactDOM.render(
129 - <textarea value="gorilla" onChange={emptyFunction} />,
130 - container,
131 - );
177 + await act(() => {
178 + root.render(<textarea value="gorilla" onChange={emptyFunction} />);
179 + });
180 +
181 expect(node.value).toEqual('gorilla');
182 });
183
135 - it('will not initially assign an empty value (covers case where firefox throws a validation error when required attribute is set)', () => {
184 + it('will not initially assign an empty value (covers case where firefox throws a validation error when required attribute is set)', async () => {
185 const container = document.createElement('div');
186
187 let counter = 0;
@@ -156,7 +205,10 @@ describe('ReactDOMTextarea', () => {
205 },
206 );
207
159 - ReactDOM.render(<textarea value="" readOnly={true} />, container);
208 + const root = ReactDOMClient.createRoot(container);
209 + await act(() => {
210 + root.render(<textarea value="" readOnly={true} />);
211 + });
212
213 expect(counter).toEqual(0);
214 });
@@ -178,38 +230,50 @@ describe('ReactDOMTextarea', () => {
230 expect(div.firstChild.getAttribute('defaultValue')).toBe(null);
231 });
232
181 - it('should allow setting `value` to `true`', () => {
233 + it('should allow setting `value` to `true`', async () => {
234 const container = document.createElement('div');
183 - let stub = <textarea value="giraffe" onChange={emptyFunction} />;
184 - const node = renderTextarea(stub, container);
235 + const root = ReactDOMClient.createRoot(container);
236 + const node = await renderTextarea(
237 + <textarea value="giraffe" onChange={emptyFunction} />,
238 + container,
239 + root,
240 + );
241
242 expect(node.value).toBe('giraffe');
243
188 - stub = ReactDOM.render(
189 - <textarea value={true} onChange={emptyFunction} />,
190 - container,
191 - );
244 + await act(() => {
245 + root.render(<textarea value={true} onChange={emptyFunction} />);
246 + });
247 +
248 expect(node.value).toEqual('true');
249 });
250
195 - it('should allow setting `value` to `false`', () => {
251 + it('should allow setting `value` to `false`', async () => {
252 const container = document.createElement('div');
197 - let stub = <textarea value="giraffe" onChange={emptyFunction} />;
198 - const node = renderTextarea(stub, container);
253 + const root = ReactDOMClient.createRoot(container);
254 + const node = await renderTextarea(
255 + <textarea value="giraffe" onChange={emptyFunction} />,
256 + container,
257 + root,
258 + );
259
260 expect(node.value).toBe('giraffe');
261
202 - stub = ReactDOM.render(
203 - <textarea value={false} onChange={emptyFunction} />,
204 - container,
205 - );
262 + await act(() => {
263 + root.render(<textarea value={false} onChange={emptyFunction} />);
264 + });
265 +
266 expect(node.value).toEqual('false');
267 });
268
209 - it('should allow setting `value` to `objToString`', () => {
269 + it('should allow setting `value` to `objToString`', async () => {
270 const container = document.createElement('div');
211 - let stub = <textarea value="giraffe" onChange={emptyFunction} />;
212 - const node = renderTextarea(stub, container);
271 + const root = ReactDOMClient.createRoot(container);
272 + const node = await renderTextarea(
273 + <textarea value="giraffe" onChange={emptyFunction} />,
274 + container,
275 + root,
276 + );
277
278 expect(node.value).toBe('giraffe');
279
@@ -218,14 +282,15 @@ describe('ReactDOMTextarea', () => {
282 return 'foo';
283 },
284 };
221 - stub = ReactDOM.render(
222 - <textarea value={objToString} onChange={emptyFunction} />,
223 - container,
224 - );
285 +
286 + await act(() => {
287 + root.render(<textarea value={objToString} onChange={emptyFunction} />);
288 + });
289 +
290 expect(node.value).toEqual('foo');
291 });
292
228 - it('should throw when value is set to a Temporal-like object', () => {
293 + it('should throw when value is set to a Temporal-like object', async () => {
294 class TemporalLike {
295 valueOf() {
296 // Throwing here is the behavior of ECMAScript "Temporal" date/time API.
@@ -237,51 +302,72 @@ describe('ReactDOMTextarea', () => {
302 }
303 }
304 const container = document.createElement('div');
240 - const stub = <textarea value="giraffe" onChange={emptyFunction} />;
241 - const node = renderTextarea(stub, container);
305 + const root = ReactDOMClient.createRoot(container);
306 + const node = await renderTextarea(
307 + <textarea value="giraffe" onChange={emptyFunction} />,
308 + container,
309 + root,
310 + );
311
312 expect(node.value).toBe('giraffe');
313
245 - const test = () =>
246 - ReactDOM.render(
247 - <textarea value={new TemporalLike()} onChange={emptyFunction} />,
248 - container,
249 - );
250 - expect(() =>
251 - expect(test).toThrowError(new TypeError('prod message')),
314 + const test = async () => {
315 + await act(() => {
316 + root.render(
317 + <textarea value={new TemporalLike()} onChange={emptyFunction} />,
318 + );
319 + });
320 + };
321 + await expect(() =>
322 + expect(test).rejects.toThrowError(new TypeError('prod message')),
323 ).toErrorDev(
324 'Form field values (value, checked, defaultValue, or defaultChecked props) must be ' +
325 'strings, not TemporalLike. This value must be coerced to a string before using it here.',
326 );
327 });
328
258 - it('should take updates to `defaultValue` for uncontrolled textarea', () => {
329 + it('should take updates to `defaultValue` for uncontrolled textarea', async () => {
330 const container = document.createElement('div');
331 + const root = ReactDOMClient.createRoot(container);
332 + await act(() => {
333 + root.render(<textarea defaultValue="0" />);
334 + });
335
261 - const node = ReactDOM.render(<textarea defaultValue="0" />, container);
336 + const node = container.firstChild;
337
338 expect(node.value).toBe('0');
339
265 - ReactDOM.render(<textarea defaultValue="1" />, container);
340 + await act(() => {
341 + root.render(<textarea defaultValue="1" />);
342 + });
343
344 expect(node.value).toBe('0');
345 });
346
270 - it('should take updates to children in lieu of `defaultValue` for uncontrolled textarea', () => {
347 + it('should take updates to children in lieu of `defaultValue` for uncontrolled textarea', async () => {
348 const container = document.createElement('div');
349 + const root = ReactDOMClient.createRoot(container);
350 + await act(() => {
351 + root.render(<textarea defaultValue="0" />);
352 + });
353
273 - const node = ReactDOM.render(<textarea defaultValue="0" />, container);
354 + const node = container.firstChild;
355
356 expect(node.value).toBe('0');
357
277 - ReactDOM.render(<textarea>1</textarea>, container);
358 + await act(() => {
359 + root.render(<textarea>1</textarea>);
360 + });
361
362 expect(node.value).toBe('0');
363 });
364
282 - it('should not incur unnecessary DOM mutations', () => {
365 + it('should not incur unnecessary DOM mutations', async () => {
366 const container = document.createElement('div');
284 - ReactDOM.render(<textarea value="a" onChange={emptyFunction} />, container);
367 + const root = ReactDOMClient.createRoot(container);
368 + await act(() => {
369 + root.render(<textarea value="a" onChange={emptyFunction} />);
370 + });
371
372 const node = container.firstChild;
373 let nodeValue = 'a';
@@ -295,25 +381,35 @@ describe('ReactDOMTextarea', () => {
381 }),
382 });
383
298 - ReactDOM.render(<textarea value="a" onChange={emptyFunction} />, container);
384 + await act(() => {
385 + root.render(<textarea value="a" onChange={emptyFunction} />);
386 + });
387 +
388 expect(nodeValueSetter).toHaveBeenCalledTimes(0);
389
301 - ReactDOM.render(<textarea value="b" onChange={emptyFunction} />, container);
390 + await act(() => {
391 + root.render(<textarea value="b" onChange={emptyFunction} />);
392 + });
393 +
394 expect(nodeValueSetter).toHaveBeenCalledTimes(1);
395 });
396
305 - it('should properly control a value of number `0`', () => {
306 - const stub = <textarea value={0} onChange={emptyFunction} />;
397 + it('should properly control a value of number `0`', async () => {
398 const setUntrackedValue = Object.getOwnPropertyDescriptor(
399 HTMLTextAreaElement.prototype,
400 'value',
401 ).set;
402
403 const container = document.createElement('div');
404 + const root = ReactDOMClient.createRoot(container);
405 document.body.appendChild(container);
406
407 try {
316 - const node = renderTextarea(stub, container);
408 + const node = await renderTextarea(
409 + <textarea value={0} onChange={emptyFunction} />,
410 + container,
411 + root,
412 + );
413
414 setUntrackedValue.call(node, 'giraffe');
415 node.dispatchEvent(
@@ -326,30 +422,41 @@ describe('ReactDOMTextarea', () => {
422 });
423
424 if (ReactFeatureFlags.disableTextareaChildren) {
329 - it('should ignore children content', () => {
425 + it('should ignore children content', async () => {
426 const container = document.createElement('div');
331 - let stub = <textarea>giraffe</textarea>;
427 + const root = ReactDOMClient.createRoot(container);
428 let node;
429
334 - expect(() => {
335 - node = renderTextarea(stub, container);
430 + await expect(async () => {
431 + node = await renderTextarea(
432 + <textarea>giraffe</textarea>,
433 + container,
434 + root,
435 + );
436 }).toErrorDev(
437 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
438 );
439 expect(node.value).toBe('');
340 - // Changing children should do nothing, it functions like `defaultValue`.
341 - stub = ReactDOM.render(<textarea>gorilla</textarea>, container);
440 +
441 + await act(() => {
442 + root.render(<textarea>gorilla</textarea>);
443 + });
444 +
445 expect(node.value).toEqual('');
446 });
447 }
448
449 if (ReactFeatureFlags.disableTextareaChildren) {
347 - it('should receive defaultValue and still ignore children content', () => {
450 + it('should receive defaultValue and still ignore children content', async () => {
451 + const container = document.createElement('div');
452 + const root = ReactDOMClient.createRoot(container);
453 let node;
454
350 - expect(() => {
351 - node = renderTextarea(
455 + await expect(async () => {
456 + node = await renderTextarea(
457 <textarea defaultValue="dragon">monkey</textarea>,
458 + container,
459 + root,
460 );
461 }).toErrorDev(
462 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
@@ -359,67 +466,80 @@ describe('ReactDOMTextarea', () => {
466 }
467
468 if (!ReactFeatureFlags.disableTextareaChildren) {
362 - it('should treat children like `defaultValue`', () => {
469 + it('should treat children like `defaultValue`', async () => {
470 const container = document.createElement('div');
364 - let stub = <textarea>giraffe</textarea>;
471 + const root = ReactDOMClient.createRoot(container);
472 let node;
473
367 - expect(() => {
368 - node = renderTextarea(stub, container);
474 + await expect(async () => {
475 + node = await renderTextarea(
476 + <textarea>giraffe</textarea>,
477 + container,
478 + root,
479 + );
480 }).toErrorDev(
481 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
482 );
483
484 expect(node.value).toBe('giraffe');
485
375 - // Changing children should do nothing, it functions like `defaultValue`.
376 - stub = ReactDOM.render(<textarea>gorilla</textarea>, container);
486 + await act(() => {
487 + root.render(<textarea>gorilla</textarea>);
488 + });
489 +
490 expect(node.value).toEqual('giraffe');
491 });
492 }
493
381 - it('should keep value when switching to uncontrolled element if not changed', () => {
494 + it('should keep value when switching to uncontrolled element if not changed', async () => {
495 const container = document.createElement('div');
383 -
384 - const node = renderTextarea(
496 + const root = ReactDOMClient.createRoot(container);
497 + const node = await renderTextarea(
498 <textarea value="kitten" onChange={emptyFunction} />,
499 container,
500 + root,
501 );
502
503 expect(node.value).toBe('kitten');
504
391 - ReactDOM.render(<textarea defaultValue="gorilla" />, container);
505 + await act(() => {
506 + root.render(<textarea defaultValue="gorilla" />);
507 + });
508
509 expect(node.value).toEqual('kitten');
510 });
511
396 - it('should keep value when switching to uncontrolled element if changed', () => {
512 + it('should keep value when switching to uncontrolled element if changed', async () => {
513 const container = document.createElement('div');
398 -
399 - const node = renderTextarea(
514 + const root = ReactDOMClient.createRoot(container);
515 + const node = await renderTextarea(
516 <textarea value="kitten" onChange={emptyFunction} />,
517 container,
518 + root,
519 );
520
521 expect(node.value).toBe('kitten');
522
406 - ReactDOM.render(
407 - <textarea value="puppies" onChange={emptyFunction} />,
408 - container,
409 - );
523 + await act(() => {
524 + root.render(<textarea value="puppies" onChange={emptyFunction} />);
525 + });
526
527 expect(node.value).toBe('puppies');
528
413 - ReactDOM.render(<textarea defaultValue="gorilla" />, container);
529 + await act(() => {
530 + root.render(<textarea defaultValue="gorilla" />);
531 + });
532
533 expect(node.value).toEqual('puppies');
534 });
535
536 if (ReactFeatureFlags.disableTextareaChildren) {
419 - it('should ignore numbers as children', () => {
537 + it('should ignore numbers as children', async () => {
538 + const container = document.createElement('div');
539 + const root = ReactDOMClient.createRoot(container);
540 let node;
421 - expect(() => {
422 - node = renderTextarea(<textarea>{17}</textarea>);
541 + await expect(async () => {
542 + node = await renderTextarea(<textarea>{17}</textarea>, container, root);
543 }).toErrorDev(
544 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
545 );
@@ -428,10 +548,12 @@ describe('ReactDOMTextarea', () => {
548 }
549
550 if (!ReactFeatureFlags.disableTextareaChildren) {
431 - it('should allow numbers as children', () => {
551 + it('should allow numbers as children', async () => {
552 + const container = document.createElement('div');
553 + const root = ReactDOMClient.createRoot(container);
554 let node;
433 - expect(() => {
434 - node = renderTextarea(<textarea>{17}</textarea>);
555 + await expect(async () => {
556 + node = await renderTextarea(<textarea>{17}</textarea>, container, root);
557 }).toErrorDev(
558 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
559 );
@@ -440,10 +562,16 @@ describe('ReactDOMTextarea', () => {
562 }
563
564 if (ReactFeatureFlags.disableTextareaChildren) {
443 - it('should ignore booleans as children', () => {
565 + it('should ignore booleans as children', async () => {
566 + const container = document.createElement('div');
567 + const root = ReactDOMClient.createRoot(container);
568 let node;
445 - expect(() => {
446 - node = renderTextarea(<textarea>{false}</textarea>);
569 + await expect(async () => {
570 + node = await renderTextarea(
571 + <textarea>{false}</textarea>,
572 + container,
573 + root,
574 + );
575 }).toErrorDev(
576 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
577 );
@@ -452,10 +580,16 @@ describe('ReactDOMTextarea', () => {
580 }
581
582 if (!ReactFeatureFlags.disableTextareaChildren) {
455 - it('should allow booleans as children', () => {
583 + it('should allow booleans as children', async () => {
584 + const container = document.createElement('div');
585 + const root = ReactDOMClient.createRoot(container);
586 let node;
457 - expect(() => {
458 - node = renderTextarea(<textarea>{false}</textarea>);
587 + await expect(async () => {
588 + node = await renderTextarea(
589 + <textarea>{false}</textarea>,
590 + container,
591 + root,
592 + );
593 }).toErrorDev(
594 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
595 );
@@ -464,15 +598,21 @@ describe('ReactDOMTextarea', () => {
598 }
599
600 if (ReactFeatureFlags.disableTextareaChildren) {
467 - it('should ignore objects as children', () => {
601 + it('should ignore objects as children', async () => {
602 + const container = document.createElement('div');
603 + const root = ReactDOMClient.createRoot(container);
604 const obj = {
605 toString: function () {
606 return 'sharkswithlasers';
607 },
608 };
609 let node;
474 - expect(() => {
475 - node = renderTextarea(<textarea>{obj}</textarea>);
610 + await expect(async () => {
611 + node = await renderTextarea(
612 + <textarea>{obj}</textarea>,
613 + container,
614 + root,
615 + );
616 }).toErrorDev(
617 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
618 );
@@ -481,15 +621,21 @@ describe('ReactDOMTextarea', () => {
621 }
622
623 if (!ReactFeatureFlags.disableTextareaChildren) {
484 - it('should allow objects as children', () => {
624 + it('should allow objects as children', async () => {
625 + const container = document.createElement('div');
626 + const root = ReactDOMClient.createRoot(container);
627 const obj = {
628 toString: function () {
629 return 'sharkswithlasers';
630 },
631 };
632 let node;
491 - expect(() => {
492 - node = renderTextarea(<textarea>{obj}</textarea>);
633 + await expect(async () => {
634 + node = await renderTextarea(
635 + <textarea>{obj}</textarea>,
636 + container,
637 + root,
638 + );
639 }).toErrorDev(
640 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
641 );
@@ -498,64 +644,86 @@ describe('ReactDOMTextarea', () => {
644 }
645
646 if (!ReactFeatureFlags.disableTextareaChildren) {
501 - it('should throw with multiple or invalid children', () => {
502 - expect(() => {
503 - expect(() =>
504 - ReactTestUtils.renderIntoDocument(
505 - <textarea>
506 - {'hello'}
507 - {'there'}
508 - </textarea>,
509 - ),
510 - ).toThrow('<textarea> can only have at most one child');
511 - }).toErrorDev(
647 + it('should throw with multiple or invalid children', async () => {
648 + const container = document.createElement('div');
649 + const root = ReactDOMClient.createRoot(container);
650 + await expect(async () => {
651 + await expect(async () => {
652 + await act(() => {
653 + root.render(
654 + <textarea>
655 + {'hello'}
656 + {'there'}
657 + </textarea>,
658 + );
659 + });
660 + }).rejects.toThrow('<textarea> can only have at most one child');
661 + }).toErrorDev([
662 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
513 - );
663 + 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
664 + ]);
665
666 let node;
516 - expect(() => {
517 - expect(
518 - () =>
519 - (node = renderTextarea(
667 + await expect(async () => {
668 + await expect(
669 + (async () =>
670 + (node = await renderTextarea(
671 <textarea>
672 <strong />
673 </textarea>,
523 - )),
524 - ).not.toThrow();
525 - }).toErrorDev(
674 + container,
675 + root,
676 + )))(),
677 + ).resolves.not.toThrow();
678 + }).toErrorDev([
679 'Use the `defaultValue` or `value` props instead of setting children on <textarea>.',
527 - );
680 + ]);
681
682 expect(node.value).toBe('[object Object]');
683 });
684 }
685
533 - it('should unmount', () => {
686 + it('should unmount', async () => {
687 const container = document.createElement('div');
535 - renderTextarea(<textarea />, container);
536 - ReactDOM.unmountComponentAtNode(container);
688 + const root = ReactDOMClient.createRoot(container);
689 + await act(() => {
690 + root.render(<textarea />);
691 + });
692 +
693 + await act(() => {
694 + root.unmount();
695 + });
696 });
697
539 - it('should warn if value is null', () => {
540 - expect(() =>
541 - ReactTestUtils.renderIntoDocument(<textarea value={null} />),
542 - ).toErrorDev(
698 + it('should warn if value is null', async () => {
699 + const container = document.createElement('div');
700 + const root = ReactDOMClient.createRoot(container);
701 + await expect(async () => {
702 + await act(() => {
703 + root.render(<textarea value={null} />);
704 + });
705 + }).toErrorDev(
706 '`value` prop on `textarea` should not be null. ' +
707 'Consider using an empty string to clear the component or `undefined` ' +
708 'for uncontrolled components.',
709 );
710
548 - // No additional warnings are expected
549 - ReactTestUtils.renderIntoDocument(<textarea value={null} />);
711 + await act(() => {
712 + root.render(<textarea value={null} />);
713 + });
714 });
715
552 - it('should warn if value and defaultValue are specified', () => {
716 + it('should warn if value and defaultValue are specified', async () => {
717 const InvalidComponent = () => (
718 <textarea value="foo" defaultValue="bar" readOnly={true} />
719 );
556 - expect(() =>
557 - ReactTestUtils.renderIntoDocument(<InvalidComponent />),
558 - ).toErrorDev(
720 + let container = document.createElement('div');
721 + let root = ReactDOMClient.createRoot(container);
722 + await expect(async () => {
723 + await act(() => {
724 + root.render(<InvalidComponent />);
725 + });
726 + }).toErrorDev(
727 'InvalidComponent contains a textarea with both value and defaultValue props. ' +
728 'Textarea elements must be either controlled or uncontrolled ' +
729 '(specify either the value prop, or the defaultValue prop, but not ' +
@@ -564,18 +732,33 @@ describe('ReactDOMTextarea', () => {
732 'https://reactjs.org/link/controlled-components',
733 );
734
567 - // No additional warnings are expected
568 - ReactTestUtils.renderIntoDocument(<InvalidComponent />);
735 + container = document.createElement('div');
736 + root = ReactDOMClient.createRoot(container);
737 +
738 + await act(() => {
739 + root.render(<InvalidComponent />);
740 + });
741 });
742
571 - it('should not warn about missing onChange in uncontrolled textareas', () => {
743 + it('should not warn about missing onChange in uncontrolled textareas', async () => {
744 const container = document.createElement('div');
573 - ReactDOM.render(<textarea />, container);
574 - ReactDOM.unmountComponentAtNode(container);
575 - ReactDOM.render(<textarea value={undefined} />, container);
745 + let root = ReactDOMClient.createRoot(container);
746 +
747 + await act(() => {
748 + root.render(<textarea />);
749 + });
750 +
751 + await act(() => {
752 + root.unmount();
753 + });
754 + root = ReactDOMClient.createRoot(container);
755 +
756 + await act(() => {
757 + root.render(<textarea value={undefined} />);
758 + });
759 });
760
578 - it('does not set textContent if value is unchanged', () => {
761 + it('does not set textContent if value is unchanged', async () => {
762 const container = document.createElement('div');
763 let node;
764 let instance;
@@ -609,7 +792,11 @@ describe('ReactDOMTextarea', () => {
792 );
793 }
794 }
612 - ReactDOM.render(<App />, container);
795 + const root = ReactDOMClient.createRoot(container);
796 + await act(() => {
797 + root.render(<App />);
798 + });
799 +
800 defaultValue = node.defaultValue;
801 Object.defineProperty(node, 'defaultValue', {get, set});
802 instance.setState({count: 1});
@@ -617,59 +804,77 @@ describe('ReactDOMTextarea', () => {
804 });
805
806 describe('When given a Symbol value', () => {
620 - it('treats initial Symbol value as an empty string', () => {
807 + it('treats initial Symbol value as an empty string', async () => {
808 const container = document.createElement('div');
622 - expect(() =>
623 - ReactDOM.render(
624 - <textarea value={Symbol('foobar')} onChange={() => {}} />,
625 - container,
626 - ),
627 - ).toErrorDev('Invalid value for prop `value`');
809 + const root = ReactDOMClient.createRoot(container);
810 + await expect(async () => {
811 + await act(() => {
812 + root.render(
813 + <textarea value={Symbol('foobar')} onChange={() => {}} />,
814 + );
815 + });
816 + }).toErrorDev('Invalid value for prop `value`');
817 const node = container.firstChild;
818
819 expect(node.value).toBe('');
820 });
821
633 - it('treats initial Symbol children as an empty string', () => {
822 + it('treats initial Symbol children as an empty string', async () => {
823 const container = document.createElement('div');
635 - expect(() =>
636 - ReactDOM.render(
637 - <textarea onChange={() => {}}>{Symbol('foo')}</textarea>,
638 - container,
639 - ),
640 - ).toErrorDev('Use the `defaultValue` or `value` props');
824 + const root = ReactDOMClient.createRoot(container);
825 + await expect(async () => {
826 + await act(() => {
827 + root.render(<textarea onChange={() => {}}>{Symbol('foo')}</textarea>);
828 + });
829 + }).toErrorDev('Use the `defaultValue` or `value` props');
830 const node = container.firstChild;
831
832 expect(node.value).toBe('');
833 });
834
646 - it('treats updated Symbol value as an empty string', () => {
835 + it('treats updated Symbol value as an empty string', async () => {
836 const container = document.createElement('div');
648 - ReactDOM.render(<textarea value="foo" onChange={() => {}} />, container);
649 - expect(() =>
650 - ReactDOM.render(
651 - <textarea value={Symbol('foo')} onChange={() => {}} />,
652 - container,
653 - ),
654 - ).toErrorDev('Invalid value for prop `value`');
837 + const root = ReactDOMClient.createRoot(container);
838 +
839 + await act(() => {
840 + root.render(<textarea value="foo" onChange={() => {}} />);
841 + });
842 +
843 + await expect(async () => {
844 + await act(() => {
845 + root.render(<textarea value={Symbol('foo')} onChange={() => {}} />);
846 + });
847 + }).toErrorDev('Invalid value for prop `value`');
848 const node = container.firstChild;
849
850 expect(node.value).toBe('');
851 });
852
660 - it('treats initial Symbol defaultValue as an empty string', () => {
853 + it('treats initial Symbol defaultValue as an empty string', async () => {
854 const container = document.createElement('div');
662 - ReactDOM.render(<textarea defaultValue={Symbol('foobar')} />, container);
855 + const root = ReactDOMClient.createRoot(container);
856 +
857 + await act(() => {
858 + root.render(<textarea defaultValue={Symbol('foobar')} />);
859 + });
860 +
861 const node = container.firstChild;
862
863 // TODO: defaultValue is a reserved prop and is not validated. Check warnings when they are.
864 expect(node.value).toBe('');
865 });
866
669 - it('treats updated Symbol defaultValue as an empty string', () => {
867 + it('treats updated Symbol defaultValue as an empty string', async () => {
868 const container = document.createElement('div');
671 - ReactDOM.render(<textarea defaultValue="foo" />, container);
672 - ReactDOM.render(<textarea defaultValue={Symbol('foobar')} />, container);
869 + const root = ReactDOMClient.createRoot(container);
870 + await act(() => {
871 + root.render(<textarea defaultValue="foo" />);
872 + });
873 +
874 + await act(() => {
875 + root.render(<textarea defaultValue={Symbol('foobar')} />);
876 + });
877 +
878 const node = container.firstChild;
879
880 // TODO: defaultValue is a reserved prop and is not validated. Check warnings when they are.
@@ -678,59 +883,76 @@ describe('ReactDOMTextarea', () => {
883 });
884
885 describe('When given a function value', () => {
681 - it('treats initial function value as an empty string', () => {
886 + it('treats initial function value as an empty string', async () => {
887 const container = document.createElement('div');
683 - expect(() =>
684 - ReactDOM.render(
685 - <textarea value={() => {}} onChange={() => {}} />,
686 - container,
687 - ),
688 - ).toErrorDev('Invalid value for prop `value`');
888 + await expect(async () => {
889 + const root = ReactDOMClient.createRoot(container);
890 +
891 + await act(() => {
892 + root.render(<textarea value={() => {}} onChange={() => {}} />);
893 + });
894 + }).toErrorDev('Invalid value for prop `value`');
895 const node = container.firstChild;
896
897 expect(node.value).toBe('');
898 });
899
694 - it('treats initial function children as an empty string', () => {
900 + it('treats initial function children as an empty string', async () => {
901 const container = document.createElement('div');
696 - expect(() =>
697 - ReactDOM.render(
698 - <textarea onChange={() => {}}>{() => {}}</textarea>,
699 - container,
700 - ),
701 - ).toErrorDev('Use the `defaultValue` or `value` props');
902 + await expect(async () => {
903 + const root = ReactDOMClient.createRoot(container);
904 +
905 + await act(() => {
906 + root.render(<textarea onChange={() => {}}>{() => {}}</textarea>);
907 + });
908 + }).toErrorDev('Use the `defaultValue` or `value` props');
909 const node = container.firstChild;
910
911 expect(node.value).toBe('');
912 });
913
707 - it('treats updated function value as an empty string', () => {
914 + it('treats updated function value as an empty string', async () => {
915 const container = document.createElement('div');
709 - ReactDOM.render(<textarea value="foo" onChange={() => {}} />, container);
710 - expect(() =>
711 - ReactDOM.render(
712 - <textarea value={() => {}} onChange={() => {}} />,
713 - container,
714 - ),
715 - ).toErrorDev('Invalid value for prop `value`');
916 + const root = ReactDOMClient.createRoot(container);
917 +
918 + await act(() => {
919 + root.render(<textarea value="foo" onChange={() => {}} />);
920 + });
921 +
922 + await expect(async () => {
923 + await act(() => {
924 + root.render(<textarea value={() => {}} onChange={() => {}} />);
925 + });
926 + }).toErrorDev('Invalid value for prop `value`');
927 const node = container.firstChild;
928
929 expect(node.value).toBe('');
930 });
931
721 - it('treats initial function defaultValue as an empty string', () => {
932 + it('treats initial function defaultValue as an empty string', async () => {
933 const container = document.createElement('div');
723 - ReactDOM.render(<textarea defaultValue={() => {}} />, container);
934 + const root = ReactDOMClient.createRoot(container);
935 + await act(() => {
936 + root.render(<textarea defaultValue={() => {}} />);
937 + });
938 +
939 const node = container.firstChild;
940
941 // TODO: defaultValue is a reserved prop and is not validated. Check warnings when they are.
942 expect(node.value).toBe('');
943 });
944
730 - it('treats updated function defaultValue as an empty string', () => {
945 + it('treats updated function defaultValue as an empty string', async () => {
946 const container = document.createElement('div');
732 - ReactDOM.render(<textarea defaultValue="foo" />, container);
733 - ReactDOM.render(<textarea defaultValue={() => {}} />, container);
947 + const root = ReactDOMClient.createRoot(container);
948 + await act(() => {
949 + root.render(<textarea defaultValue="foo" />);
950 + });
951 +
952 + await act(() => {
953 + root.render(<textarea defaultValue={() => {}} />);
954 + });
955 +
956 const node = container.firstChild;
957
958 // TODO: defaultValue is a reserved prop and is not validated. Check warnings when they are.
@@ -738,69 +960,103 @@ describe('ReactDOMTextarea', () => {
960 });
961 });
962
741 - it('should remove previous `defaultValue`', () => {
963 + it('should remove previous `defaultValue`', async () => {
964 const container = document.createElement('div');
743 - const node = ReactDOM.render(<textarea defaultValue="0" />, container);
965 + const root = ReactDOMClient.createRoot(container);
966 + await act(() => {
967 + root.render(<textarea defaultValue="0" />);
968 + });
969 +
970 + const node = container.firstChild;
971
972 expect(node.value).toBe('0');
973 expect(node.defaultValue).toBe('0');
974
748 - ReactDOM.render(<textarea />, container);
975 + await act(() => {
976 + root.render(<textarea />);
977 + });
978 +
979 expect(node.defaultValue).toBe('');
980 });
981
752 - it('should treat `defaultValue={null}` as missing', () => {
982 + it('should treat `defaultValue={null}` as missing', async () => {
983 const container = document.createElement('div');
754 - const node = ReactDOM.render(<textarea defaultValue="0" />, container);
984 + const root = ReactDOMClient.createRoot(container);
985 + await act(() => {
986 + root.render(<textarea defaultValue="0" />);
987 + });
988 +
989 + const node = container.firstChild;
990
991 expect(node.value).toBe('0');
992 expect(node.defaultValue).toBe('0');
993
759 - ReactDOM.render(<textarea defaultValue={null} />, container);
994 + await act(() => {
995 + root.render(<textarea defaultValue={null} />);
996 + });
997 +
998 expect(node.defaultValue).toBe('');
999 });
1000
763 - it('should not warn about missing onChange if value is not set', () => {
764 - expect(() => {
765 - ReactTestUtils.renderIntoDocument(<textarea />);
766 - }).not.toThrow();
1001 + it('should not warn about missing onChange if value is not set', async () => {
1002 + const container = document.createElement('div');
1003 + const root = ReactDOMClient.createRoot(container);
1004 + await expect(
1005 + act(() => {
1006 + root.render(<textarea />);
1007 + }),
1008 + ).resolves.not.toThrow();
1009 });
1010
769 - it('should not warn about missing onChange if value is undefined', () => {
770 - expect(() => {
771 - ReactTestUtils.renderIntoDocument(<textarea value={undefined} />);
772 - }).not.toThrow();
1011 + it('should not warn about missing onChange if value is undefined', async () => {
1012 + const container = document.createElement('div');
1013 + const root = ReactDOMClient.createRoot(container);
1014 + await expect(
1015 + act(() => {
1016 + root.render(<textarea value={undefined} />);
1017 + }),
1018 + ).resolves.not.toThrow();
1019 });
1020
775 - it('should not warn about missing onChange if onChange is set', () => {
776 - expect(() => {
777 - const change = jest.fn();
778 - ReactTestUtils.renderIntoDocument(
779 - <textarea value="something" onChange={change} />,
780 - );
781 - }).not.toThrow();
1021 + it('should not warn about missing onChange if onChange is set', async () => {
1022 + const change = jest.fn();
1023 + const container = document.createElement('div');
1024 + const root = ReactDOMClient.createRoot(container);
1025 + await expect(
1026 + act(() => {
1027 + root.render(<textarea value="something" onChange={change} />);
1028 + }),
1029 + ).resolves.not.toThrow();
1030 });
1031
784 - it('should not warn about missing onChange if disabled is true', () => {
785 - expect(() => {
786 - ReactTestUtils.renderIntoDocument(
787 - <textarea value="something" disabled={true} />,
788 - );
789 - }).not.toThrow();
1032 + it('should not warn about missing onChange if disabled is true', async () => {
1033 + const container = document.createElement('div');
1034 + const root = ReactDOMClient.createRoot(container);
1035 + await expect(
1036 + act(() => {
1037 + root.render(<textarea value="something" disabled={true} />);
1038 + }),
1039 + ).resolves.not.toThrow();
1040 });
1041
792 - it('should not warn about missing onChange if value is not set', () => {
793 - expect(() => {
794 - ReactTestUtils.renderIntoDocument(
795 - <textarea value="something" readOnly={true} />,
796 - );
797 - }).not.toThrow();
1042 + it('should not warn about missing onChange if value is not set', async () => {
1043 + const container = document.createElement('div');
1044 + const root = ReactDOMClient.createRoot(container);
1045 + await expect(
1046 + act(() => {
1047 + root.render(<textarea value="something" readOnly={true} />);
1048 + }),
1049 + ).resolves.not.toThrow();
1050 });
1051
800 - it('should warn about missing onChange if value is false', () => {
801 - expect(() =>
802 - ReactTestUtils.renderIntoDocument(<textarea value={false} />),
803 - ).toErrorDev(
1052 + it('should warn about missing onChange if value is false', async () => {
1053 + const container = document.createElement('div');
1054 + const root = ReactDOMClient.createRoot(container);
1055 + await expect(async () => {
1056 + await act(() => {
1057 + root.render(<textarea value={false} />);
1058 + });
1059 + }).toErrorDev(
1060 'Warning: You provided a `value` prop to a form ' +
1061 'field without an `onChange` handler. This will render a read-only ' +
1062 'field. If the field should be mutable use `defaultValue`. ' +
@@ -808,10 +1064,14 @@ describe('ReactDOMTextarea', () => {
1064 );
1065 });
1066
811 - it('should warn about missing onChange if value is 0', () => {
812 - expect(() =>
813 - ReactTestUtils.renderIntoDocument(<textarea value={0} />),
814 - ).toErrorDev(
1067 + it('should warn about missing onChange if value is 0', async () => {
1068 + const container = document.createElement('div');
1069 + const root = ReactDOMClient.createRoot(container);
1070 + await expect(async () => {
1071 + await act(() => {
1072 + root.render(<textarea value={0} />);
1073 + });
1074 + }).toErrorDev(
1075 'Warning: You provided a `value` prop to a form ' +
1076 'field without an `onChange` handler. This will render a read-only ' +
1077 'field. If the field should be mutable use `defaultValue`. ' +
@@ -819,10 +1079,14 @@ describe('ReactDOMTextarea', () => {
1079 );
1080 });
1081
822 - it('should warn about missing onChange if value is "0"', () => {
823 - expect(() =>
824 - ReactTestUtils.renderIntoDocument(<textarea value="0" />),
825 - ).toErrorDev(
1082 + it('should warn about missing onChange if value is "0"', async () => {
1083 + const container = document.createElement('div');
1084 + const root = ReactDOMClient.createRoot(container);
1085 + await expect(async () => {
1086 + await act(() => {
1087 + root.render(<textarea value="0" />);
1088 + });
1089 + }).toErrorDev(
1090 'Warning: You provided a `value` prop to a form ' +
1091 'field without an `onChange` handler. This will render a read-only ' +
1092 'field. If the field should be mutable use `defaultValue`. ' +
@@ -830,10 +1094,14 @@ describe('ReactDOMTextarea', () => {
1094 );
1095 });
1096
833 - it('should warn about missing onChange if value is ""', () => {
834 - expect(() =>
835 - ReactTestUtils.renderIntoDocument(<textarea value="" />),
836 - ).toErrorDev(
1097 + it('should warn about missing onChange if value is ""', async () => {
1098 + const container = document.createElement('div');
1099 + const root = ReactDOMClient.createRoot(container);
1100 + await expect(async () => {
1101 + await act(() => {
1102 + root.render(<textarea value="" />);
1103 + });
1104 + }).toErrorDev(
1105 'Warning: You provided a `value` prop to a form ' +
1106 'field without an `onChange` handler. This will render a read-only ' +
1107 'field. If the field should be mutable use `defaultValue`. ' +