frontend-testing
Skill1 starsChanged 58 days agoMITView on GitHub
Write behavioral tests for feature modules using Vitest, React Testing Library, and MSW v2. Use when writing tests, adding test coverage, setting up test infrastructure (MSW handlers, shared factories, jsdom polyfills), or when the user says "write tests", "add coverage", "test this component/hook/feature". NOT for judging existing test quality inside the pipeline (the test-reviewer agent owns that gate) or finding the root cause of a failing test (systematic-debugging).
18 other projects use a version of this.
Adopted by
Most recently taken up by
- lien0219/openxflowtheir own version, first seen Nov 8, 2026
- theexperiencecompany/gaiatheir own version, first seen Nov 3, 2026
- YosemiteCrew/Yosemite-Crewtheir own version, first seen Nov 1, 2026
- langgenius/difytheir own version, first seen Oct 29, 2026
- aboio-labs/aboio-skillstheir own version, first seen Oct 13, 2026
- nightscout/nocturnetheir own version, first seen Oct 8, 2026
Used with
Projects with it also use backend-code-review, frontend-query-mutation and 3 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
jpoindexterSkillbackend-code-review
Review backend code for quality, security, maintainability, and best practices based on established checklist rules. Use when the user requests a review, analysis, or improvement of backend files (e.g., `.py`) under the `api/` directory. Do NOT use for frontend files (e.g., `.tsx`, `.ts`, `.js`). Supports pending-change review, code snippets review, and file-focused review.
Also in 5 projects that use this
lien0219Skillfrontend-query-mutation
Guide for implementing Langflow frontend query and mutation patterns with Axios and TanStack React Query v5. Trigger when creating or updating API hooks in controllers/API/queries, consuming UseRequestProcessor in components, deciding whether to use useQuery or useMutation, handling conditional queries, cache invalidation, mutation error handling, or migrating legacy API calls to the query hook pattern.
Also in 3 projects that use this
Charlie85270Skillcomponent-refactoring
Refactor high-complexity React components in Dify frontend. Use when `pnpm analyze-component --json` shows complexity > 50 or lineCount > 300, when the user asks for code splitting, hook extraction, or complexity reduction, or when `pnpm analyze-component` warns to refactor before testing; avoid for simple/well-structured components, third-party wrappers, or when the user explicitly wants testing without refactoring.
Also in 4 projects that use this
chendongqiSkillfrontend-code-review
Trigger when the user requests a review of frontend files (e.g., `.tsx`, `.ts`, `.js`). Support both pending-change reviews and focused file reviews while applying the checklist rules.
Also in 7 projects that use this
etewiahSkille2e-testing
Playwright E2E testing and Lighthouse performance auditing. Use when setting up E2E tests, running Playwright tests, performing Lighthouse audits, or debugging E2E test failures.
Also in 3 projects that use this
Other versions18 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 18
- langgenius/dify★ 161ksame name
- langflow-ai/langflow★ 156ksame name
- YFGaia/dify-plus★ 2.3ksame name
- YosemiteCrew/Yosemite-Crew★ 2ksame name
- Ohh-889/skyroc-admin★ 795same name
- theexperiencecompany/gaia★ 325same name
- mcowger/plexus★ 239same name
- nightscout/nocturne★ 163same name
- nilecui/SkillsBase★ 27same name
- lien0219/openxflow★ 8same name
8 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository27 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- design-craft/CLAUDE.mdCLAUDE.md
- android-emulatorSkill
- audit-branchSkill
- brainstormSkill
- create-devtoolSkill
- create-featureSkill
- create-infrastructureSkill
- execute-tasksSkill
- finish-featureSkill
- generate-feature-docSkill
- generate-prdSkill
- git-commitSkill
- ios-simulatorSkill
- modify-featureSkill
- pipeline-helpSkill
- plan-implementationSkill
- prd-clarifierSkill
- prd-to-uxSkill
- prototypeSkill
- react-clean-architectureSkill
- receiving-code-reviewSkill
- refactoring-uiSkill
- setup-daher-skillsSkill
- systematic-debuggingSkill
- test-mobile-appSkill
- writing-skillsSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
15 rules. 0 are common, 9 are distinctive, and 6 are very specific to this project.
0
Common
Most of the 18 edited versions we read keep these.
9
Distinctive
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 9
Write tests that give confidence your app works — not tests that verify implementation details.
- Distinctive: line 64
Do NOT write all the tests first and then all the implementation.
- Distinctive: line 95
Follow AAA pattern: Arrange, Act, Assert
- Distinctive: line 99
Always prefer queries that match how users and assistive technology interact with your app.
- Distinctive: line 131
When a label is not available (less ideal than label text).
- Distinctive: line 146
Only use when semantic queries don't work or don't make sense (e.g., dynamic text, non-semantic wrappers).
- Very specific: line 210
When a test needs a *different* router shape (e.g., populated query), keep the mock's mutable pieces at module scope and reset them in …
- Distinctive: line 253
No mocking needed. Test inputs → outputs.
- Very specific: line 293
When testing async behavior, use waitFor for:
- Very specific: line 353
Always await userEvent methods
- Very specific: line 354
Use waitFor or findBy* for async assertions
- Distinctive: line 355
If you see act() warnings, your test is incomplete
- Distinctive: line 367
Read references/factories.md for the full patterns (factory templates, MSW wiring, mapper tests, contract smoke) before writing factories …
- Very specific: line 384
Read the feature's existing artifacts (.claude/pipeline/{feature}/ and the feature folder: DESIGN.md, PRD.md, UX-spec.md, the …
- Very specific: line 388
Use the AskUserQuestion tool for the transition (never numbered text options), with a one-line summary of what was completed.
“Common” is measured: kept by at least 6 in 10 of the 18 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Project config:
{app}is the project's new-code root from.claude/rules/project-structure.md; project values (package manager, commands, base branch, error surface) come fromdocs/agents/project-conventions.md. Resolve both before writing any file — never assume. If a needed file is missing, stop: "Run/setup-daher-skillsfirst — missing<file>."
Frontend Testing
Write tests that give confidence your app works — not tests that verify implementation details.
Testing Philosophy
"The more your tests resemble the way your software is used, the more confidence they can give you." — Kent C. Dodds
Do NOT test implementation details
Implementation details are things users of your code will not typically use, see, or know about. Testing them leads to:
- False negatives — tests break when you refactor, even though the app still works
- False positives — tests pass even though the app is broken
// ❌ DON'T: Test hook return values directly
const { result } = renderHook(() => useNavbar())
expect(result.current.isOpen).toBe(false) // user never sees "isOpen"
// ✅ DO: Test what the user sees and does
render(<Navbar />)
await userEvent.click(screen.getByLabelText(/menu/i))
expect(screen.getByRole('navigation')).toBeVisible()Who are the "users"?
- End users — interact via rendered UI (buttons, text, navigation)
- Developers — interact via props and public hook APIs
Tests should only assert on things these two users care about.
When renderHook IS appropriate
- Provider/context hooks (
useAuth) — the hook IS the public developer API - TanStack Query hooks — testing data fetching behavior
- Pure logic hooks with no UI counterpart (rare)
For hooks that power a component (useNavbar, useBackHeader, useLanguageSelector), test through the component instead.
Stack
| Tool | Purpose |
| Vitest | Test runner and assertions |
| React Testing Library | Component and hook rendering |
| @testing-library/user-event | Simulating user interactions |
| MSW v2 | API mocking at the network level |
| jsdom | Browser environment simulation |
Test-First Workflow
Build features as vertical slices with a red-green loop — one test, one piece of implementation, repeat.
Anti-pattern: horizontal slices
Do NOT write all the tests first and then all the implementation. Tests written in bulk verify imagined behavior — they end up asserting on the shape of things (data structures, signatures) instead of user-facing behavior, pass when behavior breaks, and fail when it's fine.
WRONG (horizontal): RIGHT (vertical, tracer bullets):
RED: test1..test5 RED→GREEN: test1→impl1
GREEN: impl1..impl5 RED→GREEN: test2→impl2
RED→GREEN: test3→impl3Tracer bullet first
Start with ONE test that proves the path end-to-end — component renders → hook fires → MSW handler answers → UI shows the result — then write the minimal code to pass it. Each subsequent test responds to what the previous cycle taught you: because you just wrote the code, you know exactly which behavior matters and how to verify it.
The loop
- RED — write the next failing test and watch it fail:
pnpm vitest run {features-root}/{feature}(orpnpm test:watchwhile looping — a long-lived process: stop it when the loop ends, and prefervitest runin agent sessions) - GREEN — write the minimal code that passes. No speculative features, no code for tests you haven't written yet.
- REFACTOR — clean up with tests green. Never refactor while RED.
Mock only at system boundaries: HTTP via MSW handlers (see references/msw-setup.md), next/router, time. Never mock your own modules just to force a test green — see references/testing-anti-patterns.md.
Regression tests must fail first
A bug-fix test only counts if you have verified it fails without the fix. If it passes before the fix lands, it isn't testing the bug.
Test File Conventions
Co-location (tests live next to their source file, never in separate test directories), runner config, test commands, and per-feature helper locations are defined in the project rule — see .claude/rules/testing.md. Beyond the rule:
- One describe block per module, nested describes for scenarios
- Follow AAA pattern: Arrange, Act, Assert
Query Priority (Accessibility First)
Always prefer queries that match how users and assistive technology interact with your app. Follow this priority order:
1. 🥇 getByRole (Preferred)
Queries elements exposed in the accessibility tree. Best for testing accessibility and should be your default choice.
// Buttons
screen.getByRole('button', { name: /submit/i })
screen.getByRole('button', { name: /cancel/i })
// Form fields
screen.getByRole('textbox', { name: /email/i })
screen.getByRole('textbox', { name: /password/i })
screen.getByRole('combobox', { name: /country/i })
// Navigation
screen.getByRole('navigation')
screen.getByRole('link', { name: /home/i })
// Headings
screen.getByRole('heading', { name: /dashboard/i, level: 1 })2. 🥈 getByLabelText
For form fields associated with labels. Excellent for accessibility.
screen.getByLabelText(/email address/i)
screen.getByLabelText(/password/i)3. 🥉 getByPlaceholderText
When a label is not available (less ideal than label text).
screen.getByPlaceholderText(/search\.\.\./i)4. getByText
For non-interactive elements (paragraphs, divs, spans) or finding by text content.
screen.getByText(/welcome back/i)
screen.getByText(/no results found/i)5. 🚫 getByTestId (Last Resort)
Only use when semantic queries don't work or don't make sense (e.g., dynamic text, non-semantic wrappers).
// Avoid this when possible
screen.getByTestId('user-avatar')Why this order matters:
getByRoleandgetByLabelTexttest accessibility- They force proper semantic HTML
- They align with how screen readers work
- They make your app more accessible to users with disabilities
Testing Strategy by Layer
1. Components (Primary — test behavior through UI)
This is where most tests should live. Render the component, interact as a user would, assert on visible output.
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
// vi.mock is HOISTED to the top of the file — declare it at module level,
// never inside an `it` block. Module-scope mock fns are safe to reference
// because the factory's returned functions read them at render time.
const mockPush = vi.fn()
vi.mock('next/router', () => ({
useRouter: () => ({ push: mockPush, isReady: true, query: {} }),
}))
describe('Navbar', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('opens the menu when hamburger is clicked', async () => {
render(<Navbar />)
// Use getByRole for buttons with accessible name
await userEvent.click(screen.getByRole('button', { name: /menu/i }))
expect(screen.getByRole('navigation')).toBeVisible()
})
it('hides auth-required items when unauthenticated', async () => {
render(<Navbar />) // default: unauthenticated
await userEvent.click(screen.getByRole('button', { name: /menu/i }))
expect(screen.queryByText(/messages/i)).not.toBeInTheDocument()
})
it('navigates to home when logo is clicked', async () => {
render(<Navbar />)
await userEvent.click(screen.getByRole('link', { name: /home/i }))
expect(mockPush).toHaveBeenCalledWith('/')
})
})When a test needs a different router shape (e.g., populated query), keep the mock's mutable pieces at module scope and reset them in beforeEach — the same pattern the project's existing tests use:
const mockPush = vi.fn().mockResolvedValue(undefined)
const mockQuery: Record<string, string> = {}
vi.mock('next/router', () => ({
useRouter: () => ({ isReady: true, query: mockQuery, push: mockPush }),
}))
beforeEach(() => {
vi.clearAllMocks()
Object.keys(mockQuery).forEach((key) => delete mockQuery[key])
})
it('preselects the reservation from the URL', () => {
mockQuery.reservationId = '42'
// ...
})2. Providers / Context Hooks (renderHook is OK)
The hook IS the public API for developers. Test it directly.
import { renderHook, waitFor } from '@testing-library/react'
import { AuthProvider, useAuth } from './auth-provider'
const wrapper = ({ children }) => <AuthProvider>{children}</AuthProvider>
describe('useAuth', () => {
it('returns authenticated when valid user in localStorage', async () => {
localStorage.setItem('user', JSON.stringify(validUser))
const { result } = renderHook(() => useAuth(), { wrapper })
await waitFor(() => expect(result.current.isAuthenticated).toBe(true))
})
})3. Domain Layer (Pure Functions)
No mocking needed. Test inputs → outputs.
import { canEdit, getStatusLabel } from '../{feature}.service'
describe('{feature}.service', () => {
it('returns true for active items', () => {
expect(canEdit({ status: 'active' })).toBe(true)
})
})4. API Layer (MSW)
Use MSW to mock HTTP at the network level. The server is set up per test file — there is no global MSW server (see references/msw-setup.md). Handlers return wire shapes — use DTO factories (see Shared Test Factories below) rather than inline literals for anything non-trivial. Adapter tests then assert on the mapped domain output, exercising the full validate→map boundary (rules/api-boundary.md).
import { setupServer } from 'msw/node'
import { http, HttpResponse } from 'msw'
const server = setupServer()
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
describe('{feature}Api', () => {
it('maps response correctly', async () => {
server.use(
http.get('*/{feature}s', () => HttpResponse.json({ results: [{ id: 1 }] }))
)
const result = await {feature}Api.getAll({})
expect(result.results).toHaveLength(1)
})
})Async Testing Patterns
Use waitFor for Async Assertions
When testing async behavior, use waitFor for:
- Component re-renders
- State changes after async operations
- Asynchronous updates from data fetching
import { waitFor } from '@testing-library/react'
it('displays data after loading', async () => {
render(<UserProfile userId="123" />)
// Wait for loading to complete
await waitFor(() => {
expect(screen.queryByText(/loading/i)).not.toBeInTheDocument()
})
// Assert final state
expect(screen.getByText(/john doe/i)).toBeInTheDocument()
})Alternative: findBy Queries
For simpler cases, use findBy* queries (combines getBy + waitFor):
it('displays data after loading', async () => {
render(<UserProfile userId="123" />)
// Automatically waits for element to appear
expect(await screen.findByText(/john doe/i)).toBeInTheDocument()
})Handling act() Warnings
React Testing Library methods handle act() automatically. If you see act() warnings:
What it means:
- Component state updated but test didn't wait for it
- Indicates untested component updates
How to fix:
// ❌ BAD: Missing await
userEvent.click(button)
expect(screen.getByText(/success/i)).toBeInTheDocument()
// ✅ GOOD: Await user interaction
await userEvent.click(button)
expect(screen.getByText(/success/i)).toBeInTheDocument()
// ✅ GOOD: Wait for async updates
await userEvent.click(button)
await waitFor(() => {
expect(screen.getByText(/success/i)).toBeInTheDocument()
})General rule:
- Always
awaituserEvent methods - Use
waitFororfindBy*for async assertions - If you see act() warnings, your test is incomplete
Additional Resources
- For test utilities, wrapper patterns, rendering helpers, and the gotchas that flake tests (
QueryClientlifetime, disappearing loaders, error-boundary spies, the jsdom/real-browser boundary, whenrenderHookis warranted), see references/test-utilities.md - For MSW v2 setup, handler patterns, and network mocking, see references/msw-setup.md
- For the ways tests rot into false confidence (testing mocks, test-only production code, mocking without understanding), see references/testing-anti-patterns.md
Factories, Mapper Tests, and Contract Smoke
When a domain type appears in 3+ test files (or has 5+ required fields), use a shared test factory — two levels, mirroring the API boundary: DTO factories (wire shapes, satisfies z.input<typeof xDtoSchema>, feed MSW handlers) and domain factories (hand-authored domain types, feed component/hook/domain tests). Mappers always get direct unit tests — the cheapest tests in the codebase.
Read references/factories.md for the full patterns (factory templates, MSW wiring, mapper tests, contract smoke) before writing factories or MSW handlers.
Checklist
[ ] Test-first: every test was seen RED before its implementation landed
[ ] Component tests: render + userEvent + screen assertions
[ ] Provider tests: renderHook for public API hooks only
[ ] Domain tests: pure function input/output
[ ] Mapper tests: toX(dtoFactory()) asserts mapping + normalization
[ ] API tests: MSW handlers return DTO-factory output; assert mapped domain result
[ ] DTO factories declare `satisfies z.input<typeof xDtoSchema>`
[ ] NO hook-level tests for hooks that power componentsResume After Context Cleanup
Read the feature's existing artifacts (.claude/pipeline/{feature}/ and the feature folder: DESIGN.md, PRD.md, UX-spec.md, the implementation plan, PROGRESS.md — whichever exist), then the feature code and its existing test files, and continue from where they leave off — never restart from scratch.
Next Step
Use the AskUserQuestion tool for the transition (never numbered text options), with a one-line summary of what was completed. Options:
- generate-feature-doc — document the implementation
- Something else — do something different
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

