Просмотр исходного кода

✅ test(area-select): 为地区选择组件添加集成测试支持

- 为AreaSelect组件添加data-testid属性,支持测试ID前缀配置
- 为AreaSelectForm组件添加testIdPrefix属性,支持测试ID前缀配置
- 使用useWatch替代_getWatch监听表单值变化,提升性能
- 重构集成测试,使用data-testid定位元素,提高测试稳定性
- 新增AreaSelect组件集成测试,覆盖完整交互流程
- 优化测试交互,使用userEvent和fireEvent模拟真实用户操作
yourname 2 недель назад
Родитель
Сommit
f94569502a

+ 6 - 3
packages/area-management-ui/src/components/AreaSelect.tsx

@@ -37,6 +37,9 @@ export const AreaSelect: React.FC<AreaSelectProps> = ({
   const [selectedCity, setSelectedCity] = useState<number | undefined>(value.cityId);
   const [selectedDistrict, setSelectedDistrict] = useState<number | undefined>(value.districtId);
 
+  // 生成test ID前缀
+  const testIdPrefix = testId || 'area-select';
+
   // 查询省份列表
   const { data: provinces, isLoading: isLoadingProvinces } = useQuery({
     queryKey: ['areas', 'provinces'],
@@ -174,7 +177,7 @@ export const AreaSelect: React.FC<AreaSelectProps> = ({
             onValueChange={handleProvinceChange}
             disabled={disabled || isLoadingProvinces}
           >
-            <SelectTrigger id="province-select">
+            <SelectTrigger id="province-select" data-testid={`${testIdPrefix}-province`}>
               <SelectValue placeholder="选择省份" />
             </SelectTrigger>
             <SelectContent>
@@ -201,7 +204,7 @@ export const AreaSelect: React.FC<AreaSelectProps> = ({
             onValueChange={handleCityChange}
             disabled={disabled || !selectedProvince || isLoadingCities}
           >
-            <SelectTrigger id="city-select">
+            <SelectTrigger id="city-select" data-testid={`${testIdPrefix}-city`}>
               <SelectValue placeholder="选择城市" />
             </SelectTrigger>
             <SelectContent>
@@ -228,7 +231,7 @@ export const AreaSelect: React.FC<AreaSelectProps> = ({
             onValueChange={handleDistrictChange}
             disabled={disabled || !selectedCity || isLoadingDistricts}
           >
-            <SelectTrigger id="district-select">
+            <SelectTrigger id="district-select" data-testid={`${testIdPrefix}-district`}>
               <SelectValue placeholder="选择区县" />
             </SelectTrigger>
             <SelectContent>

+ 10 - 8
packages/area-management-ui/src/components/AreaSelectForm.tsx

@@ -1,4 +1,4 @@
-import { Controller, useFormContext, Control, FieldValues, Path } from 'react-hook-form';
+import { Controller, useFormContext, Control, FieldValues, Path, useWatch } from 'react-hook-form';
 import { useQuery } from '@tanstack/react-query';
 import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@d8d/shared-ui-components/components/ui/select';
 import { FormDescription, FormLabel } from '@d8d/shared-ui-components/components/ui/form';
@@ -15,6 +15,7 @@ interface AreaSelectFormProps<T extends FieldValues = FieldValues> {
   disabled?: boolean;
   className?: string;
   control?: Control<T>;
+  testIdPrefix?: string;
 }
 
 // 类型定义
@@ -29,7 +30,8 @@ export const AreaSelectForm = <T extends FieldValues = FieldValues>({
   required = false,
   disabled = false,
   className,
-  control: propControl
+  control: propControl,
+  testIdPrefix = 'area-select'
 }: AreaSelectFormProps<T>) => {
   // 使用传入的 control,或者从上下文中获取
   const formContext = useFormContext<T>();
@@ -40,9 +42,9 @@ export const AreaSelectForm = <T extends FieldValues = FieldValues>({
     return null;
   }
 
-  // 获取表单值
-  const provinceValue = control._getWatch(provinceName);
-  const cityValue = control._getWatch(cityName);
+  // 使用useWatch监听表单值变化
+  const provinceValue = useWatch({ control, name: provinceName });
+  const cityValue = useWatch({ control, name: cityName });
   // districtValue 变量未使用,移除
 
   // 查询省份列表
@@ -147,7 +149,7 @@ export const AreaSelectForm = <T extends FieldValues = FieldValues>({
                 }}
                 disabled={disabled || isLoadingProvinces}
               >
-                <SelectTrigger>
+                <SelectTrigger data-testid={`${testIdPrefix}-province`}>
                   <SelectValue placeholder="选择省份" />
                 </SelectTrigger>
                 <SelectContent>
@@ -185,7 +187,7 @@ export const AreaSelectForm = <T extends FieldValues = FieldValues>({
                 }}
                 disabled={disabled || !provinceValue || isLoadingCities}
               >
-                <SelectTrigger>
+                <SelectTrigger data-testid={`${testIdPrefix}-city`}>
                   <SelectValue placeholder="选择城市" />
                 </SelectTrigger>
                 <SelectContent>
@@ -220,7 +222,7 @@ export const AreaSelectForm = <T extends FieldValues = FieldValues>({
                   }}
                   disabled={disabled || !cityValue || isLoadingDistricts}
                 >
-                  <SelectTrigger>
+                  <SelectTrigger data-testid={`${testIdPrefix}-district`}>
                     <SelectValue placeholder="选择区县" />
                   </SelectTrigger>
                   <SelectContent>

+ 123 - 49
packages/area-management-ui/tests/integration/area-select-form.integration.test.tsx

@@ -1,5 +1,6 @@
 import { describe, it, expect, vi, beforeEach } from 'vitest';
 import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
 import { useForm, FormProvider } from 'react-hook-form';
 import { zodResolver } from '@hookform/resolvers/zod';
 import { z } from 'zod';
@@ -125,6 +126,7 @@ const TestForm = () => {
               label="地区选择"
               required={true}
               control={form.control}
+              testIdPrefix="test-area"
             />
             <Button type="submit" data-testid="submit-button">
               提交
@@ -181,6 +183,7 @@ describe('AreaSelectForm 集成测试', () => {
                   label="地区选择"
                   required={true}
                   control={form.control}
+                  testIdPrefix="test-area"
                 />
                 <Button type="submit" data-testid="submit-button">
                   提交
@@ -219,31 +222,52 @@ describe('AreaSelectForm 集成测试', () => {
 
     // 等待省份数据加载
     await waitFor(() => {
-      expect(screen.getByText('选择省份')).toBeInTheDocument();
+      expect(screen.getByTestId('test-area-province')).toBeInTheDocument();
+    });
+
+    // 使用fireEvent点击省份选择
+    const provinceSelect = screen.getByTestId('test-area-province');
+    await act(async () => {
+      fireEvent.click(provinceSelect);
+    });
+
+    // 等待选项出现
+    await waitFor(() => {
+      expect(screen.getByText('北京市')).toBeInTheDocument();
+    });
+
+    // 选择北京市
+    await act(async () => {
+      fireEvent.click(screen.getByText('北京市'));
     });
 
-    // 找到隐藏的省份 select 元素并设置值
-    const hiddenProvinceSelects = document.querySelectorAll('select[aria-hidden="true"]');
-    expect(hiddenProvinceSelects.length).toBeGreaterThan(0);
-    const provinceSelect = hiddenProvinceSelects[0];
-    fireEvent.change(provinceSelect, { target: { value: '1' } });
+    // 等待城市选择启用 - 检查disabled属性是否移除
+    await waitFor(() => {
+      const citySelect = screen.getByTestId('test-area-city');
+      expect(citySelect).not.toHaveAttribute('disabled');
+    }, { timeout: 2000 });
+
+    // 点击城市选择
+    const citySelect = screen.getByTestId('test-area-city');
+    await act(async () => {
+      fireEvent.click(citySelect);
+    });
 
-    // 等待城市选择可用
+    // 等待城市选项出现
     await waitFor(() => {
-      // 城市选择应该不再被禁用
-      const cityTriggers = screen.getAllByText('选择城市');
-      expect(cityTriggers.length).toBeGreaterThan(0);
+      expect(screen.getByText('北京市')).toBeInTheDocument();
     });
 
-    // 找到隐藏的城市 select 元素并设置值
-    const hiddenCitySelects = document.querySelectorAll('select[aria-hidden="true"]');
-    expect(hiddenCitySelects.length).toBeGreaterThan(1);
-    const citySelect = hiddenCitySelects[1];
-    fireEvent.change(citySelect, { target: { value: '3' } });
+    // 选择北京市(城市)
+    await act(async () => {
+      fireEvent.click(screen.getByText('北京市'));
+    });
 
     // 提交表单
     const submitButton = screen.getByTestId('submit-button');
-    fireEvent.click(submitButton);
+    await act(async () => {
+      fireEvent.click(submitButton);
+    });
 
     // 不应该有验证错误
     await waitFor(() => {
@@ -257,21 +281,36 @@ describe('AreaSelectForm 集成测试', () => {
 
     // 等待省份数据加载
     await waitFor(() => {
-      expect(screen.getByText('选择省份')).toBeInTheDocument();
+      expect(screen.getByTestId('test-area-province')).toBeInTheDocument();
     });
 
-    // 找到隐藏的省份 select 元素并设置值
-    const hiddenProvinceSelects = document.querySelectorAll('select[aria-hidden="true"]');
-    expect(hiddenProvinceSelects.length).toBeGreaterThan(0);
-    const provinceSelect = hiddenProvinceSelects[0];
-    fireEvent.change(provinceSelect, { target: { value: '1' } });
+    // 点击省份选择
+    const provinceSelect = screen.getByTestId('test-area-province');
+    await act(async () => {
+      fireEvent.click(provinceSelect);
+    });
 
-    // 等待一下,让表单状态更新
-    await new Promise(resolve => setTimeout(resolve, 100));
+    // 等待选项出现
+    await waitFor(() => {
+      expect(screen.getByText('北京市')).toBeInTheDocument();
+    });
+
+    // 选择北京市
+    await act(async () => {
+      fireEvent.click(screen.getByText('北京市'));
+    });
+
+    // 等待城市选择启用
+    await waitFor(() => {
+      const citySelect = screen.getByTestId('test-area-city');
+      expect(citySelect).not.toHaveAttribute('disabled');
+    }, { timeout: 2000 });
 
     // 提交表单(不选择城市)
     const submitButton = screen.getByTestId('submit-button');
-    fireEvent.click(submitButton);
+    await act(async () => {
+      fireEvent.click(submitButton);
+    });
 
     // 应该只有城市验证错误
     await waitFor(() => {
@@ -294,7 +333,9 @@ describe('AreaSelectForm 集成测试', () => {
 
     // 提交表单(不选择任何省份和城市)
     const submitButton = screen.getByTestId('submit-button');
-    fireEvent.click(submitButton);
+    await act(async () => {
+      fireEvent.click(submitButton);
+    });
 
     // 应该显示省份和城市的验证错误
     await waitFor(() => {
@@ -308,42 +349,74 @@ describe('AreaSelectForm 集成测试', () => {
 
     // 等待省份数据加载
     await waitFor(() => {
-      expect(screen.getByText('选择省份')).toBeInTheDocument();
+      expect(screen.getByTestId('test-area-province')).toBeInTheDocument();
     });
 
-    // 选择省份:北京市
-    const hiddenProvinceSelects = document.querySelectorAll('select[aria-hidden="true"]');
-    expect(hiddenProvinceSelects.length).toBeGreaterThan(0);
-    const provinceSelect = hiddenProvinceSelects[0];
-    fireEvent.change(provinceSelect, { target: { value: '1' } });
+    // 点击省份选择
+    const provinceSelect = screen.getByTestId('test-area-province');
+    await act(async () => {
+      fireEvent.click(provinceSelect);
+    });
 
-    // 等待城市选择可用
+    // 等待选项出现
     await waitFor(() => {
-      const cityTriggers = screen.getAllByText('选择城市');
-      expect(cityTriggers.length).toBeGreaterThan(0);
+      expect(screen.getByText('北京市')).toBeInTheDocument();
     });
 
-    // 选择城市:北京市
-    const hiddenCitySelects = document.querySelectorAll('select[aria-hidden="true"]');
-    expect(hiddenCitySelects.length).toBeGreaterThan(1);
-    const citySelect = hiddenCitySelects[1];
-    fireEvent.change(citySelect, { target: { value: '3' } });
+    // 选择北京市
+    await act(async () => {
+      fireEvent.click(screen.getByText('北京市'));
+    });
 
-    // 等待区县选择可
+    // 等待城市选择启
     await waitFor(() => {
-      const districtTriggers = screen.getAllByText('选择区县');
-      expect(districtTriggers.length).toBeGreaterThan(0);
+      const citySelect = screen.getByTestId('test-area-city');
+      expect(citySelect).not.toHaveAttribute('disabled');
+    }, { timeout: 2000 });
+
+    // 点击城市选择
+    const citySelect = screen.getByTestId('test-area-city');
+    await act(async () => {
+      fireEvent.click(citySelect);
     });
 
-    // 选择区县:东城区(可选)
-    const hiddenDistrictSelects = document.querySelectorAll('select[aria-hidden="true"]');
-    expect(hiddenDistrictSelects.length).toBeGreaterThan(2);
-    const districtSelect = hiddenDistrictSelects[2];
-    fireEvent.change(districtSelect, { target: { value: '5' } });
+    // 等待城市选项出现
+    await waitFor(() => {
+      expect(screen.getByText('北京市')).toBeInTheDocument();
+    });
+
+    // 选择北京市(城市)
+    await act(async () => {
+      fireEvent.click(screen.getByText('北京市'));
+    });
+
+    // 等待区县选择启用
+    await waitFor(() => {
+      const districtSelect = screen.getByTestId('test-area-district');
+      expect(districtSelect).not.toHaveAttribute('disabled');
+    }, { timeout: 2000 });
+
+    // 点击区县选择
+    const districtSelect = screen.getByTestId('test-area-district');
+    await act(async () => {
+      fireEvent.click(districtSelect);
+    });
+
+    // 等待区县选项出现
+    await waitFor(() => {
+      expect(screen.getByText('东城区')).toBeInTheDocument();
+    });
+
+    // 选择东城区
+    await act(async () => {
+      fireEvent.click(screen.getByText('东城区'));
+    });
 
     // 提交表单
     const submitButton = screen.getByTestId('submit-button');
-    fireEvent.click(submitButton);
+    await act(async () => {
+      fireEvent.click(submitButton);
+    });
 
     // 不应该有验证错误
     await waitFor(() => {
@@ -375,6 +448,7 @@ describe('AreaSelectForm 集成测试', () => {
                 required={true}
                 disabled={true}
                 control={form.control}
+                testIdPrefix="test-area"
               />
             </Form>
           </FormProvider>

+ 403 - 0
packages/area-management-ui/tests/integration/area-select.integration.test.tsx

@@ -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();
+  });
+});