From 216a7f1ffe68d7814763022732d2c9b9925c1267 Mon Sep 17 00:00:00 2001 From: Ben Stull Date: Thu, 11 Jun 2026 08:48:13 -0700 Subject: [PATCH] test(f7): host E2E for the track-changes preview (#21) Co-Authored-By: Claude Opus 4.8 --- test/e2e/fixtures/workspace/docs/notes.txt | 1 + test/e2e/fixtures/workspace/docs/preview.md | 14 +++ test/e2e/suite/trackChangesPreview.test.ts | 108 ++++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 test/e2e/fixtures/workspace/docs/notes.txt create mode 100644 test/e2e/fixtures/workspace/docs/preview.md create mode 100644 test/e2e/suite/trackChangesPreview.test.ts diff --git a/test/e2e/fixtures/workspace/docs/notes.txt b/test/e2e/fixtures/workspace/docs/notes.txt new file mode 100644 index 0000000..9165c00 --- /dev/null +++ b/test/e2e/fixtures/workspace/docs/notes.txt @@ -0,0 +1 @@ +Plain text, not markdown — F7 must refuse this (F6 covers it instead). diff --git a/test/e2e/fixtures/workspace/docs/preview.md b/test/e2e/fixtures/workspace/docs/preview.md new file mode 100644 index 0000000..612b81b --- /dev/null +++ b/test/e2e/fixtures/workspace/docs/preview.md @@ -0,0 +1,14 @@ +# Preview fixture + +A target sentence Claude will rewrite via the seam. + +Some stable prose that does not change during the test run. + +```mermaid +flowchart LR + a --> b +``` + +```ts +const stable = true; +``` diff --git a/test/e2e/suite/trackChangesPreview.test.ts b/test/e2e/suite/trackChangesPreview.test.ts new file mode 100644 index 0000000..6b1c1c2 --- /dev/null +++ b/test/e2e/suite/trackChangesPreview.test.ts @@ -0,0 +1,108 @@ +import * as assert from "assert"; +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/preview.md"; +const docUri = () => vscode.Uri.file(path.join(WS, DOC_REL)).toString(); + +async function openDoc(rel = DOC_REL): Promise { + const uri = vscode.Uri.file(path.join(WS, 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?.trackChangesPreviewController, "extension exports trackChangesPreviewController"); + return api; +} +const settle = () => new Promise((r) => setTimeout(r, 400)); +const kinds = (api: CowritingApi) => + (api.trackChangesPreviewController.getLastModel(docUri()) ?? []).map((o) => o.kind); + +// Order-dependent (F2–F6 pattern): later tests consume earlier state. +suite("F7 track-changes preview (host E2E — markdown only, programmatic seam, no LLM)", () => { + const TARGET = "A target sentence Claude will rewrite via the seam."; + const REPLACEMENT = "A SENTENCE CLAUDE REWROTE via the seam."; + + test("open on a markdown doc → panel open, opened baseline shows no changes (PUC-1)", async () => { + const doc = await openDoc(); + const api = await getApi(); + assert.strictEqual(api.trackChangesPreviewController.isOpen(docUri()), false, "no panel yet"); + await vscode.commands.executeCommand("cowriting.showTrackChangesPreview"); + await settle(); + assert.strictEqual(api.trackChangesPreviewController.isOpen(docUri()), true, "panel open"); + const model = api.trackChangesPreviewController.getLastModel(docUri()); + assert.ok(model && model.length > 0, "a model was computed"); + assert.ok( + model!.every((o) => o.kind === "unchanged"), + "baseline == buffer at open → every block unchanged", + ); + void doc; + }); + + test("typing produces a changed/added block (PUC-2)", async () => { + const doc = await openDoc(); + const api = await getApi(); + const edit = new vscode.WorkspaceEdit(); + const end = doc.positionAt(doc.getText().length); + edit.insert(doc.uri, end, "\n\nA freshly typed paragraph.\n"); + assert.ok(await vscode.workspace.applyEdit(edit), "operator edit applied"); + await settle(); + assert.ok( + kinds(api).some((k) => k === "added" || k === "changed"), + "an added/changed block appears after typing", + ); + }); + + test("accepting a proposal advances the baseline; the landed block goes unchanged (PUC-3, 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 sentence"); + const id = await vscode.commands.executeCommand("cowriting.proposeAgentEdit", { + uri: doc.uri.toString(), + start, + end: start + TARGET.length, + newText: REPLACEMENT, + model: "sonnet", + sessionId: "e2e-f7", + turnId: "turn-f7-1", + }); + assert.ok(id, "propose returns an id"); + assert.ok(await api.proposalController.acceptById(DOC_REL, id!), "accept applies via the seam"); + await settle(); + // The baseline advanced to include REPLACEMENT, so the buffer == baseline for + // that block → it is NOT marked. Confirm the replacement is not flagged as a change. + const model = api.trackChangesPreviewController.getLastModel(docUri()) ?? []; + const replacementMarked = model.some( + (o) => o.kind !== "unchanged" && o.block.raw.includes("CLAUDE REWROTE"), + ); + assert.ok(!replacementMarked, "the just-landed text renders unmarked (baseline advanced)"); + }); + + test("pin resets the baseline to now → preview shows no marks (PUC-4)", async () => { + const doc = await openDoc(); + const api = await getApi(); + await vscode.commands.executeCommand("cowriting.pinDiffBaseline"); + await settle(); + assert.ok( + kinds(api).every((k) => k === "unchanged"), + "after pin, every block is unchanged (baseline == buffer)", + ); + void doc; + }); + + test("a non-markdown doc → command warns and opens no panel (PUC-6, markdown-only)", async () => { + const txt = await openDoc("docs/notes.txt"); + const api = await getApi(); + const key = txt.uri.toString(); + assert.notStrictEqual(txt.languageId, "markdown", "fixture is not markdown"); + await vscode.commands.executeCommand("cowriting.showTrackChangesPreview"); + await settle(); + assert.strictEqual(api.trackChangesPreviewController.isOpen(key), false, "no panel for non-markdown"); + }); +});