Native surfaces migration — evolve the extension onto VS Code's native review surfaces (9-task plan, spec v0.2.1) (#72)
This commit was merged in pull request #72.
This commit is contained in:
@@ -10,7 +10,7 @@ const settle = () => new Promise((r) => setTimeout(r, 400));
|
||||
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 && api?.proposalController, "exports controllers");
|
||||
assert.ok(api?.proposalController && api?.editorProposalController, "exports controllers");
|
||||
return api;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,9 @@ async function freshDoc(rel: string, body: string): Promise<vscode.TextDocument>
|
||||
fs.writeFileSync(abs, body, "utf8");
|
||||
const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(abs));
|
||||
await vscode.window.showTextDocument(doc);
|
||||
// INV-10: attribution tracking (accept's per-word attribution, INV-40) is
|
||||
// gated on coediting (Task 4).
|
||||
await vscode.commands.executeCommand("cowriting.coeditDocument");
|
||||
await settle();
|
||||
return doc;
|
||||
}
|
||||
@@ -36,13 +39,13 @@ suite("F12 SLICE-2 — block-granularity document proposals (#47, INV-39/40/41)"
|
||||
"# Doc\n\nFirst paragraph alpha.\n\nSecond paragraph beta.\n\nThird paragraph gamma.\n",
|
||||
);
|
||||
const api = await getApi();
|
||||
const ctl = api.trackChangesPreviewController;
|
||||
ctl.setEditTurnForTest(async () => ({
|
||||
const editFlow = api.editFlow;
|
||||
editFlow.setEditTurnForTest(async () => ({
|
||||
replacement: "# Doc\n\nFirst paragraph ALPHA.\n\nSecond paragraph beta.\n\nThird paragraph GAMMA.\n",
|
||||
model: "sonnet",
|
||||
sessionId: "e2e-f12-multi",
|
||||
}));
|
||||
const ids = await ctl.runEditAndPropose(doc, { kind: "document" }, "uppercase the first/last nouns");
|
||||
const ids = await editFlow.runEditAndPropose(doc, { kind: "document" }, "uppercase the first/last nouns");
|
||||
await settle();
|
||||
assert.strictEqual(ids.length, 2, "two changed blocks → two proposals; the unchanged middle block → none");
|
||||
|
||||
@@ -59,13 +62,13 @@ suite("F12 SLICE-2 — block-granularity document proposals (#47, INV-39/40/41)"
|
||||
"# Code\n\n```js\nconst a = 1;\nconst b = 2;\n```\n",
|
||||
);
|
||||
const api = await getApi();
|
||||
const ctl = api.trackChangesPreviewController;
|
||||
ctl.setEditTurnForTest(async () => ({
|
||||
const editFlow = api.editFlow;
|
||||
editFlow.setEditTurnForTest(async () => ({
|
||||
replacement: "# Code\n\n```js\nconst a = 10;\nconst b = 2;\n```\n",
|
||||
model: "sonnet",
|
||||
sessionId: "e2e-f12-fence",
|
||||
}));
|
||||
const ids = await ctl.runEditAndPropose(doc, { kind: "document" }, "bump a to 10");
|
||||
const ids = await editFlow.runEditAndPropose(doc, { kind: "document" }, "bump a to 10");
|
||||
await settle();
|
||||
assert.strictEqual(ids.length, 1, "a changed fence is one atomic proposal");
|
||||
const view = api.proposalController.listProposals(doc).find((v) => v.id === ids[0])!;
|
||||
@@ -77,15 +80,15 @@ suite("F12 SLICE-2 — block-granularity document proposals (#47, INV-39/40/41)"
|
||||
test("accepting a block proposal attributes only the changed words to Claude (INV-40)", async () => {
|
||||
const doc = await freshDoc("docs/f12-attr.md", "# T\n\nThe quick brown fox jumps lazily.\n");
|
||||
const api = await getApi();
|
||||
const ctl = api.trackChangesPreviewController;
|
||||
const editFlow = api.editFlow;
|
||||
const key = api.proposalController.keyFor(doc);
|
||||
|
||||
ctl.setEditTurnForTest(async () => ({
|
||||
editFlow.setEditTurnForTest(async () => ({
|
||||
replacement: "# T\n\nThe quick RED fox jumps SLOWLY.\n",
|
||||
model: "sonnet",
|
||||
sessionId: "e2e-f12-attr",
|
||||
}));
|
||||
const ids = await ctl.runEditAndPropose(doc, { kind: "document" }, "change two words");
|
||||
const ids = await editFlow.runEditAndPropose(doc, { kind: "document" }, "change two words");
|
||||
await settle();
|
||||
assert.strictEqual(ids.length, 1, "one changed paragraph → one block proposal");
|
||||
|
||||
@@ -115,11 +118,11 @@ suite("F12 SLICE-2 — block-granularity document proposals (#47, INV-39/40/41)"
|
||||
const rewrite = "# Ins\n\nAlpha block.\n\nBrand new middle block.\n\nBeta block.\n";
|
||||
const doc = await freshDoc("docs/f12-insert.md", original);
|
||||
const api = await getApi();
|
||||
const ctl = api.trackChangesPreviewController;
|
||||
const editFlow = api.editFlow;
|
||||
const key = api.proposalController.keyFor(doc);
|
||||
|
||||
ctl.setEditTurnForTest(async () => ({ replacement: rewrite, model: "sonnet", sessionId: "e2e-f12-insert" }));
|
||||
const ids = await ctl.runEditAndPropose(doc, { kind: "document" }, "insert a paragraph");
|
||||
editFlow.setEditTurnForTest(async () => ({ replacement: rewrite, model: "sonnet", sessionId: "e2e-f12-insert" }));
|
||||
const ids = await editFlow.runEditAndPropose(doc, { kind: "document" }, "insert a paragraph");
|
||||
await settle();
|
||||
assert.ok(ids.length >= 1, "the insertion produced at least one proposal");
|
||||
|
||||
@@ -132,3 +135,55 @@ suite("F12 SLICE-2 — block-granularity document proposals (#47, INV-39/40/41)"
|
||||
assert.strictEqual(api.proposalController.listProposals(doc).length, 0, "no proposals left pending");
|
||||
});
|
||||
});
|
||||
|
||||
// Task 8 (PUC-2 lens copy): the CodeLens titles read "✓ Keep"/"✗ Reject" per
|
||||
// proposal, and — once ≥2 proposals are pending — a top-of-file "Keep all
|
||||
// (N)"/"Reject all" pair routes straight at the batch commands.
|
||||
suite("F12 CodeLens copy (Task 8, PUC-2)", () => {
|
||||
test("one pending proposal → a per-proposal ✓ Keep / ✗ Reject pair, no top-of-file pair", async () => {
|
||||
const doc = await freshDoc("docs/f12-lens-one.md", "# Lens\n\nAlpha block here.\n");
|
||||
const api = await getApi();
|
||||
const editFlow = api.editFlow;
|
||||
editFlow.setEditTurnForTest(async () => ({
|
||||
replacement: "# Lens\n\nALPHA block here.\n",
|
||||
model: "sonnet",
|
||||
sessionId: "e2e-lens-one",
|
||||
}));
|
||||
const ids = await editFlow.runEditAndPropose(doc, { kind: "document" }, "uppercase");
|
||||
await settle();
|
||||
assert.strictEqual(ids.length, 1, "one changed block → one proposal");
|
||||
|
||||
const lenses = api.editorProposalController.provideCodeLenses(doc);
|
||||
const titles = lenses.map((l) => l.command?.title);
|
||||
assert.deepStrictEqual(titles, ["✓ Keep", "✗ Reject"], "exactly one per-proposal pair, no top-of-file pair below threshold");
|
||||
});
|
||||
|
||||
test("two pending proposals → a top-of-file 'Keep all (2)'/'Reject all' pair routed at the batch commands", async () => {
|
||||
const doc = await freshDoc(
|
||||
"docs/f12-lens-two.md",
|
||||
"# Lens\n\nFirst block here.\n\nSecond block here.\n",
|
||||
);
|
||||
const api = await getApi();
|
||||
const editFlow = api.editFlow;
|
||||
editFlow.setEditTurnForTest(async () => ({
|
||||
replacement: "# Lens\n\nFIRST block here.\n\nSECOND block here.\n",
|
||||
model: "sonnet",
|
||||
sessionId: "e2e-lens-two",
|
||||
}));
|
||||
const ids = await editFlow.runEditAndPropose(doc, { kind: "document" }, "uppercase both");
|
||||
await settle();
|
||||
assert.strictEqual(ids.length, 2, "two changed blocks → two proposals");
|
||||
|
||||
const lenses = api.editorProposalController.provideCodeLenses(doc);
|
||||
const titles = lenses.map((l) => l.command?.title);
|
||||
assert.strictEqual(titles[0], "✓ Keep all (2)", "top-of-file accept-all lens leads");
|
||||
assert.strictEqual(titles[1], "✗ Reject all", "top-of-file reject-all lens follows");
|
||||
assert.deepStrictEqual(titles.slice(2), ["✓ Keep", "✗ Reject", "✓ Keep", "✗ Reject"], "per-proposal pairs follow");
|
||||
|
||||
const acceptAll = lenses[0].command!;
|
||||
const rejectAll = lenses[1].command!;
|
||||
assert.strictEqual(acceptAll.command, "cowriting.acceptAllProposals", "top-of-file accept-all routes at the batch command");
|
||||
assert.strictEqual(rejectAll.command, "cowriting.rejectAllProposals", "top-of-file reject-all routes at the batch command");
|
||||
assert.deepStrictEqual(acceptAll.arguments, undefined, "acceptAllProposals takes no arguments (targets the active editor)");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user