Claude Code Plugins

Community-maintained marketplace

Feedback

component-testing

@PrasadTelasula/EvokeQOne
0
0

Test React components with React Testing Library and Playwright. Use when building UI components or verifying frontend functionality.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name component-testing
description Test React components with React Testing Library and Playwright. Use when building UI components or verifying frontend functionality.
allowed-tools Read, Write, Bash, Glob

You help test React components and pages for the QA Team Portal frontend using React Testing Library and Playwright.

When to Use This Skill

  • Testing React components after creation
  • Writing unit tests for component logic
  • Testing user interactions (clicks, typing, form submission)
  • E2E testing of complete user flows
  • Accessibility testing
  • Visual regression testing

Testing Approaches

1. Unit Tests with React Testing Library

Basic Component Test

// tests/unit/components/TeamMemberCard.test.tsx
import { render, screen } from '@testing-library/react'
import { TeamMemberCard } from '@/components/public/TeamIntro/TeamMemberCard'

const mockMember = {
  id: '123',
  name: 'John Doe',
  role: 'QA Lead',
  email: 'john@example.com',
  profilePhotoUrl: '/path/to/photo.jpg'
}

describe('TeamMemberCard', () => {
  it('renders member name and role', () => {
    render(<TeamMemberCard member={mockMember} />)

    expect(screen.getByText('John Doe')).toBeInTheDocument()
    expect(screen.getByText('QA Lead')).toBeInTheDocument()
  })

  it('displays profile photo with alt text', () => {
    render(<TeamMemberCard member={mockMember} />)

    const img = screen.getByRole('img', { name: /john doe/i })
    expect(img).toHaveAttribute('src', mockMember.profilePhotoUrl)
  })

  it('shows email link when provided', () => {
    render(<TeamMemberCard member={mockMember} />)

    const emailLink = screen.getByRole('link', { name: /email/i })
    expect(emailLink).toHaveAttribute('href', 'mailto:john@example.com')
  })
})

Testing User Interactions

import { render, screen, fireEvent } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { UpdatesModal } from '@/components/public/Updates/UpdateModal'

describe('UpdatesModal', () => {
  it('closes modal when close button clicked', async () => {
    const onClose = vi.fn()
    render(<UpdatesModal isOpen={true} onClose={onClose} update={mockUpdate} />)

    const closeButton = screen.getByRole('button', { name: /close/i })
    await userEvent.click(closeButton)

    expect(onClose).toHaveBeenCalledTimes(1)
  })

  it('closes modal on escape key press', async () => {
    const onClose = vi.fn()
    render(<UpdatesModal isOpen={true} onClose={onClose} update={mockUpdate} />)

    fireEvent.keyDown(document, { key: 'Escape' })

    expect(onClose).toHaveBeenCalled()
  })
})

Testing Forms

import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { LoginForm } from '@/components/admin/auth/LoginForm'

describe('LoginForm', () => {
  it('submits form with valid data', async () => {
    const onSubmit = vi.fn()
    render(<LoginForm onSubmit={onSubmit} />)

    await userEvent.type(
      screen.getByLabelText(/email/i),
      'admin@test.com'
    )
    await userEvent.type(
      screen.getByLabelText(/password/i),
      'password123'
    )

    await userEvent.click(screen.getByRole('button', { name: /login/i }))

    await waitFor(() => {
      expect(onSubmit).toHaveBeenCalledWith({
        email: 'admin@test.com',
        password: 'password123'
      })
    })
  })

  it('shows validation errors for invalid email', async () => {
    render(<LoginForm onSubmit={vi.fn()} />)

    await userEvent.type(
      screen.getByLabelText(/email/i),
      'invalid-email'
    )
    await userEvent.click(screen.getByRole('button', { name: /login/i }))

    await waitFor(() => {
      expect(screen.getByText(/invalid email/i)).toBeInTheDocument()
    })
  })
})

Testing API Integration

import { render, screen, waitFor } from '@testing-library/react'
import { TeamList } from '@/components/public/TeamIntro/TeamList'
import { rest } from 'msw'
import { setupServer } from 'msw/node'

const mockTeamMembers = [
  { id: '1', name: 'John Doe', role: 'QA Lead' },
  { id: '2', name: 'Jane Smith', role: 'QA Engineer' }
]

const server = setupServer(
  rest.get('/api/v1/team-members', (req, res, ctx) => {
    return res(ctx.json(mockTeamMembers))
  })
)

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

describe('TeamList', () => {
  it('displays loading state initially', () => {
    render(<TeamList />)
    expect(screen.getByRole('status')).toBeInTheDocument()
  })

  it('displays team members after loading', async () => {
    render(<TeamList />)

    await waitFor(() => {
      expect(screen.getByText('John Doe')).toBeInTheDocument()
      expect(screen.getByText('Jane Smith')).toBeInTheDocument()
    })
  })

  it('displays error message on API failure', async () => {
    server.use(
      rest.get('/api/v1/team-members', (req, res, ctx) => {
        return res(ctx.status(500))
      })
    )

    render(<TeamList />)

    await waitFor(() => {
      expect(screen.getByText(/error loading/i)).toBeInTheDocument()
    })
  })
})

