Commit 6deae3a69b7873e1e3b25cacad1fdc58da894ae4

Authored by zichun
1 parent ccafa9e8

feat(usr): 用户列表工具栏 UserToolbar REQ-USR-003

frontend/src/pages/usr/UserList/UserToolbar.tsx 0 → 100644
  1 +// REQ-USR-003: 用户列表工具栏(刷新/新增/导出Excel/设置齿轮占位,BR8/BR9/BR13/D7/D10)
  2 +import { Button } from 'antd';
  3 +import {
  4 + FileExcelOutlined,
  5 + PlusCircleOutlined,
  6 + ReloadOutlined,
  7 + SettingOutlined,
  8 +} from '@ant-design/icons';
  9 +import { TEXT_ADD, TEXT_EXPORT, TEXT_REFRESH } from './constants';
  10 +import styles from './UserList.module.css';
  11 +
  12 +export interface UserToolbarProps {
  13 + onRefresh(): void;
  14 + onAdd(): void;
  15 + onExport(): void;
  16 + exporting: boolean;
  17 + loading?: boolean;
  18 +}
  19 +
  20 +export default function UserToolbar({
  21 + onRefresh,
  22 + onAdd,
  23 + onExport,
  24 + exporting,
  25 + loading = false,
  26 +}: UserToolbarProps) {
  27 + return (
  28 + <div className={styles.toolbar}>
  29 + <Button
  30 + type="text"
  31 + icon={<ReloadOutlined />}
  32 + loading={loading}
  33 + onClick={() => onRefresh()}
  34 + data-testid="btn-refresh"
  35 + >
  36 + {TEXT_REFRESH}
  37 + </Button>
  38 + <Button
  39 + type="text"
  40 + icon={<PlusCircleOutlined />}
  41 + onClick={() => onAdd()}
  42 + data-testid="btn-add"
  43 + >
  44 + {TEXT_ADD}
  45 + </Button>
  46 + <Button
  47 + type="text"
  48 + icon={<FileExcelOutlined />}
  49 + loading={exporting}
  50 + disabled={exporting}
  51 + onClick={() => onExport()}
  52 + data-testid="btn-export"
  53 + >
  54 + {TEXT_EXPORT}
  55 + </Button>
  56 +
  57 + <span className={styles.toolbarSpacer} />
  58 +
  59 + {/* 设置齿轮:列显隐 / 偏好预留占位,无后端动作(D7) */}
  60 + <span
  61 + className={styles.gear}
  62 + data-testid="btn-gear"
  63 + role="presentation"
  64 + aria-label="设置"
  65 + >
  66 + <SettingOutlined />
  67 + </span>
  68 + </div>
  69 + );
  70 +}
frontend/tests/unit/UserToolbar.test.tsx 0 → 100644
  1 +// REQ-USR-003: UserToolbar 工具栏单测(BR8/BR9/BR13/D7/D10)
  2 +import { describe, it, expect, vi, beforeEach } from 'vitest';
  3 +import { screen } from '@testing-library/react';
  4 +import userEvent from '@testing-library/user-event';
  5 +import { renderShell } from './renderShell';
  6 +import UserToolbar from '../../src/pages/usr/UserList/UserToolbar';
  7 +
  8 +function setup(over?: { exporting?: boolean; loading?: boolean }) {
  9 + const onRefresh = vi.fn();
  10 + const onAdd = vi.fn();
  11 + const onExport = vi.fn();
  12 + renderShell(
  13 + <UserToolbar
  14 + onRefresh={onRefresh}
  15 + onAdd={onAdd}
  16 + onExport={onExport}
  17 + exporting={over?.exporting ?? false}
  18 + loading={over?.loading ?? false}
  19 + />,
  20 + { preloadedAuth: { token: 't', user: { id: 1, sUserName: 'a', sUserType: 'x', sLanguage: '中文' } } },
  21 + );
  22 + return { onRefresh, onAdd, onExport };
  23 +}
  24 +
  25 +describe('UserToolbar', () => {
  26 + beforeEach(() => {
  27 + vi.clearAllMocks();
  28 + });
  29 +
  30 + it('renders 刷新/新增/导出Excel/设置 buttons', () => {
  31 + setup();
  32 + expect(screen.getByTestId('btn-refresh')).toBeInTheDocument();
  33 + expect(screen.getByText('刷新')).toBeInTheDocument();
  34 + expect(screen.getByText('新增')).toBeInTheDocument();
  35 + expect(screen.getByText('导出Excel')).toBeInTheDocument();
  36 + expect(screen.getByTestId('btn-gear')).toBeInTheDocument();
  37 + });
  38 +
  39 + it('click 刷新 calls onRefresh / click 新增 calls onAdd (BR8/BR13)', async () => {
  40 + const user = userEvent.setup();
  41 + const { onRefresh, onAdd } = setup();
  42 + await user.click(screen.getByTestId('btn-refresh'));
  43 + expect(onRefresh).toHaveBeenCalledTimes(1);
  44 + await user.click(screen.getByTestId('btn-add'));
  45 + expect(onAdd).toHaveBeenCalledTimes(1);
  46 + });
  47 +
  48 + it('click 导出Excel calls onExport (BR9)', async () => {
  49 + const user = userEvent.setup();
  50 + const { onExport } = setup();
  51 + await user.click(screen.getByTestId('btn-export'));
  52 + expect(onExport).toHaveBeenCalledTimes(1);
  53 + });
  54 +
  55 + it('exporting disables 导出Excel and shows loading (BR9)', async () => {
  56 + const user = userEvent.setup();
  57 + const { onExport } = setup({ exporting: true });
  58 + const btn = screen.getByTestId('btn-export');
  59 + expect(btn).toBeDisabled();
  60 + await user.click(btn);
  61 + expect(onExport).not.toHaveBeenCalled();
  62 + });
  63 +
  64 + it('gear setting is placeholder (no callback) (D7)', async () => {
  65 + const user = userEvent.setup();
  66 + const { onRefresh, onAdd, onExport } = setup();
  67 + await user.click(screen.getByTestId('btn-gear'));
  68 + expect(onRefresh).not.toHaveBeenCalled();
  69 + expect(onAdd).not.toHaveBeenCalled();
  70 + expect(onExport).not.toHaveBeenCalled();
  71 + });
  72 +});