0bdee1c286
cowriting.editSelection collapsed four distinct failures (no editor / no selection / unsaved / outside the workspace folder) into one misleading "select some text in a workspace document first" warning — so editing a selection in a file OUTSIDE the EDH workspace root (e.g. a content-repo file while the EDH opens sandbox/) was rejected as if no text were selected. - New pure, vscode-free src/workspacePath.ts: isUnderRoot() (separator-bounded membership, fixing a latent startsWith() prefix collision where a sibling whose name prefixes the root — e.g. vscode-cowriting-plugin-content vs vscode-cowriting-plugin — falsely matched) + selectionRejection() (one message per condition). Unit-tested (8 cases), incl. the reported bug. - Wire isUnderRoot into all five membership checks: extension.ts (editSelection guard + renderIfOpen) and the thread/attribution/proposal controllers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
56 lines
2.2 KiB
TypeScript
56 lines
2.2 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { isUnderRoot, selectionRejection } from "../src/workspacePath";
|
|
|
|
describe("isUnderRoot", () => {
|
|
const root = "/a/vscode-cowriting-plugin/sandbox";
|
|
|
|
it("accepts the root itself and files inside it", () => {
|
|
expect(isUnderRoot(root, root)).toBe(true);
|
|
expect(isUnderRoot(`${root}/docs/x.md`, root)).toBe(true);
|
|
});
|
|
|
|
it("rejects files outside the root", () => {
|
|
expect(isUnderRoot("/a/other/x.md", root)).toBe(false);
|
|
});
|
|
|
|
it("rejects a SIBLING whose path is a string-prefix of the root's parent (the prefix-collision bug)", () => {
|
|
// The reported case: EDH root is .../vscode-cowriting-plugin/sandbox, the file
|
|
// is in the sibling content repo. Plain startsWith on the plugin-repo prefix
|
|
// would falsely match; the separator boundary must reject it.
|
|
const pluginRoot = "/a/vscode-cowriting-plugin";
|
|
const contentFile = "/a/vscode-cowriting-plugin-content/issues/x.md";
|
|
expect(contentFile.startsWith(pluginRoot)).toBe(true); // the latent bug
|
|
expect(isUnderRoot(contentFile, pluginRoot)).toBe(false); // fixed
|
|
});
|
|
});
|
|
|
|
describe("selectionRejection", () => {
|
|
const root = "/ws";
|
|
const ok = { hasEditor: true, selectionEmpty: false, scheme: "file", fsPath: "/ws/a.md", root };
|
|
|
|
it("returns null when everything is valid", () => {
|
|
expect(selectionRejection(ok)).toBeNull();
|
|
});
|
|
|
|
it("names the missing editor", () => {
|
|
const msg = selectionRejection({ ...ok, hasEditor: false });
|
|
expect(msg).toMatch(/focus a text editor/i);
|
|
});
|
|
|
|
it("names the empty selection (only when there IS an editor)", () => {
|
|
const msg = selectionRejection({ ...ok, selectionEmpty: true });
|
|
expect(msg).toMatch(/select some text/i);
|
|
});
|
|
|
|
it("names an unsaved/non-file document", () => {
|
|
const msg = selectionRejection({ ...ok, scheme: "untitled" });
|
|
expect(msg).toMatch(/save this document/i);
|
|
});
|
|
|
|
it("names an out-of-workspace file (the reported bug — NOT a selection problem)", () => {
|
|
const msg = selectionRejection({ ...ok, fsPath: "/elsewhere/a.md" });
|
|
expect(msg).toMatch(/outside your workspace folder/i);
|
|
expect(msg).not.toMatch(/select some text/i);
|
|
});
|
|
});
|