import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { TestWrapper } from '@/client/__test_utils__/test-render'; import { UsersPage } from '../Users'; import { userClient } from '@/client/api'; // Mock the API client vi.mock('@/client/api', () => ({ userClient: { $get: vi.fn(), $post: vi.fn(), ':id': { $put: vi.fn(), $delete: vi.fn() } } })); // Mock the toast notification vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn() } })); describe('UsersPage Component', () => { const mockUsers = [ { id: 1, username: 'admin', nickname: '管理员', email: 'admin@example.com', phone: '13800138000', name: '系统管理员', isDisabled: 0, createdAt: '2024-01-01T00:00:00.000Z', roles: [{ id: 1, name: 'admin' }] }, { id: 2, username: 'user1', nickname: '用户1', email: 'user1@example.com', phone: '13900139000', name: '张三', isDisabled: 0, createdAt: '2024-01-02T00:00:00.000Z', roles: [{ id: 2, name: 'user' }] } ]; beforeEach(() => { vi.clearAllMocks(); // Mock successful API response - return a proper Response object (userClient.$get as any).mockImplementation(async (params: any) => { console.log('API called with params:', params); return { status: 200, ok: true, headers: new Headers({ 'content-type': 'application/json' }), json: async () => ({ data: mockUsers, pagination: { total: 2, current: 1, pageSize: 10 } }) }; }); console.log('Mock setup complete'); }); it('应该渲染用户列表页面', async () => { render( ); // 检查页面标题 expect(screen.getByText('用户管理')).toBeInTheDocument(); // 检查创建用户按钮 expect(screen.getByText('创建用户')).toBeInTheDocument(); // 等待数据加载完成 await waitFor(() => { expect(screen.getByText('admin')).toBeInTheDocument(); expect(screen.getByText('user1')).toBeInTheDocument(); }, { timeout: 10000 }); // 检查API是否被调用 expect(userClient.$get).toHaveBeenCalled(); // 检查用户总数显示 expect(screen.getByText(/共 2 位用户/)).toBeInTheDocument(); }); it('应该显示搜索框和过滤按钮', async () => { render( ); // 等待数据加载完成 await waitFor(() => { expect(screen.getByText('admin')).toBeInTheDocument(); }, { timeout: 10000 }); // 检查搜索框 expect(screen.getByPlaceholderText('搜索用户名、昵称或邮箱...')).toBeInTheDocument(); // 检查搜索按钮 expect(screen.getByText('搜索')).toBeInTheDocument(); // 检查高级筛选按钮 expect(screen.getByText('高级筛选')).toBeInTheDocument(); }); it('应该支持关键词搜索', async () => { const user = userEvent.setup(); render( ); // 等待数据加载完成 await waitFor(() => { expect(screen.getByText('admin')).toBeInTheDocument(); }, { timeout: 10000 }); // 在搜索框中输入关键词 - 使用paste来避免防抖中间状态 const searchInput = screen.getByPlaceholderText('搜索用户名、昵称或邮箱...'); await user.clear(searchInput); await user.click(searchInput); await user.paste('admin'); // 等待防抖完成(300ms + 缓冲时间) await new Promise(resolve => setTimeout(resolve, 400)); // 点击搜索按钮 const searchButton = screen.getByText('搜索'); await user.click(searchButton); // 验证API被调用正确的参数 const calls = (userClient.$get as any).mock.calls; const lastCall = calls[calls.length - 1]; // 检查搜索参数 const queryParams = lastCall[0].query; expect(queryParams.page).toBe(1); expect(queryParams.pageSize).toBe(10); expect(queryParams.keyword).toBe('admin'); }); it('应该显示高级筛选面板', async () => { const user = userEvent.setup(); render( ); // 等待数据加载完成 await waitFor(() => { expect(screen.getByText('admin')).toBeInTheDocument(); }, { timeout: 10000 }); // 点击高级筛选按钮 const filterButton = screen.getByText('高级筛选'); await user.click(filterButton); // 检查筛选面板是否显示 expect(screen.getByText('用户状态')).toBeInTheDocument(); expect(screen.getByText('用户角色')).toBeInTheDocument(); // 使用更具体的查询来避免与表格标题冲突 expect(screen.getAllByText('创建时间')[0]).toBeInTheDocument(); }); it('应该显示加载骨架屏', async () => { // 清除之前的mock vi.clearAllMocks(); // 模拟延迟响应 (userClient.$get as any).mockImplementation(() => new Promise(resolve => setTimeout(() => resolve({ status: 200, ok: true, json: async () => ({ data: mockUsers, pagination: { total: 2, current: 1, pageSize: 10 } }) }), 100)) ); render( ); // 检查骨架屏是否显示 expect(screen.getByText('用户管理')).toBeInTheDocument(); expect(screen.getByText('创建用户')).toBeInTheDocument(); // 检查骨架屏元素 // 先检查所有元素来调试角色问题 const allElements = screen.getAllByRole('generic'); console.log('All elements with generic role:', allElements.length); // 尝试查找骨架屏元素 const skeletons = screen.queryAllByRole('status'); console.log('Elements with status role:', skeletons.length); // 如果找不到status角色,尝试通过data-slot查找 if (skeletons.length === 0) { const skeletonElements = screen.queryAllByTestId('skeleton'); if (skeletonElements.length === 0) { // 使用data-slot属性查找 const slotSkeletons = document.querySelectorAll('[data-slot="skeleton"]'); expect(slotSkeletons.length).toBeGreaterThan(0); } else { expect(skeletonElements.length).toBeGreaterThan(0); } } else { expect(skeletons.length).toBeGreaterThan(0); } // 等待数据加载完成 await waitFor(() => { expect(screen.getByText('admin')).toBeInTheDocument(); }); // 检查骨架屏已消失 const remainingSkeletons = screen.queryAllByRole('status'); if (remainingSkeletons.length === 0) { // 也检查通过testid查找的骨架屏 const remainingTestidSkeletons = screen.queryAllByTestId('skeleton'); if (remainingTestidSkeletons.length === 0) { // 检查通过data-slot查找的骨架屏 const remainingSlotSkeletons = document.querySelectorAll('[data-slot="skeleton"]'); expect(remainingSlotSkeletons).toHaveLength(0); } else { expect(remainingTestidSkeletons).toHaveLength(0); } } else { expect(remainingSkeletons).toHaveLength(0); } }); it('应该处理API错误', async () => { // 模拟API错误 (userClient.$get as any).mockResolvedValue({ status: 500, ok: false, json: async () => ({ error: 'Internal server error' }) }); render( ); // 检查页面仍然渲染 expect(screen.getByText('用户管理')).toBeInTheDocument(); expect(screen.getByText('创建用户')).toBeInTheDocument(); // 等待加载完成(应该没有数据) await waitFor(() => { expect(screen.queryByText('admin')).not.toBeInTheDocument(); expect(screen.queryByText('user1')).not.toBeInTheDocument(); }); }); it('应该显示分页控件', async () => { // 模拟多页数据 (userClient.$get as any).mockResolvedValue({ status: 200, ok: true, json: async () => ({ data: mockUsers, pagination: { total: 25, current: 1, pageSize: 10 } }) }); render( ); await waitFor(() => { // 检查分页控件 expect(screen.getByText('1')).toBeInTheDocument(); expect(screen.getByText('2')).toBeInTheDocument(); expect(screen.getByText('3')).toBeInTheDocument(); }); }); });