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'; function Boom(): never { throw new Error('resource route render blew up'); } function replaceRouteElementWithBoom(nodes: RouteObject[], path: string): RouteObject[] { return nodes.map((node) => { const cloned: RouteObject = { ...node }; if (cloned.path === path) { cloned.element = ; } if (cloned.children) { cloned.children = replaceRouteElementWithBoom(cloned.children, path); } return cloned; }); } let root: Root | null = null; let container: HTMLDivElement; beforeAll(() => { Object.defineProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT', { configurable: true, value: true, }); }); afterAll(() => { Reflect.deleteProperty(globalThis, 'IS_REACT_ACT_ENVIRONMENT'); }); afterEach(async () => { await act(async () => { root?.unmount(); }); document.body.replaceChildren(); root = null; useSessionMock.mockReset(); }); describe('resource route error boundaries', () => { it.each(['/projects', '/projects/:id', '/tasks'])( 'renders a recoverable fallback when %s throws during route render', async (path) => { useSessionMock.mockReturnValue({ data: { user: { id: 'user-1' } }, isPending: false }); const initialEntry = path === '/projects/:id' ? '/projects/project-1' : path; const router = createMemoryRouter(replaceRouteElementWithBoom(routes, path), { initialEntries: [initialEntry], }); 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(); }, ); });