447a1170ce
Task 2 of the native-surfaces migration plan. GitBaselineAdapter resolves a
document's HEAD blob via the built-in vscode.git extension (hardened with a
.git/logs/HEAD watch that nudges repo.status(), since the git extension's own
watcher doesn't reliably notice out-of-band commits on non-workspace-folder
repos in the E2E host). DiffViewController is reworked into a baseline router:
head mode (git-tracked, never persisted, re-read on commit) vs snapshot mode
(captured on CoeditingRegistry entry, re-pinned by "Mark Changes as Reviewed"
— renamed from cowriting.pinDiffBaseline, D14). The shipped machine-landing
baseline advance (#48/INV-18) is retired: a landed Claude edit now stays a
visible change-since-baseline until commit or review (INV-7/D21). Legacy
on-disk reasons ("opened"/"machine-landing") migrate to "entered" via
BaselineStore.normalizeReason on load.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
141 lines
6.5 KiB
TypeScript
141 lines
6.5 KiB
TypeScript
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();
|
||
|
||
/** Open the doc AND enter coediting (INV-10/Task 2) — the baseline is
|
||
* established only on entry; re-entering an already-coedited doc is a no-op
|
||
* so the once-captured baseline survives across this order-dependent suite. */
|
||
async function openDoc(rel = DOC_REL): Promise<vscode.TextDocument> {
|
||
const uri = vscode.Uri.file(path.join(WS, rel));
|
||
const doc = await vscode.workspace.openTextDocument(uri);
|
||
await vscode.window.showTextDocument(doc);
|
||
await vscode.commands.executeCommand("cowriting.coeditDocument");
|
||
return doc;
|
||
}
|
||
async function getApi(): Promise<CowritingApi> {
|
||
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 does NOT advance the baseline; the landed block stays marked (PUC-3, INV-7/D21, #48 retired)", 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<string>("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();
|
||
// Native-surfaces migration (INV-7/D21): the shipped machine-landing baseline
|
||
// advance (#48/INV-18) is retired — the landed block stays a visible change
|
||
// until "Mark Changes as Reviewed".
|
||
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 still renders marked (baseline does not auto-advance)");
|
||
});
|
||
|
||
test("markReviewed 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.markReviewed");
|
||
await settle();
|
||
assert.ok(
|
||
kinds(api).every((k) => k === "unchanged"),
|
||
"after markReviewed, 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");
|
||
});
|
||
|
||
test("a changed flowchart augments the emitted mermaid source (#22, INV-29)", async () => {
|
||
const doc = await openDoc();
|
||
const api = await getApi();
|
||
// Show the preview and pin the baseline so the fixture's `a --> b` flowchart
|
||
// is the "before"; then add an edge `a --> c` and confirm the intra-diagram diff.
|
||
await vscode.commands.executeCommand("cowriting.showTrackChangesPreview");
|
||
await vscode.commands.executeCommand("cowriting.markReviewed");
|
||
await settle();
|
||
|
||
const anchor = doc.getText().indexOf("a --> b");
|
||
assert.ok(anchor >= 0, "fixture contains the flowchart edge");
|
||
const insertAt = doc.positionAt(anchor + "a --> b".length);
|
||
const edit = new vscode.WorkspaceEdit();
|
||
edit.insert(doc.uri, insertAt, "\n a --> c");
|
||
assert.ok(await vscode.workspace.applyEdit(edit), "flowchart edit applied");
|
||
await settle();
|
||
|
||
const model = api.trackChangesPreviewController.getLastModel(docUri()) ?? [];
|
||
const mermaidOp = model.find((o) => o.kind === "changed" && o.block.type === "mermaid");
|
||
assert.ok(mermaidOp, "the flowchart block is a changed mermaid op");
|
||
|
||
const html = api.trackChangesPreviewController.renderHtmlFor(docUri());
|
||
assert.match(html, /classDef cwAdded/, "augmented with cwAdded classDef");
|
||
assert.match(html, /class\s+c\s+cwAdded/, "node c colored added");
|
||
assert.match(html, /cw-mermaid-legend/, "legend emitted");
|
||
});
|
||
});
|