|
|
@@ -0,0 +1,210 @@
|
|
|
+import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
|
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
|
|
|
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
|
+import { FormProvider, useForm } from 'react-hook-form';
|
|
|
+import { AreaSelect } from '../../src/components/AreaSelect';
|
|
|
+
|
|
|
+// Mock API客户端
|
|
|
+vi.mock('../../src/api/areaClient', () => ({
|
|
|
+ areaClientManager: {
|
|
|
+ get: vi.fn(() => ({
|
|
|
+ index: {
|
|
|
+ $get: vi.fn(() => Promise.resolve({
|
|
|
+ status: 200,
|
|
|
+ json: () => Promise.resolve({
|
|
|
+ data: [
|
|
|
+ { id: 1, name: '北京市', level: 1 },
|
|
|
+ { id: 2, name: '上海市', level: 1 }
|
|
|
+ ]
|
|
|
+ })
|
|
|
+ }))
|
|
|
+ }
|
|
|
+ }))
|
|
|
+ }
|
|
|
+}));
|
|
|
+
|
|
|
+// 测试组件包装器
|
|
|
+const TestWrapper = ({ children }: { children: React.ReactNode }) => {
|
|
|
+ const queryClient = new QueryClient({
|
|
|
+ defaultOptions: {
|
|
|
+ queries: {
|
|
|
+ retry: false,
|
|
|
+ },
|
|
|
+ },
|
|
|
+ });
|
|
|
+
|
|
|
+ // 创建一个简单的form context
|
|
|
+ const methods = useForm();
|
|
|
+
|
|
|
+ return (
|
|
|
+ <QueryClientProvider client={queryClient}>
|
|
|
+ <FormProvider {...methods}>
|
|
|
+ {children}
|
|
|
+ </FormProvider>
|
|
|
+ </QueryClientProvider>
|
|
|
+ );
|
|
|
+};
|
|
|
+
|
|
|
+describe('AreaSelect 组件测试', () => {
|
|
|
+ beforeEach(() => {
|
|
|
+ vi.clearAllMocks();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该正确渲染AreaSelect组件', () => {
|
|
|
+ render(
|
|
|
+ <TestWrapper>
|
|
|
+ <AreaSelect />
|
|
|
+ </TestWrapper>
|
|
|
+ );
|
|
|
+
|
|
|
+ expect(screen.getByText('选择省份')).toBeInTheDocument();
|
|
|
+ expect(screen.getByText('选择城市')).toBeInTheDocument();
|
|
|
+ expect(screen.getByText('选择区县')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('当required=true时,省份和城市应该显示必填标记,区县不应该显示必填标记', () => {
|
|
|
+ render(
|
|
|
+ <TestWrapper>
|
|
|
+ <AreaSelect required={true} />
|
|
|
+ </TestWrapper>
|
|
|
+ );
|
|
|
+
|
|
|
+ // 检查省份标签是否包含星号
|
|
|
+ const provinceLabel = screen.getByText('省份');
|
|
|
+ expect(provinceLabel.parentElement).toContainHTML('*');
|
|
|
+
|
|
|
+ // 检查城市标签是否包含星号
|
|
|
+ const cityLabel = screen.getByText('城市');
|
|
|
+ expect(cityLabel.parentElement).toContainHTML('*');
|
|
|
+
|
|
|
+ // 检查区县标签不应该包含星号
|
|
|
+ const districtLabel = screen.getByText('区县');
|
|
|
+ expect(districtLabel.parentElement).not.toContainHTML('*');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('当required=false时,所有字段都不应该显示必填标记', () => {
|
|
|
+ render(
|
|
|
+ <TestWrapper>
|
|
|
+ <AreaSelect required={false} />
|
|
|
+ </TestWrapper>
|
|
|
+ );
|
|
|
+
|
|
|
+ const provinceLabel = screen.getByText('省份');
|
|
|
+ expect(provinceLabel.parentElement).not.toContainHTML('*');
|
|
|
+
|
|
|
+ const cityLabel = screen.getByText('城市');
|
|
|
+ expect(cityLabel.parentElement).not.toContainHTML('*');
|
|
|
+
|
|
|
+ const districtLabel = screen.getByText('区县');
|
|
|
+ expect(districtLabel.parentElement).not.toContainHTML('*');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('当选择了城市时,区县字段不应该显示必填标记(即使required=true)', async () => {
|
|
|
+ // Mock省份选择后的城市查询
|
|
|
+ const mockCityQuery = vi.fn(() => Promise.resolve({
|
|
|
+ status: 200,
|
|
|
+ json: () => Promise.resolve({
|
|
|
+ data: [
|
|
|
+ { id: 3, name: '北京市', level: 2 },
|
|
|
+ { id: 4, name: '上海市', level: 2 }
|
|
|
+ ]
|
|
|
+ })
|
|
|
+ }));
|
|
|
+
|
|
|
+ vi.mocked(require('../../src/api/areaClient').areaClientManager.get().index.$get)
|
|
|
+ .mockImplementationOnce(() => Promise.resolve({
|
|
|
+ status: 200,
|
|
|
+ json: () => Promise.resolve({
|
|
|
+ data: [
|
|
|
+ { id: 1, name: '北京市', level: 1 },
|
|
|
+ { id: 2, name: '上海市', level: 1 }
|
|
|
+ ]
|
|
|
+ })
|
|
|
+ }))
|
|
|
+ .mockImplementationOnce(mockCityQuery);
|
|
|
+
|
|
|
+ render(
|
|
|
+ <TestWrapper>
|
|
|
+ <AreaSelect required={true} />
|
|
|
+ </TestWrapper>
|
|
|
+ );
|
|
|
+
|
|
|
+ // 等待省份数据加载
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('北京市')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 选择省份
|
|
|
+ const provinceSelect = screen.getByRole('combobox', { name: /选择省份/i });
|
|
|
+ fireEvent.click(provinceSelect);
|
|
|
+ const beijingOption = screen.getByText('北京市');
|
|
|
+ fireEvent.click(beijingOption);
|
|
|
+
|
|
|
+ // 等待城市数据加载
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(mockCityQuery).toHaveBeenCalled();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 检查区县标签不应该包含星号
|
|
|
+ const districtLabel = screen.getByText('区县');
|
|
|
+ expect(districtLabel.parentElement).not.toContainHTML('*');
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该正确处理onChange回调', async () => {
|
|
|
+ const handleChange = vi.fn();
|
|
|
+
|
|
|
+ render(
|
|
|
+ <TestWrapper>
|
|
|
+ <AreaSelect onChange={handleChange} />
|
|
|
+ </TestWrapper>
|
|
|
+ );
|
|
|
+
|
|
|
+ // 等待省份数据加载
|
|
|
+ await waitFor(() => {
|
|
|
+ expect(screen.getByText('北京市')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+
|
|
|
+ // 选择省份
|
|
|
+ const provinceSelect = screen.getByRole('combobox', { name: /选择省份/i });
|
|
|
+ fireEvent.click(provinceSelect);
|
|
|
+ const beijingOption = screen.getByText('北京市');
|
|
|
+ fireEvent.click(beijingOption);
|
|
|
+
|
|
|
+ // 验证onChange被调用
|
|
|
+ expect(handleChange).toHaveBeenCalledWith({
|
|
|
+ provinceId: 1,
|
|
|
+ cityId: undefined,
|
|
|
+ districtId: undefined
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该支持禁用状态', () => {
|
|
|
+ render(
|
|
|
+ <TestWrapper>
|
|
|
+ <AreaSelect disabled={true} />
|
|
|
+ </TestWrapper>
|
|
|
+ );
|
|
|
+
|
|
|
+ const provinceSelect = screen.getByRole('combobox', { name: /选择省份/i });
|
|
|
+ expect(provinceSelect).toBeDisabled();
|
|
|
+ });
|
|
|
+
|
|
|
+ it('应该正确处理初始值', () => {
|
|
|
+ const initialValue = {
|
|
|
+ provinceId: 1,
|
|
|
+ cityId: 3,
|
|
|
+ districtId: 5
|
|
|
+ };
|
|
|
+
|
|
|
+ render(
|
|
|
+ <TestWrapper>
|
|
|
+ <AreaSelect value={initialValue} />
|
|
|
+ </TestWrapper>
|
|
|
+ );
|
|
|
+
|
|
|
+ // 注意:由于组件内部使用useState和useEffect同步值,且API查询是异步的
|
|
|
+ // 这里我们主要验证组件能接收并处理初始值
|
|
|
+ // 实际显示值会在API数据加载后更新
|
|
|
+ expect(screen.getByText('选择省份')).toBeInTheDocument();
|
|
|
+ });
|
|
|
+});
|