Quellcode durchsuchen

🗑️ chore(components): 删除未使用的组件和测试文件

- 移除 AreaCascader 省市区选择器组件及其测试文件
- 移除 LocationSearch 地点搜索组件及其测试文件
- 移除 RouteFilter 路线筛选组件及其测试文件
- 清理相关组件依赖和测试用例
yourname vor 3 Monaten
Ursprung
Commit
e97bfb0e8c

+ 0 - 178
mini/src/components/AreaCascader.tsx

@@ -1,178 +0,0 @@
-import React, { useState, useEffect } from 'react'
-import { View, Text, Picker } from '@tarojs/components'
-import { useQuery } from '@tanstack/react-query'
-import { areaClient } from '../api'
-
-interface Area {
-  id: number
-  name: string
-  level: number
-  parentId?: number
-}
-
-interface AreaCascaderProps {
-  value?: number[]
-  onChange?: (value: number[]) => void
-  placeholder?: string
-}
-
-export const AreaCascader: React.FC<AreaCascaderProps> = ({
-  value = [],
-  onChange,
-  placeholder = '请选择省市区'
-}) => {
-  const [selectedProvince, setSelectedProvince] = useState<number | undefined>(value[0])
-  const [selectedCity, setSelectedCity] = useState<number | undefined>(value[1])
-  const [selectedDistrict, setSelectedDistrict] = useState<number | undefined>(value[2])
-
-  // 获取省份列表
-  const { data: provinces = [] } = useQuery({
-    queryKey: ['areas', 'provinces'],
-    queryFn: async () => {
-      const res = await areaClient.provinces.$get()
-      if (res.status !== 200) throw new Error('获取省份列表失败')
-      return await res.json()
-    }
-  })
-
-  // 获取城市列表
-  const { data: cities = [] } = useQuery({
-    queryKey: ['areas', 'cities', selectedProvince],
-    queryFn: async () => {
-      if (!selectedProvince) return []
-      const res = await areaClient.cities.$get({
-        query: { provinceId: selectedProvince }
-      })
-      if (res.status !== 200) throw new Error('获取城市列表失败')
-      return await res.json()
-    },
-    enabled: !!selectedProvince
-  })
-
-  // 获取区县列表
-  const { data: districts = [] } = useQuery({
-    queryKey: ['areas', 'districts', selectedCity],
-    queryFn: async () => {
-      if (!selectedCity) return []
-      const res = await areaClient.districts.$get({
-        query: { cityId: selectedCity }
-      })
-      if (res.status !== 200) throw new Error('获取区县列表失败')
-      return await res.json()
-    },
-    enabled: !!selectedCity
-  })
-
-  // 处理省份选择
-  const handleProvinceChange = (e: any) => {
-    const provinceId = Number(e.detail.value)
-    setSelectedProvince(provinceId)
-    setSelectedCity(undefined)
-    setSelectedDistrict(undefined)
-
-    if (onChange) {
-      onChange([provinceId])
-    }
-  }
-
-  // 处理城市选择
-  const handleCityChange = (e: any) => {
-    const cityId = Number(e.detail.value)
-    setSelectedCity(cityId)
-    setSelectedDistrict(undefined)
-
-    if (onChange && selectedProvince) {
-      onChange([selectedProvince, cityId])
-    }
-  }
-
-  // 处理区县选择
-  const handleDistrictChange = (e: any) => {
-    const districtId = Number(e.detail.value)
-    setSelectedDistrict(districtId)
-
-    if (onChange && selectedProvince && selectedCity) {
-      onChange([selectedProvince, selectedCity, districtId])
-    }
-  }
-
-  // 获取显示文本
-  const getDisplayText = () => {
-    if (!selectedProvince) return placeholder
-
-    const province = provinces.find(p => p.id === selectedProvince)
-    const city = cities.find(c => c.id === selectedCity)
-    const district = districts.find(d => d.id === selectedDistrict)
-
-    if (district && city && province) {
-      return `${province.name} ${city.name} ${district.name}`
-    } else if (city && province) {
-      return `${province.name} ${city.name}`
-    } else if (province) {
-      return province.name
-    }
-
-    return placeholder
-  }
-
-  return (
-    <View className="flex flex-col space-y-2">
-      <View className="flex space-x-2">
-        {/* 省份选择器 */}
-        <View className="flex-1">
-          <Text className="text-sm text-gray-600 mb-1 block">省份</Text>
-          <Picker
-            mode="selector"
-            range={provinces}
-            rangeKey="name"
-            value={selectedProvince ? provinces.findIndex(p => p.id === selectedProvince) : 0}
-            onChange={handleProvinceChange}
-          >
-            <View className="border border-gray-300 rounded px-3 py-2 text-sm">
-              {selectedProvince ? provinces.find(p => p.id === selectedProvince)?.name : '请选择省份'}
-            </View>
-          </Picker>
-        </View>
-
-        {/* 城市选择器 */}
-        <View className="flex-1">
-          <Text className="text-sm text-gray-600 mb-1 block">城市</Text>
-          <Picker
-            mode="selector"
-            range={cities}
-            rangeKey="name"
-            value={selectedCity ? cities.findIndex(c => c.id === selectedCity) : 0}
-            onChange={handleCityChange}
-            disabled={!selectedProvince}
-          >
-            <View className={`border border-gray-300 rounded px-3 py-2 text-sm ${!selectedProvince ? 'bg-gray-100 text-gray-400' : ''}`}>
-              {selectedCity ? cities.find(c => c.id === selectedCity)?.name : '请选择城市'}
-            </View>
-          </Picker>
-        </View>
-
-        {/* 区县选择器 */}
-        <View className="flex-1">
-          <Text className="text-sm text-gray-600 mb-1 block">区县</Text>
-          <Picker
-            mode="selector"
-            range={districts}
-            rangeKey="name"
-            value={selectedDistrict ? districts.findIndex(d => d.id === selectedDistrict) : 0}
-            onChange={handleDistrictChange}
-            disabled={!selectedCity}
-          >
-            <View className={`border border-gray-300 rounded px-3 py-2 text-sm ${!selectedCity ? 'bg-gray-100 text-gray-400' : ''}`}>
-              {selectedDistrict ? districts.find(d => d.id === selectedDistrict)?.name : '请选择区县'}
-            </View>
-          </Picker>
-        </View>
-      </View>
-
-      {/* 显示完整选择路径 */}
-      <View className="text-sm text-gray-500">
-        {getDisplayText()}
-      </View>
-    </View>
-  )
-}

