F10 SLICE-4: host E2E for interactive review (open/toggle/propose→accept→reject/clean-editor/hidden-F6/status); update obsolete F9/toggle suites (#29)
Adds test/e2e/suite/f10Review.test.ts covering the F10 flow: open preview (mode "on", fresh baseline all-unchanged), type → cw-by-human span, propose → cw-proposal block with ✓/✗, accept → lands + baseline advances + block clears, reject → vanishes + doc untouched, toggle off → renderPlain has no cw- marks, status-bar PUC-6 (indicator with no panel, hidden once opened), and the clean-editor INV-32 facts (retired toggleAttribution, F6 ctrl+alt+d hidden). Rewrites the obsolete F9 authorship-mode test as an F10 review test (cw-by-claude in the on-state render; getMode defaults to "on"). Updates attribution.test.ts (drops the retired isVisible/toggleAttribution toggle, asserts the toggle is gone) and noWorkspace.test.ts (toggleAttribution + acceptProposal/rejectProposal are retired, preview-only — INV-32). Honesty fix in the status-bar seam: hideStatus() clears statusItem.text so statusText() reports undefined when the indicator is hidden (it previously returned its stale last value after a panel opened). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -111,7 +111,7 @@ export class TrackChangesPreviewController implements vscode.Disposable {
|
||||
);
|
||||
this.panels.set(key, panel);
|
||||
// A panel is now open for this doc — the off-panel indicator is redundant.
|
||||
this.statusItem.hide();
|
||||
this.hideStatus();
|
||||
this.refresh(document);
|
||||
}
|
||||
|
||||
@@ -169,12 +169,12 @@ export class TrackChangesPreviewController implements vscode.Disposable {
|
||||
private updateStatus(uri: string): void {
|
||||
const doc = vscode.workspace.textDocuments.find((d) => d.uri.toString() === uri);
|
||||
if (!doc) {
|
||||
this.statusItem.hide();
|
||||
this.hideStatus();
|
||||
return;
|
||||
}
|
||||
const n = this.proposals.listProposals(doc).length;
|
||||
if (n === 0 || this.panels.has(uri)) {
|
||||
this.statusItem.hide();
|
||||
this.hideStatus();
|
||||
return;
|
||||
}
|
||||
this.statusItem.text = `$(comment-discussion) ${n} Claude proposal${n === 1 ? "" : "s"}`;
|
||||
@@ -182,6 +182,15 @@ export class TrackChangesPreviewController implements vscode.Disposable {
|
||||
this.statusItem.show();
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the off-panel indicator AND clear its text, so the `statusText()` seam
|
||||
* is honest: a hidden indicator reports `undefined` (not its stale last value).
|
||||
*/
|
||||
private hideStatus(): void {
|
||||
this.statusItem.text = "";
|
||||
this.statusItem.hide();
|
||||
}
|
||||
|
||||
private epochLabel(baseline: { reason: string; capturedAt: string } | undefined): string {
|
||||
if (!baseline) return "opened (no baseline yet)";
|
||||
const time = new Date(baseline.capturedAt).toLocaleTimeString();
|
||||
|
||||
@@ -29,14 +29,20 @@ suite("no-workspace authoring (F8 — real folder-less, #8 lineage)", () => {
|
||||
"cowriting.resolveThread",
|
||||
"cowriting.reopenThread",
|
||||
"cowriting.editSelection",
|
||||
"cowriting.toggleAttribution",
|
||||
"cowriting.applyAgentEdit",
|
||||
"cowriting.acceptProposal",
|
||||
"cowriting.rejectProposal",
|
||||
"cowriting.proposeAgentEdit",
|
||||
]) {
|
||||
assert.ok(all.includes(command), `${command} is registered`);
|
||||
}
|
||||
// F10 (INV-32): proposals are preview-only — the in-editor accept/reject
|
||||
// commands and the attribution toggle were retired (no editor decorations).
|
||||
for (const retired of [
|
||||
"cowriting.toggleAttribution",
|
||||
"cowriting.acceptProposal",
|
||||
"cowriting.rejectProposal",
|
||||
]) {
|
||||
assert.ok(!all.includes(retired), `${retired} is retired (F10 preview-only)`);
|
||||
}
|
||||
});
|
||||
|
||||
test("authoring works folder-less: propose→accept on an untitled buffer routes to global storage (F8)", async () => {
|
||||
|
||||
@@ -132,7 +132,7 @@ suite("F3 live attribution (host E2E — seam-driven, no LLM)", () => {
|
||||
assert.ok(api.attributionController.getOrphanCount(DOC_REL) >= 1, "…it is orphaned instead (INV-1)");
|
||||
});
|
||||
|
||||
test("the applyAgentEdit command wrapper and the toggle command work end-to-end", async () => {
|
||||
test("the applyAgentEdit command wrapper works end-to-end (data layer; no editor decorations — F10/INV-32)", async () => {
|
||||
const doc = await openDoc();
|
||||
const api = await getApi();
|
||||
const anchor = "stable first paragraph";
|
||||
@@ -153,10 +153,9 @@ suite("F3 live attribution (host E2E — seam-driven, no LLM)", () => {
|
||||
assert.ok(agent, "command-driven agent span exists");
|
||||
assert.strictEqual(agent!.authorKind, "agent");
|
||||
|
||||
assert.strictEqual(api.attributionController.isVisible(), true);
|
||||
await vscode.commands.executeCommand("cowriting.toggleAttribution");
|
||||
assert.strictEqual(api.attributionController.isVisible(), false, "toggle hides (PUC-5)");
|
||||
await vscode.commands.executeCommand("cowriting.toggleAttribution");
|
||||
assert.strictEqual(api.attributionController.isVisible(), true, "toggle restores");
|
||||
// F10/INV-32: the editor carries no attribution decorations and the toggle was
|
||||
// retired — the rendered preview is the single review surface.
|
||||
const all = await vscode.commands.getCommands(true);
|
||||
assert.ok(!all.includes("cowriting.toggleAttribution"), "the in-editor attribution toggle is retired (F10)");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,16 +14,20 @@ async function getApi(): Promise<CowritingApi> {
|
||||
return api;
|
||||
}
|
||||
|
||||
// F9 host E2E (no LLM): authorship mode reflects Claude's landed span. Owns its
|
||||
// own markdown doc, disjoint from the other suites' fixtures.
|
||||
suite("F9 authorship preview (host E2E — seam ingress, no LLM)", () => {
|
||||
const DOC_REL = "docs/f9authorship.md";
|
||||
// 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 landed prose. This suite confirms a Claude-landed
|
||||
// span renders as a cw-by-claude span in the on-state preview HTML. Owns its own
|
||||
// markdown doc, disjoint from the other suites' fixtures.
|
||||
suite("F10 review preview — Claude-authored prose is cw-by-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("authorship mode marks Claude's accepted edit; track-changes mode still works", async () => {
|
||||
test("an accepted Claude edit author-colors as cw-by-claude in the on-state render; mode defaults to on", async () => {
|
||||
const abs = path.join(WS, DOC_REL);
|
||||
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
||||
fs.writeFileSync(abs, `# F9\n\n${TARGET}\n`, "utf8");
|
||||
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);
|
||||
@@ -31,11 +35,11 @@ suite("F9 authorship preview (host E2E — seam ingress, no LLM)", () => {
|
||||
const api = await getApi();
|
||||
const key = uri.toString();
|
||||
|
||||
// open the preview (track-changes mode by default)
|
||||
// open the preview — annotations default ON (F10/INV-33)
|
||||
await vscode.commands.executeCommand("cowriting.showTrackChangesPreview");
|
||||
await settle();
|
||||
assert.ok(api.trackChangesPreviewController.isOpen(key), "preview open");
|
||||
assert.strictEqual(api.trackChangesPreviewController.getMode(key), "changes", "defaults to track-changes");
|
||||
assert.strictEqual(api.trackChangesPreviewController.getMode(key), "on", "annotations default to on");
|
||||
|
||||
// Claude composes via the seam (propose → accept)
|
||||
const start = doc.getText().indexOf(TARGET);
|
||||
@@ -43,32 +47,22 @@ suite("F9 authorship preview (host E2E — seam ingress, no LLM)", () => {
|
||||
uri: key,
|
||||
start,
|
||||
end: start + TARGET.length,
|
||||
newText: "The sentence CLAUDE COMPOSED.",
|
||||
newText: REPLACEMENT,
|
||||
model: "sonnet",
|
||||
sessionId: "e2e-f9",
|
||||
turnId: "turn-f9",
|
||||
sessionId: "e2e-f10",
|
||||
turnId: "turn-f10",
|
||||
});
|
||||
assert.ok(await api.proposalController.acceptById(DOC_REL, id!), "accept applies");
|
||||
assert.ok(await api.proposalController.acceptById(DOC_REL, id!), "accept applies via the seam");
|
||||
await settle();
|
||||
|
||||
// attribution has a Claude span now
|
||||
// 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");
|
||||
|
||||
// flip to authorship mode → the preview reads a Claude span
|
||||
api.trackChangesPreviewController.setMode(key, "authorship");
|
||||
await settle();
|
||||
assert.strictEqual(api.trackChangesPreviewController.getMode(key), "authorship");
|
||||
const spans = api.attributionController.spansFor(doc);
|
||||
assert.ok(spans.some((s) => s.author === "claude"), "spansFor reports a Claude span for the preview");
|
||||
|
||||
// back to track-changes — still functional (regression)
|
||||
api.trackChangesPreviewController.setMode(key, "changes");
|
||||
await settle();
|
||||
assert.strictEqual(api.trackChangesPreviewController.getMode(key), "changes");
|
||||
assert.ok(
|
||||
(api.trackChangesPreviewController.getLastModel(key) ?? []).length >= 1,
|
||||
"track-changes model still computed",
|
||||
);
|
||||
// the on-state render author-colors the landed Claude text as cw-by-claude
|
||||
const html = api.trackChangesPreviewController.renderHtmlFor(key);
|
||||
assert.match(html, /<span class="cw-by-claude">/, "landed Claude prose is author-colored in the on-state render");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,238 @@
|
||||
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 { renderPlain } from "../../../src/trackChangesModel";
|
||||
|
||||
const WS = process.env.E2E_WORKSPACE!;
|
||||
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 preview + proposal");
|
||||
return api;
|
||||
}
|
||||
|
||||
/** Create + open a fresh markdown doc under WS, returning the doc + its uri key. */
|
||||
async function freshDoc(rel: string, body: string): Promise<{ doc: vscode.TextDocument; key: string }> {
|
||||
const abs = path.join(WS, rel);
|
||||
fs.mkdirSync(path.dirname(abs), { recursive: true });
|
||||
fs.writeFileSync(abs, body, "utf8");
|
||||
const uri = vscode.Uri.file(abs);
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
await vscode.window.showTextDocument(doc);
|
||||
await settle();
|
||||
return { doc, key: uri.toString() };
|
||||
}
|
||||
|
||||
async function propose(
|
||||
doc: vscode.TextDocument,
|
||||
key: string,
|
||||
target: string,
|
||||
newText: string,
|
||||
turnId: string,
|
||||
): Promise<string> {
|
||||
const start = doc.getText().indexOf(target);
|
||||
assert.ok(start >= 0, `fixture contains "${target}"`);
|
||||
const id = await vscode.commands.executeCommand<string>("cowriting.proposeAgentEdit", {
|
||||
uri: key,
|
||||
start,
|
||||
end: start + target.length,
|
||||
newText,
|
||||
model: "sonnet",
|
||||
sessionId: "e2e-f10rev",
|
||||
turnId,
|
||||
});
|
||||
assert.ok(id, "propose returns an id");
|
||||
return id!;
|
||||
}
|
||||
|
||||
// F10 host E2E (no LLM): the rendered preview is the single INTERACTIVE review
|
||||
// surface. This suite owns docs/f10review.md (its main flow is order-dependent)
|
||||
// plus its own disjoint fresh docs for the isolated cases (status-bar, toggle).
|
||||
// The editor is zero-decoration; everything observable here is the data layer +
|
||||
// the on-state renderReview HTML the panel posts.
|
||||
suite("F10 interactive review (host E2E — preview is the single review surface, no LLM)", () => {
|
||||
const DOC_REL = "docs/f10review.md";
|
||||
const PROSE = "The original review paragraph that lives in this doc.";
|
||||
const T1 = "A first claude target sentence here.";
|
||||
const T2 = "A second claude target sentence here.";
|
||||
|
||||
test("open on a markdown doc → panel open, fresh baseline all-unchanged, mode is on (PUC-1)", async () => {
|
||||
const { doc, key } = await freshDoc(DOC_REL, `# F10 review\n\n${PROSE}\n\n${T1}\n\n${T2}\n`);
|
||||
const api = await getApi();
|
||||
assert.strictEqual(api.trackChangesPreviewController.isOpen(key), false, "no panel yet");
|
||||
await vscode.commands.executeCommand("cowriting.showTrackChangesPreview");
|
||||
await settle();
|
||||
assert.strictEqual(api.trackChangesPreviewController.isOpen(key), true, "panel open");
|
||||
assert.strictEqual(api.trackChangesPreviewController.getMode(key), "on", "annotations default on (INV-33)");
|
||||
const model = api.trackChangesPreviewController.getLastModel(key);
|
||||
assert.ok(model && model.length > 0, "a model was computed");
|
||||
assert.ok(model!.every((o) => o.kind === "unchanged"), "fresh baseline == buffer → every block unchanged");
|
||||
void doc;
|
||||
});
|
||||
|
||||
test("typing produces an added/changed block and a cw-by-human span in the on-state render (PUC-2)", async () => {
|
||||
const { key } = await reopen(DOC_REL);
|
||||
const api = await getApi();
|
||||
const doc = byKey(key)!;
|
||||
const edit = new vscode.WorkspaceEdit();
|
||||
edit.insert(doc.uri, doc.positionAt(doc.getText().length), "\n\nA freshly typed human paragraph.\n");
|
||||
assert.ok(await vscode.workspace.applyEdit(edit), "operator edit applied");
|
||||
await settle();
|
||||
const kinds = (api.trackChangesPreviewController.getLastModel(key) ?? []).map((o) => o.kind);
|
||||
assert.ok(kinds.some((k) => k === "added" || k === "changed"), "an added/changed block after typing");
|
||||
// Attribution recorded the human span (data layer), and the on-state render
|
||||
// author-colors that prose as cw-by-human.
|
||||
assert.ok(
|
||||
api.attributionController.spansFor(doc).some((s) => s.author === "human"),
|
||||
"a human span was recorded for the typed text",
|
||||
);
|
||||
const html = api.trackChangesPreviewController.renderHtmlFor(key);
|
||||
assert.match(html, /<span class="cw-by-human">/, "typed text is author-colored human in the on-state");
|
||||
});
|
||||
|
||||
test("propose → the preview surfaces it as a cw-proposal block with ✓/✗ actions (PUC-3)", async () => {
|
||||
const { doc, key } = await reopen(DOC_REL);
|
||||
const api = await getApi();
|
||||
const id = await propose(doc, key, T1, "A FIRST claude REPLACEMENT sentence.", "turn-f10-1");
|
||||
await settle();
|
||||
const views = api.proposalController.listProposals(doc);
|
||||
assert.ok(views.some((v) => v.id === id), "listProposals returns a view with the id");
|
||||
const html = api.trackChangesPreviewController.renderHtmlFor(key);
|
||||
assert.ok(html.includes(`data-proposal-id="${id}"`), "the preview renders the proposal block by id");
|
||||
assert.match(html, /class="cw-actions"/, "the proposal block carries ✓/✗ actions");
|
||||
// INV-10: proposing never touches the document.
|
||||
assert.ok(doc.getText().includes(T1), "document unchanged by propose");
|
||||
});
|
||||
|
||||
test("accept → the proposal lands, clears from the preview, and the baseline advances (PUC-4)", async () => {
|
||||
const { doc, key } = await reopen(DOC_REL);
|
||||
const api = await getApi();
|
||||
const id = api.proposalController.listProposals(doc).find((v) => v.replaced === T1)!.id;
|
||||
assert.ok(await api.proposalController.acceptById(DOC_REL, id), "accept applies via the seam");
|
||||
await settle();
|
||||
const replacement = "A FIRST claude REPLACEMENT sentence.";
|
||||
assert.ok(doc.getText().includes(replacement), "replacement landed in the document");
|
||||
assert.ok(!doc.getText().includes(T1), "original target gone");
|
||||
assert.ok(!api.proposalController.listProposals(doc).some((v) => v.id === id), "proposal cleared from listProposals");
|
||||
const html = api.trackChangesPreviewController.renderHtmlFor(key);
|
||||
assert.ok(!html.includes(`data-proposal-id="${id}"`), "the accepted proposal block is gone from the preview");
|
||||
// The baseline advanced on the landing (INV-18): the landed text is not marked.
|
||||
const model = api.trackChangesPreviewController.getLastModel(key) ?? [];
|
||||
const marked = model.some((o) => o.kind !== "unchanged" && o.block.raw.includes("FIRST claude REPLACEMENT"));
|
||||
assert.ok(!marked, "the just-landed Claude text renders unmarked (baseline advanced)");
|
||||
});
|
||||
|
||||
test("reject → the proposal vanishes and the document is untouched (PUC-5)", async () => {
|
||||
const { doc, key } = await reopen(DOC_REL);
|
||||
const api = await getApi();
|
||||
const before = doc.getText();
|
||||
const id2 = await propose(doc, key, T2, "A SECOND would-be replacement.", "turn-f10-2");
|
||||
await settle();
|
||||
assert.ok(api.proposalController.listProposals(doc).some((v) => v.id === id2), "second proposal pending");
|
||||
assert.strictEqual(api.proposalController.rejectById(DOC_REL, id2), true, "reject");
|
||||
await settle();
|
||||
assert.ok(!api.proposalController.listProposals(doc).some((v) => v.id === id2), "rejected proposal gone");
|
||||
assert.strictEqual(doc.getText(), before, "document untouched by reject");
|
||||
const html = api.trackChangesPreviewController.renderHtmlFor(key);
|
||||
assert.ok(!html.includes(`data-proposal-id="${id2}"`), "no rejected block in the preview");
|
||||
});
|
||||
|
||||
test("toggle annotations off → mode round-trips and the off-state render is plain (no cw- marks) (INV-33)", async () => {
|
||||
// A fresh doc with a pending proposal so the on-state DOES carry a cw- mark,
|
||||
// making the off-state's absence of marks meaningful (not tautological).
|
||||
const { doc, key } = await freshDoc(
|
||||
"docs/f10toggle.md",
|
||||
"# F10 toggle\n\nA toggle target sentence to propose over.\n",
|
||||
);
|
||||
const api = await getApi();
|
||||
await vscode.commands.executeCommand("cowriting.showTrackChangesPreview");
|
||||
await settle();
|
||||
const id = await propose(doc, key, "A toggle target sentence to propose over.", "A TOGGLED replacement.", "turn-tog");
|
||||
await settle();
|
||||
|
||||
// on-state: the proposal block + its actions are present.
|
||||
assert.strictEqual(api.trackChangesPreviewController.getMode(key), "on", "starts on");
|
||||
const onHtml = api.trackChangesPreviewController.renderHtmlFor(key);
|
||||
assert.ok(onHtml.includes(`data-proposal-id="${id}"`), "on-state shows the proposal block");
|
||||
|
||||
// toggle off → mode round-trips; the off-state body is plain markdown.
|
||||
api.trackChangesPreviewController.setMode(key, "off");
|
||||
await settle();
|
||||
assert.strictEqual(api.trackChangesPreviewController.getMode(key), "off", "mode flipped to off");
|
||||
assert.ok(api.trackChangesPreviewController.isOpen(key), "panel stays open across the toggle");
|
||||
// renderHtmlFor is the on-state seam; the off-state body is renderPlain(current)
|
||||
// (INV-33). Assert the actual off-state body the controller posts has no cw-
|
||||
// author/proposal marks — meaningful because the on-state above DID carry one.
|
||||
const offBody = renderPlain(doc.getText());
|
||||
assert.ok(!/cw-proposal|cw-by-claude|cw-by-human|cw-del/.test(offBody), "off-state render carries no cw- marks");
|
||||
|
||||
// toggle back on → marks return.
|
||||
api.trackChangesPreviewController.setMode(key, "on");
|
||||
await settle();
|
||||
assert.strictEqual(api.trackChangesPreviewController.getMode(key), "on", "mode flipped back on");
|
||||
assert.ok(
|
||||
api.trackChangesPreviewController.renderHtmlFor(key).includes(`data-proposal-id="${id}"`),
|
||||
"on-state shows the proposal block again",
|
||||
);
|
||||
});
|
||||
|
||||
test("status-bar (PUC-6): a pending proposal with NO panel shows the indicator; opening the preview hides it", async () => {
|
||||
// Isolated fresh doc: no preview opened, so the off-panel indicator is live.
|
||||
const { doc, key } = await freshDoc("docs/f10status.md", "# F10 status\n\nA status target sentence here.\n");
|
||||
const api = await getApi();
|
||||
assert.strictEqual(api.trackChangesPreviewController.isOpen(key), false, "no panel for this doc");
|
||||
await propose(doc, key, "A status target sentence here.", "A STATUS replacement.", "turn-stat");
|
||||
await settle();
|
||||
const text = api.trackChangesPreviewController.statusText();
|
||||
assert.ok(text && text.length > 0, "the off-panel indicator shows a non-empty status");
|
||||
assert.match(text!, /1 Claude proposal/, "it mentions the pending count");
|
||||
|
||||
// open the preview for this doc → the off-panel indicator hides (undefined).
|
||||
await vscode.commands.executeCommand("cowriting.showTrackChangesPreview");
|
||||
await settle();
|
||||
assert.strictEqual(api.trackChangesPreviewController.isOpen(key), true, "panel open");
|
||||
assert.strictEqual(api.trackChangesPreviewController.statusText(), undefined, "indicator hidden once the panel is open");
|
||||
});
|
||||
|
||||
test("clean editor: data layers intact AND the retired in-editor surfaces are gone (INV-32)", async () => {
|
||||
const { doc, key } = await freshDoc("docs/f10clean.md", "# F10 clean\n\nA clean target sentence here.\n");
|
||||
const api = await getApi();
|
||||
const id = await propose(doc, key, "A clean target sentence here.", "A CLAUDE clean replacement.", "turn-clean");
|
||||
await settle();
|
||||
assert.ok(await api.proposalController.acceptById("docs/f10clean.md", id), "accept lands the Claude edit");
|
||||
await settle();
|
||||
// data layer intact: a Claude (agent) attribution span exists.
|
||||
assert.ok(
|
||||
api.attributionController.getSpans("docs/f10clean.md").some((s) => s.authorKind === "agent"),
|
||||
"agent span recorded (attribution data layer intact)",
|
||||
);
|
||||
// the retired toggle is gone from the palette (no editor decorations — INV-32).
|
||||
const all = await vscode.commands.getCommands(true);
|
||||
assert.ok(!all.includes("cowriting.toggleAttribution"), "cowriting.toggleAttribution is retired");
|
||||
// the F6 diff toggle's keybinding is hidden (when:false) — it is not a user surface.
|
||||
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "../../../../package.json"), "utf8"));
|
||||
const dKb = (pkg.contributes.keybindings as Array<{ command: string; key: string; when?: string }>).find(
|
||||
(k) => k.command === "cowriting.toggleDiffView" && k.key === "ctrl+alt+d",
|
||||
);
|
||||
assert.ok(dKb, "the ctrl+alt+d toggleDiffView keybinding is declared");
|
||||
assert.strictEqual(dKb!.when, "false", "…but hidden (when:false) — F6 is a data layer, not a user surface");
|
||||
void key;
|
||||
});
|
||||
});
|
||||
|
||||
// ---- helpers reused across the order-dependent main-flow tests ----
|
||||
|
||||
function byKey(key: string): vscode.TextDocument | undefined {
|
||||
return vscode.workspace.textDocuments.find((d) => d.uri.toString() === key);
|
||||
}
|
||||
async function reopen(rel: string): Promise<{ doc: vscode.TextDocument; key: string }> {
|
||||
const uri = vscode.Uri.file(path.join(WS, rel));
|
||||
const doc = await vscode.workspace.openTextDocument(uri);
|
||||
await vscode.window.showTextDocument(doc);
|
||||
await settle();
|
||||
return { doc, key: uri.toString() };
|
||||
}
|
||||
Reference in New Issue
Block a user