diff --git a/apps/fabro-web/app/root.test.tsx b/apps/fabro-web/app/root.test.tsx new file mode 100644 index 000000000..ab13a3457 --- /dev/null +++ b/apps/fabro-web/app/root.test.tsx @@ -0,0 +1,76 @@ +import { describe, expect, test } from "bun:test"; +import { renderToStaticMarkup } from "react-dom/server"; +import { createMemoryRouter, RouterProvider, type RouteObject } from "react-router"; + +import { installRoutes } from "./install-router"; +import { routes } from "./router"; + +async function renderPage(routeObjects: RouteObject[], path: string, navigate = false) { + const router = createMemoryRouter(routeObjects, { + initialEntries: [navigate ? "/" : path], + }); + try { + if (navigate) await router.navigate(path); + return renderToStaticMarkup(); + } finally { + router.dispose(); + } +} + +describe("root error boundary", () => { + for (const path of ["/some_random_route", "/runs/existing-run/billing"]) { + for (const navigate of [false, true]) { + test(`${path} displays 404 on ${navigate ? "navigation" : "initial load"}`, async () => { + const html = await renderPage(routes, path, navigate); + + expect(html).toContain(">404

"); + expect(html).toContain("Page not found"); + expect(html).not.toContain("Something went wrong"); + expect(html).toContain('href="/runs"'); + expect(html).toContain("Go back"); + }); + } + } + + for (const [name, root] of [["app", routes[0]], ["installer", installRoutes[0]]] as const) { + async function renderFailure(error: unknown) { + return renderPage([ + { + ...root, + children: [{ + path: "failure", + loader() { throw error; }, + Component: () => null, + }], + }, + ], "/failure", true); + } + + test(`${name} displays a route's 404 response as page not found`, async () => { + const html = await renderFailure(new Response(null, { status: 404 })); + + expect(html).toContain(">404

"); + expect(html).toContain("Page not found"); + expect(html).not.toContain("Something went wrong"); + }); + + test.each([403, 500])(`${name} preserves a route's %i error response`, async (status) => { + const statusText = status === 403 ? "Forbidden" : "Internal Server Error"; + const html = await renderFailure(new Response(null, { status, statusText })); + + expect(html).toContain(`>${status}

`); + expect(html).toContain(`${status} Error`); + expect(html).toContain(statusText); + expect(html).not.toContain("Page not found"); + }); + + test(`${name} keeps unexpected exceptions as 500 errors`, async () => { + const html = await renderFailure(new Error("Unexpected route failure")); + + expect(html).toContain(">500

"); + expect(html).toContain("Something went wrong"); + expect(html).toContain("Unexpected route failure"); + expect(html).not.toContain("Page not found"); + }); + } +}); diff --git a/apps/fabro-web/app/root.tsx b/apps/fabro-web/app/root.tsx index 063d3579d..3ae5d252e 100644 --- a/apps/fabro-web/app/root.tsx +++ b/apps/fabro-web/app/root.tsx @@ -1,10 +1,11 @@ -import { isRouteErrorResponse, Outlet } from "react-router"; +import { isRouteErrorResponse, Outlet, useRouteError } from "react-router"; export default function Root() { return ; } -export function ErrorBoundary({ error }: any) { +export function ErrorBoundary() { + const error = useRouteError(); let status = 500; let heading = "Something went wrong"; let message = "An unexpected error occurred. Please try again.";