|
|
@@ -0,0 +1,403 @@
|
|
|
+import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
|
+import userEvent from '@testing-library/user-event';
|
|
|
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
|
+import { AreaSelect } from '../../src/components/AreaSelect';
|
|
|
+
|
|
|
+// Mock API 调用
|
|
|
+vi.mock('../../src/api/areaClient', () => ({
|
|
|
+ areaClientManager: {
|
|
|
+ get: vi.fn(() => ({
|
|
|
+ index: {
|
|
|
+ $get: vi.fn(async ({ query }) => {
|
|
|
+ const filters = JSON.parse(query.filters);
|
|
|
+
|
|
|
+ if (filters.level === 1) {
|
|
|
+ // 省份数据
|
|
|
+ return {
|
|
|
+ status: 200,
|
|
|
+ json: async () => ({
|
|
|
+ data: [
|
|
|
+ { id: 1, name: '北京市', level: 1, parentId: null },
|
|
|
+ { id: 2, name: '上海市', level: 1, parentId: null }
|
|
|
+ ]
|
|
|
+ })
|
|
|
+ };
|
|
|
+ } else if (filters.level === 2 && filters.parentId === 1) {
|
|
|
+ // 北京市的城市数据
|
|
|
+ return {
|
|
|
+ status: 200,
|
|
|
+ json: async () => ({
|
|
|
+ data: [
|
|
|
+ { id: 3, name: '北京市', level: 2, parentId: 1 }
|
|
|
+ ]
|
|
|
+ })
|
|
|
+ };
|
|
|
+ } else if (filters.level === 2 && filters.parentId === 2) {
|
|
|
+ // 上海市的城市数据
|
|
|
+ return {
|
|
|
+ status: 200,
|
|
|
+ json: async () => ({
|
|
|
+ data: [
|
|
|
+ { id: 4, name: '上海市', level: 2, parentId: 2 }
|
|
|
+ ]
|
|
|
+ })
|
|
|
+ };
|
|
|
+ } else if (filters.level === 3 && filters.parentId === 3) {
|
|
|
+ // 北京市的区县数据
|
|
|
+ return {
|
|
|
+ status: 200,
|
|
|
+ json: async () => ({
|
|
|
+ data: [
|
|
|
+ { id: 5, name: '东城区', level: 3, parentId: 3 },
|
|
|
+ { id: 6, name: '西城区', level: 3, parentId: 3 }
|
|
|
+ ]
|
|
|
+ })
|
|
|
+ };
|
|
|
+ } else if (filters.level === 3 && filters.parentId === 4) {
|
|
|
+ // 上海市的区县数据
|
|
|
+ return {
|
|
|
+ status: 200,
|
|
|
+ json: async () => ({
|
|
|
+ data: [
|
|
|
+ { id: 7, name: '黄浦区', level: 3, parentId: 4 },
|
|
|
+ { id: 8, name: '徐汇区', level: 3, parentId: 4 }
|
|
|
+ ]
|
|
|
+ })
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ return {
|
|
|
+ status: 200,
|
|
|
+ json: async () => ({ data: [] })
|
|
|
+ };
|
|
|
+ })
|
|
|
+ }
|
|
|
+ }))
|
|
|
+ }
|
|
|
+}));
|
|
|
+
|
|
|
+// 创建测试用的 QueryClient
|
|
|
+const createTestQueryClient = () => new QueryClient({
|
|
|
+ defaultOptions: {
|
|
|
+ queries: {
|
|
|
+ retry: false,
|
|
|
+ },
|
|
|
+ },
|
|
|
+});
|
|
|
+
|
|
|
+// 测试组件包装器
|
|
|
+const TestWrapper = ({ children }: { children: React.ReactNode }) => {
|
|
|
+ return (
|
|
|
+ <QueryClientProvider client={createTestQueryClient()}>
|
|
|
+ {children}
|
|
|
+ </QueryClientProvider>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+describe('AreaSelect 集成测试', () => {
|
|
|
+ beforeEach(() => {
|
|
|
+ vi.clearAllMocks();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该正确渲染 AreaSelect 组件', async () => {
|
|
|
+ render(
|
|
|
+ <TestWrapper>
|
|
|
+ <AreaSelect />
|
|
|
+ </TestWrapper>
|
|
|
+ );
|
|
|
+
|
|
|
+ // 等待省份数据加载 - AreaSelect 显示的是"选择所在省份"
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('选择所在省份')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ expect(screen.getByText('省份')).toBeInTheDocument();
|
|
|
+ expect(screen.getByText('城市')).toBeInTheDocument();
|
|
|
+ expect(screen.getByText('区县')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该显示必填标记当 required=true 时', async () => {
|
|
|
+ render(
|
|
|
+ <TestWrapper>
|
|
|
+ <AreaSelect required={true} />
|
|
|
+ </TestWrapper>
|
|
|
+ );
|
|
|
+
|
|
|
+ // 等待省份数据加载 - AreaSelect 显示的是"选择所在省份"
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('选择所在省份')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 检查省份标签是否包含星号
|
|
|
+ const provinceLabel = screen.getByText('省份');
|
|
|
+ expect(provinceLabel.innerHTML).toContain('*');
|
|
|
+
|
|
|
+ // 城市和区县不应该显示星号(初始状态)
|
|
|
+ const cityLabel = screen.getByText('城市');
|
|
|
+ expect(cityLabel.innerHTML).not.toContain('*');
|
|
|
+
|
|
|
+ const districtLabel = screen.getByText('区县');
|
|
|
+ expect(districtLabel.innerHTML).not.toContain('*');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该正确处理省份选择并加载城市数据', async () => {
|
|
|
+ const user = userEvent.setup();
|
|
|
+ const handleChange = vi.fn();
|
|
|
+
|
|
|
+ render(
|
|
|
+ <TestWrapper>
|
|
|
+ <AreaSelect onChange={handleChange} />
|
|
|
+ </TestWrapper>
|
|
|
+ );
|
|
|
+
|
|
|
+ // 等待省份数据加载
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('选择所在省份')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 使用test ID查找省份选择框
|
|
|
+ const provinceSelect = screen.getByTestId('area-select-province');
|
|
|
+ await user.click(provinceSelect);
|
|
|
+
|
|
|
+ // 等待下拉菜单出现并选择北京市
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('北京市')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+ await user.click(screen.getByText('北京市'));
|
|
|
+
|
|
|
+ // 验证onChange被调用
|
|
|
+ expect(handleChange).toHaveBeenCalledWith({
|
|
|
+ provinceId: 1,
|
|
|
+ cityId: undefined,
|
|
|
+ districtId: undefined
|
|
|
+ });
|
|
|
+
|
|
|
+ // 验证城市选择框应该被启用(因为选择了省份)
|
|
|
+ const citySelect = screen.getByTestId('area-select-city');
|
|
|
+ expect(citySelect).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该正确处理城市选择并加载区县数据', async () => {
|
|
|
+ const user = userEvent.setup();
|
|
|
+ const handleChange = vi.fn();
|
|
|
+
|
|
|
+ render(
|
|
|
+ <TestWrapper>
|
|
|
+ <AreaSelect onChange={handleChange} />
|
|
|
+ </TestWrapper>
|
|
|
+ );
|
|
|
+
|
|
|
+ // 等待省份数据加载
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('选择所在省份')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 先选择省份(北京市)
|
|
|
+ const provinceSelect = screen.getByTestId('area-select-province');
|
|
|
+ await user.click(provinceSelect);
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('北京市')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+ await user.click(screen.getByText('北京市'));
|
|
|
+
|
|
|
+ // 等待城市数据加载
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('选择所在城市')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 点击城市选择框
|
|
|
+ const citySelect = screen.getByTestId('area-select-city');
|
|
|
+ await user.click(citySelect);
|
|
|
+
|
|
|
+ // 选择北京市(城市)
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('北京市')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+ await user.click(screen.getByText('北京市'));
|
|
|
+
|
|
|
+ // 验证onChange被调用
|
|
|
+ expect(handleChange).toHaveBeenCalledWith({
|
|
|
+ provinceId: 1,
|
|
|
+ cityId: 3,
|
|
|
+ districtId: undefined
|
|
|
+ });
|
|
|
+
|
|
|
+ // 验证区县选择框应该被启用(因为选择了城市)
|
|
|
+ const districtSelect = screen.getByTestId('area-select-district');
|
|
|
+ expect(districtSelect).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该正确处理区县选择', async () => {
|
|
|
+ const user = userEvent.setup();
|
|
|
+ const handleChange = vi.fn();
|
|
|
+
|
|
|
+ render(
|
|
|
+ <TestWrapper>
|
|
|
+ <AreaSelect onChange={handleChange} />
|
|
|
+ </TestWrapper>
|
|
|
+ );
|
|
|
+
|
|
|
+ // 等待省份数据加载
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('选择所在省份')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 先选择省份(北京市)
|
|
|
+ const provinceSelect = screen.getByTestId('area-select-province');
|
|
|
+ await user.click(provinceSelect);
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('北京市')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+ await user.click(screen.getByText('北京市'));
|
|
|
+
|
|
|
+ // 等待城市数据加载
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('选择所在城市')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 选择城市(北京市)
|
|
|
+ const citySelect = screen.getByTestId('area-select-city');
|
|
|
+ await user.click(citySelect);
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('北京市')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+ await user.click(screen.getByText('北京市'));
|
|
|
+
|
|
|
+ // 等待区县数据加载
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('选择所在区县')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 点击区县选择框
|
|
|
+ const districtSelect = screen.getByTestId('area-select-district');
|
|
|
+ await user.click(districtSelect);
|
|
|
+
|
|
|
+ // 选择东城区
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('东城区')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+ await user.click(screen.getByText('东城区'));
|
|
|
+
|
|
|
+ // 验证onChange被调用
|
|
|
+ expect(handleChange).toHaveBeenCalledWith({
|
|
|
+ provinceId: 1,
|
|
|
+ cityId: 3,
|
|
|
+ districtId: 5
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该支持禁用状态', async () => {
|
|
|
+ render(
|
|
|
+ <TestWrapper>
|
|
|
+ <AreaSelect disabled={true} />
|
|
|
+ </TestWrapper>
|
|
|
+ );
|
|
|
+
|
|
|
+ // 等待省份数据加载 - AreaSelect 显示的是"选择所在省份"
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('选择所在省份')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 检查组件渲染正常
|
|
|
+ expect(screen.getByText('省份')).toBeInTheDocument();
|
|
|
+ expect(screen.getByText('城市')).toBeInTheDocument();
|
|
|
+ expect(screen.getByText('区县')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该正确处理初始值', async () => {
|
|
|
+ const initialValue = {
|
|
|
+ provinceId: 1,
|
|
|
+ cityId: 3,
|
|
|
+ districtId: 5
|
|
|
+ };
|
|
|
+
|
|
|
+ render(
|
|
|
+ <TestWrapper>
|
|
|
+ <AreaSelect value={initialValue} />
|
|
|
+ </TestWrapper>
|
|
|
+ );
|
|
|
+
|
|
|
+ // 等待省份数据加载 - AreaSelect 显示的是"选择所在省份"
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('选择所在省份')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 组件应该能正常渲染,初始值会在组件内部处理
|
|
|
+ expect(screen.getByText('省份')).toBeInTheDocument();
|
|
|
+ expect(screen.getByText('城市')).toBeInTheDocument();
|
|
|
+ expect(screen.getByText('区县')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('当选择新省份时应该清空城市和区县选择', async () => {
|
|
|
+ const user = userEvent.setup();
|
|
|
+ const handleChange = vi.fn();
|
|
|
+
|
|
|
+ render(
|
|
|
+ <TestWrapper>
|
|
|
+ <AreaSelect onChange={handleChange} />
|
|
|
+ </TestWrapper>
|
|
|
+ );
|
|
|
+
|
|
|
+ // 等待省份数据加载
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('选择所在省份')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 先选择省份(北京市)
|
|
|
+ const provinceSelect = screen.getByTestId('area-select-province');
|
|
|
+ await user.click(provinceSelect);
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('北京市')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+ await user.click(screen.getByText('北京市'));
|
|
|
+
|
|
|
+ // 等待城市数据加载并选择城市
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('选择所在城市')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+ const citySelect = screen.getByTestId('area-select-city');
|
|
|
+ await user.click(citySelect);
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('北京市')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+ await user.click(screen.getByText('北京市'));
|
|
|
+
|
|
|
+ // 等待区县数据加载并选择区县
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('选择所在区县')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+ const districtSelect = screen.getByTestId('area-select-district');
|
|
|
+ await user.click(districtSelect);
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('东城区')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+ await user.click(screen.getByText('东城区'));
|
|
|
+
|
|
|
+ // 验证已经选择了完整的省市区
|
|
|
+ expect(handleChange).toHaveBeenLastCalledWith({
|
|
|
+ provinceId: 1,
|
|
|
+ cityId: 3,
|
|
|
+ districtId: 5
|
|
|
+ });
|
|
|
+
|
|
|
+ // 现在选择新的省份(上海市)
|
|
|
+ await user.click(screen.getByTestId('area-select-province')); // 重新打开省份选择
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('上海市')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+ await user.click(screen.getByText('上海市'));
|
|
|
+
|
|
|
+ // 验证onChange被调用,城市和区县被清空
|
|
|
+ expect(handleChange).toHaveBeenLastCalledWith({
|
|
|
+ provinceId: 2,
|
|
|
+ cityId: undefined,
|
|
|
+ districtId: undefined
|
|
|
+ });
|
|
|
+
|
|
|
+ // 验证城市选择框应该显示"选择城市"(被清空)
|
|
|
+ expect(screen.getByTestId('area-select-city')).toBeInTheDocument();
|
|
|
+ // 验证区县选择框应该显示"选择区县"(被清空)
|
|
|
+ expect(screen.getByTestId('area-select-district')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+});
|