AppLayout.shell.test.tsx 3.69 KB
// REQ-USR-003: AppLayout 外壳装配 + 标签↔路由同步(ready / navOverlayOpen / tabOpen 态)
import { describe, it, expect } from 'vitest';
import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Routes, Route, useLocation } from 'react-router-dom';
import { renderShell } from './renderShell';
import AppLayout from '../../src/layouts/AppLayout/AppLayout';
import type { AuthUser } from '../../src/api/types';

const ADMIN: AuthUser = { id: 1, sUserName: '朱子纯', sUserType: '超级管理员', sLanguage: '中文' };

function LocProbe() {
  const loc = useLocation();
  return <div data-testid="loc">{loc.pathname}</div>;
}

function renderLayout(initialEntries: string[]) {
  return renderShell(
    <Routes>
      <Route element={<AppLayout />}>
        <Route
          path="/"
          element={
            <>
              <LocProbe />
              <div data-testid="home-outlet">home-outlet</div>
            </>
          }
        />
        <Route
          path="/usr/users"
          element={
            <>
              <LocProbe />
              <div data-testid="users-outlet">users-outlet</div>
            </>
          }
        />
      </Route>
    </Routes>,
    { initialEntries, preloadedAuth: { token: 't', user: ADMIN } },
  );
}

describe('AppLayout shell', () => {
  it('renders TopBar + Outlet when ready', () => {
    renderLayout(['/']);
    // 顶栏(全部导航 + 当前用户)
    expect(screen.getByRole('button', { name: '全部导航' })).toBeInTheDocument();
    expect(screen.getByText('朱子纯(超级管理员)')).toBeInTheDocument();
    // Outlet 子内容
    expect(screen.getByTestId('home-outlet')).toBeInTheDocument();
  });

  it('toggle 全部导航 opens/closes overlay', async () => {
    renderLayout(['/']);
    expect(screen.queryByTestId('nav-overlay')).not.toBeInTheDocument();
    await userEvent.click(screen.getByRole('button', { name: '全部导航' }));
    expect(screen.getByTestId('nav-overlay')).toBeInTheDocument();
    // 点遮罩关闭
    await userEvent.click(screen.getByTestId('nav-overlay-mask'));
    expect(screen.queryByTestId('nav-overlay')).not.toBeInTheDocument();
  });

  it('nav overlay 用户列表 navigates and opens tab', async () => {
    renderLayout(['/']);
    await userEvent.click(screen.getByRole('button', { name: '全部导航' }));
    await userEvent.click(screen.getByRole('button', { name: /用户列表/ }));
    // overlay 关闭
    expect(screen.queryByTestId('nav-overlay')).not.toBeInTheDocument();
    // URL 到 /usr/users
    expect(screen.getByTestId('loc').textContent).toBe('/usr/users');
    // 顶栏出现「用户列表」标签并激活
    const tab = screen.getByTestId('tab-userlist');
    expect(tab).toHaveTextContent('用户列表');
    expect(tab.getAttribute('aria-pressed')).toBe('true');
  });

  it('clicking home tab navigates back to /', async () => {
    renderLayout(['/']);
    // 先打开用户列表标签
    await userEvent.click(screen.getByRole('button', { name: '全部导航' }));
    await userEvent.click(screen.getByRole('button', { name: /用户列表/ }));
    expect(screen.getByTestId('loc').textContent).toBe('/usr/users');
    // 点主页标签回 /
    await userEvent.click(screen.getByTestId('tab-home'));
    expect(screen.getByTestId('loc').textContent).toBe('/');
    expect(screen.getByTestId('tab-home').getAttribute('aria-pressed')).toBe('true');
  });

  it('active tab syncs with current route', () => {
    renderLayout(['/usr/users']);
    // 直接进 /usr/users,用户列表标签应存在且激活
    const tab = screen.getByTestId('tab-userlist');
    expect(tab.getAttribute('aria-pressed')).toBe('true');
  });
});