---
title: 测试文档
order: 4
category: pro
---

# PisellFilter 测试文档

## 测试策略

PisellFilter 组件的测试应覆盖以下几个方面：

1. **基础渲染**: 组件能否正常渲染
2. **属性传递**: 属性是否正确传递给子组件
3. **表单集成**: Form 集成是否正常工作
4. **状态管理**: 内部状态和外部状态同步
5. **交互行为**: 筛选项的交互是否符合预期
6. **边界情况**: 异常输入的处理

## 测试环境配置

### 依赖安装

```bash
pnpm add -D @testing-library/react @testing-library/jest-dom @testing-library/user-event
```

### 测试文件结构

```
pisellFilter/
├── index.tsx
├── type.ts
└── __tests__/
    ├── PisellFilter.test.tsx       # 基础测试
    ├── PisellFilter.state.test.tsx  # 状态管理测试
    └── PisellFilter.integration.test.tsx  # 集成测试
```

## 测试用例

### 1. 基础渲染测试

#### 1.1 默认渲染

**测试目标**: 验证组件能否正常渲染

```
import { render } from '@testing-library/react';
import { PisellFilter } from '@pisell/materials';

describe('PisellFilter 基础渲染', () => {
  test('应该正常渲染', () => {
    const { container } = render(
      <PisellFilter
        quickFilterMaxLength={5}
        formFiltersPrefix="filter_"
      />
    );

    expect(container.firstChild).toBeInTheDocument();
    expect(container.querySelector('.ant-form')).toBeInTheDocument();
  });

  test('应该渲染为大尺寸表单', () => {
    const { container } = render(
      <PisellFilter
        quickFilterMaxLength={5}
        formFiltersPrefix="filter_"
      />
    );

    const form = container.querySelector('.ant-form');
    expect(form).toHaveClass('ant-form-large');
  });

  test('应该使用垂直布局', () => {
    const { container } = render(
      <PisellFilter
        quickFilterMaxLength={5}
        formFiltersPrefix="filter_"
      />
    );

    const form = container.querySelector('.ant-form');
    expect(form).toHaveClass('ant-form-vertical');
  });
});
```

#### 1.2 空值渲染

**测试目标**: 验证空值时的渲染

```
test('应该能处理空筛选值', () => {
  const { container } = render(
    <PisellFilter
      value={{
        quickFilter: [],
        otherFilter: [],
      }}
      quickFilterMaxLength={5}
      formFiltersPrefix="filter_"
    />
  );

  expect(container.firstChild).toBeInTheDocument();
});

test('应该能处理 undefined 值', () => {
  const { container } = render(
    <PisellFilter
      value={undefined}
      quickFilterMaxLength={5}
      formFiltersPrefix="filter_"
    />
  );

  expect(container.firstChild).toBeInTheDocument();
});
```

### 2. 属性传递测试

#### 2.1 筛选值传递

**测试目标**: 验证筛选值是否正确传递

```
test('应该正确传递快速筛选值', () => {
  const filterValue = {
    quickFilter: [
      {
        type: 'single' as const,
        key: 'status',
        label: '状态',
        name: 'status',
        props: {},
        other: {},
      },
    ],
    otherFilter: [],
  };

  const { getByText } = render(
    <PisellFilter
      value={filterValue}
      quickFilterMaxLength={5}
      formFiltersPrefix="filter_"
      hiddenQuickFilter={false}
    />
  );

  // 验证快速筛选项是否渲染
  // 注意：实际测试需要根据 FilterButton 的实现来验证
});
```

#### 2.2 显示控制传递

**测试目标**: 验证显示控制属性

```
test('应该能隐藏快速筛选', () => {
  const { container } = render(
    <PisellFilter
      hiddenQuickFilter={true}
      hiddenOtherFilter={false}
      quickFilterMaxLength={5}
      formFiltersPrefix="filter_"
    />
  );

  // 验证快速筛选是否隐藏
  // 实际验证逻辑取决于 FilterButton 的实现
});

test('应该能隐藏高级筛选', () => {
  const { container } = render(
    <PisellFilter
      hiddenQuickFilter={false}
      hiddenOtherFilter={true}
      quickFilterMaxLength={5}
      formFiltersPrefix="filter_"
    />
  );

  // 验证高级筛选是否隐藏
});
```

#### 2.3 配置项传递

**测试目标**: 验证配置项是否正确传递

