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"; import { renderHtmlFor } from "./helpers"; const WS = process.env.E2E_WORKSPACE!; const settle = () => new Promise((r) => setTimeout(r, 300)); async function getApi(): Promise { const ext = vscode.extensions.getExtension("benstull.vscode-cowriting-plugin")!; const api = (await ext.activate()) as CowritingApi; assert.ok(api?.attributionController && api?.proposalController, "exports attribution + proposal"); return api; } // F10 host E2E (no LLM): the rewrite of the obsolete F9 authorship-mode test. // F9's "authorship" mode / renderAuthorship is gone — the on-state renderReview // now author-colors Claude's PENDING proposal blocks as cw-ins-claude. This // suite checks that render BEFORE accepting (the class to check is cw-ins-claude, // the proposal block, NOT cw-by-claude — the old F9 authorship render) and that // the attribution data layer survives the accept; it does not exercise the F6 // baseline (see diffView.test.ts / baselineRouter.test.ts for the post-accept // baseline behavior, INV-7/D21). Owns its own markdown doc. Task 8: the render // probe is now the pure `renderReview` directly (the webview that used to wrap // it, `renderHtmlFor`, is gone). suite("F10 review preview — pending Claude proposal renders cw-ins-claude in the on-state (host E2E, no LLM)", () => { const DOC_REL = "docs/f10claude.md"; const TARGET = "The sentence Claude will compose over."; const REPLACEMENT = "The sentence CLAUDE COMPOSED via the seam."; test("a pending Claude proposal carries cw-ins-claude; accepted proposal leaves attribution data intact", async () => { const abs = path.join(WS, DOC_REL); fs.mkdirSync(path.dirname(abs), { recursive: true }); fs.writeFileSync(abs, `# F10\n\n${TARGET}\n`, "utf8"); const uri = vscode.Uri.file(abs); const doc = await vscode.workspace.openTextDocument(uri); await vscode.window.showTextDocument(doc); // INV-10: entering coediting is required for attribution tracking + F12 // optimistic-apply to fire — this suite never entered it (pre-migration). await vscode.commands.executeCommand("cowriting.coeditDocument"); await settle(); const api = await getApi(); const key = uri.toString(); // Claude proposes via the seam (F12 optimistic-apply; proposal stays PENDING) const start = doc.getText().indexOf(TARGET); const id = await vscode.commands.executeCommand("cowriting.proposeAgentEdit", { uri: key, start, end: start + TARGET.length, newText: REPLACEMENT, model: "sonnet", sessionId: "e2e-f10", turnId: "turn-f10", }); assert.ok(id, "propose returns an id"); await settle(); // PENDING proposal: the preview renders the proposal block with cw-ins-claude. // After accepting, the baseline advances (INV-18) → the text becomes "unchanged" // and renders plain — so we check BEFORE accepting. const pendingHtml = renderHtmlFor(api, doc, key); assert.ok( pendingHtml.includes("cw-ins-claude"), "pending Claude proposal block carries cw-ins-claude in the on-state render", ); // accept via the seam — the proposal lands; the baseline no longer auto-advances // (INV-18/#48 retired, spec INV-7), but that isn't asserted here (no coediting // entry / baseline established for this doc — only the attribution data layer). assert.ok(await api.proposalController.acceptById(DOC_REL, id!), "accept applies via the seam"); await settle(); // attribution recorded a Claude (agent) span (data layer intact) const claudeSpan = api.attributionController.getSpans(DOC_REL).find((s) => s.authorKind === "agent"); assert.ok(claudeSpan, "Claude span recorded by F3 (data layer intact after accept)"); const spans = api.attributionController.spansFor(doc); assert.ok(spans.some((s) => s.author === "claude"), "spansFor reports a Claude span for the preview"); }); });