+ 0 - 175
mini/src/components/LocationSearch.tsx

@@ -1,175 +0,0 @@
-import React, { useState, useEffect } from 'react'
-import { View, Text, Input, ScrollView } from '@tarojs/components'
-import { useQuery } from '@tanstack/react-query'
-import { locationClient } from '../api'
-
-interface Location {
-  id: number
-  name: string
-  province?: string
-  city?: string
-  district?: string
-  address?: string
-}
-
-interface LocationSearchProps {
-  value?: Location | null
-  onChange?: (location: Location | null) => void
-  placeholder?: string
-  areaFilter?: {
-    provinceId?: number
-    cityId?: number
-    districtId?: number
-  }
-}
-
-export const LocationSearch: React.FC<LocationSearchProps> = ({
-  value,
-  onChange,
-  placeholder = '搜索地点',
-  areaFilter = {}
-}) => {
-  const [keyword, setKeyword] = useState('')
-  const [showResults, setShowResults] = useState(false)
-  const [debouncedKeyword, setDebouncedKeyword] = useState('')
-
-  // 防抖处理
-  useEffect(() => {
-    const timer = setTimeout(() => {
-      setDebouncedKeyword(keyword)
-    }, 300)
-
-    return () => clearTimeout(timer)
-  }, [keyword])
-
-  // 搜索地点
-  const { data: locations = [], isLoading } = useQuery({
-    queryKey: ['locations', debouncedKeyword, areaFilter],
-    queryFn: async () => {
-      if (!debouncedKeyword.trim()) return []
-
-      const res = await locationClient.$get({
-        query: {
-          keyword: debouncedKeyword,
-          provinceId: areaFilter.provinceId,
-          cityId: areaFilter.cityId,
-          districtId: areaFilter.districtId
-        }
-      })
-      if (res.status !== 200) throw new Error('搜索地点失败')
-      return await res.json()
-    },
-    enabled: !!debouncedKeyword.trim()
-  })
-
-  // 处理输入变化
-  const handleInputChange = (e: any) => {
-    const value = e.detail.value
-    setKeyword(value)
-    if (value) {
-      setShowResults(true)
-    } else {
-      setShowResults(false)
-    }
-  }
-
-  // 选择地点
-  const handleSelectLocation = (location: Location) => {
-    setKeyword(location.name)
-    setShowResults(false)
-    if (onChange) {
-      onChange(location)
-    }
-  }
-
-  // 清除选择
-  const handleClear = () => {
-    setKeyword('')
-    setShowResults(false)
-    if (onChange) {
-      onChange(null)
-    }
-  }
-
-  // 获取显示文本
-  const getDisplayText = (location: Location) => {
-    const parts = [location.name]
-    if (location.district) parts.push(location.district)
-    if (location.city) parts.push(location.city)
-    if (location.province) parts.push(location.province)
-
-    return parts.join(' · ')
-  }
-
-  return (
-    <View className="relative">
-      {/* 搜索输入框 */}
-      <View className="relative">
-        <Input
-          type="text"
-          value={keyword}
-          placeholder={placeholder}
-          onInput={handleInputChange}
-          onFocus={() => {
-            if (keyword && locations.length > 0) {
-              setShowResults(true)
-            }
-          }}
-          className="border border-gray-300 rounded px-3 py-2 text-sm w-full"
-        />
-
-        {/* 清除按钮 */}
-        {keyword && (
-          <View
-            className="absolute right-2 top-1/2 transform -translate-y-1/2 text-gray-400 text-lg"
-            onClick={handleClear}
-          >
-            ×
-          </View>
-        )}
-      </View>
-
-      {/* 搜索结果 */}
-      {showResults && (
-        <View className="absolute top-full left-0 right-0 bg-white border border-gray-300 rounded shadow-lg z-10 max-h-60 overflow-y-auto">
-          {isLoading ? (
-            <View className="px-3 py-2 text-sm text-gray-500">搜索中...</View>
-          ) : locations.length === 0 ? (
-            <View className="px-3 py-2 text-sm text-gray-500">
-              {debouncedKeyword ? '未找到相关地点' : '请输入地点名称'}
-            </View>
-          ) : (
-            <ScrollView scrollY className="max-h-60">
-              {locations.map((location: Location) => (
-                <View
-                  key={location.id}
-                  className="px-3 py-2 border-b border-gray-100 hover:bg-gray-50 active:bg-gray-100"
-                  onClick={() => handleSelectLocation(location)}
-                >
-                  <Text className="text-sm font-medium">{location.name}</Text>
-                  {location.address && (
-                    <Text className="text-xs text-gray-500 block mt-1">
-                      {location.address}
-                    </Text>
-                  )}
-                  {(location.province || location.city || location.district) && (
-                    <Text className="text-xs text-gray-400 block">
-                      {getDisplayText(location)}
-                    </Text>
-                  )}
-                </View>
-              ))}
-            </ScrollView>
-          )}
-        </View>
-      )}
-
-      {/* 当前选择显示 */}
-      {value && !keyword && (
-        <View className="mt-2 p-2 bg-blue-50 rounded border border-blue-200">
-          <Text className="text-sm text-blue-800">已选择: {getDisplayText(value)}</Text>
-        </View>
-      )}
-    </View>
-  )
-}

