// REQ-USR-004: 顶栏结构 + 当前用户 + 退出登录(BR3/BR9)
import { describe, it, expect, vi } from 'vitest';
import { screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { useNavigate, useLocation, Routes, Route } from 'react-router-dom';
import { App as AntdApp } from 'antd';
import { renderShell } from './renderShell';
import TopBar from '../../src/layouts/AppLayout/TopBar';
import { useAppDispatch } from '../../src/store/hooks';
import { clearCredentials } from '../../src/store/slices/authSlice';
import { LOGOUT_SUCCESS_TEXT } from '../../src/layouts/AppLayout/shellMessages';
import { HOME_TAB, BIZ_TABS, type TabItem } from '../../src/layouts/AppLayout/useTabStack';
import type { AuthUser } from '../../src/api/types';
const ADMIN: AuthUser = { id: 1, sUserName: '朱子纯', sUserType: '超级管理员', sLanguage: '中文' };
// 顶栏测试宿主:以真实 onLogout(dispatch + message + navigate)驱动,模拟 AppLayout 提供的回调
function TopBarHost({
user,
tabs,
activeKey = 'home',
navOverlayOpen = false,
onToggleNav = vi.fn(),
onSelectTab = vi.fn(),
onCloseTab = vi.fn(),
onLogoHome = vi.fn(),
}: {
user: AuthUser | null;
tabs: TabItem[];
activeKey?: string;
navOverlayOpen?: boolean;
onToggleNav?: () => void;
onSelectTab?: (k: string) => void;
onCloseTab?: (k: string) => void;
onLogoHome?: () => void;
}) {
const dispatch = useAppDispatch();
const navigate = useNavigate();
const { message } = AntdApp.useApp();
const handleLogout = () => {
dispatch(clearCredentials());
message.success(LOGOUT_SUCCESS_TEXT);
navigate('/login', { replace: true });
};
return (
);
}
function LoginProbe() {
const loc = useLocation();
return
{loc.pathname}
;
}
describe('TopBar', () => {
it('renders brand logo / 全部导航 button / 主页 tab', () => {
renderShell(, {
preloadedAuth: { token: 't', user: ADMIN },
});
expect(screen.getByRole('button', { name: '全部导航' })).toBeInTheDocument();
expect(screen.getByRole('button', { name: '品牌Logo 回到主页' })).toBeInTheDocument();
const homeTab = screen.getByTestId('tab-home');
expect(homeTab).toHaveTextContent('主页');
// 主页 tab 无关闭按钮
expect(within(homeTab).queryByText('✕')).not.toBeInTheDocument();
});
it('renders current user as sUserName(sUserType)', () => {
renderShell(, {
preloadedAuth: { token: 't', user: ADMIN },
});
expect(screen.getByText('朱子纯(超级管理员)')).toBeInTheDocument();
});
it('user fallback when user is null', () => {
renderShell(, {
preloadedAuth: { token: 't', user: null },
});
expect(screen.getByText('未登录用户')).toBeInTheDocument();
});
it('logout menu dispatches clearCredentials, shows success, navigates /login', async () => {
localStorage.setItem('xly_erp_token', 't');
const { getState } = renderShell(
} />
} />
,
{ initialEntries: ['/'], preloadedAuth: { token: 't', user: ADMIN } },
);
// 展开当前用户下拉
await userEvent.click(screen.getByText('朱子纯(超级管理员)'));
const logout = await screen.findByText('退出登录');
await userEvent.click(logout);
expect(getState().auth.token).toBeNull();
expect(localStorage.getItem('xly_erp_token')).toBeNull();
expect(await screen.findByText(LOGOUT_SUCCESS_TEXT)).toBeInTheDocument();
expect(screen.getByTestId('login-probe').textContent).toBe('/login');
});
it('nav toggle button highlights when navOverlayOpen', () => {
renderShell(, {
preloadedAuth: { token: 't', user: ADMIN },
});
const navBtn = screen.getByRole('button', { name: '全部导航' });
expect(navBtn.getAttribute('aria-pressed')).toBe('true');
});
it('clicking business tab close calls onCloseTab', async () => {
const onCloseTab = vi.fn();
renderShell(
,
{ preloadedAuth: { token: 't', user: ADMIN } },
);
await userEvent.click(screen.getByTestId('tab-close-userlist'));
expect(onCloseTab).toHaveBeenCalledWith('userlist');
});
});