frontend-testing by langgenius/dify
npx skills add https://github.com/langgenius/dify --skill frontend-testing此技能使 Claude 能够按照既定规范和最佳实践为 Dify 项目生成高质量、全面的前端测试。
⚠️ 权威来源:此技能源自
web/docs/test.md。使用 Vitest 模拟/定时器 API (vi.*)。
当用户出现以下情况时应用此技能:
pnpm analyze-component 输出作为上下文请勿应用于以下情况:
广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
| 工具 | 版本 | 用途 |
|---|---|---|
| Vitest | 4.0.16 | 测试运行器 |
| React Testing Library | 16.0 | 组件测试 |
| jsdom | - | 测试环境 |
| nock | 14.0 | HTTP 模拟 |
| TypeScript | 5.x | 类型安全 |
# 运行所有测试
pnpm test
# 监视模式
pnpm test:watch
# 运行特定文件
pnpm test path/to/file.spec.tsx
# 生成覆盖率报告
pnpm test:coverage
# 分析组件复杂度
pnpm analyze-component <path>
# 审查现有测试
pnpm analyze-component <path> --review
__tests__/ 目录内的 ComponentName.spec.tsx__tests__/ 文件夹中。例如,foo/index.tsx 对应 foo/__tests__/index.spec.tsx,foo/bar.ts 对应 foo/__tests__/bar.spec.ts。web/__tests__/ 目录import { render, screen, fireEvent, waitFor } from '@testing-library/react'
import Component from './index'
// ✅ 导入真实项目组件(不要模拟这些)
// import Loading from '@/app/components/base/loading'
// import { ChildComponent } from './child-component'
// ✅ 仅模拟外部依赖项
vi.mock('@/service/api')
vi.mock('next/navigation', () => ({
useRouter: () => ({ push: vi.fn() }),
usePathname: () => '/test',
}))
// ✅ Zustand 存储:使用真实存储(全局自动模拟)
// 使用以下方式设置测试状态:useAppStore.setState({ ... })
// 模拟的共享状态(如果需要)
let mockSharedState = false
describe('ComponentName', () => {
beforeEach(() => {
vi.clearAllMocks() // ✅ 在每个测试之前重置模拟
mockSharedState = false // ✅ 重置共享状态
})
// 渲染测试(必需)
describe('Rendering', () => {
it('should render without crashing', () => {
// 准备
const props = { title: 'Test' }
// 执行
render(<Component {...props} />)
// 断言
expect(screen.getByText('Test')).toBeInTheDocument()
})
})
// 属性测试(必需)
describe('Props', () => {
it('should apply custom className', () => {
render(<Component className="custom" />)
expect(screen.getByRole('button')).toHaveClass('custom')
})
})
// 用户交互
describe('User Interactions', () => {
it('should handle click events', () => {
const handleClick = vi.fn()
render(<Component onClick={handleClick} />)
fireEvent.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
})
// 边界情况(必需)
describe('Edge Cases', () => {
it('should handle null data', () => {
render(<Component data={null} />)
expect(screen.getByText(/no data/i)).toBeInTheDocument()
})
it('should handle empty array', () => {
render(<Component items={[]} />)
expect(screen.getByText(/empty/i)).toBeInTheDocument()
})
})
})
切勿一次性生成所有测试文件。 对于复杂组件或多文件目录:
对于每个文件:
┌────────────────────────────────────────┐
│ 1. 编写测试 │
│ 2. 运行: pnpm test <file>.spec.tsx │
│ 3. 通过? → 标记完成,下一个文件 │
│ 失败? → 先修复,然后继续 │
└────────────────────────────────────────┘
对于多文件测试,按此顺序处理:
📖 有关完整工作流程详情和待办事项列表格式,请参阅
references/workflow.md。
当被分配测试一个目录/路径时,测试该路径内的所有内容:
index 文件)为目录编写测试时,优先进行集成测试:
@/service/*)、next/navigation、复杂的上下文提供者@/app/components/base/*)有关正确的导入/模拟模式,请参阅测试结构模板。
nuqs 查询状态测试(URL 状态钩子必需)当组件或钩子使用 useQueryState / useQueryStates 时:
NuqsTestingAdapter(优先使用 web/test/nuqs-testing.tsx 中的共享助手)onUrlUpdate(searchParams、options.history)断言 URL 同步createParser),保持 parse 和 serialize 的双射性,并添加往返边界情况测试(%2F、%25、空格、旧版编码值)nuqs每个测试应清晰分离:
测试可观察行为,而非实现细节
使用语义查询(getByRole、getByLabelText)
避免直接测试内部状态
断言中优先使用模式匹配而非硬编码字符串:
// ❌ 避免:硬编码文本断言 expect(screen.getByText('Loading...')).toBeInTheDocument()
// ✅ 更好:基于角色的查询 expect(screen.getByRole('status')).toBeInTheDocument()
// ✅ 更好:模式匹配 expect(screen.getByText(/loading/i)).toBeInTheDocument()
每个测试验证一个用户可观察的行为:
// ✅ 良好:一个行为
it('should disable button when loading', () => {
render(<Button loading />)
expect(screen.getByRole('button')).toBeDisabled()
})
// ❌ 不良:多个行为
it('should handle loading state', () => {
render(<Button loading />)
expect(screen.getByRole('button')).toBeDisabled()
expect(screen.getByText('Loading...')).toBeInTheDocument()
expect(screen.getByRole('button')).toHaveClass('loading')
})
使用 should <行为> when <条件>:
it('should show error message when validation fails')
it('should call onSubmit when form is valid')
it('should disable input when isReadOnly is true')
| 功能 | 测试重点 |
|---|---|
useState | 初始状态、转换、清理 |
useEffect | 执行、依赖项、清理 |
| 事件处理程序 | 所有 onClick、onChange、onSubmit、键盘事件 |
| API 调用 | 加载、成功、错误状态 |
| 路由 | 导航、参数、查询字符串 |
useCallback/useMemo | 引用相等性 |
| 上下文 | 提供者值、消费者行为 |
| 表单 | 验证、提交、错误显示 |
对于生成的每个测试文件,目标是:
注意:对于多文件目录,每次处理一个文件,每个文件都达到完整覆盖率。请参阅
references/workflow.md。
更多详细信息,请参考:
references/workflow.md - 增量测试工作流程(多文件测试必读)references/mocking.md - 模拟模式、Zustand 存储测试和最佳实践references/async-testing.md - 异步操作和 API 调用references/domain-components.md - 工作流、数据集、配置测试references/common-patterns.md - 常用测试模式references/checklist.md - 测试生成清单和验证步骤web/docs/test.md - 规范的测试规范。此技能源自此文档。web/utils/classnames.spec.ts - 工具函数测试web/app/components/base/button/index.spec.tsx - 组件测试web/__mocks__/provider-context.ts - 模拟工厂示例web/vitest.config.ts - Vitest 配置web/vitest.setup.ts - 测试环境设置web/scripts/analyze-component.js - 组件分析工具web/vitest.setup.ts 中(例如 react-i18next、next/image);在测试文件中本地模拟其他模块,如 ky 或 mime。每周安装次数
1.6K
代码仓库
GitHub 星标数
134.2K
首次出现
2026年1月20日
安全审计
安装于
claude-code1.1K
gemini-cli1.1K
opencode1.1K
cursor1.0K
codex991
github-copilot903
This skill enables Claude to generate high-quality, comprehensive frontend tests for the Dify project following established conventions and best practices.
⚠️ Authoritative Source : This skill is derived from
web/docs/test.md. Use Vitest mock/timer APIs (vi.*).
Apply this skill when the user:
pnpm analyze-component output as contextDo NOT apply when:
| Tool | Version | Purpose |
|---|---|---|
| Vitest | 4.0.16 | Test runner |
| React Testing Library | 16.0 | Component testing |
| jsdom | - | Test environment |
| nock | 14.0 | HTTP mocking |
| TypeScript | 5.x | Type safety |
# Run all tests
pnpm test
# Watch mode
pnpm test:watch
# Run specific file
pnpm test path/to/file.spec.tsx
# Generate coverage report
pnpm test:coverage
# Analyze component complexity
pnpm analyze-component <path>
# Review existing test
pnpm analyze-component <path> --review
ComponentName.spec.tsx inside a same-level __tests__/ directory__tests__/ folder at the same level as the source under test. For example, foo/index.tsx maps to foo/__tests__/index.spec.tsx, and foo/bar.ts maps to foo/__tests__/bar.spec.ts.web/__tests__/ directoryimport { render, screen, fireEvent, waitFor } from '@testing-library/react'
import Component from './index'
// ✅ Import real project components (DO NOT mock these)
// import Loading from '@/app/components/base/loading'
// import { ChildComponent } from './child-component'
// ✅ Mock external dependencies only
vi.mock('@/service/api')
vi.mock('next/navigation', () => ({
useRouter: () => ({ push: vi.fn() }),
usePathname: () => '/test',
}))
// ✅ Zustand stores: Use real stores (auto-mocked globally)
// Set test state with: useAppStore.setState({ ... })
// Shared state for mocks (if needed)
let mockSharedState = false
describe('ComponentName', () => {
beforeEach(() => {
vi.clearAllMocks() // ✅ Reset mocks BEFORE each test
mockSharedState = false // ✅ Reset shared state
})
// Rendering tests (REQUIRED)
describe('Rendering', () => {
it('should render without crashing', () => {
// Arrange
const props = { title: 'Test' }
// Act
render(<Component {...props} />)
// Assert
expect(screen.getByText('Test')).toBeInTheDocument()
})
})
// Props tests (REQUIRED)
describe('Props', () => {
it('should apply custom className', () => {
render(<Component className="custom" />)
expect(screen.getByRole('button')).toHaveClass('custom')
})
})
// User Interactions
describe('User Interactions', () => {
it('should handle click events', () => {
const handleClick = vi.fn()
render(<Component onClick={handleClick} />)
fireEvent.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
})
// Edge Cases (REQUIRED)
describe('Edge Cases', () => {
it('should handle null data', () => {
render(<Component data={null} />)
expect(screen.getByText(/no data/i)).toBeInTheDocument()
})
it('should handle empty array', () => {
render(<Component items={[]} />)
expect(screen.getByText(/empty/i)).toBeInTheDocument()
})
})
})
NEVER generate all test files at once. For complex components or multi-file directories:
For each file:
┌────────────────────────────────────────┐
│ 1. Write test │
│ 2. Run: pnpm test <file>.spec.tsx │
│ 3. PASS? → Mark complete, next file │
│ FAIL? → Fix first, then continue │
└────────────────────────────────────────┘
Process in this order for multi-file testing:
📖 See
references/workflow.mdfor complete workflow details and todo list format.
When assigned to test a directory/path, test ALL content within that path:
index file)Prefer integration testing when writing tests for a directory:
@/service/*), next/navigation, complex context providers@/app/components/base/*)See Test Structure Template for correct import/mock patterns.
nuqs Query State Testing (Required for URL State Hooks)When a component or hook uses useQueryState / useQueryStates:
NuqsTestingAdapter (prefer shared helpers in web/test/nuqs-testing.tsx)onUrlUpdate (searchParams, options.history)createParser), keep parse and serialize bijective and add round-trip edge cases (%2F, %25, spaces, legacy encoded values)Every test should clearly separate:
Test observable behavior, not implementation details
Use semantic queries (getByRole, getByLabelText)
Avoid testing internal state directly
Prefer pattern matching over hardcoded strings in assertions:
// ❌ Avoid: hardcoded text assertions expect(screen.getByText('Loading...')).toBeInTheDocument()
// ✅ Better: role-based queries expect(screen.getByRole('status')).toBeInTheDocument()
// ✅ Better: pattern matching expect(screen.getByText(/loading/i)).toBeInTheDocument()
Each test verifies ONE user-observable behavior:
// ✅ Good: One behavior
it('should disable button when loading', () => {
render(<Button loading />)
expect(screen.getByRole('button')).toBeDisabled()
})
// ❌ Bad: Multiple behaviors
it('should handle loading state', () => {
render(<Button loading />)
expect(screen.getByRole('button')).toBeDisabled()
expect(screen.getByText('Loading...')).toBeInTheDocument()
expect(screen.getByRole('button')).toHaveClass('loading')
})
Use should <behavior> when <condition>:
it('should show error message when validation fails')
it('should call onSubmit when form is valid')
it('should disable input when isReadOnly is true')
| Feature | Test Focus |
|---|---|
useState | Initial state, transitions, cleanup |
useEffect | Execution, dependencies, cleanup |
| Event handlers | All onClick, onChange, onSubmit, keyboard |
| API calls | Loading, success, error states |
| Routing | Navigation, params, query strings |
useCallback/useMemo | Referential equality |
| Context | Provider values, consumer behavior |
| Forms | Validation, submission, error display |
For each test file generated, aim for:
Note : For multi-file directories, process one file at a time with full coverage each. See
references/workflow.md.
For more detailed information, refer to:
references/workflow.md - Incremental testing workflow (MUST READ for multi-file testing)references/mocking.md - Mock patterns, Zustand store testing, and best practicesreferences/async-testing.md - Async operations and API callsreferences/domain-components.md - Workflow, Dataset, Configuration testingreferences/common-patterns.md - Frequently used testing patternsreferences/checklist.md - Test generation checklist and validation stepsweb/docs/test.md - The canonical testing specification. This skill is derived from this document.web/utils/classnames.spec.ts - Utility function testsweb/app/components/base/button/index.spec.tsx - Component testsweb/__mocks__/provider-context.ts - Mock factory exampleweb/vitest.config.ts - Vitest configurationweb/vitest.setup.ts - Test environment setupweb/scripts/analyze-component.js - Component analysis toolweb/vitest.setup.ts (for example react-i18next, next/image); mock other modules like ky or mime locally in test files.Weekly Installs
1.6K
Repository
GitHub Stars
134.2K
First Seen
Jan 20, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code1.1K
gemini-cli1.1K
opencode1.1K
cursor1.0K
codex991
github-copilot903
React 组合模式指南:Vercel 组件架构最佳实践,提升代码可维护性
102,200 周安装
nuqs directly when URL behavior is explicitly out of scope for the test