import { afterEach, beforeEach, describe, expect, it, rs } from "@rstest/core";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { cleanup, render, screen, waitFor } from "@testing-library/react";
import type { PropsWithChildren } from "react";
rs.mock("@/core/artifacts/loader", () => ({
loadArtifactContent: rs.fn(),
loadArtifactContentFromToolCall: rs.fn(),
}));
import { ArtifactViewer } from "@/components/workspace/artifacts/artifact-viewer";
import { loadArtifactContent } from "@/core/artifacts/loader";
import { urlOfArtifact } from "@/core/artifacts/utils";
import { I18nProvider } from "@/core/i18n/context";
const mockedLoadArtifactContent = rs.mocked(loadArtifactContent);
const filepath = "/mnt/user-data/outputs/report.md";
const threadId = "7cfa5f8f-a2f8-47ad-acbd-da7137baf990";
function Wrapper({ children }: PropsWithChildren) {
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false } },
});
return (
{children}
);
}
function renderViewer(props?: { isMock?: boolean }) {
return render(
,
);
}
describe("ArtifactViewer", () => {
beforeEach(() => {
mockedLoadArtifactContent.mockResolvedValue({
content: "# Quarterly report\n\nRevenue is up.",
url: urlOfArtifact({ filepath, threadId }),
sha256: undefined,
truncated: false,
previewBytes: 36,
totalBytes: 36,
});
});
afterEach(() => {
cleanup();
mockedLoadArtifactContent.mockReset();
});
it("renders the markdown artifact with the app's markdown renderer", async () => {
const { container } = renderViewer();
await waitFor(() => {
expect(container.querySelector("h1")?.textContent).toContain(
"Quarterly report",
);
});
expect(container.textContent).not.toContain("# Quarterly report");
expect(mockedLoadArtifactContent).toHaveBeenCalledWith({
filepath,
threadId,
isMock: false,
full: false,
});
});
it("loads the mock artifact source when opened from a mock thread", async () => {
renderViewer({ isMock: true });
await waitFor(() => {
expect(mockedLoadArtifactContent).toHaveBeenCalledWith({
filepath,
threadId,
isMock: true,
full: false,
});
});
});
it("fetches the whole file when a truncated preview is expanded", async () => {
mockedLoadArtifactContent.mockImplementation(async ({ full }) => ({
content: full ? "# Full report\n\nEverything." : "# Full rep",
url: urlOfArtifact({ filepath, threadId }),
sha256: undefined,
truncated: !full,
previewBytes: full ? 27 : 10,
totalBytes: 27,
}));
const { container } = renderViewer();
const loadFull = await screen.findByRole("button", {
name: "Load full file",
});
loadFull.click();
await waitFor(() => {
expect(container.textContent).toContain("Everything.");
});
expect(mockedLoadArtifactContent).toHaveBeenLastCalledWith({
filepath,
threadId,
isMock: false,
full: true,
});
expect(screen.queryByRole("button", { name: "Load full file" })).toBe(null);
});
it("offers a download when the artifact cannot be loaded", async () => {
mockedLoadArtifactContent.mockRejectedValue(new Error("boom"));
renderViewer();
const download = await screen.findByRole("link", { name: /download/i });
expect(download.getAttribute("href")).toBe(
urlOfArtifact({ filepath, threadId, download: true }),
);
});
});