import { render, screen } from "@testing-library/react"; import { WidgetHost } from "./WidgetHost"; import { useSessionStore } from "../store/sessionStore"; import type { WidgetDescriptor } from "../api/types"; // Replace the widget registry with a renderer that always throws, simulating a // viewer/widget that crashes on malformed gate data (the real white-screen path). vi.mock("../widgets", () => ({ resolve: () => function Exploding() { throw new Error("bad gate payload"); }, })); let errorSpy: ReturnType; beforeEach(() => { errorSpy = vi.spyOn(console, "error").mockImplementation(() => {}); useSessionStore.getState().resetSession(); }); afterEach(() => { errorSpy.mockRestore(); useSessionStore.getState().resetSession(); }); const gate: WidgetDescriptor = { id: "g1", widget: "select", title: "Choose" }; test("a crashing widget shows a fallback instead of unmounting the app", () => { useSessionStore.setState({ pendingWidget: gate }); render(); // No throw escaped: the fallback rendered. expect(screen.getByRole("alert")).toBeInTheDocument(); expect(screen.getByText(/couldn't be displayed/i)).toBeInTheDocument(); // The store (and thus the surrounding app) is still alive and interactive. expect(useSessionStore.getState().pendingWidget).toEqual(gate); });