@@ -392,17 +392,84 @@ describe('editor utils', () => {
392392 ) ;
393393 } ) ;
394394
395- it ( `should reject if ${ editor } exits with non-zero code` , async ( ) => {
395+ it ( `should resolve and log warning if ${ editor } exits with non-zero code` , async ( ) => {
396+ const warnSpy = vi
397+ . spyOn ( debugLogger , 'warn' )
398+ . mockImplementation ( ( ) => { } ) ;
396399 const mockSpawnOn = vi . fn ( ( event , cb ) => {
397400 if ( event === 'close' ) {
398401 cb ( 1 ) ;
399402 }
400403 } ) ;
401404 ( spawn as Mock ) . mockReturnValue ( { on : mockSpawnOn } ) ;
402405
406+ await openDiff ( 'old.txt' , 'new.txt' , editor ) ;
407+ expect ( warnSpy ) . toHaveBeenCalledWith ( `${ editor } exited with code 1` ) ;
408+ } ) ;
409+
410+ it ( `should emit ExternalEditorClosed when ${ editor } exits successfully` , async ( ) => {
411+ const emitSpy = vi . spyOn ( coreEvents , 'emit' ) ;
412+ const mockSpawnOn = vi . fn ( ( event , cb ) => {
413+ if ( event === 'close' ) {
414+ cb ( 0 ) ;
415+ }
416+ } ) ;
417+ ( spawn as Mock ) . mockReturnValue ( { on : mockSpawnOn } ) ;
418+
419+ await openDiff ( 'old.txt' , 'new.txt' , editor ) ;
420+ expect ( emitSpy ) . toHaveBeenCalledWith ( CoreEvent . ExternalEditorClosed ) ;
421+ } ) ;
422+
423+ it ( `should emit ExternalEditorClosed when ${ editor } exits with non-zero code` , async ( ) => {
424+ vi . spyOn ( debugLogger , 'warn' ) . mockImplementation ( ( ) => { } ) ;
425+ const emitSpy = vi . spyOn ( coreEvents , 'emit' ) ;
426+ const mockSpawnOn = vi . fn ( ( event , cb ) => {
427+ if ( event === 'close' ) {
428+ cb ( 1 ) ;
429+ }
430+ } ) ;
431+ ( spawn as Mock ) . mockReturnValue ( { on : mockSpawnOn } ) ;
432+
433+ await openDiff ( 'old.txt' , 'new.txt' , editor ) ;
434+ expect ( emitSpy ) . toHaveBeenCalledWith ( CoreEvent . ExternalEditorClosed ) ;
435+ } ) ;
436+
437+ it ( `should emit ExternalEditorClosed when ${ editor } spawn errors` , async ( ) => {
438+ const emitSpy = vi . spyOn ( coreEvents , 'emit' ) ;
439+ const mockError = new Error ( 'spawn error' ) ;
440+ const mockSpawnOn = vi . fn ( ( event , cb ) => {
441+ if ( event === 'error' ) {
442+ cb ( mockError ) ;
443+ }
444+ } ) ;
445+ ( spawn as Mock ) . mockReturnValue ( { on : mockSpawnOn } ) ;
446+
403447 await expect ( openDiff ( 'old.txt' , 'new.txt' , editor ) ) . rejects . toThrow (
404- `${ editor } exited with code 1` ,
448+ 'spawn error' ,
449+ ) ;
450+ expect ( emitSpy ) . toHaveBeenCalledWith ( CoreEvent . ExternalEditorClosed ) ;
451+ } ) ;
452+
453+ it ( `should only emit ExternalEditorClosed once when ${ editor } fires both error and close` , async ( ) => {
454+ const emitSpy = vi . spyOn ( coreEvents , 'emit' ) ;
455+ const callbacks : Record < string , ( arg : unknown ) => void > = { } ;
456+ const mockSpawnOn = vi . fn (
457+ ( event : string , cb : ( arg : unknown ) => void ) => {
458+ callbacks [ event ] = cb ;
459+ } ,
460+ ) ;
461+ ( spawn as Mock ) . mockReturnValue ( { on : mockSpawnOn } ) ;
462+
463+ const promise = openDiff ( 'old.txt' , 'new.txt' , editor ) ;
464+ // Simulate Node.js behavior: error fires first, then close.
465+ callbacks [ 'error' ] ( new Error ( 'spawn error' ) ) ;
466+ callbacks [ 'close' ] ( 1 ) ;
467+
468+ await expect ( promise ) . rejects . toThrow ( 'spawn error' ) ;
469+ const editorClosedEmissions = emitSpy . mock . calls . filter (
470+ ( call ) => call [ 0 ] === CoreEvent . ExternalEditorClosed ,
405471 ) ;
472+ expect ( editorClosedEmissions ) . toHaveLength ( 1 ) ;
406473 } ) ;
407474 }
408475
0 commit comments