// REQ-USR-003: UserTable 表格单测(BR1/BR6/BR11/BR12/BR14/D8) import { describe, it, expect, vi, beforeEach } from 'vitest'; import { screen } 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 { 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( , { 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(); }); });