Files
vscode-cowriting-plugin/test/e2e/suite/reviewPanelMenu.test.ts
T

162 lines
8.4 KiB
TypeScript

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";
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?.coeditingRegistry, "exports coeditingRegistry");
return api;
}
/** Write a markdown file under WS and return its uri (without opening it). */
function writeFile(rel: string, body: string): vscode.Uri {
const abs = path.join(WS, rel);
fs.mkdirSync(path.dirname(abs), { recursive: true });
fs.writeFileSync(abs, body, "utf8");
return vscode.Uri.file(abs);
}
function pkg(): any {
return JSON.parse(fs.readFileSync(path.join(__dirname, "../../../../package.json"), "utf8"));
}
// Task 8 (native-surfaces migration, spec §6.10): the review-webview's #41
// right-click entries ("Open Cowriting Review Panel") are replaced by
// `cowriting.openReviewPreview` — a minimal wrapper that resolves the clicked
// document (or falls back to the active editor, mirroring #41's original
// behavior), enters coediting if not already (so there is a baseline to review
// against), and hands off to VS Code's OWN "Open Preview to the Side"
// (`markdown.showPreviewToSide`) — the built-in preview IS the review surface
// now (Task 7's annotations render inside it). There is no controller-level
// `isOpen` seam anymore (no bespoke webview to introspect); these tests observe
// the command's OWN side effects instead — which document it focused/entered
// coediting on — the same signal #41's original suite checked via `isOpen`.
suite("Open Cowriting Review Preview (host E2E — menu wiring + clicked-doc resolution, Task 8)", () => {
test("command with a Uri targets the CLICKED doc, not the active editor", async () => {
// Two markdown docs; A is the active editor, B is the right-clicked target.
const aUri = writeFile("docs/menu-active.md", "# Active\n\nThe active editor's document.\n");
const bUri = writeFile("docs/menu-clicked.md", "# Clicked\n\nThe right-clicked document.\n");
const aDoc = await vscode.workspace.openTextDocument(aUri);
await vscode.window.showTextDocument(aDoc);
const bDoc = await vscode.workspace.openTextDocument(bUri); // open but NOT active
await settle();
const api = await getApi();
await vscode.commands.executeCommand("cowriting.openReviewPreview", bUri);
await settle();
// `markdown.showPreviewToSide` itself ends with the preview WEBVIEW focused
// (not a text editor), so `activeTextEditor` is not a stable post-command
// signal — `isCoediting` (set by the command's OWN resolve-and-enter step,
// before it hands off to the preview) is: the clicked doc B entered, the
// still-open-but-not-clicked doc A did not.
assert.strictEqual(api.coeditingRegistry.isCoediting(bUri), true, "the clicked doc B entered coediting (a baseline to review against)");
assert.strictEqual(api.coeditingRegistry.isCoediting(aUri), false, "the active-but-not-clicked doc A did NOT enter coediting");
assert.ok(bDoc, "clicked doc handle held");
});
test("command with a Uri for a not-yet-open file opens it and focuses it (explorer case)", async () => {
// Write a file but do not openTextDocument it — mimics an Explorer right-click.
const uri = writeFile("docs/menu-unopened.md", "# Unopened\n\nNever opened before the right-click.\n");
const key = uri.toString();
const api = await getApi();
assert.ok(
!vscode.workspace.textDocuments.some((d) => d.uri.toString() === key),
"precondition: the file is not an open document",
);
await vscode.commands.executeCommand("cowriting.openReviewPreview", uri);
await settle();
assert.ok(
vscode.workspace.textDocuments.some((d) => d.uri.toString() === key),
"the clicked-but-unopened file was opened",
);
assert.strictEqual(api.coeditingRegistry.isCoediting(uri), true, "it entered coediting");
});
test("no-arg invocation still falls back to the active editor (palette / keybinding unchanged)", async () => {
const uri = writeFile("docs/menu-noarg.md", "# No-arg\n\nActive editor for the no-arg path.\n");
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc);
await settle();
const api = await getApi();
await vscode.commands.executeCommand("cowriting.openReviewPreview");
await settle();
assert.strictEqual(api.coeditingRegistry.isCoediting(uri), true, "no-arg targets the active editor");
});
test("a non-markdown doc: command warns, no coediting entered", async () => {
const uri = writeFile("docs/menu-notmd.txt", "Not markdown.\n");
const doc = await vscode.workspace.openTextDocument(uri);
await vscode.window.showTextDocument(doc);
await settle();
const api = await getApi();
assert.notStrictEqual(doc.languageId, "markdown", "fixture is not markdown");
await vscode.commands.executeCommand("cowriting.openReviewPreview");
await settle();
assert.strictEqual(api.coeditingRegistry.isCoediting(uri), false, "a non-markdown doc never enters coediting via this gateway");
});
test("explorer/context contributes the review-preview item for markdown only", () => {
const items = (pkg().contributes.menus["explorer/context"] ?? []) as Array<{ command: string; when?: string }>;
const entry = items.find((m) => m.command === "cowriting.openReviewPreview");
assert.ok(entry, "explorer/context has an openReviewPreview entry");
assert.match(entry!.when ?? "", /resourceLangId == markdown/, "gated on markdown resources");
});
test("editor/title/context (tab right-click) contributes the review-preview item for markdown only", () => {
const items = (pkg().contributes.menus["editor/title/context"] ?? []) as Array<{ command: string; when?: string }>;
const entry = items.find((m) => m.command === "cowriting.openReviewPreview");
assert.ok(entry, "editor/title/context has an openReviewPreview entry");
assert.match(entry!.when ?? "", /resourceLangId == markdown/, "gated on markdown tabs");
});
test("editor/title (toolbar icon) contributes the review-preview item for markdown only", () => {
const items = (pkg().contributes.menus["editor/title"] ?? []) as Array<{ command: string; when?: string }>;
const entry = items.find((m) => m.command === "cowriting.openReviewPreview");
assert.ok(entry, "editor/title has an openReviewPreview entry");
assert.match(entry!.when ?? "", /editorLangId == markdown/, "gated on markdown");
});
test("the command title reads 'Open Cowriting Review Preview'", () => {
const cmd = (pkg().contributes.commands as Array<{ command: string; title: string }>).find(
(c) => c.command === "cowriting.openReviewPreview",
);
assert.ok(cmd, "command is contributed");
assert.strictEqual(cmd!.title, "Open Cowriting Review Preview", "menus render this title");
});
test("the old cowriting.showTrackChangesPreview command/menus/keybinding are fully gone (Task 8 exit criterion)", () => {
const p = pkg();
const commands = p.contributes.commands as Array<{ command: string }>;
assert.ok(!commands.some((c) => c.command === "cowriting.showTrackChangesPreview"), "command contribution removed");
for (const menuId of ["commandPalette", "editor/title", "editor/title/context", "explorer/context"]) {
const items = (p.contributes.menus[menuId] ?? []) as Array<{ command: string }>;
assert.ok(!items.some((m) => m.command === "cowriting.showTrackChangesPreview"), `${menuId} no longer references it`);
}
const kb = (p.contributes.keybindings as Array<{ command: string }>).find(
(k) => k.command === "cowriting.showTrackChangesPreview",
);
assert.ok(!kb, "keybinding removed");
});
test("ctrl+alt+r now targets cowriting.reviewChanges (the native diff), not the retired preview command", () => {
const kb = (pkg().contributes.keybindings as Array<{ command: string; key: string; when?: string }>).find(
(k) => k.key === "ctrl+alt+r",
);
assert.ok(kb, "ctrl+alt+r keybinding still present");
assert.strictEqual(kb!.command, "cowriting.reviewChanges", "the chord now opens the native diff (Step 1 re-point)");
});
});