/* Copyright 2026 Marimo. All rights reserved. */ import { fireEvent, render, screen, waitFor } from "@testing-library/react"; import type { PropsWithChildren, ReactNode } from "react"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { MockRequestClient } from "@/__mocks__/requests"; import { ModalProvider } from "@/components/modal/ImperativeModal"; import { TooltipProvider } from "@/components/ui/tooltip"; import { clearToasts } from "@/components/ui/use-toast"; import { initialNotebookState, notebookAtom } from "@/core/cells/cells"; import { viewStateAtom } from "@/core/mode"; import { connectionAtom } from "@/core/network/connection"; import { requestClientAtom } from "@/core/network/requests"; import type { EnvironmentInfo } from "@/core/network/types"; import { filenameAtom } from "@/core/saving/file-state"; import { store } from "@/core/state/jotai"; import { ConnectionState } from "@/core/websocket/types"; import { ErrorBoundary } from "../ErrorBoundary"; vi.mock("@/utils/copy", () => ({ copyToClipboard: vi.fn().mockResolvedValue(undefined), })); const environment: EnvironmentInfo = { marimo: "1.2.3", editable: false, location: "~/.venv/site-packages/marimo", OS: "Darwin", "OS Version": "25.0", Processor: "arm", "Python Version": "3.12.9", Locale: "en_US", Binaries: { Browser: "chrome 140", Node: "v22", uv: "0.11" }, Dependencies: { click: "8.4.2" }, "Optional Dependencies": { pandas: "3.0.0" }, "Experimental Flags": {}, }; const CRASH_MESSAGE = "cell output crashed"; function CrashingChild(): never { throw new Error(CRASH_MESSAGE); } function RecoverableChild({ crash }: { crash: boolean }) { if (crash) { throw new Error(CRASH_MESSAGE); } return

recovered child

; } function DescendantProviders({ children }: PropsWithChildren) { return ( {children} ); } function isExpectedReactExceptionOutput(args: unknown[]): boolean { return args.some((arg) => { if (arg instanceof Error) { return arg.message.includes(CRASH_MESSAGE); } return ( typeof arg === "string" && (arg.includes(CRASH_MESSAGE) || arg.includes("The above error occurred") || arg.includes("React will try to recreate this component tree")) ); }); } function suppressExpectedReactException() { return vi.spyOn(console, "error").mockImplementation((...args) => { if (isExpectedReactExceptionOutput(args)) { return; } throw new Error(`Unexpected console.error: ${args.map(String).join(" ")}`); }); } let consoleError: ReturnType | undefined; function renderCrashedBoundary(child: ReactNode = ) { consoleError?.mockRestore(); consoleError = suppressExpectedReactException(); return render({child}); } function setRequestClient( overrides?: Parameters[0], ) { const client = MockRequestClient.create(overrides); store.set(requestClientAtom, client); return client; } async function openReportDialog() { const trigger = screen.getByRole("button", { name: "Report an issue" }); trigger.focus(); fireEvent.click(trigger); return screen.findByRole("dialog"); } async function expectReportButtonFocused() { await waitFor(() => { expect( screen.getByRole("button", { name: "Report an issue" }), ).toHaveFocus(); }); } async function closeReportDialog(method: "escape" | "close") { const dialog = await screen.findByRole("dialog"); if (method === "escape") { fireEvent.keyDown(dialog, { key: "Escape" }); } else { fireEvent.click(screen.getByRole("button", { name: "Close" })); } await waitFor(() => { expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); }); await expectReportButtonFocused(); } async function expectRealReportModal() { const dialog = await openReportDialog(); expect(dialog).toHaveTextContent("Report an issue"); expect(screen.getByRole("link", { name: "Open GitHub issue" })).toBeVisible(); expect( screen.getByRole("checkbox", { name: "Include errors" }), ).toBeVisible(); expect( screen.getByRole("checkbox", { name: "Include notebook code" }), ).toBeVisible(); await screen.findByText("Environment details"); return dialog; } function resetSharedState() { localStorage.clear(); clearToasts(); store.set(requestClientAtom, null); store.set(notebookAtom, initialNotebookState()); store.set(viewStateAtom, { mode: "edit", cellAnchor: null }); store.set(connectionAtom, { state: ConnectionState.OPEN }); store.set(filenameAtom, "/project/example.py"); } describe("ErrorBoundary report dialog", () => { beforeEach(() => { resetSharedState(); }); afterEach(() => { consoleError?.mockRestore(); consoleError = undefined; resetSharedState(); }); it("opens the report modal after a child render exception", async () => { const { getEnvironmentInfo } = setRequestClient({ getEnvironmentInfo: vi.fn().mockResolvedValue(environment), }); renderCrashedBoundary(); expect(screen.getByText("Something went wrong")).toBeVisible(); expect(screen.getByText("cell output crashed")).toBeVisible(); expect(screen.getByTestId("reset-error-boundary-button")).toBeVisible(); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); expect(getEnvironmentInfo).not.toHaveBeenCalled(); await expectRealReportModal(); await waitFor(() => expect(getEnvironmentInfo).toHaveBeenCalledOnce()); }); it("still opens one report dialog after descendant providers disappear", async () => { const { getEnvironmentInfo } = setRequestClient({ getEnvironmentInfo: vi.fn().mockResolvedValue(environment), }); renderCrashedBoundary( , ); await expectRealReportModal(); expect(screen.getAllByRole("dialog")).toHaveLength(1); await waitFor(() => expect(getEnvironmentInfo).toHaveBeenCalledOnce()); }); it("shows partial diagnostics when the request client is missing", async () => { renderCrashedBoundary(); await openReportDialog(); await screen.findByText("Environment details"); expect( screen.getByText("Server environment information unavailable"), ).toBeVisible(); expect( screen.queryByText("Loading environment details…"), ).not.toBeInTheDocument(); expect( screen.queryByRole("button", { name: "Retry" }), ).not.toBeInTheDocument(); expect( screen.getByRole("checkbox", { name: "Include notebook code" }), ).toBeDisabled(); expect(screen.getByText("Notebook source is unavailable.")).toBeVisible(); const link = screen.getByRole("link", { name: "Open GitHub issue" }); expect(link.getAttribute("href") ?? "").toContain("&env="); }); it("does not request environment or source while the report dialog is closed", async () => { localStorage.setItem( "marimo:issue-report:include-code", JSON.stringify(true), ); const { getEnvironmentInfo, readCode } = setRequestClient({ getEnvironmentInfo: vi.fn().mockResolvedValue(environment), readCode: vi.fn().mockResolvedValue({ contents: "import marimo" }), }); renderCrashedBoundary(); expect(screen.getByText("Something went wrong")).toBeVisible(); expect(screen.queryByRole("dialog")).not.toBeInTheDocument(); expect(getEnvironmentInfo).not.toHaveBeenCalled(); expect(readCode).not.toHaveBeenCalled(); }); it("restores focus to the report button after closing with Escape", async () => { setRequestClient({ getEnvironmentInfo: vi.fn().mockResolvedValue(environment), }); renderCrashedBoundary(); await openReportDialog(); await closeReportDialog("escape"); expect(screen.getByText(CRASH_MESSAGE)).toBeVisible(); }); it("restores focus to the report button after closing with the close control", async () => { setRequestClient({ getEnvironmentInfo: vi.fn().mockResolvedValue(environment), }); renderCrashedBoundary(); await openReportDialog(); await closeReportDialog("close"); expect(screen.getByText(CRASH_MESSAGE)).toBeVisible(); }); it("reopens the report dialog while the original error remains", async () => { setRequestClient({ getEnvironmentInfo: vi.fn().mockResolvedValue(environment), }); renderCrashedBoundary(); await openReportDialog(); await closeReportDialog("escape"); expect(screen.getByText(CRASH_MESSAGE)).toBeVisible(); await expectRealReportModal(); expect(screen.getByText(CRASH_MESSAGE)).toBeVisible(); }); it("restores the child after try again once the failure is cleared", () => { setRequestClient({ getEnvironmentInfo: vi.fn().mockResolvedValue(environment), }); const view = renderCrashedBoundary(); expect(screen.getByText("Something went wrong")).toBeVisible(); view.rerender( , ); fireEvent.click(screen.getByTestId("reset-error-boundary-button")); expect(screen.getByText("recovered child")).toBeVisible(); expect(screen.queryByText("Something went wrong")).not.toBeInTheDocument(); }); it("does not read notebook source when opened with fresh inclusion preferences", async () => { const { readCode } = setRequestClient({ getEnvironmentInfo: vi.fn().mockResolvedValue(environment), readCode: vi.fn().mockResolvedValue({ contents: "import marimo" }), }); renderCrashedBoundary(); await expectRealReportModal(); expect(readCode).not.toHaveBeenCalled(); }); it("renders the copy toast after the ancestor toaster is gone", async () => { setRequestClient({ getEnvironmentInfo: vi.fn().mockResolvedValue(environment), }); renderCrashedBoundary(); await expectRealReportModal(); fireEvent.click( screen.getByRole("button", { name: "Copy environment JSON" }), ); expect(await screen.findByText("Environment details copied")).toBeVisible(); }); });