@@ -777,50 +777,55 @@ describe("GhosttyTerminalSurface visibility", () => {
777777 expect ( readText ) . not . toHaveBeenCalled ( ) ;
778778 } ) ;
779779
780- it ( "pastes once per shortcut whichever of the clipboard read and native paste lands first" , async ( ) => {
780+ function paste ( surface : GhosttyTerminalSurface , text : string ) {
781+ surface . input . dispatchEvent (
782+ Object . assign ( new Event ( "paste" , { cancelable : true } ) , {
783+ clipboardData : { getData : ( ) => text } ,
784+ } ) ,
785+ ) ;
786+ }
787+
788+ it . each ( [ "native" , "clipboard" ] ) ( "pastes once when %s arrives first" , async ( first ) => {
781789 const harness = createHarness ( ) ;
782- const readText = vi . fn ( async ( ) => "echo hi" ) ;
783- vi . stubGlobal ( "navigator" , { platform : "MacIntel" , clipboard : { readText } } ) ;
790+ const read = Promise . resolve ( "echo hi" ) ;
791+ vi . stubGlobal ( "navigator" , {
792+ platform : "MacIntel" ,
793+ clipboard : { readText : ( ) => read } ,
794+ } ) ;
784795 const surface = await harness . create ( { beforeKey : ( ) => true } ) ;
785- const paste = ( text : string ) =>
786- surface . input . dispatchEvent (
787- Object . assign ( new Event ( "paste" , { cancelable : true } ) , {
788- clipboardData : { getData : ( ) => text } ,
789- } ) ,
790- ) ;
791- const settleRead = async ( reads : number ) => {
792- expect ( readText ) . toHaveBeenCalledTimes ( reads ) ;
793- await readText . mock . results [ reads - 1 ] ?. value ;
794- } ;
795796
796- // Read first (Chromium menu paste): the native paste that follows is dropped.
797797 key ( surface , "v" , "KeyV" , { metaKey : true } ) ;
798- await settleRead ( 1 ) ;
799- paste ( "echo hi" ) ;
800- key ( surface , "v" , "KeyV" , { metaKey : true } , "keyup" ) ;
801- expect ( harness . onData ) . toHaveBeenCalledTimes ( 1 ) ;
798+ if ( first === "native" ) paste ( surface , "echo hi" ) ;
799+ await read ;
800+ if ( first === "clipboard" ) paste ( surface , "echo hi" ) ;
802801
803- // Native first: the read that settles afterwards is dropped.
804- key ( surface , "v" , "KeyV" , { metaKey : true } ) ;
805- paste ( "echo hi" ) ;
806- await settleRead ( 2 ) ;
807- key ( surface , "v" , "KeyV" , { metaKey : true } , "keyup" ) ;
808- expect ( harness . onData ) . toHaveBeenCalledTimes ( 2 ) ;
809-
810- // A shortcut served only by the read must not swallow a later Edit → Paste
811- // of the same text, after the keyup or after a blur.
812- key ( surface , "v" , "KeyV" , { metaKey : true } ) ;
813- await settleRead ( 3 ) ;
814- key ( surface , "v" , "KeyV" , { metaKey : true } , "keyup" ) ;
815- paste ( "echo hi" ) ;
816- key ( surface , "v" , "KeyV" , { metaKey : true } ) ;
817- await settleRead ( 4 ) ;
818- surface . input . dispatchEvent ( new Event ( "blur" ) ) ;
819- paste ( "echo hi" ) ;
820- expect ( harness . onData ) . toHaveBeenCalledTimes ( 6 ) ;
821- expect ( new Set ( harness . onData . mock . calls . map ( ( [ data ] ) => data ) ) ) . toEqual ( new Set ( [ "echo hi" ] ) ) ;
802+ expect ( harness . onData . mock . calls ) . toEqual ( [ [ "echo hi" ] ] ) ;
822803 } ) ;
823804
805+ it . each ( [ "V keyup" , "blur" , "Meta keyup without V keyup" ] ) (
806+ "accepts a later identical menu paste after %s" ,
807+ async ( end ) => {
808+ const harness = createHarness ( ) ;
809+ const read = Promise . resolve ( "echo hi" ) ;
810+ vi . stubGlobal ( "navigator" , {
811+ platform : "MacIntel" ,
812+ clipboard : { readText : ( ) => read } ,
813+ } ) ;
814+ const surface = await harness . create ( { beforeKey : ( ) => true } ) ;
815+
816+ key ( surface , "v" , "KeyV" , { metaKey : true } ) ;
817+ await read ;
818+ expect ( harness . onData . mock . calls ) . toEqual ( [ [ "echo hi" ] ] ) ;
819+ if ( end === "blur" ) surface . input . dispatchEvent ( new Event ( "blur" ) ) ;
820+ else if ( end === "V keyup" ) key ( surface , "v" , "KeyV" , { metaKey : true } , "keyup" ) ;
821+ // macOS can omit V keyup while Command is held.
822+ else key ( surface , "Meta" , "MetaLeft" , { } , "keyup" ) ;
823+ paste ( surface , "echo hi" ) ;
824+
825+ expect ( harness . onData . mock . calls ) . toEqual ( [ [ "echo hi" ] , [ "echo hi" ] ] ) ;
826+ } ,
827+ ) ;
828+
824829 it ( "starts a selection when dragging from a link" , async ( ) => {
825830 const harness = createHarness ( ) ;
826831 const onLinkActivate = vi . fn ( ) ;
0 commit comments