@@ -17,43 +17,54 @@ vi.mock('open', () => ({
1717} ) )
1818
1919vi . mock ( 'shelljs' , ( ) => ( {
20- exec : vi . fn ( ) . mockImplementation ( ( cmd , options , callback ) => {
21- if ( cmd . includes ( 'git remote get-url' ) ) {
22- return { stdout : 'https://github.com/user/repo.git' }
23- }
20+ default : {
21+ exec : vi . fn ( ) . mockImplementation ( ( cmd , options , callback ) => {
22+ if ( cmd . includes ( 'git remote get-url' ) ) {
23+ return { stdout : 'https://github.com/user/repo.git' }
24+ }
2425
25- // Handle async exec for git push
26- if ( options && options . async ) {
27- const mockChild = {
28- stdout : {
26+ // Handle async exec for git push
27+ if ( options && options . async ) {
28+ // Create a mock child process that can be killed and emits exit afterwards
29+ let killed = false
30+ const listeners = { exit : [ ] }
31+ const mockChild = {
32+ stdout : {
33+ on : vi . fn ( ( event , handler ) => {
34+ if ( event === 'data' ) {
35+ setTimeout ( ( ) => handler ( Buffer . from ( 'Everything up-to-date\n' ) ) , 10 )
36+ }
37+ } )
38+ } ,
39+ stderr : {
40+ on : vi . fn ( ( event , handler ) => {
41+ // no stderr by default
42+ } )
43+ } ,
2944 on : vi . fn ( ( event , handler ) => {
30- if ( event === 'data' ) {
31- // Simulate some stdout data
32- setTimeout ( ( ) => handler ( Buffer . from ( 'Everything up-to-date\n' ) ) , 10 )
45+ if ( event === 'exit' ) {
46+ listeners . exit . push ( handler )
47+ // Simulate successful exit after a short delay unless killed
48+ setTimeout ( ( ) => {
49+ const code = killed ? 1 : 0
50+ listeners . exit . forEach ( ( cb ) => cb ( code ) )
51+ } , 20 )
3352 }
53+ } ) ,
54+ kill : vi . fn ( ( ) => {
55+ killed = true
3456 } )
35- } ,
36- stderr : {
37- on : vi . fn ( ( event , handler ) => {
38- // No stderr by default
39- } )
40- } ,
41- on : vi . fn ( ( event , handler ) => {
42- if ( event === 'exit' ) {
43- // Simulate successful exit
44- setTimeout ( ( ) => handler ( 0 ) , 20 )
45- }
46- } )
57+ }
58+ return mockChild
4759 }
48- return mockChild
49- }
5060
51- if ( callback ) {
52- callback ( 0 , 'success' , '' )
53- }
54- return { code : 0 , stdout : 'success' , stderr : '' }
55- } ) ,
56- which : vi . fn ( ) . mockReturnValue ( true )
61+ if ( callback ) {
62+ callback ( 0 , 'success' , '' )
63+ }
64+ return { code : 0 , stdout : 'success' , stderr : '' }
65+ } ) ,
66+ which : vi . fn ( ) . mockReturnValue ( true )
67+ }
5768} ) )
5869
5970vi . mock ( 'git-branch' , ( ) => ( {
@@ -66,7 +77,8 @@ console.log = vi.fn()
6677import ora from 'ora'
6778import open from 'open'
6879import gitPushPR , { getPullRequestUrl } from './index.js'
69- import { exec , which } from 'shelljs'
80+ import shell from 'shelljs'
81+ const { exec, which } = shell
7082import branch from 'git-branch'
7183
7284let exitSpy
@@ -161,6 +173,21 @@ describe('gitPushPR', () => {
161173 await promise
162174 } )
163175
176+ it ( 'should gracefully abort on SIGINT (Ctrl+C)' , async ( ) => {
177+ // Start the process
178+ const promise = gitPushPR ( { remote : 'origin' } )
179+
180+ // Emit SIGINT shortly after to simulate Ctrl+C
181+ setTimeout ( ( ) => process . emit ( 'SIGINT' ) , 5 )
182+
183+ // Wait to allow the mocked child to exit and our handler to run
184+ await new Promise ( ( resolve ) => setTimeout ( resolve , 200 ) )
185+
186+ // Since we no longer call process.exit on abort, just assert no PR was opened and spinner failed path was taken
187+ expect ( open ) . not . toHaveBeenCalled ( )
188+ await promise
189+ } )
190+
164191 const expectExitWithError = async ( fn ) => {
165192 let error
166193 try {
0 commit comments