@@ -1129,6 +1129,138 @@ describe("shared-tab dispatch fences", () => {
11291129 const fresh = await f . command ( "sync" ) ;
11301130 expect ( await f . act ( fresh . context_id ) ) . toEqual ( { } ) ;
11311131 } ) ;
1132+ it . each ( [
1133+ "before-release" ,
1134+ "after-release" ,
1135+ ] ) ( "preserves a completed click when navigation invalidates its document %s" , async ( navigation ) => {
1136+ const f = await fixture ( ) ;
1137+ const receipt = { tab_id : 7 , used_ref : "e1" , x : 4 , y : 5 } ;
1138+ const result = await f . act ( f . first . context_id , async ( runner ) => {
1139+ await runner . send ( 7 , "Input.dispatchMouseEvent" , {
1140+ type : "mousePressed" ,
1141+ button : "left" ,
1142+ x : 4 ,
1143+ y : 5 ,
1144+ } ) ;
1145+ if ( navigation === "before-release" ) f . controller . invalidate ( 7 ) ;
1146+ await runner . send ( 7 , "Input.dispatchMouseEvent" , {
1147+ type : "mouseReleased" ,
1148+ button : "left" ,
1149+ x : 4 ,
1150+ y : 5 ,
1151+ } ) ;
1152+ if ( navigation === "after-release" ) f . controller . invalidate ( 7 ) ;
1153+ return receipt ;
1154+ } ) ;
1155+ expect ( result ) . toEqual ( receipt ) ;
1156+ expect ( f . native ) . toHaveBeenCalledTimes ( 2 ) ;
1157+ expect ( await f . act ( ) ) . toMatchObject ( { code : "permission_denied" } ) ;
1158+ const fresh = await f . command ( "sync" ) ;
1159+ expect ( await f . act ( fresh . context_id ) ) . toEqual ( { } ) ;
1160+ } ) ;
1161+ it . each ( [
1162+ "human" ,
1163+ "hold" ,
1164+ "stop" ,
1165+ "cancel" ,
1166+ "failed-release" ,
1167+ "more-input" ,
1168+ ] ) ( "does not hide %s after a navigating click" , async ( intervention ) => {
1169+ const f = await fixture ( ) ;
1170+ const result = await f . act ( f . first . context_id , async ( runner ) => {
1171+ await runner . send ( 7 , "Input.dispatchMouseEvent" , {
1172+ type : "mousePressed" ,
1173+ button : "left" ,
1174+ x : 4 ,
1175+ y : 5 ,
1176+ } ) ;
1177+ f . controller . invalidate ( 7 ) ;
1178+ if ( intervention === "human" ) f . change ( { human : 1 , lastInput : 10000 } ) ;
1179+ if ( intervention === "hold" )
1180+ await f . controller . control ( 7 , f . controller . view ( 7 ) ! . revision , "hold" ) ;
1181+ if ( intervention === "stop" ) f . controller . stopSession ( "shared" ) ;
1182+ if ( intervention === "cancel" ) f . abort . abort ( ) ;
1183+ if ( intervention === "failed-release" )
1184+ return { code : "cdp_failed" , message : "release failed" } ;
1185+ await runner . send ( 7 , "Input.dispatchMouseEvent" , {
1186+ type : "mouseReleased" ,
1187+ button : "left" ,
1188+ x : 4 ,
1189+ y : 5 ,
1190+ } ) ;
1191+ if ( intervention === "more-input" )
1192+ await runner . send ( 7 , "Input.insertText" , { text : "stale" } ) ;
1193+ return { tab_id : 7 , x : 4 , y : 5 } ;
1194+ } ) ;
1195+ expect ( result ) . toMatchObject ( {
1196+ code :
1197+ intervention === "cancel"
1198+ ? "cancelled"
1199+ : intervention === "stop"
1200+ ? "user_aborted"
1201+ : "permission_denied" ,
1202+ data : { effect_state : "unknown" } ,
1203+ } ) ;
1204+ expect ( f . native ) . toHaveBeenCalledTimes ( intervention === "failed-release" ? 1 : 2 ) ;
1205+ } ) ;
1206+ it ( "preserves a completed Enter submission after its document is replaced" , async ( ) => {
1207+ const f = await fixture ( ) ;
1208+ const receipt = { tab_id : 7 , key : "Enter" , code : "Enter" , modifiers : [ ] } ;
1209+ const result = await f . act (
1210+ f . first . context_id ,
1211+ async ( runner ) => {
1212+ await runner . send ( 7 , "Input.dispatchKeyEvent" , { type : "rawKeyDown" , key : "Enter" } ) ;
1213+ await runner . send ( 7 , "Input.dispatchKeyEvent" , { type : "char" , key : "Enter" , text : "\r" } ) ;
1214+ f . controller . invalidate ( 7 ) ;
1215+ await runner . send ( 7 , "Input.dispatchKeyEvent" , { type : "keyUp" , key : "Enter" } ) ;
1216+ return receipt ;
1217+ } ,
1218+ "tool.press" ,
1219+ ) ;
1220+ expect ( result ) . toEqual ( receipt ) ;
1221+ expect ( await f . act ( ) ) . toMatchObject ( { code : "permission_denied" } ) ;
1222+ const fresh = await f . command ( "sync" ) ;
1223+ expect ( await f . act ( fresh . context_id ) ) . toEqual ( { } ) ;
1224+ } ) ;
1225+ it . each ( [
1226+ "tool.drag" ,
1227+ "tool.select" ,
1228+ ] ) ( "preserves completed %s receipts after navigation" , async ( method ) => {
1229+ const f = await fixture ( ) ;
1230+ const receipt = { tab_id : 7 } ;
1231+ const result = await f . act (
1232+ f . first . context_id ,
1233+ async ( runner ) => {
1234+ if ( method === "tool.drag" ) {
1235+ await runner . send ( 7 , "Input.dispatchMouseEvent" , {
1236+ type : "mousePressed" ,
1237+ button : "left" ,
1238+ x : 4 ,
1239+ y : 5 ,
1240+ } ) ;
1241+ await runner . send ( 7 , "Input.dispatchMouseEvent" , {
1242+ type : "mouseReleased" ,
1243+ button : "left" ,
1244+ x : 10 ,
1245+ y : 5 ,
1246+ } ) ;
1247+ } else {
1248+ await runner . beginDomMutation ?.( ) ;
1249+ await runner . send ( 7 , "Runtime.callFunctionOn" , {
1250+ objectId : "select" ,
1251+ functionDeclaration : "function() { this.dispatchEvent(new Event('change')); }" ,
1252+ } ) ;
1253+ }
1254+ f . controller . invalidate ( 7 ) ;
1255+ return receipt ;
1256+ } ,
1257+ method ,
1258+ ) ;
1259+ expect ( result ) . toEqual ( receipt ) ;
1260+ expect ( await f . act ( ) ) . toMatchObject ( { code : "permission_denied" } ) ;
1261+ const fresh = await f . command ( "sync" ) ;
1262+ expect ( await f . act ( fresh . context_id ) ) . toEqual ( { } ) ;
1263+ } ) ;
11321264 it ( "still blocks input after a navigation invalidates the action's document" , async ( ) => {
11331265 const f = await fixture ( ) ;
11341266 const result = await f . act (
0 commit comments