import { mock, describe, expect, test } from "bun:test"; // Mock log.ts to cut the heavy dependency chain mock.module("src/utils/log.ts", () => ({ logError: () => {}, logToFile: () => {}, getLogDisplayTitle: () => "", logEvent: () => {}, logMCPError: () => {}, logMCPDebug: () => {}, dateToFilename: (d: Date) => d.toISOString().replace(/[:.]/g, "-"), getLogFilePath: () => "/tmp/mock-log", attachErrorLogSink: () => {}, getInMemoryErrors: () => [], loadErrorLogs: async () => [], getErrorLogByIndex: async () => null, captureAPIRequest: () => {}, _resetErrorLogForTesting: () => {}, })); const { normalizeQuotes, stripTrailingWhitespace, findActualString, preserveQuoteStyle, applyEditToFile, LEFT_SINGLE_CURLY_QUOTE, RIGHT_SINGLE_CURLY_QUOTE, LEFT_DOUBLE_CURLY_QUOTE, RIGHT_DOUBLE_CURLY_QUOTE, } = await import("../utils"); // ─── normalizeQuotes ──────────────────────────────────────────────────── describe("normalizeQuotes", () => { test("converts left single curly to straight", () => { expect(normalizeQuotes(`${LEFT_SINGLE_CURLY_QUOTE}hello`)).toBe("'hello"); }); test("converts right single curly to straight", () => { expect(normalizeQuotes(`hello${RIGHT_SINGLE_CURLY_QUOTE}`)).toBe("hello'"); }); test("converts left double curly to straight", () => { expect(normalizeQuotes(`${LEFT_DOUBLE_CURLY_QUOTE}hello`)).toBe('"hello'); }); test("converts right double curly to straight", () => { expect(normalizeQuotes(`hello${RIGHT_DOUBLE_CURLY_QUOTE}`)).toBe('hello"'); }); test("leaves straight quotes unchanged", () => { expect(normalizeQuotes("'hello' \"world\"")).toBe("'hello' \"world\""); }); test("handles empty string", () => { expect(normalizeQuotes("")).toBe(""); }); }); // ─── stripTrailingWhitespace ──────────────────────────────────────────── describe("stripTrailingWhitespace", () => { test("strips trailing spaces from lines", () => { expect(stripTrailingWhitespace("hello \nworld ")).toBe("hello\nworld"); }); test("strips trailing tabs", () => { expect(stripTrailingWhitespace("hello\t\nworld\t")).toBe("hello\nworld"); }); test("preserves leading whitespace", () => { expect(stripTrailingWhitespace(" hello \n world ")).toBe( " hello\n world" ); }); test("handles empty string", () => { expect(stripTrailingWhitespace("")).toBe(""); }); test("handles CRLF line endings", () => { expect(stripTrailingWhitespace("hello \r\nworld ")).toBe( "hello\r\nworld" ); }); test("handles no trailing whitespace", () => { expect(stripTrailingWhitespace("hello\nworld")).toBe("hello\nworld"); }); }); // ─── findActualString ─────────────────────────────────────────────────── describe("findActualString", () => { test("finds exact match", () => { expect(findActualString("hello world", "hello")).toBe("hello"); }); test("finds match with curly quotes normalized", () => { const fileContent = `${LEFT_DOUBLE_CURLY_QUOTE}hello${RIGHT_DOUBLE_CURLY_QUOTE}`; const result = findActualString(fileContent, '"hello"'); expect(result).not.toBeNull(); }); test("returns null when not found", () => { expect(findActualString("hello world", "xyz")).toBeNull(); }); test("returns null for empty search in non-empty content", () => { // Empty string is always found at index 0 via includes() const result = findActualString("hello", ""); expect(result).toBe(""); }); }); // ─── preserveQuoteStyle ───────────────────────────────────────────────── describe("preserveQuoteStyle", () => { test("returns newString unchanged when no normalization happened", () => { expect(preserveQuoteStyle("hello", "hello", "world")).toBe("world"); }); test("converts straight double quotes to curly in replacement", () => { const oldString = '"hello"'; const actualOldString = `${LEFT_DOUBLE_CURLY_QUOTE}hello${RIGHT_DOUBLE_CURLY_QUOTE}`; const newString = '"world"'; const result = preserveQuoteStyle(oldString, actualOldString, newString); expect(result).toContain(LEFT_DOUBLE_CURLY_QUOTE); expect(result).toContain(RIGHT_DOUBLE_CURLY_QUOTE); }); }); // ─── applyEditToFile ──────────────────────────────────────────────────── describe("applyEditToFile", () => { test("replaces first occurrence by default", () => { expect(applyEditToFile("foo bar foo", "foo", "baz")).toBe("baz bar foo"); }); test("replaces all occurrences with replaceAll=true", () => { expect(applyEditToFile("foo bar foo", "foo", "baz", true)).toBe( "baz bar baz" ); }); test("handles deletion (empty newString) with trailing newline", () => { const result = applyEditToFile("line1\nline2\nline3\n", "line2", ""); expect(result).toBe("line1\nline3\n"); }); test("handles deletion without trailing newline", () => { const result = applyEditToFile("foobar", "foo", ""); expect(result).toBe("bar"); }); test("handles no match (returns original)", () => { expect(applyEditToFile("hello world", "xyz", "abc")).toBe("hello world"); }); test("handles empty original content with insertion", () => { expect(applyEditToFile("", "", "new content")).toBe("new content"); }); });