461
onEnter.mock.calls.length + enterCallsAfterFallback,
462
).toBeGreaterThanOrEqual(1);
463
});
464
+
465
+ // @gate enableViewTransition
466
+ it('does not fire onExit/onEnter on nested ViewTransition when the subtree is removed as one unit', async () => {
467
+ const onParentExit = jest.fn();
468
+ const onParentEnter = jest.fn();
469
+ const onNestedExit = jest.fn();
470
+ const onNestedEnter = jest.fn();
471
+
472
+ function App({show}) {
473
+ if (!show) {
474
+ return null;
475
+ }
476
+ return (
477
+ <ViewTransition
478
+ exit="page-exit"
479
+ enter="page-enter"
480
+ onExit={onParentExit}
481
+ onEnter={onParentEnter}>
482
+ <div>
483
+ <ViewTransition
484
+ exit="nested-exit"
485
+ enter="nested-enter"
486
+ onExit={onNestedExit}
487
+ onEnter={onNestedEnter}>
488
+ <div>Item</div>
489
+ </ViewTransition>
490
+ </div>
491
+ </ViewTransition>
492
+ );
493
+ }
494
+
495
+ const root = ReactDOMClient.createRoot(container);
496
+
497
+ await act(() => {
498
+ startTransition(() => {
499
+ root.render(<App show={false} />);
500
+ });
501
+ });
502
+
503
+ onParentEnter.mockClear();
504
+ onNestedEnter.mockClear();
505
+
506
+ await act(() => {
507
+ startTransition(() => {
508
+ root.render(<App show={true} />);
509
+ });
510
+ });
511
+
512
+ expect(onParentEnter).toHaveBeenCalledTimes(1);
513
+ expect(onNestedEnter).not.toHaveBeenCalled();
514
+
515
+ onParentExit.mockClear();
516
+ onNestedExit.mockClear();
517
+
518
+ await act(() => {
519
+ startTransition(() => {
520
+ root.render(<App show={false} />);
521
+ });
522
+ });
523
+
524
+ expect(onParentExit).toHaveBeenCalledTimes(1);
525
+ expect(onNestedExit).not.toHaveBeenCalled();
526
+ });
527
+
528
+ // @gate enableViewTransition && enableViewTransitionParentEnterExit
529
+ it('fires onParentExit when ancestor ViewTransition exits', async () => {
530
+ const onParentExit = jest.fn();
531
+ const onNestedExit = jest.fn();
532
+ const onParentExitNested = jest.fn();
533
+
534
+ function App({show}) {
535
+ if (!show) {
536
+ return null;
537
+ }
538
+ return (
539
+ <ViewTransition exit="page-exit" onExit={onParentExit}>
540
+ <div>
541
+ <ViewTransition
542
+ exit="nested-exit"
543
+ onExit={onNestedExit}
544
+ parentExit="nested-parent-exit"
545
+ onParentExit={onParentExitNested}>
546
+ <div>Item</div>
547
+ </ViewTransition>
548
+ </div>
549
+ </ViewTransition>
550
+ );
551
+ }
552
+
553
+ const root = ReactDOMClient.createRoot(container);
554
+
555
+ await act(() => {
556
+ startTransition(() => {
557
+ root.render(<App show={true} />);
558
+ });
559
+ });
560
+
561
+ onParentExit.mockClear();
562
+ onNestedExit.mockClear();
563
+ onParentExitNested.mockClear();
564
+
565
+ await act(() => {
566
+ startTransition(() => {
567
+ root.render(<App show={false} />);
568
+ });
569
+ });
570
+
571
+ expect(onParentExit).toHaveBeenCalledTimes(1);
572
+ expect(onNestedExit).not.toHaveBeenCalled();
573
+ expect(onParentExitNested).toHaveBeenCalledTimes(1);
574
+ });
575
+
576
+ // @gate enableViewTransition && enableViewTransitionParentEnterExit
577
+ it('fires onParentEnter when ancestor ViewTransition enters', async () => {
578
+ const onParentEnter = jest.fn();
579
+ const onNestedEnter = jest.fn();
580
+ const onParentEnterNested = jest.fn();
581
+
582
+ function App({show}) {
583
+ if (!show) {
584
+ return null;
585
+ }
586
+ return (
587
+ <ViewTransition enter="page-enter" onEnter={onParentEnter}>
588
+ <div>
589
+ <ViewTransition
590
+ enter="nested-enter"
591
+ onEnter={onNestedEnter}
592
+ parentEnter="nested-parent-enter"
593
+ onParentEnter={onParentEnterNested}>
594
+ <div>Item</div>
595
+ </ViewTransition>
596
+ </div>
597
+ </ViewTransition>
598
+ );
599
+ }
600
+
601
+ const root = ReactDOMClient.createRoot(container);
602
+
603
+ await act(() => {
604
+ startTransition(() => {
605
+ root.render(<App show={false} />);
606
+ });
607
+ });
608
+
609
+ onParentEnter.mockClear();
610
+ onNestedEnter.mockClear();
611
+ onParentEnterNested.mockClear();
612
+
613
+ await act(() => {
614
+ startTransition(() => {
615
+ root.render(<App show={true} />);
616
+ });
617
+ });
618
+
619
+ expect(onParentEnter).toHaveBeenCalledTimes(1);
620
+ expect(onNestedEnter).not.toHaveBeenCalled();
621
+ expect(onParentEnterNested).toHaveBeenCalledTimes(1);
622
+ });
623
+
624
+ // @gate enableViewTransition && enableViewTransitionParentEnterExit
625
+ it('breaks parentExit chain when intermediate ViewTransition lacks parentExit', async () => {
626
+ const onParentExit1 = jest.fn();
627
+ const onParentExit2 = jest.fn();
628
+
629
+ function App({show}) {
630
+ if (!show) {
631
+ return null;
632
+ }
633
+ return (
634
+ <ViewTransition exit="page-exit">
635
+ <div>
636
+ <ViewTransition parentExit="relay-exit">
637
+ <div>
638
+ <ViewTransition>
639
+ <div>
640
+ <ViewTransition
641
+ parentExit="nested-exit"
642
+ onParentExit={onParentExit1}>
643
+ <div>Deep</div>
644
+ </ViewTransition>
645
+ </div>
646
+ </ViewTransition>
647
+ </div>
648
+ </ViewTransition>
649
+ <ViewTransition
650
+ parentExit="nested-exit"
651
+ onParentExit={onParentExit2}>
652
+ <div>Shallow</div>
653
+ </ViewTransition>
654
+ </div>
655
+ </ViewTransition>
656
+ );
657
+ }
658
+
659
+ const root = ReactDOMClient.createRoot(container);
660
+
661
+ await act(() => {
662
+ startTransition(() => {
663
+ root.render(<App show={true} />);
664
+ });
665
+ });
666
+
667
+ onParentExit1.mockClear();
668
+ onParentExit2.mockClear();
669
+
670
+ await act(() => {
671
+ startTransition(() => {
672
+ root.render(<App show={false} />);
673
+ });
674
+ });
675
+
676
+ expect(onParentExit1).not.toHaveBeenCalled();
677
+ expect(onParentExit2).toHaveBeenCalledTimes(1);
678
+ });
679
+
680
+ // @gate enableViewTransition && enableViewTransitionParentEnterExit
681
+ it('stops the parentExit relay when an intermediate class is "none"', async () => {
682
+ const onParentExitDeep = jest.fn();
683
+ const onParentExitSibling = jest.fn();
684
+
685
+ function App({show}) {
686
+ if (!show) {
687
+ return null;
688
+ }
689
+ return (
690
+ <ViewTransition exit="page-exit">
691
+ <div>
692
+ <ViewTransition parentExit="none">
693
+ <div>
694
+ <ViewTransition
695
+ parentExit="nested-exit"
696
+ onParentExit={onParentExitDeep}>
697
+ <div>Deep</div>
698
+ </ViewTransition>
699
+ </div>
700
+ </ViewTransition>
701
+ <ViewTransition
702
+ parentExit="nested-exit"
703
+ onParentExit={onParentExitSibling}>
704
+ <div>Shallow</div>
705
+ </ViewTransition>
706
+ </div>
707
+ </ViewTransition>
708
+ );
709
+ }
710
+
711
+ const root = ReactDOMClient.createRoot(container);
712
+
713
+ await act(() => {
714
+ startTransition(() => {
715
+ root.render(<App show={true} />);
716
+ });
717
+ });
718
+
719
+ onParentExitDeep.mockClear();
720
+ onParentExitSibling.mockClear();
721
+
722
+ await act(() => {
723
+ startTransition(() => {
724
+ root.render(<App show={false} />);
725
+ });
726
+ });
727
+
728
+ // The "none" class stops the relay so nested activations below it never fire.
729
+ expect(onParentExitDeep).not.toHaveBeenCalled();
730
+ // A sibling that is not behind a "none" boundary still relays.
731
+ expect(onParentExitSibling).toHaveBeenCalledTimes(1);
732
+ });
733
+
734
+ // @gate enableViewTransition && enableViewTransitionParentEnterExit
735
+ it('stops the parentEnter relay when an intermediate class is "none"', async () => {
736
+ const onParentEnterDeep = jest.fn();
737
+ const onParentEnterSibling = jest.fn();
738
+
739
+ function App({show}) {
740
+ if (!show) {
741
+ return null;
742
+ }
743
+ return (
744
+ <ViewTransition enter="page-enter">
745
+ <div>
746
+ <ViewTransition parentEnter="none">
747
+ <div>
748
+ <ViewTransition
749
+ parentEnter="nested-enter"
750
+ onParentEnter={onParentEnterDeep}>
751
+ <div>Deep</div>
752
+ </ViewTransition>
753
+ </div>
754
+ </ViewTransition>
755
+ <ViewTransition
756
+ parentEnter="nested-enter"
757
+ onParentEnter={onParentEnterSibling}>
758
+ <div>Shallow</div>
759
+ </ViewTransition>
760
+ </div>
761
+ </ViewTransition>
762
+ );
763
+ }
764
+
765
+ const root = ReactDOMClient.createRoot(container);
766
+
767
+ await act(() => {
768
+ startTransition(() => {
769
+ root.render(<App show={false} />);
770
+ });
771
+ });
772
+
773
+ await act(() => {
774
+ startTransition(() => {
775
+ root.render(<App show={true} />);
776
+ });
777
+ });
778
+
779
+ // The "none" class stops the relay so nested activations below it never fire.
780
+ expect(onParentEnterDeep).not.toHaveBeenCalled();
781
+ // A sibling that is not behind a "none" boundary still relays.
782
+ expect(onParentEnterSibling).toHaveBeenCalledTimes(1);
783
+ });
784
+
785
+ it('does not fire onParentEnter when ancestor exits', async () => {
786
+ const onParentEnter = jest.fn();
787
+
788
+ function App({show}) {
789
+ if (!show) {
790
+ return null;
791
+ }
792
+ return (
793
+ <ViewTransition exit="page-exit">
794
+ <div>
795
+ <ViewTransition parentExit="relay-exit">
796
+ <ViewTransition
797
+ parentEnter="nested-enter"
798
+ onParentEnter={onParentEnter}>
799
+ <div>Item</div>
800
+ </ViewTransition>
801
+ </ViewTransition>
802
+ </div>
803
+ </ViewTransition>
804
+ );
805
+ }
806
+
807
+ const root = ReactDOMClient.createRoot(container);
808
+
809
+ await act(() => {
810
+ startTransition(() => {
811
+ root.render(<App show={true} />);
812
+ });
813
+ });
814
+
815
+ onParentEnter.mockClear();
816
+
817
+ await act(() => {
818
+ startTransition(() => {
819
+ root.render(<App show={false} />);
820
+ });
821
+ });
822
+
823
+ expect(onParentEnter).not.toHaveBeenCalled();
824
+ });
825
+
826
+ // @gate enableViewTransition
827
+ it('does not fire onParentExit when ancestor shares instead of exiting', async () => {
828
+ const onShare = jest.fn();
829
+ const onParentExit = jest.fn();
830
+
831
+ function App({page}) {
832
+ if (page === 'a') {
833
+ return (
834
+ <ViewTransition key="a" name="hero" onShare={onShare}>
835
+ <ViewTransition
836
+ parentExit="child-parent-exit"
837
+ onParentExit={onParentExit}>
838
+ <div>Page A</div>
839
+ </ViewTransition>
840
+ </ViewTransition>
841
+ );
842
+ }
843
+ return (
844
+ <ViewTransition key="b" name="hero">
845
+ <ViewTransition
846
+ parentExit="child-parent-exit"
847
+ onParentExit={onParentExit}>
848
+ <div>Page B</div>
849
+ </ViewTransition>
850
+ </ViewTransition>
851
+ );
852
+ }
853
+
854
+ const root = ReactDOMClient.createRoot(container);
855
+
856
+ await act(() => {
857
+ startTransition(() => {
858
+ root.render(<App page="a" />);
859
+ });
860
+ });
861
+
862
+ onShare.mockClear();
863
+ onParentExit.mockClear();
864
+
865
+ await act(() => {
866
+ startTransition(() => {
867
+ root.render(<App page="b" />);
868
+ });
869
+ });
870
+
871
+ expect(onShare).toHaveBeenCalledTimes(1);
872
+ expect(onParentExit).not.toHaveBeenCalled();
873
+ });
874
+
875
+ // @gate enableViewTransition
876
+ it('does not fire onParentEnter when ancestor shares instead of entering', async () => {
877
+ const onShare = jest.fn();
878
+ const onParentEnter = jest.fn();
879
+
880
+ function App({page}) {
881
+ if (page === 'a') {
882
+ return (
883
+ <ViewTransition key="a" name="hero" onShare={onShare}>
884
+ <ViewTransition
885
+ parentEnter="child-parent-enter"
886
+ onParentEnter={onParentEnter}>
887
+ <div>Page A</div>
888
+ </ViewTransition>
889
+ </ViewTransition>
890
+ );
891
+ }
892
+ return (
893
+ <ViewTransition key="b" name="hero">
894
+ <ViewTransition
895
+ parentEnter="child-parent-enter"
896
+ onParentEnter={onParentEnter}>
897
+ <div>Page B</div>
898
+ </ViewTransition>
899
+ </ViewTransition>
900
+ );
901
+ }
902
+
903
+ const root = ReactDOMClient.createRoot(container);
904
+
905
+ await act(() => {
906
+ startTransition(() => {
907
+ root.render(<App page="a" />);
908
+ });
909
+ });
910
+
911
+ onShare.mockClear();
912
+ onParentEnter.mockClear();
913
+
914
+ await act(() => {
915
+ startTransition(() => {
916
+ root.render(<App page="b" />);
917
+ });
918
+ });
919
+
920
+ expect(onShare).toHaveBeenCalledTimes(1);
921
+ expect(onParentEnter).not.toHaveBeenCalled();
922
+ });
923
+
924
+ it('does not fire onParentExit when ancestor exit is none', async () => {
925
+ const onParentExit = jest.fn();
926
+
927
+ function App({show}) {
928
+ if (!show) {
929
+ return null;
930
+ }
931
+ return (
932
+ <ViewTransition exit="none">
933
+ <ViewTransition
934
+ parentExit="nested-parent-exit"
935
+ onParentExit={onParentExit}>
936
+ <div>Item</div>
937
+ </ViewTransition>
938
+ </ViewTransition>
939
+ );
940
+ }
941
+
942
+ const root = ReactDOMClient.createRoot(container);
943
+
944
+ await act(() => {
945
+ startTransition(() => {
946
+ root.render(<App show={true} />);
947
+ });
948
+ });
949
+
950
+ onParentExit.mockClear();
951
+
952
+ await act(() => {
953
+ startTransition(() => {
954
+ root.render(<App show={false} />);
955
+ });
956
+ });
957
+
958
+ expect(onParentExit).not.toHaveBeenCalled();
959
+ });
960
+
961
+ it('does not fire onParentEnter when ancestor enter is none', async () => {
962
+ const onParentEnter = jest.fn();
963
+
964
+ function App({show}) {
965
+ if (!show) {
966
+ return null;
967
+ }
968
+ return (
969
+ <ViewTransition enter="none">
970
+ <ViewTransition
971
+ parentEnter="nested-parent-enter"
972
+ onParentEnter={onParentEnter}>
973
+ <div>Item</div>
974
+ </ViewTransition>
975
+ </ViewTransition>
976
+ );
977
+ }
978
+
979
+ const root = ReactDOMClient.createRoot(container);
980
+
981
+ await act(() => {
982
+ startTransition(() => {
983
+ root.render(<App show={false} />);
984
+ });
985
+ });
986
+
987
+ onParentEnter.mockClear();
988
+
989
+ await act(() => {
990
+ startTransition(() => {
991
+ root.render(<App show={true} />);
992
+ });
993
+ });
994
+
995
+ expect(onParentEnter).not.toHaveBeenCalled();
996
+ });
997
+
998
+ // @gate enableViewTransition && enableViewTransitionParentEnterExit
999
+ it('relays parentExit chain through unstyled parentExit', async () => {
1000
+ const onParentExit = jest.fn();
1001
+
1002
+ function App({show}) {
1003
+ if (!show) {
1004
+ return null;
1005
+ }
1006
+ return (
1007
+ <ViewTransition exit="page-exit">
1008
+ <div>
1009
+ <ViewTransition parentExit="auto">
1010
+ <ViewTransition
1011
+ parentExit="nested-exit"
1012
+ onParentExit={onParentExit}>
1013
+ <div>Item</div>
1014
+ </ViewTransition>
1015
+ </ViewTransition>
1016
+ </div>
1017
+ </ViewTransition>
1018
+ );
1019
+ }
1020
+
1021
+ const root = ReactDOMClient.createRoot(container);
1022
+
1023
+ await act(() => {
1024
+ startTransition(() => {
1025
+ root.render(<App show={true} />);
1026
+ });
1027
+ });
1028
+
1029
+ onParentExit.mockClear();
1030
+
1031
+ await act(() => {
1032
+ startTransition(() => {
1033
+ root.render(<App show={false} />);
1034
+ });
1035
+ });
1036
+
1037
+ expect(onParentExit).toHaveBeenCalledTimes(1);
1038
+ });
1039
+
1040
+ // @gate enableViewTransition && enableViewTransitionParentEnterExit
1041
+ it('fires onParentExit when ancestor ViewTransition exits with handler only', async () => {
1042
+ const onParentExit = jest.fn();
1043
+ const onRelayParentExit = jest.fn();
1044
+ const onParentExitDeep = jest.fn();
1045
+
1046
+ function App({show}) {
1047
+ if (!show) {
1048
+ return null;
1049
+ }
1050
+ return (
1051
+ <ViewTransition onExit={onParentExit}>
1052
+ <div>
1053
+ <ViewTransition onParentExit={onRelayParentExit}>
1054
+ <ViewTransition onParentExit={onParentExitDeep}>
1055
+ <div>Item</div>
1056
+ </ViewTransition>
1057
+ </ViewTransition>
1058
+ </div>
1059
+ </ViewTransition>
1060
+ );
1061
+ }
1062
+
1063
+ const root = ReactDOMClient.createRoot(container);
1064
+
1065
+ await act(() => {
1066
+ startTransition(() => {
1067
+ root.render(<App show={true} />);
1068
+ });
1069
+ });
1070
+
1071
+ onParentExit.mockClear();
1072
+ onRelayParentExit.mockClear();
1073
+ onParentExitDeep.mockClear();
1074
+
1075
+ await act(() => {
1076
+ startTransition(() => {
1077
+ root.render(<App show={false} />);
1078
+ });
1079
+ });
1080
+
1081
+ expect(onParentExit).toHaveBeenCalledTimes(1);
1082
+ expect(onRelayParentExit).toHaveBeenCalledTimes(1);
1083
+ expect(onParentExitDeep).toHaveBeenCalledTimes(1);
1084
+ });
1085
+
1086
+ // @gate enableViewTransition && enableViewTransitionParentEnterExit
1087
+ it('fires onParentEnter when ancestor ViewTransition enters with handler only', async () => {
1088
+ const onParentEnter = jest.fn();
1089
+ const onRelayParentEnter = jest.fn();
1090
+ const onParentEnterDeep = jest.fn();
1091
+
1092
+ function App({show}) {
1093
+ if (!show) {
1094
+ return null;
1095
+ }
1096
+ return (
1097
+ <ViewTransition onEnter={onParentEnter}>
1098
+ <div>
1099
+ <ViewTransition onParentEnter={onRelayParentEnter}>
1100
+ <ViewTransition onParentEnter={onParentEnterDeep}>
1101
+ <div>Item</div>
1102
+ </ViewTransition>
1103
+ </ViewTransition>
1104
+ </div>
1105
+ </ViewTransition>
1106
+ );
1107
+ }
1108
+
1109
+ const root = ReactDOMClient.createRoot(container);
1110
+
1111
+ await act(() => {
1112
+ startTransition(() => {
1113
+ root.render(<App show={false} />);
1114
+ });
1115
+ });
1116
+
1117
+ onParentEnter.mockClear();
1118
+ onRelayParentEnter.mockClear();
1119
+ onParentEnterDeep.mockClear();
1120
+
1121
+ await act(() => {
1122
+ startTransition(() => {
1123
+ root.render(<App show={true} />);
1124
+ });
1125
+ });
1126
+
1127
+ expect(onParentEnter).toHaveBeenCalledTimes(1);
1128
+ expect(onRelayParentEnter).toHaveBeenCalledTimes(1);
1129
+ expect(onParentEnterDeep).toHaveBeenCalledTimes(1);
1130
+ });
1131
+
1132
+ it('does not fire onParentEnter when ancestor enter is none with handler only', async () => {
1133
+ const onParentEnter = jest.fn();
1134
+
1135
+ function App({show}) {
1136
+ if (!show) {
1137
+ return null;
1138
+ }
1139
+ return (
1140
+ <ViewTransition enter="none">
1141
+ <ViewTransition onParentEnter={onParentEnter}>
1142
+ <div>Item</div>
1143
+ </ViewTransition>
1144
+ </ViewTransition>
1145
+ );
1146
+ }
1147
+
1148
+ const root = ReactDOMClient.createRoot(container);
1149
+
1150
+ await act(() => {
1151
+ startTransition(() => {
1152
+ root.render(<App show={false} />);
1153
+ });
1154
+ });
1155
+
1156
+ onParentEnter.mockClear();
1157
+
1158
+ await act(() => {
1159
+ startTransition(() => {
1160
+ root.render(<App show={true} />);
1161
+ });
1162
+ });
1163
+
1164
+ expect(onParentEnter).not.toHaveBeenCalled();
1165
+ });
1166
+
1167
+ // @gate enableViewTransition && enableViewTransitionParentEnterExit
1168
+ it('relays parentEnter chain to handler-only child through intermediate divs', async () => {
1169
+ const onParentEnter = jest.fn();
1170
+ const onParentEnterNested = jest.fn();
1171
+
1172
+ function App({show}) {
1173
+ if (!show) {
1174
+ return null;
1175
+ }
1176
+ return (
1177
+ <ViewTransition onEnter={onParentEnter}>
1178
+ <div>
1179
+ <div>
1180
+ <ViewTransition onParentEnter={onParentEnterNested}>
1181
+ <div>Item</div>
1182
+ </ViewTransition>
1183
+ </div>
1184
+ </div>
1185
+ </ViewTransition>
1186
+ );
1187
+ }
1188
+
1189
+ const root = ReactDOMClient.createRoot(container);
1190
+
1191
+ await act(() => {
1192
+ startTransition(() => {
1193
+ root.render(<App show={false} />);
1194
+ });
1195
+ });
1196
+
1197
+ onParentEnter.mockClear();
1198
+ onParentEnterNested.mockClear();
1199
+
1200
+ await act(() => {
1201
+ startTransition(() => {
1202
+ root.render(<App show={true} />);
1203
+ });
1204
+ });
1205
+
1206
+ expect(onParentEnter).toHaveBeenCalledTimes(1);
1207
+ expect(onParentEnterNested).toHaveBeenCalledTimes(1);
1208
+ });
1209
+
1210
+ // @gate enableViewTransition
1211
+ it('enters without props and does not fire handlers', async () => {
1212
+ const startViewTransitionSpy = jest.fn(document.startViewTransition);
1213
+ document.startViewTransition = startViewTransitionSpy;
1214
+
1215
+ function App({show}) {
1216
+ if (!show) {
1217
+ return null;
1218
+ }
1219
+ return (
1220
+ <ViewTransition>
1221
+ <div>Hello</div>
1222
+ </ViewTransition>
1223
+ );
1224
+ }
1225
+
1226
+ const root = ReactDOMClient.createRoot(container);
1227
+
1228
+ await act(() => {
1229
+ root.render(<App show={false} />);
1230
+ });
1231
+ expect(startViewTransitionSpy).not.toHaveBeenCalled();
1232
+
1233
+ await act(() => {
1234
+ startTransition(() => {
1235
+ root.render(<App show={true} />);
1236
+ });
1237
+ });
1238
+
1239
+ expect(startViewTransitionSpy).toHaveBeenCalled();
1240
+ });
1241
+
1242
+ // @gate enableViewTransition
1243
+ it('exits without props and does not fire handlers', async () => {
1244
+ const startViewTransitionSpy = jest.fn(document.startViewTransition);
1245
+ document.startViewTransition = startViewTransitionSpy;
1246
+
1247
+ function App({show}) {
1248
+ if (!show) {
1249
+ return null;
1250
+ }
1251
+ return (
1252
+ <ViewTransition>
1253
+ <div>Goodbye</div>
1254
+ </ViewTransition>
1255
+ );
1256
+ }
1257
+
1258
+ const root = ReactDOMClient.createRoot(container);
1259
+
1260
+ await act(() => {
1261
+ startTransition(() => {
1262
+ root.render(<App show={true} />);
1263
+ });
1264
+ });
1265
+ startViewTransitionSpy.mockClear();
1266
+
1267
+ await act(() => {
1268
+ startTransition(() => {
1269
+ root.render(<App show={false} />);
1270
+ });
1271
+ });
1272
+
1273
+ expect(startViewTransitionSpy).toHaveBeenCalled();
1274
+ });
1275
+
1276
+ // @gate enableViewTransition && enableViewTransitionParentEnterExit
1277
+ it('fires onParentEnter when ancestor ViewTransition has no props', async () => {
1278
+ const onParentEnterNested = jest.fn();
1279
+
1280
+ function App({show}) {
1281
+ if (!show) {
1282
+ return null;
1283
+ }
1284
+ return (
1285
+ <ViewTransition>
1286
+ <div>
1287
+ <ViewTransition onParentEnter={onParentEnterNested}>
1288
+ <div>Item</div>
1289
+ </ViewTransition>
1290
+ </div>
1291
+ </ViewTransition>
1292
+ );
1293
+ }
1294
+
1295
+ const root = ReactDOMClient.createRoot(container);
1296
+
1297
+ await act(() => {
1298
+ startTransition(() => {
1299
+ root.render(<App show={false} />);
1300
+ });
1301
+ });
1302
+
1303
+ onParentEnterNested.mockClear();
1304
+
1305
+ await act(() => {
1306
+ startTransition(() => {
1307
+ root.render(<App show={true} />);
1308
+ });
1309
+ });
1310
+
1311
+ expect(onParentEnterNested).toHaveBeenCalledTimes(1);
1312
+ });
1313
+
1314
+ // @gate enableViewTransition && enableViewTransitionParentEnterExit
1315
+ it('fires onParentExit when ancestor ViewTransition has no props', async () => {
1316
+ const onParentExitNested = jest.fn();
1317
+
1318
+ function App({show}) {
1319
+ if (!show) {
1320
+ return null;
1321
+ }
1322
+ return (
1323
+ <ViewTransition>
1324
+ <div>
1325
+ <ViewTransition onParentExit={onParentExitNested}>
1326
+ <div>Item</div>
1327
+ </ViewTransition>
1328
+ </div>
1329
+ </ViewTransition>
1330
+ );
1331
+ }
1332
+
1333
+ const root = ReactDOMClient.createRoot(container);
1334
+
1335
+ await act(() => {
1336
+ startTransition(() => {
1337
+ root.render(<App show={true} />);
1338
+ });
1339
+ });
1340
+
1341
+ onParentExitNested.mockClear();
1342
+
1343
+ await act(() => {
1344
+ startTransition(() => {
1345
+ root.render(<App show={false} />);
1346
+ });
1347
+ });
1348
+
1349
+ expect(onParentExitNested).toHaveBeenCalledTimes(1);
1350
+ });
1351
});
1352
});