// 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'); }); });