@@ -47,6 +47,33 @@ function resetFakeTimers(): void {
4747 mock . timers . reset ( ) ;
4848}
4949
50+ type TerminalStopHarness = {
51+ terminal : ProcessTerminal ;
52+ cleanup ( ) : void ;
53+ } ;
54+
55+ function setupTerminalStopHarness ( wasRaw : boolean , restoreRawMode : ( mode : boolean ) => void ) : TerminalStopHarness {
56+ const terminal = new ProcessTerminal ( ) ;
57+ const previousPause = process . stdin . pause ;
58+ const previousSetRawMode = process . stdin . setRawMode ;
59+
60+ Reflect . set ( terminal , "wasRaw" , wasRaw ) ;
61+ Reflect . set ( terminal , "rawStdoutWrite" , ( _data : string ) => { } ) ;
62+ Reflect . set ( process . stdin , "pause" , ( ) => process . stdin ) ;
63+ Reflect . set ( process . stdin , "setRawMode" , ( mode : boolean ) => {
64+ restoreRawMode ( mode ) ;
65+ return process . stdin ;
66+ } ) ;
67+
68+ return {
69+ terminal,
70+ cleanup ( ) : void {
71+ Reflect . set ( process . stdin , "pause" , previousPause ) ;
72+ Reflect . set ( process . stdin , "setRawMode" , previousSetRawMode ) ;
73+ } ,
74+ } ;
75+ }
76+
5077describe ( "normalizeAppleTerminalInput" , ( ) => {
5178 it ( "rewrites Apple Terminal Return to CSI-u Shift+Enter when Shift is pressed" , ( ) => {
5279 assert . equal ( normalizeAppleTerminalInput ( "\r" , true , true ) , "\x1b[13;2u" ) ;
@@ -295,6 +322,59 @@ describe("ProcessTerminal Kitty keyboard protocol negotiation", () => {
295322 } ) ;
296323} ) ;
297324
325+ describe ( "ProcessTerminal stop" , ( ) => {
326+ it ( "restores the previous raw mode during stop" , ( ) => {
327+ // Given
328+ const restoredModes : boolean [ ] = [ ] ;
329+ const harness = setupTerminalStopHarness ( true , ( mode ) => {
330+ restoredModes . push ( mode ) ;
331+ } ) ;
332+
333+ try {
334+ // When
335+ harness . terminal . stop ( ) ;
336+
337+ // Then
338+ assert . deepEqual ( restoredModes , [ true ] ) ;
339+ } finally {
340+ harness . cleanup ( ) ;
341+ }
342+ } ) ;
343+
344+ it ( "does not throw when raw-mode restoration fails during stop" , ( ) => {
345+ // Given
346+ const eio = Object . assign ( new Error ( "setRawMode failed" ) , { code : "EIO" } ) ;
347+ const harness = setupTerminalStopHarness ( false , ( ) => {
348+ throw eio ;
349+ } ) ;
350+
351+ try {
352+ // When / Then
353+ assert . doesNotThrow ( ( ) => harness . terminal . stop ( ) ) ;
354+ } finally {
355+ harness . cleanup ( ) ;
356+ }
357+ } ) ;
358+
359+ it ( "rethrows unexpected raw-mode restoration failures" , ( ) => {
360+ // Given
361+ const invalidArgument = Object . assign ( new Error ( "unexpected setRawMode failure" ) , { code : "EINVAL" } ) ;
362+ const harness = setupTerminalStopHarness ( false , ( ) => {
363+ throw invalidArgument ;
364+ } ) ;
365+
366+ try {
367+ // When / Then
368+ assert . throws (
369+ ( ) => harness . terminal . stop ( ) ,
370+ ( error : unknown ) => error === invalidArgument ,
371+ ) ;
372+ } finally {
373+ harness . cleanup ( ) ;
374+ }
375+ } ) ;
376+ } ) ;
377+
298378describe ( "ProcessTerminal dimensions" , ( ) => {
299379 it ( "falls back to COLUMNS and LINES before default dimensions" , ( ) => {
300380 const previousColumnsDescriptor = Object . getOwnPropertyDescriptor ( process . stdout , "columns" ) ;
0 commit comments