
Frontend Testing
- 457 installs
- 305 repo stars
- Updated March 4, 2026
- aj-geddes/useful-ai-prompts
frontend-testing is a useful-ai-prompts skill that guides adding unit, component, and integration tests for UI logic, hooks, and user flows before merge or release.
About
frontend-testing is a prompt skill from aj-geddes/useful-ai-prompts for adding frontend test coverage before merge or release. It structures work on unit tests for utilities, component tests for UI pieces, and integration tests for hooks and user flows so regressions surface in CI instead of production. Developers reach for frontend-testing when a feature branch needs systematic test plans for React or similar UI stacks without writing boilerplate guidance from scratch. The skill encodes testing workflows agents can execute against existing component and hook files.
- Component and hook unit testing
- User interaction and accessibility assertions
- Mocking network and router dependencies
- Snapshot vs behavior-driven test choices
- CI-friendly test layout and coverage targets
Frontend Testing by the numbers
- 457 all-time installs (skills.sh)
- Ranked #624 of 2,153 Testing & QA skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
npx skills add https://github.com/aj-geddes/useful-ai-prompts --skill frontend-testingAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 457 |
|---|---|
| repo stars | ★ 305 |
| Last updated | March 4, 2026 |
| Repository | aj-geddes/useful-ai-prompts ↗ |
How do you add frontend tests for hooks and user flows?
Add unit, component, and integration tests for UI logic, hooks, and user flows before merging or releasing frontend changes.
Who is it for?
Frontend developers preparing a feature branch for merge who need structured unit, component, and integration test coverage.
Skip if: Backend-only changes with no UI surface or teams already running exhaustive E2E suites that cover the same flows end to end.
When should I use this skill?
Frontend changes need unit, component, or integration tests for hooks and user flows before merge or release.
What you get
Unit tests, component tests, and integration tests covering UI logic, hooks, and critical user flows
- Unit test files
- Component test suites
- User-flow integration tests
Files
Frontend Testing
Table of Contents
Overview
Build comprehensive test suites for frontend applications including unit tests, integration tests, and end-to-end tests with proper coverage and assertions.
When to Use
- Component testing
- Integration testing
- End-to-end testing
- Regression prevention
- Quality assurance
- Test-driven development
Quick Start
Minimal working example:
// Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { Button } from './Button';
describe('Button Component', () => {
it('renders button with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
it('calls onClick handler when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click</Button>);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('disables button when disabled prop is true', () => {
render(<Button disabled>Click me</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
it('applies variant styles correctly', () => {
// ... (see reference guides for full implementation)Reference Guides
Detailed implementations in the references/ directory:
| Guide | Contents |
|---|---|
| Jest Unit Testing (React) | Jest Unit Testing (React) |
| React Testing Library Integration Tests | React Testing Library Integration Tests |
| Vitest for Vue Testing | Vitest for Vue Testing |
| Cypress E2E Testing | Cypress E2E Testing |
| Test Coverage Configuration | Test Coverage Configuration |
Best Practices
✅ DO
- Follow established patterns and conventions
- Write clean, maintainable code
- Add appropriate documentation
- Test thoroughly before deploying
❌ DON'T
- Skip testing or validation
- Ignore error handling
- Hard-code configuration values
Cypress E2E Testing
Cypress E2E Testing
// cypress/e2e/login.cy.ts
describe("Login Flow", () => {
beforeEach(() => {
cy.visit("http://localhost:3000/login");
});
it("logs in with valid credentials", () => {
cy.get('input[name="email"]').type("user@example.com");
cy.get('input[name="password"]').type("password123");
cy.get('button[type="submit"]').click();
cy.url().should("include", "/dashboard");
cy.get("h1").should("contain", "Welcome");
});
it("displays error for invalid credentials", () => {
cy.get('input[name="email"]').type("user@example.com");
cy.get('input[name="password"]').type("wrongpassword");
cy.get('button[type="submit"]').click();
cy.get(".error-message").should("contain", "Invalid credentials");
});
it("validates email field", () => {
cy.get('input[name="email"]').type("invalid-email");
cy.get('input[name="password"]').type("password123");
cy.get('button[type="submit"]').click();
cy.get(".error-message").should("contain", "Invalid email");
});
});
// cypress/e2e/user-management.cy.ts
describe("User Management", () => {
beforeEach(() => {
cy.login("admin@example.com", "password123");
cy.visit("http://localhost:3000/users");
});
it("creates a new user", () => {
cy.get('button:contains("Add User")').click();
cy.get('input[name="name"]').type("New User");
cy.get('input[name="email"]').type("newuser@example.com");
cy.get('button[type="submit"]').click();
cy.get(".success-message").should("contain", "User created");
cy.get("table tbody").should("contain", "New User");
});
it("edits an existing user", () => {
cy.get("table tbody tr").first().contains("button", "Edit").click();
cy.get('input[name="name"]').clear().type("Updated Name");
cy.get('button[type="submit"]').click();
cy.get(".success-message").should("contain", "User updated");
});
it("deletes a user with confirmation", () => {
cy.get("table tbody tr").first().contains("button", "Delete").click();
cy.get('.modal button:contains("Confirm")').click();
cy.get(".success-message").should("contain", "User deleted");
});
});
// cypress/support/commands.ts
Cypress.Commands.add("login", (email: string, password: string) => {
cy.visit("http://localhost:3000/login");
cy.get('input[name="email"]').type(email);
cy.get('input[name="password"]').type(password);
cy.get('button[type="submit"]').click();
cy.url().should("include", "/dashboard");
});Jest Unit Testing (React)
Jest Unit Testing (React)
// Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom';
import { Button } from './Button';
describe('Button Component', () => {
it('renders button with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByRole('button')).toHaveTextContent('Click me');
});
it('calls onClick handler when clicked', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click</Button>);
fireEvent.click(screen.getByRole('button'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
it('disables button when disabled prop is true', () => {
render(<Button disabled>Click me</Button>);
expect(screen.getByRole('button')).toBeDisabled();
});
it('applies variant styles correctly', () => {
const { container } = render(<Button variant="primary">Click</Button>);
const button = container.querySelector('button');
expect(button).toHaveClass('bg-blue-500');
});
it('applies size classes correctly', () => {
const { container } = render(<Button size="lg">Click</Button>);
const button = container.querySelector('button');
expect(button).toHaveClass('px-6 py-3 text-lg');
});
});
// hooks.test.ts
import { renderHook, act } from '@testing-library/react';
import { useCounter } from './useCounter';
describe('useCounter', () => {
it('initializes with default value', () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
});
it('increments count', () => {
const { result } = renderHook(() => useCounter());
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
it('decrements count', () => {
const { result } = renderHook(() => useCounter(5));
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(4);
});
it('resets count', () => {
const { result } = renderHook(() => useCounter(5));
act(() => {
result.current.increment();
result.current.reset();
});
expect(result.current.count).toBe(5);
});
});React Testing Library Integration Tests
React Testing Library Integration Tests
// UserForm.test.tsx
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserForm } from './UserForm';
describe('UserForm Integration', () => {
beforeEach(() => {
// Clear mocks before each test
jest.clearAllMocks();
});
it('submits form with valid data', async () => {
const handleSubmit = jest.fn();
render(<UserForm onSubmit={handleSubmit} />);
await userEvent.type(screen.getByLabelText(/name/i), 'John Doe');
await userEvent.type(screen.getByLabelText(/email/i), 'john@example.com');
await userEvent.type(screen.getByLabelText(/password/i), 'password123');
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(handleSubmit).toHaveBeenCalledWith({
name: 'John Doe',
email: 'john@example.com',
password: 'password123'
});
});
});
it('displays validation errors for empty fields', async () => {
render(<UserForm onSubmit={jest.fn()} />);
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(screen.getByText(/name is required/i)).toBeInTheDocument();
expect(screen.getByText(/email is required/i)).toBeInTheDocument();
});
});
it('displays validation error for invalid email', async () => {
render(<UserForm onSubmit={jest.fn()} />);
await userEvent.type(screen.getByLabelText(/email/i), 'invalid-email');
fireEvent.click(screen.getByRole('button', { name: /submit/i }));
await waitFor(() => {
expect(screen.getByText(/invalid email/i)).toBeInTheDocument();
});
});
});
// UserList.test.tsx with data fetching
import { render, screen, waitFor } from '@testing-library/react';
import { UserList } from './UserList';
describe('UserList with API', () => {
beforeEach(() => {
jest.spyOn(global, 'fetch').mockClear();
});
it('displays loading state initially', () => {
(global.fetch as jest.Mock).mockImplementation(
() => new Promise(() => {}) // Never resolves
);
render(<UserList />);
expect(screen.getByText(/loading/i)).toBeInTheDocument();
});
it('fetches and displays users', async () => {
const mockUsers = [
{ id: 1, name: 'User 1', email: 'user1@example.com' },
{ id: 2, name: 'User 2', email: 'user2@example.com' }
];
(global.fetch as jest.Mock).mockResolvedValueOnce({
ok: true,
json: async () => mockUsers
});
render(<UserList />);
await waitFor(() => {
expect(screen.getByText('User 1')).toBeInTheDocument();
expect(screen.getByText('User 2')).toBeInTheDocument();
});
});
it('displays error message on fetch failure', async () => {
(global.fetch as jest.Mock).mockRejectedValueOnce(new Error('Network error'));
render(<UserList />);
await waitFor(() => {
expect(screen.getByText(/error/i)).toBeInTheDocument();
});
});
});Test Coverage Configuration
Test Coverage Configuration
// jest.config.js
module.exports = {
preset: 'ts-jest',
testEnvironment: 'jsdom',
roots: ['<rootDir>/src'],
testMatch: ['**/__tests__/**/*.ts?(x)', '**/?(*.)+(spec|test).ts?(x)'],
collectCoverageFrom: [
'src/**/*.{ts,tsx}',
'!src/**/*.d.ts',
'!src/index.tsx',
'!src/reportWebVitals.ts'
],
coverageThreshold: {
global: {
branches: 70,
functions: 70,
lines: 70,
statements: 70
}
},
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1'
},
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
transform: {
'^.+\\.tsx?$': ['ts-jest', {
tsconfig: {
jsx: 'react-jsx'
}
}]
}
};
// package.json scripts
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage",
"cypress": "cypress open",
"cypress:headless": "cypress run"
}
}Vitest for Vue Testing
Vitest for Vue Testing
// Button.spec.ts
import { describe, it, expect } from "vitest";
import { mount } from "@vue/test-utils";
import Button from "./Button.vue";
describe("Button.vue", () => {
it("renders slot content", () => {
const wrapper = mount(Button, {
slots: {
default: "Click me",
},
});
expect(wrapper.text()).toContain("Click me");
});
it("emits click event", async () => {
const wrapper = mount(Button);
await wrapper.trigger("click");
expect(wrapper.emitted("click")).toHaveLength(1);
});
it("disables button when disabled prop is true", () => {
const wrapper = mount(Button, {
props: { disabled: true },
});
expect(wrapper.attributes("disabled")).toBeDefined();
});
it("applies variant class", () => {
const wrapper = mount(Button, {
props: { variant: "primary" },
});
expect(wrapper.classes()).toContain("bg-blue-500");
});
});
// composable.spec.ts
import { describe, it, expect } from "vitest";
import { useCounter } from "./useCounter";
describe("useCounter", () => {
it("initializes with default value", () => {
const { count } = useCounter();
expect(count.value).toBe(0);
});
it("increments count", () => {
const { count, increment } = useCounter();
increment();
expect(count.value).toBe(1);
});
});#!/bin/bash
# scaffold-tests.sh - Generate test file scaffolding
# Usage: ./scaffold-tests.sh <source_file> [--framework jest|pytest|mocha]
set -euo pipefail
SOURCE_FILE="${{1:?Usage: $0 <source_file> [--framework jest|pytest|mocha]}}"
FRAMEWORK="${{2:-jest}}"
echo "Scaffolding tests for: $SOURCE_FILE (framework: $FRAMEWORK)"
# TODO: Implement test scaffolding logic
# - Parse source file for exported functions/classes
# - Generate test stubs for each export
# - Include setup/teardown boilerplate
# - Add common assertion patterns
echo "Test scaffolding complete."
// Test Template
// TODO: Customize for your testing framework and project
describe('ModuleName', () => {
// Setup
beforeEach(() => {
// TODO: Add test setup
});
afterEach(() => {
// TODO: Add cleanup
});
describe('functionName', () => {
it('should handle the happy path', () => {
// TODO: Add assertion
});
it('should handle edge cases', () => {
// TODO: Add edge case tests
});
it('should handle errors gracefully', () => {
// TODO: Add error handling tests
});
});
});
Related skills
FAQ
What test types does frontend-testing cover?
frontend-testing covers unit tests for UI logic, component tests for interface pieces, and integration tests for hooks and user flows before frontend changes merge or release.
When should developers use frontend-testing?
Use frontend-testing when a frontend feature branch needs systematic test coverage for components, hooks, and user flows and you want agent-guided testing workflows instead of ad hoc test stubs.