+ 0 - 179
mini/src/components/RouteFilter.tsx

@@ -1,179 +0,0 @@
-import React, { useState } from 'react'
-import { View, Text } from '@tarojs/components'
-
-interface FilterOption {
-  label: string
-  value: string
-}
-
-interface RouteFilterProps {
-  routeType?: string
-  vehicleType?: string
-  sortBy?: string
-  sortOrder?: 'asc' | 'desc'
-  onRouteTypeChange?: (type: string) => void
-  onVehicleTypeChange?: (type: string) => void
-  onSortChange?: (sortBy: string, sortOrder: 'asc' | 'desc') => void
-}
-
-export const RouteFilter: React.FC<RouteFilterProps> = ({
-  routeType = 'all',
-  vehicleType = 'all',
-  sortBy = 'departureTime',
-  sortOrder = 'asc',
-  onRouteTypeChange,
-  onVehicleTypeChange,
-  onSortChange
-}) => {
-  const [showFilters, setShowFilters] = useState(false)
-
-  // 路线类型选项
-  const routeTypeOptions: FilterOption[] = [
-    { label: '全部', value: 'all' },
-    { label: '去程', value: 'departure' },
-    { label: '返程', value: 'return' }
-  ]
-
-  // 车辆类型选项
-  const vehicleTypeOptions: FilterOption[] = [
-    { label: '全部', value: 'all' },
-    { label: '大巴拼车', value: 'bus' },
-    { label: '商务拼车', value: 'business' },
-    { label: '包车', value: 'charter' }
-  ]
-
-  // 排序选项
-  const sortOptions: FilterOption[] = [
-    { label: '出发时间', value: 'departureTime' },
-    { label: '价格', value: 'price' }
-  ]
-
-  // 处理路线类型选择
-  const handleRouteTypeChange = (type: string) => {
-    if (onRouteTypeChange) {
-      onRouteTypeChange(type)
-    }
-  }
-
-  // 处理车辆类型选择
-  const handleVehicleTypeChange = (type: string) => {
-    if (onVehicleTypeChange) {
-      onVehicleTypeChange(type)
-    }
-  }
-
-  // 处理排序选择
-  const handleSortChange = (newSortBy: string) => {
-    if (onSortChange) {
-      const newSortOrder = sortBy === newSortBy && sortOrder === 'asc' ? 'desc' : 'asc'
-      onSortChange(newSortBy, newSortOrder)
-    }
-  }
-
-  // 获取排序图标
-  const getSortIcon = (optionValue: string) => {
-    if (sortBy !== optionValue) return ''
-    return sortOrder === 'asc' ? '↑' : '↓'
-  }
-
-  return (
-    <View className="bg-white border-b border-gray-200">
-      {/* 主要筛选栏 */}
-      <View className="flex justify-between items-center p-3">
-        {/* 路线类型筛选 */}
-        <View className="flex space-x-1">
-          {routeTypeOptions.map(option => (
-            <View
-              key={option.value}
-              className={`px-3 py-1 rounded-full text-sm border ${
-                routeType === option.value
-                  ? 'bg-blue-500 text-white border-blue-500'
-                  : 'bg-white text-gray-600 border-gray-300'
-              }`}
-              onClick={() => handleRouteTypeChange(option.value)}
-            >
-              <Text>{option.label}</Text>
-            </View>
-          ))}
-        </View>
-
-        {/* 筛选按钮 */}
-        <View
-          className="flex items-center space-x-1 px-3 py-1 rounded-full border border-gray-300"
-          onClick={() => setShowFilters(!showFilters)}
-        >
-          <Text className="text-sm text-gray-600">筛选</Text>
-          <Text className="text-xs text-gray-400">{showFilters ? '▲' : '▼'}</Text>
-        </View>
-      </View>
-
-      {/* 扩展筛选区域 */}
-      {showFilters && (
-        <View className="border-t border-gray-100 p-3 space-y-3">
-          {/* 车辆类型筛选 */}
-          <View>
-            <Text className="text-sm text-gray-600 mb-2 block">车辆类型</Text>
-            <View className="flex flex-wrap gap-2">
-              {vehicleTypeOptions.map(option => (
-                <View
-                  key={option.value}
-                  className={`px-3 py-1 rounded-full text-sm border ${
-                    vehicleType === option.value
-                      ? 'bg-green-500 text-white border-green-500'
-                      : 'bg-white text-gray-600 border-gray-300'
-                  }`}
-                  onClick={() => handleVehicleTypeChange(option.value)}
-                >
-                  <Text>{option.label}</Text>
-                </View>
-              ))}
-            </View>
-          </View>
-
-          {/* 排序选项 */}
-          <View>
-            <Text className="text-sm text-gray-600 mb-2 block">排序方式</Text>
-            <View className="flex space-x-2">
-              {sortOptions.map(option => (
-                <View
-                  key={option.value}
-                  className={`px-3 py-1 rounded-full text-sm border flex items-center space-x-1 ${
-                    sortBy === option.value
-                      ? 'bg-orange-500 text-white border-orange-500'
-                      : 'bg-white text-gray-600 border-gray-300'
-                  }`}
-                  onClick={() => handleSortChange(option.value)}
-                >
-                  <Text>{option.label}</Text>
-                  {sortBy === option.value && (
-                    <Text className="text-xs">{getSortIcon(option.value)}</Text>
-                  )}
-                </View>
-              ))}
-            </View>
-          </View>
-        </View>
-      )}
-
-      {/* 当前筛选状态显示 */}
-      <View className="px-3 py-2 bg-gray-50 border-t border-gray-100">
-        <View className="flex flex-wrap gap-1">
-          {routeType !== 'all' && (
-            <View className="bg-blue-100 text-blue-800 text-xs px-2 py-1 rounded-full">
-              {routeTypeOptions.find(o => o.value === routeType)?.label}
-            </View>
-          )}
-          {vehicleType !== 'all' && (
-            <View className="bg-green-100 text-green-800 text-xs px-2 py-1 rounded-full">
-              {vehicleTypeOptions.find(o => o.value === vehicleType)?.label}
-            </View>
-          )}
-          <View className="bg-orange-100 text-orange-800 text-xs px-2 py-1 rounded-full">
-            {sortOptions.find(o => o.value === sortBy)?.label}
-            {sortOrder === 'asc' ? '↑' : '↓'}
-          </View>
-        </View>
-      </View>
-    </View>
-  )
-}

