mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
Fix UTF-8 encoding in ExecaTerminalProcess (#3989)
* Fix UTF-8 encoding issue in integrated terminal - Set LANG and LC_ALL environment variables to en_US.UTF-8 - Resolves Encoding::CompatibilityError in Ruby/CocoaPods commands - Ensures consistent UTF-8 encoding across all terminal sessions This change addresses the terminal encoding issue where commands like 'pod install' would fail due to incompatible character encoding. The fix ensures all integrated terminals are initialized with proper UTF-8 locale settings. * Add comprehensive unit tests for ExecaTerminalProcess UTF-8 encoding fix - Tests verify LANG and LC_ALL are set to en_US.UTF-8 - Tests ensure existing environment variables are preserved - Tests confirm UTF-8 settings override conflicting locale values - Addresses PR feedback requesting test coverage for encoding fix - All 7 tests passing with proper mocking of execa and ps-tree
This commit is contained in:
parent
116a50f7b4
commit
177e7a8eb9
2 changed files with 129 additions and 0 deletions
|
|
@ -40,6 +40,12 @@ export class ExecaTerminalProcess extends BaseTerminalProcess {
|
|||
shell: true,
|
||||
cwd: this.terminal.getCurrentWorkingDirectory(),
|
||||
all: true,
|
||||
env: {
|
||||
...process.env,
|
||||
// Ensure UTF-8 encoding for Ruby, CocoaPods, etc.
|
||||
LANG: "en_US.UTF-8",
|
||||
LC_ALL: "en_US.UTF-8",
|
||||
},
|
||||
})`${command}`
|
||||
|
||||
this.pid = subprocess.pid
|
||||
|
|
|
|||
123
src/integrations/terminal/__tests__/ExecaTerminalProcess.spec.ts
Normal file
123
src/integrations/terminal/__tests__/ExecaTerminalProcess.spec.ts
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
// npx vitest run integrations/terminal/__tests__/ExecaTerminalProcess.spec.ts
|
||||
import { vitest, describe, it, expect, beforeEach, afterEach } from "vitest"
|
||||
|
||||
const mockPid = 12345
|
||||
|
||||
vitest.mock("execa", () => {
|
||||
const mockKill = vitest.fn()
|
||||
const execa = vitest.fn((options: any) => {
|
||||
return (_template: TemplateStringsArray, ...args: any[]) => ({
|
||||
pid: mockPid,
|
||||
iterable: (_opts: any) =>
|
||||
(async function* () {
|
||||
yield "test output\n"
|
||||
})(),
|
||||
kill: mockKill,
|
||||
})
|
||||
})
|
||||
return { execa, ExecaError: class extends Error {} }
|
||||
})
|
||||
|
||||
vitest.mock("ps-tree", () => ({
|
||||
default: vitest.fn((_: number, cb: any) => cb(null, [])),
|
||||
}))
|
||||
|
||||
import { execa } from "execa"
|
||||
import { ExecaTerminalProcess } from "../ExecaTerminalProcess"
|
||||
import type { RooTerminal } from "../types"
|
||||
|
||||
describe("ExecaTerminalProcess", () => {
|
||||
let mockTerminal: RooTerminal
|
||||
let terminalProcess: ExecaTerminalProcess
|
||||
let originalEnv: NodeJS.ProcessEnv
|
||||
|
||||
beforeEach(() => {
|
||||
originalEnv = { ...process.env }
|
||||
mockTerminal = {
|
||||
provider: "execa",
|
||||
id: 1,
|
||||
busy: false,
|
||||
running: false,
|
||||
getCurrentWorkingDirectory: vitest.fn().mockReturnValue("/test/cwd"),
|
||||
isClosed: vitest.fn().mockReturnValue(false),
|
||||
runCommand: vitest.fn(),
|
||||
setActiveStream: vitest.fn(),
|
||||
shellExecutionComplete: vitest.fn(),
|
||||
getProcessesWithOutput: vitest.fn().mockReturnValue([]),
|
||||
getUnretrievedOutput: vitest.fn().mockReturnValue(""),
|
||||
getLastCommand: vitest.fn().mockReturnValue(""),
|
||||
cleanCompletedProcessQueue: vitest.fn(),
|
||||
} as unknown as RooTerminal
|
||||
terminalProcess = new ExecaTerminalProcess(mockTerminal)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
process.env = originalEnv
|
||||
vitest.clearAllMocks()
|
||||
})
|
||||
|
||||
describe("UTF-8 encoding fix", () => {
|
||||
it("should set LANG and LC_ALL to en_US.UTF-8", async () => {
|
||||
await terminalProcess.run("echo test")
|
||||
const execaMock = vitest.mocked(execa)
|
||||
expect(execaMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
shell: true,
|
||||
cwd: "/test/cwd",
|
||||
all: true,
|
||||
env: expect.objectContaining({
|
||||
LANG: "en_US.UTF-8",
|
||||
LC_ALL: "en_US.UTF-8",
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
it("should preserve existing environment variables", async () => {
|
||||
process.env.EXISTING_VAR = "existing"
|
||||
terminalProcess = new ExecaTerminalProcess(mockTerminal)
|
||||
await terminalProcess.run("echo test")
|
||||
const execaMock = vitest.mocked(execa)
|
||||
const calledOptions = execaMock.mock.calls[0][0] as any
|
||||
expect(calledOptions.env.EXISTING_VAR).toBe("existing")
|
||||
})
|
||||
|
||||
it("should override existing LANG and LC_ALL values", async () => {
|
||||
process.env.LANG = "C"
|
||||
process.env.LC_ALL = "POSIX"
|
||||
terminalProcess = new ExecaTerminalProcess(mockTerminal)
|
||||
await terminalProcess.run("echo test")
|
||||
const execaMock = vitest.mocked(execa)
|
||||
const calledOptions = execaMock.mock.calls[0][0] as any
|
||||
expect(calledOptions.env.LANG).toBe("en_US.UTF-8")
|
||||
expect(calledOptions.env.LC_ALL).toBe("en_US.UTF-8")
|
||||
})
|
||||
})
|
||||
|
||||
describe("basic functionality", () => {
|
||||
it("should create instance with terminal reference", () => {
|
||||
expect(terminalProcess).toBeInstanceOf(ExecaTerminalProcess)
|
||||
expect(terminalProcess.terminal).toBe(mockTerminal)
|
||||
})
|
||||
|
||||
it("should emit shell_execution_complete with exitCode 0", async () => {
|
||||
const spy = vitest.fn()
|
||||
terminalProcess.on("shell_execution_complete", spy)
|
||||
await terminalProcess.run("echo test")
|
||||
expect(spy).toHaveBeenCalledWith({ exitCode: 0 })
|
||||
})
|
||||
|
||||
it("should emit completed event with full output", async () => {
|
||||
const spy = vitest.fn()
|
||||
terminalProcess.on("completed", spy)
|
||||
await terminalProcess.run("echo test")
|
||||
expect(spy).toHaveBeenCalledWith("test output\n")
|
||||
})
|
||||
|
||||
it("should set and clear active stream", async () => {
|
||||
await terminalProcess.run("echo test")
|
||||
expect(mockTerminal.setActiveStream).toHaveBeenCalledWith(expect.any(Object), mockPid)
|
||||
expect(mockTerminal.setActiveStream).toHaveBeenLastCalledWith(undefined)
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
Add table
Reference in a new issue