import { act } from 'react'; import { createRoot, type Root } from 'react-dom/client'; import { createMemoryRouter, RouterProvider, type RouteObject } from 'react-router-dom'; import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from 'vitest'; const { useSessionMock } = vi.hoisted(() => ({ useSessionMock: vi.fn(), })); vi.mock('@/lib/auth-client', () => ({ useSession: useSessionMock, })); import { routes } from '@/routes'; beforeAll(() => { Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', { configurable: true, value: true, }); }); afterAll(() => { Reflect.deleteProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT'); }); function Boom(): never { throw new Error('render blew up'); } /** Recursively clones the real exported route table, replacing only the * `/chat` route's `element` with `` — every other route (including * the real `AuthGuard` nesting and the real `/chat` `errorElement`) is left * exactly as exported. This is what makes the test fail if a future change * removes the real route's `errorElement`, unlike a hand-built independent * route tree that could drift from production undetected. */ function replaceChatElementWithBoom(nodes: RouteObject[]): RouteObject[] { return nodes.map((node) => { const cloned: RouteObject = { ...node }; if (cloned.path === '/chat') { cloned.element = ; } if (cloned.children) { cloned.children = replaceChatElementWithBoom(cloned.children); } return cloned; }); } let root: Root | null; let container: HTMLElement; afterEach(async () => { await act(async () => { root?.unmount(); }); document.body.replaceChildren(); root = null; useSessionMock.mockReset(); }); describe('ChatRouteErrorBoundary', () => { it('renders a recoverable, non-blank fallback when the /chat route element throws during render', async () => { useSessionMock.mockReturnValue({ data: { user: { id: 'user-1' } }, isPending: false }); const routeObjects = replaceChatElementWithBoom(routes); const router = createMemoryRouter(routeObjects, { initialEntries: ['/chat'] }); container = document.createElement('div'); document.body.append(container); root = createRoot(container); const consoleErrorSpy = vi.spyOn(console, 'error').mockImplementation(() => {}); try { await act(async () => { root?.render(); }); expect(consoleErrorSpy).toHaveBeenCalled(); } finally { consoleErrorSpy.mockRestore(); } expect(container.textContent).not.toBe(''); expect(container.querySelector('[role="alert"]')).toBeTruthy(); }); });