main
js 941 lines 25.8 KB
Raw
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 findDOMNode;
15 let ReactDOMClient;
16 let PropTypes;
17
18 let act;
19 let assertConsoleErrorDev;
20
21 describe('ReactLegacyCompositeComponent', () => {
22 beforeEach(() => {
23 jest.resetModules();
24 React = require('react');
25 ReactDOM = require('react-dom');
26 ReactDOMClient = require('react-dom/client');
27 findDOMNode =
28 ReactDOM.__DOM_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE
29 .findDOMNode;
30 PropTypes = require('prop-types');
31 ({act, assertConsoleErrorDev} = require('internal-test-utils'));
32 });
33
34 // @gate !disableLegacyMode
35 it('should warn about `setState` in render in legacy mode', () => {
36 const container = document.createElement('div');
37
38 let renderedState = -1;
39 let renderPasses = 0;
40
41 class Component extends React.Component {
42 state = {value: 0};
43
44 render() {
45 renderPasses++;
46 renderedState = this.state.value;
47 if (this.state.value === 0) {
48 this.setState({value: 1});
49 }
50 return <div />;
51 }
52 }
53 const instance = ReactDOM.render(<Component />, container);
54 assertConsoleErrorDev([
55 'Cannot update during an existing state transition (such as within ' +
56 '`render`). Render methods should be a pure function of props and state.\n' +
57 ' in Component (at **)',
58 ]);
59
60 // The setState call is queued and then executed as a second pass. This
61 // behavior is undefined though so we're free to change it to suit the
62 // implementation details.
63 expect(renderPasses).toBe(2);
64 expect(renderedState).toBe(1);
65 expect(instance.state.value).toBe(1);
66
67 // Forcing a rerender anywhere will cause the update to happen.
68 const instance2 = ReactDOM.render(<Component prop={123} />, container);
69 expect(instance).toBe(instance2);
70 expect(renderedState).toBe(1);
71 expect(instance2.state.value).toBe(1);
72
73 // Test deduplication; (no additional warnings are expected).
74 ReactDOM.unmountComponentAtNode(container);
75 ReactDOM.render(<Component prop={123} />, container);
76 });
77
78 // @gate !disableLegacyContext
79 it('should pass context to children when not owner', async () => {
80 class Parent extends React.Component {
81 render() {
82 return (
83 <Child>
84 <Grandchild />
85 </Child>
86 );
87 }
88 }
89
90 class Child extends React.Component {
91 static childContextTypes = {
92 foo: PropTypes.string,
93 };
94
95 getChildContext() {
96 return {
97 foo: 'bar',
98 };
99 }
100
101 render() {
102 return React.Children.only(this.props.children);
103 }
104 }
105
106 class Grandchild extends React.Component {
107 static contextTypes = {
108 foo: PropTypes.string,
109 };
110
111 render() {
112 return <div>{this.context.foo}</div>;
113 }
114 }
115 const container = document.createElement('div');
116 const root = ReactDOMClient.createRoot(container);
117 let component;
118 await act(() => {
119 root.render(<Parent ref={current => (component = current)} />);
120 });
121 assertConsoleErrorDev([
122 'Child uses the legacy childContextTypes API which will soon be removed. ' +
123 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
124 ' in Parent (at **)',
125 'Grandchild uses the legacy contextTypes API which will soon be removed. ' +
126 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
127 ' in Parent (at **)',
128 ]);
129 expect(findDOMNode(component).innerHTML).toBe('bar');
130 });
131
132 // @gate !disableLegacyContext
133 it('should pass context when re-rendered for static child', async () => {
134 let parentInstance = null;
135 let childInstance = null;
136
137 class Parent extends React.Component {
138 static childContextTypes = {
139 foo: PropTypes.string,
140 flag: PropTypes.bool,
141 };
142
143 state = {
144 flag: false,
145 };
146
147 getChildContext() {
148 return {
149 foo: 'bar',
150 flag: this.state.flag,
151 };
152 }
153
154 render() {
155 return React.Children.only(this.props.children);
156 }
157 }
158
159 class Middle extends React.Component {
160 render() {
161 return this.props.children;
162 }
163 }
164
165 class Child extends React.Component {
166 static contextTypes = {
167 foo: PropTypes.string,
168 flag: PropTypes.bool,
169 };
170
171 render() {
172 childInstance = this;
173 return <span>Child</span>;
174 }
175 }
176
177 const container = document.createElement('div');
178 const root = ReactDOMClient.createRoot(container);
179
180 await act(() => {
181 root.render(
182 <Parent ref={current => (parentInstance = current)}>
183 <Middle>
184 <Child />
185 </Middle>
186 </Parent>,
187 );
188 });
189
190 expect(parentInstance.state.flag).toBe(false);
191 expect(childInstance.context).toEqual({foo: 'bar', flag: false});
192
193 assertConsoleErrorDev([
194 'Parent uses the legacy childContextTypes API which will soon be removed. ' +
195 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
196 ' in Parent (at **)',
197 'Child uses the legacy contextTypes API which will soon be removed. ' +
198 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
199 ' in Child (at **)',
200 ]);
201
202 await act(() => {
203 parentInstance.setState({flag: true});
204 });
205 expect(parentInstance.state.flag).toBe(true);
206 expect(childInstance.context).toEqual({foo: 'bar', flag: true});
207 });
208
209 // @gate !disableLegacyContext
210 it('should pass context when re-rendered for static child within a composite component', async () => {
211 class Parent extends React.Component {
212 static childContextTypes = {
213 flag: PropTypes.bool,
214 };
215
216 state = {
217 flag: true,
218 };
219
220 getChildContext() {
221 return {
222 flag: this.state.flag,
223 };
224 }
225
226 render() {
227 return <div>{this.props.children}</div>;
228 }
229 }
230
231 class Child extends React.Component {
232 static contextTypes = {
233 flag: PropTypes.bool,
234 };
235
236 render() {
237 return <div />;
238 }
239 }
240
241 class Wrapper extends React.Component {
242 parentRef = React.createRef();
243 childRef = React.createRef();
244
245 render() {
246 return (
247 <Parent ref={this.parentRef}>
248 <Child ref={this.childRef} />
249 </Parent>
250 );
251 }
252 }
253
254 const container = document.createElement('div');
255 const root = ReactDOMClient.createRoot(container);
256 let wrapper;
257 await act(() => {
258 root.render(<Wrapper ref={current => (wrapper = current)} />);
259 });
260
261 assertConsoleErrorDev([
262 'Parent uses the legacy childContextTypes API which will soon be removed. ' +
263 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
264 ' in Wrapper (at **)',
265 'Child uses the legacy contextTypes API which will soon be removed. ' +
266 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
267 ' in Wrapper (at **)',
268 ]);
269
270 expect(wrapper.parentRef.current.state.flag).toEqual(true);
271 expect(wrapper.childRef.current.context).toEqual({flag: true});
272
273 // We update <Parent /> while <Child /> is still a static prop relative to this update
274 await act(() => {
275 wrapper.parentRef.current.setState({flag: false});
276 });
277
278 expect(wrapper.parentRef.current.state.flag).toEqual(false);
279 expect(wrapper.childRef.current.context).toEqual({flag: false});
280 });
281
282 // @gate !disableLegacyContext
283 it('should pass context transitively', async () => {
284 let childInstance = null;
285 let grandchildInstance = null;
286
287 class Parent extends React.Component {
288 static childContextTypes = {
289 foo: PropTypes.string,
290 depth: PropTypes.number,
291 };
292
293 getChildContext() {
294 return {
295 foo: 'bar',
296 depth: 0,
297 };
298 }
299
300 render() {
301 return <Child />;
302 }
303 }
304
305 class Child extends React.Component {
306 static contextTypes = {
307 foo: PropTypes.string,
308 depth: PropTypes.number,
309 };
310
311 static childContextTypes = {
312 depth: PropTypes.number,
313 };
314
315 getChildContext() {
316 return {
317 depth: this.context.depth + 1,
318 };
319 }
320
321 render() {
322 childInstance = this;
323 return <Grandchild />;
324 }
325 }
326
327 class Grandchild extends React.Component {
328 static contextTypes = {
329 foo: PropTypes.string,
330 depth: PropTypes.number,
331 };
332
333 render() {
334 grandchildInstance = this;
335 return <div />;
336 }
337 }
338
339 const container = document.createElement('div');
340 const root = ReactDOMClient.createRoot(container);
341 await act(() => {
342 root.render(<Parent />);
343 });
344
345 assertConsoleErrorDev([
346 'Parent uses the legacy childContextTypes API which will soon be removed. ' +
347 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
348 ' in Parent (at **)',
349 'Child uses the legacy childContextTypes API which will soon be removed. ' +
350 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
351 ' in Parent (at **)',
352 'Child uses the legacy contextTypes API which will soon be removed. ' +
353 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
354 ' in Parent (at **)',
355 'Grandchild uses the legacy contextTypes API which will soon be removed. ' +
356 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
357 ' in Child (at **)\n' +
358 ' in Parent (at **)',
359 ]);
360
361 expect(childInstance.context).toEqual({foo: 'bar', depth: 0});
362 expect(grandchildInstance.context).toEqual({foo: 'bar', depth: 1});
363 });
364
365 // @gate !disableLegacyContext
366 it('should pass context when re-rendered', async () => {
367 let parentInstance = null;
368 let childInstance = null;
369
370 class Parent extends React.Component {
371 static childContextTypes = {
372 foo: PropTypes.string,
373 depth: PropTypes.number,
374 };
375
376 state = {
377 flag: false,
378 };
379
380 getChildContext() {
381 return {
382 foo: 'bar',
383 depth: 0,
384 };
385 }
386
387 render() {
388 let output = <Child />;
389 if (!this.state.flag) {
390 output = <span>Child</span>;
391 }
392 return output;
393 }
394 }
395
396 class Child extends React.Component {
397 static contextTypes = {
398 foo: PropTypes.string,
399 depth: PropTypes.number,
400 };
401
402 render() {
403 childInstance = this;
404 return <span>Child</span>;
405 }
406 }
407
408 const container = document.createElement('div');
409 const root = ReactDOMClient.createRoot(container);
410 await act(() => {
411 root.render(<Parent ref={current => (parentInstance = current)} />);
412 });
413 assertConsoleErrorDev([
414 'Parent uses the legacy childContextTypes API which will soon be removed. ' +
415 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
416 ' in Parent (at **)',
417 ]);
418
419 expect(childInstance).toBeNull();
420
421 expect(parentInstance.state.flag).toBe(false);
422 await act(() => {
423 parentInstance.setState({flag: true});
424 });
425 assertConsoleErrorDev([
426 'Child uses the legacy contextTypes API which will soon be removed. ' +
427 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
428 ' in Parent (at **)',
429 ]);
430
431 expect(parentInstance.state.flag).toBe(true);
432
433 expect(childInstance.context).toEqual({foo: 'bar', depth: 0});
434 });
435
436 // @gate !disableLegacyContext
437 // @gate !disableLegacyMode
438 it('unmasked context propagates through updates', () => {
439 class Leaf extends React.Component {
440 static contextTypes = {
441 foo: PropTypes.string.isRequired,
442 };
443
444 UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
445 expect('foo' in nextContext).toBe(true);
446 }
447
448 shouldComponentUpdate(nextProps, nextState, nextContext) {
449 expect('foo' in nextContext).toBe(true);
450 return true;
451 }
452
453 render() {
454 return <span>{this.context.foo}</span>;
455 }
456 }
457
458 class Intermediary extends React.Component {
459 UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
460 expect('foo' in nextContext).toBe(false);
461 }
462
463 shouldComponentUpdate(nextProps, nextState, nextContext) {
464 expect('foo' in nextContext).toBe(false);
465 return true;
466 }
467
468 render() {
469 return <Leaf />;
470 }
471 }
472
473 class Parent extends React.Component {
474 static childContextTypes = {
475 foo: PropTypes.string,
476 };
477
478 getChildContext() {
479 return {
480 foo: this.props.cntxt,
481 };
482 }
483
484 render() {
485 return <Intermediary />;
486 }
487 }
488
489 const div = document.createElement('div');
490 ReactDOM.render(<Parent cntxt="noise" />, div);
491 assertConsoleErrorDev([
492 'Parent uses the legacy childContextTypes API which will soon be removed. ' +
493 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
494 ' in Parent (at **)',
495 'Leaf uses the legacy contextTypes API which will soon be removed. ' +
496 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
497 ' in Intermediary (at **)\n' +
498 ' in Parent (at **)',
499 ]);
500 expect(div.children[0].innerHTML).toBe('noise');
501 div.children[0].innerHTML = 'aliens';
502 div.children[0].id = 'aliens';
503 expect(div.children[0].innerHTML).toBe('aliens');
504 expect(div.children[0].id).toBe('aliens');
505 ReactDOM.render(<Parent cntxt="bar" />, div);
506 expect(div.children[0].innerHTML).toBe('bar');
507 expect(div.children[0].id).toBe('aliens');
508 });
509
510 // @gate !disableLegacyContext
511 // @gate !disableLegacyMode
512 it('should trigger componentWillReceiveProps for context changes', () => {
513 let contextChanges = 0;
514 let propChanges = 0;
515
516 class GrandChild extends React.Component {
517 static contextTypes = {
518 foo: PropTypes.string.isRequired,
519 };
520
521 UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
522 expect('foo' in nextContext).toBe(true);
523
524 if (nextProps !== this.props) {
525 propChanges++;
526 }
527
528 if (nextContext !== this.context) {
529 contextChanges++;
530 }
531 }
532
533 render() {
534 return <span className="grand-child">{this.props.children}</span>;
535 }
536 }
537
538 class ChildWithContext extends React.Component {
539 static contextTypes = {
540 foo: PropTypes.string.isRequired,
541 };
542
543 UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
544 expect('foo' in nextContext).toBe(true);
545
546 if (nextProps !== this.props) {
547 propChanges++;
548 }
549
550 if (nextContext !== this.context) {
551 contextChanges++;
552 }
553 }
554
555 render() {
556 return <div className="child-with">{this.props.children}</div>;
557 }
558 }
559
560 class ChildWithoutContext extends React.Component {
561 UNSAFE_componentWillReceiveProps(nextProps, nextContext) {
562 expect('foo' in nextContext).toBe(false);
563
564 if (nextProps !== this.props) {
565 propChanges++;
566 }
567
568 if (nextContext !== this.context) {
569 contextChanges++;
570 }
571 }
572
573 render() {
574 return <div className="child-without">{this.props.children}</div>;
575 }
576 }
577
578 class Parent extends React.Component {
579 static childContextTypes = {
580 foo: PropTypes.string,
581 };
582
583 state = {
584 foo: 'abc',
585 };
586
587 getChildContext() {
588 return {
589 foo: this.state.foo,
590 };
591 }
592
593 render() {
594 return <div className="parent">{this.props.children}</div>;
595 }
596 }
597
598 const div = document.createElement('div');
599
600 let parentInstance = null;
601 ReactDOM.render(
602 <Parent ref={inst => (parentInstance = inst)}>
603 <ChildWithoutContext>
604 A1
605 <GrandChild>A2</GrandChild>
606 </ChildWithoutContext>
607
608 <ChildWithContext>
609 B1
610 <GrandChild>B2</GrandChild>
611 </ChildWithContext>
612 </Parent>,
613 div,
614 );
615 assertConsoleErrorDev([
616 'Parent uses the legacy childContextTypes API which will soon be removed. ' +
617 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
618 ' in Parent (at **)',
619 'GrandChild uses the legacy contextTypes API which will soon be removed. ' +
620 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
621 ' in GrandChild (at **)',
622 'ChildWithContext uses the legacy contextTypes API which will soon be removed. ' +
623 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
624 ' in ChildWithContext (at **)',
625 ]);
626
627 parentInstance.setState({
628 foo: 'def',
629 });
630
631 expect(propChanges).toBe(0);
632 expect(contextChanges).toBe(3); // ChildWithContext, GrandChild x 2
633 });
634
635 // @gate !disableLegacyMode
636 it('only renders once if updated in componentWillReceiveProps in legacy mode', () => {
637 let renders = 0;
638
639 class Component extends React.Component {
640 state = {updated: false};
641
642 UNSAFE_componentWillReceiveProps(props) {
643 expect(props.update).toBe(1);
644 expect(renders).toBe(1);
645 this.setState({updated: true});
646 expect(renders).toBe(1);
647 }
648
649 render() {
650 renders++;
651 return <div />;
652 }
653 }
654
655 const container = document.createElement('div');
656 const instance = ReactDOM.render(<Component update={0} />, container);
657 expect(renders).toBe(1);
658 expect(instance.state.updated).toBe(false);
659 ReactDOM.render(<Component update={1} />, container);
660 expect(renders).toBe(2);
661 expect(instance.state.updated).toBe(true);
662 });
663
664 // @gate !disableLegacyMode
665 it('only renders once if updated in componentWillReceiveProps when batching in legacy mode', () => {
666 let renders = 0;
667
668 class Component extends React.Component {
669 state = {updated: false};
670
671 UNSAFE_componentWillReceiveProps(props) {
672 expect(props.update).toBe(1);
673 expect(renders).toBe(1);
674 this.setState({updated: true});
675 expect(renders).toBe(1);
676 }
677
678 render() {
679 renders++;
680 return <div />;
681 }
682 }
683
684 const container = document.createElement('div');
685 const instance = ReactDOM.render(<Component update={0} />, container);
686 expect(renders).toBe(1);
687 expect(instance.state.updated).toBe(false);
688 ReactDOM.unstable_batchedUpdates(() => {
689 ReactDOM.render(<Component update={1} />, container);
690 });
691 expect(renders).toBe(2);
692 expect(instance.state.updated).toBe(true);
693 });
694
695 // @gate !disableLegacyMode
696 it('should update refs if shouldComponentUpdate gives false in legacy mode', () => {
697 class Static extends React.Component {
698 shouldComponentUpdate() {
699 return false;
700 }
701
702 render() {
703 return <div>{this.props.children}</div>;
704 }
705 }
706
707 class Component extends React.Component {
708 static0Ref = React.createRef();
709 static1Ref = React.createRef();
710
711 render() {
712 if (this.props.flipped) {
713 return (
714 <div>
715 <Static ref={this.static0Ref} key="B">
716 B (ignored)
717 </Static>
718 <Static ref={this.static1Ref} key="A">
719 A (ignored)
720 </Static>
721 </div>
722 );
723 } else {
724 return (
725 <div>
726 <Static ref={this.static0Ref} key="A">
727 A
728 </Static>
729 <Static ref={this.static1Ref} key="B">
730 B
731 </Static>
732 </div>
733 );
734 }
735 }
736 }
737
738 const container = document.createElement('div');
739 const comp = ReactDOM.render(<Component flipped={false} />, container);
740 expect(findDOMNode(comp.static0Ref.current).textContent).toBe('A');
741 expect(findDOMNode(comp.static1Ref.current).textContent).toBe('B');
742
743 // When flipping the order, the refs should update even though the actual
744 // contents do not
745 ReactDOM.render(<Component flipped={true} />, container);
746 expect(findDOMNode(comp.static0Ref.current).textContent).toBe('B');
747 expect(findDOMNode(comp.static1Ref.current).textContent).toBe('A');
748 });
749
750 // @gate !disableLegacyMode
751 it('should allow access to findDOMNode in componentWillUnmount in legacy mode', () => {
752 let a = null;
753 let b = null;
754
755 class Component extends React.Component {
756 componentDidMount() {
757 a = findDOMNode(this);
758 expect(a).not.toBe(null);
759 }
760
761 componentWillUnmount() {
762 b = findDOMNode(this);
763 expect(b).not.toBe(null);
764 }
765
766 render() {
767 return <div />;
768 }
769 }
770
771 const container = document.createElement('div');
772 expect(a).toBe(container.firstChild);
773 ReactDOM.render(<Component />, container);
774 ReactDOM.unmountComponentAtNode(container);
775 expect(a).toBe(b);
776 });
777
778 // @gate !disableLegacyContext || !__DEV__
779 // @gate !disableLegacyMode
780 it('context should be passed down from the parent', () => {
781 class Parent extends React.Component {
782 static childContextTypes = {
783 foo: PropTypes.string,
784 };
785
786 getChildContext() {
787 return {
788 foo: 'bar',
789 };
790 }
791
792 render() {
793 return <div>{this.props.children}</div>;
794 }
795 }
796
797 class Component extends React.Component {
798 static contextTypes = {
799 foo: PropTypes.string.isRequired,
800 };
801
802 render() {
803 return <div />;
804 }
805 }
806
807 const div = document.createElement('div');
808 ReactDOM.render(
809 <Parent>
810 <Component />
811 </Parent>,
812 div,
813 );
814 assertConsoleErrorDev([
815 'Parent uses the legacy childContextTypes API which will soon be removed. ' +
816 'Use React.createContext() instead. (https://react.dev/link/legacy-context)\n' +
817 ' in Parent (at **)',
818 'Component uses the legacy contextTypes API which will soon be removed. ' +
819 'Use React.createContext() with static contextType instead. (https://react.dev/link/legacy-context)\n' +
820 ' in Component (at **)',
821 ]);
822 });
823
824 it('should replace state in legacy mode', async () => {
825 class Moo extends React.Component {
826 state = {x: 1};
827 render() {
828 return <div />;
829 }
830 }
831
832 const container = document.createElement('div');
833 const root = ReactDOMClient.createRoot(container);
834 let moo;
835 await act(() => {
836 root.render(<Moo ref={current => (moo = current)} />);
837 });
838
839 // No longer a public API, but we can test that it works internally by
840 // reaching into the updater.
841 await act(() => {
842 moo.updater.enqueueReplaceState(moo, {y: 2});
843 });
844 expect('x' in moo.state).toBe(false);
845 expect(moo.state.y).toBe(2);
846 });
847
848 it('should support objects with prototypes as state in legacy mode', async () => {
849 const NotActuallyImmutable = function (str) {
850 this.str = str;
851 };
852 NotActuallyImmutable.prototype.amIImmutable = function () {
853 return true;
854 };
855 class Moo extends React.Component {
856 state = new NotActuallyImmutable('first');
857 // No longer a public API, but we can test that it works internally by
858 // reaching into the updater.
859 _replaceState = update => this.updater.enqueueReplaceState(this, update);
860 render() {
861 return <div />;
862 }
863 }
864
865 const container = document.createElement('div');
866 const root = ReactDOMClient.createRoot(container);
867 let moo;
868 await act(() => {
869 root.render(<Moo ref={current => (moo = current)} />);
870 });
871
872 expect(moo.state.str).toBe('first');
873 expect(moo.state.amIImmutable()).toBe(true);
874
875 const secondState = new NotActuallyImmutable('second');
876 await act(() => {
877 moo._replaceState(secondState);
878 });
879 expect(moo.state.str).toBe('second');
880 expect(moo.state.amIImmutable()).toBe(true);
881 expect(moo.state).toBe(secondState);
882
883 await act(() => {
884 moo.setState({str: 'third'});
885 });
886 expect(moo.state.str).toBe('third');
887 // Here we lose the prototype.
888 expect(moo.state.amIImmutable).toBe(undefined);
889
890 // When more than one state update is enqueued, we have the same behavior
891 const fifthState = new NotActuallyImmutable('fifth');
892 await act(() => {
893 moo.setState({str: 'fourth'});
894 moo._replaceState(fifthState);
895 });
896 expect(moo.state).toBe(fifthState);
897
898 // When more than one state update is enqueued, we have the same behavior
899 const sixthState = new NotActuallyImmutable('sixth');
900 await act(() => {
901 moo._replaceState(sixthState);
902 moo.setState({str: 'seventh'});
903 });
904 expect(moo.state.str).toBe('seventh');
905 expect(moo.state.amIImmutable).toBe(undefined);
906 });
907
908 // @gate !disableLegacyMode
909 it('should not warn about unmounting during unmounting in legacy mode', () => {
910 const container = document.createElement('div');
911 const layer = document.createElement('div');
912
913 class Component extends React.Component {
914 componentDidMount() {
915 ReactDOM.render(<div />, layer);
916 }
917
918 componentWillUnmount() {
919 ReactDOM.unmountComponentAtNode(layer);
920 }
921
922 render() {
923 return <div />;
924 }
925 }
926
927 class Outer extends React.Component {
928 render() {
929 return <div>{this.props.children}</div>;
930 }
931 }
932
933 ReactDOM.render(
934 <Outer>
935 <Component />
936 </Outer>,
937 container,
938 );
939 ReactDOM.render(<Outer />, container);
940 });
941 });