@samitouri / QOS-React-2 / commits / 8bb6ee1d33

Update ReactUpdates-test (#28061)

## Overview These tests are important for `ReactDOM.render`, so instead of just re-writing them to `createRoot` and losing coverage: - Moved the `.render` tests to `ReactLegacyUpdates` - Re-wrote the tests in `ReactUpdates` to use `createRoot` - Remove `unstable_batchedUpdates` from `ReactUpdates` In a future PR, when I flag `batchedUpdates` with a Noop, I can add the gate to just the tests in `ReactLegacyUpdates`.

Ricky committed Jan 25, 2024 at 01:17 UTC 8bb6ee1d33ca6c7e34342bc4b17aac0449ab6899
2 files changed +2158 -475
packages/react-dom/src/__tests__/ReactLegacyUpdates-test.js new
+1591
@@ -0,0 +1,1591 @@
1 +/**
2 + * Copyright (c) Meta Platforms, Inc. and affiliates.
3 + *
4 + * This source code is licensed under the MIT license found in the
5 + * LICENSE file in the root directory of this source tree.
6 + *
7 + * @emails react-core
8 + */
9 +
10 +'use strict';
11 +
12 +let React;
13 +let ReactDOM;
14 +let ReactTestUtils;
15 +let act;
16 +let Scheduler;
17 +let assertLog;
18 +
19 +// Copy of ReactUpdates using ReactDOM.render and ReactDOM.unstable_batchedUpdates.
20 +// Can be deleted when we remove both.
21 +describe('ReactLegacyUpdates', () => {
22 + beforeEach(() => {
23 + jest.resetModules();
24 + React = require('react');
25 + ReactDOM = require('react-dom');
26 + ReactTestUtils = require('react-dom/test-utils');
27 + act = require('internal-test-utils').act;
28 + Scheduler = require('scheduler');
29 +
30 + const InternalTestUtils = require('internal-test-utils');
31 + assertLog = InternalTestUtils.assertLog;
32 + });
33 +
34 + it('should batch state when updating state twice', () => {
35 + let updateCount = 0;
36 +
37 + class Component extends React.Component {
38 + state = {x: 0};
39 +
40 + componentDidUpdate() {
41 + updateCount++;
42 + }
43 +
44 + render() {
45 + return <div>{this.state.x}</div>;
46 + }
47 + }
48 +
49 + const instance = ReactTestUtils.renderIntoDocument(<Component />);
50 + expect(instance.state.x).toBe(0);
51 +
52 + ReactDOM.unstable_batchedUpdates(function () {
53 + instance.setState({x: 1});
54 + instance.setState({x: 2});
55 + expect(instance.state.x).toBe(0);
56 + expect(updateCount).toBe(0);
57 + });
58 +
59 + expect(instance.state.x).toBe(2);
60 + expect(updateCount).toBe(1);
61 + });
62 +
63 + it('should batch state when updating two different state keys', () => {
64 + let updateCount = 0;
65 +
66 + class Component extends React.Component {
67 + state = {x: 0, y: 0};
68 +
69 + componentDidUpdate() {
70 + updateCount++;
71 + }
72 +
73 + render() {
74 + return <div>{`(${this.state.x}, ${this.state.y})`}</div>;
75 + }
76 + }
77 +
78 + const instance = ReactTestUtils.renderIntoDocument(<Component />);
79 + expect(instance.state.x).toBe(0);
80 + expect(instance.state.y).toBe(0);
81 +
82 + ReactDOM.unstable_batchedUpdates(function () {
83 + instance.setState({x: 1});
84 + instance.setState({y: 2});
85 + expect(instance.state.x).toBe(0);
86 + expect(instance.state.y).toBe(0);
87 + expect(updateCount).toBe(0);
88 + });
89 +
90 + expect(instance.state.x).toBe(1);
91 + expect(instance.state.y).toBe(2);
92 + expect(updateCount).toBe(1);
93 + });
94 +
95 + it('should batch state and props together', () => {
96 + let updateCount = 0;
97 +
98 + class Component extends React.Component {
99 + state = {y: 0};
100 +
101 + componentDidUpdate() {
102 + updateCount++;
103 + }
104 +
105 + render() {
106 + return <div>{`(${this.props.x}, ${this.state.y})`}</div>;
107 + }
108 + }
109 +
110 + const container = document.createElement('div');
111 + const instance = ReactDOM.render(<Component x={0} />, container);
112 + expect(instance.props.x).toBe(0);
113 + expect(instance.state.y).toBe(0);
114 +
115 + ReactDOM.unstable_batchedUpdates(function () {
116 + ReactDOM.render(<Component x={1} />, container);
117 + instance.setState({y: 2});
118 + expect(instance.props.x).toBe(0);
119 + expect(instance.state.y).toBe(0);
120 + expect(updateCount).toBe(0);
121 + });
122 +
123 + expect(instance.props.x).toBe(1);
124 + expect(instance.state.y).toBe(2);
125 + expect(updateCount).toBe(1);
126 + });
127 +
128 + it('should batch parent/child state updates together', () => {
129 + let parentUpdateCount = 0;
130 +
131 + class Parent extends React.Component {
132 + state = {x: 0};
133 + childRef = React.createRef();
134 +
135 + componentDidUpdate() {
136 + parentUpdateCount++;
137 + }
138 +
139 + render() {
140 + return (
141 + <div>
142 + <Child ref={this.childRef} x={this.state.x} />
143 + </div>
144 + );
145 + }
146 + }
147 +
148 + let childUpdateCount = 0;
149 +
150 + class Child extends React.Component {
151 + state = {y: 0};
152 +
153 + componentDidUpdate() {
154 + childUpdateCount++;
155 + }
156 +
157 + render() {
158 + return <div>{this.props.x + this.state.y}</div>;
159 + }
160 + }
161 +
162 + const instance = ReactTestUtils.renderIntoDocument(<Parent />);
163 + const child = instance.childRef.current;
164 + expect(instance.state.x).toBe(0);
165 + expect(child.state.y).toBe(0);
166 +
167 + ReactDOM.unstable_batchedUpdates(function () {
168 + instance.setState({x: 1});
169 + child.setState({y: 2});
170 + expect(instance.state.x).toBe(0);
171 + expect(child.state.y).toBe(0);
172 + expect(parentUpdateCount).toBe(0);
173 + expect(childUpdateCount).toBe(0);
174 + });
175 +
176 + expect(instance.state.x).toBe(1);
177 + expect(child.state.y).toBe(2);
178 + expect(parentUpdateCount).toBe(1);
179 + expect(childUpdateCount).toBe(1);
180 + });
181 +
182 + it('should batch child/parent state updates together', () => {
183 + let parentUpdateCount = 0;
184 +
185 + class Parent extends React.Component {
186 + state = {x: 0};
187 + childRef = React.createRef();
188 +
189 + componentDidUpdate() {
190 + parentUpdateCount++;
191 + }
192 +
193 + render() {
194 + return (
195 + <div>
196 + <Child ref={this.childRef} x={this.state.x} />
197 + </div>
198 + );
199 + }
200 + }
201 +
202 + let childUpdateCount = 0;
203 +
204 + class Child extends React.Component {
205 + state = {y: 0};
206 +
207 + componentDidUpdate() {
208 + childUpdateCount++;
209 + }
210 +
211 + render() {
212 + return <div>{this.props.x + this.state.y}</div>;
213 + }
214 + }
215 +
216 + const instance = ReactTestUtils.renderIntoDocument(<Parent />);
217 + const child = instance.childRef.current;
218 + expect(instance.state.x).toBe(0);
219 + expect(child.state.y).toBe(0);
220 +
221 + ReactDOM.unstable_batchedUpdates(function () {
222 + child.setState({y: 2});
223 + instance.setState({x: 1});
224 + expect(instance.state.x).toBe(0);
225 + expect(child.state.y).toBe(0);
226 + expect(parentUpdateCount).toBe(0);
227 + expect(childUpdateCount).toBe(0);
228 + });
229 +
230 + expect(instance.state.x).toBe(1);
231 + expect(child.state.y).toBe(2);
232 + expect(parentUpdateCount).toBe(1);
233 +
234 + // Batching reduces the number of updates here to 1.
235 + expect(childUpdateCount).toBe(1);
236 + });
237 +
238 + it('should support chained state updates', () => {
239 + let updateCount = 0;
240 +
241 + class Component extends React.Component {
242 + state = {x: 0};
243 +
244 + componentDidUpdate() {
245 + updateCount++;
246 + }
247 +
248 + render() {
249 + return <div>{this.state.x}</div>;
250 + }
251 + }
252 +
253 + const instance = ReactTestUtils.renderIntoDocument(<Component />);
254 + expect(instance.state.x).toBe(0);
255 +
256 + let innerCallbackRun = false;
257 + ReactDOM.unstable_batchedUpdates(function () {
258 + instance.setState({x: 1}, function () {
259 + instance.setState({x: 2}, function () {
260 + expect(this).toBe(instance);
261 + innerCallbackRun = true;
262 + expect(instance.state.x).toBe(2);
263 + expect(updateCount).toBe(2);
264 + });
265 + expect(instance.state.x).toBe(1);
266 + expect(updateCount).toBe(1);
267 + });
268 + expect(instance.state.x).toBe(0);
269 + expect(updateCount).toBe(0);
270 + });
271 +
272 + expect(innerCallbackRun).toBeTruthy();
273 + expect(instance.state.x).toBe(2);
274 + expect(updateCount).toBe(2);
275 + });
276 +
277 + it('should batch forceUpdate together', () => {
278 + let shouldUpdateCount = 0;
279 + let updateCount = 0;
280 +
281 + class Component extends React.Component {
282 + state = {x: 0};
283 +
284 + shouldComponentUpdate() {
285 + shouldUpdateCount++;
286 + }
287 +
288 + componentDidUpdate() {
289 + updateCount++;
290 + }
291 +
292 + render() {
293 + return <div>{this.state.x}</div>;
294 + }
295 + }
296 +
297 + const instance = ReactTestUtils.renderIntoDocument(<Component />);
298 + expect(instance.state.x).toBe(0);
299 +
300 + let callbacksRun = 0;
301 + ReactDOM.unstable_batchedUpdates(function () {
302 + instance.setState({x: 1}, function () {
303 + callbacksRun++;
304 + });
305 + instance.forceUpdate(function () {
306 + callbacksRun++;
307 + });
308 + expect(instance.state.x).toBe(0);
309 + expect(updateCount).toBe(0);
310 + });
311 +
312 + expect(callbacksRun).toBe(2);
313 + // shouldComponentUpdate shouldn't be called since we're forcing
314 + expect(shouldUpdateCount).toBe(0);
315 + expect(instance.state.x).toBe(1);
316 + expect(updateCount).toBe(1);
317 + });
318 +
319 + it('should update children even if parent blocks updates', () => {
320 + let parentRenderCount = 0;
321 + let childRenderCount = 0;
322 +
323 + class Parent extends React.Component {
324 + childRef = React.createRef();
325 +
326 + shouldComponentUpdate() {
327 + return false;
328 + }
329 +
330 + render() {
331 + parentRenderCount++;
332 + return <Child ref={this.childRef} />;
333 + }
334 + }
335 +
336 + class Child extends React.Component {
337 + render() {
338 + childRenderCount++;
339 + return <div />;
340 + }
341 + }
342 +
343 + expect(parentRenderCount).toBe(0);
344 + expect(childRenderCount).toBe(0);
345 +
346 + let instance = <Parent />;
347 + instance = ReactTestUtils.renderIntoDocument(instance);
348 +
349 + expect(parentRenderCount).toBe(1);
350 + expect(childRenderCount).toBe(1);
351 +
352 + ReactDOM.unstable_batchedUpdates(function () {
353 + instance.setState({x: 1});
354 + });
355 +
356 + expect(parentRenderCount).toBe(1);
357 + expect(childRenderCount).toBe(1);
358 +
359 + ReactDOM.unstable_batchedUpdates(function () {
360 + instance.childRef.current.setState({x: 1});
361 + });
362 +
363 + expect(parentRenderCount).toBe(1);
364 + expect(childRenderCount).toBe(2);
365 + });
366 +
367 + it('should not reconcile children passed via props', () => {
368 + let numMiddleRenders = 0;
369 + let numBottomRenders = 0;
370 +
371 + class Top extends React.Component {
372 + render() {
373 + return (
374 + <Middle>
375 + <Bottom />
376 + </Middle>
377 + );
378 + }
379 + }
380 +
381 + class Middle extends React.Component {
382 + componentDidMount() {
383 + this.forceUpdate();
384 + }
385 +
386 + render() {
387 + numMiddleRenders++;
388 + return React.Children.only(this.props.children);
389 + }
390 + }
391 +
392 + class Bottom extends React.Component {
393 + render() {
394 + numBottomRenders++;
395 + return null;
396 + }
397 + }
398 +
399 + ReactTestUtils.renderIntoDocument(<Top />);
400 + expect(numMiddleRenders).toBe(2);
401 + expect(numBottomRenders).toBe(1);
402 + });
403 +
404 + it('should flow updates correctly', () => {
405 + let willUpdates = [];
406 + let didUpdates = [];
407 +
408 + const UpdateLoggingMixin = {
409 + UNSAFE_componentWillUpdate: function () {
410 + willUpdates.push(this.constructor.displayName);
411 + },
412 + componentDidUpdate: function () {
413 + didUpdates.push(this.constructor.displayName);
414 + },
415 + };
416 +
417 + class Box extends React.Component {
418 + boxDivRef = React.createRef();
419 +
420 + render() {
421 + return <div ref={this.boxDivRef}>{this.props.children}</div>;
422 + }
423 + }
424 + Object.assign(Box.prototype, UpdateLoggingMixin);
425 +
426 + class Child extends React.Component {
427 + spanRef = React.createRef();
428 +
429 + render() {
430 + return <span ref={this.spanRef}>child</span>;
431 + }
432 + }
433 + Object.assign(Child.prototype, UpdateLoggingMixin);
434 +
435 + class Switcher extends React.Component {
436 + state = {tabKey: 'hello'};
437 + boxRef = React.createRef();
438 + switcherDivRef = React.createRef();
439 + render() {
440 + const child = this.props.children;
441 +
442 + return (
443 + <Box ref={this.boxRef}>
444 + <div
445 + ref={this.switcherDivRef}
446 + style={{
447 + display: this.state.tabKey === child.key ? '' : 'none',
448 + }}>
449 + {child}
450 + </div>
451 + </Box>
452 + );
453 + }
454 + }
455 + Object.assign(Switcher.prototype, UpdateLoggingMixin);
456 +
457 + class App extends React.Component {
458 + switcherRef = React.createRef();
459 + childRef = React.createRef();
460 +
461 + render() {
462 + return (
463 + <Switcher ref={this.switcherRef}>
464 + <Child key="hello" ref={this.childRef} />
465 + </Switcher>
466 + );
467 + }
468 + }
469 + Object.assign(App.prototype, UpdateLoggingMixin);
470 +
471 + let root = <App />;
472 + root = ReactTestUtils.renderIntoDocument(root);
473 +
474 + function expectUpdates(desiredWillUpdates, desiredDidUpdates) {
475 + let i;
476 + for (i = 0; i < desiredWillUpdates; i++) {
477 + expect(willUpdates).toContain(desiredWillUpdates[i]);
478 + }
479 + for (i = 0; i < desiredDidUpdates; i++) {
480 + expect(didUpdates).toContain(desiredDidUpdates[i]);
481 + }
482 + willUpdates = [];
483 + didUpdates = [];
484 + }
485 +
486 + function triggerUpdate(c) {
487 + c.setState({x: 1});
488 + }
489 +
490 + function testUpdates(components, desiredWillUpdates, desiredDidUpdates) {
491 + let i;
492 +
493 + ReactDOM.unstable_batchedUpdates(function () {
494 + for (i = 0; i < components.length; i++) {
495 + triggerUpdate(components[i]);
496 + }
497 + });
498 +
499 + expectUpdates(desiredWillUpdates, desiredDidUpdates);
500 +
501 + // Try them in reverse order
502 +
503 + ReactDOM.unstable_batchedUpdates(function () {
504 + for (i = components.length - 1; i >= 0; i--) {
505 + triggerUpdate(components[i]);
506 + }
507 + });
508 +
509 + expectUpdates(desiredWillUpdates, desiredDidUpdates);
510 + }
511 + testUpdates(
512 + [root.switcherRef.current.boxRef.current, root.switcherRef.current],
513 + // Owner-child relationships have inverse will and did
514 + ['Switcher', 'Box'],
515 + ['Box', 'Switcher'],
516 + );
517 +
518 + testUpdates(
519 + [root.childRef.current, root.switcherRef.current.boxRef.current],
520 + // Not owner-child so reconcile independently
521 + ['Box', 'Child'],
522 + ['Box', 'Child'],
523 + );
524 +
525 + testUpdates(
526 + [root.childRef.current, root.switcherRef.current],
527 + // Switcher owns Box and Child, Box does not own Child
528 + ['Switcher', 'Box', 'Child'],
529 + ['Box', 'Switcher', 'Child'],
530 + );
531 + });
532 +
533 + it('should queue mount-ready handlers across different roots', () => {
534 + // We'll define two components A and B, then update both of them. When A's
535 + // componentDidUpdate handlers is called, B's DOM should already have been
536 + // updated.
537 +
538 + const bContainer = document.createElement('div');
539 +
540 + let b;
541 +
542 + let aUpdated = false;
543 +
544 + class A extends React.Component {
545 + state = {x: 0};
546 +
547 + componentDidUpdate() {
548 + expect(ReactDOM.findDOMNode(b).textContent).toBe('B1');
549 + aUpdated = true;
550 + }
551 +
552 + render() {
553 + let portal = null;
554 + // If we're using Fiber, we use Portals instead to achieve this.
555 + portal = ReactDOM.createPortal(<B ref={n => (b = n)} />, bContainer);
556 + return (
557 + <div>
558 + A{this.state.x}
559 + {portal}
560 + </div>
561 + );
562 + }
563 + }
564 +
565 + class B extends React.Component {
566 + state = {x: 0};
567 +
568 + render() {
569 + return <div>B{this.state.x}</div>;
570 + }
571 + }
572 +
573 + const a = ReactTestUtils.renderIntoDocument(<A />);
574 + ReactDOM.unstable_batchedUpdates(function () {
575 + a.setState({x: 1});
576 + b.setState({x: 1});
577 + });
578 +
579 + expect(aUpdated).toBe(true);
580 + });
581 +
582 + it('should flush updates in the correct order', () => {
583 + const updates = [];
584 +
585 + class Outer extends React.Component {
586 + state = {x: 0};
587 + innerRef = React.createRef();
588 +
589 + render() {
590 + updates.push('Outer-render-' + this.state.x);
591 + return (
592 + <div>
593 + <Inner x={this.state.x} ref={this.innerRef} />
594 + </div>
595 + );
596 + }
597 +
598 + componentDidUpdate() {
599 + const x = this.state.x;
600 + updates.push('Outer-didUpdate-' + x);
601 + updates.push('Inner-setState-' + x);
602 + this.innerRef.current.setState({x: x}, function () {
603 + updates.push('Inner-callback-' + x);
604 + });
605 + }
606 + }
607 +
608 + class Inner extends React.Component {
609 + state = {x: 0};
610 +
611 + render() {
612 + updates.push('Inner-render-' + this.props.x + '-' + this.state.x);
613 + return <div />;
614 + }
615 +
616 + componentDidUpdate() {
617 + updates.push('Inner-didUpdate-' + this.props.x + '-' + this.state.x);
618 + }
619 + }
620 +
621 + const instance = ReactTestUtils.renderIntoDocument(<Outer />);
622 +
623 + updates.push('Outer-setState-1');
624 + instance.setState({x: 1}, function () {
625 + updates.push('Outer-callback-1');
626 + updates.push('Outer-setState-2');
627 + instance.setState({x: 2}, function () {
628 + updates.push('Outer-callback-2');
629 + });
630 + });
631 +
632 + /* eslint-disable indent */
633 + expect(updates).toEqual([
634 + 'Outer-render-0',
635 + 'Inner-render-0-0',
636 +
637 + 'Outer-setState-1',
638 + 'Outer-render-1',
639 + 'Inner-render-1-0',
640 + 'Inner-didUpdate-1-0',
641 + 'Outer-didUpdate-1',
642 + // Happens in a batch, so don't re-render yet
643 + 'Inner-setState-1',
644 + 'Outer-callback-1',
645 +
646 + // Happens in a batch
647 + 'Outer-setState-2',
648 +
649 + // Flush batched updates all at once
650 + 'Outer-render-2',
651 + 'Inner-render-2-1',
652 + 'Inner-didUpdate-2-1',
653 + 'Inner-callback-1',
654 + 'Outer-didUpdate-2',
655 + 'Inner-setState-2',
656 + 'Outer-callback-2',
657 + 'Inner-render-2-2',
658 + 'Inner-didUpdate-2-2',
659 + 'Inner-callback-2',
660 + ]);
661 + /* eslint-enable indent */
662 + });
663 +
664 + it('should flush updates in the correct order across roots', () => {
665 + const instances = [];
666 + const updates = [];
667 +
668 + class MockComponent extends React.Component {
669 + render() {
670 + updates.push(this.props.depth);
671 + return <div />;
672 + }
673 +
674 + componentDidMount() {
675 + instances.push(this);
676 + if (this.props.depth < this.props.count) {
677 + ReactDOM.render(
678 + <MockComponent
679 + depth={this.props.depth + 1}
680 + count={this.props.count}
681 + />,
682 + ReactDOM.findDOMNode(this),
683 + );
684 + }
685 + }
686 + }
687 +
688 + ReactTestUtils.renderIntoDocument(<MockComponent depth={0} count={2} />);
689 +
690 + expect(updates).toEqual([0, 1, 2]);
691 +
692 + ReactDOM.unstable_batchedUpdates(function () {
693 + // Simulate update on each component from top to bottom.
694 + instances.forEach(function (instance) {
695 + instance.forceUpdate();
696 + });
697 + });
698 +
699 + expect(updates).toEqual([0, 1, 2, 0, 1, 2]);
700 + });
701 +
702 + it('should queue nested updates', () => {
703 + // See https://github.com/facebook/react/issues/1147
704 +
705 + class X extends React.Component {
706 + state = {s: 0};
707 +
708 + render() {
709 + if (this.state.s === 0) {
710 + return (
711 + <div>
712 + <span>0</span>
713 + </div>
714 + );
715 + } else {
716 + return <div>1</div>;
717 + }
718 + }
719 +
720 + go = () => {
721 + this.setState({s: 1});
722 + this.setState({s: 0});
723 + this.setState({s: 1});
724 + };
725 + }
726 +
727 + class Y extends React.Component {
728 + render() {
729 + return (
730 + <div>
731 + <Z />
732 + </div>
733 + );
734 + }
735 + }
736 +
737 + class Z extends React.Component {
738 + render() {
739 + return <div />;
740 + }
741 +
742 + UNSAFE_componentWillUpdate() {
743 + x.go();
744 + }
745 + }
746 +
747 + const x = ReactTestUtils.renderIntoDocument(<X />);
748 + const y = ReactTestUtils.renderIntoDocument(<Y />);
749 + expect(ReactDOM.findDOMNode(x).textContent).toBe('0');
750 +
751 + y.forceUpdate();
752 + expect(ReactDOM.findDOMNode(x).textContent).toBe('1');
753 + });
754 +
755 + it('should queue updates from during mount', () => {
756 + // See https://github.com/facebook/react/issues/1353
757 + let a;
758 +
759 + class A extends React.Component {
760 + state = {x: 0};
761 +
762 + UNSAFE_componentWillMount() {
763 + a = this;
764 + }
765 +
766 + render() {
767 + return <div>A{this.state.x}</div>;
768 + }
769 + }
770 +
771 + class B extends React.Component {
772 + UNSAFE_componentWillMount() {
773 + a.setState({x: 1});
774 + }
775 +
776 + render() {
777 + return <div />;
778 + }
779 + }
780 +
781 + ReactDOM.unstable_batchedUpdates(function () {
782 + ReactTestUtils.renderIntoDocument(
783 + <div>
784 + <A />
785 + <B />
786 + </div>,
787 + );
788 + });
789 +
790 + expect(a.state.x).toBe(1);
791 + expect(ReactDOM.findDOMNode(a).textContent).toBe('A1');
792 + });
793 +
794 + it('calls componentWillReceiveProps setState callback properly', () => {
795 + let callbackCount = 0;
796 +
797 + class A extends React.Component {
798 + state = {x: this.props.x};
799 +
800 + UNSAFE_componentWillReceiveProps(nextProps) {
801 + const newX = nextProps.x;
802 + this.setState({x: newX}, function () {
803 + // State should have updated by the time this callback gets called
804 + expect(this.state.x).toBe(newX);
805 + callbackCount++;
806 + });
807 + }
808 +
809 + render() {
810 + return <div>{this.state.x}</div>;
811 + }
812 + }
813 +
814 + const container = document.createElement('div');
815 + ReactDOM.render(<A x={1} />, container);
816 + ReactDOM.render(<A x={2} />, container);
817 + expect(callbackCount).toBe(1);
818 + });
819 +
820 + it('does not call render after a component as been deleted', () => {
821 + let renderCount = 0;
822 + let componentB = null;
823 +
824 + class B extends React.Component {
825 + state = {updates: 0};
826 +
827 + componentDidMount() {
828 + componentB = this;
829 + }
830 +
831 + render() {
832 + renderCount++;
833 + return <div />;
834 + }
835 + }
836 +
837 + class A extends React.Component {
838 + state = {showB: true};
839 +
840 + render() {
841 + return this.state.showB ? <B /> : <div />;
842 + }
843 + }
844 +
845 + const component = ReactTestUtils.renderIntoDocument(<A />);
846 +
847 + ReactDOM.unstable_batchedUpdates(function () {
848 + // B will have scheduled an update but the batching should ensure that its
849 + // update never fires.
850 + componentB.setState({updates: 1});
851 + component.setState({showB: false});
852 + });
853 +
854 + expect(renderCount).toBe(1);
855 + });
856 +
857 + it('throws in setState if the update callback is not a function', () => {
858 + function Foo() {
859 + this.a = 1;
860 + this.b = 2;
861 + }
862 +
863 + class A extends React.Component {
864 + state = {};
865 +
866 + render() {
867 + return <div />;
868 + }
869 + }
870 +
871 + let component = ReactTestUtils.renderIntoDocument(<A />);
872 +
873 + expect(() => {
874 + expect(() => component.setState({}, 'no')).toErrorDev(
875 + 'setState(...): Expected the last optional `callback` argument to be ' +
876 + 'a function. Instead received: no.',
877 + );
878 + }).toThrowError(
879 + 'Invalid argument passed as callback. Expected a function. Instead ' +
880 + 'received: no',
881 + );
882 + component = ReactTestUtils.renderIntoDocument(<A />);
883 + expect(() => {
884 + expect(() => component.setState({}, {foo: 'bar'})).toErrorDev(
885 + 'setState(...): Expected the last optional `callback` argument to be ' +
886 + 'a function. Instead received: [object Object].',
887 + );
888 + }).toThrowError(
889 + 'Invalid argument passed as callback. Expected a function. Instead ' +
890 + 'received: [object Object]',
891 + );
892 + // Make sure the warning is deduplicated and doesn't fire again
893 + component = ReactTestUtils.renderIntoDocument(<A />);
894 + expect(() => component.setState({}, new Foo())).toThrowError(
895 + 'Invalid argument passed as callback. Expected a function. Instead ' +
896 + 'received: [object Object]',
897 + );
898 + });
899 +
900 + it('throws in forceUpdate if the update callback is not a function', () => {
901 + function Foo() {
902 + this.a = 1;
903 + this.b = 2;
904 + }
905 +
906 + class A extends React.Component {
907 + state = {};
908 +
909 + render() {
910 + return <div />;
911 + }
912 + }
913 +
914 + let component = ReactTestUtils.renderIntoDocument(<A />);
915 +
916 + expect(() => {
917 + expect(() => component.forceUpdate('no')).toErrorDev(
918 + 'forceUpdate(...): Expected the last optional `callback` argument to be ' +
919 + 'a function. Instead received: no.',
920 + );
921 + }).toThrowError(
922 + 'Invalid argument passed as callback. Expected a function. Instead ' +
923 + 'received: no',
924 + );
925 + component = ReactTestUtils.renderIntoDocument(<A />);
926 + expect(() => {
927 + expect(() => component.forceUpdate({foo: 'bar'})).toErrorDev(
928 + 'forceUpdate(...): Expected the last optional `callback` argument to be ' +
929 + 'a function. Instead received: [object Object].',
930 + );
931 + }).toThrowError(
932 + 'Invalid argument passed as callback. Expected a function. Instead ' +
933 + 'received: [object Object]',
934 + );
935 + // Make sure the warning is deduplicated and doesn't fire again
936 + component = ReactTestUtils.renderIntoDocument(<A />);
937 + expect(() => component.forceUpdate(new Foo())).toThrowError(
938 + 'Invalid argument passed as callback. Expected a function. Instead ' +
939 + 'received: [object Object]',
940 + );
941 + });
942 +
943 + it('does not update one component twice in a batch (#2410)', () => {
944 + class Parent extends React.Component {
945 + childRef = React.createRef();
946 +
947 + getChild = () => {
948 + return this.childRef.current;
949 + };
950 +
951 + render() {
952 + return <Child ref={this.childRef} />;
953 + }
954 + }
955 +
956 + let renderCount = 0;
957 + let postRenderCount = 0;
958 + let once = false;
959 +
960 + class Child extends React.Component {
961 + state = {updated: false};
962 +
963 + UNSAFE_componentWillUpdate() {
964 + if (!once) {
965 + once = true;
966 + this.setState({updated: true});
967 + }
968 + }
969 +
970 + componentDidMount() {
971 + expect(renderCount).toBe(postRenderCount + 1);
972 + postRenderCount++;
973 + }
974 +
975 + componentDidUpdate() {
976 + expect(renderCount).toBe(postRenderCount + 1);
977 + postRenderCount++;
978 + }
979 +
980 + render() {
981 + expect(renderCount).toBe(postRenderCount);
982 + renderCount++;
983 + return <div />;
984 + }
985 + }
986 +
987 + const parent = ReactTestUtils.renderIntoDocument(<Parent />);
988 + const child = parent.getChild();
989 + ReactDOM.unstable_batchedUpdates(function () {
990 + parent.forceUpdate();
991 + child.forceUpdate();
992 + });
993 + });
994 +
995 + it('does not update one component twice in a batch (#6371)', () => {
996 + let callbacks = [];
997 + function emitChange() {
998 + callbacks.forEach(c => c());
999 + }
1000 +
1001 + class App extends React.Component {
1002 + constructor(props) {
1003 + super(props);
1004 + this.state = {showChild: true};
1005 + }
1006 + componentDidMount() {
1007 + this.setState({showChild: false});
1008 + }
1009 + render() {
1010 + return (
1011 + <div>
1012 + <ForceUpdatesOnChange />
1013 + {this.state.showChild && <EmitsChangeOnUnmount />}
1014 + </div>
1015 + );
1016 + }
1017 + }
1018 +
1019 + class EmitsChangeOnUnmount extends React.Component {
1020 + componentWillUnmount() {
1021 + emitChange();
1022 + }
1023 + render() {
1024 + return null;
1025 + }
1026 + }
1027 +
1028 + class ForceUpdatesOnChange extends React.Component {
1029 + componentDidMount() {
1030 + this.onChange = () => this.forceUpdate();
1031 + this.onChange();
1032 + callbacks.push(this.onChange);
1033 + }
1034 + componentWillUnmount() {
1035 + callbacks = callbacks.filter(c => c !== this.onChange);
1036 + }
1037 + render() {
1038 + return <div key={Math.random()} onClick={function () {}} />;
1039 + }
1040 + }
1041 +
1042 + ReactDOM.render(<App />, document.createElement('div'));
1043 + });
1044 +
1045 + it('unstable_batchedUpdates should return value from a callback', () => {
1046 + const result = ReactDOM.unstable_batchedUpdates(function () {
1047 + return 42;
1048 + });
1049 + expect(result).toEqual(42);
1050 + });
1051 +
1052 + it('unmounts and remounts a root in the same batch', () => {
1053 + const container = document.createElement('div');
1054 + ReactDOM.render(<span>a</span>, container);
1055 + ReactDOM.unstable_batchedUpdates(function () {
1056 + ReactDOM.unmountComponentAtNode(container);
1057 + ReactDOM.render(<span>b</span>, container);
1058 + });
1059 + expect(container.textContent).toBe('b');
1060 + });
1061 +
1062 + it('handles reentrant mounting in synchronous mode', () => {
1063 + let mounts = 0;
1064 + class Editor extends React.Component {
1065 + render() {
1066 + return <div>{this.props.text}</div>;
1067 + }
1068 + componentDidMount() {
1069 + mounts++;
1070 + // This should be called only once but we guard just in case.
1071 + if (!this.props.rendered) {
1072 + this.props.onChange({rendered: true});
1073 + }
1074 + }
1075 + }
1076 +
1077 + const container = document.createElement('div');
1078 + function render() {
1079 + ReactDOM.render(
1080 + <Editor
1081 + onChange={newProps => {
1082 + props = {...props, ...newProps};
1083 + render();
1084 + }}
1085 + {...props}
1086 + />,
1087 + container,
1088 + );
1089 + }
1090 +
1091 + let props = {text: 'hello', rendered: false};
1092 + render();
1093 + props = {...props, text: 'goodbye'};
1094 + render();
1095 + expect(container.textContent).toBe('goodbye');
1096 + expect(mounts).toBe(1);
1097 + });
1098 +
1099 + it('mounts and unmounts are sync even in a batch', () => {
1100 + const ops = [];
1101 + const container = document.createElement('div');
1102 + ReactDOM.unstable_batchedUpdates(() => {
1103 + ReactDOM.render(<div>Hello</div>, container);
1104 + ops.push(container.textContent);
1105 + ReactDOM.unmountComponentAtNode(container);
1106 + ops.push(container.textContent);
1107 + });
1108 + expect(ops).toEqual(['Hello', '']);
1109 + });
1110 +
1111 + it(
1112 + 'in legacy mode, updates in componentWillUpdate and componentDidUpdate ' +
1113 + 'should both flush in the immediately subsequent commit',
1114 + () => {
1115 + const ops = [];
1116 + class Foo extends React.Component {
1117 + state = {a: false, b: false};
1118 + UNSAFE_componentWillUpdate(_, nextState) {
1119 + if (!nextState.a) {
1120 + this.setState({a: true});
1121 + }
1122 + }
1123 + componentDidUpdate() {
1124 + ops.push('Foo updated');
1125 + if (!this.state.b) {
1126 + this.setState({b: true});
1127 + }
1128 + }
1129 + render() {
1130 + ops.push(`a: ${this.state.a}, b: ${this.state.b}`);
1131 + return null;
1132 + }
1133 + }
1134 +
1135 + const container = document.createElement('div');
1136 + // Mount
1137 + ReactDOM.render(<Foo />, container);
1138 + // Root update
1139 + ReactDOM.render(<Foo />, container);
1140 + expect(ops).toEqual([
1141 + // Mount
1142 + 'a: false, b: false',
1143 + // Root update
1144 + 'a: false, b: false',
1145 + 'Foo updated',
1146 + // Subsequent update (both a and b should have flushed)
1147 + 'a: true, b: true',
1148 + 'Foo updated',
1149 + // There should not be any additional updates
1150 + ]);
1151 + },
1152 + );
1153 +
1154 + it(
1155 + 'in legacy mode, updates in componentWillUpdate and componentDidUpdate ' +
1156 + '(on a sibling) should both flush in the immediately subsequent commit',
1157 + () => {
1158 + const ops = [];
1159 + class Foo extends React.Component {
1160 + state = {a: false};
1161 + UNSAFE_componentWillUpdate(_, nextState) {
1162 + if (!nextState.a) {
1163 + this.setState({a: true});
1164 + }
1165 + }
1166 + componentDidUpdate() {
1167 + ops.push('Foo updated');
1168 + }
1169 + render() {
1170 + ops.push(`a: ${this.state.a}`);
1171 + return null;
1172 + }
1173 + }
1174 +
1175 + class Bar extends React.Component {
1176 + state = {b: false};
1177 + componentDidUpdate() {
1178 + ops.push('Bar updated');
1179 + if (!this.state.b) {
1180 + this.setState({b: true});
1181 + }
1182 + }
1183 + render() {
1184 + ops.push(`b: ${this.state.b}`);
1185 + return null;
1186 + }
1187 + }
1188 +
1189 + const container = document.createElement('div');
1190 + // Mount
1191 + ReactDOM.render(
1192 + <div>
1193 + <Foo />
1194 + <Bar />
1195 + </div>,
1196 + container,
1197 + );
1198 + // Root update
1199 + ReactDOM.render(
1200 + <div>
1201 + <Foo />
1202 + <Bar />
1203 + </div>,
1204 + container,
1205 + );
1206 + expect(ops).toEqual([
1207 + // Mount
1208 + 'a: false',
1209 + 'b: false',
1210 + // Root update
1211 + 'a: false',
1212 + 'b: false',
1213 + 'Foo updated',
1214 + 'Bar updated',
1215 + // Subsequent update (both a and b should have flushed)
1216 + 'a: true',
1217 + 'b: true',
1218 + 'Foo updated',
1219 + 'Bar updated',
1220 + // There should not be any additional updates
1221 + ]);
1222 + },
1223 + );
1224 +
1225 + it('uses correct base state for setState inside render phase', () => {
1226 + const ops = [];
1227 +
1228 + class Foo extends React.Component {
1229 + state = {step: 0};
1230 + render() {
1231 + const memoizedStep = this.state.step;
1232 + this.setState(baseState => {
1233 + const baseStep = baseState.step;
1234 + ops.push(`base: ${baseStep}, memoized: ${memoizedStep}`);
1235 + return baseStep === 0 ? {step: 1} : null;
1236 + });
1237 + return null;
1238 + }
1239 + }
1240 +
1241 + const container = document.createElement('div');
1242 + expect(() => ReactDOM.render(<Foo />, container)).toErrorDev(
1243 + 'Cannot update during an existing state transition',
1244 + );
1245 + expect(ops).toEqual(['base: 0, memoized: 0', 'base: 1, memoized: 1']);
1246 + });
1247 +
1248 + it('does not re-render if state update is null', () => {
1249 + const container = document.createElement('div');
1250 +
1251 + let instance;
1252 + let ops = [];
1253 + class Foo extends React.Component {
1254 + render() {
1255 + instance = this;
1256 + ops.push('render');
1257 + return <div />;
1258 + }
1259 + }
1260 + ReactDOM.render(<Foo />, container);
1261 +
1262 + ops = [];
1263 + instance.setState(() => null);
1264 + expect(ops).toEqual([]);
1265 + });
1266 +
1267 + // Will change once we switch to async by default
1268 + it('synchronously renders hidden subtrees', () => {
1269 + const container = document.createElement('div');
1270 + let ops = [];
1271 +
1272 + function Baz() {
1273 + ops.push('Baz');
1274 + return null;
1275 + }
1276 +
1277 + function Bar() {
1278 + ops.push('Bar');
1279 + return null;
1280 + }
1281 +
1282 + function Foo() {
1283 + ops.push('Foo');
1284 + return (
1285 + <div>
1286 + <div hidden={true}>
1287 + <Bar />
1288 + </div>
1289 + <Baz />
1290 + </div>
1291 + );
1292 + }
1293 +
1294 + // Mount
1295 + ReactDOM.render(<Foo />, container);
1296 + expect(ops).toEqual(['Foo', 'Bar', 'Baz']);
1297 + ops = [];
1298 +
1299 + // Update
1300 + ReactDOM.render(<Foo />, container);
1301 + expect(ops).toEqual(['Foo', 'Bar', 'Baz']);
1302 + });
1303 +
1304 + it('can render ridiculously large number of roots without triggering infinite update loop error', () => {
1305 + class Foo extends React.Component {
1306 + componentDidMount() {
1307 + const limit = 1200;
1308 + for (let i = 0; i < limit; i++) {
1309 + if (i < limit - 1) {
1310 + ReactDOM.render(<div />, document.createElement('div'));
1311 + } else {
1312 + ReactDOM.render(<div />, document.createElement('div'), () => {
1313 + // The "nested update limit" error isn't thrown until setState
1314 + this.setState({});
1315 + });
1316 + }
1317 + }
1318 + }
1319 + render() {
1320 + return null;
1321 + }
1322 + }
1323 +
1324 + const container = document.createElement('div');
1325 + ReactDOM.render(<Foo />, container);
1326 + });
1327 +
1328 + it('resets the update counter for unrelated updates', () => {
1329 + const container = document.createElement('div');
1330 + const ref = React.createRef();
1331 +
1332 + class EventuallyTerminating extends React.Component {
1333 + state = {step: 0};
1334 + componentDidMount() {
1335 + this.setState({step: 1});
1336 + }
1337 + componentDidUpdate() {
1338 + if (this.state.step < limit) {
1339 + this.setState({step: this.state.step + 1});
1340 + }
1341 + }
1342 + render() {
1343 + return this.state.step;
1344 + }
1345 + }
1346 +
1347 + let limit = 55;
1348 + expect(() => {
1349 + ReactDOM.render(<EventuallyTerminating ref={ref} />, container);
1350 + }).toThrow('Maximum');
1351 +
1352 + // Verify that we don't go over the limit if these updates are unrelated.
1353 + limit -= 10;
1354 + ReactDOM.render(<EventuallyTerminating ref={ref} />, container);
1355 + expect(container.textContent).toBe(limit.toString());
1356 + ref.current.setState({step: 0});
1357 + expect(container.textContent).toBe(limit.toString());
1358 + ref.current.setState({step: 0});
1359 + expect(container.textContent).toBe(limit.toString());
1360 +
1361 + limit += 10;
1362 + expect(() => {
1363 + ref.current.setState({step: 0});
1364 + }).toThrow('Maximum');
1365 + expect(ref.current).toBe(null);
1366 + });
1367 +
1368 + it('does not fall into an infinite update loop', () => {
1369 + class NonTerminating extends React.Component {
1370 + state = {step: 0};
1371 + componentDidMount() {
1372 + this.setState({step: 1});
1373 + }
1374 + UNSAFE_componentWillUpdate() {
1375 + this.setState({step: 2});
1376 + }
1377 + render() {
1378 + return (
1379 + <div>
1380 + Hello {this.props.name}
1381 + {this.state.step}
1382 + </div>
1383 + );
1384 + }
1385 + }
1386 +
1387 + const container = document.createElement('div');
1388 + expect(() => {
1389 + ReactDOM.render(<NonTerminating />, container);
1390 + }).toThrow('Maximum');
1391 + });
1392 +
1393 + it('does not fall into an infinite update loop with useLayoutEffect', () => {
1394 + function NonTerminating() {
1395 + const [step, setStep] = React.useState(0);
1396 + React.useLayoutEffect(() => {
1397 + setStep(x => x + 1);
1398 + });
1399 + return step;
1400 + }
1401 +
1402 + const container = document.createElement('div');
1403 + expect(() => {
1404 + ReactDOM.render(<NonTerminating />, container);
1405 + }).toThrow('Maximum');
1406 + });
1407 +
1408 + it('can recover after falling into an infinite update loop', () => {
1409 + class NonTerminating extends React.Component {
1410 + state = {step: 0};
1411 + componentDidMount() {
1412 + this.setState({step: 1});
1413 + }
1414 + componentDidUpdate() {
1415 + this.setState({step: 2});
1416 + }
1417 + render() {
1418 + return this.state.step;
1419 + }
1420 + }
1421 +
1422 + class Terminating extends React.Component {
1423 + state = {step: 0};
1424 + componentDidMount() {
1425 + this.setState({step: 1});
1426 + }
1427 + render() {
1428 + return this.state.step;
1429 + }
1430 + }
1431 +
1432 + const container = document.createElement('div');
1433 + expect(() => {
1434 + ReactDOM.render(<NonTerminating />, container);
1435 + }).toThrow('Maximum');
1436 +
1437 + ReactDOM.render(<Terminating />, container);
1438 + expect(container.textContent).toBe('1');
1439 +
1440 + expect(() => {
1441 + ReactDOM.render(<NonTerminating />, container);
1442 + }).toThrow('Maximum');
1443 +
1444 + ReactDOM.render(<Terminating />, container);
1445 + expect(container.textContent).toBe('1');
1446 + });
1447 +
1448 + it('does not fall into mutually recursive infinite update loop with same container', () => {
1449 + // Note: this test would fail if there were two or more different roots.
1450 +
1451 + class A extends React.Component {
1452 + componentDidMount() {
1453 + ReactDOM.render(<B />, container);
1454 + }
1455 + render() {
1456 + return null;
1457 + }
1458 + }
1459 +
1460 + class B extends React.Component {
1461 + componentDidMount() {
1462 + ReactDOM.render(<A />, container);
1463 + }
1464 + render() {
1465 + return null;
1466 + }
1467 + }
1468 +
1469 + const container = document.createElement('div');
1470 + expect(() => {
1471 + ReactDOM.render(<A />, container);
1472 + }).toThrow('Maximum');
1473 + });
1474 +
1475 + it('does not fall into an infinite error loop', () => {
1476 + function BadRender() {
1477 + throw new Error('error');
1478 + }
1479 +
1480 + class ErrorBoundary extends React.Component {
1481 + componentDidCatch() {
1482 + // Schedule a no-op state update to avoid triggering a DEV warning in the test.
1483 + this.setState({});
1484 +
1485 + this.props.parent.remount();
1486 + }
1487 + render() {
1488 + return <BadRender />;
1489 + }
1490 + }
1491 +
1492 + class NonTerminating extends React.Component {
1493 + state = {step: 0};
1494 + remount() {
1495 + this.setState(state => ({step: state.step + 1}));
1496 + }
1497 + render() {
1498 + return <ErrorBoundary key={this.state.step} parent={this} />;
1499 + }
1500 + }
1501 +
1502 + const container = document.createElement('div');
1503 + expect(() => {
1504 + ReactDOM.render(<NonTerminating />, container);
1505 + }).toThrow('Maximum');
1506 + });
1507 +
1508 + it('can schedule ridiculously many updates within the same batch without triggering a maximum update error', () => {
1509 + const subscribers = [];
1510 +
1511 + class Child extends React.Component {
1512 + state = {value: 'initial'};
1513 + componentDidMount() {
1514 + subscribers.push(this);
1515 + }
1516 + render() {
1517 + return null;
1518 + }
1519 + }
1520 +
1521 + class App extends React.Component {
1522 + render() {
1523 + const children = [];
1524 + for (let i = 0; i < 1200; i++) {
1525 + children.push(<Child key={i} />);
1526 + }
1527 + return children;
1528 + }
1529 + }
1530 +
1531 + const container = document.createElement('div');
1532 + ReactDOM.render(<App />, container);
1533 +
1534 + ReactDOM.unstable_batchedUpdates(() => {
1535 + subscribers.forEach(s => {
1536 + s.setState({value: 'update'});
1537 + });
1538 + });
1539 + });
1540 +
1541 + // TODO: Replace this branch with @gate pragmas
1542 + if (__DEV__) {
1543 + it('can have nested updates if they do not cross the limit', async () => {
1544 + let _setStep;
1545 + const LIMIT = 50;
1546 +
1547 + function Terminating() {
1548 + const [step, setStep] = React.useState(0);
1549 + _setStep = setStep;
1550 + React.useEffect(() => {
1551 + if (step < LIMIT) {
1552 + setStep(x => x + 1);
1553 + }
1554 + });
1555 + Scheduler.log(step);
1556 + return step;
1557 + }
1558 +
1559 + const container = document.createElement('div');
1560 + await act(() => {
1561 + ReactDOM.render(<Terminating />, container);
1562 + });
1563 + expect(container.textContent).toBe('50');
1564 + await act(() => {
1565 + _setStep(0);
1566 + });
1567 + expect(container.textContent).toBe('50');
1568 + });
1569 +
1570 + it('can have many updates inside useEffect without triggering a warning', async () => {
1571 + function Terminating() {
1572 + const [step, setStep] = React.useState(0);
1573 + React.useEffect(() => {
1574 + for (let i = 0; i < 1000; i++) {
1575 + setStep(x => x + 1);
1576 + }
1577 + Scheduler.log('Done');
1578 + }, []);
1579 + return step;
1580 + }
1581 +
1582 + const container = document.createElement('div');
1583 + await act(() => {
1584 + ReactDOM.render(<Terminating />, container);
1585 + });
1586 +
1587 + assertLog(['Done']);
1588 + expect(container.textContent).toBe('1000');
1589 + });
1590 + }
1591 +});
packages/react-dom/src/__tests__/ReactUpdates-test.js
+567 -475
@@ -48,226 +48,283 @@ describe('ReactUpdates', () => {
48 );
49 }
50
51 - it('should batch state when updating state twice', () => {
52 - let updateCount = 0;
53 -
54 - class Component extends React.Component {
55 - state = {x: 0};
56 -
57 - componentDidUpdate() {
58 - updateCount++;
59 - }
51 + it('should batch state when updating state twice', async () => {
52 + let componentState;
53 + let setState;
54 +
55 + function Component() {
56 + const [state, _setState] = React.useState(0);
57 + componentState = state;
58 + setState = _setState;
59 + React.useLayoutEffect(() => {
60 + Scheduler.log('Commit');
61 + });
62
61 - render() {
62 - return <div>{this.state.x}</div>;
63 - }
63 + return <div>{state}</div>;
64 }
65
66 - const instance = ReactTestUtils.renderIntoDocument(<Component />);
67 - expect(instance.state.x).toBe(0);
66 + const container = document.createElement('div');
67 + const root = ReactDOMClient.createRoot(container);
68 + await act(() => {
69 + root.render(<Component />);
70 + });
71
69 - ReactDOM.unstable_batchedUpdates(function () {
70 - instance.setState({x: 1});
71 - instance.setState({x: 2});
72 - expect(instance.state.x).toBe(0);
73 - expect(updateCount).toBe(0);
72 + assertLog(['Commit']);
73 + expect(container.firstChild.textContent).toBe('0');
74 +
75 + await act(() => {
76 + setState(1);
77 + setState(2);
78 + expect(componentState).toBe(0);
79 + expect(container.firstChild.textContent).toBe('0');
80 + assertLog([]);
81 });
82
76 - expect(instance.state.x).toBe(2);
77 - expect(updateCount).toBe(1);
83 + expect(componentState).toBe(2);
84 + assertLog(['Commit']);
85 + expect(container.firstChild.textContent).toBe('2');
86 });
87
80 - it('should batch state when updating two different state keys', () => {
81 - let updateCount = 0;
88 + it('should batch state when updating two different states', async () => {
89 + let componentStateA;
90 + let componentStateB;
91 + let setStateA;
92 + let setStateB;
93
83 - class Component extends React.Component {
84 - state = {x: 0, y: 0};
94 + function Component() {
95 + const [stateA, _setStateA] = React.useState(0);
96 + const [stateB, _setStateB] = React.useState(0);
97 + componentStateA = stateA;
98 + componentStateB = stateB;
99 + setStateA = _setStateA;
100 + setStateB = _setStateB;
101
86 - componentDidUpdate() {
87 - updateCount++;
88 - }
102 + React.useLayoutEffect(() => {
103 + Scheduler.log('Commit');
104 + });
105
90 - render() {
91 - return (
92 - <div>
93 - ({this.state.x}, {this.state.y})
94 - </div>
95 - );
96 - }
106 + return (
107 + <div>
108 + {stateA} {stateB}
109 + </div>
110 + );
111 }
112
99 - const instance = ReactTestUtils.renderIntoDocument(<Component />);
100 - expect(instance.state.x).toBe(0);
101 - expect(instance.state.y).toBe(0);
113 + const container = document.createElement('div');
114 + const root = ReactDOMClient.createRoot(container);
115 + await act(() => {
116 + root.render(<Component />);
117 + });
118
103 - ReactDOM.unstable_batchedUpdates(function () {
104 - instance.setState({x: 1});
105 - instance.setState({y: 2});
106 - expect(instance.state.x).toBe(0);
107 - expect(instance.state.y).toBe(0);
108 - expect(updateCount).toBe(0);
119 + assertLog(['Commit']);
120 + expect(container.firstChild.textContent).toBe('0 0');
121 +
122 + await act(() => {
123 + setStateA(1);
124 + setStateB(2);
125 + expect(componentStateA).toBe(0);
126 + expect(componentStateB).toBe(0);
127 + expect(container.firstChild.textContent).toBe('0 0');
128 + assertLog([]);
129 });
130
111 - expect(instance.state.x).toBe(1);
112 - expect(instance.state.y).toBe(2);
113 - expect(updateCount).toBe(1);
131 + expect(componentStateA).toBe(1);
132 + expect(componentStateB).toBe(2);
133 + assertLog(['Commit']);
134 + expect(container.firstChild.textContent).toBe('1 2');
135 });
136
116 - it('should batch state and props together', () => {
117 - let updateCount = 0;
137 + it('should batch state and props together', async () => {
138 + let setState;
139 + let componentProp;
140 + let componentState;
141
119 - class Component extends React.Component {
120 - state = {y: 0};
142 + function Component({prop}) {
143 + const [state, _setState] = React.useState(0);
144 + componentProp = prop;
145 + componentState = state;
146 + setState = _setState;
147
122 - componentDidUpdate() {
123 - updateCount++;
124 - }
148 + React.useLayoutEffect(() => {
149 + Scheduler.log('Commit');
150 + });
151
126 - render() {
127 - return (
128 - <div>
129 - ({this.props.x}, {this.state.y})
130 - </div>
131 - );
132 - }
152 + return (
153 + <div>
154 + {prop} {state}
155 + </div>
156 + );
157 }
158
159 const container = document.createElement('div');
136 - const instance = ReactDOM.render(<Component x={0} />, container);
137 - expect(instance.props.x).toBe(0);
138 - expect(instance.state.y).toBe(0);
139 -
140 - ReactDOM.unstable_batchedUpdates(function () {
141 - ReactDOM.render(<Component x={1} />, container);
142 - instance.setState({y: 2});
143 - expect(instance.props.x).toBe(0);
144 - expect(instance.state.y).toBe(0);
145 - expect(updateCount).toBe(0);
160 + const root = ReactDOMClient.createRoot(container);
161 + await act(() => {
162 + root.render(<Component prop={0} />);
163 + });
164 +
165 + assertLog(['Commit']);
166 + expect(container.firstChild.textContent).toBe('0 0');
167 +
168 + await act(() => {
169 + root.render(<Component prop={1} />);
170 + setState(2);
171 + expect(componentProp).toBe(0);
172 + expect(componentState).toBe(0);
173 + expect(container.firstChild.textContent).toBe('0 0');
174 + assertLog([]);
175 });
176
148 - expect(instance.props.x).toBe(1);
149 - expect(instance.state.y).toBe(2);
150 - expect(updateCount).toBe(1);
177 + expect(componentProp).toBe(1);
178 + expect(componentState).toBe(2);
179 + assertLog(['Commit']);
180 + expect(container.firstChild.textContent).toBe('1 2');
181 });
182
153 - it('should batch parent/child state updates together', () => {
154 - let parentUpdateCount = 0;
183 + it('should batch parent/child state updates together', async () => {
184 + let childRef;
185 + let parentState;
186 + let childState;
187 + let setParentState;
188 + let setChildState;
189
156 - class Parent extends React.Component {
157 - state = {x: 0};
158 - childRef = React.createRef();
190 + function Parent() {
191 + const [state, _setState] = React.useState(0);
192 + parentState = state;
193 + setParentState = _setState;
194
160 - componentDidUpdate() {
161 - parentUpdateCount++;
162 - }
195 + React.useLayoutEffect(() => {
196 + Scheduler.log('Parent Commit');
197 + });
198
164 - render() {
165 - return (
166 - <div>
167 - <Child ref={this.childRef} x={this.state.x} />
168 - </div>
169 - );
170 - }
199 + return (
200 + <div>
201 + <Child prop={state} />
202 + </div>
203 + );
204 }
205
173 - let childUpdateCount = 0;
174 -
175 - class Child extends React.Component {
176 - state = {y: 0};
206 + function Child({prop}) {
207 + const [state, _setState] = React.useState(0);
208 + childState = state;
209 + setChildState = _setState;
210
178 - componentDidUpdate() {
179 - childUpdateCount++;
180 - }
211 + React.useLayoutEffect(() => {
212 + Scheduler.log('Child Commit');
213 + });
214
182 - render() {
183 - return <div>{this.props.x + this.state.y}</div>;
184 - }
215 + return (
216 + <div
217 + ref={ref => {
218 + childRef = ref;
219 + }}>
220 + {prop} {state}
221 + </div>
222 + );
223 }
224
187 - const instance = ReactTestUtils.renderIntoDocument(<Parent />);
188 - const child = instance.childRef.current;
189 - expect(instance.state.x).toBe(0);
190 - expect(child.state.y).toBe(0);
225 + const container = document.createElement('div');
226 + const root = ReactDOMClient.createRoot(container);
227 + await act(() => {
228 + root.render(<Parent />);
229 + });
230
192 - ReactDOM.unstable_batchedUpdates(function () {
193 - instance.setState({x: 1});
194 - child.setState({y: 2});
195 - expect(instance.state.x).toBe(0);
196 - expect(child.state.y).toBe(0);
197 - expect(parentUpdateCount).toBe(0);
198 - expect(childUpdateCount).toBe(0);
231 + assertLog(['Child Commit', 'Parent Commit']);
232 + expect(childRef.textContent).toBe('0 0');
233 +
234 + await act(() => {
235 + // Parent update first.
236 + setParentState(1);
237 + setChildState(2);
238 + expect(parentState).toBe(0);
239 + expect(childState).toBe(0);
240 + expect(childRef.textContent).toBe('0 0');
241 + assertLog([]);
242 });
243
201 - expect(instance.state.x).toBe(1);
202 - expect(child.state.y).toBe(2);
203 - expect(parentUpdateCount).toBe(1);
204 - expect(childUpdateCount).toBe(1);
244 + expect(parentState).toBe(1);
245 + expect(childState).toBe(2);
246 + expect(childRef.textContent).toBe('1 2');
247 + assertLog(['Child Commit', 'Parent Commit']);
248 });
249
207 - it('should batch child/parent state updates together', () => {
208 - let parentUpdateCount = 0;
250 + it('should batch child/parent state updates together', async () => {
251 + let childRef;
252 + let parentState;
253 + let childState;
254 + let setParentState;
255 + let setChildState;
256
210 - class Parent extends React.Component {
211 - state = {x: 0};
212 - childRef = React.createRef();
257 + function Parent() {
258 + const [state, _setState] = React.useState(0);
259 + parentState = state;
260 + setParentState = _setState;
261
214 - componentDidUpdate() {
215 - parentUpdateCount++;
216 - }
262 + React.useLayoutEffect(() => {
263 + Scheduler.log('Parent Commit');
264 + });
265
218 - render() {
219 - return (
220 - <div>
221 - <Child ref={this.childRef} x={this.state.x} />
222 - </div>
223 - );
224 - }
266 + return (
267 + <div>
268 + <Child prop={state} />
269 + </div>
270 + );
271 }
272
227 - let childUpdateCount = 0;
273 + function Child({prop}) {
274 + const [state, _setState] = React.useState(0);
275 + childState = state;
276 + setChildState = _setState;
277
229 - class Child extends React.Component {
230 - state = {y: 0};
231 -
232 - componentDidUpdate() {
233 - childUpdateCount++;
234 - }
278 + React.useLayoutEffect(() => {
279 + Scheduler.log('Child Commit');
280 + });
281
236 - render() {
237 - return <div>{this.props.x + this.state.y}</div>;
238 - }
282 + return (
283 + <div
284 + ref={ref => {
285 + childRef = ref;
286 + }}>
287 + {prop} {state}
288 + </div>
289 + );
290 }
291
241 - const instance = ReactTestUtils.renderIntoDocument(<Parent />);
242 - const child = instance.childRef.current;
243 - expect(instance.state.x).toBe(0);
244 - expect(child.state.y).toBe(0);
245 -
246 - ReactDOM.unstable_batchedUpdates(function () {
247 - child.setState({y: 2});
248 - instance.setState({x: 1});
249 - expect(instance.state.x).toBe(0);
250 - expect(child.state.y).toBe(0);
251 - expect(parentUpdateCount).toBe(0);
252 - expect(childUpdateCount).toBe(0);
292 + const container = document.createElement('div');
293 + const root = ReactDOMClient.createRoot(container);
294 + await act(() => {
295 + root.render(<Parent />);
296 });
297
255 - expect(instance.state.x).toBe(1);
256 - expect(child.state.y).toBe(2);
257 - expect(parentUpdateCount).toBe(1);
298 + assertLog(['Child Commit', 'Parent Commit']);
299 + expect(childRef.textContent).toBe('0 0');
300 +
301 + await act(() => {
302 + // Child update first.
303 + setChildState(2);
304 + setParentState(1);
305 + expect(parentState).toBe(0);
306 + expect(childState).toBe(0);
307 + expect(childRef.textContent).toBe('0 0');
308 + assertLog([]);
309 + });
310
259 - // Batching reduces the number of updates here to 1.
260 - expect(childUpdateCount).toBe(1);
311 + expect(parentState).toBe(1);
312 + expect(childState).toBe(2);
313 + expect(childRef.textContent).toBe('1 2');
314 + assertLog(['Child Commit', 'Parent Commit']);
315 });
316
263 - it('should support chained state updates', () => {
264 - let updateCount = 0;
265 -
317 + it('should support chained state updates', async () => {
318 + let instance;
319 class Component extends React.Component {
320 state = {x: 0};
321 + constructor(props) {
322 + super(props);
323 + instance = this;
324 + }
325
326 componentDidUpdate() {
270 - updateCount++;
327 + Scheduler.log('Update');
328 }
329
330 render() {
@@ -275,43 +332,55 @@ describe('ReactUpdates', () => {
332 }
333 }
334
278 - const instance = ReactTestUtils.renderIntoDocument(<Component />);
335 + const container = document.createElement('div');
336 + const root = ReactDOMClient.createRoot(container);
337 + await act(() => {
338 + root.render(<Component />);
339 + });
340 +
341 expect(instance.state.x).toBe(0);
342 + expect(container.firstChild.textContent).toBe('0');
343
344 let innerCallbackRun = false;
282 - ReactDOM.unstable_batchedUpdates(function () {
345 + await act(() => {
346 instance.setState({x: 1}, function () {
347 instance.setState({x: 2}, function () {
285 - expect(this).toBe(instance);
348 innerCallbackRun = true;
349 expect(instance.state.x).toBe(2);
288 - expect(updateCount).toBe(2);
350 + expect(container.firstChild.textContent).toBe('2');
351 + assertLog(['Update']);
352 });
353 expect(instance.state.x).toBe(1);
291 - expect(updateCount).toBe(1);
354 + expect(container.firstChild.textContent).toBe('1');
355 + assertLog(['Update']);
356 });
357 expect(instance.state.x).toBe(0);
294 - expect(updateCount).toBe(0);
358 + expect(container.firstChild.textContent).toBe('0');
359 + assertLog([]);
360 });
361
297 - expect(innerCallbackRun).toBeTruthy();
362 + assertLog([]);
363 expect(instance.state.x).toBe(2);
299 - expect(updateCount).toBe(2);
364 + expect(innerCallbackRun).toBeTruthy();
365 + expect(container.firstChild.textContent).toBe('2');
366 });
367
302 - it('should batch forceUpdate together', () => {
368 + it('should batch forceUpdate together', async () => {
369 + let instance;
370 let shouldUpdateCount = 0;
304 - let updateCount = 0;
305 -
371 class Component extends React.Component {
372 state = {x: 0};
373
374 + constructor(props) {
375 + super(props);
376 + instance = this;
377 + }
378 shouldComponentUpdate() {
379 shouldUpdateCount++;
380 }
381
382 componentDidUpdate() {
314 - updateCount++;
383 + Scheduler.log('Update');
384 }
385
386 render() {
@@ -319,80 +388,82 @@ describe('ReactUpdates', () => {
388 }
389 }
390
322 - const instance = ReactTestUtils.renderIntoDocument(<Component />);
391 + const container = document.createElement('div');
392 + const root = ReactDOMClient.createRoot(container);
393 + await act(() => {
394 + root.render(<Component />);
395 + });
396 +
397 + assertLog([]);
398 expect(instance.state.x).toBe(0);
399
325 - let callbacksRun = 0;
326 - ReactDOM.unstable_batchedUpdates(function () {
400 + await act(() => {
401 instance.setState({x: 1}, function () {
328 - callbacksRun++;
402 + Scheduler.log('callback');
403 });
404 instance.forceUpdate(function () {
331 - callbacksRun++;
405 + Scheduler.log('forceUpdate');
406 });
407 + assertLog([]);
408 expect(instance.state.x).toBe(0);
334 - expect(updateCount).toBe(0);
409 + expect(container.firstChild.textContent).toBe('0');
410 });
411
337 - expect(callbacksRun).toBe(2);
412 // shouldComponentUpdate shouldn't be called since we're forcing
413 expect(shouldUpdateCount).toBe(0);
414 + assertLog(['Update', 'callback', 'forceUpdate']);
415 expect(instance.state.x).toBe(1);
341 - expect(updateCount).toBe(1);
416 + expect(container.firstChild.textContent).toBe('1');
417 });
418
344 - it('should update children even if parent blocks updates', () => {
345 - let parentRenderCount = 0;
346 - let childRenderCount = 0;
347 -
419 + it('should update children even if parent blocks updates', async () => {
420 + let instance;
421 class Parent extends React.Component {
422 childRef = React.createRef();
423
424 + constructor(props) {
425 + super(props);
426 + instance = this;
427 + }
428 shouldComponentUpdate() {
429 return false;
430 }
431
432 render() {
356 - parentRenderCount++;
433 + Scheduler.log('Parent render');
434 return <Child ref={this.childRef} />;
435 }
436 }
437
438 class Child extends React.Component {
439 render() {
363 - childRenderCount++;
440 + Scheduler.log('Child render');
441 return <div />;
442 }
443 }
444
368 - expect(parentRenderCount).toBe(0);
369 - expect(childRenderCount).toBe(0);
370 -
371 - let instance = <Parent />;
372 - instance = ReactTestUtils.renderIntoDocument(instance);
445 + const container = document.createElement('div');
446 + const root = ReactDOMClient.createRoot(container);
447 + await act(() => {
448 + root.render(<Parent />);
449 + });
450
374 - expect(parentRenderCount).toBe(1);
375 - expect(childRenderCount).toBe(1);
451 + assertLog(['Parent render', 'Child render']);
452
377 - ReactDOM.unstable_batchedUpdates(function () {
453 + await act(() => {
454 instance.setState({x: 1});
455 });
456
381 - expect(parentRenderCount).toBe(1);
382 - expect(childRenderCount).toBe(1);
457 + assertLog([]);
458
384 - ReactDOM.unstable_batchedUpdates(function () {
459 + await act(() => {
460 instance.childRef.current.setState({x: 1});
461 });
462
388 - expect(parentRenderCount).toBe(1);
389 - expect(childRenderCount).toBe(2);
463 + assertLog(['Child render']);
464 });
465
392 - it('should not reconcile children passed via props', () => {
393 - let numMiddleRenders = 0;
394 - let numBottomRenders = 0;
395 -
466 + it('should not reconcile children passed via props', async () => {
467 class Top extends React.Component {
468 render() {
469 return (
@@ -409,26 +480,31 @@ describe('ReactUpdates', () => {
480 }
481
482 render() {
412 - numMiddleRenders++;
483 + Scheduler.log('Middle');
484 return React.Children.only(this.props.children);
485 }
486 }
487
488 class Bottom extends React.Component {
489 render() {
419 - numBottomRenders++;
490 + Scheduler.log('Bottom');
491 return null;
492 }
493 }
494
424 - ReactTestUtils.renderIntoDocument(<Top />);
425 - expect(numMiddleRenders).toBe(2);
426 - expect(numBottomRenders).toBe(1);
495 + const container = document.createElement('div');
496 + const root = ReactDOMClient.createRoot(container);
497 + await act(() => {
498 + root.render(<Top />);
499 + });
500 +
501 + assertLog(['Middle', 'Bottom', 'Middle']);
502 });
503
429 - it('should flow updates correctly', () => {
504 + it('should flow updates correctly', async () => {
505 let willUpdates = [];
506 let didUpdates = [];
507 + let instance;
508
509 const UpdateLoggingMixin = {
510 UNSAFE_componentWillUpdate: function () {
@@ -482,7 +558,10 @@ describe('ReactUpdates', () => {
558 class App extends React.Component {
559 switcherRef = React.createRef();
560 childRef = React.createRef();
485 -
561 + constructor(props) {
562 + super(props);
563 + instance = this;
564 + }
565 render() {
566 return (
567 <Switcher ref={this.switcherRef}>
@@ -493,8 +572,10 @@ describe('ReactUpdates', () => {
572 }
573 Object.assign(App.prototype, UpdateLoggingMixin);
574
496 - let root = <App />;
497 - root = ReactTestUtils.renderIntoDocument(root);
575 + const container = document.createElement('div');
576 + await act(() => {
577 + ReactDOMClient.createRoot(container).render(<App />);
578 + });
579
580 function expectUpdates(desiredWillUpdates, desiredDidUpdates) {
581 let i;
@@ -512,10 +593,14 @@ describe('ReactUpdates', () => {
593 c.setState({x: 1});
594 }
595
515 - function testUpdates(components, desiredWillUpdates, desiredDidUpdates) {
596 + async function testUpdates(
597 + components,
598 + desiredWillUpdates,
599 + desiredDidUpdates,
600 + ) {
601 let i;
602
518 - ReactDOM.unstable_batchedUpdates(function () {
603 + await act(() => {
604 for (i = 0; i < components.length; i++) {
605 triggerUpdate(components[i]);
606 }
@@ -525,7 +610,7 @@ describe('ReactUpdates', () => {
610
611 // Try them in reverse order
612
528 - ReactDOM.unstable_batchedUpdates(function () {
613 + await act(() => {
614 for (i = components.length - 1; i >= 0; i--) {
615 triggerUpdate(components[i]);
616 }
@@ -533,42 +618,48 @@ describe('ReactUpdates', () => {
618
619 expectUpdates(desiredWillUpdates, desiredDidUpdates);
620 }
536 - testUpdates(
537 - [root.switcherRef.current.boxRef.current, root.switcherRef.current],
621 + await testUpdates(
622 + [
623 + instance.switcherRef.current.boxRef.current,
624 + instance.switcherRef.current,
625 + ],
626 // Owner-child relationships have inverse will and did
627 ['Switcher', 'Box'],
628 ['Box', 'Switcher'],
629 );
630
543 - testUpdates(
544 - [root.childRef.current, root.switcherRef.current.boxRef.current],
631 + await testUpdates(
632 + [instance.childRef.current, instance.switcherRef.current.boxRef.current],
633 // Not owner-child so reconcile independently
634 ['Box', 'Child'],
635 ['Box', 'Child'],
636 );
637
550 - testUpdates(
551 - [root.childRef.current, root.switcherRef.current],
638 + await testUpdates(
639 + [instance.childRef.current, instance.switcherRef.current],
640 // Switcher owns Box and Child, Box does not own Child
641 ['Switcher', 'Box', 'Child'],
642 ['Box', 'Switcher', 'Child'],
643 );
644 });
645
558 - it('should queue mount-ready handlers across different roots', () => {
646 + it('should queue mount-ready handlers across different roots', async () => {
647 // We'll define two components A and B, then update both of them. When A's
648 // componentDidUpdate handlers is called, B's DOM should already have been
649 // updated.
650
651 const bContainer = document.createElement('div');
564 -
652 + let a;
653 let b;
654
655 let aUpdated = false;
656
657 class A extends React.Component {
658 state = {x: 0};
571 -
659 + constructor(props) {
660 + super(props);
661 + a = this;
662 + }
663 componentDidUpdate() {
664 expect(ReactDOM.findDOMNode(b).textContent).toBe('B1');
665 aUpdated = true;
@@ -576,7 +667,6 @@ describe('ReactUpdates', () => {
667
668 render() {
669 let portal = null;
579 - // If we're using Fiber, we use Portals instead to achieve this.
670 portal = ReactDOM.createPortal(<B ref={n => (b = n)} />, bContainer);
671 return (
672 <div>
@@ -595,8 +685,13 @@ describe('ReactUpdates', () => {
685 }
686 }
687
598 - const a = ReactTestUtils.renderIntoDocument(<A />);
599 - ReactDOM.unstable_batchedUpdates(function () {
688 + const container = document.createElement('div');
689 + const root = ReactDOMClient.createRoot(container);
690 + await act(() => {
691 + root.render(<A />);
692 + });
693 +
694 + await act(() => {
695 a.setState({x: 1});
696 b.setState({x: 1});
697 });
@@ -604,13 +699,16 @@ describe('ReactUpdates', () => {
699 expect(aUpdated).toBe(true);
700 });
701
607 - it('should flush updates in the correct order', () => {
702 + it('should flush updates in the correct order', async () => {
703 const updates = [];
609 -
704 + let instance;
705 class Outer extends React.Component {
706 state = {x: 0};
707 innerRef = React.createRef();
613 -
708 + constructor(props) {
709 + super(props);
710 + instance = this;
711 + }
712 render() {
713 updates.push('Outer-render-' + this.state.x);
714 return (
@@ -643,14 +741,20 @@ describe('ReactUpdates', () => {
741 }
742 }
743
646 - const instance = ReactTestUtils.renderIntoDocument(<Outer />);
744 + const container = document.createElement('div');
745 + const root = ReactDOMClient.createRoot(container);
746 + await act(() => {
747 + root.render(<Outer />);
748 + });
749
648 - updates.push('Outer-setState-1');
649 - instance.setState({x: 1}, function () {
650 - updates.push('Outer-callback-1');
651 - updates.push('Outer-setState-2');
652 - instance.setState({x: 2}, function () {
653 - updates.push('Outer-callback-2');
750 + await act(() => {
751 + updates.push('Outer-setState-1');
752 + instance.setState({x: 1}, function () {
753 + updates.push('Outer-callback-1');
754 + updates.push('Outer-setState-2');
755 + instance.setState({x: 2}, function () {
756 + updates.push('Outer-callback-2');
757 + });
758 });
759 });
760
@@ -686,7 +790,7 @@ describe('ReactUpdates', () => {
790 /* eslint-enable indent */
791 });
792
689 - it('should flush updates in the correct order across roots', () => {
793 + it('should flush updates in the correct order across roots', async () => {
794 const instances = [];
795 const updates = [];
796
@@ -699,22 +803,26 @@ describe('ReactUpdates', () => {
803 componentDidMount() {
804 instances.push(this);
805 if (this.props.depth < this.props.count) {
702 - ReactDOM.render(
806 + const root = ReactDOMClient.createRoot(ReactDOM.findDOMNode(this));
807 + root.render(
808 <MockComponent
809 depth={this.props.depth + 1}
810 count={this.props.count}
811 />,
707 - ReactDOM.findDOMNode(this),
812 );
813 }
814 }
815 }
816
713 - ReactTestUtils.renderIntoDocument(<MockComponent depth={0} count={2} />);
817 + const container = document.createElement('div');
818 + const root = ReactDOMClient.createRoot(container);
819 + await act(() => {
820 + root.render(<MockComponent depth={0} count={2} />);
821 + });
822
823 expect(updates).toEqual([0, 1, 2]);
824
717 - ReactDOM.unstable_batchedUpdates(function () {
825 + await act(() => {
826 // Simulate update on each component from top to bottom.
827 instances.forEach(function (instance) {
828 instance.forceUpdate();
@@ -777,7 +885,7 @@ describe('ReactUpdates', () => {
885 expect(ReactDOM.findDOMNode(x).textContent).toBe('1');
886 });
887
780 - it('should queue updates from during mount', () => {
888 + it('should queue updates from during mount', async () => {
889 // See https://github.com/facebook/react/issues/1353
890 let a;
891
@@ -803,8 +911,11 @@ describe('ReactUpdates', () => {
911 }
912 }
913
806 - ReactDOM.unstable_batchedUpdates(function () {
807 - ReactTestUtils.renderIntoDocument(
914 + const container = document.createElement('div');
915 + const root = ReactDOMClient.createRoot(container);
916 +
917 + await act(() => {
918 + root.render(
919 <div>
920 <A />
921 <B />
@@ -812,13 +923,10 @@ describe('ReactUpdates', () => {
923 );
924 });
925
815 - expect(a.state.x).toBe(1);
816 - expect(ReactDOM.findDOMNode(a).textContent).toBe('A1');
926 + expect(container.firstChild.textContent).toBe('A1');
927 });
928
819 - it('calls componentWillReceiveProps setState callback properly', () => {
820 - let callbackCount = 0;
821 -
929 + it('calls componentWillReceiveProps setState callback properly', async () => {
930 class A extends React.Component {
931 state = {x: this.props.x};
932
@@ -827,7 +935,7 @@ describe('ReactUpdates', () => {
935 this.setState({x: newX}, function () {
936 // State should have updated by the time this callback gets called
937 expect(this.state.x).toBe(newX);
830 - callbackCount++;
938 + Scheduler.log('Callback');
939 });
940 }
941
@@ -837,13 +945,22 @@ describe('ReactUpdates', () => {
945 }
946
947 const container = document.createElement('div');
840 - ReactDOM.render(<A x={1} />, container);
841 - ReactDOM.render(<A x={2} />, container);
842 - expect(callbackCount).toBe(1);
948 + const root = ReactDOMClient.createRoot(container);
949 + await act(() => {
950 + root.render(<A x={1} />);
951 + });
952 + assertLog([]);
953 +
954 + // Needs to be a separate act, or it will be batched.
955 + await act(() => {
956 + root.render(<A x={2} />);
957 + });
958 +
959 + assertLog(['Callback']);
960 });
961
845 - it('does not call render after a component as been deleted', () => {
846 - let renderCount = 0;
962 + it('does not call render after a component as been deleted', async () => {
963 + let componentA = null;
964 let componentB = null;
965
966 class B extends React.Component {
@@ -854,7 +971,7 @@ describe('ReactUpdates', () => {
971 }
972
973 render() {
857 - renderCount++;
974 + Scheduler.log('B');
975 return <div />;
976 }
977 }
@@ -862,21 +979,29 @@ describe('ReactUpdates', () => {
979 class A extends React.Component {
980 state = {showB: true};
981
982 + componentDidMount() {
983 + componentA = this;
984 + }
985 render() {
986 return this.state.showB ? <B /> : <div />;
987 }
988 }
989
870 - const component = ReactTestUtils.renderIntoDocument(<A />);
990 + const container = document.createElement('div');
991 + const root = ReactDOMClient.createRoot(container);
992 + await act(() => {
993 + root.render(<A />);
994 + });
995 + assertLog(['B']);
996
872 - ReactDOM.unstable_batchedUpdates(function () {
997 + await act(() => {
998 // B will have scheduled an update but the batching should ensure that its
999 // update never fires.
1000 componentB.setState({updates: 1});
876 - component.setState({showB: false});
1001 + componentA.setState({showB: false});
1002 });
1003
879 - expect(renderCount).toBe(1);
1004 + assertLog([]);
1005 });
1006
1007 it('throws in setState if the update callback is not a function', () => {
@@ -965,10 +1090,14 @@ describe('ReactUpdates', () => {
1090 );
1091 });
1092
968 - it('does not update one component twice in a batch (#2410)', () => {
1093 + it('does not update one component twice in a batch (#2410)', async () => {
1094 + let parent;
1095 class Parent extends React.Component {
1096 childRef = React.createRef();
1097
1098 + componentDidMount() {
1099 + parent = this;
1100 + }
1101 getChild = () => {
1102 return this.childRef.current;
1103 };
@@ -1009,15 +1138,22 @@ describe('ReactUpdates', () => {
1138 }
1139 }
1140
1012 - const parent = ReactTestUtils.renderIntoDocument(<Parent />);
1141 + const container = document.createElement('div');
1142 + const root = ReactDOMClient.createRoot(container);
1143 + await act(() => {
1144 + root.render(<Parent />);
1145 + });
1146 +
1147 const child = parent.getChild();
1014 - ReactDOM.unstable_batchedUpdates(function () {
1148 + await act(() => {
1149 parent.forceUpdate();
1150 child.forceUpdate();
1151 });
1152 +
1153 + expect.assertions(6);
1154 });
1155
1020 - it('does not update one component twice in a batch (#6371)', () => {
1156 + it('does not update one component twice in a batch (#6371)', async () => {
1157 let callbacks = [];
1158 function emitChange() {
1159 callbacks.forEach(c => c());
@@ -1064,34 +1200,23 @@ describe('ReactUpdates', () => {
1200 }
1201 }
1202
1067 - ReactDOM.render(<App />, document.createElement('div'));
1068 - });
1069 -
1070 - it('unstable_batchedUpdates should return value from a callback', () => {
1071 - const result = ReactDOM.unstable_batchedUpdates(function () {
1072 - return 42;
1203 + const root = ReactDOMClient.createRoot(document.createElement('div'));
1204 + await act(() => {
1205 + root.render(<App />);
1206 });
1074 - expect(result).toEqual(42);
1075 - });
1207
1077 - it('unmounts and remounts a root in the same batch', () => {
1078 - const container = document.createElement('div');
1079 - ReactDOM.render(<span>a</span>, container);
1080 - ReactDOM.unstable_batchedUpdates(function () {
1081 - ReactDOM.unmountComponentAtNode(container);
1082 - ReactDOM.render(<span>b</span>, container);
1083 - });
1084 - expect(container.textContent).toBe('b');
1208 + // Error should not be thrown.
1209 + expect(true).toBe(true);
1210 });
1211
1087 - it('handles reentrant mounting in synchronous mode', () => {
1088 - let mounts = 0;
1212 + it('handles reentrant mounting in synchronous mode', async () => {
1213 + let onChangeCalled = false;
1214 class Editor extends React.Component {
1215 render() {
1216 return <div>{this.props.text}</div>;
1217 }
1218 componentDidMount() {
1094 - mounts++;
1219 + Scheduler.log('Mount');
1220 // This should be called only once but we guard just in case.
1221 if (!this.props.rendered) {
1222 this.props.onChange({rendered: true});
@@ -1100,163 +1225,57 @@ describe('ReactUpdates', () => {
1225 }
1226
1227 const container = document.createElement('div');
1228 + const root = ReactDOMClient.createRoot(container);
1229 function render() {
1104 - ReactDOM.render(
1230 + root.render(
1231 <Editor
1232 onChange={newProps => {
1233 + onChangeCalled = true;
1234 props = {...props, ...newProps};
1235 render();
1236 }}
1237 {...props}
1238 />,
1112 - container,
1239 );
1240 }
1241
1242 let props = {text: 'hello', rendered: false};
1117 - render();
1243 + await act(() => {
1244 + render();
1245 + });
1246 + assertLog(['Mount']);
1247 props = {...props, text: 'goodbye'};
1119 - render();
1248 + await act(() => {
1249 + render();
1250 + });
1251 +
1252 + assertLog([]);
1253 expect(container.textContent).toBe('goodbye');
1121 - expect(mounts).toBe(1);
1254 + expect(onChangeCalled).toBeTruthy();
1255 });
1256
1124 - it('mounts and unmounts are sync even in a batch', () => {
1125 - const ops = [];
1257 + it('mounts and unmounts are batched', async () => {
1258 const container = document.createElement('div');
1127 - ReactDOM.unstable_batchedUpdates(() => {
1128 - ReactDOM.render(<div>Hello</div>, container);
1129 - ops.push(container.textContent);
1130 - ReactDOM.unmountComponentAtNode(container);
1131 - ops.push(container.textContent);
1132 - });
1133 - expect(ops).toEqual(['Hello', '']);
1134 - });
1135 -
1136 - it(
1137 - 'in legacy mode, updates in componentWillUpdate and componentDidUpdate ' +
1138 - 'should both flush in the immediately subsequent commit',
1139 - () => {
1140 - const ops = [];
1141 - class Foo extends React.Component {
1142 - state = {a: false, b: false};
1143 - UNSAFE_componentWillUpdate(_, nextState) {
1144 - if (!nextState.a) {
1145 - this.setState({a: true});
1146 - }
1147 - }
1148 - componentDidUpdate() {
1149 - ops.push('Foo updated');
1150 - if (!this.state.b) {
1151 - this.setState({b: true});
1152 - }
1153 - }
1154 - render() {
1155 - ops.push(`a: ${this.state.a}, b: ${this.state.b}`);
1156 - return null;
1157 - }
1158 - }
1159 -
1160 - const container = document.createElement('div');
1161 - // Mount
1162 - ReactDOM.render(<Foo />, container);
1163 - // Root update
1164 - ReactDOM.render(<Foo />, container);
1165 - expect(ops).toEqual([
1166 - // Mount
1167 - 'a: false, b: false',
1168 - // Root update
1169 - 'a: false, b: false',
1170 - 'Foo updated',
1171 - // Subsequent update (both a and b should have flushed)
1172 - 'a: true, b: true',
1173 - 'Foo updated',
1174 - // There should not be any additional updates
1175 - ]);
1176 - },
1177 - );
1178 -
1179 - it(
1180 - 'in legacy mode, updates in componentWillUpdate and componentDidUpdate ' +
1181 - '(on a sibling) should both flush in the immediately subsequent commit',
1182 - () => {
1183 - const ops = [];
1184 - class Foo extends React.Component {
1185 - state = {a: false};
1186 - UNSAFE_componentWillUpdate(_, nextState) {
1187 - if (!nextState.a) {
1188 - this.setState({a: true});
1189 - }
1190 - }
1191 - componentDidUpdate() {
1192 - ops.push('Foo updated');
1193 - }
1194 - render() {
1195 - ops.push(`a: ${this.state.a}`);
1196 - return null;
1197 - }
1198 - }
1259 + const root = ReactDOMClient.createRoot(container);
1260
1200 - class Bar extends React.Component {
1201 - state = {b: false};
1202 - componentDidUpdate() {
1203 - ops.push('Bar updated');
1204 - if (!this.state.b) {
1205 - this.setState({b: true});
1206 - }
1207 - }
1208 - render() {
1209 - ops.push(`b: ${this.state.b}`);
1210 - return null;
1211 - }
1212 - }
1261 + await act(() => {
1262 + root.render(<div>Hello</div>);
1263 + expect(container.textContent).toBe('');
1264 + root.unmount(container);
1265 + expect(container.textContent).toBe('');
1266 + });
1267
1214 - const container = document.createElement('div');
1215 - // Mount
1216 - ReactDOM.render(
1217 - <div>
1218 - <Foo />
1219 - <Bar />
1220 - </div>,
1221 - container,
1222 - );
1223 - // Root update
1224 - ReactDOM.render(
1225 - <div>
1226 - <Foo />
1227 - <Bar />
1228 - </div>,
1229 - container,
1230 - );
1231 - expect(ops).toEqual([
1232 - // Mount
1233 - 'a: false',
1234 - 'b: false',
1235 - // Root update
1236 - 'a: false',
1237 - 'b: false',
1238 - 'Foo updated',
1239 - 'Bar updated',
1240 - // Subsequent update (both a and b should have flushed)
1241 - 'a: true',
1242 - 'b: true',
1243 - 'Foo updated',
1244 - 'Bar updated',
1245 - // There should not be any additional updates
1246 - ]);
1247 - },
1248 - );
1249 -
1250 - it('uses correct base state for setState inside render phase', () => {
1251 - const ops = [];
1268 + expect(container.textContent).toBe('');
1269 + });
1270
1271 + it('uses correct base state for setState inside render phase', async () => {
1272 class Foo extends React.Component {
1273 state = {step: 0};
1274 render() {
1275 const memoizedStep = this.state.step;
1276 this.setState(baseState => {
1277 const baseStep = baseState.step;
1259 - ops.push(`base: ${baseStep}, memoized: ${memoizedStep}`);
1278 + Scheduler.log(`base: ${baseStep}, memoized: ${memoizedStep}`);
1279 return baseStep === 0 ? {step: 1} : null;
1280 });
1281 return null;
@@ -1264,48 +1283,54 @@ describe('ReactUpdates', () => {
1283 }
1284
1285 const container = document.createElement('div');
1267 - expect(() => ReactDOM.render(<Foo />, container)).toErrorDev(
1268 - 'Cannot update during an existing state transition',
1269 - );
1270 - expect(ops).toEqual(['base: 0, memoized: 0', 'base: 1, memoized: 1']);
1286 + const root = ReactDOMClient.createRoot(container);
1287 + await expect(async () => {
1288 + await act(() => {
1289 + root.render(<Foo />);
1290 + });
1291 + }).toErrorDev('Cannot update during an existing state transition');
1292 +
1293 + assertLog(['base: 0, memoized: 0', 'base: 1, memoized: 1']);
1294 });
1295
1273 - it('does not re-render if state update is null', () => {
1296 + it('does not re-render if state update is null', async () => {
1297 const container = document.createElement('div');
1298
1299 let instance;
1277 - let ops = [];
1300 class Foo extends React.Component {
1301 render() {
1302 instance = this;
1281 - ops.push('render');
1303 + Scheduler.log('render');
1304 return <div />;
1305 }
1306 }
1285 - ReactDOM.render(<Foo />, container);
1307 + const root = ReactDOMClient.createRoot(container);
1308 + await act(() => {
1309 + root.render(<Foo />);
1310 + });
1311
1287 - ops = [];
1288 - instance.setState(() => null);
1289 - expect(ops).toEqual([]);
1312 + assertLog(['render']);
1313 + await act(() => {
1314 + instance.setState(() => null);
1315 + });
1316 + assertLog([]);
1317 });
1318
1292 - // Will change once we switch to async by default
1293 - it('synchronously renders hidden subtrees', () => {
1319 + it('synchronously renders hidden subtrees', async () => {
1320 const container = document.createElement('div');
1295 - let ops = [];
1321
1322 function Baz() {
1298 - ops.push('Baz');
1323 + Scheduler.log('Baz');
1324 return null;
1325 }
1326
1327 function Bar() {
1303 - ops.push('Bar');
1328 + Scheduler.log('Bar');
1329 return null;
1330 }
1331
1332 function Foo() {
1308 - ops.push('Foo');
1333 + Scheduler.log('Foo');
1334 return (
1335 <div>
1336 <div hidden={true}>
@@ -1316,14 +1341,18 @@ describe('ReactUpdates', () => {
1341 );
1342 }
1343
1319 - // Mount
1320 - ReactDOM.render(<Foo />, container);
1321 - expect(ops).toEqual(['Foo', 'Bar', 'Baz']);
1322 - ops = [];
1344 + const root = ReactDOMClient.createRoot(container);
1345 + await act(() => {
1346 + // Mount
1347 + root.render(<Foo />);
1348 + });
1349 + assertLog(['Foo', 'Bar', 'Baz']);
1350
1324 - // Update
1325 - ReactDOM.render(<Foo />, container);
1326 - expect(ops).toEqual(['Foo', 'Bar', 'Baz']);
1351 + await act(() => {
1352 + // Update
1353 + root.render(<Foo />);
1354 + });
1355 + assertLog(['Foo', 'Bar', 'Baz']);
1356 });
1357
1358 // @gate www
@@ -1383,18 +1412,33 @@ describe('ReactUpdates', () => {
1412 expect(hiddenDiv.innerHTML).toBe('<p>bar 1</p>');
1413 });
1414
1386 - it('can render ridiculously large number of roots without triggering infinite update loop error', () => {
1415 + it('can render ridiculously large number of roots without triggering infinite update loop error', async () => {
1416 + function Component({trigger}) {
1417 + const [state, setState] = React.useState(0);
1418 +
1419 + React.useEffect(() => {
1420 + if (trigger) {
1421 + Scheduler.log('Trigger');
1422 + setState(c => c + 1);
1423 + }
1424 + }, [trigger]);
1425 +
1426 + return <div>{state}</div>;
1427 + }
1428 +
1429 class Foo extends React.Component {
1430 componentDidMount() {
1431 const limit = 1200;
1432 for (let i = 0; i < limit; i++) {
1433 if (i < limit - 1) {
1392 - ReactDOM.render(<div />, document.createElement('div'));
1434 + ReactDOMClient.createRoot(document.createElement('div')).render(
1435 + <Component />,
1436 + );
1437 } else {
1394 - ReactDOM.render(<div />, document.createElement('div'), () => {
1395 - // The "nested update limit" error isn't thrown until setState
1396 - this.setState({});
1397 - });
1438 + // The "nested update limit" error isn't thrown until setState
1439 + ReactDOMClient.createRoot(document.createElement('div')).render(
1440 + <Component trigger={true} />,
1441 + );
1442 }
1443 }
1444 }
@@ -1403,11 +1447,16 @@ describe('ReactUpdates', () => {
1447 }
1448 }
1449
1406 - const container = document.createElement('div');
1407 - ReactDOM.render(<Foo />, container);
1450 + const root = ReactDOMClient.createRoot(document.createElement('div'));
1451 + await act(() => {
1452 + root.render(<Foo />);
1453 + });
1454 +
1455 + // Make sure the setState trigger runs.
1456 + assertLog(['Trigger']);
1457 });
1458
1410 - it('resets the update counter for unrelated updates', () => {
1459 + it('resets the update counter for unrelated updates', async () => {
1460 const container = document.createElement('div');
1461 const ref = React.createRef();
1462
@@ -1427,22 +1476,35 @@ describe('ReactUpdates', () => {
1476 }
1477
1478 let limit = 55;
1479 + const root = ReactDOMClient.createRoot(container);
1480 expect(() => {
1431 - ReactDOM.render(<EventuallyTerminating ref={ref} />, container);
1481 + ReactDOM.flushSync(() => {
1482 + root.render(<EventuallyTerminating ref={ref} />);
1483 + });
1484 }).toThrow('Maximum');
1485
1486 // Verify that we don't go over the limit if these updates are unrelated.
1487 limit -= 10;
1436 - ReactDOM.render(<EventuallyTerminating ref={ref} />, container);
1488 + await act(() => {
1489 + root.render(<EventuallyTerminating ref={ref} />);
1490 + });
1491 expect(container.textContent).toBe(limit.toString());
1438 - ref.current.setState({step: 0});
1492 +
1493 + await act(() => {
1494 + ref.current.setState({step: 0});
1495 + });
1496 expect(container.textContent).toBe(limit.toString());
1440 - ref.current.setState({step: 0});
1497 +
1498 + await act(() => {
1499 + ref.current.setState({step: 0});
1500 + });
1501 expect(container.textContent).toBe(limit.toString());
1502
1503 limit += 10;
1504 expect(() => {
1445 - ref.current.setState({step: 0});
1505 + ReactDOM.flushSync(() => {
1506 + ref.current.setState({step: 0});
1507 + });
1508 }).toThrow('Maximum');
1509 expect(ref.current).toBe(null);
1510 });
@@ -1450,12 +1512,15 @@ describe('ReactUpdates', () => {
1512 it('does not fall into an infinite update loop', () => {
1513 class NonTerminating extends React.Component {
1514 state = {step: 0};
1515 +
1516 componentDidMount() {
1517 this.setState({step: 1});
1518 }
1456 - UNSAFE_componentWillUpdate() {
1519 +
1520 + componentDidUpdate() {
1521 this.setState({step: 2});
1522 }
1523 +
1524 render() {
1525 return (
1526 <div>
@@ -1467,8 +1532,12 @@ describe('ReactUpdates', () => {
1532 }
1533
1534 const container = document.createElement('div');
1535 + const root = ReactDOMClient.createRoot(container);
1536 +
1537 expect(() => {
1471 - ReactDOM.render(<NonTerminating />, container);
1538 + ReactDOM.flushSync(() => {
1539 + root.render(<NonTerminating />);
1540 + });
1541 }).toThrow('Maximum');
1542 });
1543
@@ -1482,12 +1551,15 @@ describe('ReactUpdates', () => {
1551 }
1552
1553 const container = document.createElement('div');
1554 + const root = ReactDOMClient.createRoot(container);
1555 expect(() => {
1486 - ReactDOM.render(<NonTerminating />, container);
1556 + ReactDOM.flushSync(() => {
1557 + root.render(<NonTerminating />);
1558 + });
1559 }).toThrow('Maximum');
1560 });
1561
1490 - it('can recover after falling into an infinite update loop', () => {
1562 + it('can recover after falling into an infinite update loop', async () => {
1563 class NonTerminating extends React.Component {
1564 state = {step: 0};
1565 componentDidMount() {
@@ -1512,27 +1584,36 @@ describe('ReactUpdates', () => {
1584 }
1585
1586 const container = document.createElement('div');
1587 + const root = ReactDOMClient.createRoot(container);
1588 expect(() => {
1516 - ReactDOM.render(<NonTerminating />, container);
1589 + ReactDOM.flushSync(() => {
1590 + root.render(<NonTerminating />);
1591 + });
1592 }).toThrow('Maximum');
1593
1519 - ReactDOM.render(<Terminating />, container);
1594 + await act(() => {
1595 + root.render(<Terminating />);
1596 + });
1597 expect(container.textContent).toBe('1');
1598
1599 expect(() => {
1523 - ReactDOM.render(<NonTerminating />, container);
1600 + ReactDOM.flushSync(() => {
1601 + root.render(<NonTerminating />);
1602 + });
1603 }).toThrow('Maximum');
1525 -
1526 - ReactDOM.render(<Terminating />, container);
1604 + await act(() => {
1605 + root.render(<Terminating />);
1606 + });
1607 expect(container.textContent).toBe('1');
1608 });
1609
1610 it('does not fall into mutually recursive infinite update loop with same container', () => {
1611 // Note: this test would fail if there were two or more different roots.
1532 -
1612 + const container = document.createElement('div');
1613 + const root = ReactDOMClient.createRoot(container);
1614 class A extends React.Component {
1615 componentDidMount() {
1535 - ReactDOM.render(<B />, container);
1616 + root.render(<B />);
1617 }
1618 render() {
1619 return null;
@@ -1541,16 +1622,17 @@ describe('ReactUpdates', () => {
1622
1623 class B extends React.Component {
1624 componentDidMount() {
1544 - ReactDOM.render(<A />, container);
1625 + root.render(<A />);
1626 }
1627 render() {
1628 return null;
1629 }
1630 }
1631
1551 - const container = document.createElement('div');
1632 expect(() => {
1553 - ReactDOM.render(<A />, container);
1633 + ReactDOM.flushSync(() => {
1634 + root.render(<A />);
1635 + });
1636 }).toThrow('Maximum');
1637 });
1638
@@ -1582,14 +1664,17 @@ describe('ReactUpdates', () => {
1664 }
1665
1666 const container = document.createElement('div');
1667 + const root = ReactDOMClient.createRoot(container);
1668 expect(() => {
1586 - ReactDOM.render(<NonTerminating />, container);
1669 + ReactDOM.flushSync(() => {
1670 + root.render(<NonTerminating />);
1671 + });
1672 }).toThrow('Maximum');
1673 });
1674
1590 - it('can schedule ridiculously many updates within the same batch without triggering a maximum update error', () => {
1675 + it('can schedule ridiculously many updates within the same batch without triggering a maximum update error', async () => {
1676 const subscribers = [];
1592 -
1677 + const limit = 1200;
1678 class Child extends React.Component {
1679 state = {value: 'initial'};
1680 componentDidMount() {
@@ -1603,7 +1688,7 @@ describe('ReactUpdates', () => {
1688 class App extends React.Component {
1689 render() {
1690 const children = [];
1606 - for (let i = 0; i < 1200; i++) {
1691 + for (let i = 0; i < limit; i++) {
1692 children.push(<Child key={i} />);
1693 }
1694 return children;
@@ -1611,13 +1696,18 @@ describe('ReactUpdates', () => {
1696 }
1697
1698 const container = document.createElement('div');
1614 - ReactDOM.render(<App />, container);
1699 + const root = ReactDOMClient.createRoot(container);
1700 + await act(() => {
1701 + root.render(<App />);
1702 + });
1703
1616 - ReactDOM.unstable_batchedUpdates(() => {
1704 + await act(() => {
1705 subscribers.forEach(s => {
1706 s.setState({value: 'update'});
1707 });
1708 });
1709 +
1710 + expect(subscribers.length).toBe(limit);
1711 });
1712
1713 // TODO: Replace this branch with @gate pragmas
@@ -1673,8 +1763,9 @@ describe('ReactUpdates', () => {
1763 }
1764
1765 const container = document.createElement('div');
1766 + const root = ReactDOMClient.createRoot(container);
1767 await act(() => {
1677 - ReactDOM.render(<Terminating />, container);
1768 + root.render(<Terminating />);
1769 });
1770 expect(container.textContent).toBe('50');
1771 await act(() => {
@@ -1696,8 +1787,9 @@ describe('ReactUpdates', () => {
1787 }
1788
1789 const container = document.createElement('div');
1790 + const root = ReactDOMClient.createRoot(container);
1791 await act(() => {
1700 - ReactDOM.render(<Terminating />, container);
1792 + root.render(<Terminating />);
1793 });
1794
1795 assertLog(['Done']);