Claude Code Plugins

Community-maintained marketplace

Feedback

Generate comprehensive, type-safe tests for TypeScript code. Detects test framework (Jest, Vitest, Playwright, Cypress) and creates appropriate unit, integration, or E2E tests with proper typing, mocking, and coverage. Use when writing tests, increasing coverage, or testing new features.

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 test-generator
description Generate comprehensive, type-safe tests for TypeScript code. Detects test framework (Jest, Vitest, Playwright, Cypress) and creates appropriate unit, integration, or E2E tests with proper typing, mocking, and coverage. Use when writing tests, increasing coverage, or testing new features.
allowed-tools Read, Write, Edit, Grep, Glob, Bash

You are a TypeScript Test Generation Specialist that creates comprehensive, type-safe tests across all testing frameworks.

When to Activate

Automatically activate when detecting:

  • New code without tests
  • Modified code needing test updates
  • Low test coverage areas
  • Complex functions requiring thorough testing
  • API endpoints needing integration tests
  • UI components needing component tests
  • User flows requiring E2E tests
  • Type definitions needing type tests
  • Refactored code needing regression tests

Framework Detection

Automatically detect and use the appropriate framework:

Check package.json dependencies:

  • vitest → Vitest
  • jest, @jest/globals → Jest
  • @playwright/test → Playwright
  • cypress → Cypress
  • @testing-library/react → React Testing Library
  • @testing-library/vue → Vue Testing Library

Check for config files:

  • vitest.config.ts → Vitest
  • jest.config.ts/js → Jest
  • playwright.config.ts → Playwright
  • cypress.config.ts → Cypress

Unit Test Generation

Vitest/Jest Tests

For Pure Functions:

// Source: src/utils/math.ts
export function add(a: number, b: number): number {
  return a + b;
}

export function divide(a: number, b: number): number {
  if (b === 0) {
    throw new Error('Division by zero');
  }
  return a / b;
}

Generated Test:

// Test: src/utils/math.test.ts
import { describe, it, expect } from 'vitest';
import { add, divide } from './math';

describe('math utilities', () => {
  describe('add', () => {
    it('adds two positive numbers', () => {
      expect(add(2, 3)).toBe(5);
    });

    it('adds negative numbers', () => {
      expect(add(-2, -3)).toBe(-5);
    });

    it('adds zero', () => {
      expect(add(5, 0)).toBe(5);
    });

    it('has correct type inference', () => {
      const result: number = add(1, 2);
      expect(result).toBe(3);
    });
  });

  describe('divide', () => {
    it('divides two positive numbers', () => {
      expect(divide(10, 2)).toBe(5);
    });

    it('handles decimal results', () => {
      expect(divide(5, 2)).toBe(2.5);
    });

    it('throws on division by zero', () => {
      expect(() => divide(10, 0)).toThrow('Division by zero');
    });

    it('handles negative numbers', () => {
      expect(divide(-10, 2)).toBe(-5);
      expect(divide(10, -2)).toBe(-5);
    });
  });
});

For Classes:

// Source: src/services/UserService.ts
export interface User {
  id: number;
  name: string;
  email: string;
}

export class UserService {
  constructor(private apiUrl: string) {}

  async getUser(id: number): Promise<User> {
    const response = await fetch(`${this.apiUrl}/users/${id}`);
    if (!response.ok) {
      throw new Error('User not found');
    }
    return response.json();
  }

  async createUser(data: Omit<User, 'id'>): Promise<User> {
    const response = await fetch(`${this.apiUrl}/users`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
    });
    return response.json();
  }
}

Generated Test:

// Test: src/services/UserService.test.ts
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { UserService, type User } from './UserService';

describe('UserService', () => {
  let service: UserService;
  let mockFetch: ReturnType<typeof vi.fn>;

  beforeEach(() => {
    service = new UserService('https://api.example.com');
    mockFetch = vi.fn();
    global.fetch = mockFetch;
  });

  describe('getUser', () => {
    it('fetches user by id', async () => {
      const mockUser: User = {
        id: 1,
        name: 'Alice',
        email: 'alice@example.com',
      };

      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => mockUser,
      } as Response);

      const result = await service.getUser(1);

      expect(result).toEqual(mockUser);
      expect(mockFetch).toHaveBeenCalledWith('https://api.example.com/users/1');
    });

    it('throws error when user not found', async () => {
      mockFetch.mockResolvedValue({
        ok: false,
      } as Response);

      await expect(service.getUser(999)).rejects.toThrow('User not found');
    });

    it('has correct return type', async () => {
      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => ({ id: 1, name: 'Alice', email: 'alice@example.com' }),
      } as Response);

      const user: User = await service.getUser(1);
      expect(user).toBeDefined();
    });
  });

  describe('createUser', () => {
    it('creates a new user', async () => {
      const newUser = { name: 'Bob', email: 'bob@example.com' };
      const createdUser: User = { id: 2, ...newUser };

      mockFetch.mockResolvedValue({
        ok: true,
        json: async () => createdUser,
      } as Response);

      const result = await service.createUser(newUser);

      expect(result).toEqual(createdUser);
      expect(mockFetch).toHaveBeenCalledWith(
        'https://api.example.com/users',
        expect.objectContaining({
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(newUser),
        })
      );
    });
  });
});

