Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…

: 'babel-jest',\n },\n\n // Module name mapper for static assets and CSS\n moduleNameMapper: {\n '\\\\.(css|less|scss|sass)

Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…

: 'identity-obj-proxy',\n '\\\\.(jpg|jpeg|png|gif|svg)

Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…

: '\u003crootDir>/__mocks__/fileMock.js',\n },\n\n // Coverage configuration\n collectCoverageFrom: [\n 'src/**/*.{js,jsx}',\n '!src/index.js',\n '!src/**/*.test.{js,jsx}',\n '!src/**/__tests__/**',\n ],\n\n // Coverage thresholds\n coverageThreshold: {\n global: {\n branches: 80,\n functions: 80,\n lines: 80,\n statements: 80,\n },\n },\n};\n\nmodule.exports = config;\n```\n\n**jest.config.js** (TypeScript projects):\n```typescript\nimport type {Config} from 'jest';\n\nconst config: Config = {\n preset: 'ts-jest',\n testEnvironment: 'jsdom',\n setupFilesAfterEnv: ['\u003crootDir>/src/setupTests.ts'],\n\n moduleDirectories: ['node_modules', 'src'],\n\n transform: {\n '^.+\\\\.tsx?

Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…

: 'ts-jest',\n },\n\n moduleNameMapper: {\n '\\\\.(css|less|scss|sass)

Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…

: 'identity-obj-proxy',\n '\\\\.(jpg|jpeg|png|gif|svg)

Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…

: '\u003crootDir>/__mocks__/fileMock.ts',\n '^@/(.*)

Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…

: '\u003crootDir>/src/$1',\n },\n\n collectCoverageFrom: [\n 'src/**/*.{ts,tsx}',\n '!src/index.tsx',\n '!src/**/*.test.{ts,tsx}',\n '!src/**/__tests__/**',\n '!src/**/*.d.ts',\n ],\n\n coverageThreshold: {\n global: {\n branches: 80,\n functions: 80,\n lines: 80,\n statements: 80,\n },\n },\n};\n\nexport default config;\n```\n\n### Setup Files\n\n**src/setupTests.js**:\n```javascript\n// Add custom jest matchers from jest-dom\nimport '@testing-library/jest-dom';\n\n// Extend expect with jest-extended matchers (optional)\nimport * as matchers from 'jest-extended';\nexpect.extend(matchers);\n\n// Mock window.matchMedia\nObject.defineProperty(window, 'matchMedia', {\n writable: true,\n value: jest.fn().mockImplementation(query => ({\n matches: false,\n media: query,\n onchange: null,\n addListener: jest.fn(),\n removeListener: jest.fn(),\n addEventListener: jest.fn(),\n removeEventListener: jest.fn(),\n dispatchEvent: jest.fn(),\n })),\n});\n\n// Mock IntersectionObserver\nglobal.IntersectionObserver = class IntersectionObserver {\n constructor() {}\n disconnect() {}\n observe() {}\n takeRecords() {\n return [];\n }\n unobserve() {}\n};\n\n// Suppress console errors in tests (optional)\nconst originalError = console.error;\nbeforeAll(() => {\n console.error = (...args) => {\n if (\n typeof args[0] === 'string' &&\n args[0].includes('Warning: ReactDOM.render')\n ) {\n return;\n }\n originalError.call(console, ...args);\n };\n});\n\nafterAll(() => {\n console.error = originalError;\n});\n\n// Reset mocks after each test\nafterEach(() => {\n jest.clearAllMocks();\n});\n```\n\n### File Mocks\n\n**__mocks__/fileMock.js**:\n```javascript\nmodule.exports = 'test-file-stub';\n```\n\n**__mocks__/styleMock.js**:\n```javascript\nmodule.exports = {};\n```\n\n## React Testing Library Queries\n\n### Query Types\n\nReact Testing Library provides three types of queries:\n\n1. **getBy**: Returns element or throws error (use for elements that should exist)\n2. **queryBy**: Returns element or null (use for elements that may not exist)\n3. **findBy**: Returns promise that resolves to element (use for async elements)\n\n### Query Priority\n\n**Recommended Query Order** (accessibility-focused):\n\n1. **getByRole**: Most accessible query\n ```javascript\n getByRole('button', { name: /submit/i })\n getByRole('heading', { level: 1 })\n getByRole('textbox', { name: /username/i })\n ```\n\n2. **getByLabelText**: For form fields with labels\n ```javascript\n getByLabelText(/email address/i)\n getByLabelText('Password')\n ```\n\n3. **getByPlaceholderText**: For inputs with placeholders\n ```javascript\n getByPlaceholderText(/search/i)\n ```\n\n4. **getByText**: For non-interactive elements with text\n ```javascript\n getByText(/welcome/i)\n getByText('Error: Invalid credentials')\n ```\n\n5. **getByDisplayValue**: For form elements with values\n ```javascript\n getByDisplayValue('John Doe')\n ```\n\n6. **getByAltText**: For images with alt text\n ```javascript\n getByAltText(/profile picture/i)\n ```\n\n7. **getByTitle**: For elements with title attribute\n ```javascript\n getByTitle(/close/i)\n ```\n\n8. **getByTestId**: Last resort when other queries don't work\n ```javascript\n getByTestId('custom-element')\n ```\n\n### Query Variants\n\n```javascript\n// Single element queries\nscreen.getByRole('button') // Throws if not found or multiple found\nscreen.queryByRole('button') // Returns null if not found\nawait screen.findByRole('button') // Async, waits up to 1000ms\n\n// Multiple element queries\nscreen.getAllByRole('listitem') // Throws if none found\nscreen.queryAllByRole('listitem') // Returns [] if none found\nawait screen.findAllByRole('listitem') // Async version\n```\n\n## Component Testing Strategies\n\n### Basic Component Test\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport { Greeting } from './Greeting';\n\ndescribe('Greeting Component', () => {\n it('renders greeting message', () => {\n render(\u003cGreeting name=\"Alice\" />);\n\n expect(screen.getByText(/hello, alice/i)).toBeInTheDocument();\n });\n\n it('renders default greeting when no name provided', () => {\n render(\u003cGreeting />);\n\n expect(screen.getByText(/hello, guest/i)).toBeInTheDocument();\n });\n});\n```\n\n### Testing User Interactions\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { Counter } from './Counter';\n\ndescribe('Counter Component', () => {\n it('increments counter on button click', async () => {\n const user = userEvent.setup();\n render(\u003cCounter />);\n\n const button = screen.getByRole('button', { name: /increment/i });\n const count = screen.getByText(/count: 0/i);\n\n expect(count).toBeInTheDocument();\n\n await user.click(button);\n\n expect(screen.getByText(/count: 1/i)).toBeInTheDocument();\n });\n\n it('decrements counter on button click', async () => {\n const user = userEvent.setup();\n render(\u003cCounter initialCount={5} />);\n\n const decrementBtn = screen.getByRole('button', { name: /decrement/i });\n\n await user.click(decrementBtn);\n\n expect(screen.getByText(/count: 4/i)).toBeInTheDocument();\n });\n});\n```\n\n### Testing Forms\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { LoginForm } from './LoginForm';\n\ndescribe('LoginForm Component', () => {\n it('submits form with username and password', async () => {\n const user = userEvent.setup();\n const handleSubmit = jest.fn();\n\n render(\u003cLoginForm onSubmit={handleSubmit} />);\n\n const usernameInput = screen.getByLabelText(/username/i);\n const passwordInput = screen.getByLabelText(/password/i);\n const submitButton = screen.getByRole('button', { name: /submit/i });\n\n await user.type(usernameInput, 'testuser');\n await user.type(passwordInput, 'password123');\n await user.click(submitButton);\n\n expect(handleSubmit).toHaveBeenCalledTimes(1);\n expect(handleSubmit).toHaveBeenCalledWith({\n username: 'testuser',\n password: 'password123',\n });\n });\n\n it('shows validation errors for empty fields', async () => {\n const user = userEvent.setup();\n render(\u003cLoginForm onSubmit={jest.fn()} />);\n\n const submitButton = screen.getByRole('button', { name: /submit/i });\n\n await user.click(submitButton);\n\n expect(screen.getByText(/username is required/i)).toBeInTheDocument();\n expect(screen.getByText(/password is required/i)).toBeInTheDocument();\n });\n});\n```\n\n### Testing Conditional Rendering\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport { UserProfile } from './UserProfile';\n\ndescribe('UserProfile Component', () => {\n it('shows loading state when loading', () => {\n render(\u003cUserProfile loading={true} />);\n\n expect(screen.getByText(/loading/i)).toBeInTheDocument();\n expect(screen.queryByRole('heading')).not.toBeInTheDocument();\n });\n\n it('shows user data when loaded', () => {\n const user = {\n name: 'John Doe',\n email: '[email protected]',\n };\n\n render(\u003cUserProfile loading={false} user={user} />);\n\n expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();\n expect(screen.getByRole('heading', { name: /john doe/i })).toBeInTheDocument();\n expect(screen.getByText(/[email protected]/i)).toBeInTheDocument();\n });\n\n it('shows error message when error occurs', () => {\n render(\u003cUserProfile loading={false} error=\"Failed to load user\" />);\n\n expect(screen.getByText(/failed to load user/i)).toBeInTheDocument();\n expect(screen.queryByRole('heading')).not.toBeInTheDocument();\n });\n});\n```\n\n## Mocking Patterns\n\n### Mocking Modules\n\n**Automatic Mock**:\n```javascript\n// __mocks__/axios.js\nexport default {\n get: jest.fn(),\n post: jest.fn(),\n put: jest.fn(),\n delete: jest.fn(),\n};\n```\n\n**Usage in test**:\n```javascript\nimport axios from 'axios';\nimport { UserService } from './UserService';\n\njest.mock('axios');\n\ndescribe('UserService', () => {\n beforeEach(() => {\n jest.clearAllMocks();\n });\n\n it('fetches users successfully', async () => {\n const mockUsers = [{ id: 1, name: 'Alice' }];\n axios.get.mockResolvedValue({ data: mockUsers });\n\n const users = await UserService.getUsers();\n\n expect(axios.get).toHaveBeenCalledWith('/api/users');\n expect(users).toEqual(mockUsers);\n });\n\n it('handles fetch error', async () => {\n axios.get.mockRejectedValue(new Error('Network Error'));\n\n await expect(UserService.getUsers()).rejects.toThrow('Network Error');\n });\n});\n```\n\n### Mocking Functions\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { Button } from './Button';\n\ndescribe('Button Component', () => {\n it('calls onClick handler when clicked', async () => {\n const user = userEvent.setup();\n const handleClick = jest.fn();\n\n render(\u003cButton onClick={handleClick}>Click Me\u003c/Button>);\n\n await user.click(screen.getByRole('button'));\n\n expect(handleClick).toHaveBeenCalledTimes(1);\n });\n\n it('calls onClick with event object', async () => {\n const user = userEvent.setup();\n const handleClick = jest.fn();\n\n render(\u003cButton onClick={handleClick}>Click Me\u003c/Button>);\n\n await user.click(screen.getByRole('button'));\n\n expect(handleClick).toHaveBeenCalledWith(\n expect.objectContaining({\n type: 'click',\n })\n );\n });\n});\n```\n\n### Mocking API Calls with MSW (Mock Service Worker)\n\n**Setup MSW**:\n```javascript\n// src/mocks/handlers.js\nimport { rest } from 'msw';\n\nexport const handlers = [\n rest.get('/api/users', (req, res, ctx) => {\n return res(\n ctx.status(200),\n ctx.json([\n { id: 1, name: 'Alice' },\n { id: 2, name: 'Bob' },\n ])\n );\n }),\n\n rest.post('/api/users', async (req, res, ctx) => {\n const { name, email } = await req.json();\n\n return res(\n ctx.status(201),\n ctx.json({\n id: 3,\n name,\n email,\n })\n );\n }),\n];\n```\n\n**Setup server**:\n```javascript\n// src/mocks/server.js\nimport { setupServer } from 'msw/node';\nimport { handlers } from './handlers';\n\nexport const server = setupServer(...handlers);\n```\n\n**Configure in setupTests.js**:\n```javascript\nimport { server } from './mocks/server';\n\nbeforeAll(() => server.listen());\nafterEach(() => server.resetHandlers());\nafterAll(() => server.close());\n```\n\n**Usage in tests**:\n```javascript\nimport { render, screen, waitFor } from '@testing-library/react';\nimport { server } from './mocks/server';\nimport { rest } from 'msw';\nimport { UserList } from './UserList';\n\ndescribe('UserList Component', () => {\n it('fetches and displays users', async () => {\n render(\u003cUserList />);\n\n expect(screen.getByText(/loading/i)).toBeInTheDocument();\n\n await waitFor(() => {\n expect(screen.getByText(/alice/i)).toBeInTheDocument();\n expect(screen.getByText(/bob/i)).toBeInTheDocument();\n });\n });\n\n it('handles server error', async () => {\n server.use(\n rest.get('/api/users', (req, res, ctx) => {\n return res(\n ctx.status(500),\n ctx.json({ message: 'Internal Server Error' })\n );\n })\n );\n\n render(\u003cUserList />);\n\n await waitFor(() => {\n expect(screen.getByText(/error loading users/i)).toBeInTheDocument();\n });\n });\n});\n```\n\n### Mocking Context\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport { AuthContext } from './AuthContext';\nimport { ProtectedComponent } from './ProtectedComponent';\n\nconst mockAuthContext = (overrides = {}) => ({\n user: { id: 1, name: 'Test User' },\n isAuthenticated: true,\n login: jest.fn(),\n logout: jest.fn(),\n ...overrides,\n});\n\ndescribe('ProtectedComponent', () => {\n it('renders content for authenticated user', () => {\n const contextValue = mockAuthContext();\n\n render(\n \u003cAuthContext.Provider value={contextValue}>\n \u003cProtectedComponent />\n \u003c/AuthContext.Provider>\n );\n\n expect(screen.getByText(/welcome, test user/i)).toBeInTheDocument();\n });\n\n it('renders login prompt for unauthenticated user', () => {\n const contextValue = mockAuthContext({\n user: null,\n isAuthenticated: false,\n });\n\n render(\n \u003cAuthContext.Provider value={contextValue}>\n \u003cProtectedComponent />\n \u003c/AuthContext.Provider>\n );\n\n expect(screen.getByText(/please log in/i)).toBeInTheDocument();\n });\n});\n```\n\n### Mocking Child Components\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport { ParentComponent } from './ParentComponent';\n\n// Mock the child component\njest.mock('./ChildComponent', () => ({\n ChildComponent: ({ title, onAction }) => (\n \u003cdiv>\n \u003ch2>{title}\u003c/h2>\n \u003cbutton onClick={onAction}>Mocked Action\u003c/button>\n \u003c/div>\n ),\n}));\n\ndescribe('ParentComponent', () => {\n it('renders with mocked child', () => {\n render(\u003cParentComponent />);\n\n expect(screen.getByText(/mocked action/i)).toBeInTheDocument();\n });\n});\n```\n\n## Async Testing Patterns\n\n### Testing with waitFor\n\n```javascript\nimport { render, screen, waitFor } from '@testing-library/react';\nimport { AsyncComponent } from './AsyncComponent';\n\ndescribe('AsyncComponent', () => {\n it('loads and displays data', async () => {\n render(\u003cAsyncComponent />);\n\n expect(screen.getByText(/loading/i)).toBeInTheDocument();\n\n await waitFor(() => {\n expect(screen.getByText(/data loaded/i)).toBeInTheDocument();\n });\n });\n\n it('waits for specific condition', async () => {\n render(\u003cAsyncComponent />);\n\n await waitFor(\n () => {\n expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();\n },\n { timeout: 3000 }\n );\n });\n});\n```\n\n### Testing with findBy Queries\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport { DataFetcher } from './DataFetcher';\n\ndescribe('DataFetcher Component', () => {\n it('displays fetched data', async () => {\n render(\u003cDataFetcher />);\n\n // findBy automatically waits for element to appear\n const heading = await screen.findByRole('heading', { name: /data/i });\n expect(heading).toBeInTheDocument();\n });\n\n it('handles timeout for missing elements', async () => {\n render(\u003cDataFetcher url=\"/api/missing\" />);\n\n await expect(\n screen.findByText(/success/i, {}, { timeout: 500 })\n ).rejects.toThrow();\n });\n});\n```\n\n### Testing Promises\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { AsyncForm } from './AsyncForm';\n\ndescribe('AsyncForm Component', () => {\n it('submits form and shows success message', async () => {\n const user = userEvent.setup();\n render(\u003cAsyncForm />);\n\n const input = screen.getByLabelText(/name/i);\n const submitBtn = screen.getByRole('button', { name: /submit/i });\n\n await user.type(input, 'John Doe');\n await user.click(submitBtn);\n\n const successMsg = await screen.findByText(/submitted successfully/i);\n expect(successMsg).toBeInTheDocument();\n });\n\n it('shows error message on failure', async () => {\n const user = userEvent.setup();\n render(\u003cAsyncForm shouldFail={true} />);\n\n const submitBtn = screen.getByRole('button', { name: /submit/i });\n await user.click(submitBtn);\n\n const errorMsg = await screen.findByRole('alert');\n expect(errorMsg).toHaveTextContent(/submission failed/i);\n });\n});\n```\n\n### Testing with Fake Timers\n\n```javascript\nimport { render, screen, act } from '@testing-library/react';\nimport { Timer } from './Timer';\n\ndescribe('Timer Component', () => {\n beforeEach(() => {\n jest.useFakeTimers();\n });\n\n afterEach(() => {\n jest.useRealTimers();\n });\n\n it('updates timer every second', () => {\n render(\u003cTimer />);\n\n expect(screen.getByText(/0 seconds/i)).toBeInTheDocument();\n\n act(() => {\n jest.advanceTimersByTime(1000);\n });\n\n expect(screen.getByText(/1 second/i)).toBeInTheDocument();\n\n act(() => {\n jest.advanceTimersByTime(3000);\n });\n\n expect(screen.getByText(/4 seconds/i)).toBeInTheDocument();\n });\n\n it('cleans up timer on unmount', () => {\n const { unmount } = render(\u003cTimer />);\n\n const clearIntervalSpy = jest.spyOn(global, 'clearInterval');\n\n unmount();\n\n expect(clearIntervalSpy).toHaveBeenCalled();\n });\n});\n```\n\n## Testing Custom Hooks\n\n### Basic Hook Testing\n\n```javascript\nimport { renderHook } from '@testing-library/react';\nimport { useCounter } from './useCounter';\n\ndescribe('useCounter Hook', () => {\n it('initializes with default value', () => {\n const { result } = renderHook(() => useCounter());\n\n expect(result.current.count).toBe(0);\n });\n\n it('initializes with provided value', () => {\n const { result } = renderHook(() => useCounter(10));\n\n expect(result.current.count).toBe(10);\n });\n\n it('increments count', () => {\n const { result } = renderHook(() => useCounter());\n\n act(() => {\n result.current.increment();\n });\n\n expect(result.current.count).toBe(1);\n });\n\n it('decrements count', () => {\n const { result } = renderHook(() => useCounter(5));\n\n act(() => {\n result.current.decrement();\n });\n\n expect(result.current.count).toBe(4);\n });\n});\n```\n\n### Testing Hooks with Props\n\n```javascript\nimport { renderHook } from '@testing-library/react';\nimport { useFetch } from './useFetch';\n\ndescribe('useFetch Hook', () => {\n it('fetches data for given URL', async () => {\n const { result } = renderHook(() => useFetch('/api/users'));\n\n expect(result.current.loading).toBe(true);\n\n await waitFor(() => {\n expect(result.current.loading).toBe(false);\n });\n\n expect(result.current.data).toBeDefined();\n expect(result.current.error).toBeNull();\n });\n\n it('refetches when URL changes', async () => {\n const { result, rerender } = renderHook(\n ({ url }) => useFetch(url),\n { initialProps: { url: '/api/users' } }\n );\n\n await waitFor(() => {\n expect(result.current.loading).toBe(false);\n });\n\n const firstData = result.current.data;\n\n rerender({ url: '/api/posts' });\n\n await waitFor(() => {\n expect(result.current.data).not.toEqual(firstData);\n });\n });\n});\n```\n\n### Testing Hooks with Context\n\n```javascript\nimport { renderHook } from '@testing-library/react';\nimport { ThemeProvider } from './ThemeContext';\nimport { useTheme } from './useTheme';\n\ndescribe('useTheme Hook', () => {\n const wrapper = ({ children }) => (\n \u003cThemeProvider initialTheme=\"light\">\n {children}\n \u003c/ThemeProvider>\n );\n\n it('returns current theme', () => {\n const { result } = renderHook(() => useTheme(), { wrapper });\n\n expect(result.current.theme).toBe('light');\n });\n\n it('toggles theme', () => {\n const { result } = renderHook(() => useTheme(), { wrapper });\n\n act(() => {\n result.current.toggleTheme();\n });\n\n expect(result.current.theme).toBe('dark');\n });\n});\n```\n\n### Testing Async Hooks\n\n```javascript\nimport { renderHook, waitFor } from '@testing-library/react';\nimport { useAsyncData } from './useAsyncData';\n\ndescribe('useAsyncData Hook', () => {\n it('loads data asynchronously', async () => {\n const { result } = renderHook(() => useAsyncData('/api/data'));\n\n expect(result.current.loading).toBe(true);\n expect(result.current.data).toBeNull();\n\n await waitFor(() => {\n expect(result.current.loading).toBe(false);\n });\n\n expect(result.current.data).toBeDefined();\n });\n\n it('handles errors', async () => {\n const { result } = renderHook(() => useAsyncData('/api/error'));\n\n await waitFor(() => {\n expect(result.current.loading).toBe(false);\n });\n\n expect(result.current.error).toBeDefined();\n expect(result.current.data).toBeNull();\n });\n});\n```\n\n## Integration Testing Patterns\n\n### Testing Component Integration\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { App } from './App';\n\ndescribe('App Integration', () => {\n it('navigates between pages', async () => {\n const user = userEvent.setup();\n render(\u003cApp />);\n\n expect(screen.getByText(/home page/i)).toBeInTheDocument();\n\n const aboutLink = screen.getByRole('link', { name: /about/i });\n await user.click(aboutLink);\n\n expect(screen.getByText(/about page/i)).toBeInTheDocument();\n });\n\n it('completes full user flow', async () => {\n const user = userEvent.setup();\n render(\u003cApp />);\n\n // Navigate to signup\n await user.click(screen.getByRole('link', { name: /sign up/i }));\n\n // Fill out form\n await user.type(screen.getByLabelText(/email/i), '[email protected]');\n await user.type(screen.getByLabelText(/password/i), 'password123');\n\n // Submit form\n await user.click(screen.getByRole('button', { name: /submit/i }));\n\n // Verify success\n expect(await screen.findByText(/welcome/i)).toBeInTheDocument();\n });\n});\n```\n\n### Testing with Router\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport { MemoryRouter } from 'react-router-dom';\nimport userEvent from '@testing-library/user-event';\nimport { AppRoutes } from './AppRoutes';\n\nconst renderWithRouter = (ui, { initialEntries = ['/'] } = {}) => {\n return render(\n \u003cMemoryRouter initialEntries={initialEntries}>\n {ui}\n \u003c/MemoryRouter>\n );\n};\n\ndescribe('AppRoutes Integration', () => {\n it('renders home page by default', () => {\n renderWithRouter(\u003cAppRoutes />);\n\n expect(screen.getByText(/home/i)).toBeInTheDocument();\n });\n\n it('renders user page at /users/:id', () => {\n renderWithRouter(\u003cAppRoutes />, { initialEntries: ['/users/123'] });\n\n expect(screen.getByText(/user profile/i)).toBeInTheDocument();\n });\n\n it('navigates programmatically', async () => {\n const user = userEvent.setup();\n renderWithRouter(\u003cAppRoutes />);\n\n const navButton = screen.getByRole('button', { name: /go to profile/i });\n await user.click(navButton);\n\n expect(screen.getByText(/profile page/i)).toBeInTheDocument();\n });\n});\n```\n\n### Testing with Redux\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport { Provider } from 'react-redux';\nimport { configureStore } from '@reduxjs/toolkit';\nimport userEvent from '@testing-library/user-event';\nimport { TodoList } from './TodoList';\nimport todosReducer from './todosSlice';\n\nconst createMockStore = (initialState = {}) => {\n return configureStore({\n reducer: {\n todos: todosReducer,\n },\n preloadedState: initialState,\n });\n};\n\nconst renderWithStore = (ui, { store = createMockStore() } = {}) => {\n return render(\u003cProvider store={store}>{ui}\u003c/Provider>);\n};\n\ndescribe('TodoList Integration', () => {\n it('adds new todo', async () => {\n const user = userEvent.setup();\n renderWithStore(\u003cTodoList />);\n\n const input = screen.getByPlaceholderText(/new todo/i);\n const addButton = screen.getByRole('button', { name: /add/i });\n\n await user.type(input, 'Buy groceries');\n await user.click(addButton);\n\n expect(screen.getByText(/buy groceries/i)).toBeInTheDocument();\n });\n\n it('renders initial todos from store', () => {\n const initialState = {\n todos: {\n items: [\n { id: 1, text: 'Existing todo', completed: false },\n ],\n },\n };\n\n renderWithStore(\u003cTodoList />, { store: createMockStore(initialState) });\n\n expect(screen.getByText(/existing todo/i)).toBeInTheDocument();\n });\n});\n```\n\n## Jest DOM Matchers\n\n### Common Matchers\n\n```javascript\n// Element presence\nexpect(element).toBeInTheDocument();\nexpect(element).not.toBeInTheDocument();\n\n// Visibility\nexpect(element).toBeVisible();\nexpect(element).not.toBeVisible();\n\n// Text content\nexpect(element).toHaveTextContent('Hello World');\nexpect(element).toHaveTextContent(/hello/i);\n\n// Attributes\nexpect(element).toHaveAttribute('type', 'submit');\nexpect(element).toHaveAttribute('disabled');\n\n// Classes\nexpect(element).toHaveClass('active');\nexpect(element).toHaveClass('btn', 'btn-primary');\n\n// Styles\nexpect(element).toHaveStyle({ color: 'red' });\nexpect(element).toHaveStyle('display: none');\n\n// Forms\nexpect(input).toHaveValue('test');\nexpect(input).toHaveDisplayValue('Test');\nexpect(checkbox).toBeChecked();\nexpect(checkbox).not.toBeChecked();\nexpect(input).toBeDisabled();\nexpect(input).toBeEnabled();\nexpect(input).toBeRequired();\nexpect(input).toBeInvalid();\nexpect(input).toBeValid();\n\n// Focus\nexpect(element).toHaveFocus();\n\n// Accessibility\nexpect(element).toHaveAccessibleName('Submit button');\nexpect(element).toHaveAccessibleDescription('Click to submit form');\n\n// Contains\nexpect(container).toContainElement(child);\nexpect(container).toContainHTML('\u003cspan>Text\u003c/span>');\n```\n\n## Best Practices\n\n### Test Organization\n\n1. **Group Related Tests**: Use `describe` blocks to organize tests\n ```javascript\n describe('UserProfile', () => {\n describe('when loading', () => {\n it('shows loading spinner', () => {});\n });\n\n describe('when loaded', () => {\n it('displays user information', () => {});\n it('shows profile picture', () => {});\n });\n });\n ```\n\n2. **Use Descriptive Test Names**: Test names should describe behavior\n ```javascript\n // Good\n it('displays error message when login fails', () => {});\n\n // Bad\n it('test login', () => {});\n ```\n\n3. **Follow AAA Pattern**: Arrange, Act, Assert\n ```javascript\n it('increments counter', async () => {\n // Arrange\n const user = userEvent.setup();\n render(\u003cCounter />);\n\n // Act\n await user.click(screen.getByRole('button', { name: /increment/i }));\n\n // Assert\n expect(screen.getByText(/count: 1/i)).toBeInTheDocument();\n });\n ```\n\n### Query Best Practices\n\n1. **Prefer Accessible Queries**: Use getByRole, getByLabelText\n2. **Use Screen Queries**: Import from screen instead of destructuring render\n3. **Avoid getByTestId**: Use it as last resort only\n4. **Use Regular Expressions**: More flexible than exact strings\n\n### Async Testing Best Practices\n\n1. **Use findBy for Async**: Prefer findBy over getBy + waitFor\n2. **Set Proper Timeouts**: Configure waitFor timeouts for slow operations\n3. **Avoid act() Warnings**: Use userEvent, waitFor, findBy appropriately\n4. **Clean Up Timers**: Use jest.useFakeTimers() and cleanup properly\n\n### Mocking Best Practices\n\n1. **Mock at Right Level**: Mock external dependencies, not internal logic\n2. **Reset Mocks**: Clear mocks between tests\n3. **Use MSW for API**: Prefer MSW over mocking axios/fetch directly\n4. **Avoid Over-Mocking**: Don't mock what you're testing\n\n### Coverage Best Practices\n\n1. **Focus on Behavior**: Test user-facing behavior, not implementation\n2. **Don't Chase 100%**: Focus on critical paths\n3. **Test Error States**: Include error handling tests\n4. **Test Edge Cases**: Include boundary conditions\n\n## Common Testing Patterns\n\n### Testing Lists and Iterations\n\n```javascript\nit('renders list of items', () => {\n const items = ['Apple', 'Banana', 'Cherry'];\n render(\u003cItemList items={items} />);\n\n items.forEach(item => {\n expect(screen.getByText(item)).toBeInTheDocument();\n });\n});\n```\n\n### Testing Accessibility\n\n```javascript\nit('has accessible form', () => {\n render(\u003cContactForm />);\n\n const nameInput = screen.getByLabelText(/name/i);\n expect(nameInput).toHaveAccessibleName('Name');\n expect(nameInput).toBeRequired();\n\n const submitButton = screen.getByRole('button', { name: /submit/i });\n expect(submitButton).toHaveAttribute('type', 'submit');\n});\n```\n\n### Testing Error Boundaries\n\n```javascript\nit('catches errors and displays fallback', () => {\n const ThrowError = () => {\n throw new Error('Test error');\n };\n\n // Suppress console.error for this test\n const spy = jest.spyOn(console, 'error').mockImplementation(() => {});\n\n render(\n \u003cErrorBoundary fallback={\u003cdiv>Something went wrong\u003c/div>}>\n \u003cThrowError />\n \u003c/ErrorBoundary>\n );\n\n expect(screen.getByText(/something went wrong/i)).toBeInTheDocument();\n\n spy.mockRestore();\n});\n```\n\n### Testing Portals\n\n```javascript\nit('renders modal in portal', () => {\n render(\u003cModal isOpen={true}>Modal Content\u003c/Modal>);\n\n const modal = screen.getByText(/modal content/i);\n expect(modal).toBeInTheDocument();\n\n // Modal should be in document.body, not in the component tree\n expect(modal.parentElement).toBe(document.body);\n});\n```\n\n## Troubleshooting\n\n### Common Issues\n\n**Issue**: \"Unable to find element\"\n- **Solution**: Use screen.debug() to see DOM, check query type, wait for async updates\n\n**Issue**: \"Act warnings\"\n- **Solution**: Use userEvent instead of fireEvent, wrap state updates in act(), use waitFor/findBy\n\n**Issue**: \"Jest timeout\"\n- **Solution**: Increase timeout, check for infinite loops, ensure async operations complete\n\n**Issue**: \"Cannot read property of undefined\"\n- **Solution**: Check mocks are set up correctly, ensure components receive required props\n\n**Issue**: \"Multiple elements found\"\n- **Solution**: Make queries more specific, use getAllBy for multiple elements\n\n## Resources\n\n- Jest Documentation: https://jestjs.io/\n- React Testing Library: https://testing-library.com/react\n- Testing Library Queries: https://testing-library.com/docs/queries/about\n- Jest DOM Matchers: https://github.com/testing-library/jest-dom\n- MSW Documentation: https://mswjs.io/\n- Common Mistakes: https://kentcdodds.com/blog/common-mistakes-with-react-testing-library\n\n---\n\n**Skill Version**: 1.0.0\n**Last Updated**: October 2025\n**Skill Category**: Testing, React, Quality Assurance\n**Compatible With**: Jest 29+, React Testing Library 13+, React 16.8+\n---","attachment_filenames":["EXAMPLES.md","README.md"],"attachments":[{"filename":"EXAMPLES.md","content":"# Jest React Testing Examples\n\nComprehensive collection of real-world testing examples covering common scenarios and patterns in React application testing.\n\n## Table of Contents\n\n1. [Basic Component Testing](#1-basic-component-testing)\n2. [Form Testing with Validation](#2-form-testing-with-validation)\n3. [Async API Data Fetching](#3-async-api-data-fetching)\n4. [User Authentication Flow](#4-user-authentication-flow)\n5. [Modal and Dialog Testing](#5-modal-and-dialog-testing)\n6. [Dropdown and Select Testing](#6-dropdown-and-select-testing)\n7. [Testing Custom Hooks](#7-testing-custom-hooks)\n8. [Testing with React Router](#8-testing-with-react-router)\n9. [Testing with Redux](#9-testing-with-redux)\n10. [Testing with Context API](#10-testing-with-context-api)\n11. [File Upload Component](#11-file-upload-component)\n12. [Autocomplete Component](#12-autocomplete-component)\n13. [Infinite Scroll List](#13-infinite-scroll-list)\n14. [Error Boundary Testing](#14-error-boundary-testing)\n15. [Accessibility Testing](#15-accessibility-testing)\n16. [Testing Timers and Intervals](#16-testing-timers-and-intervals)\n17. [Testing WebSocket Integration](#17-testing-websocket-integration)\n18. [Multi-step Form Wizard](#18-multi-step-form-wizard)\n\n---\n\n## 1. Basic Component Testing\n\n### Component: Button\n\n```javascript\n// Button.jsx\nexport const Button = ({\n children,\n variant = 'primary',\n disabled = false,\n onClick\n}) => {\n return (\n \u003cbutton\n className={`btn btn-${variant}`}\n disabled={disabled}\n onClick={onClick}\n >\n {children}\n \u003c/button>\n );\n};\n```\n\n### Tests\n\n```javascript\n// Button.test.jsx\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { Button } from './Button';\n\ndescribe('Button Component', () => {\n it('renders with children text', () => {\n render(\u003cButton>Click Me\u003c/Button>);\n\n expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument();\n });\n\n it('applies correct variant class', () => {\n render(\u003cButton variant=\"secondary\">Secondary\u003c/Button>);\n\n const button = screen.getByRole('button');\n expect(button).toHaveClass('btn-secondary');\n });\n\n it('applies primary variant by default', () => {\n render(\u003cButton>Default\u003c/Button>);\n\n const button = screen.getByRole('button');\n expect(button).toHaveClass('btn-primary');\n });\n\n it('calls onClick handler when clicked', async () => {\n const user = userEvent.setup();\n const handleClick = jest.fn();\n\n render(\u003cButton onClick={handleClick}>Click\u003c/Button>);\n\n await user.click(screen.getByRole('button'));\n\n expect(handleClick).toHaveBeenCalledTimes(1);\n });\n\n it('does not call onClick when disabled', async () => {\n const user = userEvent.setup();\n const handleClick = jest.fn();\n\n render(\u003cButton onClick={handleClick} disabled>Disabled\u003c/Button>);\n\n const button = screen.getByRole('button');\n\n // Verify button is disabled\n expect(button).toBeDisabled();\n\n // Try to click - should not work\n await user.click(button);\n\n expect(handleClick).not.toHaveBeenCalled();\n });\n});\n```\n\n---\n\n## 2. Form Testing with Validation\n\n### Component: LoginForm\n\n```javascript\n// LoginForm.jsx\nimport { useState } from 'react';\n\nexport const LoginForm = ({ onSubmit }) => {\n const [email, setEmail] = useState('');\n const [password, setPassword] = useState('');\n const [errors, setErrors] = useState({});\n\n const validate = () => {\n const newErrors = {};\n\n if (!email) {\n newErrors.email = 'Email is required';\n } else if (!/\\S+@\\S+\\.\\S+/.test(email)) {\n newErrors.email = 'Email is invalid';\n }\n\n if (!password) {\n newErrors.password = 'Password is required';\n } else if (password.length \u003c 8) {\n newErrors.password = 'Password must be at least 8 characters';\n }\n\n return newErrors;\n };\n\n const handleSubmit = (e) => {\n e.preventDefault();\n\n const newErrors = validate();\n\n if (Object.keys(newErrors).length === 0) {\n onSubmit({ email, password });\n setEmail('');\n setPassword('');\n } else {\n setErrors(newErrors);\n }\n };\n\n return (\n \u003cform onSubmit={handleSubmit}>\n \u003cdiv>\n \u003clabel htmlFor=\"email\">Email\u003c/label>\n \u003cinput\n id=\"email\"\n type=\"email\"\n value={email}\n onChange={(e) => setEmail(e.target.value)}\n aria-invalid={errors.email ? 'true' : 'false'}\n aria-describedby={errors.email ? 'email-error' : undefined}\n />\n {errors.email && (\n \u003cspan id=\"email-error\" role=\"alert\">\n {errors.email}\n \u003c/span>\n )}\n \u003c/div>\n\n \u003cdiv>\n \u003clabel htmlFor=\"password\">Password\u003c/label>\n \u003cinput\n id=\"password\"\n type=\"password\"\n value={password}\n onChange={(e) => setPassword(e.target.value)}\n aria-invalid={errors.password ? 'true' : 'false'}\n aria-describedby={errors.password ? 'password-error' : undefined}\n />\n {errors.password && (\n \u003cspan id=\"password-error\" role=\"alert\">\n {errors.password}\n \u003c/span>\n )}\n \u003c/div>\n\n \u003cbutton type=\"submit\">Login\u003c/button>\n \u003c/form>\n );\n};\n```\n\n### Tests\n\n```javascript\n// LoginForm.test.jsx\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { LoginForm } from './LoginForm';\n\ndescribe('LoginForm Component', () => {\n it('renders form fields', () => {\n render(\u003cLoginForm onSubmit={jest.fn()} />);\n\n expect(screen.getByLabelText(/email/i)).toBeInTheDocument();\n expect(screen.getByLabelText(/password/i)).toBeInTheDocument();\n expect(screen.getByRole('button', { name: /login/i })).toBeInTheDocument();\n });\n\n it('submits form with valid data', async () => {\n const user = userEvent.setup();\n const handleSubmit = jest.fn();\n\n render(\u003cLoginForm onSubmit={handleSubmit} />);\n\n await user.type(screen.getByLabelText(/email/i), '[email protected]');\n await user.type(screen.getByLabelText(/password/i), 'password123');\n await user.click(screen.getByRole('button', { name: /login/i }));\n\n expect(handleSubmit).toHaveBeenCalledWith({\n email: '[email protected]',\n password: 'password123',\n });\n });\n\n it('shows validation error for empty email', async () => {\n const user = userEvent.setup();\n\n render(\u003cLoginForm onSubmit={jest.fn()} />);\n\n await user.click(screen.getByRole('button', { name: /login/i }));\n\n expect(screen.getByText(/email is required/i)).toBeInTheDocument();\n });\n\n it('shows validation error for invalid email format', async () => {\n const user = userEvent.setup();\n\n render(\u003cLoginForm onSubmit={jest.fn()} />);\n\n await user.type(screen.getByLabelText(/email/i), 'invalid-email');\n await user.click(screen.getByRole('button', { name: /login/i }));\n\n expect(screen.getByText(/email is invalid/i)).toBeInTheDocument();\n });\n\n it('shows validation error for short password', async () => {\n const user = userEvent.setup();\n\n render(\u003cLoginForm onSubmit={jest.fn()} />);\n\n await user.type(screen.getByLabelText(/email/i), '[email protected]');\n await user.type(screen.getByLabelText(/password/i), 'short');\n await user.click(screen.getByRole('button', { name: /login/i }));\n\n expect(screen.getByText(/password must be at least 8 characters/i)).toBeInTheDocument();\n });\n\n it('clears form after successful submission', async () => {\n const user = userEvent.setup();\n\n render(\u003cLoginForm onSubmit={jest.fn()} />);\n\n const emailInput = screen.getByLabelText(/email/i);\n const passwordInput = screen.getByLabelText(/password/i);\n\n await user.type(emailInput, '[email protected]');\n await user.type(passwordInput, 'password123');\n await user.click(screen.getByRole('button', { name: /login/i }));\n\n expect(emailInput).toHaveValue('');\n expect(passwordInput).toHaveValue('');\n });\n});\n```\n\n---\n\n## 3. Async API Data Fetching\n\n### Component: UserProfile\n\n```javascript\n// UserProfile.jsx\nimport { useState, useEffect } from 'react';\n\nexport const UserProfile = ({ userId }) => {\n const [user, setUser] = useState(null);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState(null);\n\n useEffect(() => {\n const fetchUser = async () => {\n setLoading(true);\n setError(null);\n\n try {\n const response = await fetch(`/api/users/${userId}`);\n\n if (!response.ok) {\n throw new Error('Failed to fetch user');\n }\n\n const data = await response.json();\n setUser(data);\n } catch (err) {\n setError(err.message);\n } finally {\n setLoading(false);\n }\n };\n\n fetchUser();\n }, [userId]);\n\n if (loading) {\n return \u003cdiv>Loading user profile...\u003c/div>;\n }\n\n if (error) {\n return \u003cdiv role=\"alert\">Error: {error}\u003c/div>;\n }\n\n if (!user) {\n return \u003cdiv>No user found\u003c/div>;\n }\n\n return (\n \u003cdiv>\n \u003ch1>{user.name}\u003c/h1>\n \u003cp>Email: {user.email}\u003c/p>\n \u003cp>Role: {user.role}\u003c/p>\n \u003c/div>\n );\n};\n```\n\n### Tests with MSW\n\n```javascript\n// UserProfile.test.jsx\nimport { render, screen, waitFor } from '@testing-library/react';\nimport { rest } from 'msw';\nimport { setupServer } from 'msw/node';\nimport { UserProfile } from './UserProfile';\n\nconst server = setupServer(\n rest.get('/api/users/:userId', (req, res, ctx) => {\n const { userId } = req.params;\n\n return res(\n ctx.json({\n id: userId,\n name: 'John Doe',\n email: '[email protected]',\n role: 'Admin',\n })\n );\n })\n);\n\nbeforeAll(() => server.listen());\nafterEach(() => server.resetHandlers());\nafterAll(() => server.close());\n\ndescribe('UserProfile Component', () => {\n it('shows loading state initially', () => {\n render(\u003cUserProfile userId=\"1\" />);\n\n expect(screen.getByText(/loading user profile/i)).toBeInTheDocument();\n });\n\n it('displays user data after loading', async () => {\n render(\u003cUserProfile userId=\"1\" />);\n\n expect(screen.getByText(/loading/i)).toBeInTheDocument();\n\n await waitFor(() => {\n expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();\n });\n\n expect(screen.getByRole('heading', { name: /john doe/i })).toBeInTheDocument();\n expect(screen.getByText(/email: [email protected]/i)).toBeInTheDocument();\n expect(screen.getByText(/role: admin/i)).toBeInTheDocument();\n });\n\n it('uses findBy for async elements', async () => {\n render(\u003cUserProfile userId=\"1\" />);\n\n // findBy automatically waits for element\n const heading = await screen.findByRole('heading', { name: /john doe/i });\n expect(heading).toBeInTheDocument();\n });\n\n it('displays error message on fetch failure', async () => {\n server.use(\n rest.get('/api/users/:userId', (req, res, ctx) => {\n return res(ctx.status(500));\n })\n );\n\n render(\u003cUserProfile userId=\"1\" />);\n\n const errorMessage = await screen.findByRole('alert');\n expect(errorMessage).toHaveTextContent(/failed to fetch user/i);\n });\n\n it('refetches when userId changes', async () => {\n const { rerender } = render(\u003cUserProfile userId=\"1\" />);\n\n await screen.findByRole('heading', { name: /john doe/i });\n\n // Update server to return different user\n server.use(\n rest.get('/api/users/:userId', (req, res, ctx) => {\n return res(\n ctx.json({\n id: '2',\n name: 'Jane Smith',\n email: '[email protected]',\n role: 'User',\n })\n );\n })\n );\n\n rerender(\u003cUserProfile userId=\"2\" />);\n\n const newHeading = await screen.findByRole('heading', { name: /jane smith/i });\n expect(newHeading).toBeInTheDocument();\n });\n});\n```\n\n---\n\n## 4. User Authentication Flow\n\n### Component: AuthProvider & useAuth\n\n```javascript\n// AuthContext.jsx\nimport { createContext, useContext, useState } from 'react';\n\nconst AuthContext = createContext(null);\n\nexport const AuthProvider = ({ children }) => {\n const [user, setUser] = useState(null);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState(null);\n\n const login = async (email, password) => {\n setLoading(true);\n setError(null);\n\n try {\n const response = await fetch('/api/auth/login', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ email, password }),\n });\n\n if (!response.ok) {\n throw new Error('Login failed');\n }\n\n const data = await response.json();\n setUser(data.user);\n localStorage.setItem('token', data.token);\n\n return { success: true };\n } catch (err) {\n setError(err.message);\n return { success: false, error: err.message };\n } finally {\n setLoading(false);\n }\n };\n\n const logout = () => {\n setUser(null);\n localStorage.removeItem('token');\n };\n\n return (\n \u003cAuthContext.Provider value={{ user, loading, error, login, logout }}>\n {children}\n \u003c/AuthContext.Provider>\n );\n};\n\nexport const useAuth = () => {\n const context = useContext(AuthContext);\n if (!context) {\n throw new Error('useAuth must be used within AuthProvider');\n }\n return context;\n};\n```\n\n### Tests\n\n```javascript\n// Auth.test.jsx\nimport { render, screen, waitFor } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { rest } from 'msw';\nimport { setupServer } from 'msw/node';\nimport { AuthProvider, useAuth } from './AuthContext';\n\nconst server = setupServer(\n rest.post('/api/auth/login', async (req, res, ctx) => {\n const { email, password } = await req.json();\n\n if (email === '[email protected]' && password === 'password123') {\n return res(\n ctx.json({\n user: { id: 1, email: '[email protected]', name: 'Test User' },\n token: 'fake-jwt-token',\n })\n );\n }\n\n return res(ctx.status(401), ctx.json({ message: 'Invalid credentials' }));\n })\n);\n\nbeforeAll(() => server.listen());\nafterEach(() => {\n server.resetHandlers();\n localStorage.clear();\n});\nafterAll(() => server.close());\n\n// Test component that uses useAuth\nconst TestComponent = () => {\n const { user, login, logout, loading, error } = useAuth();\n\n const handleLogin = async () => {\n await login('[email protected]', 'password123');\n };\n\n if (loading) return \u003cdiv>Loading...\u003c/div>;\n\n return (\n \u003cdiv>\n {user ? (\n \u003cdiv>\n \u003cp>Welcome, {user.name}\u003c/p>\n \u003cbutton onClick={logout}>Logout\u003c/button>\n \u003c/div>\n ) : (\n \u003cdiv>\n \u003cp>Please log in\u003c/p>\n \u003cbutton onClick={handleLogin}>Login\u003c/button>\n \u003c/div>\n )}\n {error && \u003cdiv role=\"alert\">{error}\u003c/div>}\n \u003c/div>\n );\n};\n\ndescribe('Authentication Flow', () => {\n it('logs in user successfully', async () => {\n const user = userEvent.setup();\n\n render(\n \u003cAuthProvider>\n \u003cTestComponent />\n \u003c/AuthProvider>\n );\n\n expect(screen.getByText(/please log in/i)).toBeInTheDocument();\n\n await user.click(screen.getByRole('button', { name: /login/i }));\n\n await waitFor(() => {\n expect(screen.getByText(/welcome, test user/i)).toBeInTheDocument();\n });\n\n expect(localStorage.getItem('token')).toBe('fake-jwt-token');\n });\n\n it('logs out user', async () => {\n const user = userEvent.setup();\n\n render(\n \u003cAuthProvider>\n \u003cTestComponent />\n \u003c/AuthProvider>\n );\n\n // Login first\n await user.click(screen.getByRole('button', { name: /login/i }));\n await screen.findByText(/welcome, test user/i);\n\n // Logout\n await user.click(screen.getByRole('button', { name: /logout/i }));\n\n expect(screen.getByText(/please log in/i)).toBeInTheDocument();\n expect(localStorage.getItem('token')).toBeNull();\n });\n\n it('shows error on failed login', async () => {\n const user = userEvent.setup();\n\n server.use(\n rest.post('/api/auth/login', (req, res, ctx) => {\n return res(ctx.status(401));\n })\n );\n\n render(\n \u003cAuthProvider>\n \u003cTestComponent />\n \u003c/AuthProvider>\n );\n\n await user.click(screen.getByRole('button', { name: /login/i }));\n\n const errorMessage = await screen.findByRole('alert');\n expect(errorMessage).toHaveTextContent(/login failed/i);\n });\n});\n```\n\n---\n\n## 5. Modal and Dialog Testing\n\n### Component: Modal\n\n```javascript\n// Modal.jsx\nimport { useEffect } from 'react';\nimport { createPortal } from 'react-dom';\n\nexport const Modal = ({ isOpen, onClose, title, children }) => {\n useEffect(() => {\n const handleEscape = (e) => {\n if (e.key === 'Escape') {\n onClose();\n }\n };\n\n if (isOpen) {\n document.addEventListener('keydown', handleEscape);\n document.body.style.overflow = 'hidden';\n }\n\n return () => {\n document.removeEventListener('keydown', handleEscape);\n document.body.style.overflow = 'unset';\n };\n }, [isOpen, onClose]);\n\n if (!isOpen) return null;\n\n return createPortal(\n \u003cdiv\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"modal-title\"\n className=\"modal-overlay\"\n onClick={onClose}\n >\n \u003cdiv className=\"modal-content\" onClick={(e) => e.stopPropagation()}>\n \u003cdiv className=\"modal-header\">\n \u003ch2 id=\"modal-title\">{title}\u003c/h2>\n \u003cbutton\n onClick={onClose}\n aria-label=\"Close modal\"\n className=\"close-button\"\n >\n ×\n \u003c/button>\n \u003c/div>\n \u003cdiv className=\"modal-body\">{children}\u003c/div>\n \u003c/div>\n \u003c/div>,\n document.body\n );\n};\n```\n\n### Tests\n\n```javascript\n// Modal.test.jsx\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { Modal } from './Modal';\n\ndescribe('Modal Component', () => {\n it('does not render when closed', () => {\n render(\n \u003cModal isOpen={false} onClose={jest.fn()} title=\"Test Modal\">\n Content\n \u003c/Modal>\n );\n\n expect(screen.queryByRole('dialog')).not.toBeInTheDocument();\n });\n\n it('renders when open', () => {\n render(\n \u003cModal isOpen={true} onClose={jest.fn()} title=\"Test Modal\">\n Modal Content\n \u003c/Modal>\n );\n\n expect(screen.getByRole('dialog')).toBeInTheDocument();\n expect(screen.getByText('Test Modal')).toBeInTheDocument();\n expect(screen.getByText('Modal Content')).toBeInTheDocument();\n });\n\n it('closes when close button clicked', async () => {\n const user = userEvent.setup();\n const handleClose = jest.fn();\n\n render(\n \u003cModal isOpen={true} onClose={handleClose} title=\"Test Modal\">\n Content\n \u003c/Modal>\n );\n\n await user.click(screen.getByRole('button', { name: /close modal/i }));\n\n expect(handleClose).toHaveBeenCalledTimes(1);\n });\n\n it('closes when overlay clicked', async () => {\n const user = userEvent.setup();\n const handleClose = jest.fn();\n\n render(\n \u003cModal isOpen={true} onClose={handleClose} title=\"Test Modal\">\n Content\n \u003c/Modal>\n );\n\n const overlay = screen.getByRole('dialog');\n await user.click(overlay);\n\n expect(handleClose).toHaveBeenCalledTimes(1);\n });\n\n it('does not close when content clicked', async () => {\n const user = userEvent.setup();\n const handleClose = jest.fn();\n\n render(\n \u003cModal isOpen={true} onClose={handleClose} title=\"Test Modal\">\n \u003cbutton>Content Button\u003c/button>\n \u003c/Modal>\n );\n\n await user.click(screen.getByRole('button', { name: /content button/i }));\n\n expect(handleClose).not.toHaveBeenCalled();\n });\n\n it('closes on Escape key press', async () => {\n const user = userEvent.setup();\n const handleClose = jest.fn();\n\n render(\n \u003cModal isOpen={true} onClose={handleClose} title=\"Test Modal\">\n Content\n \u003c/Modal>\n );\n\n await user.keyboard('{Escape}');\n\n expect(handleClose).toHaveBeenCalledTimes(1);\n });\n\n it('has correct accessibility attributes', () => {\n render(\n \u003cModal isOpen={true} onClose={jest.fn()} title=\"Accessible Modal\">\n Content\n \u003c/Modal>\n );\n\n const dialog = screen.getByRole('dialog');\n expect(dialog).toHaveAttribute('aria-modal', 'true');\n expect(dialog).toHaveAttribute('aria-labelledby', 'modal-title');\n });\n});\n```\n\n---\n\n## 6. Dropdown and Select Testing\n\n### Component: Dropdown\n\n```javascript\n// Dropdown.jsx\nimport { useState, useRef, useEffect } from 'react';\n\nexport const Dropdown = ({ options, value, onChange, placeholder, label }) => {\n const [isOpen, setIsOpen] = useState(false);\n const dropdownRef = useRef(null);\n\n useEffect(() => {\n const handleClickOutside = (event) => {\n if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {\n setIsOpen(false);\n }\n };\n\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, []);\n\n const selectedOption = options.find(opt => opt.value === value);\n\n const handleSelect = (option) => {\n onChange(option.value);\n setIsOpen(false);\n };\n\n return (\n \u003cdiv ref={dropdownRef} className=\"dropdown\">\n \u003clabel id=\"dropdown-label\">{label}\u003c/label>\n \u003cbutton\n type=\"button\"\n onClick={() => setIsOpen(!isOpen)}\n aria-haspopup=\"listbox\"\n aria-expanded={isOpen}\n aria-labelledby=\"dropdown-label\"\n >\n {selectedOption ? selectedOption.label : placeholder}\n \u003c/button>\n\n {isOpen && (\n \u003cul role=\"listbox\" aria-labelledby=\"dropdown-label\">\n {options.map((option) => (\n \u003cli\n key={option.value}\n role=\"option\"\n aria-selected={option.value === value}\n onClick={() => handleSelect(option)}\n >\n {option.label}\n \u003c/li>\n ))}\n \u003c/ul>\n )}\n \u003c/div>\n );\n};\n```\n\n### Tests\n\n```javascript\n// Dropdown.test.jsx\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { Dropdown } from './Dropdown';\n\nconst options = [\n { value: 'apple', label: 'Apple' },\n { value: 'banana', label: 'Banana' },\n { value: 'cherry', label: 'Cherry' },\n];\n\ndescribe('Dropdown Component', () => {\n it('renders with placeholder', () => {\n render(\n \u003cDropdown\n options={options}\n value=\"\"\n onChange={jest.fn()}\n placeholder=\"Select a fruit\"\n label=\"Fruit\"\n />\n );\n\n expect(screen.getByText(/select a fruit/i)).toBeInTheDocument();\n });\n\n it('shows selected value', () => {\n render(\n \u003cDropdown\n options={options}\n value=\"banana\"\n onChange={jest.fn()}\n placeholder=\"Select a fruit\"\n label=\"Fruit\"\n />\n );\n\n expect(screen.getByText('Banana')).toBeInTheDocument();\n });\n\n it('opens dropdown on button click', async () => {\n const user = userEvent.setup();\n\n render(\n \u003cDropdown\n options={options}\n value=\"\"\n onChange={jest.fn()}\n placeholder=\"Select a fruit\"\n label=\"Fruit\"\n />\n );\n\n const button = screen.getByRole('button');\n expect(screen.queryByRole('listbox')).not.toBeInTheDocument();\n\n await user.click(button);\n\n expect(screen.getByRole('listbox')).toBeInTheDocument();\n });\n\n it('displays all options when open', async () => {\n const user = userEvent.setup();\n\n render(\n \u003cDropdown\n options={options}\n value=\"\"\n onChange={jest.fn()}\n placeholder=\"Select a fruit\"\n label=\"Fruit\"\n />\n );\n\n await user.click(screen.getByRole('button'));\n\n expect(screen.getByRole('option', { name: /apple/i })).toBeInTheDocument();\n expect(screen.getByRole('option', { name: /banana/i })).toBeInTheDocument();\n expect(screen.getByRole('option', { name: /cherry/i })).toBeInTheDocument();\n });\n\n it('calls onChange when option selected', async () => {\n const user = userEvent.setup();\n const handleChange = jest.fn();\n\n render(\n \u003cDropdown\n options={options}\n value=\"\"\n onChange={handleChange}\n placeholder=\"Select a fruit\"\n label=\"Fruit\"\n />\n );\n\n await user.click(screen.getByRole('button'));\n await user.click(screen.getByRole('option', { name: /banana/i }));\n\n expect(handleChange).toHaveBeenCalledWith('banana');\n });\n\n it('closes dropdown after selection', async () => {\n const user = userEvent.setup();\n\n render(\n \u003cDropdown\n options={options}\n value=\"\"\n onChange={jest.fn()}\n placeholder=\"Select a fruit\"\n label=\"Fruit\"\n />\n );\n\n await user.click(screen.getByRole('button'));\n expect(screen.getByRole('listbox')).toBeInTheDocument();\n\n await user.click(screen.getByRole('option', { name: /apple/i }));\n\n expect(screen.queryByRole('listbox')).not.toBeInTheDocument();\n });\n});\n```\n\n---\n\n## 7. Testing Custom Hooks\n\n### Hook: useLocalStorage\n\n```javascript\n// useLocalStorage.js\nimport { useState, useEffect } from 'react';\n\nexport const useLocalStorage = (key, initialValue) => {\n const [value, setValue] = useState(() => {\n try {\n const item = window.localStorage.getItem(key);\n return item ? JSON.parse(item) : initialValue;\n } catch (error) {\n console.error(error);\n return initialValue;\n }\n });\n\n useEffect(() => {\n try {\n window.localStorage.setItem(key, JSON.stringify(value));\n } catch (error) {\n console.error(error);\n }\n }, [key, value]);\n\n return [value, setValue];\n};\n```\n\n### Tests\n\n```javascript\n// useLocalStorage.test.js\nimport { renderHook, act } from '@testing-library/react';\nimport { useLocalStorage } from './useLocalStorage';\n\ndescribe('useLocalStorage Hook', () => {\n beforeEach(() => {\n localStorage.clear();\n });\n\n it('returns initial value when localStorage is empty', () => {\n const { result } = renderHook(() => useLocalStorage('testKey', 'initial'));\n\n expect(result.current[0]).toBe('initial');\n });\n\n it('returns value from localStorage if it exists', () => {\n localStorage.setItem('testKey', JSON.stringify('stored'));\n\n const { result } = renderHook(() => useLocalStorage('testKey', 'initial'));\n\n expect(result.current[0]).toBe('stored');\n });\n\n it('updates localStorage when value changes', () => {\n const { result } = renderHook(() => useLocalStorage('testKey', 'initial'));\n\n act(() => {\n result.current[1]('updated');\n });\n\n expect(localStorage.getItem('testKey')).toBe(JSON.stringify('updated'));\n expect(result.current[0]).toBe('updated');\n });\n\n it('handles objects', () => {\n const { result } = renderHook(() =>\n useLocalStorage('testKey', { name: 'John' })\n );\n\n act(() => {\n result.current[1]({ name: 'Jane', age: 30 });\n });\n\n expect(result.current[0]).toEqual({ name: 'Jane', age: 30 });\n expect(JSON.parse(localStorage.getItem('testKey'))).toEqual({\n name: 'Jane',\n age: 30,\n });\n });\n\n it('handles arrays', () => {\n const { result } = renderHook(() => useLocalStorage('todos', []));\n\n act(() => {\n result.current[1]([{ id: 1, text: 'Todo 1' }]);\n });\n\n expect(result.current[0]).toEqual([{ id: 1, text: 'Todo 1' }]);\n });\n\n it('updates when key changes', () => {\n localStorage.setItem('key1', JSON.stringify('value1'));\n localStorage.setItem('key2', JSON.stringify('value2'));\n\n const { result, rerender } = renderHook(\n ({ key }) => useLocalStorage(key, 'default'),\n { initialProps: { key: 'key1' } }\n );\n\n expect(result.current[0]).toBe('value1');\n\n rerender({ key: 'key2' });\n\n expect(result.current[0]).toBe('value2');\n });\n});\n```\n\n---\n\n## 8. Testing with React Router\n\n### Component: Navigation\n\n```javascript\n// Navigation.jsx\nimport { Link, useNavigate, useLocation } from 'react-router-dom';\n\nexport const Navigation = () => {\n const navigate = useNavigate();\n const location = useLocation();\n\n const handleProfileClick = () => {\n navigate('/profile');\n };\n\n return (\n \u003cnav>\n \u003cLink to=\"/\">Home\u003c/Link>\n \u003cLink to=\"/about\">About\u003c/Link>\n \u003cLink to=\"/products\">Products\u003c/Link>\n \u003cbutton onClick={handleProfileClick}>Profile\u003c/button>\n \u003cspan>Current: {location.pathname}\u003c/span>\n \u003c/nav>\n );\n};\n```\n\n### Tests\n\n```javascript\n// Navigation.test.jsx\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { MemoryRouter, Routes, Route } from 'react-router-dom';\nimport { Navigation } from './Navigation';\n\nconst renderWithRouter = (ui, { initialEntries = ['/'] } = {}) => {\n return render(\n \u003cMemoryRouter initialEntries={initialEntries}>\n \u003cRoutes>\n \u003cRoute path=\"*\" element={ui} />\n \u003c/Routes>\n \u003c/MemoryRouter>\n );\n};\n\ndescribe('Navigation Component', () => {\n it('renders all navigation links', () => {\n renderWithRouter(\u003cNavigation />);\n\n expect(screen.getByRole('link', { name: /home/i })).toBeInTheDocument();\n expect(screen.getByRole('link', { name: /about/i })).toBeInTheDocument();\n expect(screen.getByRole('link', { name: /products/i })).toBeInTheDocument();\n expect(screen.getByRole('button', { name: /profile/i })).toBeInTheDocument();\n });\n\n it('displays current pathname', () => {\n renderWithRouter(\u003cNavigation />, { initialEntries: ['/about'] });\n\n expect(screen.getByText(/current: \\/about/i)).toBeInTheDocument();\n });\n\n it('has correct href attributes', () => {\n renderWithRouter(\u003cNavigation />);\n\n expect(screen.getByRole('link', { name: /home/i })).toHaveAttribute('href', '/');\n expect(screen.getByRole('link', { name: /about/i })).toHaveAttribute('href', '/about');\n expect(screen.getByRole('link', { name: /products/i })).toHaveAttribute('href', '/products');\n });\n});\n```\n\n---\n\n## 9. Testing with Redux\n\n### Component: TodoList with Redux\n\n```javascript\n// TodoList.jsx\nimport { useSelector, useDispatch } from 'react-redux';\nimport { addTodo, toggleTodo, removeTodo } from './todosSlice';\nimport { useState } from 'react';\n\nexport const TodoList = () => {\n const [input, setInput] = useState('');\n const todos = useSelector((state) => state.todos.items);\n const dispatch = useDispatch();\n\n const handleSubmit = (e) => {\n e.preventDefault();\n if (input.trim()) {\n dispatch(addTodo(input));\n setInput('');\n }\n };\n\n return (\n \u003cdiv>\n \u003cform onSubmit={handleSubmit}>\n \u003cinput\n type=\"text\"\n value={input}\n onChange={(e) => setInput(e.target.value)}\n placeholder=\"Add todo\"\n />\n \u003cbutton type=\"submit\">Add\u003c/button>\n \u003c/form>\n\n \u003cul>\n {todos.map((todo) => (\n \u003cli key={todo.id}>\n \u003cinput\n type=\"checkbox\"\n checked={todo.completed}\n onChange={() => dispatch(toggleTodo(todo.id))}\n aria-label={`Toggle ${todo.text}`}\n />\n \u003cspan style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}>\n {todo.text}\n \u003c/span>\n \u003cbutton\n onClick={() => dispatch(removeTodo(todo.id))}\n aria-label={`Remove ${todo.text}`}\n >\n Remove\n \u003c/button>\n \u003c/li>\n ))}\n \u003c/ul>\n \u003c/div>\n );\n};\n```\n\n### Tests\n\n```javascript\n// TodoList.test.jsx\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { Provider } from 'react-redux';\nimport { configureStore } from '@reduxjs/toolkit';\nimport { TodoList } from './TodoList';\nimport todosReducer from './todosSlice';\n\nconst createMockStore = (initialState = {}) => {\n return configureStore({\n reducer: {\n todos: todosReducer,\n },\n preloadedState: initialState,\n });\n};\n\nconst renderWithStore = (ui, { store = createMockStore() } = {}) => {\n return render(\u003cProvider store={store}>{ui}\u003c/Provider>);\n};\n\ndescribe('TodoList with Redux', () => {\n it('adds new todo', async () => {\n const user = userEvent.setup();\n renderWithStore(\u003cTodoList />);\n\n const input = screen.getByPlaceholderText(/add todo/i);\n const addButton = screen.getByRole('button', { name: /add/i });\n\n await user.type(input, 'Buy groceries');\n await user.click(addButton);\n\n expect(screen.getByText(/buy groceries/i)).toBeInTheDocument();\n });\n\n it('toggles todo completion', async () => {\n const user = userEvent.setup();\n const initialState = {\n todos: {\n items: [{ id: 1, text: 'Test todo', completed: false }],\n },\n };\n\n renderWithStore(\u003cTodoList />, { store: createMockStore(initialState) });\n\n const checkbox = screen.getByRole('checkbox', { name: /toggle test todo/i });\n const text = screen.getByText(/test todo/i);\n\n expect(checkbox).not.toBeChecked();\n expect(text).toHaveStyle({ textDecoration: 'none' });\n\n await user.click(checkbox);\n\n expect(checkbox).toBeChecked();\n expect(text).toHaveStyle({ textDecoration: 'line-through' });\n });\n\n it('removes todo', async () => {\n const user = userEvent.setup();\n const initialState = {\n todos: {\n items: [{ id: 1, text: 'Test todo', completed: false }],\n },\n };\n\n renderWithStore(\u003cTodoList />, { store: createMockStore(initialState) });\n\n expect(screen.getByText(/test todo/i)).toBeInTheDocument();\n\n await user.click(screen.getByRole('button', { name: /remove test todo/i }));\n\n expect(screen.queryByText(/test todo/i)).not.toBeInTheDocument();\n });\n\n it('clears input after adding todo', async () => {\n const user = userEvent.setup();\n renderWithStore(\u003cTodoList />);\n\n const input = screen.getByPlaceholderText(/add todo/i);\n\n await user.type(input, 'New todo');\n await user.click(screen.getByRole('button', { name: /add/i }));\n\n expect(input).toHaveValue('');\n });\n});\n```\n\n---\n\n## 10. Testing with Context API\n\n### Component: Theme Context\n\n```javascript\n// ThemeContext.jsx\nimport { createContext, useContext, useState } from 'react';\n\nconst ThemeContext = createContext(null);\n\nexport const ThemeProvider = ({ children }) => {\n const [theme, setTheme] = useState('light');\n\n const toggleTheme = () => {\n setTheme((prev) => (prev === 'light' ? 'dark' : 'light'));\n };\n\n return (\n \u003cThemeContext.Provider value={{ theme, toggleTheme }}>\n {children}\n \u003c/ThemeContext.Provider>\n );\n};\n\nexport const useTheme = () => {\n const context = useContext(ThemeContext);\n if (!context) {\n throw new Error('useTheme must be used within ThemeProvider');\n }\n return context;\n};\n\nexport const ThemedButton = () => {\n const { theme, toggleTheme } = useTheme();\n\n return (\n \u003cbutton\n onClick={toggleTheme}\n style={{\n backgroundColor: theme === 'light' ? '#fff' : '#333',\n color: theme === 'light' ? '#000' : '#fff',\n }}\n >\n Toggle Theme (Current: {theme})\n \u003c/button>\n );\n};\n```\n\n### Tests\n\n```javascript\n// ThemeContext.test.jsx\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { ThemeProvider, ThemedButton, useTheme } from './ThemeContext';\n\ndescribe('Theme Context', () => {\n it('provides default theme', () => {\n render(\n \u003cThemeProvider>\n \u003cThemedButton />\n \u003c/ThemeProvider>\n );\n\n expect(screen.getByRole('button')).toHaveTextContent(/current: light/i);\n });\n\n it('toggles theme', async () => {\n const user = userEvent.setup();\n\n render(\n \u003cThemeProvider>\n \u003cThemedButton />\n \u003c/ThemeProvider>\n );\n\n const button = screen.getByRole('button');\n\n expect(button).toHaveTextContent(/current: light/i);\n expect(button).toHaveStyle({ backgroundColor: '#fff', color: '#000' });\n\n await user.click(button);\n\n expect(button).toHaveTextContent(/current: dark/i);\n expect(button).toHaveStyle({ backgroundColor: '#333', color: '#fff' });\n });\n\n it('throws error when used outside provider', () => {\n // Suppress console.error for this test\n const spy = jest.spyOn(console, 'error').mockImplementation(() => {});\n\n const InvalidComponent = () => {\n useTheme(); // Will throw\n return null;\n };\n\n expect(() => render(\u003cInvalidComponent />)).toThrow(\n /useTheme must be used within ThemeProvider/i\n );\n\n spy.mockRestore();\n });\n});\n```\n\n---\n\n## 11. File Upload Component\n\n### Component: FileUpload\n\n```javascript\n// FileUpload.jsx\nimport { useState } from 'react';\n\nexport const FileUpload = ({ onUpload, accept = '*', maxSize = 5 * 1024 * 1024 }) => {\n const [file, setFile] = useState(null);\n const [error, setError] = useState(null);\n const [uploading, setUploading] = useState(false);\n\n const handleFileChange = (e) => {\n const selectedFile = e.target.files[0];\n setError(null);\n\n if (!selectedFile) return;\n\n if (selectedFile.size > maxSize) {\n setError(`File size must be less than ${maxSize / 1024 / 1024}MB`);\n return;\n }\n\n setFile(selectedFile);\n };\n\n const handleUpload = async () => {\n if (!file) {\n setError('Please select a file');\n return;\n }\n\n setUploading(true);\n setError(null);\n\n try {\n await onUpload(file);\n setFile(null);\n } catch (err) {\n setError(err.message);\n } finally {\n setUploading(false);\n }\n };\n\n return (\n \u003cdiv>\n \u003cinput\n type=\"file\"\n onChange={handleFileChange}\n accept={accept}\n aria-label=\"File input\"\n />\n\n {file && \u003cp>Selected: {file.name}\u003c/p>}\n\n \u003cbutton onClick={handleUpload} disabled={!file || uploading}>\n {uploading ? 'Uploading...' : 'Upload'}\n \u003c/button>\n\n {error && \u003cdiv role=\"alert\">{error}\u003c/div>}\n \u003c/div>\n );\n};\n```\n\n### Tests\n\n```javascript\n// FileUpload.test.jsx\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { FileUpload } from './FileUpload';\n\ndescribe('FileUpload Component', () => {\n it('displays selected file name', async () => {\n const user = userEvent.setup();\n render(\u003cFileUpload onUpload={jest.fn()} />);\n\n const file = new File(['hello'], 'hello.png', { type: 'image/png' });\n const input = screen.getByLabelText(/file input/i);\n\n await user.upload(input, file);\n\n expect(screen.getByText(/selected: hello.png/i)).toBeInTheDocument();\n });\n\n it('uploads file successfully', async () => {\n const user = userEvent.setup();\n const handleUpload = jest.fn(() => Promise.resolve());\n\n render(\u003cFileUpload onUpload={handleUpload} />);\n\n const file = new File(['content'], 'test.txt', { type: 'text/plain' });\n const input = screen.getByLabelText(/file input/i);\n\n await user.upload(input, file);\n await user.click(screen.getByRole('button', { name: /upload/i }));\n\n expect(handleUpload).toHaveBeenCalledWith(file);\n });\n\n it('shows error for files exceeding max size', async () => {\n const user = userEvent.setup();\n const maxSize = 1024; // 1KB\n\n render(\u003cFileUpload onUpload={jest.fn()} maxSize={maxSize} />);\n\n const largeFile = new File(['x'.repeat(2000)], 'large.txt', {\n type: 'text/plain',\n });\n\n const input = screen.getByLabelText(/file input/i);\n\n await user.upload(input, largeFile);\n\n expect(screen.getByRole('alert')).toHaveTextContent(/file size must be less than/i);\n });\n\n it('shows uploading state', async () => {\n const user = userEvent.setup();\n const handleUpload = jest.fn(\n () => new Promise((resolve) => setTimeout(resolve, 100))\n );\n\n render(\u003cFileUpload onUpload={handleUpload} />);\n\n const file = new File(['content'], 'test.txt', { type: 'text/plain' });\n const input = screen.getByLabelText(/file input/i);\n\n await user.upload(input, file);\n const uploadButton = screen.getByRole('button', { name: /upload/i });\n\n await user.click(uploadButton);\n\n expect(screen.getByRole('button')).toHaveTextContent(/uploading/i);\n expect(screen.getByRole('button')).toBeDisabled();\n });\n\n it('disables upload button when no file selected', () => {\n render(\u003cFileUpload onUpload={jest.fn()} />);\n\n expect(screen.getByRole('button', { name: /upload/i })).toBeDisabled();\n });\n});\n```\n\n---\n\n## 12. Autocomplete Component\n\n### Component: Autocomplete\n\n```javascript\n// Autocomplete.jsx\nimport { useState, useEffect, useRef } from 'react';\n\nexport const Autocomplete = ({ options, onSelect, placeholder }) => {\n const [input, setInput] = useState('');\n const [filteredOptions, setFilteredOptions] = useState([]);\n const [isOpen, setIsOpen] = useState(false);\n const [highlightedIndex, setHighlightedIndex] = useState(-1);\n const inputRef = useRef(null);\n\n useEffect(() => {\n if (input) {\n const filtered = options.filter((option) =>\n option.toLowerCase().includes(input.toLowerCase())\n );\n setFilteredOptions(filtered);\n setIsOpen(true);\n } else {\n setFilteredOptions([]);\n setIsOpen(false);\n }\n }, [input, options]);\n\n const handleSelect = (option) => {\n setInput(option);\n setIsOpen(false);\n onSelect(option);\n };\n\n const handleKeyDown = (e) => {\n if (!isOpen) return;\n\n if (e.key === 'ArrowDown') {\n e.preventDefault();\n setHighlightedIndex((prev) =>\n prev \u003c filteredOptions.length - 1 ? prev + 1 : prev\n );\n } else if (e.key === 'ArrowUp') {\n e.preventDefault();\n setHighlightedIndex((prev) => (prev > 0 ? prev - 1 : prev));\n } else if (e.key === 'Enter' && highlightedIndex >= 0) {\n handleSelect(filteredOptions[highlightedIndex]);\n } else if (e.key === 'Escape') {\n setIsOpen(false);\n }\n };\n\n return (\n \u003cdiv>\n \u003cinput\n ref={inputRef}\n type=\"text\"\n value={input}\n onChange={(e) => setInput(e.target.value)}\n onKeyDown={handleKeyDown}\n placeholder={placeholder}\n aria-autocomplete=\"list\"\n aria-expanded={isOpen}\n aria-controls=\"autocomplete-list\"\n />\n\n {isOpen && filteredOptions.length > 0 && (\n \u003cul id=\"autocomplete-list\" role=\"listbox\">\n {filteredOptions.map((option, index) => (\n \u003cli\n key={option}\n role=\"option\"\n aria-selected={index === highlightedIndex}\n onClick={() => handleSelect(option)}\n style={{\n backgroundColor: index === highlightedIndex ? '#eee' : '#fff',\n }}\n >\n {option}\n \u003c/li>\n ))}\n \u003c/ul>\n )}\n \u003c/div>\n );\n};\n```\n\n### Tests\n\n```javascript\n// Autocomplete.test.jsx\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { Autocomplete } from './Autocomplete';\n\nconst options = ['Apple', 'Apricot', 'Banana', 'Cherry', 'Date'];\n\ndescribe('Autocomplete Component', () => {\n it('filters options based on input', async () => {\n const user = userEvent.setup();\n\n render(\u003cAutocomplete options={options} onSelect={jest.fn()} />);\n\n const input = screen.getByRole('textbox');\n\n await user.type(input, 'ap');\n\n expect(screen.getByRole('option', { name: /apple/i })).toBeInTheDocument();\n expect(screen.getByRole('option', { name: /apricot/i })).toBeInTheDocument();\n expect(screen.queryByRole('option', { name: /banana/i })).not.toBeInTheDocument();\n });\n\n it('calls onSelect when option clicked', async () => {\n const user = userEvent.setup();\n const handleSelect = jest.fn();\n\n render(\u003cAutocomplete options={options} onSelect={handleSelect} />);\n\n await user.type(screen.getByRole('textbox'), 'ban');\n await user.click(screen.getByRole('option', { name: /banana/i }));\n\n expect(handleSelect).toHaveBeenCalledWith('Banana');\n });\n\n it('navigates options with arrow keys', async () => {\n const user = userEvent.setup();\n\n render(\u003cAutocomplete options={options} onSelect={jest.fn()} />);\n\n const input = screen.getByRole('textbox');\n\n await user.type(input, 'a');\n\n const options = screen.getAllByRole('option');\n\n await user.keyboard('{ArrowDown}');\n expect(options[0]).toHaveAttribute('aria-selected', 'true');\n\n await user.keyboard('{ArrowDown}');\n expect(options[1]).toHaveAttribute('aria-selected', 'true');\n\n await user.keyboard('{ArrowUp}');\n expect(options[0]).toHaveAttribute('aria-selected', 'true');\n });\n\n it('selects option with Enter key', async () => {\n const user = userEvent.setup();\n const handleSelect = jest.fn();\n\n render(\u003cAutocomplete options={options} onSelect={handleSelect} />);\n\n const input = screen.getByRole('textbox');\n\n await user.type(input, 'a');\n await user.keyboard('{ArrowDown}');\n await user.keyboard('{Enter}');\n\n expect(handleSelect).toHaveBeenCalledWith('Apple');\n });\n\n it('closes list on Escape key', async () => {\n const user = userEvent.setup();\n\n render(\u003cAutocomplete options={options} onSelect={jest.fn()} />);\n\n await user.type(screen.getByRole('textbox'), 'a');\n\n expect(screen.getByRole('listbox')).toBeInTheDocument();\n\n await user.keyboard('{Escape}');\n\n expect(screen.queryByRole('listbox')).not.toBeInTheDocument();\n });\n\n it('case-insensitive filtering', async () => {\n const user = userEvent.setup();\n\n render(\u003cAutocomplete options={options} onSelect={jest.fn()} />);\n\n await user.type(screen.getByRole('textbox'), 'APP');\n\n expect(screen.getByRole('option', { name: /apple/i })).toBeInTheDocument();\n });\n});\n```\n\n---\n\n## 13. Infinite Scroll List\n\n### Component: InfiniteScrollList\n\n```javascript\n// InfiniteScrollList.jsx\nimport { useState, useEffect, useRef } from 'react';\n\nexport const InfiniteScrollList = ({ fetchItems, pageSize = 20 }) => {\n const [items, setItems] = useState([]);\n const [page, setPage] = useState(1);\n const [loading, setLoading] = useState(false);\n const [hasMore, setHasMore] = useState(true);\n const observerTarget = useRef(null);\n\n const loadMore = async () => {\n if (loading || !hasMore) return;\n\n setLoading(true);\n\n try {\n const newItems = await fetchItems(page, pageSize);\n\n if (newItems.length === 0) {\n setHasMore(false);\n } else {\n setItems((prev) => [...prev, ...newItems]);\n setPage((prev) => prev + 1);\n }\n } catch (error) {\n console.error('Failed to load items:', error);\n } finally {\n setLoading(false);\n }\n };\n\n useEffect(() => {\n loadMore();\n }, []);\n\n useEffect(() => {\n const observer = new IntersectionObserver(\n (entries) => {\n if (entries[0].isIntersecting && hasMore && !loading) {\n loadMore();\n }\n },\n { threshold: 1.0 }\n );\n\n if (observerTarget.current) {\n observer.observe(observerTarget.current);\n }\n\n return () => {\n if (observerTarget.current) {\n observer.unobserve(observerTarget.current);\n }\n };\n }, [hasMore, loading]);\n\n return (\n \u003cdiv>\n \u003cul>\n {items.map((item, index) => (\n \u003cli key={`${item.id}-${index}`}>{item.text}\u003c/li>\n ))}\n \u003c/ul>\n\n {loading && \u003cdiv>Loading more items...\u003c/div>}\n {!hasMore && \u003cdiv>No more items\u003c/div>}\n\n \u003cdiv ref={observerTarget} style={{ height: '20px' }} />\n \u003c/div>\n );\n};\n```\n\n### Tests\n\n```javascript\n// InfiniteScrollList.test.jsx\nimport { render, screen, waitFor } from '@testing-library/react';\nimport { InfiniteScrollList } from './InfiniteScrollList';\n\n// Mock IntersectionObserver\nconst mockIntersectionObserver = jest.fn();\nmockIntersectionObserver.mockReturnValue({\n observe: () => null,\n unobserve: () => null,\n disconnect: () => null,\n});\nwindow.IntersectionObserver = mockIntersectionObserver;\n\ndescribe('InfiniteScrollList Component', () => {\n it('loads initial items', async () => {\n const fetchItems = jest.fn(() =>\n Promise.resolve([\n { id: 1, text: 'Item 1' },\n { id: 2, text: 'Item 2' },\n ])\n );\n\n render(\u003cInfiniteScrollList fetchItems={fetchItems} />);\n\n await waitFor(() => {\n expect(screen.getByText(/item 1/i)).toBeInTheDocument();\n expect(screen.getByText(/item 2/i)).toBeInTheDocument();\n });\n\n expect(fetchItems).toHaveBeenCalledWith(1, 20);\n });\n\n it('shows loading state', () => {\n const fetchItems = jest.fn(\n () => new Promise((resolve) => setTimeout(resolve, 100))\n );\n\n render(\u003cInfiniteScrollList fetchItems={fetchItems} />);\n\n expect(screen.getByText(/loading more items/i)).toBeInTheDocument();\n });\n\n it('shows no more items message when all loaded', async () => {\n const fetchItems = jest.fn()\n .mockResolvedValueOnce([{ id: 1, text: 'Item 1' }])\n .mockResolvedValueOnce([]);\n\n render(\u003cInfiniteScrollList fetchItems={fetchItems} />);\n\n await waitFor(() => {\n expect(screen.getByText(/item 1/i)).toBeInTheDocument();\n });\n\n // Manually trigger loadMore for second call\n await waitFor(() => {\n expect(fetchItems).toHaveBeenCalledTimes(1);\n });\n });\n\n it('does not load more when already loading', async () => {\n const fetchItems = jest.fn(\n () => new Promise((resolve) => setTimeout(resolve, 1000))\n );\n\n render(\u003cInfiniteScrollList fetchItems={fetchItems} />);\n\n await waitFor(() => {\n expect(fetchItems).toHaveBeenCalledTimes(1);\n });\n });\n});\n```\n\n---\n\n## 14. Error Boundary Testing\n\n### Component: ErrorBoundary\n\n```javascript\n// ErrorBoundary.jsx\nimport React from 'react';\n\nexport class ErrorBoundary extends React.Component {\n constructor(props) {\n super(props);\n this.state = { hasError: false, error: null };\n }\n\n static getDerivedStateFromError(error) {\n return { hasError: true, error };\n }\n\n componentDidCatch(error, errorInfo) {\n console.error('Error caught by boundary:', error, errorInfo);\n this.props.onError?.(error, errorInfo);\n }\n\n render() {\n if (this.state.hasError) {\n return this.props.fallback || (\n \u003cdiv role=\"alert\">\n \u003ch1>Something went wrong\u003c/h1>\n \u003cp>{this.state.error?.message}\u003c/p>\n \u003c/div>\n );\n }\n\n return this.props.children;\n }\n}\n```\n\n### Tests\n\n```javascript\n// ErrorBoundary.test.jsx\nimport { render, screen } from '@testing-library/react';\nimport { ErrorBoundary } from './ErrorBoundary';\n\nconst ThrowError = ({ shouldThrow }) => {\n if (shouldThrow) {\n throw new Error('Test error');\n }\n return \u003cdiv>No error\u003c/div>;\n};\n\ndescribe('ErrorBoundary Component', () => {\n // Suppress console.error for these tests\n const originalError = console.error;\n\n beforeAll(() => {\n console.error = jest.fn();\n });\n\n afterAll(() => {\n console.error = originalError;\n });\n\n it('renders children when no error', () => {\n render(\n \u003cErrorBoundary>\n \u003cdiv>Child component\u003c/div>\n \u003c/ErrorBoundary>\n );\n\n expect(screen.getByText(/child component/i)).toBeInTheDocument();\n });\n\n it('renders error UI when error occurs', () => {\n render(\n \u003cErrorBoundary>\n \u003cThrowError shouldThrow={true} />\n \u003c/ErrorBoundary>\n );\n\n expect(screen.getByRole('alert')).toBeInTheDocument();\n expect(screen.getByText(/something went wrong/i)).toBeInTheDocument();\n expect(screen.getByText(/test error/i)).toBeInTheDocument();\n });\n\n it('renders custom fallback', () => {\n const fallback = \u003cdiv>Custom error message\u003c/div>;\n\n render(\n \u003cErrorBoundary fallback={fallback}>\n \u003cThrowError shouldThrow={true} />\n \u003c/ErrorBoundary>\n );\n\n expect(screen.getByText(/custom error message/i)).toBeInTheDocument();\n });\n\n it('calls onError callback', () => {\n const handleError = jest.fn();\n\n render(\n \u003cErrorBoundary onError={handleError}>\n \u003cThrowError shouldThrow={true} />\n \u003c/ErrorBoundary>\n );\n\n expect(handleError).toHaveBeenCalled();\n expect(handleError.mock.calls[0][0]).toEqual(expect.any(Error));\n });\n});\n```\n\n---\n\n## 15. Accessibility Testing\n\n### Component: AccessibleForm\n\n```javascript\n// AccessibleForm.jsx\nexport const AccessibleForm = ({ onSubmit }) => {\n const [name, setName] = useState('');\n const [errors, setErrors] = useState({});\n\n const handleSubmit = (e) => {\n e.preventDefault();\n\n if (!name) {\n setErrors({ name: 'Name is required' });\n return;\n }\n\n onSubmit({ name });\n };\n\n return (\n \u003cform onSubmit={handleSubmit} aria-labelledby=\"form-title\">\n \u003ch2 id=\"form-title\">User Information\u003c/h2>\n\n \u003cdiv>\n \u003clabel htmlFor=\"name-input\">\n Full Name \u003cspan aria-label=\"required\">*\u003c/span>\n \u003c/label>\n \u003cinput\n id=\"name-input\"\n type=\"text\"\n value={name}\n onChange={(e) => setName(e.target.value)}\n aria-required=\"true\"\n aria-invalid={errors.name ? 'true' : 'false'}\n aria-describedby={errors.name ? 'name-error' : undefined}\n />\n {errors.name && (\n \u003cdiv id=\"name-error\" role=\"alert\" aria-live=\"polite\">\n {errors.name}\n \u003c/div>\n )}\n \u003c/div>\n\n \u003cbutton type=\"submit\" aria-label=\"Submit form\">\n Submit\n \u003c/button>\n \u003c/form>\n );\n};\n```\n\n### Tests\n\n```javascript\n// AccessibleForm.test.jsx\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { AccessibleForm } from './AccessibleForm';\n\ndescribe('AccessibleForm - Accessibility', () => {\n it('has accessible form structure', () => {\n render(\u003cAccessibleForm onSubmit={jest.fn()} />);\n\n const form = screen.getByRole('form');\n expect(form).toHaveAccessibleName('User Information');\n });\n\n it('has accessible form fields', () => {\n render(\u003cAccessibleForm onSubmit={jest.fn()} />);\n\n const input = screen.getByLabelText(/full name/i);\n expect(input).toHaveAttribute('aria-required', 'true');\n expect(input).toHaveAttribute('id', 'name-input');\n });\n\n it('associates errors with input fields', async () => {\n const user = userEvent.setup();\n render(\u003cAccessibleForm onSubmit={jest.fn()} />);\n\n await user.click(screen.getByRole('button', { name: /submit form/i }));\n\n const input = screen.getByLabelText(/full name/i);\n expect(input).toHaveAttribute('aria-invalid', 'true');\n expect(input).toHaveAttribute('aria-describedby', 'name-error');\n\n const error = screen.getByRole('alert');\n expect(error).toHaveAttribute('id', 'name-error');\n });\n\n it('has accessible button', () => {\n render(\u003cAccessibleForm onSubmit={jest.fn()} />);\n\n const button = screen.getByRole('button', { name: /submit form/i });\n expect(button).toHaveAttribute('type', 'submit');\n });\n\n it('announces errors to screen readers', async () => {\n const user = userEvent.setup();\n render(\u003cAccessibleForm onSubmit={jest.fn()} />);\n\n await user.click(screen.getByRole('button'));\n\n const alert = screen.getByRole('alert');\n expect(alert).toHaveAttribute('aria-live', 'polite');\n expect(alert).toHaveTextContent(/name is required/i);\n });\n});\n```\n\n---\n\n## 16. Testing Timers and Intervals\n\n### Component: Countdown\n\n```javascript\n// Countdown.jsx\nimport { useState, useEffect } from 'react';\n\nexport const Countdown = ({ initialSeconds, onComplete }) => {\n const [seconds, setSeconds] = useState(initialSeconds);\n const [isActive, setIsActive] = useState(false);\n\n useEffect(() => {\n let interval = null;\n\n if (isActive && seconds > 0) {\n interval = setInterval(() => {\n setSeconds((prev) => prev - 1);\n }, 1000);\n } else if (seconds === 0) {\n onComplete?.();\n }\n\n return () => {\n if (interval) clearInterval(interval);\n };\n }, [isActive, seconds, onComplete]);\n\n const start = () => setIsActive(true);\n const pause = () => setIsActive(false);\n const reset = () => {\n setIsActive(false);\n setSeconds(initialSeconds);\n };\n\n return (\n \u003cdiv>\n \u003cdiv>{seconds} seconds remaining\u003c/div>\n \u003cbutton onClick={start} disabled={isActive}>\n Start\n \u003c/button>\n \u003cbutton onClick={pause} disabled={!isActive}>\n Pause\n \u003c/button>\n \u003cbutton onClick={reset}>Reset\u003c/button>\n \u003c/div>\n );\n};\n```\n\n### Tests\n\n```javascript\n// Countdown.test.jsx\nimport { render, screen, act } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { Countdown } from './Countdown';\n\ndescribe('Countdown Component', () => {\n beforeEach(() => {\n jest.useFakeTimers();\n });\n\n afterEach(() => {\n jest.useRealTimers();\n });\n\n it('displays initial countdown', () => {\n render(\u003cCountdown initialSeconds={10} />);\n\n expect(screen.getByText(/10 seconds remaining/i)).toBeInTheDocument();\n });\n\n it('counts down when started', async () => {\n const user = userEvent.setup({ delay: null });\n render(\u003cCountdown initialSeconds={5} />);\n\n await user.click(screen.getByRole('button', { name: /start/i }));\n\n act(() => {\n jest.advanceTimersByTime(1000);\n });\n\n expect(screen.getByText(/4 seconds remaining/i)).toBeInTheDocument();\n\n act(() => {\n jest.advanceTimersByTime(2000);\n });\n\n expect(screen.getByText(/2 seconds remaining/i)).toBeInTheDocument();\n });\n\n it('pauses countdown', async () => {\n const user = userEvent.setup({ delay: null });\n render(\u003cCountdown initialSeconds={10} />);\n\n await user.click(screen.getByRole('button', { name: /start/i }));\n\n act(() => {\n jest.advanceTimersByTime(3000);\n });\n\n expect(screen.getByText(/7 seconds remaining/i)).toBeInTheDocument();\n\n await user.click(screen.getByRole('button', { name: /pause/i }));\n\n act(() => {\n jest.advanceTimersByTime(5000);\n });\n\n // Should still be 7 after pausing\n expect(screen.getByText(/7 seconds remaining/i)).toBeInTheDocument();\n });\n\n it('resets countdown', async () => {\n const user = userEvent.setup({ delay: null });\n render(\u003cCountdown initialSeconds={10} />);\n\n await user.click(screen.getByRole('button', { name: /start/i }));\n\n act(() => {\n jest.advanceTimersByTime(3000);\n });\n\n await user.click(screen.getByRole('button', { name: /reset/i }));\n\n expect(screen.getByText(/10 seconds remaining/i)).toBeInTheDocument();\n });\n\n it('calls onComplete when countdown reaches zero', async () => {\n const user = userEvent.setup({ delay: null });\n const handleComplete = jest.fn();\n\n render(\u003cCountdown initialSeconds={3} onComplete={handleComplete} />);\n\n await user.click(screen.getByRole('button', { name: /start/i }));\n\n act(() => {\n jest.advanceTimersByTime(3000);\n });\n\n expect(handleComplete).toHaveBeenCalledTimes(1);\n expect(screen.getByText(/0 seconds remaining/i)).toBeInTheDocument();\n });\n\n it('cleans up interval on unmount', () => {\n const clearIntervalSpy = jest.spyOn(global, 'clearInterval');\n const { unmount } = render(\u003cCountdown initialSeconds={10} />);\n\n unmount();\n\n expect(clearIntervalSpy).toHaveBeenCalled();\n });\n});\n```\n\n---\n\n## 17. Testing WebSocket Integration\n\n### Component: ChatRoom\n\n```javascript\n// ChatRoom.jsx\nimport { useState, useEffect, useRef } from 'react';\n\nexport const ChatRoom = ({ roomId, websocketUrl }) => {\n const [messages, setMessages] = useState([]);\n const [input, setInput] = useState('');\n const [connectionStatus, setConnectionStatus] = useState('disconnected');\n const wsRef = useRef(null);\n\n useEffect(() => {\n const ws = new WebSocket(websocketUrl);\n wsRef.current = ws;\n\n ws.onopen = () => {\n setConnectionStatus('connected');\n ws.send(JSON.stringify({ type: 'join', roomId }));\n };\n\n ws.onmessage = (event) => {\n const data = JSON.parse(event.data);\n setMessages((prev) => [...prev, data]);\n };\n\n ws.onerror = () => {\n setConnectionStatus('error');\n };\n\n ws.onclose = () => {\n setConnectionStatus('disconnected');\n };\n\n return () => {\n ws.close();\n };\n }, [roomId, websocketUrl]);\n\n const sendMessage = () => {\n if (input.trim() && wsRef.current?.readyState === WebSocket.OPEN) {\n wsRef.current.send(\n JSON.stringify({\n type: 'message',\n text: input,\n })\n );\n setInput('');\n }\n };\n\n return (\n \u003cdiv>\n \u003cdiv>Status: {connectionStatus}\u003c/div>\n\n \u003cul>\n {messages.map((msg, index) => (\n \u003cli key={index}>{msg.text}\u003c/li>\n ))}\n \u003c/ul>\n\n \u003cinput\n value={input}\n onChange={(e) => setInput(e.target.value)}\n placeholder=\"Type a message\"\n />\n \u003cbutton onClick={sendMessage}>Send\u003c/button>\n \u003c/div>\n );\n};\n```\n\n### Tests\n\n```javascript\n// ChatRoom.test.jsx\nimport { render, screen, waitFor } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { ChatRoom } from './ChatRoom';\nimport WS from 'jest-websocket-mock';\n\ndescribe('ChatRoom Component', () => {\n let server;\n\n beforeEach(async () => {\n server = new WS('ws://localhost:1234');\n });\n\n afterEach(() => {\n WS.clean();\n });\n\n it('connects to websocket on mount', async () => {\n render(\u003cChatRoom roomId=\"test-room\" websocketUrl=\"ws://localhost:1234\" />);\n\n await server.connected;\n\n await waitFor(() => {\n expect(screen.getByText(/status: connected/i)).toBeInTheDocument();\n });\n });\n\n it('sends join message on connection', async () => {\n render(\u003cChatRoom roomId=\"test-room\" websocketUrl=\"ws://localhost:1234\" />);\n\n await server.connected;\n await expect(server).toReceiveMessage(\n JSON.stringify({ type: 'join', roomId: 'test-room' })\n );\n });\n\n it('displays received messages', async () => {\n render(\u003cChatRoom roomId=\"test-room\" websocketUrl=\"ws://localhost:1234\" />);\n\n await server.connected;\n\n server.send(JSON.stringify({ type: 'message', text: 'Hello!' }));\n\n await waitFor(() => {\n expect(screen.getByText('Hello!')).toBeInTheDocument();\n });\n });\n\n it('sends messages', async () => {\n const user = userEvent.setup();\n render(\u003cChatRoom roomId=\"test-room\" websocketUrl=\"ws://localhost:1234\" />);\n\n await server.connected;\n\n const input = screen.getByPlaceholderText(/type a message/i);\n const sendButton = screen.getByRole('button', { name: /send/i });\n\n await user.type(input, 'Test message');\n await user.click(sendButton);\n\n await expect(server).toReceiveMessage(\n JSON.stringify({ type: 'message', text: 'Test message' })\n );\n\n expect(input).toHaveValue('');\n });\n\n it('shows error status on connection error', async () => {\n render(\u003cChatRoom roomId=\"test-room\" websocketUrl=\"ws://localhost:1234\" />);\n\n server.error();\n\n await waitFor(() => {\n expect(screen.getByText(/status: error/i)).toBeInTheDocument();\n });\n });\n});\n```\n\n---\n\n## 18. Multi-step Form Wizard\n\n### Component: FormWizard\n\n```javascript\n// FormWizard.jsx\nimport { useState } from 'react';\n\nexport const FormWizard = ({ steps, onComplete }) => {\n const [currentStep, setCurrentStep] = useState(0);\n const [formData, setFormData] = useState({});\n\n const handleNext = (stepData) => {\n const updatedData = { ...formData, ...stepData };\n setFormData(updatedData);\n\n if (currentStep === steps.length - 1) {\n onComplete(updatedData);\n } else {\n setCurrentStep((prev) => prev + 1);\n }\n };\n\n const handleBack = () => {\n setCurrentStep((prev) => Math.max(0, prev - 1));\n };\n\n const CurrentStepComponent = steps[currentStep].component;\n\n return (\n \u003cdiv>\n \u003cdiv>\n Step {currentStep + 1} of {steps.length}: {steps[currentStep].title}\n \u003c/div>\n\n \u003cdiv role=\"progressbar\" aria-valuenow={currentStep + 1} aria-valuemax={steps.length}>\n Progress: {Math.round(((currentStep + 1) / steps.length) * 100)}%\n \u003c/div>\n\n \u003cCurrentStepComponent\n onNext={handleNext}\n onBack={handleBack}\n initialData={formData}\n isFirstStep={currentStep === 0}\n isLastStep={currentStep === steps.length - 1}\n />\n \u003c/div>\n );\n};\n```\n\n### Tests\n\n```javascript\n// FormWizard.test.jsx\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { FormWizard } from './FormWizard';\n\n// Mock step components\nconst Step1 = ({ onNext, isFirstStep }) => (\n \u003cdiv>\n \u003ch2>Step 1 Content\u003c/h2>\n \u003cbutton onClick={() => onNext({ step1Data: 'value1' })}>\n {isFirstStep ? 'Next' : 'Continue'}\n \u003c/button>\n \u003c/div>\n);\n\nconst Step2 = ({ onNext, onBack }) => (\n \u003cdiv>\n \u003ch2>Step 2 Content\u003c/h2>\n \u003cbutton onClick={onBack}>Back\u003c/button>\n \u003cbutton onClick={() => onNext({ step2Data: 'value2' })}>Next\u003c/button>\n \u003c/div>\n);\n\nconst Step3 = ({ onNext, isLastStep }) => (\n \u003cdiv>\n \u003ch2>Step 3 Content\u003c/h2>\n \u003cbutton onClick={() => onNext({ step3Data: 'value3' })}>\n {isLastStep ? 'Submit' : 'Next'}\n \u003c/button>\n \u003c/div>\n);\n\nconst steps = [\n { title: 'Personal Info', component: Step1 },\n { title: 'Address', component: Step2 },\n { title: 'Review', component: Step3 },\n];\n\ndescribe('FormWizard Component', () => {\n it('renders first step', () => {\n render(\u003cFormWizard steps={steps} onComplete={jest.fn()} />);\n\n expect(screen.getByText(/step 1 of 3: personal info/i)).toBeInTheDocument();\n expect(screen.getByText(/step 1 content/i)).toBeInTheDocument();\n });\n\n it('advances to next step', async () => {\n const user = userEvent.setup();\n render(\u003cFormWizard steps={steps} onComplete={jest.fn()} />);\n\n await user.click(screen.getByRole('button', { name: /next/i }));\n\n expect(screen.getByText(/step 2 of 3: address/i)).toBeInTheDocument();\n expect(screen.getByText(/step 2 content/i)).toBeInTheDocument();\n });\n\n it('goes back to previous step', async () => {\n const user = userEvent.setup();\n render(\u003cFormWizard steps={steps} onComplete={jest.fn()} />);\n\n await user.click(screen.getByRole('button', { name: /next/i }));\n expect(screen.getByText(/step 2 content/i)).toBeInTheDocument();\n\n await user.click(screen.getByRole('button', { name: /back/i }));\n expect(screen.getByText(/step 1 content/i)).toBeInTheDocument();\n });\n\n it('completes wizard and calls onComplete with all data', async () => {\n const user = userEvent.setup();\n const handleComplete = jest.fn();\n\n render(\u003cFormWizard steps={steps} onComplete={handleComplete} />);\n\n // Step 1\n await user.click(screen.getByRole('button', { name: /next/i }));\n\n // Step 2\n await user.click(screen.getAllByRole('button', { name: /next/i })[1]);\n\n // Step 3 - Submit\n await user.click(screen.getByRole('button', { name: /submit/i }));\n\n expect(handleComplete).toHaveBeenCalledWith({\n step1Data: 'value1',\n step2Data: 'value2',\n step3Data: 'value3',\n });\n });\n\n it('shows correct progress', async () => {\n const user = userEvent.setup();\n render(\u003cFormWizard steps={steps} onComplete={jest.fn()} />);\n\n expect(screen.getByText(/progress: 33%/i)).toBeInTheDocument();\n\n await user.click(screen.getByRole('button', { name: /next/i }));\n\n expect(screen.getByText(/progress: 67%/i)).toBeInTheDocument();\n });\n\n it('has accessible progressbar', () => {\n render(\u003cFormWizard steps={steps} onComplete={jest.fn()} />);\n\n const progressbar = screen.getByRole('progressbar');\n expect(progressbar).toHaveAttribute('aria-valuenow', '1');\n expect(progressbar).toHaveAttribute('aria-valuemax', '3');\n });\n});\n```\n\n---\n\n## Summary\n\nThese 18 comprehensive examples cover:\n\n1. **Basic component testing** - Props, events, rendering\n2. **Form validation** - Input validation, error handling\n3. **Async operations** - API calls, loading states\n4. **Authentication** - Login/logout flows with context\n5. **Modals** - Portals, keyboard events, accessibility\n6. **Dropdowns** - Complex UI interactions\n7. **Custom hooks** - Hook testing patterns\n8. **React Router** - Navigation testing\n9. **Redux** - State management testing\n10. **Context API** - Provider testing\n11. **File uploads** - File handling, validation\n12. **Autocomplete** - Keyboard navigation, filtering\n13. **Infinite scroll** - Intersection Observer\n14. **Error boundaries** - Error handling\n15. **Accessibility** - ARIA attributes, screen readers\n16. **Timers** - Fake timers, intervals\n17. **WebSockets** - Real-time communication\n18. **Multi-step forms** - Wizard flows, progress tracking\n\nEach example demonstrates real-world patterns with complete, runnable test code following Jest and React Testing Library best practices.\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":68465,"content_sha256":"2daca8d23c09d8313fe1a4a880c81e1fd269fde05f0050e366b94f2fa1e3f35d"},{"filename":"README.md","content":"# Jest React Testing\n\n> Comprehensive testing guide for React applications using Jest and React Testing Library\n\n## Overview\n\nThis skill provides a complete guide to testing React applications using Jest and React Testing Library. It covers everything from basic setup to advanced patterns, focusing on writing maintainable, user-centric tests that give you confidence in your application.\n\n## What This Skill Covers\n\n- **Jest Configuration**: Complete setup for JavaScript and TypeScript projects\n- **React Testing Library**: Query strategies, user interactions, and best practices\n- **Mocking Strategies**: Modules, functions, API calls, context, and child components\n- **Async Testing**: Promises, timers, API calls, and loading states\n- **Hooks Testing**: Custom hooks, context hooks, and async hooks\n- **Integration Testing**: Multi-component flows, routing, and state management\n- **Best Practices**: Accessible queries, test organization, and maintainable patterns\n\n## Philosophy\n\nThis skill follows the core principles of Testing Library:\n\n1. **Test Behavior, Not Implementation**\n - Focus on what users see and do\n - Avoid testing internal state or implementation details\n - Tests should break when behavior changes, not when code refactors\n\n2. **Accessibility First**\n - Use queries that encourage accessible components\n - Prefer `getByRole` and `getByLabelText` over `getByTestId`\n - Build components that work for all users\n\n3. **Confidence Over Coverage**\n - Write tests that give real confidence\n - Focus on critical user paths\n - Don't chase 100% coverage at the expense of test quality\n\n4. **Maintainable Tests**\n - Clear, descriptive test names\n - Simple, focused test cases\n - Minimal mocking and setup\n\n## Setup\n\n### Installation\n\nFor React 18+ projects:\n```bash\nnpm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event jest jest-environment-jsdom\n```\n\nFor TypeScript projects, also install:\n```bash\nnpm install --save-dev @types/jest ts-jest\n```\n\n### Basic Configuration\n\nCreate `jest.config.js` in your project root:\n\n```javascript\n/** @type {import('jest').Config} */\nconst config = {\n testEnvironment: 'jsdom',\n setupFilesAfterEnv: ['\u003crootDir>/src/setupTests.js'],\n moduleNameMapper: {\n '\\\\.(css|less|scss|sass)

Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…

