Roo-Code/src/utils/__tests__/fileEncoding.spec.ts
Roo Code 57f07a8756 feat: add support for VSCode file encodings
- Add iconv-lite dependency for encoding conversion
- Create src/utils/fileEncoding.ts utility that:
  - Gets file encoding from VSCode settings (files.encoding)
  - Provides readFileWithEncoding() and writeFileWithEncoding() functions
  - Includes fallback to UTF-8 if encoding conversion fails
- Update DiffViewProvider.ts to use encoding-aware file operations
- Update ApplyPatchTool.ts to use encoding-aware file operations
- Add comprehensive tests for the encoding utility

Fixes #11002
2026-01-28 10:17:41 +00:00

272 lines
8.6 KiB
TypeScript

import * as vscode from "vscode"
import * as fs from "fs/promises"
import * as iconv from "iconv-lite"
import {
getFileEncoding,
normalizeEncoding,
isEncodingSupported,
readFileWithEncoding,
writeFileWithEncoding,
} from "../fileEncoding"
// Mock vscode module
vi.mock("vscode", () => ({
workspace: {
getConfiguration: vi.fn(),
},
Uri: {
file: vi.fn((path: string) => ({ fsPath: path })),
},
}))
// Mock fs/promises module
vi.mock("fs/promises", () => ({
default: {
readFile: vi.fn(),
writeFile: vi.fn(),
},
readFile: vi.fn(),
writeFile: vi.fn(),
}))
// Mock iconv-lite module
vi.mock("iconv-lite", () => ({
default: {
encodingExists: vi.fn(),
decode: vi.fn(),
encode: vi.fn(),
},
encodingExists: vi.fn(),
decode: vi.fn(),
encode: vi.fn(),
}))
describe("fileEncoding", () => {
const mockedVscode = vi.mocked(vscode)
const mockedFs = vi.mocked(fs)
const mockedIconv = vi.mocked(iconv)
beforeEach(() => {
vi.clearAllMocks()
})
describe("getFileEncoding", () => {
it("should return the configured encoding from VSCode settings", () => {
const mockConfig = {
get: vi.fn().mockReturnValue("cp852"),
}
mockedVscode.workspace.getConfiguration = vi.fn().mockReturnValue(mockConfig)
const encoding = getFileEncoding("/path/to/file.txt")
expect(mockedVscode.Uri.file).toHaveBeenCalledWith("/path/to/file.txt")
expect(mockedVscode.workspace.getConfiguration).toHaveBeenCalledWith("files", {
fsPath: "/path/to/file.txt",
})
expect(mockConfig.get).toHaveBeenCalledWith("encoding", "utf8")
expect(encoding).toBe("cp852")
})
it("should return utf8 as default if no encoding is configured", () => {
const mockConfig = {
get: vi.fn().mockReturnValue("utf8"),
}
mockedVscode.workspace.getConfiguration = vi.fn().mockReturnValue(mockConfig)
const encoding = getFileEncoding("/path/to/file.txt")
expect(encoding).toBe("utf8")
})
})
describe("normalizeEncoding", () => {
it("should normalize utf-8 to utf8", () => {
expect(normalizeEncoding("utf-8")).toBe("utf8")
expect(normalizeEncoding("UTF-8")).toBe("utf8")
})
it("should normalize windows code pages", () => {
expect(normalizeEncoding("windows1252")).toBe("windows1252")
expect(normalizeEncoding("windows-1252")).toBe("windows1252")
})
it("should normalize DOS code pages", () => {
expect(normalizeEncoding("cp852")).toBe("cp852")
expect(normalizeEncoding("CP852")).toBe("cp852")
})
it("should normalize ISO encodings", () => {
expect(normalizeEncoding("iso88591")).toBe("iso88591")
expect(normalizeEncoding("iso-8859-1")).toBe("iso88591")
})
it("should return the original encoding if not in the map", () => {
expect(normalizeEncoding("unknown-encoding")).toBe("unknown-encoding")
})
})
describe("isEncodingSupported", () => {
it("should return true for supported encodings", () => {
mockedIconv.encodingExists = vi.fn().mockReturnValue(true)
expect(isEncodingSupported("utf8")).toBe(true)
expect(mockedIconv.encodingExists).toHaveBeenCalledWith("utf8")
})
it("should return false for unsupported encodings", () => {
mockedIconv.encodingExists = vi.fn().mockReturnValue(false)
expect(isEncodingSupported("unknown")).toBe(false)
expect(mockedIconv.encodingExists).toHaveBeenCalledWith("unknown")
})
})
describe("readFileWithEncoding", () => {
beforeEach(() => {
const mockConfig = {
get: vi.fn().mockReturnValue("utf8"),
}
mockedVscode.workspace.getConfiguration = vi.fn().mockReturnValue(mockConfig)
})
it("should read file with UTF-8 encoding directly", async () => {
const mockBuffer = Buffer.from("Hello World", "utf8")
mockedFs.readFile = vi.fn().mockResolvedValue(mockBuffer)
const result = await readFileWithEncoding("/path/to/file.txt")
expect(result.content).toBe("Hello World")
expect(result.encoding).toBe("utf8")
expect(result.usedFallback).toBe(false)
})
it("should read file with CP852 encoding", async () => {
const mockConfig = {
get: vi.fn().mockReturnValue("cp852"),
}
mockedVscode.workspace.getConfiguration = vi.fn().mockReturnValue(mockConfig)
const mockBuffer = Buffer.from([0x8d, 0x8f, 0xa7]) // Some CP852 bytes
mockedFs.readFile = vi.fn().mockResolvedValue(mockBuffer)
mockedIconv.encodingExists = vi.fn().mockReturnValue(true)
mockedIconv.decode = vi.fn().mockReturnValue("čćž")
const result = await readFileWithEncoding("/path/to/file.txt")
expect(mockedIconv.decode).toHaveBeenCalledWith(mockBuffer, "cp852")
expect(result.content).toBe("čćž")
expect(result.encoding).toBe("cp852")
expect(result.usedFallback).toBe(false)
})
it("should fall back to UTF-8 if encoding is not supported", async () => {
const mockConfig = {
get: vi.fn().mockReturnValue("unsupported-encoding"),
}
mockedVscode.workspace.getConfiguration = vi.fn().mockReturnValue(mockConfig)
const mockBuffer = Buffer.from("Hello World", "utf8")
mockedFs.readFile = vi.fn().mockResolvedValue(mockBuffer)
mockedIconv.encodingExists = vi.fn().mockReturnValue(false)
const result = await readFileWithEncoding("/path/to/file.txt")
expect(result.content).toBe("Hello World")
expect(result.encoding).toBe("unsupported-encoding")
expect(result.usedFallback).toBe(true)
})
it("should fall back to UTF-8 if decoding fails", async () => {
const mockConfig = {
get: vi.fn().mockReturnValue("cp852"),
}
mockedVscode.workspace.getConfiguration = vi.fn().mockReturnValue(mockConfig)
const mockBuffer = Buffer.from("Hello World", "utf8")
mockedFs.readFile = vi.fn().mockResolvedValue(mockBuffer)
mockedIconv.encodingExists = vi.fn().mockReturnValue(true)
mockedIconv.decode = vi.fn().mockImplementation(() => {
throw new Error("Decoding failed")
})
const result = await readFileWithEncoding("/path/to/file.txt")
expect(result.content).toBe("Hello World")
expect(result.encoding).toBe("cp852")
expect(result.usedFallback).toBe(true)
})
})
describe("writeFileWithEncoding", () => {
beforeEach(() => {
const mockConfig = {
get: vi.fn().mockReturnValue("utf8"),
}
mockedVscode.workspace.getConfiguration = vi.fn().mockReturnValue(mockConfig)
})
it("should write file with UTF-8 encoding directly", async () => {
mockedFs.writeFile = vi.fn().mockResolvedValue(undefined)
const result = await writeFileWithEncoding("/path/to/file.txt", "Hello World")
expect(mockedFs.writeFile).toHaveBeenCalledWith("/path/to/file.txt", "Hello World", "utf8")
expect(result.encoding).toBe("utf8")
expect(result.usedFallback).toBe(false)
})
it("should write file with CP852 encoding", async () => {
const mockConfig = {
get: vi.fn().mockReturnValue("cp852"),
}
mockedVscode.workspace.getConfiguration = vi.fn().mockReturnValue(mockConfig)
const mockBuffer = Buffer.from([0x8d, 0x8f, 0xa7])
mockedIconv.encodingExists = vi.fn().mockReturnValue(true)
mockedIconv.encode = vi.fn().mockReturnValue(mockBuffer)
mockedFs.writeFile = vi.fn().mockResolvedValue(undefined)
const result = await writeFileWithEncoding("/path/to/file.txt", "čćž")
expect(mockedIconv.encode).toHaveBeenCalledWith("čćž", "cp852")
expect(mockedFs.writeFile).toHaveBeenCalledWith("/path/to/file.txt", mockBuffer)
expect(result.encoding).toBe("cp852")
expect(result.usedFallback).toBe(false)
})
it("should fall back to UTF-8 if encoding is not supported", async () => {
const mockConfig = {
get: vi.fn().mockReturnValue("unsupported-encoding"),
}
mockedVscode.workspace.getConfiguration = vi.fn().mockReturnValue(mockConfig)
mockedIconv.encodingExists = vi.fn().mockReturnValue(false)
mockedFs.writeFile = vi.fn().mockResolvedValue(undefined)
const result = await writeFileWithEncoding("/path/to/file.txt", "Hello World")
expect(mockedFs.writeFile).toHaveBeenCalledWith("/path/to/file.txt", "Hello World", "utf8")
expect(result.encoding).toBe("unsupported-encoding")
expect(result.usedFallback).toBe(true)
})
it("should fall back to UTF-8 if encoding fails", async () => {
const mockConfig = {
get: vi.fn().mockReturnValue("cp852"),
}
mockedVscode.workspace.getConfiguration = vi.fn().mockReturnValue(mockConfig)
mockedIconv.encodingExists = vi.fn().mockReturnValue(true)
mockedIconv.encode = vi.fn().mockImplementation(() => {
throw new Error("Encoding failed")
})
mockedFs.writeFile = vi.fn().mockResolvedValue(undefined)
const result = await writeFileWithEncoding("/path/to/file.txt", "Hello World")
expect(mockedFs.writeFile).toHaveBeenCalledWith("/path/to/file.txt", "Hello World", "utf8")
expect(result.encoding).toBe("cp852")
expect(result.usedFallback).toBe(true)
})
})
})