2. E2E Tests with Playwright

Setup Playwright

// playwright.config.ts
import { defineConfig } from '@playwright/test'

export default defineConfig({
  testDir: './tests/e2e',
  use: {
    baseURL: 'http://localhost:5173',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },
  webServer: {
    command: 'npm run dev',
    port: 5173,
    reuseExistingServer: !process.env.CI,
  },
})

Basic E2E Test

// tests/e2e/landing-page.spec.ts
import { test, expect } from '@playwright/test'

test.describe('Landing Page', () => {
  test('displays all sections', async ({ page }) => {
    await page.goto('/')

    // Check all sections are visible
    await expect(page.getByRole('heading', { name: /team introduction/i })).toBeVisible()
    await expect(page.getByRole('heading', { name: /latest updates/i })).toBeVisible()
    await expect(page.getByRole('heading', { name: /tools/i })).toBeVisible()
    await expect(page.getByRole('heading', { name: /resources/i })).toBeVisible()
    await expect(page.getByRole('heading', { name: /research/i })).toBeVisible()
  })

  test('navigation links scroll to sections', async ({ page }) => {
    await page.goto('/')

    // Click tools nav link
    await page.getByRole('link', { name: /tools/i }).click()

    // Check tools section is in view
    const toolsSection = page.getByRole('heading', { name: /tools/i })
    await expect(toolsSection).toBeInViewport()
  })
})

Testing User Flows

// tests/e2e/admin-login.spec.ts
import { test, expect } from '@playwright/test'

test.describe('Admin Login', () => {
  test('admin can login and access dashboard', async ({ page }) => {
    // Navigate to login page
    await page.goto('/admin/login')

    // Fill login form
    await page.getByLabel(/email/i).fill('admin@test.com')
    await page.getByLabel(/password/i).fill('testpass123')

    // Submit form
    await page.getByRole('button', { name: /login/i }).click()

    // Wait for redirect to dashboard
    await expect(page).toHaveURL('/admin/dashboard')

    // Check dashboard loads
    await expect(page.getByRole('heading', { name: /dashboard/i })).toBeVisible()
  })

  test('shows error for invalid credentials', async ({ page }) => {
    await page.goto('/admin/login')

    await page.getByLabel(/email/i).fill('wrong@test.com')
    await page.getByLabel(/password/i).fill('wrongpass')

    await page.getByRole('button', { name: /login/i }).click()

    // Check error message appears
    await expect(page.getByText(/invalid credentials/i)).toBeVisible()
  })
})

Testing CRUD Operations

// tests/e2e/team-management.spec.ts
import { test, expect } from '@playwright/test'

test.describe('Team Management', () => {
  test.beforeEach(async ({ page }) => {
    // Login as admin
    await page.goto('/admin/login')
    await page.getByLabel(/email/i).fill('admin@test.com')
    await page.getByLabel(/password/i).fill('testpass123')
    await page.getByRole('button', { name: /login/i }).click()
    await page.waitForURL('/admin/dashboard')

    // Navigate to team management
    await page.getByRole('link', { name: /team members/i }).click()
  })

  test('can create new team member', async ({ page }) => {
    await page.getByRole('button', { name: /add member/i }).click()

    // Fill form
    await page.getByLabel(/name/i).fill('New Member')
    await page.getByLabel(/role/i).fill('QA Engineer')
    await page.getByLabel(/email/i).fill('new@test.com')

    // Upload photo
    await page.getByLabel(/photo/i).setInputFiles('./tests/fixtures/profile.jpg')

    // Submit
    await page.getByRole('button', { name: /save/i }).click()

    // Verify success message
    await expect(page.getByText(/member created successfully/i)).toBeVisible()

    // Verify appears in list
    await expect(page.getByText('New Member')).toBeVisible()
  })

  test('can edit existing team member', async ({ page }) => {
    // Click edit button for first member
    await page.getByRole('row').first().getByRole('button', { name: /edit/i }).click()

    // Update name
    await page.getByLabel(/name/i).clear()
    await page.getByLabel(/name/i).fill('Updated Name')

    // Save
    await page.getByRole('button', { name: /save/i }).click()

    // Verify updated
    await expect(page.getByText('Updated Name')).toBeVisible()
  })

  test('can delete team member', async ({ page }) => {
    // Get initial count
    const initialCount = await page.getByRole('row').count()

    // Delete first member
    await page.getByRole('row').first().getByRole('button', { name: /delete/i }).click()

    // Confirm deletion
    await page.getByRole('button', { name: /confirm/i }).click()

    // Verify count decreased
    const newCount = await page.getByRole('row').count()
    expect(newCount).toBe(initialCount - 1)
  })
})

