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 { ActivitiesPage } from '@/client/admin/pages/Activities';
import { TestWrapper } from '~/utils/client/test-render';
// Import mocked modules
import { activityClient } from '@/client/api';
// Mock API 客户端
vi.mock('@/client/api', () => ({
activityClient: {
$get: vi.fn().mockResolvedValue({
status: 200,
ok: true,
json: async () => ({
data: [
{
id: 1,
name: '北京去程活动',
description: '北京出发的旅行活动',
type: 'departure',
startDate: '2025-10-17T08:00:00.000Z',
endDate: '2025-10-17T18:00:00.000Z',
isDisabled: 0,
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z'
},
{
id: 2,
name: '上海返程活动',
description: '上海返回的旅行活动',
type: 'return',
startDate: '2025-10-17T16:00:00.000Z',
endDate: '2025-10-17T20:00:00.000Z',
isDisabled: 0,
createdAt: '2024-01-01T00:00:00.000Z',
updatedAt: '2024-01-01T00:00:00.000Z'
}
],
pagination: {
total: 2,
current: 1,
pageSize: 20
}
})
}),
$post: vi.fn().mockResolvedValue({
status: 201,
ok: true,
json: async () => ({
id: 3,
name: '新建活动',
type: 'departure',
startDate: '2025-10-18T08:00:00.000Z',
endDate: '2025-10-18T18:00:00.000Z',
isDisabled: 0,
createdAt: '2024-01-01T00:00:00.000Z'
})
}),
':id': {
$put: vi.fn().mockResolvedValue({
status: 200,
ok: true,
json: async () => ({
id: 1,
name: '更新后的活动',
type: 'departure',
startDate: '2025-10-17T08:00:00.000Z',
endDate: '2025-10-17T18:00:00.000Z',
isDisabled: 0
})
}),
$delete: vi.fn().mockResolvedValue({
status: 204,
ok: true
})
}
}
}));
describe('ActivitiesPage 集成测试', () => {
const user = userEvent.setup();
beforeEach(() => {
vi.clearAllMocks();
});
it('应该正确渲染活动管理页面标题', async () => {
render(
);
expect(screen.getByText('活动管理')).toBeInTheDocument();
expect(screen.getByText('新建活动')).toBeInTheDocument();
});
it('应该显示活动列表和搜索功能', async () => {
render(
);
// 等待数据加载
await waitFor(() => {
expect(screen.getByPlaceholderText('搜索活动名称或描述...')).toBeInTheDocument();
});
expect(screen.getByText('活动列表')).toBeInTheDocument();
expect(screen.getByText('当前共有 2 个活动')).toBeInTheDocument();
});
it('应该处理搜索功能', async () => {
render(
);
const searchInput = screen.getByPlaceholderText('搜索活动名称或描述...');
// 输入搜索关键词
await user.type(searchInput, '北京');
// 等待防抖搜索生效
await waitFor(() => {
expect(searchInput).toHaveValue('北京');
});
});
it('应该显示活动类型筛选功能', async () => {
render(
);
// 等待数据加载
await waitFor(() => {
expect(screen.getByText('活动列表')).toBeInTheDocument();
});
// 验证类型筛选器存在
const typeFilter = screen.getByRole('combobox');
expect(typeFilter).toBeInTheDocument();
});
it('应该显示创建活动按钮并打开模态框', async () => {
render(
);
// 等待数据加载
await waitFor(() => {
expect(screen.getByText('新建活动')).toBeInTheDocument();
});
const createButton = screen.getByRole('button', { name: /新建活动/i });
await user.click(createButton);
// 验证模态框标题
expect(screen.getByRole('heading', { name: '创建活动' })).toBeInTheDocument();
});
it('应该显示分页组件', async () => {
render(
);
// 验证分页控件存在
await waitFor(() => {
expect(screen.getByText(/共 \d+ 个活动/)).toBeInTheDocument();
});
});
it('应该处理表格数据加载状态', async () => {
render(
);
// 等待数据加载完成
await waitFor(() => {
expect(screen.getByText('北京去程活动')).toBeInTheDocument();
expect(screen.getByText('上海返程活动')).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();
});
});
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();
});
});
it('应该包含启用/禁用、编辑和删除操作按钮', async () => {
render(
);
// 等待数据加载完成
await waitFor(() => {
expect(screen.getByText('北京去程活动')).toBeInTheDocument();
});
// 查找操作按钮
const actionButtons = screen.getAllByRole('button');
const hasActionButtons = actionButtons.some(button =>
button.textContent?.includes('禁用') ||
button.textContent?.includes('编辑') ||
button.innerHTML.includes('edit') ||
button.innerHTML.includes('trash')
);
expect(hasActionButtons).toBe(true);
});
it('应该处理创建活动表单提交成功', async () => {
const user = userEvent.setup();
render(
);
// 等待数据加载
await waitFor(() => {
expect(screen.getByText('北京去程活动')).toBeInTheDocument();
});
// 打开创建活动模态框
const createButton = screen.getByRole('button', { name: /新建活动/i });
await user.click(createButton);
// 验证模态框显示
expect(screen.getByRole('heading', { name: '创建活动' })).toBeInTheDocument();
// 验证表单字段存在
expect(screen.getByLabelText(/活动名称/i)).toBeInTheDocument();
expect(screen.getByLabelText(/活动描述/i)).toBeInTheDocument();
expect(screen.getByLabelText(/活动类型/i)).toBeInTheDocument();
expect(screen.getByLabelText(/开始日期/i)).toBeInTheDocument();
expect(screen.getByLabelText(/结束日期/i)).toBeInTheDocument();
});
it('应该处理启用/禁用活动操作', async () => {
const user = userEvent.setup();
render(
);
await waitFor(() => {
expect(screen.getByText('北京去程活动')).toBeInTheDocument();
});
// 查找启用/禁用按钮
const toggleButtons = screen.getAllByRole('button').filter(btn =>
btn.textContent?.includes('禁用') || btn.textContent?.includes('启用')
);
if (toggleButtons.length > 0) {
// 模拟确认对话框
window.confirm = vi.fn().mockReturnValue(true);
await user.click(toggleButtons[0]);
// 验证确认对话框被调用
expect(window.confirm).toHaveBeenCalledWith('确定要禁用这个活动吗?');
}
});
it('应该处理删除活动操作', async () => {
const user = userEvent.setup();
render(
);
await waitFor(() => {
expect(screen.getByText('北京去程活动')).toBeInTheDocument();
});
// 查找删除按钮
const deleteButtons = screen.getAllByRole('button').filter(btn =>
btn.innerHTML.includes('trash') || btn.getAttribute('aria-label')?.includes('delete')
);
if (deleteButtons.length > 0) {
// 模拟确认对话框
window.confirm = vi.fn().mockReturnValue(true);
await user.click(deleteButtons[0]);
// 验证确认对话框被调用
expect(window.confirm).toHaveBeenCalledWith('确定要删除这个活动吗?');
}
});
it('应该处理活动类型筛选', async () => {
const user = userEvent.setup();
render(
);
await waitFor(() => {
expect(screen.getByText('北京去程活动')).toBeInTheDocument();
});
// 查找类型筛选器
const typeFilter = screen.getByRole('combobox');
await user.click(typeFilter);
// 验证筛选选项存在
expect(screen.getByText('去程')).toBeInTheDocument();
expect(screen.getByText('返程')).toBeInTheDocument();
});
it('应该处理API错误场景', async () => {
// 模拟API错误
(activityClient.$get as any).mockResolvedValueOnce({
status: 500,
ok: false,
json: async () => ({ error: 'Internal server error' })
});
render(
);
// 验证页面仍然渲染基本结构
expect(screen.getByText('活动管理')).toBeInTheDocument();
expect(screen.getByText('新建活动')).toBeInTheDocument();
// 验证错误处理(组件应该优雅处理错误)
await waitFor(() => {
expect(screen.queryByText('北京去程活动')).not.toBeInTheDocument();
});
});
it('应该显示筛选标签', async () => {
const user = userEvent.setup();
render(
);
await waitFor(() => {
expect(screen.getByText('北京去程活动')).toBeInTheDocument();
});
// 输入搜索关键词
const searchInput = screen.getByPlaceholderText('搜索活动名称或描述...');
await user.type(searchInput, '北京');
// 等待防抖搜索生效
await waitFor(() => {
expect(screen.getByText('搜索: 北京')).toBeInTheDocument();
});
// 选择类型筛选
const typeFilter = screen.getByRole('combobox');
await user.click(typeFilter);
const departureOption = screen.getByText('去程');
await user.click(departureOption);
// 验证筛选标签显示
await waitFor(() => {
expect(screen.getByText('类型: 去程')).toBeInTheDocument();
});
});
it('应该清除筛选标签', async () => {
const user = userEvent.setup();
render(
);
await waitFor(() => {
expect(screen.getByText('北京去程活动')).toBeInTheDocument();
});
// 输入搜索关键词
const searchInput = screen.getByPlaceholderText('搜索活动名称或描述...');
await user.type(searchInput, '北京');
// 等待筛选标签显示
await waitFor(() => {
expect(screen.getByText('搜索: 北京')).toBeInTheDocument();
});
// 清除搜索筛选
const clearSearchButton = screen.getByText('×', { selector: 'button' });
await user.click(clearSearchButton);
// 验证搜索筛选被清除
await waitFor(() => {
expect(screen.queryByText('搜索: 北京')).not.toBeInTheDocument();
});
});
});