Back to catalog

react-testing

Skill from retex73/music-app

  • Category: discovered
  • Tags: github, community
  • Version: 1.0.0
  • Author: walid
  • Downloads: 0
  • Updated: 2025-11-22

Download the latest version (v1.0.0) from this page.

SKILL.md

# React Testing Skill

## Purpose
Master React Testing Library patterns for testing components, hooks, and user interactions in the music-app project.

## Core Testing Patterns

### Component Test Template
```javascript
import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { BrowserRouter } from 'react-router-dom';
import { ThemeProvider } from '@mui/material/styles';
import darkTheme from '../../theme/darkTheme';
import TuneSummaryCard from '../TuneSummaryCard';

const renderWithProviders = (component) => {
  return render(
    <ThemeProvider theme={darkTheme}>
      <BrowserRouter>
        {component}
      </BrowserRouter>
    </ThemeProvider>
  );
};

describe('TuneSummaryCard', () => {
  const mockTune = {
    id: '1',
    'Tune Title': 'The Butterfly',
    'Rhythm': 'Slip Jig',
    'Key': 'Em'
  };

  test('renders tune information', () => {
    renderWithProviders(<TuneSummaryCard tune={mockTune} />);

    expect(screen.getByText('The Butterfly')).toBeInTheDocument();
    expect(screen.getByText(/Slip Jig/i)).toBeInTheDocument();
  });

  test('handles favorite button click', async () => {
    const user = userEvent.setup();
    const handleFavorite = jest.fn();

    renderWithProviders(
      <TuneSummaryCard tune={mockTune} onFavorite={handleFavorite} />
    );

    const favoriteButton = screen.getByRole('button', { name: /favorite/i });
    await user.click(favoriteButton);

    expect(handleFavorite).toHaveBeenCalledWith('1');
  });
});
```

### Async Testing
```javascript
test('loads data and displays it', async () => {
  renderWithProviders(<DataComponent />);

  // Wait for loading to finish
  await waitFor(() => {
    expect(screen.queryByText('Loading...')).not.toBeInTheDocument();
  });

  // Verify data displayed
  expect(screen.getByText('Data loaded')).toBeInTheDocument();
});

// Or use findBy queries (combines getBy + waitFor)
test('finds async element', async () => {
  renderWithProviders(<AsyncComponent />);

  const element = await screen.findByText('Async content');
  expect(element).toBeInTheDocument();
});
```

### Mocking Context
```javascript
jest.mock('../../contexts/AuthContext', () => ({
  useAuth: () => ({
    user: { uid: 'test-user', email: 'test@example.com' },
    login: jest.fn(),
    logout: jest.fn(),
    loading: false
  })
}));

jest.mock('../../contexts/FavoritesContext', () => ({
  useFavorites: () => ({
    favorites: ['tune1', 'tune2'],
    addFavorite: jest.fn(),
    removeFavorite: jest.fn(),
    isFavorite: (id) => ['tune1', 'tune2'].includes(id)
  })
}));
```

### Testing Hooks
```javascript
import { renderHook, waitFor } from '@testing-library/react';
import { useAuth } from '../AuthContext';
import { AuthProvider } from '../AuthContext';

test('useAuth provides user data', async () => {
  const { result } = renderHook(() => useAuth(), {
    wrapper: AuthProvider
  });

  await waitFor(() => {
    expect(result.current.user).toBeDefined();
  });

  expect(result.current.login).toBeDefined();
  expect(result.current.logout).toBeDefined();
});
```

## Query Priorities

1. **getByRole** (Preferred) - Most accessible
2. **getByLabelText** - Forms
3. **getByPlaceholderText** - Inputs
4. **getByText** - Content
5. **getByTestId** - Last resort

```javascript
// Preferred
screen.getByRole('button', { name: /submit/i })

// Forms
screen.getByLabelText(/email/i)

// Content
screen.getByText(/welcome/i)

// Last resort
screen.getByTestId('custom-element')
```

## Best Practices

✅ **Do:**
- Test user behavior, not implementation
- Use userEvent over fireEvent
- Wait for async operations
- Mock external dependencies
- Clean up after tests

❌ **Don't:**
- Test implementation details
- Query by className or id
- Skip async waiting
- Leave test.only or test.skip
- Forget to clear mocks

## Common Patterns

### Testing Forms
```javascript
test('submits form with user input', async () => {
  const user = userEvent.setup();
  const handleSubmit = jest.fn();

  renderWithProviders(<LoginForm onSubmit={handleSubmit} />);

  const emailInput = screen.getByLabelText(/email/i);
  const passwordInput = screen.getByLabelText(/password/i);
  const submitButton = screen.getByRole('button', { name: /login/i });

  await user.type(emailInput, 'test@example.com');
  await user.type(passwordInput, 'password123');
  await user.click(submitButton);

  expect(handleSubmit).toHaveBeenCalledWith({
    email: 'test@example.com',
    password: 'password123'
  });
});
```

### Testing Navigation
```javascript
import { createMemoryRouter, RouterProvider } from 'react-router-dom';

test('navigates to tune details', async () => {
  const router = createMemoryRouter([
    { path: '/', element: <HomePage /> },
    { path: '/tune/:id', element: <TuneDetailsPage /> }
  ]);

  render(<RouterProvider router={router} />);

  const user = userEvent.setup();
  await user.click(screen.getByText('The Butterfly'));

  expect(screen.getByText('Tune Details')).toBeInTheDocument();
});
```

## Test Organization

```
src/
├── components/
│   ├── NavBar.jsx
│   └── __tests__/
│       └── NavBar.test.jsx
└── services/
    ├── tunesService.js
    └── __tests__/
        └── tunesService.test.js
```

## Coverage Commands

```bash
# Run tests with coverage
npm test -- --coverage

# Run specific test
npm test -- NavBar.test.jsx

# Watch mode
npm test -- --watch
```