main
js 1,240 lines 29.3 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 * @jest-environment node
9 */
10
11 'use strict';
12
13 const ReactFeatureFlags = require('shared/ReactFeatureFlags');
14 const React = require('react');
15 const ReactTestRenderer = require('react-test-renderer');
16 const {format: prettyFormat} = require('pretty-format');
17 const InternalTestUtils = require('internal-test-utils');
18 const waitForAll = InternalTestUtils.waitForAll;
19 const act = InternalTestUtils.act;
20 const Reconciler = require('react-reconciler/src/ReactFiberReconciler');
21 const {
22 ConcurrentRoot,
23 LegacyRoot,
24 } = require('react-reconciler/src/ReactRootTags');
25
26 // Isolate noop renderer
27 jest.resetModules();
28 const ReactNoop = require('react-noop-renderer');
29
30 // Kind of hacky, but we nullify all the instances to test the tree structure
31 // with jasmine's deep equality function, and test the instances separate. We
32 // also delete children props because testing them is more annoying and not
33 // really important to verify.
34 function cleanNodeOrArray(node) {
35 if (!node) {
36 return;
37 }
38 if (Array.isArray(node)) {
39 node.forEach(cleanNodeOrArray);
40 return;
41 }
42 if (node && node.instance) {
43 node.instance = null;
44 }
45 if (node && node.props && node.props.children) {
46 const {children, ...props} = node.props;
47 node.props = props;
48 }
49 if (Array.isArray(node.rendered)) {
50 node.rendered.forEach(cleanNodeOrArray);
51 } else if (typeof node.rendered === 'object') {
52 cleanNodeOrArray(node.rendered);
53 }
54 }
55
56 describe('ReactTestRenderer', () => {
57 beforeEach(() => {
58 jest.resetModules();
59 ReactFeatureFlags.enableReactTestRendererWarning = false;
60 });
61
62 // @gate __DEV__
63 it('should warn if enableReactTestRendererWarning is enabled', () => {
64 jest.spyOn(console, 'error').mockImplementation(() => {});
65 ReactFeatureFlags.enableReactTestRendererWarning = true;
66 ReactTestRenderer.create(<div />);
67 expect(console.error).toHaveBeenCalledTimes(1);
68 expect(console.error.mock.calls[0][0]).toContain(
69 'react-test-renderer is deprecated. See https://react.dev/warnings/react-test-renderer',
70 );
71 console.error.mockRestore();
72 });
73
74 it('should not warn if enableReactTestRendererWarning is enabled but the RN global is set', () => {
75 jest.spyOn(console, 'error').mockImplementation(() => {});
76 global.IS_REACT_NATIVE_TEST_ENVIRONMENT = true;
77 ReactFeatureFlags.enableReactTestRendererWarning = true;
78 ReactTestRenderer.create(<div />);
79 expect(console.error).toHaveBeenCalledTimes(0);
80 console.error.mockRestore();
81 });
82
83 describe('root tags', () => {
84 let createContainerSpy;
85 beforeEach(() => {
86 global.IS_REACT_NATIVE_TEST_ENVIRONMENT = false;
87 createContainerSpy = jest.spyOn(Reconciler, 'createContainer');
88 });
89
90 function expectTag(tag) {
91 expect(createContainerSpy).toHaveBeenCalledWith(
92 expect.anything(),
93 tag,
94 null,
95 expect.anything(),
96 false,
97 expect.anything(),
98 expect.anything(),
99 expect.anything(),
100 expect.anything(),
101 expect.anything(),
102 null,
103 );
104 }
105
106 // @gate disableLegacyMode
107 it('should render using concurrent root if disableLegacyMode', () => {
108 ReactTestRenderer.create(<div />);
109 expectTag(ConcurrentRoot);
110 });
111
112 // @gate !disableLegacyMode
113 it('should default to legacy root if not disableLegacyMode', () => {
114 ReactTestRenderer.create(<div />);
115 expectTag(LegacyRoot);
116 });
117
118 it('should allow unstable_isConcurrent if not disableLegacyMode', async () => {
119 ReactTestRenderer.create(<div />, {
120 unstable_isConcurrent: true,
121 });
122 ReactTestRenderer.create(<div />);
123 expectTag(ConcurrentRoot);
124 });
125
126 it('should render legacy root when RN test environment', async () => {
127 global.IS_REACT_NATIVE_TEST_ENVIRONMENT = true;
128 ReactTestRenderer.create(<div />);
129 expectTag(LegacyRoot);
130 });
131 });
132
133 it('renders a simple component', async () => {
134 function Link() {
135 return <a role="link" />;
136 }
137 let renderer;
138 await act(() => {
139 renderer = ReactTestRenderer.create(<Link />);
140 });
141 expect(renderer.toJSON()).toEqual({
142 type: 'a',
143 props: {role: 'link'},
144 children: null,
145 });
146 });
147
148 it('renders a top-level empty component', async () => {
149 function Empty() {
150 return null;
151 }
152 let renderer;
153 await act(() => {
154 renderer = ReactTestRenderer.create(<Empty />);
155 });
156 expect(renderer.toJSON()).toEqual(null);
157 });
158
159 it('exposes a type flag', async () => {
160 function Link() {
161 return <a role="link" />;
162 }
163 let renderer;
164 await act(() => {
165 renderer = ReactTestRenderer.create(<Link />);
166 });
167 const object = renderer.toJSON();
168 expect(object.$$typeof).toBe(Symbol.for('react.test.json'));
169
170 // $$typeof should not be enumerable.
171 for (const key in object) {
172 if (object.hasOwnProperty(key)) {
173 expect(key).not.toBe('$$typeof');
174 }
175 }
176 });
177
178 it('can render a composite component', async () => {
179 class Component extends React.Component {
180 render() {
181 return (
182 <div className="purple">
183 <Child />
184 </div>
185 );
186 }
187 }
188
189 const Child = () => {
190 return <moo />;
191 };
192
193 let renderer;
194 await act(() => {
195 renderer = ReactTestRenderer.create(<Component />);
196 });
197 expect(renderer.toJSON()).toEqual({
198 type: 'div',
199 props: {className: 'purple'},
200 children: [{type: 'moo', props: {}, children: null}],
201 });
202 });
203
204 it('renders some basics with an update', async () => {
205 let renders = 0;
206
207 class Component extends React.Component {
208 state = {x: 3};
209
210 render() {
211 renders++;
212 return (
213 <div className="purple">
214 {this.state.x}
215 <Child />
216 <Null />
217 </div>
218 );
219 }
220
221 componentDidMount() {
222 this.setState({x: 7});
223 }
224 }
225
226 const Child = () => {
227 renders++;
228 return <moo />;
229 };
230
231 const Null = () => {
232 renders++;
233 return null;
234 };
235
236 let renderer;
237 await act(() => {
238 renderer = ReactTestRenderer.create(<Component />);
239 });
240 expect(renderer.toJSON()).toEqual({
241 type: 'div',
242 props: {className: 'purple'},
243 children: ['7', {type: 'moo', props: {}, children: null}],
244 });
245 expect(renders).toBe(6);
246 });
247
248 it('exposes the instance', async () => {
249 class Mouse extends React.Component {
250 constructor() {
251 super();
252 this.state = {mouse: 'mouse'};
253 }
254 handleMoose() {
255 this.setState({mouse: 'moose'});
256 }
257 render() {
258 return <div>{this.state.mouse}</div>;
259 }
260 }
261 let renderer;
262 await act(() => {
263 renderer = ReactTestRenderer.create(<Mouse />);
264 });
265
266 expect(renderer.toJSON()).toEqual({
267 type: 'div',
268 props: {},
269 children: ['mouse'],
270 });
271
272 const mouse = renderer.getInstance();
273 await act(() => {
274 mouse.handleMoose();
275 });
276 expect(renderer.toJSON()).toEqual({
277 type: 'div',
278 children: ['moose'],
279 props: {},
280 });
281 });
282
283 it('updates types', async () => {
284 let renderer;
285 await act(() => {
286 renderer = ReactTestRenderer.create(<div>mouse</div>);
287 });
288
289 expect(renderer.toJSON()).toEqual({
290 type: 'div',
291 props: {},
292 children: ['mouse'],
293 });
294 await act(() => {
295 renderer.update(<span>mice</span>);
296 });
297 expect(renderer.toJSON()).toEqual({
298 type: 'span',
299 props: {},
300 children: ['mice'],
301 });
302 });
303
304 it('updates children', async () => {
305 let renderer;
306 await act(() => {
307 renderer = ReactTestRenderer.create(
308 <div>
309 <span key="a">A</span>
310 <span key="b">B</span>
311 <span key="c">C</span>
312 </div>,
313 );
314 });
315
316 expect(renderer.toJSON()).toEqual({
317 type: 'div',
318 props: {},
319 children: [
320 {type: 'span', props: {}, children: ['A']},
321 {type: 'span', props: {}, children: ['B']},
322 {type: 'span', props: {}, children: ['C']},
323 ],
324 });
325
326 await act(() => {
327 renderer.update(
328 <div>
329 <span key="d">D</span>
330 <span key="c">C</span>
331 <span key="b">B</span>
332 </div>,
333 );
334 });
335 expect(renderer.toJSON()).toEqual({
336 type: 'div',
337 props: {},
338 children: [
339 {type: 'span', props: {}, children: ['D']},
340 {type: 'span', props: {}, children: ['C']},
341 {type: 'span', props: {}, children: ['B']},
342 ],
343 });
344 });
345
346 it('does the full lifecycle', async () => {
347 const log = [];
348 class Log extends React.Component {
349 render() {
350 log.push('render ' + this.props.name);
351 return <div />;
352 }
353 componentDidMount() {
354 log.push('mount ' + this.props.name);
355 }
356 componentWillUnmount() {
357 log.push('unmount ' + this.props.name);
358 }
359 }
360
361 let renderer;
362 await act(() => {
363 renderer = ReactTestRenderer.create(<Log key="foo" name="Foo" />);
364 });
365 await act(() => {
366 renderer.update(<Log key="bar" name="Bar" />);
367 });
368 await act(() => {
369 renderer.unmount();
370 });
371
372 expect(log).toEqual([
373 'render Foo',
374 'mount Foo',
375 'render Bar',
376 'unmount Foo',
377 'mount Bar',
378 'unmount Bar',
379 ]);
380 });
381
382 it('gives a ref to native components', async () => {
383 const log = [];
384 await act(() => {
385 ReactTestRenderer.create(<div ref={r => log.push(r)} />);
386 });
387 expect(log).toEqual([null]);
388 });
389
390 it('allows an optional createNodeMock function', async () => {
391 const mockDivInstance = {appendChild: () => {}};
392 const mockInputInstance = {focus: () => {}};
393 const mockListItemInstance = {click: () => {}};
394 const mockAnchorInstance = {hover: () => {}};
395 const log = [];
396 class Foo extends React.Component {
397 barRef = React.createRef();
398 componentDidMount() {
399 log.push(this.barRef.current);
400 }
401 render() {
402 return <a ref={this.barRef}>Hello, world</a>;
403 }
404 }
405 function createNodeMock(element) {
406 switch (element.type) {
407 case 'div':
408 return mockDivInstance;
409 case 'input':
410 return mockInputInstance;
411 case 'li':
412 return mockListItemInstance;
413 case 'a':
414 return mockAnchorInstance;
415 default:
416 return {};
417 }
418 }
419 await act(() => {
420 ReactTestRenderer.create(<div ref={r => log.push(r)} />, {
421 createNodeMock,
422 });
423 });
424 await act(() => {
425 ReactTestRenderer.create(<input ref={r => log.push(r)} />, {
426 createNodeMock,
427 });
428 });
429 await act(() => {
430 ReactTestRenderer.create(
431 <div>
432 <span>
433 <ul>
434 <li ref={r => log.push(r)} />
435 </ul>
436 <ul>
437 <li ref={r => log.push(r)} />
438 <li ref={r => log.push(r)} />
439 </ul>
440 </span>
441 </div>,
442 {createNodeMock, foobar: true},
443 );
444 });
445 await act(() => {
446 ReactTestRenderer.create(<Foo />, {createNodeMock});
447 });
448 await act(() => {
449 ReactTestRenderer.create(<div ref={r => log.push(r)} />);
450 });
451 await act(() => {
452 ReactTestRenderer.create(<div ref={r => log.push(r)} />, {});
453 });
454 expect(log).toEqual([
455 mockDivInstance,
456 mockInputInstance,
457 mockListItemInstance,
458 mockListItemInstance,
459 mockListItemInstance,
460 mockAnchorInstance,
461 null,
462 null,
463 ]);
464 });
465
466 it('supports unmounting when using refs', () => {
467 class Foo extends React.Component {
468 render() {
469 return <div ref={React.createRef()} />;
470 }
471 }
472 const inst = ReactTestRenderer.create(<Foo />, {
473 createNodeMock: () => 'foo',
474 });
475 expect(() => inst.unmount()).not.toThrow();
476 });
477
478 it('supports unmounting inner instances', async () => {
479 let count = 0;
480 class Foo extends React.Component {
481 componentWillUnmount() {
482 count++;
483 }
484 render() {
485 return <div />;
486 }
487 }
488 let inst;
489 await act(() => {
490 inst = ReactTestRenderer.create(
491 <div>
492 <Foo />
493 </div>,
494 {
495 createNodeMock: () => 'foo',
496 },
497 );
498 });
499 await act(() => {
500 inst.unmount();
501 });
502 expect(count).toEqual(1);
503 });
504
505 it('supports updates when using refs', async () => {
506 const log = [];
507 const createNodeMock = element => {
508 log.push(element.type);
509 return element.type;
510 };
511 class Foo extends React.Component {
512 render() {
513 return this.props.useDiv ? (
514 <div ref={React.createRef()} />
515 ) : (
516 <span ref={React.createRef()} />
517 );
518 }
519 }
520 let inst;
521 await act(() => {
522 inst = ReactTestRenderer.create(<Foo useDiv={true} />, {
523 createNodeMock,
524 });
525 });
526 await act(() => {
527 inst.update(<Foo useDiv={false} />);
528 });
529 expect(log).toEqual(['div', 'span']);
530 });
531
532 it('supports error boundaries', async () => {
533 const log = [];
534 class Angry extends React.Component {
535 render() {
536 log.push('Angry render');
537 throw new Error('Please, do not render me.');
538 }
539
540 componentDidMount() {
541 log.push('Angry componentDidMount');
542 }
543 componentWillUnmount() {
544 log.push('Angry componentWillUnmount');
545 }
546 }
547
548 class Boundary extends React.Component {
549 constructor(props) {
550 super(props);
551 this.state = {error: false};
552 }
553 render() {
554 log.push('Boundary render');
555 if (!this.state.error) {
556 return (
557 <div>
558 <button onClick={this.onClick}>ClickMe</button>
559 <Angry />
560 </div>
561 );
562 } else {
563 return <div>Happy Birthday!</div>;
564 }
565 }
566 componentDidMount() {
567 log.push('Boundary componentDidMount');
568 }
569 componentWillUnmount() {
570 log.push('Boundary componentWillUnmount');
571 }
572 onClick() {
573 /* do nothing */
574 }
575 componentDidCatch() {
576 log.push('Boundary componentDidCatch');
577 this.setState({error: true});
578 }
579 }
580
581 let renderer;
582 await act(() => {
583 renderer = ReactTestRenderer.create(<Boundary />, {
584 unstable_isConcurrent: true,
585 });
586 });
587 expect(renderer.toJSON()).toEqual({
588 type: 'div',
589 props: {},
590 children: ['Happy Birthday!'],
591 });
592 expect(log).toEqual([
593 'Boundary render',
594 'Angry render',
595 'Boundary render',
596 'Angry render',
597 'Boundary componentDidMount',
598 'Boundary componentDidCatch',
599 'Boundary render',
600 ]);
601 });
602
603 it('can update text nodes', async () => {
604 class Component extends React.Component {
605 render() {
606 return <div>{this.props.children}</div>;
607 }
608 }
609
610 let renderer;
611 await act(() => {
612 renderer = ReactTestRenderer.create(<Component>Hi</Component>);
613 });
614 expect(renderer.toJSON()).toEqual({
615 type: 'div',
616 children: ['Hi'],
617 props: {},
618 });
619 await act(() => {
620 renderer.update(<Component>{['Hi', 'Bye']}</Component>);
621 });
622 expect(renderer.toJSON()).toEqual({
623 type: 'div',
624 children: ['Hi', 'Bye'],
625 props: {},
626 });
627 await act(() => {
628 renderer.update(<Component>Bye</Component>);
629 });
630 expect(renderer.toJSON()).toEqual({
631 type: 'div',
632 children: ['Bye'],
633 props: {},
634 });
635 await act(() => {
636 renderer.update(<Component>{42}</Component>);
637 });
638 expect(renderer.toJSON()).toEqual({
639 type: 'div',
640 children: ['42'],
641 props: {},
642 });
643 await act(() => {
644 renderer.update(
645 <Component>
646 <div />
647 </Component>,
648 );
649 });
650 expect(renderer.toJSON()).toEqual({
651 type: 'div',
652 children: [
653 {
654 type: 'div',
655 children: null,
656 props: {},
657 },
658 ],
659 props: {},
660 });
661 });
662
663 it('toTree() renders simple components returning host components', async () => {
664 const Qoo = () => <span className="Qoo">Hello World!</span>;
665
666 let renderer;
667 await act(() => {
668 renderer = ReactTestRenderer.create(<Qoo />);
669 });
670 const tree = renderer.toTree();
671
672 cleanNodeOrArray(tree);
673
674 expect(prettyFormat(tree)).toEqual(
675 prettyFormat({
676 nodeType: 'component',
677 type: Qoo,
678 props: {},
679 instance: null,
680 rendered: {
681 nodeType: 'host',
682 type: 'span',
683 props: {className: 'Qoo'},
684 instance: null,
685 rendered: ['Hello World!'],
686 },
687 }),
688 );
689 });
690
691 it('toTree() handles nested Fragments', async () => {
692 const Foo = () => (
693 <>
694 <>foo</>
695 </>
696 );
697 let renderer;
698 await act(() => {
699 renderer = ReactTestRenderer.create(<Foo />);
700 });
701 const tree = renderer.toTree();
702
703 cleanNodeOrArray(tree);
704
705 expect(prettyFormat(tree)).toEqual(
706 prettyFormat({
707 nodeType: 'component',
708 type: Foo,
709 instance: null,
710 props: {},
711 rendered: 'foo',
712 }),
713 );
714 });
715
716 it('toTree() handles null rendering components', async () => {
717 class Foo extends React.Component {
718 render() {
719 return null;
720 }
721 }
722
723 let renderer;
724 await act(() => {
725 renderer = ReactTestRenderer.create(<Foo />);
726 });
727 const tree = renderer.toTree();
728
729 expect(tree.instance).toBeInstanceOf(Foo);
730
731 cleanNodeOrArray(tree);
732
733 expect(tree).toEqual({
734 type: Foo,
735 nodeType: 'component',
736 props: {},
737 instance: null,
738 rendered: null,
739 });
740 });
741
742 it('toTree() handles simple components that return arrays', async () => {
743 const Foo = ({children}) => children;
744
745 let renderer;
746 await act(() => {
747 renderer = ReactTestRenderer.create(
748 <Foo>
749 <div>One</div>
750 <div>Two</div>
751 </Foo>,
752 );
753 });
754
755 const tree = renderer.toTree();
756
757 cleanNodeOrArray(tree);
758
759 expect(prettyFormat(tree)).toEqual(
760 prettyFormat({
761 type: Foo,
762 nodeType: 'component',
763 props: {},
764 instance: null,
765 rendered: [
766 {
767 instance: null,
768 nodeType: 'host',
769 props: {},
770 rendered: ['One'],
771 type: 'div',
772 },
773 {
774 instance: null,
775 nodeType: 'host',
776 props: {},
777 rendered: ['Two'],
778 type: 'div',
779 },
780 ],
781 }),
782 );
783 });
784
785 it('toTree() handles complicated tree of arrays', async () => {
786 class Foo extends React.Component {
787 render() {
788 return this.props.children;
789 }
790 }
791
792 let renderer;
793 await act(() => {
794 renderer = ReactTestRenderer.create(
795 <div>
796 <Foo>
797 <div>One</div>
798 <div>Two</div>
799 <Foo>
800 <div>Three</div>
801 </Foo>
802 </Foo>
803 <div>Four</div>
804 </div>,
805 );
806 });
807
808 const tree = renderer.toTree();
809
810 cleanNodeOrArray(tree);
811
812 expect(prettyFormat(tree)).toEqual(
813 prettyFormat({
814 type: 'div',
815 instance: null,
816 nodeType: 'host',
817 props: {},
818 rendered: [
819 {
820 type: Foo,
821 nodeType: 'component',
822 props: {},
823 instance: null,
824 rendered: [
825 {
826 type: 'div',
827 nodeType: 'host',
828 props: {},
829 instance: null,
830 rendered: ['One'],
831 },
832 {
833 type: 'div',
834 nodeType: 'host',
835 props: {},
836 instance: null,
837 rendered: ['Two'],
838 },
839 {
840 type: Foo,
841 nodeType: 'component',
842 props: {},
843 instance: null,
844 rendered: {
845 type: 'div',
846 nodeType: 'host',
847 props: {},
848 instance: null,
849 rendered: ['Three'],
850 },
851 },
852 ],
853 },
854 {
855 type: 'div',
856 nodeType: 'host',
857 props: {},
858 instance: null,
859 rendered: ['Four'],
860 },
861 ],
862 }),
863 );
864 });
865
866 it('toTree() handles complicated tree of fragments', async () => {
867 let renderer;
868 await act(() => {
869 renderer = ReactTestRenderer.create(
870 <>
871 <>
872 <div>One</div>
873 <div>Two</div>
874 <>
875 <div>Three</div>
876 </>
877 </>
878 <div>Four</div>
879 </>,
880 );
881 });
882
883 const tree = renderer.toTree();
884
885 cleanNodeOrArray(tree);
886
887 expect(prettyFormat(tree)).toEqual(
888 prettyFormat([
889 {
890 type: 'div',
891 nodeType: 'host',
892 props: {},
893 instance: null,
894 rendered: ['One'],
895 },
896 {
897 type: 'div',
898 nodeType: 'host',
899 props: {},
900 instance: null,
901 rendered: ['Two'],
902 },
903 {
904 type: 'div',
905 nodeType: 'host',
906 props: {},
907 instance: null,
908 rendered: ['Three'],
909 },
910 {
911 type: 'div',
912 nodeType: 'host',
913 props: {},
914 instance: null,
915 rendered: ['Four'],
916 },
917 ]),
918 );
919 });
920
921 it('root instance and createNodeMock ref return the same value', async () => {
922 const createNodeMock = ref => ({node: ref});
923 let refInst = null;
924 let renderer;
925 await act(() => {
926 renderer = ReactTestRenderer.create(
927 <div ref={ref => (refInst = ref)} />,
928 {createNodeMock},
929 );
930 });
931
932 const root = renderer.getInstance();
933 expect(root).toEqual(refInst);
934 });
935
936 it('toTree() renders complicated trees of composites and hosts', async () => {
937 // SFC returning host. no children props.
938 const Qoo = () => <span className="Qoo">Hello World!</span>;
939
940 // SFC returning host. passes through children.
941 const Foo = ({className, children}) => (
942 <div className={'Foo ' + className}>
943 <span className="Foo2">Literal</span>
944 {children}
945 </div>
946 );
947
948 // class composite returning composite. passes through children.
949 class Bar extends React.Component {
950 render() {
951 const {special, children} = this.props;
952 return <Foo className={special ? 'special' : 'normal'}>{children}</Foo>;
953 }
954 }
955
956 // class composite return composite. no children props.
957 class Bam extends React.Component {
958 render() {
959 return (
960 <Bar special={true}>
961 <Qoo />
962 </Bar>
963 );
964 }
965 }
966
967 let renderer;
968 await act(() => {
969 renderer = ReactTestRenderer.create(<Bam />);
970 });
971
972 const tree = renderer.toTree();
973
974 // we test for the presence of instances before nulling them out
975 expect(tree.instance).toBeInstanceOf(Bam);
976 expect(tree.rendered.instance).toBeInstanceOf(Bar);
977
978 cleanNodeOrArray(tree);
979
980 expect(prettyFormat(tree)).toEqual(
981 prettyFormat({
982 type: Bam,
983 nodeType: 'component',
984 props: {},
985 instance: null,
986 rendered: {
987 type: Bar,
988 nodeType: 'component',
989 props: {special: true},
990 instance: null,
991 rendered: {
992 type: Foo,
993 nodeType: 'component',
994 props: {className: 'special'},
995 instance: null,
996 rendered: {
997 type: 'div',
998 nodeType: 'host',
999 props: {className: 'Foo special'},
1000 instance: null,
1001 rendered: [
1002 {
1003 type: 'span',
1004 nodeType: 'host',
1005 props: {className: 'Foo2'},
1006 instance: null,
1007 rendered: ['Literal'],
1008 },
1009 {
1010 type: Qoo,
1011 nodeType: 'component',
1012 props: {},
1013 instance: null,
1014 rendered: {
1015 type: 'span',
1016 nodeType: 'host',
1017 props: {className: 'Qoo'},
1018 instance: null,
1019 rendered: ['Hello World!'],
1020 },
1021 },
1022 ],
1023 },
1024 },
1025 },
1026 }),
1027 );
1028 });
1029
1030 it('can update text nodes when rendered as root', async () => {
1031 let renderer;
1032 await act(() => {
1033 renderer = ReactTestRenderer.create(['Hello', 'world']);
1034 });
1035 expect(renderer.toJSON()).toEqual(['Hello', 'world']);
1036 await act(() => {
1037 renderer.update(42);
1038 });
1039 expect(renderer.toJSON()).toEqual('42');
1040 await act(() => {
1041 renderer.update([42, 'world']);
1042 });
1043 expect(renderer.toJSON()).toEqual(['42', 'world']);
1044 });
1045
1046 it('can render and update root fragments', async () => {
1047 const Component = props => props.children;
1048
1049 let renderer;
1050 await act(() => {
1051 renderer = ReactTestRenderer.create([
1052 <Component key="a">Hi</Component>,
1053 <Component key="b">Bye</Component>,
1054 ]);
1055 });
1056
1057 expect(renderer.toJSON()).toEqual(['Hi', 'Bye']);
1058 await act(() => {
1059 renderer.update(<div />);
1060 });
1061 expect(renderer.toJSON()).toEqual({
1062 type: 'div',
1063 children: null,
1064 props: {},
1065 });
1066 await act(() => {
1067 renderer.update([<div key="a">goodbye</div>, 'world']);
1068 });
1069 expect(renderer.toJSON()).toEqual([
1070 {
1071 type: 'div',
1072 children: ['goodbye'],
1073 props: {},
1074 },
1075 'world',
1076 ]);
1077 });
1078
1079 it('supports context providers and consumers', async () => {
1080 const {Consumer, Provider} = React.createContext('a');
1081
1082 function Child(props) {
1083 return props.value;
1084 }
1085
1086 function App() {
1087 return (
1088 <Provider value="b">
1089 <Consumer>{value => <Child value={value} />}</Consumer>
1090 </Provider>
1091 );
1092 }
1093
1094 let renderer;
1095 await act(() => {
1096 renderer = ReactTestRenderer.create(<App />);
1097 });
1098 const child = renderer.root.findByType(Child);
1099 expect(child.children).toEqual(['b']);
1100 expect(prettyFormat(renderer.toTree())).toEqual(
1101 prettyFormat({
1102 instance: null,
1103 nodeType: 'component',
1104 props: {},
1105 rendered: {
1106 instance: null,
1107 nodeType: 'component',
1108 props: {
1109 value: 'b',
1110 },
1111 rendered: 'b',
1112 type: Child,
1113 },
1114 type: App,
1115 }),
1116 );
1117 });
1118
1119 it('supports modes', async () => {
1120 function Child(props) {
1121 return props.value;
1122 }
1123
1124 function App(props) {
1125 return (
1126 <React.StrictMode>
1127 <Child value={props.value} />
1128 </React.StrictMode>
1129 );
1130 }
1131
1132 let renderer;
1133 await act(() => {
1134 renderer = ReactTestRenderer.create(<App value="a" />);
1135 });
1136 const child = renderer.root.findByType(Child);
1137 expect(child.children).toEqual(['a']);
1138 expect(prettyFormat(renderer.toTree())).toEqual(
1139 prettyFormat({
1140 instance: null,
1141 nodeType: 'component',
1142 props: {
1143 value: 'a',
1144 },
1145 rendered: {
1146 instance: null,
1147 nodeType: 'component',
1148 props: {
1149 value: 'a',
1150 },
1151 rendered: 'a',
1152 type: Child,
1153 },
1154 type: App,
1155 }),
1156 );
1157 });
1158
1159 it('supports forwardRef', async () => {
1160 const InnerRefed = React.forwardRef((props, ref) => (
1161 <div>
1162 <span ref={ref} />
1163 </div>
1164 ));
1165
1166 let refFn;
1167
1168 class App extends React.Component {
1169 render() {
1170 refFn = inst => {
1171 this.ref = inst;
1172 };
1173 return <InnerRefed ref={refFn} />;
1174 }
1175 }
1176
1177 let renderer;
1178 await act(() => {
1179 renderer = ReactTestRenderer.create(<App />);
1180 });
1181 const tree = renderer.toTree();
1182 cleanNodeOrArray(tree);
1183
1184 expect(prettyFormat(tree)).toEqual(
1185 prettyFormat({
1186 instance: null,
1187 nodeType: 'component',
1188 props: {},
1189 rendered: {
1190 instance: null,
1191 nodeType: 'host',
1192 props: {},
1193 rendered: [
1194 {
1195 instance: null,
1196 nodeType: 'host',
1197 props: {
1198 ref: refFn,
1199 },
1200 rendered: [],
1201 type: 'span',
1202 },
1203 ],
1204 type: 'div',
1205 },
1206 type: App,
1207 }),
1208 );
1209 });
1210
1211 it('can concurrently render context with a "primary" renderer', async () => {
1212 const Context = React.createContext(null);
1213 const Indirection = React.Fragment;
1214 const App = () => (
1215 <Context.Provider value={null}>
1216 <Indirection>
1217 <Context.Consumer>{() => null}</Context.Consumer>
1218 </Indirection>
1219 </Context.Provider>
1220 );
1221 ReactNoop.render(<App />);
1222 await waitForAll([]);
1223 await act(() => {
1224 ReactTestRenderer.create(<App />);
1225 });
1226 });
1227
1228 it('calling findByType() with an invalid component will fall back to "Unknown" for component name', async () => {
1229 const App = () => null;
1230 let renderer;
1231 await act(() => {
1232 renderer = ReactTestRenderer.create(<App />);
1233 });
1234 const NonComponent = {};
1235
1236 expect(() => {
1237 renderer.root.findByType(NonComponent);
1238 }).toThrow(`No instances found with node type: "Unknown"`);
1239 });
1240 });