3. Accessibility Testing

// tests/e2e/accessibility.spec.ts
import { test, expect } from '@playwright/test'
import AxeBuilder from '@axe-core/playwright'

test.describe('Accessibility', () => {
  test('landing page has no accessibility violations', async ({ page }) => {
    await page.goto('/')

    const accessibilityScanResults = await new AxeBuilder({ page }).analyze()

    expect(accessibilityScanResults.violations).toEqual([])
  })

  test('admin dashboard has no accessibility violations', async ({ page }) => {
    // Login first
    await page.goto('/admin/login')
    await page.getByLabel(/email/i).fill('admin@test.com')
    await page.getByLabel(/password/i).fill('testpass123')
    await page.getByRole('button', { name: /login/i }).click()

    await page.waitForURL('/admin/dashboard')

    const accessibilityScanResults = await new AxeBuilder({ page }).analyze()

    expect(accessibilityScanResults.violations).toEqual([])
  })
})

Running Tests

Vitest (Unit Tests)

cd frontend

# Run all unit tests
npm run test

# Run in watch mode
npm run test:watch

# Run with coverage
npm run test:coverage

# Run specific file
npm run test -- TeamMemberCard.test.tsx

# Run with UI
npm run test:ui

Playwright (E2E Tests)

cd frontend

# Install browsers (first time)
npx playwright install

# Run all E2E tests
npx playwright test

# Run in UI mode
npx playwright test --ui

# Run specific test file
npx playwright test tests/e2e/landing-page.spec.ts

# Run in headed mode (see browser)
npx playwright test --headed

# Run in debug mode
npx playwright test --debug

# Run on specific browser
npx playwright test --project=chromium

# Generate test code
npx playwright codegen http://localhost:5173

Test Configuration

Vitest Setup (vitest.config.ts)

import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
import path from 'path'

export default defineConfig({
  plugins: [react()],
  test: {
    globals: true,
    environment: 'jsdom',
    setupFiles: './tests/setup.ts',
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
})

Test Setup File (tests/setup.ts)

import '@testing-library/jest-dom'
import { cleanup } from '@testing-library/react'
import { afterEach, vi } from 'vitest'

// Cleanup after each test
afterEach(() => {
  cleanup()
})

// Mock window.matchMedia
Object.defineProperty(window, 'matchMedia', {
  writable: true,
  value: vi.fn().mockImplementation(query => ({
    matches: false,
    media: query,
    onchange: null,
    addListener: vi.fn(),
    removeListener: vi.fn(),
    addEventListener: vi.fn(),
    removeEventListener: vi.fn(),
    dispatchEvent: vi.fn(),
  })),
})

Test Checklist

For each component, verify:

  • Rendering - Component renders without errors
  • Props - Handles different prop combinations
  • User interactions - Clicks, typing, form submission work
  • Loading states - Shows loading indicators
  • Error states - Shows error messages
  • Empty states - Handles no data gracefully
  • Accessibility - ARIA labels, keyboard navigation, screen readers
  • Responsiveness - Works on mobile/tablet/desktop
  • Edge cases - Null values, long text, special characters

Common Testing Patterns

Testing Hooks

import { renderHook, waitFor } from '@testing-library/react'
import { useTeamMembers } from '@/hooks/useTeamMembers'

test('useTeamMembers fetches data', async () => {
  const { result } = renderHook(() => useTeamMembers())

  expect(result.current.loading).toBe(true)

  await waitFor(() => {
    expect(result.current.loading).toBe(false)
    expect(result.current.data).toHaveLength(2)
  })
})

Testing Context

import { render, screen } from '@testing-library/react'
import { AuthProvider } from '@/contexts/AuthContext'
import { ProtectedComponent } from '@/components/ProtectedComponent'

test('shows content when authenticated', () => {
  render(
    <AuthProvider value={{ user: mockUser, isAuthenticated: true }}>
      <ProtectedComponent />
    </AuthProvider>
  )

  expect(screen.getByText(/protected content/i)).toBeInTheDocument()
})

Output Format

After testing, report:

  1. Tests Run: X passed, Y failed
  2. Coverage: X% of components/lines covered
  3. Failed Tests: List with error messages
  4. Accessibility Issues: WCAG violations found
  5. Performance: Slow-rendering components
  6. Recommendations: Suggested improvements

Best Practices

  1. Test user behavior, not implementation details
  2. Use semantic queries (getByRole, getByLabel, getByText)
  3. Avoid testing IDs or classes when possible
  4. Test accessibility (keyboard navigation, screen readers)
  5. Mock external dependencies (API calls, localStorage)
  6. Keep tests independent - no shared state
  7. Use descriptive test names - what you're testing and expected outcome
  8. Test error scenarios - not just happy path