--- name: Dify Frontend Testing description: Generate Jest + React Testing Library tests for Dify frontend components, hooks, and utilities. Triggers on testing, spec files, coverage, Jest, RTL, unit tests, integration tests, or write/review test requests. --- # Dify Frontend Testing Skill 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/testing/testing.md`. When in doubt, always refer to that document as the canonical specification. ## When to Apply This Skill Apply this skill when the user: - Asks to **write tests** for a component, hook, or utility - Asks to **review existing tests** for completeness - Mentions **Jest**, **React Testing Library**, **RTL**, or **spec files** - Requests **test coverage** improvement - Uses `pnpm analyze-component` output as context - Mentions **testing**, **unit tests**, or **integration tests** for frontend code - Wants to understand **testing patterns** in the Dify codebase **Do NOT apply** when: - User is asking about backend/API tests (Python/pytest) - User is asking about E2E tests (Playwright/Cypress) - User is only asking conceptual questions without code context ## Quick Reference ### Tech Stack | Tool | Version | Purpose | |------|---------|---------| | Jest | 29.7 | Test runner | | React Testing Library | 16.0 | Component testing | | happy-dom | - | Test environment | | nock | 14.0 | HTTP mocking | | TypeScript | 5.x | Type safety | ### Key Commands ```bash # 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 # Review existing test pnpm analyze-component --review ``` ### File Naming - Test files: `ComponentName.spec.tsx` (same directory as component) - Integration tests: `web/__tests__/` directory ## Test Structure Template ```typescript import { 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 jest.mock('@/service/api') jest.mock('next/navigation', () => ({ useRouter: () => ({ push: jest.fn() }), usePathname: () => '/test', })) // Shared state for mocks (if needed) let mockSharedState = false describe('ComponentName', () => { beforeEach(() => { jest.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() // Assert expect(screen.getByText('Test')).toBeInTheDocument() }) }) // Props tests (REQUIRED) describe('Props', () => { it('should apply custom className', () => { render() expect(screen.getByRole('button')).toHaveClass('custom') }) }) // User Interactions describe('User Interactions', () => { it('should handle click events', () => { const handleClick = jest.fn() render() fireEvent.click(screen.getByRole('button')) expect(handleClick).toHaveBeenCalledTimes(1) }) }) // Edge Cases (REQUIRED) describe('Edge Cases', () => { it('should handle null data', () => { render() expect(screen.getByText(/no data/i)).toBeInTheDocument() }) it('should handle empty array', () => { render() expect(screen.getByText(/empty/i)).toBeInTheDocument() }) }) }) ``` ## Testing Workflow (CRITICAL) ### ⚠️ Incremental Approach Required **NEVER generate all test files at once.** For complex components or multi-file directories: 1. **Analyze & Plan**: List all files, order by complexity (simple → complex) 1. **Process ONE at a time**: Write test → Run test → Fix if needed → Next 1. **Verify before proceeding**: Do NOT continue to next file until current passes ``` For each file: ┌────────────────────────────────────────┐ │ 1. Write test │ │ 2. Run: pnpm test -- .spec.tsx │ │ 3. PASS? → Mark complete, next file │ │ FAIL? → Fix first, then continue │ └────────────────────────────────────────┘ ``` ### Complexity-Based Order Process in this order for multi-file testing: 1. 🟢 Utility functions (simplest) 1. 🟢 Custom hooks 1. 🟡 Simple components (presentational) 1. 🟡 Medium components (state, effects) 1. 🔴 Complex components (API, routing) 1. 🔴 Integration tests (index files - last) ### When to Refactor First - **Complexity > 50**: Break into smaller pieces before testing - **500+ lines**: Consider splitting before testing - **Many dependencies**: Extract logic into hooks first > 📖 See `guides/workflow.md` for complete workflow details and todo list format. ## Testing Strategy ### Path-Level Testing (Directory Testing) When assigned to test a directory/path, test **ALL content** within that path: - Test all components, hooks, utilities in the directory (not just `index` file) - Use incremental approach: one file at a time, verify each before proceeding - Goal: 100% coverage of ALL files in the directory ### Integration Testing First **Prefer integration testing** when writing tests for a directory: - ✅ **Import real project components** directly (including base components and siblings) - ✅ **Only mock**: API services (`@/service/*`), `next/navigation`, complex context providers - ❌ **DO NOT mock** base components (`@/app/components/base/*`) - ❌ **DO NOT mock** sibling/child components in the same directory > See [Test Structure Template](#test-structure-template) for correct import/mock patterns. ## Core Principles ### 1. AAA Pattern (Arrange-Act-Assert) Every test should clearly separate: - **Arrange**: Setup test data and render component - **Act**: Perform user actions - **Assert**: Verify expected outcomes ### 2. Black-Box Testing - Test observable behavior, not implementation details - Use semantic queries (getByRole, getByLabelText) - Avoid testing internal state directly - **Prefer pattern matching over hardcoded strings** in assertions: ```typescript // ❌ 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() ``` ### 3. Single Behavior Per Test Each test verifies ONE user-observable behavior: ```typescript // ✅ Good: One behavior it('should disable button when loading', () => { render(