Files
vscode-cowriting-plugin/test/workspacePath.test.ts
Ben Stull bc77cee0bd feat(f8): isAuthorable + widened selectionRejection (file|untitled)
Spec §6.4: replace the scheme!=file / !isUnderRoot rejections with a single
!isAuthorable branch. isUnderRoot retained as a routing input. Per-condition
messaging (#24) kept for no-editor / empty-selection / non-{file,untitled}.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-11 13:06:58 -07:00

61 lines
2.6 KiB
TypeScript

import { describe, it, expect } from "vitest";
import { isAuthorable, 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("isAuthorable", () => {
it("accepts file: and untitled: schemes", () => {
expect(isAuthorable("file")).toBe(true);
expect(isAuthorable("untitled")).toBe(true);
});
it("rejects read-only / virtual schemes", () => {
expect(isAuthorable("git")).toBe(false);
expect(isAuthorable("output")).toBe(false);
expect(isAuthorable("cowriting-baseline")).toBe(false);
expect(isAuthorable("")).toBe(false);
});
});
describe("selectionRejection — F8 widened (accepts out-of-folder + untitled)", () => {
const ok = { hasEditor: true, selectionEmpty: false, scheme: "file" };
it("accepts an in-folder / out-of-folder file (file: scheme)", () => {
expect(selectionRejection({ ...ok, scheme: "file" })).toBeNull();
});
it("accepts an untitled buffer (no longer rejected)", () => {
expect(selectionRejection({ ...ok, scheme: "untitled" })).toBeNull();
});
it("names the missing editor", () => {
expect(selectionRejection({ ...ok, hasEditor: false })).toMatch(/focus a text editor/i);
});
it("names the empty selection (only when there IS an editor)", () => {
expect(selectionRejection({ ...ok, selectionEmpty: true })).toMatch(/select some text/i);
});
it("rejects a non-{file,untitled} scheme with its own message (not 'select some text')", () => {
const msg = selectionRejection({ ...ok, scheme: "git" });
expect(msg).toMatch(/can.?t be edited|read-only|not a file/i);
expect(msg).not.toMatch(/select some text/i);
});
});