import { describe, it, expect, vi, beforeEach } from 'vitest'; import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import '@testing-library/jest-dom'; import { OrdersPage } from '@/client/admin/pages/Orders'; import { TestWrapper } from '~/utils/client/test-render'; // Import mocked modules import { orderClient } from '@/client/api'; import { toast } from 'sonner'; import { OrderStatus, PaymentStatus } from '@d8d/server/share/order.types'; // Mock API 客户端 vi.mock('@/client/api', () => ({ orderClient: { $get: vi.fn().mockResolvedValue({ status: 200, ok: true, json: async () => ({ data: [ { id: 1, userId: 1, routeId: 1, passengerCount: 2, totalAmount: 100.5, status: OrderStatus.PENDING_PAYMENT, paymentStatus: PaymentStatus.PENDING, passengerSnapshots: [ { name: '张三', idCard: '123456789012345678', phone: '13800138000' }, { name: '李四', idCard: '123456789012345679', phone: '13800138001' } ], routeSnapshot: { name: '测试路线', description: '测试路线描述' }, createdBy: 1, updatedBy: null, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z', user: { id: 1, username: 'testuser', phone: '13800138000' }, route: { id: 1, name: '测试路线', description: '测试路线描述' } } ], total: 1, page: 1, pageSize: 10 }) }), stats: { $get: vi.fn().mockResolvedValue({ status: 200, ok: true, json: async () => ({ total: 10, pendingPayment: 2, waitingDeparture: 3, inProgress: 1, completed: 3, cancelled: 1 }) }) } } })); // Mock toast vi.mock('sonner', () => ({ toast: { success: vi.fn(), error: vi.fn(), info: vi.fn(), warning: vi.fn(), } })); // Mock xlsx vi.mock('xlsx', () => ({ utils: { book_new: vi.fn(() => ({})), json_to_sheet: vi.fn(() => ({})), book_append_sheet: vi.fn() }, writeFile: vi.fn() })); describe('OrdersPage 集成测试', () => { const user = userEvent.setup(); beforeEach(() => { vi.clearAllMocks(); }); it('应该正确渲染订单管理页面标题', async () => { render( ); expect(screen.getByText('订单管理')).toBeInTheDocument(); }); it('应该显示订单统计面板', async () => { render( ); // 等待数据加载 await waitFor(() => { expect(screen.getByTestId('total-orders-count')).toHaveTextContent('10'); // 总订单数 expect(screen.getByTestId('pending-payment-count')).toHaveTextContent('2'); // 待支付数量 expect(screen.getByTestId('waiting-departure-count')).toHaveTextContent('3'); // 待出发数量 expect(screen.getByTestId('in-progress-count')).toHaveTextContent('1'); // 行程中数量 expect(screen.getByTestId('completed-count')).toHaveTextContent('3'); // 已完成数量 expect(screen.getByTestId('cancelled-count')).toHaveTextContent('1'); // 已取消数量 }); }); it('应该显示订单列表和搜索功能', async () => { render( ); // 等待数据加载 await waitFor(() => { expect(screen.getByPlaceholderText('搜索订单号、用户信息...')).toBeInTheDocument(); }); expect(screen.getByText('搜索')).toBeInTheDocument(); expect(screen.getByText('高级筛选')).toBeInTheDocument(); }); it('应该处理搜索功能', async () => { render( ); const searchInput = screen.getByPlaceholderText('搜索订单号、用户信息...'); const searchButton = screen.getByText('搜索'); // 输入搜索关键词 await user.type(searchInput, 'testuser'); await user.click(searchButton); // 验证搜索参数被设置 expect(searchInput).toHaveValue('testuser'); }); it('应该显示高级筛选功能', async () => { render( ); const filterButton = screen.getByRole('button', { name: '高级筛选' }); await user.click(filterButton); // 验证筛选表单显示 expect(screen.getByText('订单状态')).toBeInTheDocument(); expect(screen.getByText('支付状态')).toBeInTheDocument(); }); it('应该处理订单状态筛选', async () => { render( ); const filterButton = screen.getByRole('button', { name: '高级筛选' }); await user.click(filterButton); // 验证筛选表单显示和状态筛选标签 expect(screen.getByText('订单状态')).toBeInTheDocument(); // 验证状态筛选器存在(通过查找Select组件) const selectElements = document.querySelectorAll('[role="combobox"]'); expect(selectElements.length).toBeGreaterThan(0); }); it('应该显示分页组件', async () => { render( ); // 验证分页控件存在 await waitFor(() => { expect(screen.getByText(/共 \d+ 个订单/)).toBeInTheDocument(); }); }); it('应该处理表格数据加载状态', async () => { render( ); // 验证骨架屏或加载状态 const skeletonElements = document.querySelectorAll('[data-slot="skeleton"]'); expect(skeletonElements.length).toBeGreaterThan(0); // 等待数据加载完成 await waitFor(() => { expect(screen.getByText('testuser')).toBeInTheDocument(); }); }); it('应该显示正确的表格列标题', async () => { render( ); // 等待数据加载 await waitFor(() => { expect(screen.getByText('订单号')).toBeInTheDocument(); expect(screen.getByText('用户')).toBeInTheDocument(); expect(screen.getByText('路线')).toBeInTheDocument(); expect(screen.getByText('乘客数量')).toBeInTheDocument(); expect(screen.getByText('订单金额')).toBeInTheDocument(); expect(screen.getByText('订单状态')).toBeInTheDocument(); expect(screen.getByText('支付状态')).toBeInTheDocument(); expect(screen.getByText('创建时间')).toBeInTheDocument(); expect(screen.getByText('操作')).toBeInTheDocument(); }); }); it('应该包含查看详情操作按钮', async () => { const { container } = render( ); // 等待数据加载完成 await waitFor(() => { expect(screen.getByText('testuser')).toBeInTheDocument(); // 查找操作按钮(通过按钮元素) const actionButtons = container.querySelectorAll('button'); const hasViewButtons = Array.from(actionButtons).some(button => button.innerHTML.includes('eye') ); expect(hasViewButtons).toBe(true); }); }); it('应该打开订单详情对话框', async () => { render( ); // 等待数据加载 await waitFor(() => { expect(screen.getByText('testuser')).toBeInTheDocument(); }); // 查找查看详情按钮 const viewButtons = screen.getAllByRole('button').filter(btn => btn.innerHTML.includes('eye') ); if (viewButtons.length > 0) { await user.click(viewButtons[0]); // 验证详情对话框打开 await waitFor(() => { expect(screen.getByRole('heading', { name: '订单详情' })).toBeInTheDocument(); }); } }); it('应该在详情对话框中显示订单信息', async () => { render( ); // 等待数据加载 await waitFor(() => { expect(screen.getByText('testuser')).toBeInTheDocument(); }); // 查找查看详情按钮 const viewButtons = screen.getAllByRole('button').filter(btn => btn.innerHTML.includes('eye') ); if (viewButtons.length > 0) { await user.click(viewButtons[0]); // 验证详情对话框内容 await waitFor(() => { expect(screen.getByText('订单信息')).toBeInTheDocument(); expect(screen.getByText('用户信息')).toBeInTheDocument(); expect(screen.getByText('路线信息')).toBeInTheDocument(); expect(screen.getByText('订单详情')).toBeInTheDocument(); expect(screen.getByText('乘客信息')).toBeInTheDocument(); // 验证具体数据 expect(screen.getByText('#1')).toBeInTheDocument(); // 订单号 expect(screen.getByText('待支付')).toBeInTheDocument(); // 订单状态 expect(screen.getByText('testuser')).toBeInTheDocument(); // 用户名 expect(screen.getByText('测试路线')).toBeInTheDocument(); // 路线名称 expect(screen.getByText('2')).toBeInTheDocument(); // 乘客数量 expect(screen.getByText('¥100.5')).toBeInTheDocument(); // 订单金额 }); } }); it('应该处理API错误场景', async () => { // 模拟API错误 (orderClient.$get as any).mockResolvedValueOnce({ status: 500, ok: false, json: async () => ({ error: 'Internal server error' }) }); render( ); // 验证页面仍然渲染基本结构 expect(screen.getByText('订单管理')).toBeInTheDocument(); // 验证错误处理(组件应该优雅处理错误) await waitFor(() => { expect(screen.queryByText('testuser')).not.toBeInTheDocument(); }); }); it('应该处理响应式布局', async () => { const { container } = render( ); // 等待数据加载 await waitFor(() => { expect(screen.getByText('testuser')).toBeInTheDocument(); }); // 展开筛选表单以显示响应式网格 const filterButton = screen.getByRole('button', { name: '高级筛选' }); await user.click(filterButton); // 验证响应式网格类名 const gridElements = container.querySelectorAll('.grid'); expect(gridElements.length).toBeGreaterThan(0); // 验证响应式类名存在 const hasResponsiveClasses = container.innerHTML.includes('md:grid-cols-2'); expect(hasResponsiveClasses).toBe(true); }); it('应该显示订单总数信息', async () => { render( ); // 验证订单总数显示 await waitFor(() => { expect(screen.getByText(/共 \d+ 个订单/)).toBeInTheDocument(); }); }); it('应该处理统计API错误场景', async () => { // 模拟统计API错误 (orderClient.stats.$get as any).mockResolvedValueOnce({ status: 500, ok: false, json: async () => ({ error: 'Internal server error' }) }); render( ); // 验证页面仍然渲染基本结构 expect(screen.getByText('订单管理')).toBeInTheDocument(); // 验证统计面板显示默认值 await waitFor(() => { expect(screen.getByText('总订单数')).toBeInTheDocument(); expect(screen.getByText('0')).toBeInTheDocument(); // 默认值 }); }); it('应该处理防抖搜索功能', async () => { render( ); const searchInput = screen.getByPlaceholderText('搜索订单号、用户信息...'); // 快速输入多个字符 await user.type(searchInput, 'test'); await user.type(searchInput, 'user'); // 验证输入值正确 expect(searchInput).toHaveValue('testuser'); // 等待防抖延迟 await waitFor(() => { expect(orderClient.$get).toHaveBeenCalled(); }, { timeout: 500 }); }); it('应该处理筛选条件重置', async () => { render( ); // 打开筛选 const filterButton = screen.getByRole('button', { name: '高级筛选' }); await user.click(filterButton); // 设置筛选条件 const statusSelect = document.querySelectorAll('[role="combobox"]')[0]; await user.click(statusSelect); // 选择订单状态 const statusOption = screen.getByText('待支付'); await user.click(statusOption); // 验证重置按钮显示 const resetButton = screen.getByRole('button', { name: '重置' }); expect(resetButton).toBeInTheDocument(); // 点击重置 await user.click(resetButton); // 验证筛选条件被重置 expect(screen.queryByText('待支付')).not.toBeInTheDocument(); }); it('应该显示导出Excel按钮', async () => { render( ); // 验证导出按钮存在 await waitFor(() => { expect(screen.getByRole('button', { name: '导出Excel' })).toBeInTheDocument(); }); }); it('应该处理导出订单数据成功场景', async () => { render( ); // 等待数据加载 await waitFor(() => { expect(screen.getByRole('button', { name: '导出Excel' })).toBeInTheDocument(); }); const exportButton = screen.getByRole('button', { name: '导出Excel' }); await user.click(exportButton); // 验证导出过程被调用 await waitFor(() => { expect(orderClient.$get).toHaveBeenCalledWith({ query: { page: 1, pageSize: 10000, keyword: '', filters: JSON.stringify({ status: undefined, paymentStatus: undefined }) } }); expect(toast.success).toHaveBeenCalledWith('成功导出 1 条订单数据'); }); }); it('应该处理导出订单数据空数据场景', async () => { // 模拟空数据响应 (orderClient.$get as any).mockResolvedValueOnce({ status: 200, ok: true, json: async () => ({ data: [], total: 0, page: 1, pageSize: 10000 }) }); render( ); // 等待数据加载 await waitFor(() => { expect(screen.getByRole('button', { name: '导出Excel' })).toBeInTheDocument(); }); const exportButton = screen.getByRole('button', { name: '导出Excel' }); await user.click(exportButton); // 验证空数据提示 await waitFor(() => { expect(toast.warning).toHaveBeenCalledWith('没有找到符合条件的订单数据'); }); }); it('应该处理导出订单数据API错误场景', async () => { // 模拟API错误 (orderClient.$get as any).mockResolvedValueOnce({ status: 500, ok: false, json: async () => ({ error: 'Internal server error' }) }); render( ); // 等待数据加载 await waitFor(() => { expect(screen.getByRole('button', { name: '导出Excel' })).toBeInTheDocument(); }); const exportButton = screen.getByRole('button', { name: '导出Excel' }); await user.click(exportButton); // 验证错误处理 await waitFor(() => { expect(toast.error).toHaveBeenCalledWith('导出订单数据失败,请稍后重试'); }); }); it('应该在导出过程中禁用导出按钮', async () => { // 模拟延迟响应 (orderClient.$get as any).mockImplementationOnce(() => new Promise(resolve => setTimeout(() => resolve({ status: 200, ok: true, json: async () => ({ data: [ { id: 1, userId: 1, routeId: 1, passengerCount: 2, totalAmount: 100.5, status: OrderStatus.PENDING_PAYMENT, paymentStatus: PaymentStatus.PENDING, passengerSnapshots: [], routeSnapshot: {}, createdBy: 1, updatedBy: null, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z', user: { id: 1, username: 'testuser', phone: '13800138000' }, route: { id: 1, name: '测试路线', description: '测试路线描述' } } ], total: 1, page: 1, pageSize: 10000 }) }), 100)) ); render( ); // 等待数据加载 await waitFor(() => { expect(screen.getByRole('button', { name: '导出Excel' })).toBeInTheDocument(); }); const exportButton = screen.getByRole('button', { name: '导出Excel' }); await user.click(exportButton); // 验证按钮被禁用 expect(exportButton).toBeDisabled(); expect(exportButton).toHaveTextContent('导出中...'); // 等待导出完成 await waitFor(() => { expect(exportButton).not.toBeDisabled(); expect(exportButton).toHaveTextContent('导出Excel'); }, { timeout: 500 }); }); it('应该包含当前筛选条件在导出请求中', async () => { render( ); // 等待数据加载 await waitFor(() => { expect(screen.getByRole('button', { name: '导出Excel' })).toBeInTheDocument(); }); // 设置搜索条件 const searchInput = screen.getByPlaceholderText('搜索订单号、用户信息...'); await user.type(searchInput, 'testuser'); // 设置筛选条件 const filterButton = screen.getByRole('button', { name: '高级筛选' }); await user.click(filterButton); // 选择订单状态 const statusSelect = document.querySelectorAll('[role="combobox"]')[0]; await user.click(statusSelect); const statusOption = screen.getByText('待支付'); await user.click(statusOption); // 点击导出 const exportButton = screen.getByRole('button', { name: '导出Excel' }); await user.click(exportButton); // 验证导出请求包含筛选条件 await waitFor(() => { expect(orderClient.$get).toHaveBeenCalledWith({ query: { page: 1, pageSize: 10000, keyword: 'testuser', filters: JSON.stringify({ status: OrderStatus.PENDING_PAYMENT, paymentStatus: undefined }) } }); }); }); });