@@ -246,6 +246,7 @@ const mockTerminal = {
246246 dispose : vi . fn ( ) ,
247247 focus : vi . fn ( ) ,
248248 loadAddon : vi . fn ( ) ,
249+ cols : 80 ,
249250 buffer : {
250251 active : {
251252 viewportY : 0 ,
@@ -6998,6 +6999,7 @@ describe("XtermHost", () => {
69986999 const vibrate = vi . fn ( ) ;
69997000 const store = createStore ( ) ;
70007001
7002+ mockTerminal . cols = 80 ;
70017003 mockTerminal . rows = 3 ;
70027004 mockTerminal . buffer . active . viewportY = 10 ;
70037005 setMockBufferLines ( [
@@ -7062,7 +7064,7 @@ describe("XtermHost", () => {
70627064 rowHeight : 20 ,
70637065 } ) ;
70647066
7065- dispatchTouchEvent ( host ! , "touchstart" , [ { identifier : 1 , clientX : 40 , clientY : 150 } ] ) ;
7067+ dispatchTouchEvent ( host ! , "touchstart" , [ { identifier : 1 , clientX : 6 , clientY : 150 } ] ) ;
70667068
70677069 await act ( async ( ) => {
70687070 vi . advanceTimersByTime ( 500 ) ;
@@ -7094,6 +7096,7 @@ describe("XtermHost", () => {
70947096 const vibrate = vi . fn ( ) ;
70957097 const store = createStore ( ) ;
70967098
7099+ mockTerminal . cols = 80 ;
70977100 mockTerminal . buffer . active . viewportY = 14 ;
70987101 setMockBufferLines ( [ [ 14 , "" , false ] ] ) ;
70997102
@@ -7145,7 +7148,7 @@ describe("XtermHost", () => {
71457148 rowHeight : 20 ,
71467149 } ) ;
71477150
7148- dispatchTouchEvent ( host ! , "touchstart" , [ { identifier : 1 , clientX : 40 , clientY : 110 } ] ) ;
7151+ dispatchTouchEvent ( host ! , "touchstart" , [ { identifier : 1 , clientX : 20 , clientY : 110 } ] ) ;
71497152
71507153 await act ( async ( ) => {
71517154 vi . advanceTimersByTime ( 500 ) ;
@@ -7177,6 +7180,7 @@ describe("XtermHost", () => {
71777180 const vibrate = vi . fn ( ) ;
71787181 const store = createStore ( ) ;
71797182
7183+ mockTerminal . cols = 80 ;
71807184 mockTerminal . buffer . active . viewportY = 3 ;
71817185 setMockBufferLines ( [ [ 3 , "disabled line" , false ] ] ) ;
71827186
@@ -7228,7 +7232,7 @@ describe("XtermHost", () => {
72287232 rowHeight : 20 ,
72297233 } ) ;
72307234
7231- dispatchTouchEvent ( host ! , "touchstart" , [ { identifier : 1 , clientX : 40 , clientY : 110 } ] ) ;
7235+ dispatchTouchEvent ( host ! , "touchstart" , [ { identifier : 1 , clientX : 20 , clientY : 110 } ] ) ;
72327236
72337237 await act ( async ( ) => {
72347238 vi . advanceTimersByTime ( 500 ) ;
@@ -7248,6 +7252,7 @@ describe("XtermHost", () => {
72487252
72497253 const originalMatchMedia = window . matchMedia ;
72507254 const writeText = vi . fn ( ) . mockResolvedValue ( undefined ) ;
7255+ mockTerminal . cols = 80 ;
72517256 mockTerminal . rows = 20 ;
72527257 mockTerminal . buffer . active . viewportY = 6 ;
72537258 mockTerminal . buffer . active . baseY = 80 ;
@@ -7296,7 +7301,7 @@ describe("XtermHost", () => {
72967301 }
72977302 ) ;
72987303
7299- dispatchTouchEvent ( host ! , "touchstart" , [ { identifier : 1 , clientX : 40 , clientY : 110 } ] ) ;
7304+ dispatchTouchEvent ( host ! , "touchstart" , [ { identifier : 1 , clientX : 20 , clientY : 110 } ] ) ;
73007305 dispatchTouchEvent ( host ! , "touchmove" , [ { identifier : 1 , clientX : 40 , clientY : 88 } ] ) ;
73017306
73027307 await act ( async ( ) => {
@@ -7318,6 +7323,7 @@ describe("XtermHost", () => {
73187323 const originalMatchMedia = window . matchMedia ;
73197324 const writeText = vi . fn ( ) . mockResolvedValue ( undefined ) ;
73207325 const vibrate = vi . fn ( ) ;
7326+ mockTerminal . cols = 80 ;
73217327 mockTerminal . rows = 20 ;
73227328 mockTerminal . buffer . active . viewportY = 6 ;
73237329 mockTerminal . buffer . active . baseY = 80 ;
@@ -7373,7 +7379,7 @@ describe("XtermHost", () => {
73737379 }
73747380 ) ;
73757381
7376- dispatchTouchEvent ( host ! , "touchstart" , [ { identifier : 1 , clientX : 40 , clientY : 110 } ] ) ;
7382+ dispatchTouchEvent ( host ! , "touchstart" , [ { identifier : 1 , clientX : 20 , clientY : 110 } ] ) ;
73777383 dispatchTouchEvent ( host ! , "touchmove" , [ { identifier : 1 , clientX : 56 , clientY : 110 } ] ) ;
73787384
73797385 await act ( async ( ) => {
@@ -7477,6 +7483,7 @@ describe("XtermHost", () => {
74777483 const vibrate = vi . fn ( ) ;
74787484 const store = createStore ( ) ;
74797485
7486+ mockTerminal . cols = 80 ;
74807487 mockTerminal . buffer . active . viewportY = 7 ;
74817488 setMockBufferLines ( [ [ 7 , "toast line" , false ] ] ) ;
74827489
@@ -7528,7 +7535,7 @@ describe("XtermHost", () => {
75287535 rowHeight : 20 ,
75297536 } ) ;
75307537
7531- dispatchTouchEvent ( host ! , "touchstart" , [ { identifier : 1 , clientX : 40 , clientY : 110 } ] ) ;
7538+ dispatchTouchEvent ( host ! , "touchstart" , [ { identifier : 1 , clientX : 20 , clientY : 110 } ] ) ;
75327539
75337540 await act ( async ( ) => {
75347541 vi . advanceTimersByTime ( 500 ) ;
@@ -7560,6 +7567,7 @@ describe("XtermHost", () => {
75607567 const vibrate = vi . fn ( ) ;
75617568 const store = createStore ( ) ;
75627569
7570+ mockTerminal . cols = 80 ;
75637571 mockTerminal . buffer . active . viewportY = 9 ;
75647572 setMockBufferLines ( [ [ 10 , "from coords" , false ] ] ) ;
75657573
@@ -7635,6 +7643,86 @@ describe("XtermHost", () => {
76357643 vi . useRealTimers ( ) ;
76367644 } ) ;
76377645
7646+ it ( "mobile line copy does not copy when a long press lands in the blank area to the right of a short row" , async ( ) => {
7647+ vi . useFakeTimers ( ) ;
7648+ viewportMocks . viewport = "mobile" ;
7649+
7650+ const originalMatchMedia = window . matchMedia ;
7651+ const writeText = vi . fn ( ) . mockResolvedValue ( undefined ) ;
7652+ const vibrate = vi . fn ( ) ;
7653+ const store = createStore ( ) ;
7654+
7655+ mockTerminal . cols = 80 ;
7656+ mockTerminal . buffer . active . viewportY = 12 ;
7657+ setMockBufferLines ( [ [ 12 , "short" , false ] ] ) ;
7658+
7659+ window . matchMedia = vi . fn ( ) . mockImplementation ( ( query : string ) => ( {
7660+ matches : query === "(pointer: coarse)" ,
7661+ media : query ,
7662+ onchange : null ,
7663+ addListener : vi . fn ( ) ,
7664+ removeListener : vi . fn ( ) ,
7665+ addEventListener : vi . fn ( ) ,
7666+ removeEventListener : vi . fn ( ) ,
7667+ dispatchEvent : vi . fn ( ) ,
7668+ } ) ) as typeof window . matchMedia ;
7669+
7670+ Object . defineProperty ( navigator , "clipboard" , {
7671+ configurable : true ,
7672+ value : { writeText } satisfies Pick < Clipboard , "writeText" > ,
7673+ } ) ;
7674+ Object . defineProperty ( navigator , "vibrate" , {
7675+ configurable : true ,
7676+ value : vibrate ,
7677+ } ) ;
7678+
7679+ store . set ( localeAtom , "en" ) ;
7680+ store . set ( terminalPreferencesAtom , { copyOnSelect : true } ) ;
7681+ store . set ( wsClientAtom , {
7682+ sendCommand : vi . fn ( ) . mockResolvedValue ( { ok : true , data : { status : "ok" } } ) ,
7683+ subscribe : vi . fn ( ( ) => ( ) => { } ) ,
7684+ getStatus : vi . fn ( ( ) => "connected" ) ,
7685+ onStatus : vi . fn ( ( ) => ( ) => { } ) ,
7686+ sendTerminalInput : vi . fn ( ) . mockResolvedValue ( undefined ) ,
7687+ } as never ) ;
7688+
7689+ const { container } = render (
7690+ < Provider store = { store } >
7691+ < XtermHost
7692+ terminalId = "mobile-line-copy-short-row-blank-area-terminal"
7693+ workspaceId = "test-workspace"
7694+ />
7695+ </ Provider >
7696+ ) ;
7697+
7698+ const host = container . querySelector ( ".xterm-host" ) as HTMLDivElement | null ;
7699+ expect ( host ) . toBeTruthy ( ) ;
7700+
7701+ const rowsElement = document . createElement ( "div" ) ;
7702+ rowsElement . className = "xterm-rows" ;
7703+ rowsElement . innerHTML = "<div><span>short</span></div>" ;
7704+ host ! . appendChild ( rowsElement ) ;
7705+ stubRowsGeometry ( host ! , rowsElement , [ rowsElement . firstElementChild as HTMLDivElement ] , {
7706+ rowsRect : { x : 0 , y : 100 , width : 320 , height : 20 } ,
7707+ rowHeight : 20 ,
7708+ } ) ;
7709+
7710+ dispatchTouchEvent ( host ! , "touchstart" , [ { identifier : 1 , clientX : 220 , clientY : 110 } ] ) ;
7711+
7712+ await act ( async ( ) => {
7713+ vi . advanceTimersByTime ( 500 ) ;
7714+ await Promise . resolve ( ) ;
7715+ await Promise . resolve ( ) ;
7716+ } ) ;
7717+
7718+ expect ( writeText ) . not . toHaveBeenCalled ( ) ;
7719+ expect ( vibrate ) . not . toHaveBeenCalled ( ) ;
7720+ expect ( store . get ( toastsAtom ) ) . toEqual ( [ ] ) ;
7721+
7722+ window . matchMedia = originalMatchMedia ;
7723+ vi . useRealTimers ( ) ;
7724+ } ) ;
7725+
76387726 it ( "mobile line copy still succeeds after the row DOM is replaced before long press matures" , async ( ) => {
76397727 vi . useFakeTimers ( ) ;
76407728 viewportMocks . viewport = "mobile" ;
@@ -7643,6 +7731,7 @@ describe("XtermHost", () => {
76437731 const writeText = vi . fn ( ) . mockResolvedValue ( undefined ) ;
76447732 const store = createStore ( ) ;
76457733
7734+ mockTerminal . cols = 80 ;
76467735 mockTerminal . buffer . active . viewportY = 4 ;
76477736 setMockBufferLines ( [ [ 5 , "stable row text" , false ] ] ) ;
76487737
0 commit comments