From 009744b71a297fa30b8fa15bbf453da903648657 Mon Sep 17 00:00:00 2001 From: CodingOnStar Date: Wed, 29 Oct 2025 17:48:48 +0800 Subject: [PATCH] docs: update frontend testing guidelines and add comprehensive testing documentation in TESTING.md --- .cursorrules | 226 ++++----------- CONTRIBUTING.md | 2 + web/README.md | 22 +- web/scripts/README.md | 51 ++-- web/scripts/TESTING.md | 459 +++++++++++++++++++++++++++++++ web/scripts/analyze-component.js | 46 ++-- 6 files changed, 591 insertions(+), 215 deletions(-) create mode 100644 web/scripts/TESTING.md diff --git a/.cursorrules b/.cursorrules index 0529506fc2..ef53fc5d09 100644 --- a/.cursorrules +++ b/.cursorrules @@ -1,193 +1,69 @@ -# Cursor Rules for Dify Project - Test Generation +# Cursor Rules for Dify Project -## When Generating Tests +## Frontend Testing Guidelines -Follow these rules when asked to generate tests for React components: +> **๐Ÿ“– Complete Testing Documentation**: For detailed testing specifications and guidelines, see [`web/scripts/TESTING.md`](./web/scripts/TESTING.md) -### Tech Stack -- Next.js 15 + React 19 + TypeScript -- Jest 29.7 + React Testing Library 16.0 -- Test environment: @happy-dom/jest-environment -- File naming: `ComponentName.spec.tsx` (same directory) +When generating tests for React components: -### Component Complexity Guidelines +### Key Requirements -#### ๐Ÿ”ด Very Complex Components (Complexity > 50) -- **Split before testing**: Break component into smaller pieces -- **Integration tests**: Test complex workflows end-to-end -- **Data-driven tests**: Use `test.each()` for multiple scenarios -- **Performance benchmarks**: Add performance tests for critical paths +1. **Tech Stack**: Next.js 15 + React 19 + TypeScript + Jest + React Testing Library +2. **File Naming**: `ComponentName.spec.tsx` (same directory as component) +3. **Code Style**: + - Use `fireEvent` (not `userEvent`) + - AAA pattern (Arrange โ†’ Act โ†’ Assert) + - Test names: `"should [behavior] when [condition]"` + - No `any` types + - Cleanup after each test: `afterEach(() => jest.clearAllMocks())` -#### โš ๏ธ Complex Components (Complexity 30-50) -- **Multiple describe blocks**: Group related test cases -- **Integration scenarios**: Test feature combinations -- **Organized structure**: Keep tests maintainable +### Required Tests (All Components) -#### ๐Ÿ“ Large Components (500+ lines) -- **Consider refactoring**: Split into smaller components if possible -- **Section testing**: Test major sections separately -- **Helper functions**: Reduce test complexity with utilities - -### Test Scenarios - -Apply based on component features: - -#### 1. Rendering Tests (REQUIRED for all) -```typescript -describe('Rendering', () => { - it('should render without crashing', () => { - render() - expect(screen.getByRole('...')).toBeInTheDocument() - }) -}) -``` - -#### 2. Props Testing (REQUIRED for all) -- Test each prop variation -- Test required vs optional props -- Test default values -- Test prop type validation - -#### 3. State Management - -**State Only (useState):** -- Test initial state values -- Test all state transitions -- Test state reset/cleanup scenarios - -**Effects Only (useEffect):** -- Test effect execution conditions -- Verify dependencies array correctness -- Test cleanup on unmount - -**State + Effects Combined:** -- Test state initialization and updates -- Test useEffect dependencies array -- Test cleanup functions (return from useEffect) -- Use `waitFor()` for async state changes - -#### 4. Performance Optimization - -**useCallback:** -- Verify callbacks maintain referential equality -- Test callback dependencies -- Ensure re-renders don't recreate functions unnecessarily - -**useMemo:** -- Test memoization dependencies -- Ensure expensive computations are cached -- Verify memo recomputation conditions - -#### 5. Event Handlers -- Test all onClick, onChange, onSubmit handlers -- Test keyboard events (Enter, Escape, Tab, etc.) -- Verify event.preventDefault() calls if needed -- Test event bubbling/propagation -- Use `fireEvent` (not userEvent) - -#### 6. API Calls & Async Operations -- Mock all API calls using `jest.mock` -- Test retry logic if applicable -- Verify error handling and user feedback -- Use `waitFor()` for async operations - -#### 7. Next.js Routing -- Mock useRouter, usePathname, useSearchParams -- Test navigation behavior and parameters -- Test query string handling -- Verify route guards/redirects if any -- Test URL parameter updates - -#### 8. Edge Cases (REQUIRED for all) -- Test null/undefined/empty values -- Test boundary conditions -- Test error states -- Test loading states -- Test unexpected inputs - -#### 9. Accessibility (Optional) -- Test keyboard navigation -- Verify ARIA attributes -- Test focus management -- Ensure screen reader compatibility - -#### 10. Snapshots (use sparingly) -- Only for stable UI (icons, badges, static layouts) -- Snapshot small sections only -- Prefer explicit assertions over snapshots -- Update snapshots intentionally, not automatically - -**Note**: Dify is a desktop app. NO responsive/mobile testing. - -### Code Style - -- Use `fireEvent` not `userEvent` -- AAA pattern: Arrange โ†’ Act โ†’ Assert -- Descriptive test names: "should [behavior] when [condition]" -- TypeScript: No `any` types -- Cleanup: `afterEach(() => jest.clearAllMocks())` - -### Dify-Specific Components - -#### General -1. **i18n**: Always return key: `t: (key) => key` -2. **Toast**: Mock `@/app/components/base/toast` -3. **Forms**: Test validation thoroughly - -#### Workflow Components (`workflow/`) -- **Node configuration**: Test all node configuration options -- **Data validation**: Verify input/output validation rules -- **Variable passing**: Test data flow between nodes -- **Edge connections**: Test graph structure and connections -- **Error handling**: Verify handling of invalid configurations -- **Integration**: Test complete workflow execution paths - -#### Dataset Components (`dataset/`) -- **File upload**: Test file upload and validation -- **File types**: Verify supported format handling -- **Pagination**: Test data loading and pagination -- **Search & filtering**: Test query functionality -- **Data format handling**: Test various data formats -- **Error states**: Test upload failures and invalid data - -#### Configuration Components (`app/configuration`, `config/`) -- **Form validation**: Test all validation rules thoroughly -- **Save/reset functionality**: Test data persistence -- **Required vs optional fields**: Verify field validation -- **Configuration persistence**: Test state preservation -- **Error feedback**: Verify user error messages -- **Default values**: Test initial configuration state - -### Test Strategy Quick Reference - -**Always Test:** -- โœ… Rendering without crashing +- โœ… Renders without crashing - โœ… Props (required, optional, defaults) -- โœ… Edge cases (null, undefined, empty) +- โœ… Edge cases (null, undefined, empty values) + +### Conditional Tests (When Present in Component) -**Test When Present:** - ๐Ÿ”„ **useState** โ†’ State initialization, transitions, cleanup - โšก **useEffect** โ†’ Execution, dependencies, cleanup -- ๐ŸŽฏ **Event handlers** โ†’ All onClick, onChange, onSubmit, keyboard events -- ๐ŸŒ **API calls** โ†’ Loading, success, error states -- ๐Ÿ”€ **Routing** โ†’ Navigation, params, query strings +- ๐ŸŽฏ **Event Handlers** โ†’ onClick, onChange, onSubmit, keyboard events +- ๐ŸŒ **API Calls** โ†’ Loading, success, error states +- ๐Ÿ”€ **Next.js Routing** โ†’ Navigation, params, query strings - ๐Ÿš€ **useCallback/useMemo** โ†’ Referential equality, dependencies -- โš™๏ธ **Workflow** โ†’ Node config, data flow, validation -- ๐Ÿ“š **Dataset** โ†’ Upload, pagination, search -- ๐ŸŽ›๏ธ **Configuration** โ†’ Form validation, persistence +- โš™๏ธ **Workflow Components** โ†’ Node config, data flow, validation +- ๐Ÿ“š **Dataset Components** โ†’ Upload, pagination, search +- ๐ŸŽ›๏ธ **Configuration Components** โ†’ Form validation, persistence -**Complex Components (30+):** -- Group tests in multiple `describe` blocks -- Test integration scenarios -- Consider splitting component before testing +### Component Complexity Strategy -### Coverage Target +- **Complexity > 50**: Refactor before testing, use integration tests and `test.each()` +- **Complexity 30-50**: Use multiple `describe` blocks to group tests +- **500+ lines**: Consider splitting component + +### Coverage Goals Aim for 100% coverage: -- Line: >95% -- Branch: >95% -- Function: 100% -- Statement: 100% +- Line coverage: >95% +- Branch coverage: >95% +- Function coverage: 100% +- Statement coverage: 100% -Generate comprehensive tests covering ALL code paths and scenarios. +### Dify-Specific Mocks +```typescript + +// Toast +jest.mock('@/app/components/base/toast') + +// Next.js Router +jest.mock('next/navigation', () => ({ + useRouter: jest.fn(), + usePathname: jest.fn(), + useSearchParams: jest.fn(), +})) +``` + +--- + +**Important**: The above is a quick reference only. When generating tests, strictly follow the complete specifications in [`web/scripts/TESTING.md`](./web/scripts/TESTING.md), including detailed examples, best practices, and FAQs. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fdc414b047..570bd15bc3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -77,6 +77,8 @@ How we prioritize: For setting up the frontend service, please refer to our comprehensive [guide](https://github.com/langgenius/dify/blob/main/web/README.md) in the `web/README.md` file. This document provides detailed instructions to help you set up the frontend environment properly. +**Testing**: All React components must have comprehensive test coverage. See [web/scripts/TESTING.md](https://github.com/langgenius/dify/blob/main/web/scripts/TESTING.md) for detailed testing guidelines. + #### Backend For setting up the backend service, kindly refer to our detailed [instructions](https://github.com/langgenius/dify/blob/main/api/README.md) in the `api/README.md` file. This document contains step-by-step guidance to help you get the backend up and running smoothly. diff --git a/web/README.md b/web/README.md index a47cfab041..2372fc8b6f 100644 --- a/web/README.md +++ b/web/README.md @@ -93,20 +93,26 @@ If your IDE is VSCode, rename `web/.vscode/settings.example.json` to `web/.vscod ## Test -We start to use [Jest](https://jestjs.io/) and [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) for Unit Testing. +We use [Jest](https://jestjs.io/) and [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) for Unit Testing. -You can create a test file with a suffix of `.spec` beside the file that to be tested. For example, if you want to test a file named `util.ts`. The test file name should be `util.spec.ts`. +**๐Ÿ“– Complete Testing Guide**: See [scripts/TESTING.md](./scripts/TESTING.md) for detailed testing specifications, best practices, and examples. -Run test: +### Example Code + +If you are not familiar with writing tests, refer to: + +- [classnames.spec.ts](./utils/classnames.spec.ts) - Utility function test example +- [index.spec.tsx](./app/components/base/button/index.spec.tsx) - Component test example + +### Analyze Component Complexity + +Before writing tests, use the script to analyze component complexity: ```bash -pnpm run test +pnpm analyze-component app/components/your-component/index.tsx ``` -If you are not familiar with writing tests, here is some code to refer to: - -- [classnames.spec.ts](./utils/classnames.spec.ts) -- [index.spec.tsx](./app/components/base/button/index.spec.tsx) +This will help you determine the testing strategy. See [scripts/TESTING.md](./scripts/TESTING.md) for details. ## Documentation diff --git a/web/scripts/README.md b/web/scripts/README.md index bfd733c07b..6f75fe9314 100644 --- a/web/scripts/README.md +++ b/web/scripts/README.md @@ -10,7 +10,7 @@ Frontend development utility scripts. --- -## ๐Ÿš€ Generate Tests (Using Cursor AI) +## ๐Ÿš€ Generate Tests (Using AI Assistants) ### Quick Start @@ -18,11 +18,14 @@ Frontend development utility scripts. # 1. Analyze component pnpm test:gen app/components/base/button/index.tsx -# Output: Component analysis + Cursor prompt (auto-copied) +# Output: Component analysis + AI prompt (auto-copied to clipboard) -# 2. In Cursor: Cmd+L โ†’ Cmd+V โ†’ Enter โ†’ Apply +# 2. Paste in your AI assistant: +# - Cursor: Cmd+L (Chat) or Cmd+I (Composer) โ†’ Cmd+V โ†’ Enter +# - GitHub Copilot Chat: Cmd+I โ†’ Cmd+V โ†’ Enter +# - Claude/ChatGPT: Paste the prompt directly -# 3. Verify +# 3. Apply the generated test and verify pnpm test app/components/base/button/index.spec.tsx ``` @@ -37,19 +40,19 @@ pnpm test app/components/base/button/index.spec.tsx Script analyzes and scores components: - **0-10**: ๐ŸŸข Simple (5-10 min to test) -- **11-30Menu ๐ŸŸก Medium (15-30 min to test) -- **31-50Menu ๐ŸŸ  Complex (30-60 min to test) +- **11-30**: ๐ŸŸก Medium (15-30 min to test) +- **31-50**: ๐ŸŸ  Complex (30-60 min to test) - **51+**: ๐Ÿ”ด Very Complex (60+ min, consider refactoring) -### Test Scenarios (11 types) +### Test Scenarios -Defined in `.cursorrules`: +Defined in `TESTING.md`: **Must test**: Rendering, Props, Edge Cases -**CommonMenuInteractions, Accessibility, i18n, Async -**OptionalMenuState, Security, Performance, Snapshots +**Conditional**: State, Effects, Events, API calls, Routing +**Optional**: Accessibility, Performance, Snapshots -Cursor AI auto-selects scenarios based on component features. +AI assistant auto-selects scenarios based on component features. --- @@ -58,10 +61,12 @@ Cursor AI auto-selects scenarios based on component features. ```bash # New component pnpm test:gen app/components/new-feature/index.tsx -# โ†’ Cursor โ†’ Apply โ†’ Done +# โ†’ Paste in AI assistant โ†’ Apply โ†’ Done -# Or even simpler in Cursor -# Cmd+I โ†’ "Generate test" โ†’ Apply +# Quick shortcuts: +# Cursor users: Cmd+I โ†’ "Generate test for [file]" โ†’ Apply +# Copilot users: Cmd+I โ†’ Paste prompt โ†’ Accept +# Others: Copy prompt โ†’ Paste in your AI tool ``` --- @@ -80,10 +85,24 @@ pnpm type-check # Type check ## ๐ŸŽฏ Customize -Edit `.cursorrules` to modify test standards for your team. +Edit testing standards for your team: ```bash +# Complete testing guide (for all team members) +code web/scripts/TESTING.md + +# Quick reference for Cursor users code .cursorrules -git commit -m "docs: update test rules" + +# Commit your changes +git commit -m "docs: update test standards" ``` +--- + +## ๐Ÿ“š Resources + +- **Testing Guide**: [TESTING.md](./TESTING.md) - Complete testing specifications +- **Quick Reference**: [.cursorrules](../../.cursorrules) - For Cursor users +- **Examples**: [classnames.spec.ts](../utils/classnames.spec.ts), [button/index.spec.tsx](../app/components/base/button/index.spec.tsx) + diff --git a/web/scripts/TESTING.md b/web/scripts/TESTING.md new file mode 100644 index 0000000000..3f182c485a --- /dev/null +++ b/web/scripts/TESTING.md @@ -0,0 +1,459 @@ +# Frontend Testing Guide + +This document is the complete testing specification for the Dify frontend project. + +## Tech Stack + +- **Framework**: Next.js 15 + React 19 + TypeScript +- **Testing Tools**: Jest 29.7 + React Testing Library 16.0 +- **Test Environment**: @happy-dom/jest-environment +- **File Naming**: `ComponentName.spec.tsx` (same directory as component) + +## Running Tests + +```bash +# Run all tests +pnpm test + +# Watch mode +pnpm test -- --watch + +# Generate coverage report +pnpm test -- --coverage + +# Run specific file +pnpm test -- path/to/file.spec.tsx +``` + +## Component Complexity Guidelines + +Use `pnpm analyze-component ` to analyze component complexity and adopt different testing strategies based on the results. + +### ๐Ÿ”ด Very Complex Components (Complexity > 50) + +- **Refactor first**: Break component into smaller pieces +- **Integration tests**: Test complex workflows end-to-end +- **Data-driven tests**: Use `test.each()` for multiple scenarios +- **Performance benchmarks**: Add performance tests for critical paths + +### โš ๏ธ Complex Components (Complexity 30-50) + +- **Multiple describe blocks**: Group related test cases +- **Integration scenarios**: Test feature combinations +- **Organized structure**: Keep tests maintainable + +### ๐Ÿ“ Large Components (500+ lines) + +- **Consider refactoring**: Split into smaller components if possible +- **Section testing**: Test major sections separately +- **Helper functions**: Reduce test complexity with utilities + +## Test Scenarios + +Apply the following test scenarios based on component features: + +### 1. Rendering Tests (REQUIRED - All Components) + +```typescript +describe('Rendering', () => { + it('should render without crashing', () => { + render() + expect(screen.getByRole('...')).toBeInTheDocument() + }) +}) +``` + +**Key Points**: +- Verify component renders properly +- Check key elements exist +- Use semantic queries (getByRole, getByLabelText) + +### 2. Props Testing (REQUIRED - All Components) + +**Must Test**: +- โœ… Different values for each prop +- โœ… Required vs optional props +- โœ… Default values +- โœ… Props type validation + +### 3. State Management + +#### useState + +When testing state-related components: +- โœ… Test initial state values +- โœ… Test all state transitions +- โœ… Test state reset/cleanup scenarios + +#### useEffect + +When testing side effects: +- โœ… Test effect execution conditions +- โœ… Verify dependencies array correctness +- โœ… Test cleanup function on unmount + +#### useState + useEffect Combined + +- โœ… Test state initialization and updates +- โœ… Test useEffect dependencies array +- โœ… Test cleanup functions (useEffect return value) +- โœ… Use `waitFor()` for async state changes + +### 4. Performance Optimization + +#### useCallback + +- โœ… Verify callbacks maintain referential equality +- โœ… Test callback dependencies +- โœ… Ensure re-renders don't recreate functions unnecessarily + +#### useMemo + +- โœ… Test memoization dependencies +- โœ… Ensure expensive computations are cached +- โœ… Verify memo recomputation conditions + +### 5. Event Handlers + +**Must Test**: +- โœ… All onClick, onChange, onSubmit handlers +- โœ… Keyboard events (Enter, Escape, Tab, etc.) +- โœ… Verify event.preventDefault() calls (if needed) +- โœ… Test event bubbling/propagation + +**Note**: Use `fireEvent` (not `userEvent`) + +### 6. API Calls and Async Operations + +**Must Test**: +- โœ… Mock all API calls using `jest.mock` +- โœ… Test retry logic (if applicable) +- โœ… Verify error handling and user feedback +- โœ… Use `waitFor()` for async operations + +### 7. Next.js Routing + +**Must Test**: +- โœ… Mock useRouter, usePathname, useSearchParams +- โœ… Test navigation behavior and parameters +- โœ… Test query string handling +- โœ… Verify route guards/redirects (if any) +- โœ… Test URL parameter updates + +### 8. Edge Cases (REQUIRED - All Components) + +**Must Test**: +- โœ… null/undefined/empty values +- โœ… Boundary conditions +- โœ… Error states +- โœ… Loading states +- โœ… Unexpected inputs + +### 9. Accessibility Testing (Optional) + +- Test keyboard navigation +- Verify ARIA attributes +- Test focus management +- Ensure screen reader compatibility + +### 10. Snapshot Testing (Use Sparingly) + +**Only Use For**: +- โœ… Stable UI (icons, badges, static layouts) +- โœ… Snapshot small sections only +- โœ… Prefer explicit assertions over snapshots +- โœ… Update snapshots intentionally, not automatically + +**Note**: Dify is a desktop application. **No need for** responsive/mobile testing. + +## Code Style + +### Basic Guidelines + +- โœ… Use `fireEvent` instead of `userEvent` +- โœ… AAA pattern: Arrange (setup) โ†’ Act (execute) โ†’ Assert (verify) +- โœ… Descriptive test names: `"should [behavior] when [condition]"` +- โœ… TypeScript: No `any` types +- โœ… Cleanup: `afterEach(() => jest.clearAllMocks())` + +### Example Structure + +```typescript +import { render, screen, fireEvent, waitFor } from '@testing-library/react' +import Component from './index' + +// Mock dependencies +jest.mock('@/service/api') + +describe('ComponentName', () => { + // Cleanup after each test + afterEach(() => { + jest.clearAllMocks() + }) + + describe('Rendering', () => { + it('should render without crashing', () => { + // Arrange + const props = { title: 'Test' } + + // Act + render() + + // Assert + expect(screen.getByText('Test')).toBeInTheDocument() + }) + }) + + describe('User Interactions', () => { + it('should handle click events', () => { + const handleClick = jest.fn() + render() + + fireEvent.click(screen.getByRole('button')) + + expect(handleClick).toHaveBeenCalledTimes(1) + }) + }) + + describe('Edge Cases', () => { + it('should handle null data', () => { + render() + expect(screen.getByText(/no data/i)).toBeInTheDocument() + }) + }) +}) +``` + +## Dify-Specific Components + +### General + +1. **i18n**: Always return key + ```typescript + jest.mock('react-i18next', () => ({ + useTranslation: () => ({ + t: (key: string) => key, + }), + })) + ``` + +2. **Toast**: Mock toast component + ```typescript + jest.mock('@/app/components/base/toast', () => ({ + notify: jest.fn(), + })) + ``` + +3. **Forms**: Test validation logic thoroughly + +### Workflow Components (`workflow/`) + +**Must Test**: +- โš™๏ธ **Node configuration**: Test all node configuration options +- โœ”๏ธ **Data validation**: Verify input/output validation rules +- ๐Ÿ”„ **Variable passing**: Test data flow between nodes +- ๐Ÿ”— **Edge connections**: Test graph structure and connections +- โŒ **Error handling**: Verify invalid configuration handling +- ๐Ÿงช **Integration**: Test complete workflow execution paths + +### Dataset Components (`dataset/`) + +**Must Test**: +- ๐Ÿ“ค **File upload**: Test file upload and validation +- ๐Ÿ“„ **File types**: Verify supported format handling +- ๐Ÿ“ƒ **Pagination**: Test data loading and pagination +- ๐Ÿ” **Search & filtering**: Test query functionality +- ๐Ÿ“Š **Data format handling**: Test various data formats +- โš ๏ธ **Error states**: Test upload failures and invalid data + +### Configuration Components (`app/configuration`, `config/`) + +**Must Test**: +- โœ… **Form validation**: Test all validation rules thoroughly +- ๐Ÿ’พ **Save/reset functionality**: Test data persistence +- ๐Ÿ”’ **Required vs optional fields**: Verify field validation +- ๐Ÿ“Œ **Configuration persistence**: Test state preservation +- ๐Ÿ’ฌ **Error feedback**: Verify user error messages +- ๐ŸŽฏ **Default values**: Test initial configuration state + +## Testing Strategy Quick Reference + +### Required (All Components) + +- โœ… Renders without crashing +- โœ… Props (required, optional, defaults) +- โœ… Edge cases (null, undefined, empty values) + +### Conditional (When Present in Component) + +- ๐Ÿ”„ **useState** โ†’ State initialization, transitions, cleanup +- โšก **useEffect** โ†’ Execution, dependencies, cleanup +- ๐ŸŽฏ **Event Handlers** โ†’ All onClick, onChange, onSubmit, keyboard events +- ๐ŸŒ **API Calls** โ†’ Loading, success, error states +- ๐Ÿ”€ **Routing** โ†’ Navigation, params, query strings +- ๐Ÿš€ **useCallback/useMemo** โ†’ Referential equality, dependencies +- โš™๏ธ **Workflow** โ†’ Node config, data flow, validation +- ๐Ÿ“š **Dataset** โ†’ Upload, pagination, search +- ๐ŸŽ›๏ธ **Configuration** โ†’ Form validation, persistence + +### Complex Components (Complexity 30+) + +- Group tests in multiple `describe` blocks +- Test integration scenarios +- Consider splitting component before testing + +## Coverage Goals + +Aim for 100% coverage: +- **Line coverage**: >95% +- **Branch coverage**: >95% +- **Function coverage**: 100% +- **Statement coverage**: 100% + +Generate comprehensive tests covering **all** code paths and scenarios. + +## Common Mock Patterns + +### Mock Hooks + +```typescript +// Mock useState +const mockSetState = jest.fn() +jest.spyOn(React, 'useState').mockImplementation((init) => [init, mockSetState]) + +// Mock useContext +jest.spyOn(React, 'useContext').mockReturnValue({ user: mockUser }) +``` + +### Mock Modules + +```typescript +// Mock entire module +jest.mock('@/utils/api', () => ({ + get: jest.fn(), + post: jest.fn(), +})) + +// Mock partial module +jest.mock('@/utils/helpers', () => ({ + ...jest.requireActual('@/utils/helpers'), + specificFunction: jest.fn(), +})) +``` + +### Mock Next.js + +```typescript +// useRouter +jest.mock('next/navigation', () => ({ + useRouter: () => ({ + push: jest.fn(), + replace: jest.fn(), + prefetch: jest.fn(), + }), + usePathname: () => '/current-path', + useSearchParams: () => new URLSearchParams(), +})) + +// next/image +jest.mock('next/image', () => ({ + __esModule: true, + default: (props: any) => , +})) +``` + +## Debugging Tips + +### View Rendered DOM + +```typescript +import { screen } from '@testing-library/react' + +// Print entire DOM +screen.debug() + +// Print specific element +screen.debug(screen.getByRole('button')) +``` + +### Finding Elements + +Priority order (recommended top to bottom): +1. `getByRole` - Most recommended, follows accessibility standards +2. `getByLabelText` - Form fields +3. `getByPlaceholderText` - Only when no label +4. `getByText` - Non-interactive elements +5. `getByDisplayValue` - Current form value +6. `getByAltText` - Images +7. `getByTitle` - Last choice +8. `getByTestId` - Only as last resort + +### Async Debugging + +```typescript +// Wait for element to appear +await waitFor(() => { + expect(screen.getByText('Loaded')).toBeInTheDocument() +}) + +// Wait for element to disappear +await waitFor(() => { + expect(screen.queryByText('Loading')).not.toBeInTheDocument() +}) + +// Find async element +const element = await screen.findByText('Async Content') +``` + +## Reference Examples + +Test examples in the project: +- [classnames.spec.ts](../utils/classnames.spec.ts) - Utility function tests +- [index.spec.tsx](../app/components/base/button/index.spec.tsx) - Component tests + +## Resources + +- [Jest Documentation](https://jestjs.io/docs/getting-started) +- [React Testing Library Documentation](https://testing-library.com/docs/react-testing-library/intro/) +- [Testing Library Best Practices](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library) +- [Jest Mock Functions](https://jestjs.io/docs/mock-functions) + +## FAQ + +### Q: When to use `getBy` vs `queryBy` vs `findBy`? + +- `getBy*`: Expect element to exist, throws error if not found +- `queryBy*`: Element may not exist, returns null if not found +- `findBy*`: Async query, returns Promise + +### Q: How to test conditional rendering? + +```typescript +it('should conditionally render content', () => { + const { rerender } = render() + expect(screen.queryByText('Content')).not.toBeInTheDocument() + + rerender() + expect(screen.getByText('Content')).toBeInTheDocument() +}) +``` + +### Q: How to test custom hooks? + +```typescript +import { renderHook, act } from '@testing-library/react' + +it('should update counter', () => { + const { result } = renderHook(() => useCounter()) + + act(() => { + result.current.increment() + }) + + expect(result.current.count).toBe(1) +}) +``` + +--- + +**Remember**: Writing tests is not just about coverage, but ensuring code quality and maintainability. Good tests should be clear, concise, and meaningful. diff --git a/web/scripts/analyze-component.js b/web/scripts/analyze-component.js index 49189c8093..83d046ce7a 100755 --- a/web/scripts/analyze-component.js +++ b/web/scripts/analyze-component.js @@ -1,9 +1,9 @@ #!/usr/bin/env node /** - * Component Analyzer for Cursor AI + * Component Analyzer for Test Generation * - * Analyzes a component and generates a structured prompt for Cursor AI. - * Copy the output and paste into Cursor Chat (Cmd+L) or Composer (Cmd+I). + * Analyzes a component and generates a structured prompt for AI assistants. + * Works with Cursor, GitHub Copilot, and other AI coding tools. * * Usage: * node scripts/analyze-component.js @@ -11,6 +11,8 @@ * Examples: * node scripts/analyze-component.js app/components/base/button/index.tsx * node scripts/analyze-component.js app/components/workflow/nodes/llm/panel.tsx + * + * For complete testing guidelines, see: web/scripts/TESTING.md */ const fs = require('node:fs') @@ -272,16 +274,16 @@ class ComponentAnalyzer { } // ============================================================================ -// Prompt Builder for Cursor +// Prompt Builder for AI Assistants // ============================================================================ -class CursorPromptBuilder { +class TestPromptBuilder { build(analysis, _sourceCode) { const testPath = analysis.path.replace(/\.tsx?$/, '.spec.tsx') return ` โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•— -โ•‘ ๐Ÿ“‹ GENERATE TEST FOR DIFY COMPONENT โ•‘ +โ•‘ ๐Ÿ“‹ GENERATE TEST FOR DIFY COMPONENT โ•‘ โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ• ๐Ÿ“ Component: ${analysis.name} @@ -307,7 +309,7 @@ Features Detected: ${analysis.hasAPI ? 'โœ“' : 'โœ—'} API calls โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” -๐Ÿ“ TASK FOR CURSOR AI: +๐Ÿ“ TASK: โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” Please generate a comprehensive test file for this component at: @@ -316,16 +318,18 @@ Please generate a comprehensive test file for this component at: The component is located at: ${analysis.path} -Follow the testing guidelines in .cursorrules file. +Follow the testing guidelines in: + - web/scripts/TESTING.md (complete testing guide) + - .cursorrules (quick reference for Cursor users) ${this.getSpecificGuidelines(analysis)} โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” -๐Ÿ“‹ COPY THIS TO CURSOR: +๐Ÿ“‹ PROMPT FOR AI ASSISTANT (COPY THIS TO YOUR AI ASSISTANT): โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ” -Generate a comprehensive test file for @${analysis.path} following the project's testing guidelines in .cursorrules. +Generate a comprehensive test file for @${analysis.path} following the project's testing guidelines in web/scripts/TESTING.md. Including but not limited to: ${this.buildFocusPoints(analysis)} @@ -514,8 +518,13 @@ Examples: node scripts/analyze-component.js app/components/base/button/index.tsx node scripts/analyze-component.js app/components/workflow/nodes/llm/panel.tsx -This tool analyzes your component and generates a prompt for Cursor AI. -Copy the output and use it in Cursor Chat (Cmd+L) or Composer (Cmd+I). +This tool analyzes your component and generates a prompt for AI assistants. +Copy the output and use it with: + - Cursor (Cmd+L for Chat, Cmd+I for Composer) + - GitHub Copilot Chat (Cmd+I) + - Claude, ChatGPT, or any other AI coding tool + +For complete testing guidelines, see: web/scripts/TESTING.md `) process.exit(1) } @@ -587,8 +596,8 @@ This component is too complex to test effectively. Please consider: process.exit(0) } - // Build prompt for Cursor - const builder = new CursorPromptBuilder() + // Build prompt for AI assistant + const builder = new TestPromptBuilder() const prompt = builder.build(analysis, sourceCode) // Output @@ -619,8 +628,13 @@ This component is too complex to test effectively. Please consider: encoding: 'utf-8', }) - if (result.status === 0) - console.log('\n๐Ÿ“‹ Prompt copied to clipboard! Paste it in Cursor Chat (Cmd+L).\n') + if (result.status === 0) { + console.log('\n๐Ÿ“‹ Prompt copied to clipboard!') + console.log(' Paste it in your AI assistant:') + console.log(' - Cursor: Cmd+L (Chat) or Cmd+I (Composer)') + console.log(' - GitHub Copilot Chat: Cmd+I') + console.log(' - Or any other AI coding tool\n') + } } catch { // pbcopy failed, but don't break the script