UserTable.test.tsx 4.85 KB
// REQ-USR-003: UserTable 表格单测(BR1/BR6/BR11/BR12/BR14/D8)
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { screen, within } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { renderShell } from './renderShell';
import UserTable from '../../src/pages/usr/UserList/UserTable';
import type { UserVO } from '../../src/api/types';

function makeUser(id: number, over?: Partial<UserVO>): UserVO {
  return {
    id,
    sUserName: `user${id}`,
    employeeName: `员工${id}`,
    sUserNo: `U00${id}`,
    departmentName: `部门${id}`,
    sUserType: '普通用户',
    sLanguage: '中文',
    iIsVoid: 0,
    tLastLoginDate: '2024-02-01T10:00:00',
    sCreator: 'admin',
    tCreateDate: '2024-01-01T00:00:00',
    ...over,
  };
}

interface Props {
  rows?: UserVO[];
  loading?: boolean;
  total?: number;
  pageNum?: number;
  pageSize?: number;
}

function setup(props?: Props) {
  const onChangePage = vi.fn();
  const onRowDoubleClick = vi.fn();
  const onSelectRow = vi.fn();
  renderShell(
    <UserTable
      rows={props?.rows ?? [makeUser(1), makeUser(2)]}
      loading={props?.loading ?? false}
      total={props?.total ?? 2}
      pageNum={props?.pageNum ?? 1}
      pageSize={props?.pageSize ?? 10}
      onChangePage={onChangePage}
      onRowDoubleClick={onRowDoubleClick}
      onSelectRow={onSelectRow}
    />,
    { preloadedAuth: { token: 't', user: { id: 1, sUserName: 'a', sUserType: 'x', sLanguage: '中文' } } },
  );
  return { onChangePage, onRowDoubleClick, onSelectRow };
}

const HEADERS = [
  '序号',
  '用户名',
  '员工名',
  '用户号',
  '部门',
  '用户类型',
  '语言',
  '作废',
  '登录日期',
  '制单人',
  '制单日期',
];

describe('UserTable', () => {
  beforeEach(() => {
    vi.clearAllMocks();
  });

  it('renders 11 column headers in order', () => {
    setup();
    const headerCells = screen.getAllByRole('columnheader');
    const texts = headerCells.map((c) => c.textContent ?? '');
    for (const h of HEADERS) {
      expect(texts.some((t) => t.includes(h))).toBe(true);
    }
    // 业务列(序号..制单日期)顺序一致(排除首个 radio 选择列空表头)
    const businessTexts = texts.filter((t) => HEADERS.some((h) => t.includes(h)));
    const orderIdx = HEADERS.map((h) => businessTexts.findIndex((t) => t.includes(h)));
    const sorted = [...orderIdx].sort((a, b) => a - b);
    expect(orderIdx).toEqual(sorted);
  });

  it('serial number is page-aware (BR1)', () => {
    setup({ pageNum: 2, pageSize: 10, rows: [makeUser(11), makeUser(12)], total: 30 });
    // 第 2 页首行序号 = (2-1)*10 + 0 + 1 = 11
    expect(screen.getByText('11')).toBeInTheDocument();
    expect(screen.getByText('12')).toBeInTheDocument();
  });

  it('作废 column renders 否/是 read-only (BR6)', async () => {
    const user = userEvent.setup();
    const { onSelectRow } = setup({
      rows: [makeUser(1, { iIsVoid: 0 }), makeUser(2, { iIsVoid: 1 })],
      total: 2,
    });
    expect(screen.getByText('否')).toBeInTheDocument();
    expect(screen.getByText('是')).toBeInTheDocument();
    // 点击「作废」单元不触发任何选择 / 写动作
    await user.click(screen.getByText('是'));
    expect(onSelectRow).not.toHaveBeenCalled();
  });

  it('double click row navigates via onRowDoubleClick (BR12)', async () => {
    const user = userEvent.setup();
    const { onRowDoubleClick } = setup({ rows: [makeUser(7)], total: 1 });
    await user.dblClick(screen.getByText('user7'));
    expect(onRowDoubleClick).toHaveBeenCalledTimes(1);
    expect(onRowDoubleClick.mock.calls[0][0]).toMatchObject({ id: 7 });
  });

  it('controlled pagination reflects current/pageSize/total + showTotal (BR11)', async () => {
    const user = userEvent.setup();
    const { onChangePage } = setup({
      rows: [makeUser(1)],
      total: 37,
      pageNum: 1,
      pageSize: 10,
    });
    expect(screen.getByText('共 37 条记录')).toBeInTheDocument();
    // 点下一页 → onChangePage 收到 pageNum=2(renderShell 注入 zhCN locale,标题为「下一页」)
    await user.click(screen.getByTitle('下一页'));
    expect(onChangePage).toHaveBeenCalled();
    expect(onChangePage.mock.calls[0][0]).toBe(2);
    expect(onChangePage.mock.calls[0][1]).toBe(10);
  });

  it('empty rows shows Empty 暂无匹配的用户 (BR14)', () => {
    setup({ rows: [], total: 0 });
    expect(screen.getByText('暂无匹配的用户')).toBeInTheDocument();
  });

  it('radio rowSelection single-select reports key without query (D8)', async () => {
    const user = userEvent.setup();
    const { onSelectRow, onChangePage } = setup({ rows: [makeUser(5)], total: 1 });
    const radio = screen.getByRole('radio');
    await user.click(radio);
    expect(onSelectRow).toHaveBeenCalledWith(5);
    // 选择不触发取数 / 翻页
    expect(onChangePage).not.toHaveBeenCalled();
  });
});