+ 0 - 336
mini/tests/components/AreaCascader.test.tsx

@@ -1,336 +0,0 @@
-import React from 'react'
-import { render, screen, fireEvent, waitFor } from '@testing-library/react'
-import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
-import { AreaCascader } from '../../src/components/AreaCascader'
-
-// Mock API 客户端
-const mockAreaClient = {
-  provinces: {
-    $get: jest.fn()
-  },
-  cities: {
-    $get: jest.fn()
-  },
-  districts: {
-    $get: jest.fn()
-  }
-}
-
-jest.mock('../../src/api', () => ({
-  areaClient: mockAreaClient
-}))
-
-// 创建测试用的 QueryClient
-const createTestQueryClient = () => new QueryClient({
-  defaultOptions: {
-    queries: {
-      retry: false,
-    },
-  },
-})
-
-// 包装组件
-const Wrapper = ({ children }: { children: React.ReactNode }) => {
-  const queryClient = createTestQueryClient()
-  return (
-    <QueryClientProvider client={queryClient}>
-      {children}
-    </QueryClientProvider>
-  )
-}
-
-// 模拟数据
-const mockProvinces = {
-  success: true,
-  data: {
-    provinces: [
-      { id: 1, name: '北京市' },
-      { id: 2, name: '上海市' },
-      { id: 3, name: '广东省' }
-    ]
-  },
-  message: ''
-}
-
-const mockCities = {
-  success: true,
-  data: {
-    cities: [
-      { id: 11, name: '北京市' },
-      { id: 12, name: '朝阳区' },
-      { id: 13, name: '海淀区' }
-    ]
-  },
-  message: ''
-}
-
-const mockDistricts = {
-  success: true,
-  data: {
-    districts: [
-      { id: 101, name: '朝阳区' },
-      { id: 102, name: '海淀区' },
-      { id: 103, name: '西城区' }
-    ]
-  },
-  message: ''
-}
-
-describe('AreaCascader 组件', () => {
-  beforeEach(() => {
-    // 重置所有 mock
-    jest.clearAllMocks()
-
-    // 设置默认的 mock 返回值
-    mockAreaClient.provinces.$get.mockResolvedValue({
-      status: 200,
-      json: async () => mockProvinces
-    })
-
-    mockAreaClient.cities.$get.mockResolvedValue({
-      status: 200,
-      json: async () => mockCities
-    })
-
-    mockAreaClient.districts.$get.mockResolvedValue({
-      status: 200,
-      json: async () => mockDistricts
-    })
-  })
-
-  test('应该正确渲染组件', async () => {
-    const onChange = jest.fn()
-
-    render(
-      <Wrapper>
-        <AreaCascader onChange={onChange} />
-      </Wrapper>
-    )
-
-    // 检查选择器标签
-    expect(screen.getByText('省份')).toBeInTheDocument()
-    expect(screen.getByText('城市')).toBeInTheDocument()
-    expect(screen.getByText('区县')).toBeInTheDocument()
-
-    // 检查默认占位符
-    expect(screen.getByText('请选择省市区')).toBeInTheDocument()
-
-    // 等待数据加载
-    await waitFor(() => {
-      expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
-    })
-  })
-
-  test('应该显示自定义占位符', () => {
-    const onChange = jest.fn()
-
-    render(
-      <Wrapper>
-        <AreaCascader
-          onChange={onChange}
-          placeholder="选择出发地"
-        />
-      </Wrapper>
-    )
-
-    expect(screen.getByText('选择出发地')).toBeInTheDocument()
-  })
-
-  test('应该初始化选择值', async () => {
-    const onChange = jest.fn()
-
-    render(
-      <Wrapper>
-        <AreaCascader
-          value={[1, 11, 101]}
-          onChange={onChange}
-        />
-      </Wrapper>
-    )
-
-    // 等待数据加载
-    await waitFor(() => {
-      expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
-    })
-
-    // 检查是否调用了城市和区县查询
-    await waitFor(() => {
-      expect(mockAreaClient.cities.$get).toHaveBeenCalledWith({
-        query: { provinceId: 1 }
-      })
-    })
-
-    await waitFor(() => {
-      expect(mockAreaClient.districts.$get).toHaveBeenCalledWith({
-        query: { cityId: 11 }
-      })
-    })
-  })
-
-  test('应该处理省份选择', async () => {
-    const onChange = jest.fn()
-
-    render(
-      <Wrapper>
-        <AreaCascader onChange={onChange} />
-      </Wrapper>
-    )
-
-    // 等待数据加载
-    await waitFor(() => {
-      expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
-    })
-
-    // 模拟选择省份
-    const provincePicker = screen.getByText('请选择省份')
-    fireEvent.click(provincePicker)
-
-    // 这里我们模拟 Picker 的 onChange 事件
-    // 在实际测试中,可能需要更复杂的模拟
-  })
-
-  test('应该正确显示已选择的省市区文本', async () => {
-    const onChange = jest.fn()
-
-    render(
-      <Wrapper>
-        <AreaCascader
-          value={[1, 11, 101]}
-          onChange={onChange}
-        />
-      </Wrapper>
-    )
-
-    // 等待数据加载
-    await waitFor(() => {
-      expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
-    })
-
-    await waitFor(() => {
-      expect(mockAreaClient.cities.$get).toHaveBeenCalled()
-    })
-
-    await waitFor(() => {
-      expect(mockAreaClient.districts.$get).toHaveBeenCalled()
-    })
-
-    // 检查显示文本
-    await waitFor(() => {
-      expect(screen.getByText('北京市 北京市 朝阳区')).toBeInTheDocument()
-    })
-  })
-
-  test('应该处理部分选择的情况', async () => {
-    const onChange = jest.fn()
-
-    render(
-      <Wrapper>
-        <AreaCascader
-          value={[1, 11]} // 只选择了省份和城市
-          onChange={onChange}
-        />
-      </Wrapper>
-    )
-
-    // 等待数据加载
-    await waitFor(() => {
-      expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
-    })
-
-    await waitFor(() => {
-      expect(mockAreaClient.cities.$get).toHaveBeenCalled()
-    })
-
-    // 检查显示文本
-    await waitFor(() => {
-      expect(screen.getByText('北京市 北京市')).toBeInTheDocument()
-    })
-  })
-
-  test('应该处理只有省份选择的情况', async () => {
-    const onChange = jest.fn()
-
-    render(
-      <Wrapper>
-        <AreaCascader
-          value={[1]} // 只选择了省份
-          onChange={onChange}
-        />
-      </Wrapper>
-    )
-
-    // 等待数据加载
-    await waitFor(() => {
-      expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
-    })
-
-    // 检查显示文本
-    await waitFor(() => {
-      expect(screen.getByText('北京市')).toBeInTheDocument()
-    })
-  })
-
-  test('应该处理 API 错误', async () => {
-    // 模拟 API 错误
-    mockAreaClient.provinces.$get.mockResolvedValue({
-      status: 500,
-      json: async () => ({ success: false, message: '服务器错误' })
-    })
-
-    const onChange = jest.fn()
-
-    render(
-      <Wrapper>
-        <AreaCascader onChange={onChange} />
-      </Wrapper>
-    )
-
-    // 组件应该正常渲染,即使 API 调用失败
-    expect(screen.getByText('省份')).toBeInTheDocument()
-
-    // 等待 API 调用
-    await waitFor(() => {
-      expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
-    })
-  })
-
-  test('应该禁用城市选择器当没有选择省份时', async () => {
-    const onChange = jest.fn()
-
-    render(
-      <Wrapper>
-        <AreaCascader onChange={onChange} />
-      </Wrapper>
-    )
-
-    // 等待数据加载
-    await waitFor(() => {
-      expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
-    })
-
-    // 检查城市选择器是否被禁用
-    const cityPicker = screen.getByText('请选择城市')
-    expect(cityPicker.parentElement).toHaveClass('bg-gray-100')
-    expect(cityPicker.parentElement).toHaveClass('text-gray-400')
-  })
-
-  test('应该禁用区县选择器当没有选择城市时', async () => {
-    const onChange = jest.fn()
-
-    render(
-      <Wrapper>
-        <AreaCascader onChange={onChange} />
-      </Wrapper>
-    )
-
-    // 等待数据加载
-    await waitFor(() => {
-      expect(mockAreaClient.provinces.$get).toHaveBeenCalled()
-    })
-
-    // 检查区县选择器是否被禁用
-    const districtPicker = screen.getByText('请选择区县')
-    expect(districtPicker.parentElement).toHaveClass('bg-gray-100')
-    expect(districtPicker.parentElement).toHaveClass('text-gray-400')
-  })
-})

