Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion packages/base/src/page/ErrorBoundary/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { superRender } from '@actiontech/shared/lib/testUtil';
import ErrorBoundary from '../index';
import ErrorBoundary, {
isChunkLoadError,
forceReloadWithCache,
errorFallback
} from '../index';
import { useNavigate } from 'react-router-dom';
import { fireEvent, screen } from '@testing-library/react';

Expand Down Expand Up @@ -32,6 +36,69 @@ jest.mock('react-router-dom', () => ({
}));

describe('base/ErrorBoundary', () => {
describe('isChunkLoadError', () => {
it('should detect chunk load errors', () => {
expect(
isChunkLoadError(
new Error('Failed to fetch dynamically imported module')
)
).toBe(true);
expect(isChunkLoadError(new Error('Loading chunk 123 failed'))).toBe(
true
);
expect(isChunkLoadError(new Error('ChunkLoadError'))).toBe(true);
});

it('should not detect normal errors', () => {
expect(isChunkLoadError(new Error('normal error'))).toBe(false);
});
});

describe('forceReloadWithCache', () => {
beforeEach(() => {
delete (window as any).location;
(window as any).location = { href: 'http://localhost:3000' };
});

it('should add timestamp to URL', () => {
forceReloadWithCache();
expect(window.location.href).toMatch(/\?_t=\d+/);
});
});

describe('errorFallback', () => {
const mockNavigate = jest.fn();
const mockResetErrorBoundary = jest.fn();

it('should render chunk error UI', () => {
const error = new Error('Failed to fetch dynamically imported module');
superRender(
<>
{errorFallback({
error,
resetErrorBoundary: mockResetErrorBoundary,
navigate: mockNavigate
})}
</>
);
expect(screen.getByText('系统已更新')).toBeInTheDocument();
});

it('should render normal error UI', () => {
const error = new Error('test error');
superRender(
<>
{errorFallback({
error,
resetErrorBoundary: mockResetErrorBoundary,
navigate: mockNavigate
})}
</>
);
expect(screen.getByText('出错了')).toBeInTheDocument();
});
});

it('should render error boundary', () => {
const navigateSpy = jest.fn();
(useNavigate as jest.Mock).mockImplementation(() => navigateSpy);
Expand Down
107 changes: 77 additions & 30 deletions packages/base/src/page/ErrorBoundary/index.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,90 @@
import { ErrorBoundary as ReactErrorBoundary } from 'react-error-boundary';
import { BasicResult, BasicButton } from '@actiontech/dms-kit';
import { BasicResult, BasicButton, ROUTE_PATHS } from '@actiontech/dms-kit';
import { useTypedNavigate } from '@actiontech/shared';
import { useTranslation } from 'react-i18next';
import { Space } from 'antd';
import { ROUTE_PATHS } from '@actiontech/dms-kit';
import { t } from '../../locale';

export const isChunkLoadError = (error: Error): boolean => {
const message = error.message || '';

Check warning on line 8 in packages/base/src/page/ErrorBoundary/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch
const stack = error.stack || '';

Check warning on line 9 in packages/base/src/page/ErrorBoundary/index.tsx

View workflow job for this annotation

GitHub Actions / Coverage annotations (🧪 jest-coverage-report-action)

🌿 Branch is not covered

Warning! Not covered branch

const chunkFailedPatterns = [
/Failed to fetch dynamically imported module/i,
/Loading chunk [\d]+ failed/i,
/ChunkLoadError/i,
/Loading CSS chunk [\d]+ failed/i,
/Importing a module script failed/i
];

return chunkFailedPatterns.some(
(pattern) => pattern.test(message) || pattern.test(stack)
);
};

export const forceReloadWithCache = () => {
const url = new URL(window.location.href);
url.searchParams.delete('_t');
url.searchParams.set('_t', Date.now().toString());
window.location.href = url.toString();
};

export const errorFallback = ({
error,
resetErrorBoundary,
navigate
}: {
error: Error;
resetErrorBoundary: () => void;
navigate: (path: string) => void;
}) => {
if (isChunkLoadError(error)) {
return (
<BasicResult
status="warning"
title={t('common.pageUpdating')}
subTitle={t('common.pageUpdatingDesc')}
extra={
<BasicButton type="primary" onClick={forceReloadWithCache}>
{t('common.refresh')}
</BasicButton>
}
/>
);
}

return (
<BasicResult
status="error"
title={t('common.somethingWentWrong')}
subTitle={error.message}
extra={
<Space>
<BasicButton
onClick={() => {
resetErrorBoundary();
navigate(ROUTE_PATHS.BASE.HOME);
}}
>
{t('common.backToHome')}
</BasicButton>
<BasicButton type="primary" onClick={resetErrorBoundary}>
{t('common.retry')}
</BasicButton>
</Space>
}
/>
);
};

const ErrorBoundary: React.FC<{ children: React.ReactNode }> = ({
children
}) => {
const { t } = useTranslation();

const navigate = useTypedNavigate();

return (
<ReactErrorBoundary
fallbackRender={({ error, resetErrorBoundary }) => {
return (
<BasicResult
status="error"
title={t('common.somethingWentWrong')}
subTitle={error.message}
extra={
<Space>
<BasicButton
onClick={() => {
resetErrorBoundary();
navigate(ROUTE_PATHS.BASE.HOME);
}}
>
{t('common.backToHome')}
</BasicButton>
<BasicButton type="primary" onClick={resetErrorBoundary}>
{t('common.retry')}
</BasicButton>
</Space>
}
/>
);
}}
fallbackRender={({ error, resetErrorBoundary }) =>
errorFallback({ error, resetErrorBoundary, navigate })
}
>
{children}
</ReactErrorBoundary>
Expand Down
6 changes: 5 additions & 1 deletion packages/dms-kit/docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,8 @@ nav:

## 1.0.6

- 添加全局操作记录path
- 添加全局操作记录path

## 1.0.7

- 国际化变更
2 changes: 1 addition & 1 deletion packages/dms-kit/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@actiontech/dms-kit",
"version": "1.0.6",
"version": "1.0.7",
"description": "DMS Kit - React UI Components Library",
"main": "lib/index.js",
"module": "es/index.js",
Expand Down
4 changes: 3 additions & 1 deletion packages/dms-kit/src/locale/zh-CN/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,5 +267,7 @@ export default {
},
deleteConfirmTitle: '确认要删除么?',
somethingWentWrong: '出错了',
backToHome: '返回首页'
backToHome: '返回首页',
pageUpdating: '系统已更新',
pageUpdatingDesc: '检测到系统有新版本,请点击下方按钮刷新页面以加载最新内容'
};