import * as assert from "assert"; import * as fs from "fs"; import * as os from "os"; import * as path from "path"; import * as vscode from "vscode"; import type { CowritingApi } from "../../../src/extension"; const WS = process.env.E2E_WORKSPACE!; const DOC_REL = "docs/diffview.md"; const docUri = () => vscode.Uri.file(path.join(WS, DOC_REL)).toString(); async function openDoc(): Promise { const uri = vscode.Uri.file(path.join(WS, DOC_REL)); const doc = await vscode.workspace.openTextDocument(uri); await vscode.window.showTextDocument(doc); return doc; } /** Open the doc AND enter coediting (INV-10) — baselines are established only * on entry (Task 2). Idempotent across tests: re-entering an already-coedited * doc is a no-op, so the once-captured baseline survives (order-dependent * suite, same as before). */ async function openAndCoedit(): Promise { const doc = await openDoc(); await vscode.commands.executeCommand("cowriting.coeditDocument"); await settle(); return doc; } async function getApi(): Promise { const ext = vscode.extensions.getExtension("benstull.vscode-cowriting-plugin")!; const api = (await ext.activate()) as CowritingApi; assert.ok(api?.diffViewController, "extension exports diffViewController"); return api; } const settle = () => new Promise((r) => setTimeout(r, 300)); // The F6 two-pane vscode.diff VIEW was removed in #34 (F10's rendered preview is // the single review surface); only the baseline DATA layer survives, consumed by // F7/F10. This suite covers that data layer. Order-dependent (F2–F4 pattern): // later tests consume earlier state. Owns docs/diffview.md exclusively. The // baseline works on ANY file (#19), so the last two tests use an out-of-workspace // file and an untitled buffer. // // Native-surfaces migration (Task 2, spec §6.4/INV-7): a baseline is now // established only once a document ENTERS coediting (not merely opened), and // the fixture workspace is NOT a git repo, so every doc here resolves to // SNAPSHOT mode. The shipped machine-landing baseline advance (#48/INV-18) is // retired — an accepted proposal now stays a visible change-since-baseline // until "Mark Changes as Reviewed" (D21). suite("F6 baseline data layer (host E2E — any file, programmatic seam ingress, no LLM)", () => { const TARGET = "A target sentence Claude will rewrite via the seam."; const REPLACEMENT = "A SENTENCE CLAUDE REWROTE via the seam."; test("entering coediting on a snapshot-mode doc captures an `entered` baseline equal to the buffer (INV-7)", async () => { const doc = await openAndCoedit(); const api = await getApi(); assert.strictEqual(api.diffViewController.modeOf(docUri()), "snapshot", "no git repo → snapshot mode"); const baseline = api.diffViewController.getBaseline(docUri()); assert.ok(baseline, "baseline captured on coediting entry"); assert.strictEqual(baseline!.reason, "entered"); assert.strictEqual(baseline!.text, doc.getText(), "baseline = entry-time buffer"); }); test("typing leaves the baseline unchanged while the buffer diverges", async () => { const doc = await openAndCoedit(); const api = await getApi(); const before = api.diffViewController.getBaseline(docUri())!.text; const edit = new vscode.WorkspaceEdit(); edit.insert(doc.uri, new vscode.Position(0, 0), "OPERATOR ADDED LINE\n"); assert.ok(await vscode.workspace.applyEdit(edit), "operator edit applied"); await settle(); assert.strictEqual(api.diffViewController.getBaseline(docUri())!.text, before, "baseline unchanged by typing"); assert.notStrictEqual(doc.getText(), before, "buffer diverged"); }); test("accepting a proposal does NOT advance the baseline — the landed text stays a change (INV-7/D21, #48 retired)", async () => { const doc = await openAndCoedit(); const api = await getApi(); const before = api.diffViewController.getBaseline(docUri())!; const start = doc.getText().indexOf(TARGET); assert.ok(start >= 0, "fixture contains the target"); const id = await vscode.commands.executeCommand("cowriting.proposeAgentEdit", { uri: doc.uri.toString(), start, end: start + TARGET.length, newText: REPLACEMENT, model: "sonnet", sessionId: "e2e-diff", turnId: "turn-d1", }); assert.ok(id, "propose returns an id"); assert.ok(await api.proposalController.acceptById(DOC_REL, id!), "accept applies via the seam"); await settle(); const baseline = api.diffViewController.getBaseline(docUri())!; assert.strictEqual(baseline.reason, before.reason, "baseline reason untouched by the landing"); assert.strictEqual(baseline.text, before.text, "baseline text untouched by the landing"); assert.ok(!baseline.text.includes(REPLACEMENT), "landed text is NOT folded into the baseline"); assert.notStrictEqual(baseline.text, doc.getText(), "baseline != buffer — the landing reads as a change"); }); test("an operator edit after the landing keeps baseline ≠ buffer (the operator delta)", async () => { const doc = await openAndCoedit(); const api = await getApi(); const edit = new vscode.WorkspaceEdit(); edit.insert(doc.uri, new vscode.Position(0, 0), "POST-LANDING OPERATOR LINE\n"); assert.ok(await vscode.workspace.applyEdit(edit)); await settle(); assert.notStrictEqual( api.diffViewController.getBaseline(docUri())!.text, doc.getText(), "operator changes show against the untouched baseline", ); }); test("markReviewed resets the baseline to now: baseline == buffer, reason pinned", async () => { const doc = await openAndCoedit(); const api = await getApi(); await vscode.commands.executeCommand("cowriting.markReviewed"); await settle(); const baseline = api.diffViewController.getBaseline(docUri())!; assert.strictEqual(baseline.reason, "pinned"); assert.strictEqual(baseline.text, doc.getText(), "pinned baseline == current buffer (change-marks empty)"); }); test("the baseline is persisted in GLOBAL storage, never the repo (INV-19)", async () => { await openAndCoedit(); const api = await getApi(); const p = api.diffViewController.baselineFilePath(docUri()); assert.ok(p, "storage-backed baseline path is available for a file: doc"); assert.ok(fs.existsSync(p!), `baseline file exists at ${p}`); const onDisk = JSON.parse(fs.readFileSync(p!, "utf8")); assert.strictEqual(onDisk.uri, docUri(), "baseline records the document URI"); assert.strictEqual(onDisk.reason, "pinned", "last epoch (markReviewed) persisted"); assert.strictEqual(onDisk.text, api.diffViewController.getBaseline(docUri())!.text, "on-disk == in-memory"); // INV-19: baseline lives under the extension's storage dir, not the repo. assert.ok(!p!.includes(`${path.sep}.threads${path.sep}`), "baseline is NOT in the sidecar tree"); assert.ok(!p!.startsWith(WS + path.sep), "baseline is NOT under the workspace folder"); assert.ok(p!.includes(`${path.sep}baselines${path.sep}`), "baseline lives under /baselines/"); }); test("a baseline is captured + persisted for a file OUTSIDE the workspace folder (#19)", async () => { const api = await getApi(); const outsideDir = fs.mkdtempSync(path.join(os.tmpdir(), "cowriting-outside-")); const outsidePath = path.join(outsideDir, "outside.md"); fs.writeFileSync(outsidePath, "# Outside the workspace\n\nThe operator edits this too.\n", "utf8"); const outsideUri = vscode.Uri.file(outsidePath); const doc = await vscode.workspace.openTextDocument(outsideUri); await vscode.window.showTextDocument(doc); await vscode.commands.executeCommand("cowriting.coeditDocument"); await settle(); // Captured on coediting entry even though it is NOT under the workspace folder. const baseline = api.diffViewController.getBaseline(outsideUri.toString()); assert.ok(baseline, "baseline captured for an out-of-folder file"); assert.strictEqual(baseline!.reason, "entered"); const fp = api.diffViewController.baselineFilePath(outsideUri.toString()); assert.ok(fp && fs.existsSync(fp), "outside-file baseline persisted in global storage"); assert.ok(!fp!.startsWith(WS + path.sep), "not under the workspace folder"); fs.rmSync(outsideDir, { recursive: true, force: true }); void doc; }); test("an UNTITLED buffer gets an in-memory baseline, never persisted to disk (#19)", async () => { const api = await getApi(); const untitled = await vscode.workspace.openTextDocument({ content: "scratch line\n", language: "markdown" }); await vscode.window.showTextDocument(untitled); await vscode.commands.executeCommand("cowriting.coeditDocument"); await settle(); const key = untitled.uri.toString(); assert.strictEqual(untitled.uri.scheme, "untitled", "it really is an untitled buffer"); const baseline = api.diffViewController.getBaseline(key); assert.ok(baseline, "untitled buffer got an in-memory baseline"); assert.strictEqual(api.diffViewController.baselineFilePath(key), undefined, "untitled is NOT persisted to disk"); }); });