+ 0 - 383
mini/tests/components/LocationSearch.test.tsx

@@ -1,383 +0,0 @@
-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 (
-    <QueryClientProvider client={queryClient}>
-      {children}
-    </QueryClientProvider>
-  )
-}
-
-// 模拟数据
-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(
-      <Wrapper>
-        <LocationSearch onChange={onChange} />
-      </Wrapper>
-    )
-
-    // 检查输入框
-    expect(screen.getByPlaceholderText('搜索地点')).toBeInTheDocument()
-  })
-
-  test('应该显示自定义占位符', () => {
-    const onChange = jest.fn()
-
-    render(
-      <Wrapper>
-        <LocationSearch
-          onChange={onChange}
-          placeholder="搜索出发地"
-        />
-      </Wrapper>
-    )
-
-    expect(screen.getByPlaceholderText('搜索出发地')).toBeInTheDocument()
-  })
-
-  test('应该处理输入变化和防抖搜索', async () => {
-    const onChange = jest.fn()
-
-    render(
-      <Wrapper>
-        <LocationSearch onChange={onChange} />
-      </Wrapper>
-    )
-
-    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(
-      <Wrapper>
-        <LocationSearch onChange={onChange} />
-      </Wrapper>
-    )
-
-    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(
-      <Wrapper>
-        <LocationSearch onChange={onChange} />
-      </Wrapper>
-    )
-
-    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(
-      <Wrapper>
-        <LocationSearch onChange={onChange} />
-      </Wrapper>
-    )
-
-    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(
-      <Wrapper>
-        <LocationSearch
-          value={selectedLocation}
-          onChange={onChange}
-        />
-      </Wrapper>
-    )
-
-    // 检查已选择的地点显示
-    expect(screen.getByText('已选择: 北京南站 · 丰台区 · 北京市 · 北京市')).toBeInTheDocument()
-  })
-
-  test('应该支持地区筛选', async () => {
-    const onChange = jest.fn()
-    const areaFilter = {
-      provinceId: 1,
-      cityId: 11,
-      districtId: 101
-    }
-
-    render(
-      <Wrapper>
-        <LocationSearch
-          onChange={onChange}
-          areaFilter={areaFilter}
-        />
-      </Wrapper>
-    )
-
-    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(
-      <Wrapper>
-        <LocationSearch onChange={onChange} />
-      </Wrapper>
-    )
-
-    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(
-      <Wrapper>
-        <LocationSearch onChange={onChange} />
-      </Wrapper>
-    )
-
-    const input = screen.getByPlaceholderText('搜索地点')
-
-    // 输入搜索关键词
-    fireEvent.input(input, { target: { value: '不存在的地点' } })
-
-    // 检查无结果状态
-    await waitFor(() => {
-      expect(screen.getByText('未找到相关地点')).toBeInTheDocument()
-    })
-  })
-
-  test('应该显示请输入关键词状态', () => {
-    const onChange = jest.fn()
-
-    render(
-      <Wrapper>
-        <LocationSearch onChange={onChange} />
-      </Wrapper>
-    )
-
-    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(
-      <Wrapper>
-        <LocationSearch onChange={onChange} />
-      </Wrapper>
-    )
-
-    const input = screen.getByPlaceholderText('搜索地点')
-
-    // 输入搜索关键词
-    fireEvent.input(input, { target: { value: '北京' } })
-
-    // 组件应该正常处理错误,不显示搜索结果
-    await waitFor(() => {
-      expect(mockLocationClient.$get).toHaveBeenCalled()
-    })
-
-    // 检查没有显示错误信息(组件应该静默处理错误)
-    expect(screen.queryByText('服务器错误')).not.toBeInTheDocument()
-  })
-})

