@@ -964,6 +964,322 @@ const {isOSS} = Fantom.getConstants();
964964 } ,
965965 ) ;
966966
967+ ( ReactNativeFeatureFlags . enableNativeEventTargetEventDispatching ( )
968+ ? describe
969+ : describe . skip ) ( 'bubbling to document element and document' , ( ) => {
970+ it ( 'event bubbles from child up to the document element' , ( ) => {
971+ const root = Fantom . createRoot ( ) ;
972+ const childRef = React . createRef < React . ElementRef < typeof View >> ( ) ;
973+ const documentElementHandler = jest . fn ( ) ;
974+
975+ Fantom . runTask ( ( ) => {
976+ root . render ( < View ref = { childRef } /> ) ;
977+ } ) ;
978+
979+ asEventTarget ( root . document . documentElement ) . addEventListener (
980+ 'pointerup' ,
981+ documentElementHandler ,
982+ ) ;
983+
984+ Fantom . dispatchNativeEvent (
985+ childRef ,
986+ 'onPointerUp' ,
987+ { x : 0 , y : 0 } ,
988+ {
989+ category : Fantom . NativeEventCategory . Discrete ,
990+ } ,
991+ ) ;
992+
993+ expect ( documentElementHandler ) . toHaveBeenCalledTimes ( 1 ) ;
994+ } ) ;
995+
996+ it ( 'event bubbles from child up to the document' , ( ) => {
997+ const root = Fantom . createRoot ( ) ;
998+ const childRef = React . createRef < React . ElementRef < typeof View >> ( ) ;
999+ const documentHandler = jest . fn ( ) ;
1000+
1001+ Fantom . runTask ( ( ) => {
1002+ root . render ( < View ref = { childRef } /> ) ;
1003+ } ) ;
1004+
1005+ asEventTarget ( root . document ) . addEventListener (
1006+ 'pointerup' ,
1007+ documentHandler ,
1008+ ) ;
1009+
1010+ Fantom . dispatchNativeEvent (
1011+ childRef ,
1012+ 'onPointerUp' ,
1013+ { x : 0 , y : 0 } ,
1014+ {
1015+ category : Fantom . NativeEventCategory . Discrete ,
1016+ } ,
1017+ ) ;
1018+
1019+ expect ( documentHandler ) . toHaveBeenCalledTimes ( 1 ) ;
1020+ } ) ;
1021+
1022+ it ( 'event bubbles from a deeply nested child up to document element and document' , ( ) => {
1023+ const root = Fantom . createRoot ( ) ;
1024+ const childRef = React . createRef < React . ElementRef < typeof View >> ( ) ;
1025+ const documentElementHandler = jest . fn ( ) ;
1026+ const documentHandler = jest . fn ( ) ;
1027+
1028+ Fantom . runTask ( ( ) => {
1029+ root . render (
1030+ < View >
1031+ < View >
1032+ < View >
1033+ < View ref = { childRef } />
1034+ </ View >
1035+ </ View >
1036+ </ View > ,
1037+ ) ;
1038+ } ) ;
1039+
1040+ asEventTarget ( root . document . documentElement ) . addEventListener (
1041+ 'pointerup' ,
1042+ documentElementHandler ,
1043+ ) ;
1044+ asEventTarget ( root . document ) . addEventListener (
1045+ 'pointerup' ,
1046+ documentHandler ,
1047+ ) ;
1048+
1049+ Fantom . dispatchNativeEvent (
1050+ childRef ,
1051+ 'onPointerUp' ,
1052+ { x : 0 , y : 0 } ,
1053+ {
1054+ category : Fantom . NativeEventCategory . Discrete ,
1055+ } ,
1056+ ) ;
1057+
1058+ expect ( documentElementHandler ) . toHaveBeenCalledTimes ( 1 ) ;
1059+ expect ( documentHandler ) . toHaveBeenCalledTimes ( 1 ) ;
1060+ } ) ;
1061+
1062+ it ( 'capture phase on document fires before capture phase on document element' , ( ) => {
1063+ const root = Fantom . createRoot ( ) ;
1064+ const childRef = React . createRef < React . ElementRef < typeof View >> ( ) ;
1065+ const order : Array < string > = [];
1066+
1067+ Fantom.runTask(() => {
1068+ root . render (
1069+ < View
1070+ ref = { childRef }
1071+ onPointerUpCapture = { ( ) => {
1072+ order . push ( 'child-capture' ) ;
1073+ } }
1074+ onPointerUp = { ( ) => {
1075+ order . push ( 'child-bubble' ) ;
1076+ } }
1077+ /> ,
1078+ ) ;
1079+ } );
1080+
1081+ asEventTarget(root.document).addEventListener(
1082+ 'pointerup',
1083+ () => {
1084+ order . push ( 'document-capture' ) ;
1085+ } ,
1086+ { capture : true } ,
1087+ );
1088+ asEventTarget(root.document.documentElement).addEventListener(
1089+ 'pointerup',
1090+ () => {
1091+ order . push ( 'documentElement-capture' ) ;
1092+ } ,
1093+ { capture : true } ,
1094+ );
1095+ asEventTarget(root.document.documentElement).addEventListener(
1096+ 'pointerup',
1097+ () => {
1098+ order . push ( 'documentElement-bubble' ) ;
1099+ } ,
1100+ );
1101+ asEventTarget(root.document).addEventListener('pointerup', () => {
1102+ order . push ( 'document-bubble' ) ;
1103+ } );
1104+
1105+ Fantom.dispatchNativeEvent(
1106+ childRef,
1107+ 'onPointerUp',
1108+ { x : 0 , y : 0 } ,
1109+ {
1110+ category : Fantom . NativeEventCategory . Discrete ,
1111+ } ,
1112+ );
1113+
1114+ expect(order).toEqual([
1115+ 'document-capture',
1116+ 'documentElement-capture',
1117+ 'child-capture',
1118+ 'child-bubble',
1119+ 'documentElement-bubble',
1120+ 'document-bubble',
1121+ ]);
1122+ } ) ;
1123+
1124+ it ( 'event.target points to the original child and event.currentTarget transitions through document element and document' , ( ) => {
1125+ const root = Fantom . createRoot ( ) ;
1126+ const childRef = React . createRef < React . ElementRef < typeof View >> ( ) ;
1127+ const targets : Array < { target : unknown , currentTarget : unknown } > = [ ] ;
1128+
1129+ Fantom . runTask ( ( ) => {
1130+ root . render ( < View ref = { childRef } /> ) ;
1131+ } ) ;
1132+
1133+ asEventTarget ( root . document . documentElement ) . addEventListener (
1134+ 'pointerup' ,
1135+ ( e : $FlowFixMe ) => {
1136+ targets . push ( { target : e . target , currentTarget : e . currentTarget } ) ;
1137+ } ,
1138+ ) ;
1139+ asEventTarget ( root . document ) . addEventListener (
1140+ 'pointerup' ,
1141+ ( e : $FlowFixMe ) => {
1142+ targets . push ( { target : e . target , currentTarget : e . currentTarget } ) ;
1143+ } ,
1144+ ) ;
1145+
1146+ Fantom . dispatchNativeEvent (
1147+ childRef ,
1148+ 'onPointerUp' ,
1149+ { x : 0 , y : 0 } ,
1150+ {
1151+ category : Fantom . NativeEventCategory . Discrete ,
1152+ } ,
1153+ ) ;
1154+
1155+ expect ( targets ) . toHaveLength ( 2 ) ;
1156+
1157+ // event.target is always the original target element
1158+ expect ( targets [ 0 ] . target ) . toBe ( childRef . current ) ;
1159+ expect ( targets [ 1 ] . target ) . toBe ( childRef . current ) ;
1160+
1161+ // event.currentTarget changes at each propagation step
1162+ expect ( targets [ 0 ] . currentTarget ) . toBe ( root . document . documentElement ) ;
1163+ expect ( targets [ 1 ] . currentTarget ) . toBe ( root . document ) ;
1164+ } ) ;
1165+
1166+ it ( 'stopPropagation on document element prevents document handler from firing' , ( ) => {
1167+ const root = Fantom . createRoot ( ) ;
1168+ const childRef = React . createRef < React . ElementRef < typeof View >> ( ) ;
1169+ const documentHandler = jest . fn ( ) ;
1170+ const documentElementHandler = jest . fn ( ( e : $FlowFixMe ) => {
1171+ e . stopPropagation ( ) ;
1172+ } ) ;
1173+
1174+ Fantom . runTask ( ( ) => {
1175+ root . render ( < View ref = { childRef } /> ) ;
1176+ } ) ;
1177+
1178+ asEventTarget ( root . document . documentElement ) . addEventListener (
1179+ 'pointerup' ,
1180+ documentElementHandler ,
1181+ ) ;
1182+ asEventTarget ( root . document ) . addEventListener (
1183+ 'pointerup' ,
1184+ documentHandler ,
1185+ ) ;
1186+
1187+ Fantom . dispatchNativeEvent (
1188+ childRef ,
1189+ 'onPointerUp' ,
1190+ { x : 0 , y : 0 } ,
1191+ {
1192+ category : Fantom . NativeEventCategory . Discrete ,
1193+ } ,
1194+ ) ;
1195+
1196+ expect ( documentElementHandler ) . toHaveBeenCalledTimes ( 1 ) ;
1197+ expect ( documentHandler ) . toHaveBeenCalledTimes ( 0 ) ;
1198+ } ) ;
1199+
1200+ it ( 'removeEventListener on document element stops events from being received' , ( ) => {
1201+ const root = Fantom . createRoot ( ) ;
1202+ const childRef = React . createRef < React . ElementRef < typeof View >> ( ) ;
1203+ const documentElementHandler = jest . fn ( ) ;
1204+
1205+ Fantom . runTask ( ( ) => {
1206+ root . render ( < View ref = { childRef } /> ) ;
1207+ } ) ;
1208+
1209+ asEventTarget ( root . document . documentElement ) . addEventListener (
1210+ 'pointerup' ,
1211+ documentElementHandler ,
1212+ ) ;
1213+
1214+ Fantom . dispatchNativeEvent (
1215+ childRef ,
1216+ 'onPointerUp' ,
1217+ { x : 0 , y : 0 } ,
1218+ {
1219+ category : Fantom . NativeEventCategory . Discrete ,
1220+ } ,
1221+ ) ;
1222+
1223+ expect ( documentElementHandler ) . toHaveBeenCalledTimes ( 1 ) ;
1224+
1225+ asEventTarget ( root . document . documentElement ) . removeEventListener (
1226+ 'pointerup' ,
1227+ documentElementHandler ,
1228+ ) ;
1229+
1230+ Fantom . dispatchNativeEvent (
1231+ childRef ,
1232+ 'onPointerUp' ,
1233+ { x : 0 , y : 0 } ,
1234+ {
1235+ category : Fantom . NativeEventCategory . Discrete ,
1236+ } ,
1237+ ) ;
1238+
1239+ expect ( documentElementHandler ) . toHaveBeenCalledTimes ( 1 ) ;
1240+ } ) ;
1241+
1242+ it ( 'direct (non-bubbling) events do not reach the document element or document' , ( ) => {
1243+ const root = Fantom . createRoot ( ) ;
1244+ const childRef = React . createRef < React . ElementRef < typeof View >> ( ) ;
1245+ const childHandler = jest . fn ( ) ;
1246+ const documentElementHandler = jest . fn ( ) ;
1247+ const documentHandler = jest . fn ( ) ;
1248+
1249+ Fantom . runTask ( ( ) => {
1250+ root . render ( < View ref = { childRef } onLayout = { childHandler } /> ) ;
1251+ } ) ;
1252+
1253+ asEventTarget ( root . document . documentElement ) . addEventListener (
1254+ 'layout' ,
1255+ documentElementHandler ,
1256+ ) ;
1257+ asEventTarget ( root . document ) . addEventListener (
1258+ 'layout' ,
1259+ documentHandler ,
1260+ ) ;
1261+
1262+ const childCallsBefore = childHandler . mock . calls . length ;
1263+
1264+ Fantom . dispatchNativeEvent (
1265+ childRef ,
1266+ 'onLayout' ,
1267+ { layout : { x : 0 , y : 0 , width : 100 , height : 50 } } ,
1268+ {
1269+ category : Fantom . NativeEventCategory . Discrete ,
1270+ } ,
1271+ ) ;
1272+
1273+ // Child handler fires
1274+ expect (
1275+ childHandler . mock . calls . length - childCallsBefore ,
1276+ ) . toBeGreaterThan ( 0 ) ;
1277+ // Non-bubbling events don't reach the document element or the document
1278+ expect ( documentElementHandler ) . toHaveBeenCalledTimes ( 0 ) ;
1279+ expect ( documentHandler ) . toHaveBeenCalledTimes ( 0 ) ;
1280+ } ) ;
1281+ } ) ;
1282+
9671283 it ( 'stopPropagation in capture phase prevents all bubble-phase handlers' , ( ) => {
9681284 const root = Fantom . createRoot ( ) ;
9691285 const childRef = React . createRef < React . ElementRef < typeof View >> ( ) ;
0 commit comments