import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import type {
BlobResourceContents,
ContentBlock,
TextResourceContents,
} from "@modelcontextprotocol/sdk/types.js";
import { renderWithMantine, screen } from "../../../test/renderWithMantine";
import { ContentViewer } from "./ContentViewer";
// Stub the lazy highlighter so JSON/XML/CSS branches are assertable
// synchronously (its real dynamic-import behavior is covered in
// CodeHighlight.test.tsx).
vi.mock("../CodeHighlight/CodeHighlight", () => ({
CodeHighlight: ({ language, code }: { language: string; code: string }) => (
{code}
),
}));
const toBase64 = (text: string): string =>
Buffer.from(text, "utf-8").toString("base64");
describe("ContentViewer", () => {
it("renders text content as-is when not JSON", () => {
const block: ContentBlock = { type: "text", text: "hello world" };
renderWithMantine();
expect(screen.getByText("hello world")).toBeInTheDocument();
});
it("pretty-prints JSON text", () => {
const block: ContentBlock = { type: "text", text: '{"a":1}' };
renderWithMantine();
expect(screen.getByText(/"a": 1/)).toBeInTheDocument();
});
it("falls back to raw text when JSON is malformed", () => {
const block: ContentBlock = { type: "text", text: "{ broken" };
renderWithMantine();
expect(screen.getByText("{ broken")).toBeInTheDocument();
});
it("renders a copy overlay when copyable", () => {
const block: ContentBlock = { type: "text", text: "hello" };
renderWithMantine();
expect(screen.getByRole("button")).toBeInTheDocument();
});
it("does not render a copy overlay by default", () => {
const block: ContentBlock = { type: "text", text: "hello" };
renderWithMantine();
expect(screen.queryByRole("button")).not.toBeInTheDocument();
});
it("renders an image block", () => {
const block: ContentBlock = {
type: "image",
mimeType: "image/png",
data: "AAAA",
};
renderWithMantine();
expect(screen.getByRole("img")).toHaveAttribute(
"src",
"data:image/png;base64,AAAA",
);
});
it("renders an audio block", () => {
const block: ContentBlock = {
type: "audio",
mimeType: "audio/mp3",
data: "BBBB",
};
const { container } = renderWithMantine();
const source = container.querySelector("source");
expect(source).toHaveAttribute("src", "data:audio/mp3;base64,BBBB");
});
it("renders a text resource block", () => {
const block: ContentBlock = {
type: "resource",
resource: { uri: "file:///foo", text: "embedded text" },
};
renderWithMantine();
expect(screen.getByText("embedded text")).toBeInTheDocument();
});
it("renders a blob resource block with placeholder", () => {
const block: ContentBlock = {
type: "resource",
resource: {
uri: "file:///bar",
blob: "abc",
mimeType: "application/octet-stream",
},
};
renderWithMantine();
expect(screen.getByText("[blob: file:///bar]")).toBeInTheDocument();
});
it("renders a resource_link with name", () => {
const block: ContentBlock = {
type: "resource_link",
uri: "ui://app",
name: "Cool App",
};
renderWithMantine();
expect(screen.getByText("Cool App")).toBeInTheDocument();
});
it("falls back to URI when resource_link has no name", () => {
const block = {
type: "resource_link",
uri: "ui://app",
} as unknown as ContentBlock;
renderWithMantine();
expect(screen.getByText("ui://app")).toBeInTheDocument();
});
it("renders a resource_link statically (no expand control)", () => {
const block: ContentBlock = {
type: "resource_link",
uri: "ui://app",
name: "Cool App",
description: "An app",
mimeType: "text/html",
};
renderWithMantine();
expect(screen.getByText("Cool App")).toBeInTheDocument();
expect(screen.getByText("An app")).toBeInTheDocument();
expect(screen.getByText("text/html")).toBeInTheDocument();
expect(screen.queryByRole("button")).not.toBeInTheDocument();
});
it("renders nothing for unknown block types", () => {
renderWithMantine();
expect(screen.queryByRole("img")).not.toBeInTheDocument();
expect(screen.queryByRole("button")).not.toBeInTheDocument();
});
it("renders plain text in a wrapping code block by default", () => {
const block: ContentBlock = { type: "text", text: "some long command" };
const { container } = renderWithMantine();
expect(container.querySelector(".mantine-Code-root")).toHaveAttribute(
"data-variant",
"wrapping",
);
});
it("renders plain text in a non-wrapping code block when wrap is false", () => {
const block: ContentBlock = { type: "text", text: "some long command" };
const { container } = renderWithMantine(
,
);
const code = container.querySelector(".mantine-Code-root");
expect(code).toHaveAttribute("data-variant", "nowrap");
// Full value exposed on hover since it may be clipped with an ellipsis.
expect(code).toHaveAttribute("title", "some long command");
});
it("does not set a title tooltip when wrapping (default)", () => {
const block: ContentBlock = { type: "text", text: "some long command" };
const { container } = renderWithMantine();
expect(container.querySelector(".mantine-Code-root")).not.toHaveAttribute(
"title",
);
});
it("renders text as markdown when mimeType is text/markdown", () => {
const block: ContentBlock = {
type: "text",
text: "# Title\n\nSome **bold** text.",
};
renderWithMantine();
expect(
screen.getByRole("heading", { level: 1, name: "Title" }),
).toBeInTheDocument();
expect(screen.getByText("bold")).toBeInTheDocument();
});
it("accepts mimeType with parameters (e.g. text/markdown; charset=utf-8)", () => {
const block: ContentBlock = { type: "text", text: "# Heading" };
renderWithMantine(
,
);
expect(
screen.getByRole("heading", { level: 1, name: "Heading" }),
).toBeInTheDocument();
});
it("falls back to code rendering for non-markdown mime types", () => {
const block: ContentBlock = { type: "text", text: "# not markdown" };
renderWithMantine();
expect(screen.getByText("# not markdown")).toBeInTheDocument();
// No generated by react-markdown
expect(screen.queryByRole("heading", { level: 1 })).not.toBeInTheDocument();
});
it("renders a copy overlay for markdown content when copyable", () => {
const block: ContentBlock = { type: "text", text: "# hi" };
renderWithMantine(
,
);
expect(screen.getByRole("button")).toBeInTheDocument();
});
it("highlights a JSON text block when mimeType is application/json", () => {
const block: ContentBlock = { type: "text", text: '{"a":1}' };
renderWithMantine(
,
);
const probe = screen.getByTestId("code-highlight");
expect(probe).toHaveAttribute("data-language", "json");
expect(probe.textContent).toContain('"a": 1');
});
it("renders nothing when neither block nor contents is provided", () => {
const { container } = renderWithMantine();
// MantineProvider injects a