whisper-money/resources/js/components/app-error-boundary.test.tsx

90 lines
2.7 KiB
TypeScript

import { reloadOnChunkLoadError } from '@/lib/chunk-load-recovery';
import { render, screen } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { AppErrorBoundary } from './app-error-boundary';
vi.mock('@/lib/chunk-load-recovery', async (importOriginal) => ({
...(await importOriginal<typeof import('@/lib/chunk-load-recovery')>()),
reloadOnChunkLoadError: vi.fn(),
}));
function Boom(): never {
throw new Error('render exploded');
}
describe('AppErrorBoundary', () => {
beforeEach(() => {
// React logs the caught error; keep the test output readable.
vi.spyOn(console, 'error').mockImplementation(() => {});
});
afterEach(() => {
vi.restoreAllMocks();
vi.mocked(reloadOnChunkLoadError).mockClear();
});
it('renders its children when nothing throws', () => {
render(
<AppErrorBoundary>
<p>the app</p>
</AppErrorBoundary>,
);
expect(screen.getByText('the app')).toBeInTheDocument();
});
it('shows a recoverable screen instead of a blank page when a child throws', () => {
render(
<AppErrorBoundary>
<Boom />
</AppErrorBoundary>,
);
expect(screen.getByText('Something went wrong.')).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'Try again' }),
).toBeInTheDocument();
expect(
screen.getByRole('button', { name: 'Go to Dashboard' }),
).toBeInTheDocument();
});
it('turns a back-button navigation into a document load while the fallback is up', () => {
const addEventListener = vi.spyOn(window, 'addEventListener');
const removeEventListener = vi.spyOn(window, 'removeEventListener');
const { unmount } = render(
<AppErrorBoundary>
<Boom />
</AppErrorBoundary>,
);
// Inertia's own popstate listener outlives the unmounted page, so
// without this the URL changes and nothing repaints.
expect(addEventListener).toHaveBeenCalledWith(
'popstate',
expect.any(Function),
);
unmount();
expect(removeEventListener).toHaveBeenCalledWith(
'popstate',
expect.any(Function),
);
});
it('hands the error to the chunk-load recovery, which the global listeners no longer see', () => {
render(
<AppErrorBoundary>
<Boom />
</AppErrorBoundary>,
);
expect(reloadOnChunkLoadError).toHaveBeenCalledWith(
expect.objectContaining({ message: 'render exploded' }),
expect.anything(),
);
});
});