fix(web): harden typed SPA chat lifecycle

Co-Authored-By: Claude Haiku 4.5 <[email protected]>
This commit is contained in:
shaggy (mosaic-dev box)
2026-08-10 00:24:20 -05:00
co-authored by Claude Haiku 4.5
parent b2e005f2b4
commit caebf9ef70
20 changed files with 1778 additions and 125 deletions
@@ -0,0 +1,58 @@
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';
import { ChatRouteErrorBoundary } from './chat-error-boundary';
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');
}
let root: Root | null;
let container: HTMLElement;
afterEach(async () => {
await act(async () => {
root?.unmount();
});
document.body.replaceChildren();
root = null;
});
describe('ChatRouteErrorBoundary', () => {
it('renders a recoverable, non-blank fallback when the /chat route element throws during render', async () => {
const routeObjects: RouteObject[] = [
{ path: '/chat', element: <Boom />, errorElement: <ChatRouteErrorBoundary /> },
];
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(<RouterProvider router={router} />);
});
expect(consoleErrorSpy).toHaveBeenCalled();
} finally {
consoleErrorSpy.mockRestore();
}
expect(container.textContent).not.toBe('');
expect(container.querySelector('[role="alert"]')).toBeTruthy();
});
});