diff --git a/test/e2e/suite-no-workspace/noWorkspace.test.ts b/test/e2e/suite-no-workspace/noWorkspace.test.ts index e2aecb7..b6c1cb5 100644 --- a/test/e2e/suite-no-workspace/noWorkspace.test.ts +++ b/test/e2e/suite-no-workspace/noWorkspace.test.ts @@ -28,8 +28,6 @@ suite("no-workspace activation (#8)", () => { "cowriting.acceptProposal", "cowriting.rejectProposal", "cowriting.proposeAgentEdit", - "cowriting.toggleDiffView", - "cowriting.pinDiffBaseline", ]) { assert.ok(all.includes(command), `${command} is registered`); } @@ -39,4 +37,26 @@ suite("no-workspace activation (#8)", () => { await vscode.commands.executeCommand("cowriting.editSelection"); await vscode.commands.executeCommand("cowriting.createThread"); }); + + // F6 (#19) is workspace-INDEPENDENT: its commands are real (not stubs) even + // with no folder open, and the toggle works on an untitled buffer. + test("F6 toggle works with no folder open (untitled buffer)", async () => { + const all = await vscode.commands.getCommands(true); + assert.ok(all.includes("cowriting.toggleDiffView"), "toggleDiffView registered"); + assert.ok(all.includes("cowriting.pinDiffBaseline"), "pinDiffBaseline registered"); + const untitled = await vscode.workspace.openTextDocument({ content: "no-folder scratch\n", language: "markdown" }); + await vscode.window.showTextDocument(untitled); + await new Promise((r) => setTimeout(r, 300)); + await vscode.commands.executeCommand("cowriting.toggleDiffView"); + await new Promise((r) => setTimeout(r, 300)); + const opened = 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() === untitled.uri.toString(), + ), + ); + assert.ok(opened, "a cowriting-baseline diff opened for the untitled buffer with no folder"); + }); }); diff --git a/test/e2e/suite/diffView.test.ts b/test/e2e/suite/diffView.test.ts index 2cc27e6..d694633 100644 --- a/test/e2e/suite/diffView.test.ts +++ b/test/e2e/suite/diffView.test.ts @@ -1,11 +1,13 @@ 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)); @@ -22,16 +24,17 @@ async function getApi(): Promise { 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)", () => { +// docs/diffview.md exclusively. F6 works on ANY file (#19), so the last two +// tests use an out-of-workspace file and an untitled buffer. +suite("F6 diff-view toggle (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("opening a tracked doc captures an `opened` baseline equal to the buffer (INV-18)", async () => { const doc = await openDoc(); + await getApi(); const api = await getApi(); - // renderIfOpen on open already called ensureBaseline; assert it captured. - const baseline = api.diffViewController.getBaseline(DOC_REL); + const baseline = api.diffViewController.getBaseline(docUri()); assert.ok(baseline, "baseline captured on first sight"); assert.strictEqual(baseline!.reason, "opened"); assert.strictEqual(baseline!.text, doc.getText(), "baseline = open-time buffer"); @@ -39,11 +42,10 @@ suite("F6 diff-view toggle (host E2E — programmatic seam ingress, no LLM)", () 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"); + assert.strictEqual(api.diffViewController.isDiffOpen(docUri()), 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. + assert.strictEqual(api.diffViewController.isDiffOpen(docUri()), true, "diff tab open"); const active = vscode.window.tabGroups.activeTabGroup.activeTab; assert.ok(active && active.input instanceof vscode.TabInputTextDiff, "active tab is a diff"); assert.strictEqual( @@ -56,12 +58,12 @@ suite("F6 diff-view toggle (host E2E — programmatic seam ingress, no LLM)", () 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 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(DOC_REL)!.text, before, "baseline unchanged by typing"); + assert.strictEqual(api.diffViewController.getBaseline(docUri())!.text, before, "baseline unchanged by typing"); assert.notStrictEqual(doc.getText(), before, "buffer diverged"); }); @@ -82,7 +84,7 @@ suite("F6 diff-view toggle (host E2E — programmatic seam ingress, no LLM)", () 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)!; + const baseline = api.diffViewController.getBaseline(docUri())!; 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"); @@ -97,7 +99,7 @@ suite("F6 diff-view toggle (host E2E — programmatic seam ingress, no LLM)", () assert.ok(await vscode.workspace.applyEdit(edit)); await settle(); assert.notStrictEqual( - api.diffViewController.getBaseline(DOC_REL)!.text, + api.diffViewController.getBaseline(docUri())!.text, doc.getText(), "operator changes show against the advanced baseline", ); @@ -108,59 +110,82 @@ suite("F6 diff-view toggle (host E2E — programmatic seam ingress, no LLM)", () const api = await getApi(); await vscode.commands.executeCommand("cowriting.pinDiffBaseline"); await settle(); - const baseline = api.diffViewController.getBaseline(DOC_REL)!; + const baseline = api.diffViewController.getBaseline(docUri())!; 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 () => { + test("the baseline is persisted in GLOBAL storage, never the repo (PUC-4, INV-19)", async () => { + await openDoc(); const api = await getApi(); - const p = api.diffViewController.baselineFilePath(DOC_REL); - assert.ok(p, "storage-backed baseline path is available"); + 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.docPath, DOC_REL); + assert.strictEqual(onDisk.uri, docUri(), "baseline records the document URI"); 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.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("toggle again closes the diff tab and reveals the normal editor (PUC-1)", async () => { const api = await getApi(); - if (!api.diffViewController.isDiffOpen(DOC_REL)) { + if (!api.diffViewController.isDiffOpen(docUri())) { + await openDoc(); await vscode.commands.executeCommand("cowriting.toggleDiffView"); await settle(); } - assert.strictEqual(api.diffViewController.isDiffOpen(DOC_REL), true, "diff open before close"); + assert.strictEqual(api.diffViewController.isDiffOpen(docUri()), 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(api.diffViewController.isDiffOpen(docUri()), false, "diff tab closed"); assert.strictEqual( vscode.window.activeTextEditor?.document.uri.toString(), - vscode.Uri.file(path.join(WS, DOC_REL)).toString(), + docUri(), "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); + test("toggle works on a file OUTSIDE the workspace folder, persisted in global storage (#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 settle(); + // Captured on open 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, "opened"); 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"); + assert.strictEqual(api.diffViewController.isDiffOpen(outsideUri.toString()), true, "diff opens for the outside file"); + 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"); + // close it so it doesn't bleed into the untitled test's active-tab checks + await vscode.commands.executeCommand("cowriting.toggleDiffView"); + await settle(); + fs.rmSync(outsideDir, { recursive: true, force: true }); + }); + + test("toggle works on an UNTITLED buffer, baseline in-memory only (#19)", async () => { + const api = await getApi(); + const untitled = await vscode.workspace.openTextDocument({ content: "scratch line\n", language: "markdown" }); + await vscode.window.showTextDocument(untitled); + 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"); + await vscode.commands.executeCommand("cowriting.toggleDiffView"); + await settle(); + assert.strictEqual(api.diffViewController.isDiffOpen(key), true, "diff opens for the untitled buffer"); }); });