Skip to main content

tdesign-test-component

This skill should be used when writing or maintaining unit tests for TDesign React components. It provides testing patterns, best practices, and SOP for Vitest-based component testing including snapshot tests.

Informações da origem

Repositório
Tencent/tdesign-react
Última atividade na origem
16 de março de 2026 às 07:15
Idioma detectado do SKILL.md
chinês
Estrelas
968
Forks
377

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
tdesign-test-component
description
This skill should be used when writing or maintaining unit tests for TDesign React components. It provides testing patterns, best practices, and SOP for Vitest-based component testing including snapshot tests.
# TDesign 组件测试指南 本技能提供 TDesign React 组件单元测试的完整 SOP 和最佳实践。 ## 触发条件 当用户需要执行以下任务时使用此技能: - 为组件编写单元测试 - 更新或修复测试用例 - 运行快照测试 - 提高测试覆盖率 ## 测试框架和工具 | 工具 | 用途 | |------|------| | Vitest | 测试运行器 | | @testing-library/react | React 组件测试工具 | | @testing-library/user-event | 用户交互模拟 | | @testing-library/jest-dom | DOM 断言扩展 | ## 测试文件规范 ### 文件位置和命名 ``` packages/components/[component-name]/ └── __tests__/ ├── vitest-[component-name].test.jsx # 单元测试 └── __snapshots__/ # 快照文件(自动生成) ``` **命名格式**:`vitest-[component-name].test.jsx` ⚠️ **注意**:测试文件由 API 平台生成,文件头部有注释标记。 ### 测试工具导入 ```javascript import { render, fireEvent, vi } from '@test/utils'; import { ComponentName } from '..'; ``` `@test/utils` 已封装常用工具,包括: - `render` - 组件渲染 - `fireEvent` - 事件触发 - `vi` - Vitest mock 工具 - `act` - React act wrapper - `userEvent` - 用户交互模拟 - `mockDelay` - 异步延迟模拟 - `simulateInputChange` - 输入模拟 - `simulateKeydownEvent` - 键盘事件模拟 ## SOP 流程 ### Step 1: 基础渲染测试 测试组件能否正常渲染: ```javascript describe('ExampleComponent', () => { it('renders correctly', () => { const { container } = render(<ExampleComponent />); expect(container.firstChild).toHaveClass('t-example-component'); }); }); ``` ### Step 2: Props 测试 为每个 prop 编写测试用例: ```javascript // Boolean prop 测试模式 it('props.disabled works fine', () => { // 默认值测试 const { container: container1 } = render(<Button>Text</Button>); expect(container1.querySelector('.t-is-disabled')).toBeFalsy(); // disabled = true const { container: container2 } = render(<Button disabled={true}>Text</Button>); expect(container2.firstChild).toHaveClass('t-is-disabled'); expect(container2).toMatchSnapshot(); // disabled = false const { container: container3 } = render(<Button disabled={false}>Text</Button>); expect(container3.querySelector('.t-is-disabled')).toBeFalsy(); }); // 枚举 prop 测试模式 ['small', 'medium', 'large'].forEach((item) => { it(`props.size is equal to ${item}`, () => { const { container } = render(<Button size={item}>Text</Button>); // medium 是默认值,不添加额外类名 if (item !== 'medium') { expect(container.firstChild).toHaveClass(`t-size-${item[0]}`); } expect(container).toMatchSnapshot(); }); }); ``` ### Step 3: TNode 渲染测试 测试自定义渲染内容: ```javascript it('props.children works fine', () => { const { container } = render( <Button> <span className="custom-node">TNode</span> </Button> ); expect(container.querySelector('.custom-node')).toBeTruthy(); expect(container).toMatchSnapshot(); }); it('props.icon works fine', () => { const { container } = render( <Button icon={<span className="custom-icon">Icon</span>}>Text</Button> ); expect(container.querySelector('.custom-icon')).toBeTruthy(); }); ``` ### Step 4: 事件测试 测试组件事件回调: ```javascript it('events.click works fine', () => { const fn = vi.fn(); const { container } = render(<Button onClick={fn}>Click</Button>); fireEvent.click(container.firstChild); expect(fn).toHaveBeenCalled(); expect(fn.mock.calls[0][0].type).toBe('click'); }); // 禁用状态下事件不触发 it('disabled button should not trigger click', () => { const fn = vi.fn(); const { container } = render(<Button disabled onClick={fn}>Click</Button>); fireEvent.click(container.firstChild); expect(fn).not.toHaveBeenCalled(); }); ``` ### Step 5: 异步操作测试 测试异步行为: ```javascript import { mockDelay } from '@test/utils'; it('async operation works fine', async () => { const { container } = render(<AsyncComponent />); // 触发异步操作 fireEvent.click(container.querySelector('.trigger')); // 等待异步完成 await mockDelay(300); expect(container.querySelector('.result')).toBeTruthy(); }); ``` ### Step 6: 用户交互测试 使用 userEvent 进行复杂交互测试: ```javascript import { userEvent } from '@test/utils'; it('user input works fine', async () => { const user = userEvent.setup(); const onChange = vi.fn(); const { container } = render(<Input onChange={onChange} />); const input = container.querySelector('input'); await user.type(input, 'hello'); expect(onChange).toHaveBeenCalled(); expect(input.value).toBe('hello'); }); ``` ## 快照测试 ### CSR 快照测试 位置:`test/snap/csr.test.jsx` 自动为所有 `_example/*.tsx` 生成快照: ```bash # 运行 CSR 快照测试 NODE_ENV=test-snap pnpm test ``` ### SSR 快照测试 位置:`test/snap/ssr.test.jsx` 测试服务端渲染输出。 ### 更新快照 ```bash # 更新所有快照 pnpm test -- --update # 更新特定组件快照 pnpm test button -- --update ``` ## 运行测试命令 ```bash # 运行所有单元测试 pnpm test # 运行特定组件测试 pnpm test button # 运行测试并生成覆盖率报告 pnpm test -- --coverage # 运行快照测试 NODE_ENV=test-snap pnpm test # 交互式测试模式 pnpm test -- --ui ``` ## 常见测试模式 ### 测试 Loading 状态 ```javascript it('loading state shows loading indicator', () => { const { container } = render(<Button loading>Text</Button>); expect(container.firstChild).toHaveClass('t-is-loading'); expect(container.querySelector('.t-loading')).toBeTruthy(); }); ``` ### 测试 DOM 属性 ```javascript it('href attribute works fine', () => { const { container } = render(<Button href="https://example.com">Link</Button>); expect(container.firstChild.getAttribute('href')).toBe('https://example.com'); expect(container.firstChild.tagName.toLowerCase()).toBe('a'); }); ``` ### 测试 className 组合 ```javascript it('custom className is merged', () => { const { container } = render(<Button className="custom-class">Text</Button>); expect(container.firstChild).toHaveClass('t-button'); expect(container.firstChild).toHaveClass('custom-class'); }); ``` ### Mock ResizeObserver ```javascript class ResizeObserver { observe() { return this; } unobserve() { return this; } } beforeAll(() => { global.ResizeObserver = ResizeObserver; }); ``` ### Mock 时间 ```javascript import MockDate from 'mockdate'; beforeEach(() => { MockDate.set('2020-12-28 00:00:00'); }); afterEach(() => { MockDate.reset(); }); ``` ## 测试检查清单 编写测试时,确保覆盖: - [ ] 默认渲染 - [ ] 所有 boolean props(true/false 两种情况) - [ ] 所有枚举 props 的每个值 - [ ] TNode 类型 props - [ ] 事件回调触发 - [ ] 禁用状态下的行为 - [ ] 加载状态 - [ ] 边界情况(空值、极端值) - [ ] 快照测试 ## 测试覆盖率要求 - 整体覆盖率目标:> 80% - 新组件要求:> 90% 行覆盖率 - 核心逻辑:100% 分支覆盖 ## 常见问题 ### Q: 测试文件是否可以手动编辑? A: `vitest-*.test.jsx` 文件由 API 平台生成,文件头有注释标记。可以在其基础上添加额外测试,但核心测试应通过 API 平台维护。 ### Q: 如何处理异步组件? A: 使用 `mockDelay` 或 `act` wrapper,确保异步操作完成后再进行断言。 ### Q: 快照测试失败怎么办? A: 首先检查变更是否预期内。如果是预期变更,使用 `--update` 参数更新快照。
Ver no GitHub