import { useState } from "react";
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
import { render, screen, cleanup, fireEvent } from "@testing-library/react";
import { ErrorBoundary } from "./ErrorBoundary";
/**
* Before this component existed, any render error unmounted the whole React
* tree: a white window, no navigation, no clue what happened. That failure mode
* is invisible to the typechecker, which is what these cover.
*/
function Boom({ shouldThrow }: { shouldThrow: boolean }): React.ReactElement {
if (shouldThrow) throw new Error("the sky is falling");
return
all good
;
}
beforeEach(() => {
// React logs caught errors itself; silence it so a passing run stays readable.
vi.spyOn(console, "error").mockImplementation(() => {});
});
afterEach(() => {
vi.restoreAllMocks();
cleanup();
});
describe("ErrorBoundary", () => {
it("renders its children when nothing goes wrong", () => {
render(
);
expect(screen.getByText("all good")).toBeDefined();
});
it("shows the error instead of a blank page", () => {
render(
);
expect(screen.getByText("This page hit an error")).toBeDefined();
// The message is surfaced, not swallowed — otherwise there is nothing to
// report and no way to tell what broke.
expect(screen.getByText("the sky is falling")).toBeDefined();
});
it("offers a way out", () => {
render(
);
expect(screen.getByRole("button", { name: /try again/i })).toBeDefined();
expect(screen.getByRole("button", { name: /reload/i })).toBeDefined();
});
it("says the rest of the app is unaffected", () => {
render(
);
expect(screen.getByText(/your stacks are unaffected/i)).toBeDefined();
});
it("clears the error when resetKey changes", () => {
// This is what makes navigating away from a broken page work: without it
// the boundary keeps showing the error on every subsequent route.
function Harness() {
const [route, setRoute] = useState("/broken");
return (
<>
>
);
}
render();
expect(screen.getByText("This page hit an error")).toBeDefined();
fireEvent.click(screen.getByText("navigate"));
expect(screen.queryByText("This page hit an error")).toBeNull();
expect(screen.getByText("all good")).toBeDefined();
});
it("stays on the error screen while the route is unchanged", () => {
function Harness() {
const [, force] = useState(0);
return (
<>
>
);
}
render();
fireEvent.click(screen.getByText("rerender"));
expect(screen.getByText("This page hit an error")).toBeDefined();
});
it("logs the error so it is recoverable from the console", () => {
render(
);
const logged = vi
.mocked(console.error)
.mock.calls.some((args) => String(args[0]).includes("Render error:"));
expect(logged).toBe(true);
});
});