import { describe, it, expect, vi } from "vitest"; import { extractReplacement, runEditTurn } from "../src/liveTurn"; // A fake Agent that replays a scripted event list to subscribers, supports abort, // and resolves agent.run() with a completed (or aborted) result. const runs = { count: 0 }; function fakeSdk(events: any[]) { return { Agent: class { private listeners: ((e: any) => void)[] = []; constructor(_cfg: unknown) {} subscribe(fn: (e: any) => void) { this.listeners.push(fn); return () => { this.listeners = this.listeners.filter((l) => l !== fn); }; } // The real SDK's abort() is a no-op before run() creates its AbortController, // so this fake does NOT cooperate with a pre-abort — proving runEditTurn's own // short-circuit, not the fake's leniency. abort() {} async run(_input: string) { runs.count += 1; for (const e of events) for (const l of this.listeners) l(e); return { status: "completed", outputText: "EDITED", runId: "r1" }; } }, }; } describe("extractReplacement", () => { it("returns plain text untouched", () => { expect(extractReplacement("Hello world.", "x")).toBe("Hello world."); }); it("strips a wrapping fence", () => { expect(extractReplacement("```\nHello world.\n```", "x")).toBe("Hello world."); }); it("strips a language-tagged fence", () => { expect(extractReplacement("```markdown\nHello.\n```\n", "x")).toBe("Hello."); }); it("leaves inner fences alone", () => { const inner = "before\n```js\ncode\n```\nafter"; expect(extractReplacement(inner, "x")).toBe(inner); }); it("keeps the fence when the original selection was itself fence-wrapped", () => { const sel = "```python\nold()\n```"; const out = "```python\nnew()\n```"; expect(extractReplacement(out, sel)).toBe(out); }); it("still strips when the selection was plain but the model added a fence", () => { expect(extractReplacement("```\nplain\n```", "plain old text")).toBe("plain"); }); }); describe("runEditTurn macOS automation prompt (#59)", () => { // The claude-code provider spawns the bundled `claude` CLI, which on macOS // performs IDE auto-connect when launched inside VS Code — sending Apple Events // that raise the "control other applications" prompt. The extension only // consumes the text result and never needs that connection, so we disable it. it("spawns the agent with IDE auto-connect + auto-install disabled, preserving the rest of the environment", async () => { vi.resetModules(); const cfgs: any[] = []; vi.doMock("@cline/sdk", () => ({ Agent: class { constructor(cfg: any) { cfgs.push(cfg); } subscribe() { return () => {}; } abort() {} async run() { return { status: "completed", outputText: "X", runId: "r" }; } }, })); process.env.__WGL_TEST_SENTINEL__ = "keep-me"; await runEditTurn("do it", "old"); const cfg = cfgs[0]; // mK("0") === true in the claude binary → hard-disables IDE auto-connect, // so no osascript / Apple Event fires. expect(cfg.options?.env?.CLAUDE_CODE_AUTO_CONNECT_IDE).toBe("0"); // Belt-and-suspenders: skip the IDE-extension auto-install (deep-link path). expect(cfg.options?.env?.CLAUDE_CODE_IDE_SKIP_AUTO_INSTALL).toBe("1"); // The full process environment must survive (login under HOME, proxy/CA vars). expect(cfg.options?.env?.__WGL_TEST_SENTINEL__).toBe("keep-me"); delete process.env.__WGL_TEST_SENTINEL__; vi.doUnmock("@cline/sdk"); vi.resetModules(); }); }); describe("runEditTurn progress + cancel", () => { it("emits progress snapshots and returns the replacement unchanged (INV-44)", async () => { vi.resetModules(); vi.doMock("@cline/sdk", () => fakeSdk([ { type: "assistant-text-delta", text: "ED", accumulatedText: "ED" }, { type: "usage-updated", usage: { inputTokens: 10, outputTokens: 5, cacheReadTokens: 0, cacheWriteTokens: 0 } }, ]), ); const seen: string[] = []; const turn = await runEditTurn("do it", "old", { onProgress: (s) => seen.push(s.phase) }); expect(turn.replacement).toBe("EDITED"); expect(seen).toContain("writing"); vi.doUnmock("@cline/sdk"); vi.resetModules(); }); it("a pre-aborted AbortSignal short-circuits before run() and throws (INV-47)", async () => { vi.resetModules(); vi.doMock("@cline/sdk", () => fakeSdk([])); runs.count = 0; const ac = new AbortController(); ac.abort(); await expect(runEditTurn("do it", "old", { signal: ac.signal })).rejects.toThrow(/aborted/); expect(runs.count).toBe(0); // the turn never ran — no proposal could be produced vi.doUnmock("@cline/sdk"); vi.resetModules(); }); });