import React from 'react' import { render, screen, fireEvent, waitFor } from '@testing-library/react' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import { LocationSearch } from '../../src/components/LocationSearch' // Mock API 客户端 const mockLocationClient = { $get: jest.fn() } jest.mock('../../src/api', () => ({ locationClient: mockLocationClient })) // 创建测试用的 QueryClient const createTestQueryClient = () => new QueryClient({ defaultOptions: { queries: { retry: false, }, }, }) // 包装组件 const Wrapper = ({ children }: { children: React.ReactNode }) => { const queryClient = createTestQueryClient() return ( {children} ) } // 模拟数据 const mockLocations = [ { id: 1, name: '北京南站', province: '北京市', city: '北京市', district: '丰台区', address: '北京市丰台区北京南站' }, { id: 2, name: '首都机场', province: '北京市', city: '北京市', district: '顺义区', address: '北京市顺义区首都机场' }, { id: 3, name: '天安门广场', province: '北京市', city: '北京市', district: '东城区', address: '北京市东城区天安门广场' } ] describe('LocationSearch 组件', () => { beforeEach(() => { // 重置所有 mock jest.clearAllMocks() // 设置默认的 mock 返回值 mockLocationClient.$get.mockResolvedValue({ status: 200, json: async () => mockLocations }) }) test('应该正确渲染组件', () => { const onChange = jest.fn() render( ) // 检查输入框 expect(screen.getByPlaceholderText('搜索地点')).toBeInTheDocument() }) test('应该显示自定义占位符', () => { const onChange = jest.fn() render( ) expect(screen.getByPlaceholderText('搜索出发地')).toBeInTheDocument() }) test('应该处理输入变化和防抖搜索', async () => { const onChange = jest.fn() render( ) const input = screen.getByPlaceholderText('搜索地点') // 输入搜索关键词 fireEvent.input(input, { target: { value: '北京' } }) // 检查输入值 expect(input).toHaveValue('北京') // 等待防抖时间 await waitFor(() => { expect(mockLocationClient.$get).toHaveBeenCalledWith({ query: { keyword: '北京', provinceId: undefined, cityId: undefined, districtId: undefined } }) }, { timeout: 500 }) }) test('应该显示搜索结果', async () => { const onChange = jest.fn() render( ) const input = screen.getByPlaceholderText('搜索地点') // 输入搜索关键词 fireEvent.input(input, { target: { value: '北京' } }) // 等待搜索结果 await waitFor(() => { expect(screen.getByText('北京南站')).toBeInTheDocument() expect(screen.getByText('首都机场')).toBeInTheDocument() expect(screen.getByText('天安门广场')).toBeInTheDocument() }) // 检查地点信息显示 expect(screen.getByText('北京南站 · 丰台区 · 北京市 · 北京市')).toBeInTheDocument() expect(screen.getByText('北京市丰台区北京南站')).toBeInTheDocument() }) test('应该处理地点选择', async () => { const onChange = jest.fn() render( ) const input = screen.getByPlaceholderText('搜索地点') // 输入搜索关键词 fireEvent.input(input, { target: { value: '北京' } }) // 等待搜索结果 await waitFor(() => { expect(screen.getByText('北京南站')).toBeInTheDocument() }) // 选择地点 const locationItem = screen.getByText('北京南站') fireEvent.click(locationItem) // 检查 onChange 被调用 expect(onChange).toHaveBeenCalledWith(mockLocations[0]) // 检查输入框值更新 expect(input).toHaveValue('北京南站') // 检查搜索结果隐藏 expect(screen.queryByText('首都机场')).not.toBeInTheDocument() }) test('应该处理清除操作', () => { const onChange = jest.fn() render( ) const input = screen.getByPlaceholderText('搜索地点') // 输入搜索关键词 fireEvent.input(input, { target: { value: '北京' } }) // 检查清除按钮出现 const clearButton = screen.getByText('×') expect(clearButton).toBeInTheDocument() // 点击清除按钮 fireEvent.click(clearButton) // 检查输入框被清空 expect(input).toHaveValue('') // 检查 onChange 被调用 expect(onChange).toHaveBeenCalledWith(null) }) test('应该显示当前选择的地点', () => { const selectedLocation = { id: 1, name: '北京南站', province: '北京市', city: '北京市', district: '丰台区', address: '北京市丰台区北京南站' } const onChange = jest.fn() render( ) // 检查已选择的地点显示 expect(screen.getByText('已选择: 北京南站 · 丰台区 · 北京市 · 北京市')).toBeInTheDocument() }) test('应该支持地区筛选', async () => { const onChange = jest.fn() const areaFilter = { provinceId: 1, cityId: 11, districtId: 101 } render( ) const input = screen.getByPlaceholderText('搜索地点') // 输入搜索关键词 fireEvent.input(input, { target: { value: '北京' } }) // 等待搜索调用 await waitFor(() => { expect(mockLocationClient.$get).toHaveBeenCalledWith({ query: { keyword: '北京', provinceId: 1, cityId: 11, districtId: 101 } }) }, { timeout: 500 }) }) test('应该显示搜索中状态', async () => { // 模拟延迟响应 mockLocationClient.$get.mockImplementation(() => new Promise(resolve => { setTimeout(() => { resolve({ status: 200, json: async () => mockLocations }) }, 100) }) ) const onChange = jest.fn() render( ) const input = screen.getByPlaceholderText('搜索地点') // 输入搜索关键词 fireEvent.input(input, { target: { value: '北京' } }) // 检查搜索中状态 await waitFor(() => { expect(screen.getByText('搜索中...')).toBeInTheDocument() }) }) test('应该显示无结果状态', async () => { // 模拟空结果 mockLocationClient.$get.mockResolvedValue({ status: 200, json: async () => [] }) const onChange = jest.fn() render( ) const input = screen.getByPlaceholderText('搜索地点') // 输入搜索关键词 fireEvent.input(input, { target: { value: '不存在的地点' } }) // 检查无结果状态 await waitFor(() => { expect(screen.getByText('未找到相关地点')).toBeInTheDocument() }) }) test('应该显示请输入关键词状态', () => { const onChange = jest.fn() render( ) const input = screen.getByPlaceholderText('搜索地点') // 聚焦输入框但不输入内容 fireEvent.focus(input) // 检查提示状态 expect(screen.getByText('请输入地点名称')).toBeInTheDocument() }) test('应该处理 API 错误', async () => { // 模拟 API 错误 mockLocationClient.$get.mockResolvedValue({ status: 500, json: async () => ({ success: false, message: '服务器错误' }) }) const onChange = jest.fn() render( ) const input = screen.getByPlaceholderText('搜索地点') // 输入搜索关键词 fireEvent.input(input, { target: { value: '北京' } }) // 组件应该正常处理错误,不显示搜索结果 await waitFor(() => { expect(mockLocationClient.$get).toHaveBeenCalled() }) // 检查没有显示错误信息(组件应该静默处理错误) expect(screen.queryByText('服务器错误')).not.toBeInTheDocument() }) })