```
test('应该正确传递 quickFilterMaxLength', () => {
  const { container } = render(
    <PisellFilter
      quickFilterMaxLength={10}
      formFiltersPrefix="filter_"
    />
  );

  // 验证 quickFilterMaxLength 是否生效
  // 可以通过快照测试或者查看 FilterButton 的 props
});

test('应该正确传递 formFiltersPrefix', () => {
  const { container } = render(
    <PisellFilter
      quickFilterMaxLength={5}
      formFiltersPrefix="custom_"
    />
  );

  // 验证表单字段前缀是否正确
});
```

### 3. 状态管理测试

#### 3.1 状态初始化

**测试目标**: 验证状态正确初始化

```
import { renderHook } from '@testing-library/react';

test('应该正确初始化内部状态', () => {
  const initialValue = {
    quickFilter: [
      {
        type: 'single' as const,
        key: 'status',
        label: '状态',
        name: 'status',
        props: {},
        other: {},
      },
    ],
    otherFilter: [],
  };

  const { container } = render(
    <PisellFilter
      value={initialValue}
      quickFilterMaxLength={5}
      formFiltersPrefix="filter_"
    />
  );

  // 验证初始状态
  expect(container.firstChild).toBeInTheDocument();
});
```

#### 3.2 状态更新

**测试目标**: 验证外部状态变化时内部状态同步

```
test('应该在外部状态变化时更新内部状态', () => {
  const { rerender } = render(
    <PisellFilter
      value={{
        quickFilter: [],
        otherFilter: [],
      }}
      quickFilterMaxLength={5}
      formFiltersPrefix="filter_"
    />
  );

  // 更新外部状态
  rerender(
    <PisellFilter
      value={{
        quickFilter: [
          {
            type: 'single' as const,
            key: 'status',
            label: '状态',
            name: 'status',
            props: {},
            other: {},
          },
        ],
        otherFilter: [],
      }}
      quickFilterMaxLength={5}
      formFiltersPrefix="filter_"
    />
  );

  // 验证内部状态已更新
  // 可以通过检查渲染结果来验证
});
```

### 4. 交互测试

#### 4.1 筛选值变化

**测试目标**: 验证筛选值变化时的回调

```
import { fireEvent } from '@testing-library/react';

test('应该在筛选值变化时触发 onChange', () => {
  const handleChange = jest.fn();

  const { container } = render(
    <PisellFilter
      value={{
        quickFilter: [],
        otherFilter: [],
      }}
      onChange={handleChange}
      quickFilterMaxLength={5}
      formFiltersPrefix="filter_"
      hiddenQuickFilter={false}
    />
  );

  // 模拟筛选操作
  // 实际测试需要根据 FilterButton 的实现来触发交互
  // fireEvent.click(...)

  // 验证回调被调用
  // expect(handleChange).toHaveBeenCalled();
});
```

#### 4.2 快速筛选交互

**测试目标**: 验证快速筛选的交互

```
test('应该能选择快速筛选项', async () => {
  const handleChange = jest.fn();

  const { getByText } = render(
    <PisellFilter
      value={{
        quickFilter: [
          {
            type: 'single' as const,
            key: 'status',
            label: '状态',
            name: 'status',
            props: {
              options: [
                { label: '全部', value: 'all' },
                { label: '启用', value: 'enabled' },
              ],
            },
            other: {},
          },
        ],
        otherFilter: [],
      }}
      onChange={handleChange}
      quickFilterMaxLength={5}
      formFiltersPrefix="filter_"
      hiddenQuickFilter={false}
    />
  );

  // 点击筛选项
  // const enabledOption = getByText('启用');
  // fireEvent.click(enabledOption);

  // 验证回调
  // expect(handleChange).toHaveBeenCalledWith(expect.objectContaining({
  //   quickFilter: expect.any(Array),
  // }));
});
```

### 5. 边界情况测试

#### 5.1 缺少必需属性

**测试目标**: 验证缺少属性时的处理

```
test('应该能处理缺少 quickFilterMaxLength', () => {
  const { container } = render(
    <PisellFilter
      // @ts-expect-error 故意不传 quickFilterMaxLength
      formFiltersPrefix="filter_"
    />
  );

  expect(container.firstChild).toBeInTheDocument();
});

test('应该能处理缺少 formFiltersPrefix', () => {
  const { container } = render(
    <PisellFilter
      // @ts-expect-error 故意不传 formFiltersPrefix
      quickFilterMaxLength={5}
    />
  );

  expect(container.firstChild).toBeInTheDocument();
});
```

#### 5.2 无效的筛选值格式

**测试目标**: 验证无效格式的处理

```
test('应该能处理无效的筛选值格式', () => {
  const { container } = render(
    <PisellFilter
      // @ts-expect-error 故意传入错误格式
      value={{ invalid: 'format' }}
      quickFilterMaxLength={5}
      formFiltersPrefix="filter_"
    />
  );

  expect(container.firstChild).toBeInTheDocument();
});
```

