import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { resetWorkspace, useWorkspace } from "../workspace";
import { PreviewPane } from "./PreviewPane";
const { readFileMock } = vi.hoisted(() => ({ readFileMock: vi.fn() }));
vi.mock("../api", () => ({
readFile: readFileMock,
rawFileUrl: (path: string) => `/api/raw?path=${encodeURIComponent(path)}`,
}));
vi.mock("./DrawioView", () => ({
DrawioView: ({ xml }: { xml: string }) => (
{xml}
),
}));
// The heavy viewers are replaced by inspectable stubs.
vi.mock("./CodeView", () => ({
CodeView: ({ content, language }: { content: string; language: string }) => (
{content}
),
}));
vi.mock("./AsciiDocView", () => ({
AsciiDocView: ({ content }: { content: string }) => (
{content}
),
}));
describe("PreviewPane", () => {
beforeEach(() => {
resetWorkspace();
readFileMock.mockReset();
});
it("asks for a selection when no file is open", () => {
render();
expect(screen.getByText(/Select a file/)).toBeDefined();
});
it("previews code with the detected language", async () => {
readFileMock.mockResolvedValue({
path: "/w/main.go",
content: "package main",
});
render();
const view = await screen.findByTestId("code-view");
expect(view.getAttribute("data-language")).toBe("go");
expect(view.textContent).toBe("package main");
// Code has no rendered form: no mode toggle.
expect(screen.queryByRole("button", { name: "Rendered" })).toBeNull();
});
it("renders markdown by default and toggles to source", async () => {
readFileMock.mockResolvedValue({
path: "/w/README.md",
content: "# Title",
});
render();
expect(await screen.findByRole("heading", { name: "Title" })).toBeDefined();
fireEvent.click(screen.getByRole("button", { name: "Source" }));
const view = await screen.findByTestId("code-view");
expect(view.getAttribute("data-language")).toBe("markdown");
expect(view.textContent).toBe("# Title");
});
it("renders asciidoc through the AsciiDoc viewer", async () => {
readFileMock.mockResolvedValue({ path: "/w/book.adoc", content: "= Book" });
render();
expect(await screen.findByTestId("asciidoc-view")).toBeDefined();
});
it("sends the file to the editor with the Edit button", async () => {
readFileMock.mockResolvedValue({
path: "/w/main.go",
content: "package main",
});
render();
await screen.findByTestId("code-view");
fireEvent.click(screen.getByRole("button", { name: "Edit" }));
expect(useWorkspace.getState()).toMatchObject({
tab: "editor",
editorPath: "/w/main.go",
});
});
it("shows images from the raw endpoint without reading them as text", async () => {
render();
const img = (await screen.findByRole("img", {
name: "logo.png",
})) as HTMLImageElement;
expect(img.getAttribute("src")).toBe(
"/api/raw?path=%2Fw%2Fdocs%2Flogo.png",
);
expect(readFileMock).not.toHaveBeenCalled();
// Images have no text form: no toggle, no Edit button.
expect(screen.queryByRole("button", { name: "Source" })).toBeNull();
expect(screen.queryByRole("button", { name: "Edit" })).toBeNull();
Object.defineProperty(img, "naturalWidth", { value: 640 });
Object.defineProperty(img, "naturalHeight", { value: 480 });
fireEvent.load(img);
expect(screen.getByText("640 × 480 px")).toBeDefined();
});
it("treats .drawio.svg exports as images", async () => {
render();
expect(
await screen.findByRole("img", { name: "flow.drawio.svg" }),
).toBeDefined();
});
it("renders draw.io diagrams through the viewer and toggles to the XML source", async () => {
readFileMock.mockResolvedValue({
path: "/w/docs/arch.drawio",
content: "",
});
render();
const view = await screen.findByTestId("drawio-view");
expect(view.textContent).toBe("");
fireEvent.click(screen.getByRole("button", { name: "Source" }));
const code = await screen.findByTestId("code-view");
expect(code.getAttribute("data-language")).toBe("xml");
expect(code.textContent).toBe("");
});
it("surfaces read errors", async () => {
readFileMock.mockRejectedValue(new Error("not a text file"));
render();
await waitFor(() =>
expect(screen.getByText("not a text file")).toBeDefined(),
);
});
});