diff --git a/test/e2e/fixtures/workspace/docs/diffview.md b/test/e2e/fixtures/workspace/docs/diffview.md new file mode 100644 index 0000000..3774a5c --- /dev/null +++ b/test/e2e/fixtures/workspace/docs/diffview.md @@ -0,0 +1,7 @@ +# Diff view fixture + +The baseline opening paragraph stays put so the diff suite can anchor on it. + +A target sentence Claude will rewrite via the seam. + +A closing paragraph for the operator to edit by hand. diff --git a/test/e2e/suite/diffView.test.ts b/test/e2e/suite/diffView.test.ts new file mode 100644 index 0000000..2cc27e6 --- /dev/null +++ b/test/e2e/suite/diffView.test.ts @@ -0,0 +1,166 @@ +import * as assert from "assert"; +import * as fs from "fs"; +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"; + +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; +} +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)); + +// Order-dependent (F2–F4 pattern): later tests consume earlier state. Owns +// docs/diffview.md exclusively. +suite("F6 diff-view toggle (host E2E — 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("opening a tracked doc captures an `opened` baseline equal to the buffer (INV-18)", async () => { + const doc = await openDoc(); + const api = await getApi(); + // renderIfOpen on open already called ensureBaseline; assert it captured. + const baseline = api.diffViewController.getBaseline(DOC_REL); + assert.ok(baseline, "baseline captured on first sight"); + assert.strictEqual(baseline!.reason, "opened"); + assert.strictEqual(baseline!.text, doc.getText(), "baseline = open-time buffer"); + }); + + test("toggle opens a diff tab (original scheme cowriting-baseline) over the live doc (PUC-1)", async () => { + const api = await getApi(); + assert.strictEqual(api.diffViewController.isDiffOpen(DOC_REL), false, "no diff open yet"); + await vscode.commands.executeCommand("cowriting.toggleDiffView"); + await settle(); + assert.strictEqual(api.diffViewController.isDiffOpen(DOC_REL), true, "diff tab open"); + // The active tab is a TextDiff whose original is the baseline scheme. + const active = vscode.window.tabGroups.activeTabGroup.activeTab; + assert.ok(active && active.input instanceof vscode.TabInputTextDiff, "active tab is a diff"); + assert.strictEqual( + (active!.input as vscode.TabInputTextDiff).original.scheme, + "cowriting-baseline", + "left side served by the baseline provider", + ); + }); + + test("typing leaves the baseline unchanged while the buffer diverges", async () => { + const doc = await openDoc(); + const api = await getApi(); + const before = api.diffViewController.getBaseline(DOC_REL)!.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(DOC_REL)!.text, before, "baseline unchanged by typing"); + assert.notStrictEqual(doc.getText(), before, "buffer diverged"); + }); + + test("accepting a proposal advances the baseline past the landed text (PUC-2, INV-18)", async () => { + const doc = await openDoc(); + const api = await getApi(); + 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(DOC_REL)!; + assert.strictEqual(baseline.reason, "machine-landing", "baseline advanced on the landing"); + assert.ok(baseline.text.includes(REPLACEMENT), "landed text is in the baseline (won't show as a change)"); + assert.ok(!baseline.text.includes(TARGET), "old target gone from the baseline too"); + assert.strictEqual(baseline.text, doc.getText(), "baseline == buffer right after the landing"); + }); + + test("an operator edit after the landing makes baseline ≠ buffer (the operator delta)", async () => { + const doc = await openDoc(); + 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(DOC_REL)!.text, + doc.getText(), + "operator changes show against the advanced baseline", + ); + }); + + test("pin resets the baseline to now: baseline == buffer, reason pinned (PUC-3)", async () => { + const doc = await openDoc(); + const api = await getApi(); + await vscode.commands.executeCommand("cowriting.pinDiffBaseline"); + await settle(); + const baseline = api.diffViewController.getBaseline(DOC_REL)!; + assert.strictEqual(baseline.reason, "pinned"); + assert.strictEqual(baseline.text, doc.getText(), "pinned baseline == current buffer (diff empties)"); + }); + + test("the baseline is persisted on disk under the storage dir with the expected content (PUC-4)", async () => { + const api = await getApi(); + const p = api.diffViewController.baselineFilePath(DOC_REL); + assert.ok(p, "storage-backed baseline path is available"); + assert.ok(fs.existsSync(p!), `baseline file exists at ${p}`); + const onDisk = JSON.parse(fs.readFileSync(p!, "utf8")); + assert.strictEqual(onDisk.docPath, DOC_REL); + assert.strictEqual(onDisk.reason, "pinned", "last epoch (pin) persisted"); + assert.strictEqual(onDisk.text, api.diffViewController.getBaseline(DOC_REL)!.text, "on-disk == in-memory"); + // INV-19: nothing leaked into the repo's .threads sidecar tree. + assert.ok(!p!.includes(`${path.sep}.threads${path.sep}`), "baseline is NOT in the sidecar tree"); + }); + + test("toggle again closes the diff tab and reveals the normal editor (PUC-1)", async () => { + const api = await getApi(); + if (!api.diffViewController.isDiffOpen(DOC_REL)) { + await vscode.commands.executeCommand("cowriting.toggleDiffView"); + await settle(); + } + assert.strictEqual(api.diffViewController.isDiffOpen(DOC_REL), true, "diff open before close"); + await vscode.commands.executeCommand("cowriting.toggleDiffView"); + await settle(); + assert.strictEqual(api.diffViewController.isDiffOpen(DOC_REL), false, "diff tab closed"); + // "Normal editor is back": the file is the active text editor, and no + // baseline-diff tab for it remains in any group. + assert.strictEqual( + vscode.window.activeTextEditor?.document.uri.toString(), + vscode.Uri.file(path.join(WS, DOC_REL)).toString(), + "the normal editor for the doc is active after closing the diff", + ); + }); + + test("toggling on an untracked doc warns and opens no diff (PUC-5)", async () => { + await getApi(); + const untracked = await vscode.workspace.openTextDocument({ content: "scratch", language: "markdown" }); + await vscode.window.showTextDocument(untracked); + await settle(); + await vscode.commands.executeCommand("cowriting.toggleDiffView"); + await settle(); + // No baseline-diff tab for an untitled doc anywhere. + const anyDiff = vscode.window.tabGroups.all.some((g) => + g.tabs.some( + (t) => + t.input instanceof vscode.TabInputTextDiff && + t.input.original.scheme === "cowriting-baseline" && + t.input.modified.toString() === untracked.uri.toString(), + ), + ); + assert.strictEqual(anyDiff, false, "no diff opened for the untracked doc"); + }); +});