#### 5.3 大量筛选项

**测试目标**: 验证大量筛选项的性能

```
test('应该能处理大量筛选项', () => {
  const largeFilterList = Array.from({ length: 100 }, (_, i) => ({
    type: 'single' as const,
    key: `filter_${i}`,
    label: `筛选项 ${i}`,
    name: `filter_${i}`,
    props: {},
    other: {},
  }));

  const { container } = render(
    <PisellFilter
      value={{
        quickFilter: largeFilterList,
        otherFilter: [],
      }}
      quickFilterMaxLength={100}
      formFiltersPrefix="filter_"
    />
  );

  expect(container.firstChild).toBeInTheDocument();
});
```

### 6. 集成测试

#### 6.1 与表单集成

**测试目标**: 验证与表单的集成

```
test('应该能与 Form 正确集成', () => {
  const { container } = render(
    <PisellFilter
      value={{
        quickFilter: [],
        otherFilter: [],
      }}
      quickFilterMaxLength={5}
      formFiltersPrefix="filter_"
    />
  );

  const form = container.querySelector('.ant-form');
  expect(form).toBeInTheDocument();
  expect(form).toHaveClass('ant-form-large');
  expect(form).toHaveClass('ant-form-vertical');
});
```

#### 6.2 完整流程测试

**测试目标**: 验证完整的使用流程

```
test('完整的筛选流程', async () => {
  const handleChange = jest.fn();

  const { rerender, getByText } = render(
    <PisellFilter
      value={{
        quickFilter: [
          {
            type: 'single' as const,
            key: 'status',
            label: '状态',
            name: 'status',
            props: {
              options: [
                { label: '全部', value: 'all' },
                { label: '启用', value: 'enabled' },
              ],
            },
            other: {},
          },
        ],
        otherFilter: [],
      }}
      onChange={handleChange}
      quickFilterMaxLength={5}
      formFiltersPrefix="filter_"
      hiddenQuickFilter={false}
    />
  );

  // 1. 选择筛选项
  // 2. 验证回调
  // 3. 更新组件
  // 4. 验证新状态
});
```

## 快照测试

### 基础快照

```
test('应该匹配快照', () => {
  const { container } = render(
    <PisellFilter
      value={{
        quickFilter: [
          {
            type: 'single' as const,
            key: 'status',
            label: '状态',
            name: 'status',
            props: {},
            other: {},
          },
        ],
        otherFilter: [],
      }}
      quickFilterMaxLength={5}
      formFiltersPrefix="filter_"
    />
  );

  expect(container).toMatchSnapshot();
});
```

## 测试覆盖率目标

- **语句覆盖率**: ≥ 80%
- **分支覆盖率**: ≥ 80%
- **函数覆盖率**: ≥ 80%
- **行覆盖率**: ≥ 80%

## 运行测试

```bash
# 运行所有测试
pnpm test

# 运行单个组件测试
pnpm test pisellFilter

# 运行测试并生成覆盖率报告
pnpm test:coverage

# 监听模式运行测试
pnpm test:watch
```

## 测试工具

- **测试框架**: Jest
- **测试库**: @testing-library/react
- **断言库**: Jest (内置)
- **Mock 工具**: Jest (内置)
- **用户事件**: @testing-library/user-event

## 注意事项

1. **异步测试**: 涉及异步操作时使用 `async/await` 或 `waitFor`
2. **清理工作**: 每个测试后清理 DOM 和状态
3. **隔离性**: 每个测试应该独立，不依赖其他测试
4. **可读性**: 测试描述应该清晰明确
5. **真实场景**: 测试应该模拟真实使用场景
6. **依赖组件**: 注意 FilterButton 组件的测试可能需要 mock

## Mock 策略

### Mock FilterButton 组件

```
jest.mock('../filter/components/FilterButton', () => {
  return function MockFilterButton(props: any) {
    return (
      <div data-testid="filter-button">
        <button onClick={() => props.onChange({ quickFilter: [], otherFilter: [] })}>
          Change Filter
        </button>
      </div>
    );
  };
});
```

## 持续集成

测试应该集成到 CI/CD 流程中：

1. **PR 检查**: 每个 PR 必须通过所有测试
2. **覆盖率门槛**: 维持或提高测试覆盖率
3. **性能监控**: 监控测试执行时间
4. **失败通知**: 测试失败时及时通知

## 相关资源

- [Jest 文档](https://jestjs.io/)
- [Testing Library 文档](https://testing-library.com/docs/react-testing-library/intro/)
- [Ant Design 测试指南](https://ant.design/docs/react/testing-cn)
- [React Testing 最佳实践](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library)
