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 { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { RouteForm } from '@/client/admin/components/RouteForm'; // 创建测试包装器 const TestWrapper = ({ children }: { children: React.ReactNode }) => { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false, }, }, }); return ( {children} ); }; // Mock API 客户端 import { locationClient, activityClient } from '@/client/api'; // Mock API 客户端 vi.mock('@/client/api', () => ({ locationClient: { $get: vi.fn().mockResolvedValue({ status: 200, ok: true, json: async () => ({ data: [ { id: 1, name: '北京工人体育场', address: '北京市朝阳区工人体育场北路', province: { id: 1, name: '北京市', code: '110000' }, city: { id: 2, name: '北京市', code: '110100' }, district: { id: 3, name: '朝阳区', code: '110105' }, latitude: 39.9334, longitude: 116.4473, isDisabled: 0, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z' }, { id: 2, name: '北京鸟巢', address: '北京市朝阳区国家体育场南路1号', province: { id: 1, name: '北京市', code: '110000' }, city: { id: 2, name: '北京市', code: '110100' }, district: { id: 3, name: '朝阳区', code: '110105' }, latitude: 39.9929, longitude: 116.3963, isDisabled: 0, createdAt: '2024-01-01T00:00:00.000Z', updatedAt: '2024-01-01T00:00:00.000Z' } ], pagination: { current: 1, pageSize: 100, total: 2 } }) }) }, activityClient: { $get: vi.fn().mockResolvedValue({ status: 200, ok: true, json: async () => ({ data: [ { id: 1, name: '测试活动', type: 'departure', startDate: '2025-10-17T08:00:00.000Z', endDate: '2025-10-17T18:00:00.000Z', isDisabled: 0 } ], pagination: { current: 1, pageSize: 100, total: 1 } }) }) } })); // Mock ActivitySelect 组件,使其自动返回有效的 activityId vi.mock('@/client/admin/components/ActivitySelect', () => ({ ActivitySelect: ({ value, onValueChange, placeholder, 'data-testid': dataTestId }: any) => ( ) })); describe('RouteForm', () => { const user = userEvent.setup(); const mockOnSubmit = vi.fn(); const mockOnCancel = vi.fn(); beforeEach(() => { vi.clearAllMocks(); }); it('应该正确渲染创建表单', () => { render( ); expect(screen.getByLabelText('路线名称 *')).toBeInTheDocument(); expect(screen.getByText('车型 *')).toBeInTheDocument(); expect(screen.getByText('出发地 *')).toBeInTheDocument(); expect(screen.getByTestId('start-location-select')).toBeInTheDocument(); expect(screen.getByText('目的地 *')).toBeInTheDocument(); expect(screen.getByTestId('end-location-select')).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('应该正确渲染编辑表单', () => { const initialData = { id: 1, name: '测试路线', description: '测试路线描述', startLocationId: 1, endLocationId: 2, pickupPoint: '测试上车点', dropoffPoint: '测试下车点', departureTime: new Date('2025-10-17T08:00:00.000Z'), vehicleType: 'bus' as const, price: 100, seatCount: 50, availableSeats: 45, activityId: 1, isDisabled: 0 }; render( ); expect(screen.getByDisplayValue('测试路线')).toBeInTheDocument(); expect(screen.getByDisplayValue('测试路线描述')).toBeInTheDocument(); expect(screen.getByDisplayValue('测试上车点')).toBeInTheDocument(); expect(screen.getByDisplayValue('测试下车点')).toBeInTheDocument(); expect(screen.getByDisplayValue('100')).toBeInTheDocument(); expect(screen.getByDisplayValue('50')).toBeInTheDocument(); expect(screen.getByDisplayValue('45')).toBeInTheDocument(); }); it('应该能够选择出发地和目的地', async () => { render( ); // 点击出发地选择器 const startLocationSelect = screen.getByTestId('start-location-select'); await user.click(startLocationSelect); // 等待地点数据加载 await waitFor(() => { expect(screen.getByText('北京工人体育场')).toBeInTheDocument(); expect(screen.getByText('北京鸟巢')).toBeInTheDocument(); }); // 选择出发地 - 使用更具体的选择器 const startLocationOptions = screen.getAllByRole('option'); const startLocationOption = startLocationOptions.find(option => option.textContent?.includes('北京工人体育场') ); expect(startLocationOption).toBeDefined(); await user.click(startLocationOption!); // 验证出发地被选中 await waitFor(() => { expect(screen.getByTestId('start-location-select')).toHaveTextContent('北京工人体育场'); }); // 点击目的地选择器 const endLocationSelect = screen.getByTestId('end-location-select'); await user.click(endLocationSelect); // 等待地点数据加载 await waitFor(() => { expect(screen.getAllByRole('option').length).toBeGreaterThan(0); }); // 选择目的地 const endLocationOptions = screen.getAllByRole('option'); const endLocationOption = endLocationOptions.find(option => option.textContent?.includes('北京鸟巢') ); expect(endLocationOption).toBeDefined(); await user.click(endLocationOption!); // 验证目的地被选中 await waitFor(() => { expect(screen.getByTestId('end-location-select')).toHaveTextContent('北京鸟巢'); }); }); it('应该验证必填字段', async () => { render( ); // 尝试提交空表单 await user.click(screen.getByRole('button', { name: '创建路线' })); // 验证错误消息 await waitFor(() => { expect(screen.getByText('路线名称不能为空')).toBeInTheDocument(); }); }); it('应该能够选择出发地和目的地', async () => { render( ); // 选择出发地 const startLocationSelect = screen.getByTestId('start-location-select'); await user.click(startLocationSelect); await waitFor(() => { expect(screen.getByText('北京工人体育场')).toBeInTheDocument(); }); // 使用更可靠的方法选择地点 const startLocationOptions = screen.getAllByRole('option'); const startLocationOption = startLocationOptions.find(option => option.textContent?.includes('北京工人体育场') ); expect(startLocationOption).toBeDefined(); await user.click(startLocationOption!); // 验证出发地被选中 await waitFor(() => { expect(screen.getByTestId('start-location-select')).toHaveTextContent('北京工人体育场'); }); // 选择目的地 const endLocationSelect = screen.getByTestId('end-location-select'); await user.click(endLocationSelect); await waitFor(() => { expect(screen.getAllByRole('option').length).toBeGreaterThan(0); }); const endLocationOptions = screen.getAllByRole('option'); const endLocationOption = endLocationOptions.find(option => option.textContent?.includes('北京鸟巢') ); expect(endLocationOption).toBeDefined(); await user.click(endLocationOption!); // 验证目的地被选中 await waitFor(() => { expect(screen.getByTestId('end-location-select')).toHaveTextContent('北京鸟巢'); }); }); it('应该能够取消表单', async () => { render( ); await user.click(screen.getByRole('button', { name: '取消' })); expect(mockOnCancel).toHaveBeenCalled(); }); it('应该在编辑模式下显示状态选择', () => { const initialData = { id: 1, name: '测试路线', description: '测试路线描述', startLocationId: 1, endLocationId: 2, pickupPoint: '测试上车点', dropoffPoint: '测试下车点', departureTime: new Date('2025-10-17T08:00:00.000Z'), vehicleType: 'bus' as const, price: 100, seatCount: 50, availableSeats: 45, activityId: 1, isDisabled: 0 }; render( ); expect(screen.getByLabelText('路线状态')).toBeInTheDocument(); }); });