| name | test-coverage-advisor |
| description | Analyze and improve test coverage for a React + TypeScript codebase using Vitest and React Testing Library. Use this skill when the user asks about test coverage, missing tests, untested code paths, writing tests, or improving test quality. Triggers on mentions of test coverage, missing tests, untested, coverage gaps, write tests, testing strategy, or coverage report. |
Test Coverage Advisor
Overview
Analyze React + TypeScript source files to identify coverage gaps and generate
tests using Vitest + React Testing Library. Follow accessibility-first testing
patterns and the project conventions in .github/copilot-instructions.md.
Testing Standards
- Framework: Vitest + React Testing Library
- Mocking: MSW (Mock Service Worker) for API calls
- Target: 80%+ code coverage
- Selector priority:
getByRole > getByLabelText > getByText > getByTestId
- File naming:
ComponentName.test.tsx next to the source file
Analysis Steps
1. Identify What Exists
- Check for a corresponding
.test.tsx or .test.ts file
- If tests exist, review what scenarios are covered
- Check
vitest.config.ts for coverage thresholds and exclude patterns
2. Identify Coverage Gaps
For components, ensure tests cover:
- Default render (smoke test)
- Each conditional branch in JSX (
{isLoading && ...}, ternaries)
- User interactions (clicks, form inputs, keyboard nav)
- Error states and fallback UI
- Accessibility: ARIA labels, roles, keyboard focus
For hooks, ensure tests cover:
- Initial state
- State transitions after actions
- Cleanup on unmount
- Error handling paths
For services/utils, ensure tests cover:
- Happy path with typical input
- Edge cases (empty input, null, undefined, boundary values)
- Error throwing / rejection paths
- Return type correctness
3. Generate Missing Tests
When writing tests, follow this structure:
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { describe, it, expect, vi } from 'vitest';
import { UserCard } from './UserCard';
describe('UserCard', () => {
it('renders the user name', () => {
render(<UserCard name="Ada Lovelace" role="Engineer" />);
expect(screen.getByRole('heading', { name: /ada lovelace/i })).toBeInTheDocument();
});
it('calls onEdit when the edit button is clicked', async () => {
const user = userEvent.setup();
const handleEdit = vi.fn();
render(<UserCard name="Ada Lovelace" role="Engineer" onEdit={handleEdit} />);
await user.click(screen.getByRole('button', { name: /edit/i }));
expect(handleEdit).toHaveBeenCalledOnce();
});
it('displays a fallback when the avatar fails to load', () => {
render(<UserCard name="Ada Lovelace" role="Engineer" avatarUrl="bad-url" />);
expect(screen.getByRole('img', { name: /ada lovelace/i })).toHaveAttribute(
'src',
expect.stringContaining('fallback')
);
});
});
MSW Mocking Pattern
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
http.get('/api/users', () => {
return HttpResponse.json([
{ id: 1, name: 'Ada Lovelace' },
{ id: 2, name: 'Grace Hopper' },
]);
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Hook Testing Pattern
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';
describe('useCounter', () => {
it('starts at the initial value', () => {
const { result } = renderHook(() => useCounter(5));
expect(result.current.count).toBe(5);
});
it('increments correctly', () => {
const { result } = renderHook(() => useCounter(0));
act(() => result.current.increment());
expect(result.current.count).toBe(1);
});
});
Output Format
When analyzing coverage gaps, provide:
- File — source file path
- Has Tests — Yes / No / Partial
- Gaps — list of untested scenarios
- Priority — High (core logic, user-facing), Medium (utility, helper), Low (static, display-only)
Then generate ready-to-use test code for the highest-priority gaps.
Example
User Request
"What's untested in my LoginForm component?"
Expected Output
## Coverage Analysis: LoginForm.tsx
**Test file**: LoginForm.test.tsx — exists, 3 tests
### Covered
✅ Renders email and password fields
✅ Shows validation error for empty email
✅ Calls onSubmit with form data
### Gaps (4 found)
| # | Gap | Priority |
|---|-----|----------|
| 1 | Password validation error display | High |
| 2 | Submit button disabled while loading | High |
| 3 | Network error message from failed API call | High |
| 4 | "Forgot password" link navigation | Medium |
### Generated Tests
[... ready-to-paste test code for gaps 1–3 ...]