mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 17:45:53 +01:00
Adds the Phase 3 UI for the workspace file viewer: - FileViewerProvider/FileViewerSheet pair, URL-addressable via ?file=/?line=/?column=/?workspace= so browser back closes or walks back through sheet history. - Remark plugin + parser that only auto-link path refs inside inline code spans (requires a slash or a line anchor to avoid prose matches like README.md). - Shared WorkspaceFileLink primitive used by markdown output; same primitive will be reusable for run output and work-product chips. - Issue toolbar "Open file" action, Command Palette entry "Open file in this issue...", and the `g f` keyboard route. - Designed state views for not found / outside workspace / denied / too large / binary / remote-only / workspace-archived / loading progressive states, with polite live-region announcements and a keyboard-scrollable code viewport. Tests cover the parser, the remark plugin, URL state reading/writing, the keyboard shortcut, and markdown-body workspace link rendering. Co-Authored-By: Paperclip <noreply@paperclip.ing>
82 lines
2.6 KiB
TypeScript
82 lines
2.6 KiB
TypeScript
// @vitest-environment node
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
import {
|
|
readFileViewerStateFromSearch,
|
|
writeFileViewerStateToSearch,
|
|
} from "./FileViewerContext";
|
|
|
|
describe("readFileViewerStateFromSearch", () => {
|
|
it("returns null when no file param is present", () => {
|
|
expect(readFileViewerStateFromSearch("")).toBeNull();
|
|
expect(readFileViewerStateFromSearch("?other=1")).toBeNull();
|
|
});
|
|
|
|
it("reads file, line, column, workspace from the search", () => {
|
|
const state = readFileViewerStateFromSearch("?file=ui/src/a.ts&line=42&column=3&workspace=project");
|
|
expect(state).toEqual({
|
|
path: "ui/src/a.ts",
|
|
line: 42,
|
|
column: 3,
|
|
workspace: "project",
|
|
});
|
|
});
|
|
|
|
it("defaults to auto workspace when param missing", () => {
|
|
const state = readFileViewerStateFromSearch("?file=ui/src/a.ts");
|
|
expect(state?.workspace).toBe("auto");
|
|
});
|
|
|
|
it("clamps invalid workspace to auto", () => {
|
|
const state = readFileViewerStateFromSearch("?file=ui/src/a.ts&workspace=bogus");
|
|
expect(state?.workspace).toBe("auto");
|
|
});
|
|
|
|
it("treats invalid line/column as null", () => {
|
|
const state = readFileViewerStateFromSearch("?file=x.ts&line=abc&column=-1");
|
|
expect(state?.line).toBeNull();
|
|
expect(state?.column).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe("writeFileViewerStateToSearch", () => {
|
|
it("sets all params when opening", () => {
|
|
const next = writeFileViewerStateToSearch(
|
|
"?existing=1",
|
|
{ path: "ui/src/a.ts", line: 42, column: 3, workspace: "project" },
|
|
);
|
|
const params = new URLSearchParams(next);
|
|
expect(params.get("file")).toBe("ui/src/a.ts");
|
|
expect(params.get("line")).toBe("42");
|
|
expect(params.get("column")).toBe("3");
|
|
expect(params.get("workspace")).toBe("project");
|
|
expect(params.get("existing")).toBe("1");
|
|
});
|
|
|
|
it("omits workspace when auto", () => {
|
|
const next = writeFileViewerStateToSearch(
|
|
"",
|
|
{ path: "a.ts", line: null, column: null, workspace: "auto" },
|
|
);
|
|
expect(next.includes("workspace")).toBe(false);
|
|
});
|
|
|
|
it("clears viewer params when closing", () => {
|
|
const next = writeFileViewerStateToSearch(
|
|
"?file=a.ts&line=1&column=2&workspace=project&keep=yes",
|
|
null,
|
|
);
|
|
const params = new URLSearchParams(next);
|
|
expect(params.get("file")).toBeNull();
|
|
expect(params.get("line")).toBeNull();
|
|
expect(params.get("column")).toBeNull();
|
|
expect(params.get("workspace")).toBeNull();
|
|
expect(params.get("keep")).toBe("yes");
|
|
});
|
|
|
|
it("returns empty string when no params remain", () => {
|
|
const next = writeFileViewerStateToSearch("?file=a.ts", null);
|
|
expect(next).toBe("");
|
|
});
|
|
});
|