|
| 1 | +// npx vitest run integrations/terminal/__tests__/ExecaTerminalProcess.spec.ts |
| 2 | +import { vitest, describe, it, expect, beforeEach, afterEach } from "vitest" |
| 3 | + |
| 4 | +const mockPid = 12345 |
| 5 | + |
| 6 | +vitest.mock("execa", () => { |
| 7 | + const mockKill = vitest.fn() |
| 8 | + const execa = vitest.fn((options: any) => { |
| 9 | + return (_template: TemplateStringsArray, ...args: any[]) => ({ |
| 10 | + pid: mockPid, |
| 11 | + iterable: (_opts: any) => |
| 12 | + (async function* () { |
| 13 | + yield "test output\n" |
| 14 | + })(), |
| 15 | + kill: mockKill, |
| 16 | + }) |
| 17 | + }) |
| 18 | + return { execa, ExecaError: class extends Error {} } |
| 19 | +}) |
| 20 | + |
| 21 | +vitest.mock("ps-tree", () => ({ |
| 22 | + default: vitest.fn((_: number, cb: any) => cb(null, [])), |
| 23 | +})) |
| 24 | + |
| 25 | +import { execa } from "execa" |
| 26 | +import { ExecaTerminalProcess } from "../ExecaTerminalProcess" |
| 27 | +import type { RooTerminal } from "../types" |
| 28 | + |
| 29 | +describe("ExecaTerminalProcess", () => { |
| 30 | + let mockTerminal: RooTerminal |
| 31 | + let terminalProcess: ExecaTerminalProcess |
| 32 | + let originalEnv: NodeJS.ProcessEnv |
| 33 | + |
| 34 | + beforeEach(() => { |
| 35 | + originalEnv = { ...process.env } |
| 36 | + mockTerminal = { |
| 37 | + provider: "execa", |
| 38 | + id: 1, |
| 39 | + busy: false, |
| 40 | + running: false, |
| 41 | + getCurrentWorkingDirectory: vitest.fn().mockReturnValue("/test/cwd"), |
| 42 | + isClosed: vitest.fn().mockReturnValue(false), |
| 43 | + runCommand: vitest.fn(), |
| 44 | + setActiveStream: vitest.fn(), |
| 45 | + shellExecutionComplete: vitest.fn(), |
| 46 | + getProcessesWithOutput: vitest.fn().mockReturnValue([]), |
| 47 | + getUnretrievedOutput: vitest.fn().mockReturnValue(""), |
| 48 | + getLastCommand: vitest.fn().mockReturnValue(""), |
| 49 | + cleanCompletedProcessQueue: vitest.fn(), |
| 50 | + } as unknown as RooTerminal |
| 51 | + terminalProcess = new ExecaTerminalProcess(mockTerminal) |
| 52 | + }) |
| 53 | + |
| 54 | + afterEach(() => { |
| 55 | + process.env = originalEnv |
| 56 | + vitest.clearAllMocks() |
| 57 | + }) |
| 58 | + |
| 59 | + describe("UTF-8 encoding fix", () => { |
| 60 | + it("should set LANG and LC_ALL to en_US.UTF-8", async () => { |
| 61 | + await terminalProcess.run("echo test") |
| 62 | + const execaMock = vitest.mocked(execa) |
| 63 | + expect(execaMock).toHaveBeenCalledWith( |
| 64 | + expect.objectContaining({ |
| 65 | + shell: true, |
| 66 | + cwd: "/test/cwd", |
| 67 | + all: true, |
| 68 | + env: expect.objectContaining({ |
| 69 | + LANG: "en_US.UTF-8", |
| 70 | + LC_ALL: "en_US.UTF-8", |
| 71 | + }), |
| 72 | + }), |
| 73 | + ) |
| 74 | + }) |
| 75 | + |
| 76 | + it("should preserve existing environment variables", async () => { |
| 77 | + process.env.EXISTING_VAR = "existing" |
| 78 | + terminalProcess = new ExecaTerminalProcess(mockTerminal) |
| 79 | + await terminalProcess.run("echo test") |
| 80 | + const execaMock = vitest.mocked(execa) |
| 81 | + const calledOptions = execaMock.mock.calls[0][0] as any |
| 82 | + expect(calledOptions.env.EXISTING_VAR).toBe("existing") |
| 83 | + }) |
| 84 | + |
| 85 | + it("should override existing LANG and LC_ALL values", async () => { |
| 86 | + process.env.LANG = "C" |
| 87 | + process.env.LC_ALL = "POSIX" |
| 88 | + terminalProcess = new ExecaTerminalProcess(mockTerminal) |
| 89 | + await terminalProcess.run("echo test") |
| 90 | + const execaMock = vitest.mocked(execa) |
| 91 | + const calledOptions = execaMock.mock.calls[0][0] as any |
| 92 | + expect(calledOptions.env.LANG).toBe("en_US.UTF-8") |
| 93 | + expect(calledOptions.env.LC_ALL).toBe("en_US.UTF-8") |
| 94 | + }) |
| 95 | + }) |
| 96 | + |
| 97 | + describe("basic functionality", () => { |
| 98 | + it("should create instance with terminal reference", () => { |
| 99 | + expect(terminalProcess).toBeInstanceOf(ExecaTerminalProcess) |
| 100 | + expect(terminalProcess.terminal).toBe(mockTerminal) |
| 101 | + }) |
| 102 | + |
| 103 | + it("should emit shell_execution_complete with exitCode 0", async () => { |
| 104 | + const spy = vitest.fn() |
| 105 | + terminalProcess.on("shell_execution_complete", spy) |
| 106 | + await terminalProcess.run("echo test") |
| 107 | + expect(spy).toHaveBeenCalledWith({ exitCode: 0 }) |
| 108 | + }) |
| 109 | + |
| 110 | + it("should emit completed event with full output", async () => { |
| 111 | + const spy = vitest.fn() |
| 112 | + terminalProcess.on("completed", spy) |
| 113 | + await terminalProcess.run("echo test") |
| 114 | + expect(spy).toHaveBeenCalledWith("test output\n") |
| 115 | + }) |
| 116 | + |
| 117 | + it("should set and clear active stream", async () => { |
| 118 | + await terminalProcess.run("echo test") |
| 119 | + expect(mockTerminal.setActiveStream).toHaveBeenCalledWith(expect.any(Object), mockPid) |
| 120 | + expect(mockTerminal.setActiveStream).toHaveBeenLastCalledWith(undefined) |
| 121 | + }) |
| 122 | + }) |
| 123 | +}) |
0 commit comments