@@ -10,6 +10,7 @@ const state = vi.hoisted(() => ({
1010 options : Record < string , unknown > ;
1111 modes : { bracketedPasteMode : boolean } ;
1212 dataListeners : Set < ( data : string ) => void > ;
13+ keyListeners : Set < ( event : { key : string } ) => void > ;
1314 selectionListeners : Set < ( ) => void > ;
1415 _core : {
1516 element : { classList : { add : ReturnType < typeof vi . fn > ; remove : ReturnType < typeof vi . fn > } } ;
@@ -31,6 +32,7 @@ vi.mock("@xterm/xterm", () => ({
3132 wheelHandler ?: ( event : WheelEvent ) => boolean ;
3233 modes = { bracketedPasteMode : false } ;
3334 dataListeners = new Set < ( data : string ) => void > ( ) ;
35+ keyListeners = new Set < ( event : { key : string } ) => void > ( ) ;
3436 selectionListeners = new Set < ( ) => void > ( ) ;
3537 _core = {
3638 element : { classList : { add : vi . fn ( ) , remove : vi . fn ( ) } } ,
@@ -62,8 +64,9 @@ vi.mock("@xterm/xterm", () => ({
6264 onRender ( ) {
6365 return { dispose : ( ) => undefined } ;
6466 }
65- onKey ( ) {
66- return { dispose : ( ) => undefined } ;
67+ onKey ( listener : ( event : { key : string } ) => void ) {
68+ this . keyListeners . add ( listener ) ;
69+ return { dispose : ( ) => this . keyListeners . delete ( listener ) } ;
6770 }
6871 onSelectionChange ( listener : ( ) => void ) {
6972 this . selectionListeners . add ( listener ) ;
@@ -359,16 +362,25 @@ describe("XtermTerminal", () => {
359362 expect ( allowed ) . toBe ( false ) ;
360363 expect ( event . preventDefault ) . toHaveBeenCalled ( ) ;
361364 expect ( event . stopPropagation ) . toHaveBeenCalled ( ) ;
362- expect ( onInput ) . toHaveBeenCalledWith ( expected , "terminal " ) ;
365+ expect ( onInput ) . toHaveBeenCalledWith ( expected , "shortcut " ) ;
363366 } ) ;
364367
365- it ( "forwards generated xterm input data such as wheel scroll reports " , ( ) => {
368+ it ( "forwards keyboard input from explicit key events " , ( ) => {
366369 const onInput = vi . fn ( ) ;
367370 render ( < XtermTerminal theme = "dark" onReady = { ( terminal ) => terminal . onUserInput ( onInput ) } /> ) ;
368371
369- state . lastTerminal ! . dataListeners . forEach ( ( listener ) => listener ( "\x1b[A" ) ) ;
372+ state . lastTerminal ! . keyListeners . forEach ( ( listener ) => listener ( { key : "a" } ) ) ;
370373
371- expect ( onInput ) . toHaveBeenCalledWith ( "\x1b[A" , "terminal" ) ;
374+ expect ( onInput ) . toHaveBeenCalledWith ( "a" , "keyboard" ) ;
375+ } ) ;
376+
377+ it ( "does not forward raw xterm data/control bytes as user input" , ( ) => {
378+ const onInput = vi . fn ( ) ;
379+ render ( < XtermTerminal theme = "dark" onReady = { ( terminal ) => terminal . onUserInput ( onInput ) } /> ) ;
380+
381+ expect ( state . lastTerminal ! . dataListeners . size ) . toBe ( 0 ) ;
382+ state . lastTerminal ! . dataListeners . forEach ( ( listener ) => listener ( "\x1b[A" ) ) ;
383+ expect ( onInput ) . not . toHaveBeenCalled ( ) ;
372384 } ) ;
373385
374386 it ( "translates wheel motion into SGR wheel reports for zellij scrollback" , ( ) => {
@@ -378,7 +390,7 @@ describe("XtermTerminal", () => {
378390 const suppressed = state . lastTerminal ! . wheelHandler ! ( { deltaY : - 50 } as WheelEvent ) ;
379391
380392 expect ( suppressed ) . toBe ( false ) ;
381- expect ( onInput ) . toHaveBeenCalledWith ( "\x1b[<64;1;1M\x1b[<64;1;1M\x1b[<64;1;1M" , "terminal " ) ;
393+ expect ( onInput ) . toHaveBeenCalledWith ( "\x1b[<64;1;1M\x1b[<64;1;1M\x1b[<64;1;1M" , "wheel " ) ;
382394 } ) ;
383395
384396 it ( "handles line- and page-mode wheels (Linux/Windows mice), not just pixel deltas" , ( ) => {
@@ -387,27 +399,27 @@ describe("XtermTerminal", () => {
387399
388400 // DOM_DELTA_LINE: deltaY is already in lines, so one notch up => one report.
389401 expect ( state . lastTerminal ! . wheelHandler ! ( { deltaY : - 1 , deltaMode : 1 } as WheelEvent ) ) . toBe ( false ) ;
390- expect ( onInput ) . toHaveBeenLastCalledWith ( "\x1b[<64;1;1M" , "terminal " ) ;
402+ expect ( onInput ) . toHaveBeenLastCalledWith ( "\x1b[<64;1;1M" , "wheel " ) ;
391403
392404 // DOM_DELTA_PAGE: one page down => rows (24) line reports down.
393405 onInput . mockClear ( ) ;
394406 expect ( state . lastTerminal ! . wheelHandler ! ( { deltaY : 1 , deltaMode : 2 } as WheelEvent ) ) . toBe ( false ) ;
395- expect ( onInput ) . toHaveBeenLastCalledWith ( "\x1b[<65;1;1M" . repeat ( 24 ) , "terminal " ) ;
407+ expect ( onInput ) . toHaveBeenLastCalledWith ( "\x1b[<65;1;1M" . repeat ( 24 ) , "wheel " ) ;
396408 } ) ;
397409
398410 it ( "scrolls down on positive wheel delta and leaves zoom (ctrl/meta) wheel alone" , ( ) => {
399411 const onInput = vi . fn ( ) ;
400412 render ( < XtermTerminal theme = "dark" onReady = { ( terminal ) => terminal . onUserInput ( onInput ) } /> ) ;
401413
402414 expect ( state . lastTerminal ! . wheelHandler ! ( { deltaY : 20 } as WheelEvent ) ) . toBe ( false ) ;
403- expect ( onInput ) . toHaveBeenCalledWith ( "\x1b[<65;1;1M" , "terminal " ) ;
415+ expect ( onInput ) . toHaveBeenCalledWith ( "\x1b[<65;1;1M" , "wheel " ) ;
404416
405417 onInput . mockClear ( ) ;
406418 expect ( state . lastTerminal ! . wheelHandler ! ( { deltaY : - 50 , ctrlKey : true } as WheelEvent ) ) . toBe ( false ) ;
407419 expect ( onInput ) . not . toHaveBeenCalled ( ) ;
408420 } ) ;
409421
410- it ( "forces plain drag selection while preserving xterm data forwarding" , ( ) => {
422+ it ( "forces plain drag selection without raw xterm data forwarding" , ( ) => {
411423 render ( < XtermTerminal theme = "dark" /> ) ;
412424
413425 expect ( state . lastTerminal ! . options . macOptionClickForcesSelection ) . toBe ( true ) ;
0 commit comments