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
})
}
});
});
});
});