@@ -7031,6 +7031,86 @@ describe("XtermHost", () => {
70317031 vi . useRealTimers ( ) ;
70327032 } ) ;
70337033
7034+ it ( "mobile line copy still copies a resolved empty logical line on long press" , async ( ) => {
7035+ vi . useFakeTimers ( ) ;
7036+ viewportMocks . viewport = "mobile" ;
7037+
7038+ const originalMatchMedia = window . matchMedia ;
7039+ const writeText = vi . fn ( ) . mockResolvedValue ( undefined ) ;
7040+ const vibrate = vi . fn ( ) ;
7041+ const store = createStore ( ) ;
7042+
7043+ mockTerminal . buffer . active . viewportY = 14 ;
7044+ setMockBufferLines ( [ [ 14 , "" , false ] ] ) ;
7045+
7046+ window . matchMedia = vi . fn ( ) . mockImplementation ( ( query : string ) => ( {
7047+ matches : query === "(pointer: coarse)" ,
7048+ media : query ,
7049+ onchange : null ,
7050+ addListener : vi . fn ( ) ,
7051+ removeListener : vi . fn ( ) ,
7052+ addEventListener : vi . fn ( ) ,
7053+ removeEventListener : vi . fn ( ) ,
7054+ dispatchEvent : vi . fn ( ) ,
7055+ } ) ) as typeof window . matchMedia ;
7056+
7057+ Object . defineProperty ( navigator , "clipboard" , {
7058+ configurable : true ,
7059+ value : { writeText } satisfies Pick < Clipboard , "writeText" > ,
7060+ } ) ;
7061+ Object . defineProperty ( navigator , "vibrate" , {
7062+ configurable : true ,
7063+ value : vibrate ,
7064+ } ) ;
7065+
7066+ store . set ( localeAtom , "en" ) ;
7067+ store . set ( terminalPreferencesAtom , { copyOnSelect : true } ) ;
7068+ store . set ( wsClientAtom , {
7069+ sendCommand : vi . fn ( ) . mockResolvedValue ( { ok : true , data : { status : "ok" } } ) ,
7070+ subscribe : vi . fn ( ( ) => ( ) => { } ) ,
7071+ getStatus : vi . fn ( ( ) => "connected" ) ,
7072+ onStatus : vi . fn ( ( ) => ( ) => { } ) ,
7073+ sendTerminalInput : vi . fn ( ) . mockResolvedValue ( undefined ) ,
7074+ } as never ) ;
7075+
7076+ const { container } = render (
7077+ < Provider store = { store } >
7078+ < XtermHost terminalId = "mobile-line-copy-empty-line-terminal" workspaceId = "test-workspace" />
7079+ </ Provider >
7080+ ) ;
7081+
7082+ const host = container . querySelector ( ".xterm-host" ) as HTMLDivElement | null ;
7083+ expect ( host ) . toBeTruthy ( ) ;
7084+
7085+ const rowsElement = document . createElement ( "div" ) ;
7086+ rowsElement . className = "xterm-rows" ;
7087+ rowsElement . innerHTML = "<div><span> </span></div>" ;
7088+ host ! . appendChild ( rowsElement ) ;
7089+
7090+ const target = rowsElement . querySelector ( "span" ) as HTMLSpanElement ;
7091+ dispatchTouchEvent ( host ! , "touchstart" , [ { identifier : 1 , clientX : 40 , clientY : 120 , target } ] ) ;
7092+
7093+ await act ( async ( ) => {
7094+ vi . advanceTimersByTime ( 500 ) ;
7095+ await Promise . resolve ( ) ;
7096+ await Promise . resolve ( ) ;
7097+ } ) ;
7098+
7099+ expect ( writeText ) . toHaveBeenCalledWith ( "" ) ;
7100+ expect ( store . get ( toastsAtom ) ) . toEqual (
7101+ expect . arrayContaining ( [
7102+ expect . objectContaining ( {
7103+ kind : "success" ,
7104+ title : "Copied current line" ,
7105+ } ) ,
7106+ ] )
7107+ ) ;
7108+ expect ( vibrate ) . toHaveBeenCalledWith ( 10 ) ;
7109+
7110+ window . matchMedia = originalMatchMedia ;
7111+ vi . useRealTimers ( ) ;
7112+ } ) ;
7113+
70347114 it ( "mobile line copy does nothing when copy-on-select is disabled" , async ( ) => {
70357115 vi . useFakeTimers ( ) ;
70367116 viewportMocks . viewport = "mobile" ;
0 commit comments