fix(web): recover pre-render boot failures

This commit is contained in:
lingniu
2026-07-16 06:04:30 +08:00
parent 4302fc8d45
commit 97fe1704a2
9 changed files with 196 additions and 20 deletions

View File

@@ -2,7 +2,7 @@ import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { lazy, useEffect, useState } from 'react';
import { MemoryRouter, useSearchParams } from 'react-router-dom';
import { isRouteChunkError, RoutePage } from './RouteBoundary';
import { isRouteChunkError, PlatformErrorBoundary, RoutePage } from './RouteBoundary';
import { ROUTER_FUTURE } from './routerConfig';
afterEach(() => {
@@ -12,6 +12,17 @@ afterEach(() => {
});
describe('route recovery boundary', () => {
it('keeps a recovery screen when the platform shell fails outside page routes', () => {
vi.spyOn(console, 'error').mockImplementation(() => undefined);
const BrokenShell = () => { throw new Error('shell exploded'); };
render(<PlatformErrorBoundary><BrokenShell /></PlatformErrorBoundary>);
expect(screen.getByRole('alert')).toHaveTextContent('平台外壳运行异常');
expect(screen.getByRole('button', { name: /重新加载平台/ })).toBeInTheDocument();
expect(screen.getByRole('link', { name: /返回全局监控/ })).toHaveAttribute('href', '/monitor');
});
it('recognizes deployment-related lazy chunk failures', () => {
expect(isRouteChunkError(new TypeError('Failed to fetch dynamically imported module: /assets/Monitor-old.js'))).toBe(true);
expect(isRouteChunkError(new Error('Loading chunk 18 failed'))).toBe(true);

View File

@@ -73,6 +73,32 @@ class RecoverableRouteBoundary extends Component<{ children: ReactNode; routeKey
}
}
export class PlatformErrorBoundary extends Component<{ children: ReactNode }, { error?: Error }> {
state: { error?: Error } = {};
static getDerivedStateFromError(error: Error) {
return { error };
}
render() {
const { error } = this.state;
if (!error) return this.props.children;
return <section className="v2-route-error v2-root-error" role="alert">
<span><IconAlertTriangle /></span>
<div>
<small></small>
<h2></h2>
<p></p>
<code>{error.message || error.name}</code>
<footer>
<button type="button" onClick={() => window.location.reload()}><IconRefresh /></button>
<a href="/monitor"><IconHome /></a>
</footer>
</div>
</section>;
}
}
export function RoutePage({ page: Page, label }: { page: ElementType; label: string }) {
const location = useLocation();
const routeKey = `${location.pathname}${location.search}`;