: 'identity-obj-proxy',\n },\n};\n\nmodule.exports = config;\n```\n\nCreate `src/setupTests.js`:\n\n```javascript\nimport '@testing-library/jest-dom';\n```\n\nAdd test script to `package.json`:\n\n```json\n{\n \"scripts\": {\n \"test\": \"jest\",\n \"test:watch\": \"jest --watch\",\n \"test:coverage\": \"jest --coverage\"\n }\n}\n```\n\n## Quick Start\n\n### Basic Component Test\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport { Greeting } from './Greeting';\n\ntest('renders greeting message', () => {\n render(\u003cGreeting name=\"World\" />);\n expect(screen.getByText(/hello, world/i)).toBeInTheDocument();\n});\n```\n\n### Testing User Interactions\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { Counter } from './Counter';\n\ntest('increments counter on click', async () => {\n const user = userEvent.setup();\n render(\u003cCounter />);\n\n await user.click(screen.getByRole('button', { name: /increment/i }));\n\n expect(screen.getByText(/count: 1/i)).toBeInTheDocument();\n});\n```\n\n### Testing Async Operations\n\n```javascript\nimport { render, screen } from '@testing-library/react';\nimport { UserProfile } from './UserProfile';\n\ntest('loads and displays user data', async () => {\n render(\u003cUserProfile userId={1} />);\n\n expect(screen.getByText(/loading/i)).toBeInTheDocument();\n\n const username = await screen.findByText(/john doe/i);\n expect(username).toBeInTheDocument();\n});\n```\n\n## Core Concepts\n\n### Query Priority\n\nUse queries in this order (most to least preferred):\n\n1. **getByRole** - Most accessible, reflects how users find elements\n ```javascript\n screen.getByRole('button', { name: /submit/i })\n ```\n\n2. **getByLabelText** - Good for form fields\n ```javascript\n screen.getByLabelText(/email/i)\n ```\n\n3. **getByPlaceholderText** - For inputs with placeholders\n ```javascript\n screen.getByPlaceholderText(/search/i)\n ```\n\n4. **getByText** - For non-interactive elements\n ```javascript\n screen.getByText(/welcome/i)\n ```\n\n5. **getByTestId** - Last resort only\n ```javascript\n screen.getByTestId('custom-element')\n ```\n\n### Query Types\n\n- **getBy**: Throws error if not found (use when element should exist)\n- **queryBy**: Returns null if not found (use to assert absence)\n- **findBy**: Returns promise, waits for element (use for async)\n\n```javascript\n// Element should exist\nconst button = screen.getByRole('button');\n\n// Element might not exist\nconst error = screen.queryByText(/error/i);\nexpect(error).not.toBeInTheDocument();\n\n// Element appears after async operation\nconst data = await screen.findByText(/loaded/i);\n```\n\n### User Events vs Fire Event\n\nAlways prefer `userEvent` over `fireEvent`:\n\n```javascript\n// Good - simulates real user interactions\nimport userEvent from '@testing-library/user-event';\n\nconst user = userEvent.setup();\nawait user.click(button);\nawait user.type(input, 'text');\n\n// Avoid - lower-level, doesn't simulate real interactions\nimport { fireEvent } from '@testing-library/react';\n\nfireEvent.click(button);\nfireEvent.change(input, { target: { value: 'text' } });\n```\n\n## Testing Patterns\n\n### Forms\n\n```javascript\ntest('submits form with user data', async () => {\n const user = userEvent.setup();\n const handleSubmit = jest.fn();\n\n render(\u003cSignupForm onSubmit={handleSubmit} />);\n\n await user.type(screen.getByLabelText(/email/i), '[email protected]');\n await user.type(screen.getByLabelText(/password/i), 'password123');\n await user.click(screen.getByRole('button', { name: /sign up/i }));\n\n expect(handleSubmit).toHaveBeenCalledWith({\n email: '[email protected]',\n password: 'password123',\n });\n});\n```\n\n### Conditional Rendering\n\n```javascript\ntest('shows error when loading fails', async () => {\n render(\u003cDataComponent url=\"/api/fail\" />);\n\n const error = await screen.findByRole('alert');\n expect(error).toHaveTextContent(/failed to load/i);\n});\n```\n\n### Lists\n\n```javascript\ntest('renders all items', () => {\n const items = ['Apple', 'Banana', 'Cherry'];\n render(\u003cItemList items={items} />);\n\n const listItems = screen.getAllByRole('listitem');\n expect(listItems).toHaveLength(3);\n});\n```\n\n## Mocking\n\n### API Calls with MSW\n\n```javascript\nimport { rest } from 'msw';\nimport { setupServer } from 'msw/node';\n\nconst server = setupServer(\n rest.get('/api/user', (req, res, ctx) => {\n return res(ctx.json({ name: 'John' }));\n })\n);\n\nbeforeAll(() => server.listen());\nafterEach(() => server.resetHandlers());\nafterAll(() => server.close());\n```\n\n### Functions\n\n```javascript\ntest('calls onClick when clicked', async () => {\n const user = userEvent.setup();\n const handleClick = jest.fn();\n\n render(\u003cButton onClick={handleClick}>Click\u003c/Button>);\n\n await user.click(screen.getByRole('button'));\n\n expect(handleClick).toHaveBeenCalledTimes(1);\n});\n```\n\n### Modules\n\n```javascript\njest.mock('./api', () => ({\n fetchUser: jest.fn(() => Promise.resolve({ name: 'John' })),\n}));\n```\n\n## Testing Custom Hooks\n\n```javascript\nimport { renderHook, act } from '@testing-library/react';\nimport { useCounter } from './useCounter';\n\ntest('increments counter', () => {\n const { result } = renderHook(() => useCounter());\n\n act(() => {\n result.current.increment();\n });\n\n expect(result.current.count).toBe(1);\n});\n```\n\n## Integration Testing\n\n### Testing with Router\n\n```javascript\nimport { MemoryRouter } from 'react-router-dom';\n\nconst renderWithRouter = (ui, { initialEntries = ['/'] } = {}) => {\n return render(\n \u003cMemoryRouter initialEntries={initialEntries}>\n {ui}\n \u003c/MemoryRouter>\n );\n};\n```\n\n### Testing with Redux\n\n```javascript\nimport { Provider } from 'react-redux';\nimport { configureStore } from '@reduxjs/toolkit';\n\nconst createMockStore = (initialState) => {\n return configureStore({\n reducer: rootReducer,\n preloadedState: initialState,\n });\n};\n\nconst renderWithStore = (ui, { store = createMockStore() } = {}) => {\n return render(\u003cProvider store={store}>{ui}\u003c/Provider>);\n};\n```\n\n## Best Practices\n\n### Do's\n\n- Use accessible queries (getByRole, getByLabelText)\n- Test user behavior, not implementation\n- Use userEvent for interactions\n- Write descriptive test names\n- Keep tests simple and focused\n- Use findBy for async elements\n- Clean up after tests\n- Test error states\n\n### Don'ts\n\n- Don't test implementation details\n- Don't use getByTestId unless necessary\n- Don't over-mock\n- Don't test internal state\n- Don't use fireEvent when userEvent works\n- Don't skip accessibility\n- Don't chase 100% coverage\n- Don't write brittle tests\n\n## Debugging\n\n### View DOM Structure\n\n```javascript\nimport { screen } from '@testing-library/react';\n\nscreen.debug(); // Prints entire DOM\nscreen.debug(element); // Prints specific element\n```\n\n### See Available Queries\n\n```javascript\nscreen.logTestingPlaygroundURL(); // Opens Testing Playground with current DOM\n```\n\n### Common Issues\n\n**Can't find element**:\n- Use `screen.debug()` to see DOM\n- Check query type (getBy vs queryBy vs findBy)\n- Verify element has correct role/text\n- Wait for async updates with findBy\n\n**Act warnings**:\n- Use userEvent instead of fireEvent\n- Use waitFor/findBy for async updates\n- Wrap state updates in act()\n\n**Test timeout**:\n- Increase timeout in waitFor\n- Check for infinite loops\n- Ensure promises resolve\n\n## Resources\n\n### Documentation\n\n- [Jest Documentation](https://jestjs.io/)\n- [React Testing Library](https://testing-library.com/react)\n- [Testing Library Queries](https://testing-library.com/docs/queries/about)\n- [Jest DOM Matchers](https://github.com/testing-library/jest-dom)\n- [User Event](https://testing-library.com/docs/user-event/intro)\n\n### Guides\n\n- [Common Mistakes](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library)\n- [Testing Playground](https://testing-playground.com/)\n- [Which Query Should I Use?](https://testing-library.com/docs/queries/about#priority)\n\n### Tools\n\n- [Testing Library Chrome Extension](https://chrome.google.com/webstore/detail/testing-library/testing-playground)\n- [MSW (Mock Service Worker)](https://mswjs.io/)\n- [Jest Preview](https://www.jest-preview.com/)\n\n## Examples\n\nSee `EXAMPLES.md` for 15+ comprehensive test examples covering:\n- Component testing\n- Form validation\n- API integration\n- Custom hooks\n- Error handling\n- Accessibility\n- Integration testing\n- And more!\n\n## Contributing\n\nThis skill is designed to be comprehensive but approachable. If you find areas that need clarification or additional examples, contributions are welcome!\n\n## License\n\nThis skill documentation is provided as-is for educational purposes.\n\n---\n\n**Version**: 1.0.0\n**Last Updated**: October 2025\n**Maintained by**: Claude Skills Team\n","content_type":"text/markdown; charset=utf-8","language":"markdown","size":10992,"content_sha256":"445e63fa8bd8966b195e01a5862e59110a3e5607cd9f5e2d994b0ae1d43ee3aa"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Jest React Testing","type":"text"}]},{"type":"paragraph","content":[{"text":"A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"When to Use This Skill","type":"text"}]},{"type":"paragraph","content":[{"text":"Use this skill when:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testing React components with Jest and React Testing Library","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Setting up Jest configuration for React projects","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Writing unit tests for components, hooks, and utilities","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testing user interactions and component behavior","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mocking modules, functions, API calls, and external dependencies","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testing asynchronous operations (API calls, timers, promises)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testing custom React hooks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Writing integration tests for complex component trees","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Debugging failing tests or improving test coverage","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Following testing best practices and patterns","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Core Concepts","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Philosophy","type":"text"}]},{"type":"paragraph","content":[{"text":"React Testing Library follows these guiding principles:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test User Behavior, Not Implementation","type":"text","marks":[{"type":"strong"}]},{"text":": Write tests that resemble how users interact with your app","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Accessibility First","type":"text","marks":[{"type":"strong"}]},{"text":": Use queries that promote accessible components (getByRole, getByLabelText)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid Testing Implementation Details","type":"text","marks":[{"type":"strong"}]},{"text":": Don't test state, props, or internal methods directly","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Maintainable Tests","type":"text","marks":[{"type":"strong"}]},{"text":": Tests should break when behavior changes, not when code refactors","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Confidence Over Coverage","type":"text","marks":[{"type":"strong"}]},{"text":": Focus on tests that give confidence, not 100% coverage","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Key Testing Concepts","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Queries","type":"text","marks":[{"type":"strong"}]},{"text":": Methods to find elements (getBy, queryBy, findBy)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"User Events","type":"text","marks":[{"type":"strong"}]},{"text":": Simulating user interactions (click, type, select)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Async Testing","type":"text","marks":[{"type":"strong"}]},{"text":": Testing components with asynchronous operations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mocking","type":"text","marks":[{"type":"strong"}]},{"text":": Replacing dependencies with controlled test doubles","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Assertions","type":"text","marks":[{"type":"strong"}]},{"text":": Verifying expected outcomes with matchers","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Jest Configuration","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Basic Jest Configuration","type":"text"}]},{"type":"paragraph","content":[{"text":"jest.config.js","type":"text","marks":[{"type":"strong"}]},{"text":" (JavaScript projects):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"/** @type {import('jest').Config} */\nconst config = {\n // Test environment for DOM testing\n testEnvironment: 'jsdom',\n\n // Setup files after environment\n setupFilesAfterEnv: ['\u003crootDir>/src/setupTests.js'],\n\n // Module paths\n moduleDirectories: ['node_modules', 'src'],\n\n // Transform files with babel-jest\n transform: {\n '^.+\\\\.(js|jsx)

Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…

: 'babel-jest',\n },\n\n // Module name mapper for static assets and CSS\n moduleNameMapper: {\n '\\\\.(css|less|scss|sass)

Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…

: 'identity-obj-proxy',\n '\\\\.(jpg|jpeg|png|gif|svg)

Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…

: '\u003crootDir>/__mocks__/fileMock.js',\n },\n\n // Coverage configuration\n collectCoverageFrom: [\n 'src/**/*.{js,jsx}',\n '!src/index.js',\n '!src/**/*.test.{js,jsx}',\n '!src/**/__tests__/**',\n ],\n\n // Coverage thresholds\n coverageThreshold: {\n global: {\n branches: 80,\n functions: 80,\n lines: 80,\n statements: 80,\n },\n },\n};\n\nmodule.exports = config;","type":"text"}]},{"type":"paragraph","content":[{"text":"jest.config.js","type":"text","marks":[{"type":"strong"}]},{"text":" (TypeScript projects):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"typescript"},"content":[{"text":"import type {Config} from 'jest';\n\nconst config: Config = {\n preset: 'ts-jest',\n testEnvironment: 'jsdom',\n setupFilesAfterEnv: ['\u003crootDir>/src/setupTests.ts'],\n\n moduleDirectories: ['node_modules', 'src'],\n\n transform: {\n '^.+\\\\.tsx?

Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…

: 'ts-jest',\n },\n\n moduleNameMapper: {\n '\\\\.(css|less|scss|sass)

Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…

: 'identity-obj-proxy',\n '\\\\.(jpg|jpeg|png|gif|svg)

Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…

: '\u003crootDir>/__mocks__/fileMock.ts',\n '^@/(.*)

Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…

: '\u003crootDir>/src/$1',\n },\n\n collectCoverageFrom: [\n 'src/**/*.{ts,tsx}',\n '!src/index.tsx',\n '!src/**/*.test.{ts,tsx}',\n '!src/**/__tests__/**',\n '!src/**/*.d.ts',\n ],\n\n coverageThreshold: {\n global: {\n branches: 80,\n functions: 80,\n lines: 80,\n statements: 80,\n },\n },\n};\n\nexport default config;","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Setup Files","type":"text"}]},{"type":"paragraph","content":[{"text":"src/setupTests.js","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// Add custom jest matchers from jest-dom\nimport '@testing-library/jest-dom';\n\n// Extend expect with jest-extended matchers (optional)\nimport * as matchers from 'jest-extended';\nexpect.extend(matchers);\n\n// Mock window.matchMedia\nObject.defineProperty(window, 'matchMedia', {\n writable: true,\n value: jest.fn().mockImplementation(query => ({\n matches: false,\n media: query,\n onchange: null,\n addListener: jest.fn(),\n removeListener: jest.fn(),\n addEventListener: jest.fn(),\n removeEventListener: jest.fn(),\n dispatchEvent: jest.fn(),\n })),\n});\n\n// Mock IntersectionObserver\nglobal.IntersectionObserver = class IntersectionObserver {\n constructor() {}\n disconnect() {}\n observe() {}\n takeRecords() {\n return [];\n }\n unobserve() {}\n};\n\n// Suppress console errors in tests (optional)\nconst originalError = console.error;\nbeforeAll(() => {\n console.error = (...args) => {\n if (\n typeof args[0] === 'string' &&\n args[0].includes('Warning: ReactDOM.render')\n ) {\n return;\n }\n originalError.call(console, ...args);\n };\n});\n\nafterAll(() => {\n console.error = originalError;\n});\n\n// Reset mocks after each test\nafterEach(() => {\n jest.clearAllMocks();\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"File Mocks","type":"text"}]},{"type":"paragraph","content":[{"text":"mocks","type":"text","marks":[{"type":"strong"},{"type":"underline"}]},{"text":"/fileMock.js","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"module.exports = 'test-file-stub';","type":"text"}]},{"type":"paragraph","content":[{"text":"mocks","type":"text","marks":[{"type":"strong"},{"type":"underline"}]},{"text":"/styleMock.js","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"module.exports = {};","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"React Testing Library Queries","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Query Types","type":"text"}]},{"type":"paragraph","content":[{"text":"React Testing Library provides three types of queries:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"getBy","type":"text","marks":[{"type":"strong"}]},{"text":": Returns element or throws error (use for elements that should exist)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"queryBy","type":"text","marks":[{"type":"strong"}]},{"text":": Returns element or null (use for elements that may not exist)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"findBy","type":"text","marks":[{"type":"strong"}]},{"text":": Returns promise that resolves to element (use for async elements)","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Query Priority","type":"text"}]},{"type":"paragraph","content":[{"text":"Recommended Query Order","type":"text","marks":[{"type":"strong"}]},{"text":" (accessibility-focused):","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"getByRole","type":"text","marks":[{"type":"strong"}]},{"text":": Most accessible query","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"getByRole('button', { name: /submit/i })\ngetByRole('heading', { level: 1 })\ngetByRole('textbox', { name: /username/i })","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"getByLabelText","type":"text","marks":[{"type":"strong"}]},{"text":": For form fields with labels","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"getByLabelText(/email address/i)\ngetByLabelText('Password')","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"getByPlaceholderText","type":"text","marks":[{"type":"strong"}]},{"text":": For inputs with placeholders","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"getByPlaceholderText(/search/i)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"getByText","type":"text","marks":[{"type":"strong"}]},{"text":": For non-interactive elements with text","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"getByText(/welcome/i)\ngetByText('Error: Invalid credentials')","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"getByDisplayValue","type":"text","marks":[{"type":"strong"}]},{"text":": For form elements with values","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"getByDisplayValue('John Doe')","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"getByAltText","type":"text","marks":[{"type":"strong"}]},{"text":": For images with alt text","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"getByAltText(/profile picture/i)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"getByTitle","type":"text","marks":[{"type":"strong"}]},{"text":": For elements with title attribute","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"getByTitle(/close/i)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"getByTestId","type":"text","marks":[{"type":"strong"}]},{"text":": Last resort when other queries don't work","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"getByTestId('custom-element')","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Query Variants","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// Single element queries\nscreen.getByRole('button') // Throws if not found or multiple found\nscreen.queryByRole('button') // Returns null if not found\nawait screen.findByRole('button') // Async, waits up to 1000ms\n\n// Multiple element queries\nscreen.getAllByRole('listitem') // Throws if none found\nscreen.queryAllByRole('listitem') // Returns [] if none found\nawait screen.findAllByRole('listitem') // Async version","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Component Testing Strategies","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Basic Component Test","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { render, screen } from '@testing-library/react';\nimport { Greeting } from './Greeting';\n\ndescribe('Greeting Component', () => {\n it('renders greeting message', () => {\n render(\u003cGreeting name=\"Alice\" />);\n\n expect(screen.getByText(/hello, alice/i)).toBeInTheDocument();\n });\n\n it('renders default greeting when no name provided', () => {\n render(\u003cGreeting />);\n\n expect(screen.getByText(/hello, guest/i)).toBeInTheDocument();\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing User Interactions","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { Counter } from './Counter';\n\ndescribe('Counter Component', () => {\n it('increments counter on button click', async () => {\n const user = userEvent.setup();\n render(\u003cCounter />);\n\n const button = screen.getByRole('button', { name: /increment/i });\n const count = screen.getByText(/count: 0/i);\n\n expect(count).toBeInTheDocument();\n\n await user.click(button);\n\n expect(screen.getByText(/count: 1/i)).toBeInTheDocument();\n });\n\n it('decrements counter on button click', async () => {\n const user = userEvent.setup();\n render(\u003cCounter initialCount={5} />);\n\n const decrementBtn = screen.getByRole('button', { name: /decrement/i });\n\n await user.click(decrementBtn);\n\n expect(screen.getByText(/count: 4/i)).toBeInTheDocument();\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Forms","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { LoginForm } from './LoginForm';\n\ndescribe('LoginForm Component', () => {\n it('submits form with username and password', async () => {\n const user = userEvent.setup();\n const handleSubmit = jest.fn();\n\n render(\u003cLoginForm onSubmit={handleSubmit} />);\n\n const usernameInput = screen.getByLabelText(/username/i);\n const passwordInput = screen.getByLabelText(/password/i);\n const submitButton = screen.getByRole('button', { name: /submit/i });\n\n await user.type(usernameInput, 'testuser');\n await user.type(passwordInput, 'password123');\n await user.click(submitButton);\n\n expect(handleSubmit).toHaveBeenCalledTimes(1);\n expect(handleSubmit).toHaveBeenCalledWith({\n username: 'testuser',\n password: 'password123',\n });\n });\n\n it('shows validation errors for empty fields', async () => {\n const user = userEvent.setup();\n render(\u003cLoginForm onSubmit={jest.fn()} />);\n\n const submitButton = screen.getByRole('button', { name: /submit/i });\n\n await user.click(submitButton);\n\n expect(screen.getByText(/username is required/i)).toBeInTheDocument();\n expect(screen.getByText(/password is required/i)).toBeInTheDocument();\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Conditional Rendering","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { render, screen } from '@testing-library/react';\nimport { UserProfile } from './UserProfile';\n\ndescribe('UserProfile Component', () => {\n it('shows loading state when loading', () => {\n render(\u003cUserProfile loading={true} />);\n\n expect(screen.getByText(/loading/i)).toBeInTheDocument();\n expect(screen.queryByRole('heading')).not.toBeInTheDocument();\n });\n\n it('shows user data when loaded', () => {\n const user = {\n name: 'John Doe',\n email: '[email protected]',\n };\n\n render(\u003cUserProfile loading={false} user={user} />);\n\n expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();\n expect(screen.getByRole('heading', { name: /john doe/i })).toBeInTheDocument();\n expect(screen.getByText(/[email protected]/i)).toBeInTheDocument();\n });\n\n it('shows error message when error occurs', () => {\n render(\u003cUserProfile loading={false} error=\"Failed to load user\" />);\n\n expect(screen.getByText(/failed to load user/i)).toBeInTheDocument();\n expect(screen.queryByRole('heading')).not.toBeInTheDocument();\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Mocking Patterns","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Mocking Modules","type":"text"}]},{"type":"paragraph","content":[{"text":"Automatic Mock","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// __mocks__/axios.js\nexport default {\n get: jest.fn(),\n post: jest.fn(),\n put: jest.fn(),\n delete: jest.fn(),\n};","type":"text"}]},{"type":"paragraph","content":[{"text":"Usage in test","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import axios from 'axios';\nimport { UserService } from './UserService';\n\njest.mock('axios');\n\ndescribe('UserService', () => {\n beforeEach(() => {\n jest.clearAllMocks();\n });\n\n it('fetches users successfully', async () => {\n const mockUsers = [{ id: 1, name: 'Alice' }];\n axios.get.mockResolvedValue({ data: mockUsers });\n\n const users = await UserService.getUsers();\n\n expect(axios.get).toHaveBeenCalledWith('/api/users');\n expect(users).toEqual(mockUsers);\n });\n\n it('handles fetch error', async () => {\n axios.get.mockRejectedValue(new Error('Network Error'));\n\n await expect(UserService.getUsers()).rejects.toThrow('Network Error');\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Mocking Functions","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { Button } from './Button';\n\ndescribe('Button Component', () => {\n it('calls onClick handler when clicked', async () => {\n const user = userEvent.setup();\n const handleClick = jest.fn();\n\n render(\u003cButton onClick={handleClick}>Click Me\u003c/Button>);\n\n await user.click(screen.getByRole('button'));\n\n expect(handleClick).toHaveBeenCalledTimes(1);\n });\n\n it('calls onClick with event object', async () => {\n const user = userEvent.setup();\n const handleClick = jest.fn();\n\n render(\u003cButton onClick={handleClick}>Click Me\u003c/Button>);\n\n await user.click(screen.getByRole('button'));\n\n expect(handleClick).toHaveBeenCalledWith(\n expect.objectContaining({\n type: 'click',\n })\n );\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Mocking API Calls with MSW (Mock Service Worker)","type":"text"}]},{"type":"paragraph","content":[{"text":"Setup MSW","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// src/mocks/handlers.js\nimport { rest } from 'msw';\n\nexport const handlers = [\n rest.get('/api/users', (req, res, ctx) => {\n return res(\n ctx.status(200),\n ctx.json([\n { id: 1, name: 'Alice' },\n { id: 2, name: 'Bob' },\n ])\n );\n }),\n\n rest.post('/api/users', async (req, res, ctx) => {\n const { name, email } = await req.json();\n\n return res(\n ctx.status(201),\n ctx.json({\n id: 3,\n name,\n email,\n })\n );\n }),\n];","type":"text"}]},{"type":"paragraph","content":[{"text":"Setup server","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// src/mocks/server.js\nimport { setupServer } from 'msw/node';\nimport { handlers } from './handlers';\n\nexport const server = setupServer(...handlers);","type":"text"}]},{"type":"paragraph","content":[{"text":"Configure in setupTests.js","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { server } from './mocks/server';\n\nbeforeAll(() => server.listen());\nafterEach(() => server.resetHandlers());\nafterAll(() => server.close());","type":"text"}]},{"type":"paragraph","content":[{"text":"Usage in tests","type":"text","marks":[{"type":"strong"}]},{"text":":","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { render, screen, waitFor } from '@testing-library/react';\nimport { server } from './mocks/server';\nimport { rest } from 'msw';\nimport { UserList } from './UserList';\n\ndescribe('UserList Component', () => {\n it('fetches and displays users', async () => {\n render(\u003cUserList />);\n\n expect(screen.getByText(/loading/i)).toBeInTheDocument();\n\n await waitFor(() => {\n expect(screen.getByText(/alice/i)).toBeInTheDocument();\n expect(screen.getByText(/bob/i)).toBeInTheDocument();\n });\n });\n\n it('handles server error', async () => {\n server.use(\n rest.get('/api/users', (req, res, ctx) => {\n return res(\n ctx.status(500),\n ctx.json({ message: 'Internal Server Error' })\n );\n })\n );\n\n render(\u003cUserList />);\n\n await waitFor(() => {\n expect(screen.getByText(/error loading users/i)).toBeInTheDocument();\n });\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Mocking Context","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { render, screen } from '@testing-library/react';\nimport { AuthContext } from './AuthContext';\nimport { ProtectedComponent } from './ProtectedComponent';\n\nconst mockAuthContext = (overrides = {}) => ({\n user: { id: 1, name: 'Test User' },\n isAuthenticated: true,\n login: jest.fn(),\n logout: jest.fn(),\n ...overrides,\n});\n\ndescribe('ProtectedComponent', () => {\n it('renders content for authenticated user', () => {\n const contextValue = mockAuthContext();\n\n render(\n \u003cAuthContext.Provider value={contextValue}>\n \u003cProtectedComponent />\n \u003c/AuthContext.Provider>\n );\n\n expect(screen.getByText(/welcome, test user/i)).toBeInTheDocument();\n });\n\n it('renders login prompt for unauthenticated user', () => {\n const contextValue = mockAuthContext({\n user: null,\n isAuthenticated: false,\n });\n\n render(\n \u003cAuthContext.Provider value={contextValue}>\n \u003cProtectedComponent />\n \u003c/AuthContext.Provider>\n );\n\n expect(screen.getByText(/please log in/i)).toBeInTheDocument();\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Mocking Child Components","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { render, screen } from '@testing-library/react';\nimport { ParentComponent } from './ParentComponent';\n\n// Mock the child component\njest.mock('./ChildComponent', () => ({\n ChildComponent: ({ title, onAction }) => (\n \u003cdiv>\n \u003ch2>{title}\u003c/h2>\n \u003cbutton onClick={onAction}>Mocked Action\u003c/button>\n \u003c/div>\n ),\n}));\n\ndescribe('ParentComponent', () => {\n it('renders with mocked child', () => {\n render(\u003cParentComponent />);\n\n expect(screen.getByText(/mocked action/i)).toBeInTheDocument();\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Async Testing Patterns","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing with waitFor","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { render, screen, waitFor } from '@testing-library/react';\nimport { AsyncComponent } from './AsyncComponent';\n\ndescribe('AsyncComponent', () => {\n it('loads and displays data', async () => {\n render(\u003cAsyncComponent />);\n\n expect(screen.getByText(/loading/i)).toBeInTheDocument();\n\n await waitFor(() => {\n expect(screen.getByText(/data loaded/i)).toBeInTheDocument();\n });\n });\n\n it('waits for specific condition', async () => {\n render(\u003cAsyncComponent />);\n\n await waitFor(\n () => {\n expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();\n },\n { timeout: 3000 }\n );\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing with findBy Queries","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { render, screen } from '@testing-library/react';\nimport { DataFetcher } from './DataFetcher';\n\ndescribe('DataFetcher Component', () => {\n it('displays fetched data', async () => {\n render(\u003cDataFetcher />);\n\n // findBy automatically waits for element to appear\n const heading = await screen.findByRole('heading', { name: /data/i });\n expect(heading).toBeInTheDocument();\n });\n\n it('handles timeout for missing elements', async () => {\n render(\u003cDataFetcher url=\"/api/missing\" />);\n\n await expect(\n screen.findByText(/success/i, {}, { timeout: 500 })\n ).rejects.toThrow();\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Promises","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { AsyncForm } from './AsyncForm';\n\ndescribe('AsyncForm Component', () => {\n it('submits form and shows success message', async () => {\n const user = userEvent.setup();\n render(\u003cAsyncForm />);\n\n const input = screen.getByLabelText(/name/i);\n const submitBtn = screen.getByRole('button', { name: /submit/i });\n\n await user.type(input, 'John Doe');\n await user.click(submitBtn);\n\n const successMsg = await screen.findByText(/submitted successfully/i);\n expect(successMsg).toBeInTheDocument();\n });\n\n it('shows error message on failure', async () => {\n const user = userEvent.setup();\n render(\u003cAsyncForm shouldFail={true} />);\n\n const submitBtn = screen.getByRole('button', { name: /submit/i });\n await user.click(submitBtn);\n\n const errorMsg = await screen.findByRole('alert');\n expect(errorMsg).toHaveTextContent(/submission failed/i);\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing with Fake Timers","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { render, screen, act } from '@testing-library/react';\nimport { Timer } from './Timer';\n\ndescribe('Timer Component', () => {\n beforeEach(() => {\n jest.useFakeTimers();\n });\n\n afterEach(() => {\n jest.useRealTimers();\n });\n\n it('updates timer every second', () => {\n render(\u003cTimer />);\n\n expect(screen.getByText(/0 seconds/i)).toBeInTheDocument();\n\n act(() => {\n jest.advanceTimersByTime(1000);\n });\n\n expect(screen.getByText(/1 second/i)).toBeInTheDocument();\n\n act(() => {\n jest.advanceTimersByTime(3000);\n });\n\n expect(screen.getByText(/4 seconds/i)).toBeInTheDocument();\n });\n\n it('cleans up timer on unmount', () => {\n const { unmount } = render(\u003cTimer />);\n\n const clearIntervalSpy = jest.spyOn(global, 'clearInterval');\n\n unmount();\n\n expect(clearIntervalSpy).toHaveBeenCalled();\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Testing Custom Hooks","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Basic Hook Testing","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { renderHook } from '@testing-library/react';\nimport { useCounter } from './useCounter';\n\ndescribe('useCounter Hook', () => {\n it('initializes with default value', () => {\n const { result } = renderHook(() => useCounter());\n\n expect(result.current.count).toBe(0);\n });\n\n it('initializes with provided value', () => {\n const { result } = renderHook(() => useCounter(10));\n\n expect(result.current.count).toBe(10);\n });\n\n it('increments count', () => {\n const { result } = renderHook(() => useCounter());\n\n act(() => {\n result.current.increment();\n });\n\n expect(result.current.count).toBe(1);\n });\n\n it('decrements count', () => {\n const { result } = renderHook(() => useCounter(5));\n\n act(() => {\n result.current.decrement();\n });\n\n expect(result.current.count).toBe(4);\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Hooks with Props","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { renderHook } from '@testing-library/react';\nimport { useFetch } from './useFetch';\n\ndescribe('useFetch Hook', () => {\n it('fetches data for given URL', async () => {\n const { result } = renderHook(() => useFetch('/api/users'));\n\n expect(result.current.loading).toBe(true);\n\n await waitFor(() => {\n expect(result.current.loading).toBe(false);\n });\n\n expect(result.current.data).toBeDefined();\n expect(result.current.error).toBeNull();\n });\n\n it('refetches when URL changes', async () => {\n const { result, rerender } = renderHook(\n ({ url }) => useFetch(url),\n { initialProps: { url: '/api/users' } }\n );\n\n await waitFor(() => {\n expect(result.current.loading).toBe(false);\n });\n\n const firstData = result.current.data;\n\n rerender({ url: '/api/posts' });\n\n await waitFor(() => {\n expect(result.current.data).not.toEqual(firstData);\n });\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Hooks with Context","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { renderHook } from '@testing-library/react';\nimport { ThemeProvider } from './ThemeContext';\nimport { useTheme } from './useTheme';\n\ndescribe('useTheme Hook', () => {\n const wrapper = ({ children }) => (\n \u003cThemeProvider initialTheme=\"light\">\n {children}\n \u003c/ThemeProvider>\n );\n\n it('returns current theme', () => {\n const { result } = renderHook(() => useTheme(), { wrapper });\n\n expect(result.current.theme).toBe('light');\n });\n\n it('toggles theme', () => {\n const { result } = renderHook(() => useTheme(), { wrapper });\n\n act(() => {\n result.current.toggleTheme();\n });\n\n expect(result.current.theme).toBe('dark');\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Async Hooks","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { renderHook, waitFor } from '@testing-library/react';\nimport { useAsyncData } from './useAsyncData';\n\ndescribe('useAsyncData Hook', () => {\n it('loads data asynchronously', async () => {\n const { result } = renderHook(() => useAsyncData('/api/data'));\n\n expect(result.current.loading).toBe(true);\n expect(result.current.data).toBeNull();\n\n await waitFor(() => {\n expect(result.current.loading).toBe(false);\n });\n\n expect(result.current.data).toBeDefined();\n });\n\n it('handles errors', async () => {\n const { result } = renderHook(() => useAsyncData('/api/error'));\n\n await waitFor(() => {\n expect(result.current.loading).toBe(false);\n });\n\n expect(result.current.error).toBeDefined();\n expect(result.current.data).toBeNull();\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Integration Testing Patterns","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Component Integration","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { render, screen } from '@testing-library/react';\nimport userEvent from '@testing-library/user-event';\nimport { App } from './App';\n\ndescribe('App Integration', () => {\n it('navigates between pages', async () => {\n const user = userEvent.setup();\n render(\u003cApp />);\n\n expect(screen.getByText(/home page/i)).toBeInTheDocument();\n\n const aboutLink = screen.getByRole('link', { name: /about/i });\n await user.click(aboutLink);\n\n expect(screen.getByText(/about page/i)).toBeInTheDocument();\n });\n\n it('completes full user flow', async () => {\n const user = userEvent.setup();\n render(\u003cApp />);\n\n // Navigate to signup\n await user.click(screen.getByRole('link', { name: /sign up/i }));\n\n // Fill out form\n await user.type(screen.getByLabelText(/email/i), '[email protected]');\n await user.type(screen.getByLabelText(/password/i), 'password123');\n\n // Submit form\n await user.click(screen.getByRole('button', { name: /submit/i }));\n\n // Verify success\n expect(await screen.findByText(/welcome/i)).toBeInTheDocument();\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing with Router","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { render, screen } from '@testing-library/react';\nimport { MemoryRouter } from 'react-router-dom';\nimport userEvent from '@testing-library/user-event';\nimport { AppRoutes } from './AppRoutes';\n\nconst renderWithRouter = (ui, { initialEntries = ['/'] } = {}) => {\n return render(\n \u003cMemoryRouter initialEntries={initialEntries}>\n {ui}\n \u003c/MemoryRouter>\n );\n};\n\ndescribe('AppRoutes Integration', () => {\n it('renders home page by default', () => {\n renderWithRouter(\u003cAppRoutes />);\n\n expect(screen.getByText(/home/i)).toBeInTheDocument();\n });\n\n it('renders user page at /users/:id', () => {\n renderWithRouter(\u003cAppRoutes />, { initialEntries: ['/users/123'] });\n\n expect(screen.getByText(/user profile/i)).toBeInTheDocument();\n });\n\n it('navigates programmatically', async () => {\n const user = userEvent.setup();\n renderWithRouter(\u003cAppRoutes />);\n\n const navButton = screen.getByRole('button', { name: /go to profile/i });\n await user.click(navButton);\n\n expect(screen.getByText(/profile page/i)).toBeInTheDocument();\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing with Redux","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"import { render, screen } from '@testing-library/react';\nimport { Provider } from 'react-redux';\nimport { configureStore } from '@reduxjs/toolkit';\nimport userEvent from '@testing-library/user-event';\nimport { TodoList } from './TodoList';\nimport todosReducer from './todosSlice';\n\nconst createMockStore = (initialState = {}) => {\n return configureStore({\n reducer: {\n todos: todosReducer,\n },\n preloadedState: initialState,\n });\n};\n\nconst renderWithStore = (ui, { store = createMockStore() } = {}) => {\n return render(\u003cProvider store={store}>{ui}\u003c/Provider>);\n};\n\ndescribe('TodoList Integration', () => {\n it('adds new todo', async () => {\n const user = userEvent.setup();\n renderWithStore(\u003cTodoList />);\n\n const input = screen.getByPlaceholderText(/new todo/i);\n const addButton = screen.getByRole('button', { name: /add/i });\n\n await user.type(input, 'Buy groceries');\n await user.click(addButton);\n\n expect(screen.getByText(/buy groceries/i)).toBeInTheDocument();\n });\n\n it('renders initial todos from store', () => {\n const initialState = {\n todos: {\n items: [\n { id: 1, text: 'Existing todo', completed: false },\n ],\n },\n };\n\n renderWithStore(\u003cTodoList />, { store: createMockStore(initialState) });\n\n expect(screen.getByText(/existing todo/i)).toBeInTheDocument();\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Jest DOM Matchers","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Common Matchers","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// Element presence\nexpect(element).toBeInTheDocument();\nexpect(element).not.toBeInTheDocument();\n\n// Visibility\nexpect(element).toBeVisible();\nexpect(element).not.toBeVisible();\n\n// Text content\nexpect(element).toHaveTextContent('Hello World');\nexpect(element).toHaveTextContent(/hello/i);\n\n// Attributes\nexpect(element).toHaveAttribute('type', 'submit');\nexpect(element).toHaveAttribute('disabled');\n\n// Classes\nexpect(element).toHaveClass('active');\nexpect(element).toHaveClass('btn', 'btn-primary');\n\n// Styles\nexpect(element).toHaveStyle({ color: 'red' });\nexpect(element).toHaveStyle('display: none');\n\n// Forms\nexpect(input).toHaveValue('test');\nexpect(input).toHaveDisplayValue('Test');\nexpect(checkbox).toBeChecked();\nexpect(checkbox).not.toBeChecked();\nexpect(input).toBeDisabled();\nexpect(input).toBeEnabled();\nexpect(input).toBeRequired();\nexpect(input).toBeInvalid();\nexpect(input).toBeValid();\n\n// Focus\nexpect(element).toHaveFocus();\n\n// Accessibility\nexpect(element).toHaveAccessibleName('Submit button');\nexpect(element).toHaveAccessibleDescription('Click to submit form');\n\n// Contains\nexpect(container).toContainElement(child);\nexpect(container).toContainHTML('\u003cspan>Text\u003c/span>');","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Best Practices","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Test Organization","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Group Related Tests","type":"text","marks":[{"type":"strong"}]},{"text":": Use ","type":"text"},{"text":"describe","type":"text","marks":[{"type":"code_inline"}]},{"text":" blocks to organize tests","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"describe('UserProfile', () => {\n describe('when loading', () => {\n it('shows loading spinner', () => {});\n });\n\n describe('when loaded', () => {\n it('displays user information', () => {});\n it('shows profile picture', () => {});\n });\n});","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use Descriptive Test Names","type":"text","marks":[{"type":"strong"}]},{"text":": Test names should describe behavior","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"// Good\nit('displays error message when login fails', () => {});\n\n// Bad\nit('test login', () => {});","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Follow AAA Pattern","type":"text","marks":[{"type":"strong"}]},{"text":": Arrange, Act, Assert","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"it('increments counter', async () => {\n // Arrange\n const user = userEvent.setup();\n render(\u003cCounter />);\n\n // Act\n await user.click(screen.getByRole('button', { name: /increment/i }));\n\n // Assert\n expect(screen.getByText(/count: 1/i)).toBeInTheDocument();\n});","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Query Best Practices","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Prefer Accessible Queries","type":"text","marks":[{"type":"strong"}]},{"text":": Use getByRole, getByLabelText","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use Screen Queries","type":"text","marks":[{"type":"strong"}]},{"text":": Import from screen instead of destructuring render","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid getByTestId","type":"text","marks":[{"type":"strong"}]},{"text":": Use it as last resort only","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use Regular Expressions","type":"text","marks":[{"type":"strong"}]},{"text":": More flexible than exact strings","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Async Testing Best Practices","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use findBy for Async","type":"text","marks":[{"type":"strong"}]},{"text":": Prefer findBy over getBy + waitFor","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Set Proper Timeouts","type":"text","marks":[{"type":"strong"}]},{"text":": Configure waitFor timeouts for slow operations","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid act() Warnings","type":"text","marks":[{"type":"strong"}]},{"text":": Use userEvent, waitFor, findBy appropriately","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Clean Up Timers","type":"text","marks":[{"type":"strong"}]},{"text":": Use jest.useFakeTimers() and cleanup properly","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Mocking Best Practices","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Mock at Right Level","type":"text","marks":[{"type":"strong"}]},{"text":": Mock external dependencies, not internal logic","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Reset Mocks","type":"text","marks":[{"type":"strong"}]},{"text":": Clear mocks between tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use MSW for API","type":"text","marks":[{"type":"strong"}]},{"text":": Prefer MSW over mocking axios/fetch directly","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Avoid Over-Mocking","type":"text","marks":[{"type":"strong"}]},{"text":": Don't mock what you're testing","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Coverage Best Practices","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Focus on Behavior","type":"text","marks":[{"type":"strong"}]},{"text":": Test user-facing behavior, not implementation","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Don't Chase 100%","type":"text","marks":[{"type":"strong"}]},{"text":": Focus on critical paths","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test Error States","type":"text","marks":[{"type":"strong"}]},{"text":": Include error handling tests","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Test Edge Cases","type":"text","marks":[{"type":"strong"}]},{"text":": Include boundary conditions","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Common Testing Patterns","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Lists and Iterations","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"it('renders list of items', () => {\n const items = ['Apple', 'Banana', 'Cherry'];\n render(\u003cItemList items={items} />);\n\n items.forEach(item => {\n expect(screen.getByText(item)).toBeInTheDocument();\n });\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Accessibility","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"it('has accessible form', () => {\n render(\u003cContactForm />);\n\n const nameInput = screen.getByLabelText(/name/i);\n expect(nameInput).toHaveAccessibleName('Name');\n expect(nameInput).toBeRequired();\n\n const submitButton = screen.getByRole('button', { name: /submit/i });\n expect(submitButton).toHaveAttribute('type', 'submit');\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Error Boundaries","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"it('catches errors and displays fallback', () => {\n const ThrowError = () => {\n throw new Error('Test error');\n };\n\n // Suppress console.error for this test\n const spy = jest.spyOn(console, 'error').mockImplementation(() => {});\n\n render(\n \u003cErrorBoundary fallback={\u003cdiv>Something went wrong\u003c/div>}>\n \u003cThrowError />\n \u003c/ErrorBoundary>\n );\n\n expect(screen.getByText(/something went wrong/i)).toBeInTheDocument();\n\n spy.mockRestore();\n});","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Testing Portals","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"javascript"},"content":[{"text":"it('renders modal in portal', () => {\n render(\u003cModal isOpen={true}>Modal Content\u003c/Modal>);\n\n const modal = screen.getByText(/modal content/i);\n expect(modal).toBeInTheDocument();\n\n // Modal should be in document.body, not in the component tree\n expect(modal.parentElement).toBe(document.body);\n});","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Troubleshooting","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Common Issues","type":"text"}]},{"type":"paragraph","content":[{"text":"Issue","type":"text","marks":[{"type":"strong"}]},{"text":": \"Unable to find element\"","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Solution","type":"text","marks":[{"type":"strong"}]},{"text":": Use screen.debug() to see DOM, check query type, wait for async updates","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Issue","type":"text","marks":[{"type":"strong"}]},{"text":": \"Act warnings\"","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Solution","type":"text","marks":[{"type":"strong"}]},{"text":": Use userEvent instead of fireEvent, wrap state updates in act(), use waitFor/findBy","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Issue","type":"text","marks":[{"type":"strong"}]},{"text":": \"Jest timeout\"","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Solution","type":"text","marks":[{"type":"strong"}]},{"text":": Increase timeout, check for infinite loops, ensure async operations complete","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Issue","type":"text","marks":[{"type":"strong"}]},{"text":": \"Cannot read property of undefined\"","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Solution","type":"text","marks":[{"type":"strong"}]},{"text":": Check mocks are set up correctly, ensure components receive required props","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Issue","type":"text","marks":[{"type":"strong"}]},{"text":": \"Multiple elements found\"","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Solution","type":"text","marks":[{"type":"strong"}]},{"text":": Make queries more specific, use getAllBy for multiple elements","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Resources","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Jest Documentation: https://jestjs.io/","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"React Testing Library: https://testing-library.com/react","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Testing Library Queries: https://testing-library.com/docs/queries/about","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Jest DOM Matchers: https://github.com/testing-library/jest-dom","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"MSW Documentation: https://mswjs.io/","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Common Mistakes: https://kentcdodds.com/blog/common-mistakes-with-react-testing-library","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}},{"type":"heading","attrs":{"level":2},"content":[{"text":"Skill Version","type":"text","marks":[{"type":"strong"}]},{"text":": 1.0.0 ","type":"text"},{"text":"Last Updated","type":"text","marks":[{"type":"strong"}]},{"text":": October 2025 ","type":"text"},{"text":"Skill Category","type":"text","marks":[{"type":"strong"}]},{"text":": Testing, React, Quality Assurance ","type":"text"},{"text":"Compatible With","type":"text","marks":[{"type":"strong"}]},{"text":": Jest 29+, React Testing Library 13+, React 16.8+","type":"text"}]}]},"metadata":{"date":"2026-06-05","name":"jest-react-testing","author":"@skillopedia","source":{"stars":57,"repo_name":"luxor-claude-marketplace","origin_url":"https://github.com/manutej/luxor-claude-marketplace/blob/HEAD/plugins/luxor-frontend-essentials/skills/jest-react-testing/SKILL.md","repo_owner":"manutej","body_sha256":"37b0e004dd8295838c051887d056fb763bbd90fbb898089f57ae1009d7049ad3","cluster_key":"0155804c6e0a027493a092cf2a6e9344b3504282a8d9c40cca3113fe97af4cc2","clean_bundle":{"format":"clean-skill-bundle-v1","source":"manutej/luxor-claude-marketplace/plugins/luxor-frontend-essentials/skills/jest-react-testing/SKILL.md","attachments":[{"id":"20f610f0-1f92-573e-b67c-6756fbabf160","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/20f610f0-1f92-573e-b67c-6756fbabf160/attachment.md","path":"EXAMPLES.md","size":68465,"sha256":"2daca8d23c09d8313fe1a4a880c81e1fd269fde05f0050e366b94f2fa1e3f35d","contentType":"text/markdown; charset=utf-8"},{"id":"4bf82761-a0dd-5e69-a92f-17e452ebe75f","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/4bf82761-a0dd-5e69-a92f-17e452ebe75f/attachment.md","path":"README.md","size":10992,"sha256":"445e63fa8bd8966b195e01a5862e59110a3e5607cd9f5e2d994b0ae1d43ee3aa","contentType":"text/markdown; charset=utf-8"}],"bundle_sha256":"fdb9cd5f3ec37d27de9ec50bd5c85e70498ea19c80e2f9c5a32fe972ca3a1e0c","attachment_count":2,"text_attachments":2,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"plugins/luxor-frontend-essentials/skills/jest-react-testing/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"testing-qa","category_label":"Testing"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"testing-qa","import_tag":"clean-skills-v1","description":"Comprehensive React component testing with Jest and React Testing Library covering configuration, mocking strategies, async testing patterns, hooks testing, and integration testing best practices"}},"renderedAt":1782979398573}

Jest React Testing A comprehensive skill for testing React applications using Jest and React Testing Library. This skill covers everything from basic component testing to advanced patterns including mocking, async testing, custom hooks testing, and integration testing strategies. When to Use This Skill Use this skill when: - Testing React components with Jest and React Testing Library - Setting up Jest configuration for React projects - Writing unit tests for components, hooks, and utilities - Testing user interactions and component behavior - Mocking modules, functions, API calls, and extern…