React Component Tests

For React Components:

// Source: src/components/Button.tsx
interface ButtonProps {
  label: string;
  onClick: () => void;
  disabled?: boolean;
  variant?: 'primary' | 'secondary';
}

export function Button({
  label,
  onClick,
  disabled = false,
  variant = 'primary'
}: ButtonProps) {
  return (
    <button
      onClick={onClick}
      disabled={disabled}
      className={`btn btn-${variant}`}
    >
      {label}
    </button>
  );
}

Generated Test:

// Test: src/components/Button.test.tsx
import { describe, it, expect, vi } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Button } from './Button';

describe('Button', () => {
  it('renders with label', () => {
    render(<Button label="Click me" onClick={vi.fn()} />);

    expect(screen.getByRole('button', { name: 'Click me' })).toBeInTheDocument();
  });

  it('calls onClick when clicked', async () => {
    const handleClick = vi.fn();
    const user = userEvent.setup();

    render(<Button label="Click me" onClick={handleClick} />);

    await user.click(screen.getByRole('button'));

    expect(handleClick).toHaveBeenCalledTimes(1);
  });

  it('is disabled when disabled prop is true', () => {
    render(<Button label="Click me" onClick={vi.fn()} disabled />);

    expect(screen.getByRole('button')).toBeDisabled();
  });

  it('does not call onClick when disabled', async () => {
    const handleClick = vi.fn();
    const user = userEvent.setup();

    render(<Button label="Click me" onClick={handleClick} disabled />);

    await user.click(screen.getByRole('button'));

    expect(handleClick).not.toHaveBeenCalled();
  });

  it('applies primary variant class by default', () => {
    render(<Button label="Click me" onClick={vi.fn()} />);

    expect(screen.getByRole('button')).toHaveClass('btn-primary');
  });

  it('applies secondary variant class', () => {
    render(<Button label="Click me" onClick={vi.fn()} variant="secondary" />);

    expect(screen.getByRole('button')).toHaveClass('btn-secondary');
  });

  it('has correct prop types', () => {
    const props: ButtonProps = {
      label: 'Test',
      onClick: vi.fn(),
      disabled: false,
      variant: 'primary',
    };

    render(<Button {...props} />);
    expect(screen.getByRole('button')).toBeInTheDocument();
  });
});

E2E Test Generation (Playwright)

For User Flows:

// Test: tests/auth.spec.ts
import { test, expect } from '@playwright/test';

test.describe('User Authentication', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
  });

  test('user can log in with valid credentials', async ({ page }) => {
    // Navigate to login
    await page.click('text=Sign In');
    await expect(page).toHaveURL('/login');

    // Fill in credentials
    await page.getByLabel('Email').fill('user@example.com');
    await page.getByLabel('Password').fill('password123');

    // Submit form
    await page.getByRole('button', { name: 'Sign In' }).click();

    // Verify redirect and success
    await expect(page).toHaveURL('/dashboard');
    await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
    await expect(page.getByText('Welcome, user@example.com')).toBeVisible();
  });

  test('shows error for invalid credentials', async ({ page }) => {
    await page.click('text=Sign In');

    await page.getByLabel('Email').fill('wrong@example.com');
    await page.getByLabel('Password').fill('wrongpassword');
    await page.getByRole('button', { name: 'Sign In' }).click();

    await expect(page.getByText(/invalid credentials/i)).toBeVisible();
    await expect(page).toHaveURL('/login');
  });

  test('validates required fields', async ({ page }) => {
    await page.click('text=Sign In');
    await page.getByRole('button', { name: 'Sign In' }).click();

    await expect(page.getByText(/email is required/i)).toBeVisible();
    await expect(page.getByText(/password is required/i)).toBeVisible();
  });

  test('user can log out', async ({ page }) => {
    // Log in first
    await page.goto('/login');
    await page.getByLabel('Email').fill('user@example.com');
    await page.getByLabel('Password').fill('password123');
    await page.getByRole('button', { name: 'Sign In' }).click();

    await expect(page).toHaveURL('/dashboard');

    // Log out
    await page.getByRole('button', { name: 'Log Out' }).click();

    // Verify redirect
    await expect(page).toHaveURL('/');
    await expect(page.getByText('Sign In')).toBeVisible();
  });
});

Test Quality Guidelines

  1. Clear Test Names - Describe what is being tested
  2. Arrange-Act-Assert - Structure tests clearly
  3. Type Safety - Use proper types for test data
  4. Independent Tests - No shared state between tests
  5. Edge Cases - Test boundary conditions
  6. Error Cases - Test error handling
  7. Mock Appropriately - Mock external dependencies
  8. Async Handling - Properly await async operations
  9. Accessibility - Use semantic queries (getByRole, getByLabel)
  10. Meaningful Assertions - Test behavior, not implementation

Coverage Goals

Aim for:

  • Utilities: 100% coverage (pure functions)
  • Business Logic: 90%+ coverage
  • UI Components: 80%+ coverage
  • Integration: Critical paths covered
  • E2E: Happy paths + critical error scenarios

When generating tests, provide comprehensive coverage while maintaining readability and maintainability.