1
+let PropTypes;
2
let React;
3
let ReactTestRenderer;
4
let ReactFeatureFlags;
11
ReactFeatureFlags = require('shared/ReactFeatureFlags');
12
ReactFeatureFlags.debugRenderPhaseSideEffectsForStrictMode = false;
13
ReactFeatureFlags.replayFailedUnitOfWorkWithInvokeGuardedCallback = false;
14
+ PropTypes = require('prop-types');
15
React = require('react');
16
Suspense = React.Suspense;
17
lazy = React.lazy;
463
expect(root).toMatchRenderedOutput('A3');
464
});
465
466
+ it('resolves defaultProps on the outer wrapper but warns', async () => {
467
+ function T(props) {
468
+ ReactTestRenderer.unstable_yield(props.inner + ' ' + props.outer);
469
+ return props.inner + ' ' + props.outer;
470
+ }
471
+ T.defaultProps = {inner: 'Hi'};
472
+ const LazyText = lazy(() => fakeImport(T));
473
+ expect(() => {
474
+ LazyText.defaultProps = {outer: 'Bye'};
475
+ }).toWarnDev(
476
+ 'React.lazy(...): It is not supported to assign `defaultProps` to ' +
477
+ 'a lazy component import. Either specify them where the component ' +
478
+ 'is defined, or create a wrapping component around it.',
479
+ {withoutStack: true},
480
+ );
481
+
482
+ const root = ReactTestRenderer.create(
483
+ <Suspense fallback={<Text text="Loading..." />}>
484
+ <LazyText />
485
+ </Suspense>,
486
+ {
487
+ unstable_isConcurrent: true,
488
+ },
489
+ );
490
+
491
+ expect(root).toFlushAndYield(['Loading...']);
492
+ expect(root).toMatchRenderedOutput(null);
493
+
494
+ await Promise.resolve();
495
+ expect(root).toFlushAndYield(['Hi Bye']);
496
+ expect(root).toMatchRenderedOutput('Hi Bye');
497
+
498
+ root.update(
499
+ <Suspense fallback={<Text text="Loading..." />}>
500
+ <LazyText outer="World" />
501
+ </Suspense>,
502
+ );
503
+ expect(root).toFlushAndYield(['Hi World']);
504
+ expect(root).toMatchRenderedOutput('Hi World');
505
+
506
+ root.update(
507
+ <Suspense fallback={<Text text="Loading..." />}>
508
+ <LazyText inner="Friends" />
509
+ </Suspense>,
510
+ );
511
+ expect(root).toFlushAndYield(['Friends Bye']);
512
+ expect(root).toMatchRenderedOutput('Friends Bye');
513
+ });
514
+
515
+ it('throws with a useful error when wrapping invalid type with lazy()', async () => {
516
+ const BadLazy = lazy(() => fakeImport(42));
517
+
518
+ const root = ReactTestRenderer.create(
519
+ <Suspense fallback={<Text text="Loading..." />}>
520
+ <BadLazy />
521
+ </Suspense>,
522
+ {
523
+ unstable_isConcurrent: true,
524
+ },
525
+ );
526
+
527
+ expect(root).toFlushAndYield(['Loading...']);
528
+ expect(root).toMatchRenderedOutput(null);
529
+
530
+ await Promise.resolve();
531
+ root.update(
532
+ <Suspense fallback={<Text text="Loading..." />}>
533
+ <BadLazy />
534
+ </Suspense>,
535
+ );
536
+ expect(() => {
537
+ root.unstable_flushAll();
538
+ }).toThrow(
539
+ 'Element type is invalid. Received a promise that resolves to: 42. ' +
540
+ 'Lazy element type must resolve to a class or function.',
541
+ );
542
+ });
543
+
544
+ it('throws with a useful error when wrapping lazy() multiple times', async () => {
545
+ const Lazy1 = lazy(() => fakeImport(Text));
546
+ const Lazy2 = lazy(() => fakeImport(Lazy1));
547
+
548
+ const root = ReactTestRenderer.create(
549
+ <Suspense fallback={<Text text="Loading..." />}>
550
+ <Lazy2 text="Hello" />
551
+ </Suspense>,
552
+ {
553
+ unstable_isConcurrent: true,
554
+ },
555
+ );
556
+
557
+ expect(root).toFlushAndYield(['Loading...']);
558
+ expect(root).toMatchRenderedOutput(null);
559
+
560
+ await Promise.resolve();
561
+ root.update(
562
+ <Suspense fallback={<Text text="Loading..." />}>
563
+ <Lazy2 text="Hello" />
564
+ </Suspense>,
565
+ );
566
+ expect(() => {
567
+ root.unstable_flushAll();
568
+ }).toThrow(
569
+ 'Element type is invalid. Received a promise that resolves to: [object Object]. ' +
570
+ 'Lazy element type must resolve to a class or function.' +
571
+ (__DEV__
572
+ ? ' Did you wrap a component in React.lazy() more than once?'
573
+ : ''),
574
+ );
575
+ });
576
+
577
+ it('warns about defining propTypes on the outer wrapper', () => {
578
+ const LazyText = lazy(() => fakeImport(Text));
579
+ expect(() => {
580
+ LazyText.propTypes = {hello: () => {}};
581
+ }).toWarnDev(
582
+ 'React.lazy(...): It is not supported to assign `propTypes` to ' +
583
+ 'a lazy component import. Either specify them where the component ' +
584
+ 'is defined, or create a wrapping component around it.',
585
+ {withoutStack: true},
586
+ );
587
+ });
588
+
589
+ async function verifyInnerPropTypesAreChecked(Add) {
590
+ const LazyAdd = lazy(() => fakeImport(Add));
591
+ expect(() => {
592
+ LazyAdd.propTypes = {};
593
+ }).toWarnDev(
594
+ 'React.lazy(...): It is not supported to assign `propTypes` to ' +
595
+ 'a lazy component import. Either specify them where the component ' +
596
+ 'is defined, or create a wrapping component around it.',
597
+ {withoutStack: true},
598
+ );
599
+
600
+ const root = ReactTestRenderer.create(
601
+ <Suspense fallback={<Text text="Loading..." />}>
602
+ <LazyAdd inner="2" outer="2" />
603
+ </Suspense>,
604
+ {
605
+ unstable_isConcurrent: true,
606
+ },
607
+ );
608
+
609
+ expect(root).toFlushAndYield(['Loading...']);
610
+ expect(root).toMatchRenderedOutput(null);
611
+
612
+ // Mount
613
+ await Promise.resolve();
614
+ expect(() => {
615
+ root.unstable_flushAll();
616
+ }).toWarnDev(
617
+ 'Invalid prop `inner` of type `string` supplied to `Add`, expected `number`.',
618
+ );
619
+ expect(root).toMatchRenderedOutput('22');
620
+
621
+ // Update
622
+ expect(() => {
623
+ root.update(
624
+ <Suspense fallback={<Text text="Loading..." />}>
625
+ <LazyAdd inner={false} outer={false} />
626
+ </Suspense>,
627
+ );
628
+ root.unstable_flushAll();
629
+ }).toWarnDev(
630
+ 'Invalid prop `inner` of type `boolean` supplied to `Add`, expected `number`.',
631
+ );
632
+ expect(root).toMatchRenderedOutput('0');
633
+ }
634
+
635
+ // Note: all "with defaultProps" tests below also verify defaultProps works as expected.
636
+ // If we ever delete or move propTypes-related tests, make sure not to delete these.
637
+ it('respects propTypes on function component with defaultProps', async () => {
638
+ function Add(props) {
639
+ expect(props.innerWithDefault).toBe(42);
640
+ return props.inner + props.outer;
641
+ }
642
+ Add.propTypes = {
643
+ inner: PropTypes.number.isRequired,
644
+ innerWithDefault: PropTypes.number.isRequired,
645
+ };
646
+ Add.defaultProps = {
647
+ innerWithDefault: 42,
648
+ };
649
+ await verifyInnerPropTypesAreChecked(Add);
650
+ });
651
+
652
+ it('respects propTypes on function component without defaultProps', async () => {
653
+ function Add(props) {
654
+ return props.inner + props.outer;
655
+ }
656
+ Add.propTypes = {
657
+ inner: PropTypes.number.isRequired,
658
+ };
659
+ await verifyInnerPropTypesAreChecked(Add);
660
+ });
661
+
662
+ it('respects propTypes on class component with defaultProps', async () => {
663
+ class Add extends React.Component {
664
+ render() {
665
+ expect(this.props.innerWithDefault).toBe(42);
666
+ return this.props.inner + this.props.outer;
667
+ }
668
+ }
669
+ Add.propTypes = {
670
+ inner: PropTypes.number.isRequired,
671
+ innerWithDefault: PropTypes.number.isRequired,
672
+ };
673
+ Add.defaultProps = {
674
+ innerWithDefault: 42,
675
+ };
676
+ await verifyInnerPropTypesAreChecked(Add);
677
+ });
678
+
679
+ it('respects propTypes on class component without defaultProps', async () => {
680
+ class Add extends React.Component {
681
+ render() {
682
+ return this.props.inner + this.props.outer;
683
+ }
684
+ }
685
+ Add.propTypes = {
686
+ inner: PropTypes.number.isRequired,
687
+ };
688
+ await verifyInnerPropTypesAreChecked(Add);
689
+ });
690
+
691
+ it('respects propTypes on forwardRef component with defaultProps', async () => {
692
+ const Add = React.forwardRef((props, ref) => {
693
+ expect(props.innerWithDefault).toBe(42);
694
+ return props.inner + props.outer;
695
+ });
696
+ Add.displayName = 'Add';
697
+ Add.propTypes = {
698
+ inner: PropTypes.number.isRequired,
699
+ innerWithDefault: PropTypes.number.isRequired,
700
+ };
701
+ Add.defaultProps = {
702
+ innerWithDefault: 42,
703
+ };
704
+ await verifyInnerPropTypesAreChecked(Add);
705
+ });
706
+
707
+ it('respects propTypes on forwardRef component without defaultProps', async () => {
708
+ const Add = React.forwardRef((props, ref) => {
709
+ return props.inner + props.outer;
710
+ });
711
+ Add.displayName = 'Add';
712
+ Add.propTypes = {
713
+ inner: PropTypes.number.isRequired,
714
+ };
715
+ await verifyInnerPropTypesAreChecked(Add);
716
+ });
717
+
718
+ it('respects propTypes on outer memo component with defaultProps', async () => {
719
+ let Add = props => {
720
+ expect(props.innerWithDefault).toBe(42);
721
+ return props.inner + props.outer;
722
+ };
723
+ Add = React.memo(Add);
724
+ Add.propTypes = {
725
+ inner: PropTypes.number.isRequired,
726
+ innerWithDefault: PropTypes.number.isRequired,
727
+ };
728
+ Add.defaultProps = {
729
+ innerWithDefault: 42,
730
+ };
731
+ await verifyInnerPropTypesAreChecked(Add);
732
+ });
733
+
734
+ it('respects propTypes on outer memo component without defaultProps', async () => {
735
+ let Add = props => {
736
+ return props.inner + props.outer;
737
+ };
738
+ Add = React.memo(Add);
739
+ Add.propTypes = {
740
+ inner: PropTypes.number.isRequired,
741
+ };
742
+ await verifyInnerPropTypesAreChecked(Add);
743
+ });
744
+
745
+ it('respects propTypes on inner memo component with defaultProps', async () => {
746
+ const Add = props => {
747
+ expect(props.innerWithDefault).toBe(42);
748
+ return props.inner + props.outer;
749
+ };
750
+ Add.displayName = 'Add';
751
+ Add.propTypes = {
752
+ inner: PropTypes.number.isRequired,
753
+ innerWithDefault: PropTypes.number.isRequired,
754
+ };
755
+ Add.defaultProps = {
756
+ innerWithDefault: 42,
757
+ };
758
+ await verifyInnerPropTypesAreChecked(React.memo(Add));
759
+ });
760
+
761
+ it('respects propTypes on inner memo component without defaultProps', async () => {
762
+ const Add = props => {
763
+ return props.inner + props.outer;
764
+ };
765
+ Add.displayName = 'Add';
766
+ Add.propTypes = {
767
+ inner: PropTypes.number.isRequired,
768
+ };
769
+ await verifyInnerPropTypesAreChecked(React.memo(Add));
770
+ });
771
+
772
+ it('uses outer resolved props for validating propTypes on memo', async () => {
773
+ let T = props => {
774
+ return <Text text={props.text} />;
775
+ };
776
+ T.defaultProps = {
777
+ text: 'Inner default text',
778
+ };
779
+ T = React.memo(T);
780
+ T.propTypes = {
781
+ // Should not be satisfied by the *inner* defaultProps.
782
+ text: PropTypes.string.isRequired,
783
+ };
784
+ const LazyText = lazy(() => fakeImport(T));
785
+ const root = ReactTestRenderer.create(
786
+ <Suspense fallback={<Text text="Loading..." />}>
787
+ <LazyText />
788
+ </Suspense>,
789
+ {
790
+ unstable_isConcurrent: true,
791
+ },
792
+ );
793
+
794
+ expect(root).toFlushAndYield(['Loading...']);
795
+ expect(root).toMatchRenderedOutput(null);
796
+
797
+ // Mount
798
+ await Promise.resolve();
799
+ expect(() => {
800
+ root.unstable_flushAll();
801
+ }).toWarnDev(
802
+ 'The prop `text` is marked as required in `T`, but its value is `undefined`',
803
+ );
804
+ expect(root).toMatchRenderedOutput('Inner default text');
805
+
806
+ // Update
807
+ expect(() => {
808
+ root.update(
809
+ <Suspense fallback={<Text text="Loading..." />}>
810
+ <LazyText text={null} />
811
+ </Suspense>,
812
+ );
813
+ root.unstable_flushAll();
814
+ }).toWarnDev(
815
+ 'The prop `text` is marked as required in `T`, but its value is `null`',
816
+ );
817
+ expect(root).toMatchRenderedOutput(null);
818
+ });
819
+
820
it('includes lazy-loaded component in warning stack', async () => {
821
const LazyFoo = lazy(() => {
822
ReactTestRenderer.unstable_yield('Started loading');