11// npx vitest run integrations/terminal/__tests__/ExecaTerminalProcess.spec.ts
22
33const mockPid = 12345
4+ let mockExitCode = 0
5+ let mockShouldThrow = false
6+ let mockErrorSignal : string | undefined
47
58vitest . mock ( "execa" , ( ) => {
69 const mockKill = vitest . fn ( )
10+ class MockExecaError extends Error {
11+ exitCode : number
12+ signal ?: string
13+ constructor ( message : string , exitCode : number , signal ?: string ) {
14+ super ( message )
15+ this . exitCode = exitCode
16+ this . signal = signal
17+ }
18+ }
719 const execa = vitest . fn ( ( options : any ) => {
8- return ( _template : TemplateStringsArray , ...args : any [ ] ) => ( {
9- pid : mockPid ,
10- iterable : ( _opts : any ) =>
11- ( async function * ( ) {
12- yield "test output\n"
13- } ) ( ) ,
14- kill : mockKill ,
15- } )
20+ return ( _template : TemplateStringsArray , ...args : any [ ] ) => {
21+ // Create a promise that resolves/rejects based on mockShouldThrow
22+ const resultPromise = new Promise ( ( resolve , reject ) => {
23+ // Use setImmediate to allow the iterable to be consumed first
24+ setImmediate ( ( ) => {
25+ if ( mockShouldThrow ) {
26+ reject ( new MockExecaError ( "Command failed" , mockExitCode , mockErrorSignal ) )
27+ } else {
28+ resolve ( { exitCode : mockExitCode } )
29+ }
30+ } )
31+ } )
32+
33+ return Object . assign ( resultPromise , {
34+ pid : mockPid ,
35+ iterable : ( _opts : any ) =>
36+ ( async function * ( ) {
37+ yield "test output\n"
38+ } ) ( ) ,
39+ kill : mockKill ,
40+ } )
41+ }
1642 } )
17- return { execa, ExecaError : class extends Error { } }
43+ return { execa, ExecaError : MockExecaError }
1844} )
1945
2046vitest . mock ( "ps-tree" , ( ) => ( {
@@ -31,6 +57,11 @@ describe("ExecaTerminalProcess", () => {
3157 let originalEnv : NodeJS . ProcessEnv
3258
3359 beforeEach ( ( ) => {
60+ // Reset mock state
61+ mockExitCode = 0
62+ mockShouldThrow = false
63+ mockErrorSignal = undefined
64+
3465 originalEnv = { ...process . env }
3566 mockTerminal = {
3667 provider : "execa" ,
@@ -163,4 +194,40 @@ describe("ExecaTerminalProcess", () => {
163194 expect ( terminalProcess [ "lastRetrievedIndex" ] ) . toBe ( 0 )
164195 } )
165196 } )
197+
198+ describe ( "exit code handling" , ( ) => {
199+ it ( "should emit shell_execution_complete with non-zero exit code when command fails" , async ( ) => {
200+ mockExitCode = 1
201+ const spy = vitest . fn ( )
202+ terminalProcess . on ( "shell_execution_complete" , spy )
203+ await terminalProcess . run ( "exit 1" )
204+ expect ( spy ) . toHaveBeenCalledWith ( { exitCode : 1 } )
205+ } )
206+
207+ it ( "should emit shell_execution_complete with specific non-zero exit code" , async ( ) => {
208+ mockExitCode = 127
209+ const spy = vitest . fn ( )
210+ terminalProcess . on ( "shell_execution_complete" , spy )
211+ await terminalProcess . run ( "nonexistent_command" )
212+ expect ( spy ) . toHaveBeenCalledWith ( { exitCode : 127 } )
213+ } )
214+
215+ it ( "should handle ExecaError thrown during await with exit code and signal" , async ( ) => {
216+ mockShouldThrow = true
217+ mockExitCode = 128
218+ mockErrorSignal = "SIGKILL"
219+ const spy = vitest . fn ( )
220+ terminalProcess . on ( "shell_execution_complete" , spy )
221+ await terminalProcess . run ( "killed_command" )
222+ expect ( spy ) . toHaveBeenCalledWith ( { exitCode : 128 , signalName : "SIGKILL" } )
223+ } )
224+
225+ it ( "should emit exitCode 0 when command succeeds" , async ( ) => {
226+ mockExitCode = 0
227+ const spy = vitest . fn ( )
228+ terminalProcess . on ( "shell_execution_complete" , spy )
229+ await terminalProcess . run ( "echo success" )
230+ expect ( spy ) . toHaveBeenCalledWith ( { exitCode : 0 } )
231+ } )
232+ } )
166233} )
0 commit comments