import { MantineProvider } from "@mantine/core"; import { fireEvent, render, screen } from "@testing-library/react"; import { type FC, lazy, Suspense } from "react"; import { MemoryRouter, useLocation, useNavigate } from "react-router"; import { describe, expect, it, vi } from "vitest"; import { RouteErrorBoundary } from "./RouteErrorBoundary"; const session = { isAuthenticated: false }; vi.mock("@/features/auth", () => ({ useAuth: () => ((/* eslint-disable unicorn/no-null -- Fixture preserves the production contract’s explicit null values. */ { user: session.isAuthenticated ? {} : null } /* eslint-enable unicorn/no-null */)), })); const ThrowingRoute: FC = () => { throw new Error("test route failure"); }; const HealthyRoute: FC = () => { return (
{useLocation().pathname === "/healthy" ? ( "healthy route" ) : ( )}
); }; const RouteHarness: FC = () => { const navigate = useNavigate(); return ( <> ); }; function renderBoundary(path: string) { return render( , ); } describe("RouteErrorBoundary", () => { it.each(["/accounts", "/login"])( "recovers a transient failure through the fallback on %s itself", async (path) => { session.isAuthenticated = path === "/accounts"; let isFailing = true; const RecoverableRoute: FC = () => { if (isFailing) throw new Error("temporary failure"); return
recovered route
; }; render( , ); const action = screen.getByRole("link", { name: "Продолжить" }); isFailing = false; fireEvent.click(action); expect(await screen.findByText("recovered route")).toBeInTheDocument(); }, ); it("keeps a persistent failure recoverable without an automatic reset loop", () => { session.isAuthenticated = true; renderBoundary("/accounts"); fireEvent.click(screen.getByRole("link", { name: "Продолжить" })); expect( screen.getByRole("heading", { name: "Не удалось открыть страницу" }), ).toBeInTheDocument(); expect(screen.queryByText("test route failure")).not.toBeInTheDocument(); expect( screen.getByRole("link", { name: "Перезагрузить страницу" }), ).toHaveAttribute("href", "/accounts"); }); it("offers document navigation when React.lazy caches a rejected chunk", async () => { session.isAuthenticated = false; const load = vi.fn(() => Promise.reject(new Error("chunk failed"))); const Chunk = lazy(load); render( loading chunk}> , ); fireEvent.click(await screen.findByRole("link", { name: "Продолжить" })); expect(load).toHaveBeenCalledTimes(1); const reload = screen.getByRole("link", { name: "Перезагрузить страницу" }); expect(reload).toHaveAttribute("href", "/login"); // Cancel browser navigation in JSDOM only after proving React Router did not intercept it. let isIntercepted = true; const preventNavigation = (event: MouseEvent) => { isIntercepted = event.defaultPrevented; event.preventDefault(); }; document.addEventListener("click", preventNavigation, { once: true }); fireEvent.click(reload); expect(isIntercepted).toBe(false); }); it("offers login after a public route error without error details", () => { session.isAuthenticated = false; renderBoundary("/login"); expect(document.title).toBe("Не удалось открыть страницу — WealthLedger"); expect(screen.getByRole("link", { name: "Продолжить" })).toHaveAttribute( "href", "/login", ); expect(screen.queryByText("test route failure")).not.toBeInTheDocument(); }); it("offers accounts after a protected route error and resets on navigation", async () => { session.isAuthenticated = true; renderBoundary("/accounts"); fireEvent.click(screen.getByRole("button", { name: "go healthy" })); expect(await screen.findByText("healthy route")).toBeInTheDocument(); }); });