// REQ-USR-001 / REQ-USR-002: PermissionGroupList 权限分类勾选列表单测(渲染/勾选集合/全选 indeterminate/回勾,BR10/BR11/D3) 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 PermissionGroupList from '../../src/pages/usr/UserDetail/PermissionGroupList'; import type { PermissionItem } from '../../src/api/types'; const PERMS: PermissionItem[] = [ { id: 1, name: '默认显示', category: '基础' }, { id: 2, name: '高级查看', category: '基础' }, { id: 3, name: '导出', category: '报表' }, ]; function setup(over: { permissions?: PermissionItem[]; checkedIds?: number[]; } = {}) { const onToggle = vi.fn(); const onToggleAll = vi.fn(); renderShell( , { preloadedAuth: { token: 't', user: { id: 1, sUserName: 'a', sUserType: 'x', sLanguage: '中文' } } }, ); return { onToggle, onToggleAll }; } function rowCheckbox(id: number): HTMLInputElement { return screen.getByTestId('perm-check-' + id) as HTMLInputElement; } function allCheckbox(): HTMLInputElement { return screen.getByTestId('perm-check-all') as HTMLInputElement; } describe('PermissionGroupList', () => { beforeEach(() => { vi.clearAllMocks(); }); it('renders header 权限分类 and one row per permission', () => { setup(); expect(screen.getByText('权限分类')).toBeInTheDocument(); expect(screen.getByText('默认显示')).toBeInTheDocument(); expect(screen.getByText('高级查看')).toBeInTheDocument(); expect(screen.getByText('导出')).toBeInTheDocument(); }); it('checked rows reflect checkedIds', () => { setup({ checkedIds: [1] }); expect(rowCheckbox(1).checked).toBe(true); expect(rowCheckbox(2).checked).toBe(false); expect(rowCheckbox(3).checked).toBe(false); }); it('toggling a row calls onToggle(id, checked)', async () => { const user = userEvent.setup(); const { onToggle } = setup({ checkedIds: [1] }); await user.click(rowCheckbox(2)); expect(onToggle).toHaveBeenCalledWith(2, true); await user.click(rowCheckbox(1)); expect(onToggle).toHaveBeenCalledWith(1, false); }); it('header select-all checked when all selected; indeterminate when partial', () => { const { } = setup({ checkedIds: [1, 2, 3] }); expect(allCheckbox().checked).toBe(true); }); it('header indeterminate when partial; unchecked when none', () => { setup({ checkedIds: [1] }); const all = allCheckbox(); expect(all.checked).toBe(false); // AntD 半选用 aria-checked='mixed' 表达于 wrapper;input 仍未 checked const wrapper = all.closest('.ant-checkbox'); expect(wrapper?.classList.contains('ant-checkbox-indeterminate')).toBe(true); }); it('header toggle calls onToggleAll', async () => { const user = userEvent.setup(); const { onToggleAll } = setup({ checkedIds: [] }); await user.click(allCheckbox()); expect(onToggleAll).toHaveBeenCalledWith(true); }); it('header toggle off when all selected', async () => { const user = userEvent.setup(); const { onToggleAll } = setup({ checkedIds: [1, 2, 3] }); await user.click(allCheckbox()); expect(onToggleAll).toHaveBeenCalledWith(false); }); it('empty permissions renders empty list (no rows)', () => { setup({ permissions: [] }); expect(screen.queryByTestId('perm-check-1')).toBeNull(); }); });