+ 0 - 318
mini/tests/components/RouteFilter.test.tsx

@@ -1,318 +0,0 @@
-import React from 'react'
-import { render, screen, fireEvent } from '@testing-library/react'
-import { RouteFilter } from '../../src/components/RouteFilter'
-
-describe('RouteFilter 组件', () => {
-  test('应该正确渲染组件', () => {
-    const onRouteTypeChange = jest.fn()
-    const onVehicleTypeChange = jest.fn()
-    const onSortChange = jest.fn()
-
-    render(
-      <RouteFilter
-        onRouteTypeChange={onRouteTypeChange}
-        onVehicleTypeChange={onVehicleTypeChange}
-        onSortChange={onSortChange}
-      />
-    )
-
-    // 检查路线类型筛选
-    expect(screen.getByText('全部')).toBeInTheDocument()
-    expect(screen.getByText('去程')).toBeInTheDocument()
-    expect(screen.getByText('返程')).toBeInTheDocument()
-
-    // 检查筛选按钮
-    expect(screen.getByText('筛选')).toBeInTheDocument()
-
-    // 检查当前筛选状态显示
-    expect(screen.getByText('出发时间↑')).toBeInTheDocument()
-  })
-
-  test('应该处理路线类型选择', () => {
-    const onRouteTypeChange = jest.fn()
-    const onVehicleTypeChange = jest.fn()
-    const onSortChange = jest.fn()
-
-    render(
-      <RouteFilter
-        onRouteTypeChange={onRouteTypeChange}
-        onVehicleTypeChange={onVehicleTypeChange}
-        onSortChange={onSortChange}
-      />
-    )
-
-    // 点击去程选项
-    const departureOption = screen.getByText('去程')
-    fireEvent.click(departureOption)
-
-    // 检查回调被调用
-    expect(onRouteTypeChange).toHaveBeenCalledWith('departure')
-    expect(onVehicleTypeChange).not.toHaveBeenCalled()
-    expect(onSortChange).not.toHaveBeenCalled()
-  })
-
-  test('应该处理车辆类型选择', () => {
-    const onRouteTypeChange = jest.fn()
-    const onVehicleTypeChange = jest.fn()
-    const onSortChange = jest.fn()
-
-    render(
-      <RouteFilter
-        onRouteTypeChange={onRouteTypeChange}
-        onVehicleTypeChange={onVehicleTypeChange}
-        onSortChange={onSortChange}
-      />
-    )
-
-    // 点击筛选按钮展开扩展筛选区域
-    const filterButton = screen.getByText('筛选')
-    fireEvent.click(filterButton)
-
-    // 点击大巴拼车选项
-    const busOption = screen.getByText('大巴拼车')
-    fireEvent.click(busOption)
-
-    // 检查回调被调用
-    expect(onVehicleTypeChange).toHaveBeenCalledWith('bus')
-    expect(onRouteTypeChange).not.toHaveBeenCalled()
-    expect(onSortChange).not.toHaveBeenCalled()
-  })
-
-  test('应该处理排序选择', () => {
-    const onRouteTypeChange = jest.fn()
-    const onVehicleTypeChange = jest.fn()
-    const onSortChange = jest.fn()
-
-    render(
-      <RouteFilter
-        onRouteTypeChange={onRouteTypeChange}
-        onVehicleTypeChange={onVehicleTypeChange}
-        onSortChange={onSortChange}
-      />
-    )
-
-    // 点击筛选按钮展开扩展筛选区域
-    const filterButton = screen.getByText('筛选')
-    fireEvent.click(filterButton)
-
-    // 点击价格排序选项
-    const priceSortOption = screen.getByText('价格')
-    fireEvent.click(priceSortOption)
-
-    // 检查回调被调用
-    expect(onSortChange).toHaveBeenCalledWith('price', 'asc')
-    expect(onRouteTypeChange).not.toHaveBeenCalled()
-    expect(onVehicleTypeChange).not.toHaveBeenCalled()
-  })
-
-  test('应该处理排序方向切换', () => {
-    const onRouteTypeChange = jest.fn()
-    const onVehicleTypeChange = jest.fn()
-    const onSortChange = jest.fn()
-
-    render(
-      <RouteFilter
-        sortBy="price"
-        sortOrder="asc"
-        onRouteTypeChange={onRouteTypeChange}
-        onVehicleTypeChange={onVehicleTypeChange}
-        onSortChange={onSortChange}
-      />
-    )
-
-    // 点击筛选按钮展开扩展筛选区域
-    const filterButton = screen.getByText('筛选')
-    fireEvent.click(filterButton)
-
-    // 再次点击价格排序选项(应该切换为降序)
-    const priceSortOption = screen.getByText('价格')
-    fireEvent.click(priceSortOption)
-
-    // 检查回调被调用,排序方向切换为降序
-    expect(onSortChange).toHaveBeenCalledWith('price', 'desc')
-  })
-
-  test('应该显示筛选状态', () => {
-    const onRouteTypeChange = jest.fn()
-    const onVehicleTypeChange = jest.fn()
-    const onSortChange = jest.fn()
-
-    render(
-      <RouteFilter
-        routeType="departure"
-        vehicleType="bus"
-        sortBy="price"
-        sortOrder="desc"
-        onRouteTypeChange={onRouteTypeChange}
-        onVehicleTypeChange={onVehicleTypeChange}
-        onSortChange={onSortChange}
-      />
-    )
-
-    // 检查筛选状态显示
-    expect(screen.getByText('去程')).toBeInTheDocument()
-    expect(screen.getByText('大巴拼车')).toBeInTheDocument()
-    expect(screen.getByText('价格↓')).toBeInTheDocument()
-  })
-
-  test('应该显示正确的排序图标', () => {
-    const onRouteTypeChange = jest.fn()
-    const onVehicleTypeChange = jest.fn()
-    const onSortChange = jest.fn()
-
-    render(
-      <RouteFilter
-        sortBy="departureTime"
-        sortOrder="asc"
-        onRouteTypeChange={onRouteTypeChange}
-        onVehicleTypeChange={onVehicleTypeChange}
-        onSortChange={onSortChange}
-      />
-    )
-
-    // 检查出发时间排序显示升序图标
-    expect(screen.getByText('出发时间↑')).toBeInTheDocument()
-  })
-
-  test('应该切换筛选区域的显示状态', () => {
-    const onRouteTypeChange = jest.fn()
-    const onVehicleTypeChange = jest.fn()
-    const onSortChange = jest.fn()
-
-    render(
-      <RouteFilter
-        onRouteTypeChange={onRouteTypeChange}
-        onVehicleTypeChange={onVehicleTypeChange}
-        onSortChange={onSortChange}
-      />
-    )
-
-    // 初始状态下扩展筛选区域应该隐藏
-    expect(screen.queryByText('车辆类型')).not.toBeInTheDocument()
-    expect(screen.queryByText('排序方式')).not.toBeInTheDocument()
-
-    // 点击筛选按钮
-    const filterButton = screen.getByText('筛选')
-    fireEvent.click(filterButton)
-
-    // 扩展筛选区域应该显示
-    expect(screen.getByText('车辆类型')).toBeInTheDocument()
-    expect(screen.getByText('排序方式')).toBeInTheDocument()
-
-    // 再次点击筛选按钮
-    fireEvent.click(filterButton)
-
-    // 扩展筛选区域应该隐藏
-    expect(screen.queryByText('车辆类型')).not.toBeInTheDocument()
-    expect(screen.queryByText('排序方式')).not.toBeInTheDocument()
-  })
-
-  test('应该显示筛选按钮的展开/收起状态', () => {
-    const onRouteTypeChange = jest.fn()
-    const onVehicleTypeChange = jest.fn()
-    const onSortChange = jest.fn()
-
-    render(
-      <RouteFilter
-        onRouteTypeChange={onRouteTypeChange}
-        onVehicleTypeChange={onVehicleTypeChange}
-        onSortChange={onSortChange}
-      />
-    )
-
-    // 初始状态下应该显示向下箭头
-    const filterButton = screen.getByText('筛选')
-    expect(filterButton.parentElement).toHaveTextContent('筛选▼')
-
-    // 点击筛选按钮
-    fireEvent.click(filterButton)
-
-    // 应该显示向上箭头
-    expect(filterButton.parentElement).toHaveTextContent('筛选▲')
-  })
-
-  test('应该正确应用选中样式', () => {
-    const onRouteTypeChange = jest.fn()
-    const onVehicleTypeChange = jest.fn()
-    const onSortChange = jest.fn()
-
-    render(
-      <RouteFilter
-        routeType="departure"
-        vehicleType="bus"
-        sortBy="price"
-        sortOrder="asc"
-        onRouteTypeChange={onRouteTypeChange}
-        onVehicleTypeChange={onVehicleTypeChange}
-        onSortChange={onSortChange}
-      />
-    )
-
-    // 展开筛选区域
-    const filterButton = screen.getByText('筛选')
-    fireEvent.click(filterButton)
-
-    // 检查去程选项的选中样式
-    const departureOption = screen.getByText('去程')
-    expect(departureOption.parentElement).toHaveClass('bg-blue-500')
-    expect(departureOption.parentElement).toHaveClass('text-white')
-
-    // 检查大巴拼车选项的选中样式
-    const busOption = screen.getByText('大巴拼车')
-    expect(busOption.parentElement).toHaveClass('bg-green-500')
-    expect(busOption.parentElement).toHaveClass('text-white')
-
-    // 检查价格排序选项的选中样式
-    const priceSortOption = screen.getByText('价格')
-    expect(priceSortOption.parentElement).toHaveClass('bg-orange-500')
-    expect(priceSortOption.parentElement).toHaveClass('text-white')
-  })
-
-  test('应该处理默认值', () => {
-    const onRouteTypeChange = jest.fn()
-    const onVehicleTypeChange = jest.fn()
-    const onSortChange = jest.fn()
-
-    render(
-      <RouteFilter
-        onRouteTypeChange={onRouteTypeChange}
-        onVehicleTypeChange={onVehicleTypeChange}
-        onSortChange={onSortChange}
-      />
-    )
-
-    // 检查默认值
-    expect(screen.getByText('全部')).toBeInTheDocument()
-    expect(screen.getByText('出发时间↑')).toBeInTheDocument()
-
-    // 展开筛选区域
-    const filterButton = screen.getByText('筛选')
-    fireEvent.click(filterButton)
-
-    // 检查车辆类型默认值
-    expect(screen.getByText('全部')).toBeInTheDocument()
-  })
-
-  test('应该不显示全部类型的筛选状态', () => {
-    const onRouteTypeChange = jest.fn()
-    const onVehicleTypeChange = jest.fn()
-    const onSortChange = jest.fn()
-
-    render(
-      <RouteFilter
-        routeType="all"
-        vehicleType="all"
-        sortBy="departureTime"
-        sortOrder="asc"
-        onRouteTypeChange={onRouteTypeChange}
-        onVehicleTypeChange={onVehicleTypeChange}
-        onSortChange={onSortChange}
-      />
-    )
-
-    // 检查筛选状态显示
-    expect(screen.queryByText('全部')).not.toBeInTheDocument() // 路线类型全部不显示
-    expect(screen.queryByText('全部')).not.toBeInTheDocument() // 车辆类型全部不显示
-    expect(screen.getByText('出发时间↑')).toBeInTheDocument() // 排序方式始终显示
-  })
-})