Files
gongxue-base/apps/admin/src/components/DefaultRoute.integration.test.tsx
wangziqi ce9bde35eb
Some checks failed
CI / check (pull_request) Failing after 2m14s
fix(admin): 修复首次进入系统后点击任意导航被拉回 dashboard
根因:RouteKeeper 保活缓存了首页 index 节点,DefaultRoute 中的声明式
<Navigate> 会随缓存节点在后续路由变化时反复触发 replace,导致所有导航
被强制拉回落地页(dashboard),刷新后因不再经过 / 而恢复正常。

修复:DefaultRoute 改为 effect 导航,仅在确实处于 / 且已算出落地页时
跳转一次;navigate 通过 ref 持有避免 effect 空转。新增集成测试覆盖
「首次进入 / → 跳转一次 → 后续导航不被劫持 → 回到 / 仍能重定向」。
2026-08-10 09:42:46 +08:00

106 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { act } from 'react';
import { createRoot } from 'react-dom/client';
import { MemoryRouter, Route, Routes, useNavigate } from 'react-router';
import { afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
import DefaultRoute from './DefaultRoute';
import { RouteKeeper } from './RouteKeeper';
import { usePermissionStore } from '../store/permission/permissionStore';
import { useUserStore } from '../store/user/userStore';
let container: HTMLDivElement | null = null;
let root: ReturnType<typeof createRoot> | null = null;
beforeAll(() => {
(
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT: boolean }
).IS_REACT_ACT_ENVIRONMENT = true;
});
beforeEach(() => {
// 清掉可能由其他测试文件遗留的持久化数据,保证会话状态可控
Object.keys(localStorage).forEach((key) => localStorage.removeItem(key));
useUserStore.setState({ token: null, user: null });
usePermissionStore.setState({ permissions: [], status: 'unknown' });
useUserStore.getState().setSession('test-token', {
id: 1,
username: 'admin',
roles: ['超级管理员'],
});
usePermissionStore.getState().writePermissions(['dashboard:view', 'student:view']);
});
afterEach(async () => {
if (root) await act(async () => root?.unmount());
container?.remove();
root = null;
container = null;
});
function PageDashboard() {
const navigate = useNavigate();
return (
<div>
<div data-testid="page-dashboard"></div>
<button data-testid="go-students" onClick={() => navigate('/students')}>
</button>
</div>
);
}
function PageStudents() {
const navigate = useNavigate();
return (
<div>
<div data-testid="page-students"></div>
<button data-testid="go-home" onClick={() => navigate('/')}>
</button>
</div>
);
}
function Harness() {
return (
<MemoryRouter initialEntries={['/']}>
<Routes>
<Route path="/" element={<RouteKeeper />}>
<Route index element={<DefaultRoute />} />
<Route path="dashboard" element={<PageDashboard />} />
<Route path="students" element={<PageStudents />} />
</Route>
</Routes>
</MemoryRouter>
);
}
async function renderHarness() {
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
await act(async () => {
root?.render(<Harness />);
});
}
describe('DefaultRoute keep-alive regression', () => {
it('redirects to landing page once and does not hijack later navigations', async () => {
await renderHarness();
// 首次进入 '/' 应跳转到落地页 /dashboard且只跳一次
expect(document.querySelector('[data-testid="page-dashboard"]')).not.toBeNull();
// 再导航到 /students不应被保活的首页节点拉回 /dashboard
await act(async () => {
(document.querySelector('[data-testid="go-students"]') as HTMLButtonElement).click();
});
expect(document.querySelector('[data-testid="page-students"]')).not.toBeNull();
// 回到 '/' 时仍应再次跳转到落地页
await act(async () => {
(document.querySelector('[data-testid="go-home"]') as HTMLButtonElement).click();
});
expect(document.querySelector('[data-testid="page-dashboard"]')).not.toBeNull();
});
});