/* Copyright 2026 Marimo. All rights reserved. */
import { act, fireEvent, render, screen } from "@testing-library/react";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { cellId } from "@/__tests__/branded";
import { TooltipProvider } from "@/components/ui/tooltip";
import { OutputArea, OutputRenderer } from "../Output";
describe("OutputRenderer renderFallback prop", () => {
it("should use renderFallback for unsupported mimetypes", () => {
const renderFallback = (mimetype: string) => (
Custom fallback for {mimetype}
);
render(
,
);
expect(screen.getByTestId("custom-fallback")).toBeInTheDocument();
expect(screen.getByText(/Custom fallback for/)).toHaveTextContent(
"Custom fallback for application/unsupported",
);
});
it("should not use renderFallback for supported mimetypes", () => {
const renderFallback = () => (
Should not appear
);
render(
,
);
expect(screen.queryByTestId("custom-fallback")).not.toBeInTheDocument();
expect(screen.getByText("Hello World")).toBeInTheDocument();
});
it("should show default error when renderFallback is not provided", () => {
render(
,
);
expect(
screen.getByText(/Unsupported mimetype: application\/unknown/),
).toBeInTheDocument();
});
});
describe("OutputArea null/undefined handling", () => {
it("should render null when output is null", () => {
const { container } = render(
,
);
expect(container.innerHTML).toBe("");
});
it("should render null when output is undefined", () => {
const { container } = render(
,
);
expect(container.innerHTML).toBe("");
});
});
describe("OutputRenderer image and SVG rendering", () => {
const plainSvgString =
'';
const base64SvgDataUrl =
"data:image/svg+xml;base64,PHN2Zz48cmVjdCB4PSIwIiB5PSIw";
const base64PngDataUrl =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAAB";
it("should render plain SVG string via renderHTML", () => {
const { container } = render(
,
);
const svgElement = container.querySelector("svg");
expect(svgElement).not.toBeNull();
const rectElement = svgElement!.querySelector("rect");
expect(rectElement).not.toBeNull();
const imgElement = container.querySelector("img");
expect(imgElement).toBeNull();
});
it("should render Base64 SVG data URL via ImageOutput", () => {
const { container } = render(
,
);
const imgElement = container.querySelector("img");
expect(imgElement).not.toBeNull();
expect(imgElement).toHaveAttribute("src", base64SvgDataUrl);
const svgElement = container.querySelector("svg");
expect(svgElement).toBeNull();
});
it("should render Base64 PNG data URL via ImageOutput", () => {
const { container } = render(
,
);
const imgElement = container.querySelector("img");
expect(imgElement).not.toBeNull();
expect(imgElement).toHaveAttribute("src", base64PngDataUrl);
});
});
describe("OutputArea fullscreen exit control", () => {
let fullscreenElement: Element | null = null;
const exitFullscreen = vi.fn(() => Promise.resolve());
beforeEach(() => {
fullscreenElement = null;
exitFullscreen.mockClear();
Object.defineProperty(document, "fullscreenElement", {
configurable: true,
get: () => fullscreenElement,
});
Object.defineProperty(document, "exitFullscreen", {
configurable: true,
value: exitFullscreen,
});
});
// Drop the stubs so that the real document properties come back.
afterEach(() => {
Reflect.deleteProperty(document, "fullscreenElement");
Reflect.deleteProperty(document, "exitFullscreen");
});
const renderOutput = () =>
render(
,
);
const enterFullscreen = (container: HTMLElement) => {
fullscreenElement = container.querySelector('[data-cell-role="output"]');
act(() => {
document.dispatchEvent(new Event("fullscreenchange"));
});
};
it("hides the exit button outside fullscreen", () => {
renderOutput();
expect(
screen.queryByTestId("exit-fullscreen-output-button"),
).not.toBeInTheDocument();
});
it("shows the exit button when the output is the fullscreen element", () => {
const { container } = renderOutput();
enterFullscreen(container);
expect(
screen.getByTestId("exit-fullscreen-output-button"),
).toBeInTheDocument();
});
it("exits fullscreen on click", () => {
const { container } = renderOutput();
enterFullscreen(container);
fireEvent.click(screen.getByTestId("exit-fullscreen-output-button"));
expect(exitFullscreen).toHaveBeenCalledTimes(1);
});
it("exits fullscreen on Escape", () => {
const { container } = renderOutput();
enterFullscreen(container);
fireEvent.keyDown(document, { key: "Escape" });
expect(exitFullscreen).toHaveBeenCalledTimes(1);
});
it("ignores Escape outside fullscreen", () => {
renderOutput();
fireEvent.keyDown(document, { key: "Escape" });
expect(exitFullscreen).not.toHaveBeenCalled();
});
});