import { describe, expect, it } from "vitest"; import { renderToStaticMarkup } from "react-dom/server"; import type { WorkspaceFileContent } from "@paperclipai/shared"; import type { FileViewerUrlState } from "@/context/FileViewerContext"; import { ThemeProvider } from "@/context/ThemeContext"; import { describeDenial, FileContentViewer, FileViewerMetadataRow } from "./FileViewerSheet"; describe("describeDenial", () => { it("returns the curated body for too_large regardless of fallback", () => { expect(describeDenial("too_large", "").body).toBe( "This file exceeds the supported preview size.", ); expect(describeDenial("too_large", "ignored fallback").body).toBe( "This file exceeds the supported preview size.", ); }); it("does not leak the raw denial code as body when fallback is empty", () => { for (const code of [ "denied_by_policy_sensitive", "outside_workspace_root", "workspace_archived", "binary_unsupported", "remote_preview_unsupported", ]) { const { body } = describeDenial(code, ""); expect(body).not.toBe(code); expect(body).not.toMatch(/^[a-z_]+$/); } }); it("describes unsupported previews in terms of text, image, and video", () => { expect(describeDenial("unsupported_content", "").body).toBe( "This file does not have a text, image, or video preview available.", ); }); it("falls back to the generic message for unknown codes with empty fallback", () => { const { body, title } = describeDenial("", ""); expect(title).toBe("Can't preview this file"); expect(body).toBe("The viewer was unable to load this file."); }); it("prefers a human-readable server message for unknown codes", () => { const { body } = describeDenial("unknown_code", "Server refused the request."); expect(body).toBe("Server refused the request."); }); }); describe("FileContentViewer", () => { function content(overrides: Partial = {}): WorkspaceFileContent { return { resource: { kind: "file", provider: "local_fs", title: "notes.txt", displayPath: "notes.txt", workspaceLabel: "Workspace", workspaceKind: "project_workspace", workspaceId: "11111111-1111-4111-8111-111111111111", contentType: "text/plain; charset=utf-8", byteSize: 18, previewKind: "text", capabilities: { preview: true, download: true, listChildren: false }, }, content: { encoding: "utf8", data: "one very long line", }, ...overrides, }; } it("wraps text content instead of forcing horizontal preformatted scrolling", () => { const markup = renderToStaticMarkup(); expect(markup).toContain("whitespace-pre-wrap"); expect(markup).toContain("break-words"); }); it("reserves code gutter space for at least four digit line numbers", () => { const markup = renderToStaticMarkup( , ); expect(markup).toContain("calc(4ch + 2rem)"); }); it("renders video previews with native controls", () => { const markup = renderToStaticMarkup( , ); expect(markup).toContain(" { const markup = renderToStaticMarkup( , ); expect(markup).toContain("Markdown preview mode"); expect(markup).toContain("Show rendered Markdown"); expect(markup).toContain("Show raw Markdown"); expect(markup).toContain("README.md rendered Markdown"); expect(markup).not.toContain("README.md source"); }); it("does not show the Markdown toggle for non-Markdown text files", () => { const markup = renderToStaticMarkup(); expect(markup).not.toContain("Markdown preview mode"); expect(markup).toContain("notes.txt source"); }); }); describe("FileViewerMetadataRow", () => { const state: FileViewerUrlState = { path: "videos/90-days-paperclip/tweet.md", workspace: "auto", line: null, column: null, projectId: null, workspaceId: null, }; it("reserves metadata row height while file details load", () => { const markup = renderToStaticMarkup(); expect(markup).toContain("min-h-(--sz-18px)"); expect(markup).toContain("Loading file details"); }); }); describe("workspace CSV content", () => { const csv: WorkspaceFileContent = { resource: { kind: "file", provider: "git_worktree", title: "export.csv", displayPath: "export.csv", workspaceLabel: "Workspace", workspaceKind: "execution_workspace", workspaceId: "ws", contentType: "text/csv", byteSize: 20, previewKind: "text", capabilities: { preview: true, download: true, listChildren: false } }, content: { encoding: "utf8", data: 'Name,Amount\n"Lee, Sam",12' }, }; it("defaults to a table and supports toolbar-controlled raw mode", () => { const rendered = renderToStaticMarkup(); expect(rendered).toContain("); expect(raw).toContain("export.csv source"); expect(raw).not.toContain(" { it.each([[500, 2], [501, 2], [1, 100], [1, 101]])( "bounds a file with %i rows and %i columns and preserves raw source", (rowCount, columnCount) => { const header = Array.from({ length: columnCount }, (_, i) => `Header${i}`).join(","); const row = Array.from({ length: columnCount }, (_, i) => `Value${i}`).join(","); const source = [header, ...Array.from({ length: rowCount }, () => row)].join("\n"); const content: WorkspaceFileContent = { resource: { kind: "file", provider: "git_worktree", title: "limits.csv", displayPath: "limits.csv", workspaceLabel: "Workspace", workspaceKind: "execution_workspace", workspaceId: "ws", contentType: "text/csv", byteSize: source.length, previewKind: "text", capabilities: { preview: true, download: true, listChildren: false } }, content: { encoding: "utf8", data: source }, }; const rendered = renderToStaticMarkup(); expect((rendered.match(/ 500 || columnCount > 100) { expect(rendered).toContain(`Showing the first ${Math.min(rowCount, 500)} rows and ${Math.min(columnCount, 100)} columns.`); } else { expect(rendered).not.toContain("Showing the first"); } const raw = renderToStaticMarkup(); expect(raw).not.toContain("