本文へ移動
cccskills
無料GitHub で公開

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.

インストール方法を見る

含まれるファイル(1)

  • SKILL.md7.9 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

TDesign 组件测试指南

本技能提供 TDesign React 组件单元测试的完整 SOP 和最佳实践。

触发条件

当用户需要执行以下任务时使用此技能:

  • 为组件编写单元测试
  • 更新或修复测试用例
  • 运行快照测试
  • 提高测试覆盖率

测试框架和工具

工具用途
Vitest测试运行器
@testing-library/reactReact 组件测试工具
@testing-library/user-event用户交互模拟
@testing-library/jest-domDOM 断言扩展

测试文件规范

文件位置和命名

packages/components/[component-name]/
└── __tests__/
    ├── vitest-[component-name].test.jsx   # 单元测试
    └── __snapshots__/                     # 快照文件(自动生成)

命名格式:vitest-[component-name].test.jsx

⚠️ 注意:测试文件由 API 平台生成,文件头部有注释标记。

测试工具导入

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: 基础渲染测试

测试组件能否正常渲染:

describe('ExampleComponent', () => {
  it('renders correctly', () => {
    const { container } = render(<ExampleComponent />);
    expect(container.firstChild).toHaveClass('t-example-component');
  });
});

Step 2: Props 测试

为每个 prop 编写测试用例:

// 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 渲染测试

测试自定义渲染内容:

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: 事件测试

测试组件事件回调:

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: 异步操作测试

测试异步行为:

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 进行复杂交互测试:

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 生成快照:

# 运行 CSR 快照测试
NODE_ENV=test-snap pnpm test

SSR 快照测试

位置:test/snap/ssr.test.jsx

测试服务端渲染输出。

更新快照

# 更新所有快照
pnpm test -- --update

# 更新特定组件快照
pnpm test button -- --update

运行测试命令

# 运行所有单元测试
pnpm test

# 运行特定组件测试
pnpm test button

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

# 运行快照测试
NODE_ENV=test-snap pnpm test

# 交互式测试模式
pnpm test -- --ui

常见测试模式

测试 Loading 状态

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 属性

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 组合

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

class ResizeObserver {
  observe() { return this; }
  unobserve() { return this; }
}

beforeAll(() => {
  global.ResizeObserver = ResizeObserver;
});

Mock 时间

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 参数更新快照。

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

同じリポジトリのスキル

概要と使いどころ

This skill should be used when developing or modifying styles for TDesign React components. It provides guidance on BEM naming conventions, CSS Variables usage, state classes, and the relationship between component code and common submodule styles.

日本語の概要は準備中です。原文の説明を表示しています。

Tencent/tdesign-react9712026年10月15日 更新

This skill should be used when creating a new TDesign React component. It provides a complete SOP for scaffolding components in the packages/components or packages/pro-components directories, including file structure, code patterns, and registration steps.

日本語の概要は準備中です。原文の説明を表示しています。

Tencent/tdesign-react9712026年10月15日 更新

This skill should be used when developing or modifying TDesign Pro Components (AIGC/Chat). It provides the ChatEngine architecture, component patterns, AG-UI protocol integration, and Generative UI development guide.

日本語の概要は準備中です。原文の説明を表示しています。

Tencent/tdesign-react9712026年10月15日 更新

Tencent のスキルをすべて見る

このスキルの問題を報告する