diff --git a/.claude/skills/pipeline-rules/SKILL.md b/.claude/skills/pipeline-rules/SKILL.md new file mode 100644 index 0000000..77a43d9 --- /dev/null +++ b/.claude/skills/pipeline-rules/SKILL.md @@ -0,0 +1,27 @@ +--- +name: pipeline-rules +description: Frontend (Next.js/TypeScript) lint, format, test, and build checks that mirror what CI enforces on this repo — ESLint, Prettier, Vitest, and a Next build. Use whenever files under web/ are added, edited, or reviewed, before considering the work done. +metadata: + type: workflow +when_to_use: "finishing a frontend change, editing a file under web/, before saying a frontend task is done, reviewing a TS/TSX diff, formatting check, eslint, prettier, vitest, next build" +--- + +# Frontend pipeline rules + +Before treating any task that touches files under `web/` as finished, run the same checks CI would run — in this order, from the `web/` directory: + +1. **Format** — `pnpm format` + Auto-fixes via Prettier. Use `pnpm format:check` instead if you only want to verify without rewriting files. + +2. **Lint** — `pnpm lint` + Runs ESLint. Fix everything it reports rather than leaving it for CI to catch. + +3. **Test** — `pnpm test` + Runs the Vitest suite. Scope it to the affected files when the full run is slow and the change is narrow. + +4. **Build** — `pnpm build` + Run this when the change is nontrivial or touches shared types/config — the Next build's type-checking catches errors that lint and unit tests don't. + +Format first, then lint, then test/build — formatting can shift lines lint doesn't care about, but do it before the final checks so the diff you hand back is already clean. + +Run this for every frontend change, not only ones that look formatting-related. Code that runs correctly locally but isn't linted, formatted, or type-clean still fails CI. diff --git a/.claude/skills/react-testing-library/README.md b/.claude/skills/react-testing-library/README.md new file mode 100644 index 0000000..9ee7d21 --- /dev/null +++ b/.claude/skills/react-testing-library/README.md @@ -0,0 +1,143 @@ +# React Testing Library Best Practices + +A comprehensive skill for writing maintainable, user-centric tests with React Testing Library. + +## Overview + +This skill contains 43 rules across 9 categories, prioritized by impact to guide test writing and code review. It covers query selection, async handling, user interactions, assertions, and common anti-patterns. + +### Structure + +``` +react-testing-library/ +├── SKILL.md # Entry point with quick reference +├── metadata.json # Version, org, references +├── README.md # This file +├── references/ +│ ├── _sections.md # Category definitions +│ ├── query-*.md # Query selection rules (CRITICAL) +│ ├── async-*.md # Async handling rules (CRITICAL) +│ ├── anti-*.md # Anti-pattern rules (CRITICAL) +│ ├── user-*.md # User interaction rules (HIGH) +│ ├── assert-*.md # Assertion rules (HIGH) +│ ├── setup-*.md # Component setup rules (MEDIUM) +│ ├── struct-*.md # Test structure rules (MEDIUM) +│ ├── debug-*.md # Debugging rules (LOW-MEDIUM) +│ └── a11y-*.md # Accessibility rules (LOW) +└── assets/ + └── templates/ + └── _template.md # Template for new rules +``` + +## Getting Started + +```bash +# Install dependencies (if in a project with validation scripts) +pnpm install + +# Build compiled documents +pnpm build + +# Validate skill structure +pnpm validate +``` + +## Creating a New Rule + +1. Choose the appropriate category prefix based on the rule's focus area +2. Create a new file in `references/` with the naming pattern `{prefix}-{description}.md` +3. Copy the template from `assets/templates/_template.md` +4. Fill in all required sections + +### Prefix Reference + +| Prefix | Category | Impact | +| --------- | --------------------- | ---------- | +| `query-` | Query Selection | CRITICAL | +| `async-` | Async Handling | CRITICAL | +| `anti-` | Common Anti-Patterns | CRITICAL | +| `user-` | User Interaction | HIGH | +| `assert-` | Assertions | HIGH | +| `setup-` | Component Setup | MEDIUM | +| `struct-` | Test Structure | MEDIUM | +| `debug-` | Debugging | LOW-MEDIUM | +| `a11y-` | Accessibility Testing | LOW | + +## Rule File Structure + +Each rule file must include: + +````markdown +--- +title: Rule Title Here +impact: CRITICAL|HIGH|MEDIUM|LOW-MEDIUM|LOW +impactDescription: Quantified impact (e.g., "2-10× improvement") +tags: category-prefix, technique, tool, concept +--- + +## Rule Title Here + +Brief explanation of WHY this matters. + +**Incorrect (what's wrong):** + +```tsx +// Bad code example +``` +```` + +**Correct (what's right):** + +```tsx +// Good code example +``` + +Reference: [Source](URL) + +``` + +## File Naming Convention + +Rule files follow the pattern: `{prefix}-{description}.md` + +- **prefix**: 3-8 character category identifier (e.g., `query`, `async`, `anti`) +- **description**: kebab-case description of the rule (e.g., `prefer-role`, `await-findby`) + +Examples: + +- `query-prefer-role.md` - Query selection rule about preferring getByRole +- `async-await-findby.md` - Async handling rule about awaiting findBy queries +- `anti-container-queries.md` - Anti-pattern rule about avoiding container queries + +## Impact Levels + +| Level | Description | +|-------|-------------| +| **CRITICAL** | Fundamental issues that cause test failures, false positives, or major maintenance burden | +| **HIGH** | Important patterns that significantly improve test quality and reliability | +| **MEDIUM** | Good practices that improve maintainability and readability | +| **LOW-MEDIUM** | Helpful patterns for specific situations | +| **LOW** | Nice-to-have improvements and advanced techniques | + +## Scripts + +| Command | Description | +|---------|-------------| +| `pnpm build` | Generate AGENTS.md compiled document | +| `pnpm validate` | Check skill structure against guidelines | +| `pnpm validate --strict` | Fail on warnings as well as errors | + +## Contributing + +1. Read existing rules to understand the expected format and quality +2. Follow the template structure exactly +3. Include realistic code examples (avoid `foo`, `bar`, generic names) +4. Quantify impact where possible (e.g., "2-10× improvement", "prevents X") +5. Run validation before submitting + +## Acknowledgments + +- [Testing Library](https://testing-library.com) - Official documentation +- [Kent C. Dodds](https://kentcdodds.com) - Creator guidance and best practices +- [jest-dom](https://github.com/testing-library/jest-dom) - Custom matchers +``` diff --git a/.claude/skills/react-testing-library/SKILL.md b/.claude/skills/react-testing-library/SKILL.md new file mode 100644 index 0000000..3802de6 --- /dev/null +++ b/.claude/skills/react-testing-library/SKILL.md @@ -0,0 +1,128 @@ +--- +name: react-testing-library +description: React Testing Library mechanics for testing React components and hooks — query selection, async handling, user interaction, assertions, provider setup, and the anti-patterns that make tests brittle. Use for any test that renders a React component or a hook, including choosing between getBy/queryBy/findBy, userEvent flows, waitFor usage, renderHook, custom render wrappers, and reviewing RTL tests for implementation-detail assertions. +metadata: + version: "1.1.0" + tags: "react, testing, rtl, components, hooks" + author: Ship Shit Dev +when_to_use: "test a React component, test a React hook, render a component in a test, getByRole, getByLabelText, queryBy vs getBy, findBy, waitFor, userEvent, fireEvent, renderHook, custom render with providers, screen.debug, my component test is brittle, review these RTL tests, testing-library anti-patterns" +--- + +# React Testing Library Best Practices + +43 rules across 9 categories for testing React components with Testing Library, prioritized by impact to guide test writing and code review. + +## Scope + +This skill owns React component and hook testing mechanics. For +framework-agnostic questions — which level a behavior belongs at, what a coverage +number means, how to choose test data, how to kill a flake — use +`testing-expert`. + +## When to Apply + +- Writing new component tests with React Testing Library +- Selecting queries (getByRole, getByLabelText, etc.) +- Handling async operations in tests (findBy, waitFor) +- Simulating user interactions (userEvent) +- Reviewing tests for anti-patterns and implementation detail testing + +## Rule Categories by Priority + +| Priority | Category | Impact | Prefix | +| -------- | --------------------- | ---------- | --------- | +| 1 | Query Selection | CRITICAL | `query-` | +| 2 | Async Handling | CRITICAL | `async-` | +| 3 | Common Anti-Patterns | CRITICAL | `anti-` | +| 4 | User Interaction | HIGH | `user-` | +| 5 | Assertions | HIGH | `assert-` | +| 6 | Component Setup | MEDIUM | `setup-` | +| 7 | Test Structure | MEDIUM | `struct-` | +| 8 | Debugging | LOW-MEDIUM | `debug-` | +| 9 | Accessibility Testing | LOW | `a11y-` | + +## Quick Reference + +### 1. Query Selection (CRITICAL) + +- [`query-prefer-role`](references/query-prefer-role.md) - Prefer getByRole over other queries +- [`query-avoid-testid`](references/query-avoid-testid.md) - Avoid getByTestId as primary query +- [`query-use-screen`](references/query-use-screen.md) - Use screen for queries +- [`query-label-text-forms`](references/query-label-text-forms.md) - Use getByLabelText for form fields +- [`query-role-name-option`](references/query-role-name-option.md) - Use name option with getByRole +- [`query-get-vs-query`](references/query-get-vs-query.md) - Use getBy for present, queryBy for absent +- [`query-within-scope`](references/query-within-scope.md) - Use within() to scope queries + +### 2. Async Handling (CRITICAL) + +- [`async-findby-over-waitfor`](references/async-findby-over-waitfor.md) - Use findBy instead of waitFor + getBy +- [`async-await-findby`](references/async-await-findby.md) - Always await findBy queries +- [`async-single-assertion-waitfor`](references/async-single-assertion-waitfor.md) - Single assertion in waitFor +- [`async-no-side-effects-waitfor`](references/async-no-side-effects-waitfor.md) - Avoid side effects in waitFor +- [`async-waitfor-disappear`](references/async-waitfor-disappear.md) - Use waitForElementToBeRemoved + +### 3. Common Anti-Patterns (CRITICAL) + +- [`anti-unnecessary-act`](references/anti-unnecessary-act.md) - Avoid unnecessary act() wrapping +- [`anti-manual-cleanup`](references/anti-manual-cleanup.md) - Remove manual cleanup calls +- [`anti-implementation-details`](references/anti-implementation-details.md) - Avoid testing implementation details +- [`anti-empty-waitfor`](references/anti-empty-waitfor.md) - Avoid empty waitFor callbacks +- [`anti-container-queries`](references/anti-container-queries.md) - Avoid using container for queries +- [`anti-redundant-roles`](references/anti-redundant-roles.md) - Avoid adding redundant ARIA roles + +### 4. User Interaction (HIGH) + +- [`user-prefer-userevent`](references/user-prefer-userevent.md) - Use userEvent over fireEvent +- [`user-setup-before-render`](references/user-setup-before-render.md) - Setup userEvent before render +- [`user-await-interactions`](references/user-await-interactions.md) - Always await userEvent interactions +- [`user-keyboard-for-special-keys`](references/user-keyboard-for-special-keys.md) - Use keyboard() for special keys +- [`user-clear-before-type`](references/user-clear-before-type.md) - Use clear() before retyping + +### 5. Assertions (HIGH) + +- [`assert-jest-dom-matchers`](references/assert-jest-dom-matchers.md) - Use jest-dom matchers +- [`assert-visible-over-in-document`](references/assert-visible-over-in-document.md) - Use toBeVisible() for visibility +- [`assert-text-content`](references/assert-text-content.md) - Use toHaveTextContent() for text +- [`assert-have-value`](references/assert-have-value.md) - Use toHaveValue() for inputs +- [`assert-accessible-description`](references/assert-accessible-description.md) - Use toHaveAccessibleDescription() + +### 6. Component Setup (MEDIUM) + +- [`setup-wrapper-providers`](references/setup-wrapper-providers.md) - Use wrapper option for providers +- [`setup-custom-render`](references/setup-custom-render.md) - Create custom render with providers +- [`setup-mock-modules`](references/setup-mock-modules.md) - Mock modules at module level +- [`setup-fake-timers`](references/setup-fake-timers.md) - Configure userEvent with fake timers +- [`setup-render-hook`](references/setup-render-hook.md) - Use renderHook for testing hooks + +### 7. Test Structure (MEDIUM) + +- [`struct-arrange-act-assert`](references/struct-arrange-act-assert.md) - Follow Arrange-Act-Assert pattern +- [`struct-one-behavior-per-test`](references/struct-one-behavior-per-test.md) - Test one behavior per test +- [`struct-descriptive-names`](references/struct-descriptive-names.md) - Use descriptive test names +- [`struct-avoid-beforeeach-render`](references/struct-avoid-beforeeach-render.md) - Avoid render() in beforeEach + +### 8. Debugging (LOW-MEDIUM) + +- [`debug-screen-debug`](references/debug-screen-debug.md) - Use screen.debug() to inspect DOM +- [`debug-logroles`](references/debug-logroles.md) - Use logRoles to find available roles +- [`debug-testing-playground`](references/debug-testing-playground.md) - Use Testing Playground for queries + +### 9. Accessibility Testing (LOW) + +- [`a11y-role-queries-verify`](references/a11y-role-queries-verify.md) - Role queries verify accessibility +- [`a11y-verify-focus`](references/a11y-verify-focus.md) - Test focus management +- [`a11y-test-aria-states`](references/a11y-test-aria-states.md) - Test ARIA states and properties + +## How to Use + +Read individual reference files for detailed explanations and code examples: + +- [Section definitions](references/_sections.md) - Category structure and impact levels +- [Rule template](assets/templates/_template.md) - Template for adding new rules + +## Reference Files + +| File | Description | +| --------------------------------------------------------------- | --------------------------------- | +| [references/\_sections.md](references/_sections.md) | Category definitions and ordering | +| [assets/templates/\_template.md](assets/templates/_template.md) | Template for new rules | diff --git a/.claude/skills/react-testing-library/assets/templates/_template.md b/.claude/skills/react-testing-library/assets/templates/_template.md new file mode 100644 index 0000000..442b8a2 --- /dev/null +++ b/.claude/skills/react-testing-library/assets/templates/_template.md @@ -0,0 +1,42 @@ +--- +title: Rule Title Here +impact: CRITICAL|HIGH|MEDIUM-HIGH|MEDIUM|LOW-MEDIUM|LOW +impactDescription: Quantified impact (e.g., "prevents flaky tests", "2-10× improvement") +tags: category-prefix, technique, tool, concept +--- + +## Rule Title Here + +Brief explanation (1-3 sentences) of WHY this matters. Focus on testing implications and user confidence. + +**Incorrect (what's wrong):** + +```tsx +// Bad code example - production-realistic, not strawman +// Comment explaining the problem/cost +``` + +**Correct (what's right):** + +```tsx +// Good code example - minimal diff from incorrect +// Comment explaining the benefit +``` + +**Alternative (context):** + +```tsx +// Alternative approach when applicable +``` + +**When NOT to use this pattern:** + +- Exception 1 +- Exception 2 + +**Benefits:** + +- Benefit 1 +- Benefit 2 + +Reference: [Reference Title](URL) diff --git a/.claude/skills/react-testing-library/plugin.json b/.claude/skills/react-testing-library/plugin.json new file mode 100644 index 0000000..4e459d0 --- /dev/null +++ b/.claude/skills/react-testing-library/plugin.json @@ -0,0 +1,12 @@ +{ + "name": "react-testing-library", + "version": "1.1.0", + "description": "React Testing Library mechanics for component and hook tests: queries, async, interaction.", + "author": { + "name": "Ship Shit Dev", + "email": "hello@shipshit.dev", + "url": "https://shipshit.dev" + }, + "license": "MIT", + "skills": "." +} diff --git a/.claude/skills/react-testing-library/references/_sections.md b/.claude/skills/react-testing-library/references/_sections.md new file mode 100644 index 0000000..652aca2 --- /dev/null +++ b/.claude/skills/react-testing-library/references/_sections.md @@ -0,0 +1,51 @@ +# Sections + +This file defines all sections, their ordering, impact levels, and descriptions. +The section ID (in parentheses) is the filename prefix used to group rules. + +--- + +## 1. Query Selection (query) + +**Impact:** CRITICAL +**Description:** Choosing the right query determines test reliability. Wrong queries test implementation details instead of user-visible behavior. + +## 2. Async Handling (async) + +**Impact:** CRITICAL +**Description:** Improper async handling causes flaky tests, race conditions, and false positives. Always await async operations correctly. + +## 3. Common Anti-Patterns (anti) + +**Impact:** CRITICAL +**Description:** Avoiding the most common mistakes that break tests or give false confidence. These patterns actively harm test quality. + +## 4. User Interaction (user) + +**Impact:** HIGH +**Description:** Using userEvent over fireEvent ensures realistic browser behavior and accessibility checks during interaction simulation. + +## 5. Assertions (assert) + +**Impact:** HIGH +**Description:** Using the right assertions with jest-dom matchers provides clearer error messages and tests the correct properties. + +## 6. Component Setup (setup) + +**Impact:** MEDIUM +**Description:** Efficient setup patterns reduce test coupling, improve speed, and make tests more maintainable. + +## 7. Test Structure (struct) + +**Impact:** MEDIUM +**Description:** Well-organized tests are easier to understand, maintain, and debug when failures occur. + +## 8. Debugging (debug) + +**Impact:** LOW-MEDIUM +**Description:** Effective debugging tools speed up test development and help diagnose failures quickly. + +## 9. Accessibility Testing (a11y) + +**Impact:** LOW +**Description:** Leveraging RTL's accessibility-first design to verify components meet accessibility requirements. diff --git a/.claude/skills/react-testing-library/references/a11y-role-queries-verify.md b/.claude/skills/react-testing-library/references/a11y-role-queries-verify.md new file mode 100644 index 0000000..c4335d4 --- /dev/null +++ b/.claude/skills/react-testing-library/references/a11y-role-queries-verify.md @@ -0,0 +1,65 @@ +--- +title: Role Queries Double as Accessibility Tests +impact: LOW +impactDescription: verifies accessibility as a side effect of testing +tags: a11y, getByRole, accessibility, implicit-testing +--- + +## Role Queries Double as Accessibility Tests + +Using `getByRole` queries inherently verifies accessibility. If an element lacks proper roles or labels, the query fails—catching accessibility bugs. + +**Query failure reveals accessibility issue:** + +```tsx +// Component missing accessible name +render( + + + +); + +screen.getByRole("button", { name: /close/i }); +// Error: Unable to find an accessible element with the role "button" +// and name "/close/i" + +// Fix: Add aria-label +render( + + + +); + +screen.getByRole("button", { name: /close/i }); +// Passes - button is now accessible +``` + +**Common accessibility issues caught by role queries:** + +```tsx +// Missing label association +render(); +screen.getByRole("textbox", { name: /email/i }); +// Fails - no accessible name + +// Fix with label +render( + <> + Email + + > +); +screen.getByRole("textbox", { name: /email/i }); +// Passes +``` + +**Testing that elements are accessible:** + +```tsx +// If this query works, the element is accessible +const submitButton = screen.getByRole("button", { name: /submit/i }); +const emailInput = screen.getByLabelText("Email"); +const dialog = screen.getByRole("dialog", { name: /confirm/i }); +``` + +Reference: [Testing Library Guiding Principles](https://testing-library.com/docs/guiding-principles) diff --git a/.claude/skills/react-testing-library/references/a11y-test-aria-states.md b/.claude/skills/react-testing-library/references/a11y-test-aria-states.md new file mode 100644 index 0000000..c1a83e6 --- /dev/null +++ b/.claude/skills/react-testing-library/references/a11y-test-aria-states.md @@ -0,0 +1,70 @@ +--- +title: Test ARIA States and Properties +impact: LOW +impactDescription: verifies dynamic accessibility states work correctly +tags: a11y, aria, states, toBeChecked, expanded +--- + +## Test ARIA States and Properties + +Use jest-dom matchers to verify ARIA states like checked, expanded, selected, and pressed update correctly. + +**Test checkbox state:** + +```tsx +test("toggles checkbox state", async () => { + const user = userEvent.setup(); + render(); + + const checkbox = screen.getByRole("checkbox", { name: /remember me/i }); + expect(checkbox).not.toBeChecked(); + + await user.click(checkbox); + + expect(checkbox).toBeChecked(); +}); +``` + +**Test expanded state:** + +```tsx +test("toggles accordion panel", async () => { + const user = userEvent.setup(); + render(); + + const button = screen.getByRole("button", { name: /details/i }); + expect(button).toHaveAttribute("aria-expanded", "false"); + + await user.click(button); + + expect(button).toHaveAttribute("aria-expanded", "true"); + expect(screen.getByRole("region")).toBeVisible(); +}); +``` + +**Test selected state (tabs):** + +```tsx +test("switches selected tab", async () => { + const user = userEvent.setup(); + render(); + + expect(screen.getByRole("tab", { name: /profile/i })).toHaveAttribute( + "aria-selected", + "true" + ); + + await user.click(screen.getByRole("tab", { name: /settings/i })); + + expect(screen.getByRole("tab", { name: /settings/i })).toHaveAttribute( + "aria-selected", + "true" + ); + expect(screen.getByRole("tab", { name: /profile/i })).toHaveAttribute( + "aria-selected", + "false" + ); +}); +``` + +Reference: [WAI-ARIA States and Properties](https://www.w3.org/TR/wai-aria-1.1/#state_prop_def) diff --git a/.claude/skills/react-testing-library/references/a11y-verify-focus.md b/.claude/skills/react-testing-library/references/a11y-verify-focus.md new file mode 100644 index 0000000..419982f --- /dev/null +++ b/.claude/skills/react-testing-library/references/a11y-verify-focus.md @@ -0,0 +1,63 @@ +--- +title: Test Focus Management +impact: LOW +impactDescription: ensures keyboard navigation works correctly +tags: a11y, focus, toHaveFocus, keyboard-navigation +--- + +## Test Focus Management + +Verify focus moves correctly during interactions. Proper focus management is essential for keyboard users. + +**Test focus on modal open:** + +```tsx +test("focuses first input when modal opens", async () => { + const user = userEvent.setup(); + render(); + + await user.click(screen.getByRole("button", { name: /open form/i })); + + expect(screen.getByLabelText("Name")).toHaveFocus(); +}); +``` + +**Test focus trap in dialogs:** + +```tsx +test("traps focus within dialog", async () => { + const user = userEvent.setup(); + render( + + + + Submit + + ); + + // Focus should cycle within dialog + await user.tab(); // First input + await user.tab(); // Last input + await user.tab(); // Submit button + await user.tab(); // Back to first input + + expect(screen.getByLabelText("First")).toHaveFocus(); +}); +``` + +**Test focus return on close:** + +```tsx +test("returns focus to trigger when modal closes", async () => { + const user = userEvent.setup(); + render(); + + const trigger = screen.getByRole("button", { name: /open/i }); + await user.click(trigger); + await user.keyboard("{Escape}"); + + expect(trigger).toHaveFocus(); +}); +``` + +Reference: [WAI-ARIA - Modal Dialog](https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/) diff --git a/.claude/skills/react-testing-library/references/anti-container-queries.md b/.claude/skills/react-testing-library/references/anti-container-queries.md new file mode 100644 index 0000000..e1e9dcc --- /dev/null +++ b/.claude/skills/react-testing-library/references/anti-container-queries.md @@ -0,0 +1,44 @@ +--- +title: Avoid Using container for Queries +impact: CRITICAL +impactDescription: prevents brittle tests tied to DOM structure +tags: anti, container, querySelector, implementation-details +--- + +## Avoid Using container for Queries + +Using `container.querySelector` bypasses Testing Library's user-centric queries. Tests become brittle and tied to specific DOM structure. + +**Incorrect (DOM structure dependency):** + +```tsx +const { container } = render(); + +const avatar = container.querySelector(".user-card__avatar img"); +const name = container.querySelector('[data-testid="name"]'); +// Tied to CSS classes and DOM structure +``` + +**Correct (user-centric queries):** + +```tsx +render(); + +const avatar = screen.getByRole("img", { name: /avatar/i }); +const name = screen.getByRole("heading", { name: /john doe/i }); +// Tests what users actually see +``` + +**When container access IS acceptable:** + +- Snapshot testing with `asFragment()` +- Testing CSS-in-JS styles (rarely needed) +- Integration with non-React DOM libraries + +```tsx +// Snapshot testing +const { asFragment } = render(); +expect(asFragment()).toMatchSnapshot(); +``` + +Reference: [Common Mistakes - Using container to Query](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#using-container-to-query-for-elements) diff --git a/.claude/skills/react-testing-library/references/anti-empty-waitfor.md b/.claude/skills/react-testing-library/references/anti-empty-waitfor.md new file mode 100644 index 0000000..c18fb48 --- /dev/null +++ b/.claude/skills/react-testing-library/references/anti-empty-waitfor.md @@ -0,0 +1,49 @@ +--- +title: Avoid Empty waitFor Callbacks +impact: CRITICAL +impactDescription: prevents tests that pass without verifying anything +tags: anti, waitFor, empty-callback, async +--- + +## Avoid Empty waitFor Callbacks + +Passing an empty callback or no callback to `waitFor` is meaningless. The test passes without waiting for or verifying anything. + +**Incorrect (empty callback):** + +```tsx +render(); + +await userEvent.click(screen.getByRole("button", { name: /submit/i })); +await waitFor(() => {}); +// Waits for nothing, passes immediately + +expect(screen.getByText("Submitted!")).toBeInTheDocument(); +// May fail if async operation hasn't completed +``` + +**Correct (wait for specific condition):** + +```tsx +render(); + +const user = userEvent.setup(); +await user.click(screen.getByRole("button", { name: /submit/i })); + +expect(await screen.findByText("Submitted!")).toBeInTheDocument(); +// Properly waits for text to appear +``` + +**Alternative - wait for mock to be called:** + +```tsx +const user = userEvent.setup(); +await user.click(screen.getByRole("button", { name: /submit/i })); + +await waitFor(() => { + expect(mockSubmit).toHaveBeenCalled(); +}); +// Waits for actual condition +``` + +Reference: [Common Mistakes - Passing Empty Callback to waitFor](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library#passing-an-empty-callback-to-waitfor) diff --git a/.claude/skills/react-testing-library/references/anti-implementation-details.md b/.claude/skills/react-testing-library/references/anti-implementation-details.md new file mode 100644 index 0000000..813b599 --- /dev/null +++ b/.claude/skills/react-testing-library/references/anti-implementation-details.md @@ -0,0 +1,49 @@ +--- +title: Avoid Testing Implementation Details +impact: CRITICAL +impactDescription: prevents 50%+ of false test failures during refactoring +tags: anti, implementation-details, behavior, refactoring +--- + +## Avoid Testing Implementation Details + +Test what users experience, not how it's implemented internally. Implementation detail tests break during refactoring even when behavior is unchanged. + +**Incorrect (tests internal state and methods):** + +```tsx +import { render } from "@testing-library/react"; + +test("counter increments", () => { + const { container } = render(); + const instance = container.firstChild._reactInternals; + + expect(instance.memoizedState).toBe(0); + instance.memoizedProps.onClick(); + expect(instance.memoizedState).toBe(1); +}); +// Tests React internals, not user behavior +``` + +**Correct (tests user-visible behavior):** + +```tsx +test("counter increments", async () => { + render(); + const user = userEvent.setup(); + + expect(screen.getByText("Count: 0")).toBeInTheDocument(); + await user.click(screen.getByRole("button", { name: /increment/i })); + expect(screen.getByText("Count: 1")).toBeInTheDocument(); +}); +// Tests what user sees and does +``` + +**Signs you're testing implementation details:** + +- Accessing component instance or internal state +- Testing specific hook calls or internal methods +- Using container.querySelector with implementation-specific selectors +- Tests break when refactoring without behavior change + +Reference: [Testing Implementation Details](https://kentcdodds.com/blog/testing-implementation-details) diff --git a/.claude/skills/react-testing-library/references/anti-manual-cleanup.md b/.claude/skills/react-testing-library/references/anti-manual-cleanup.md new file mode 100644 index 0000000..96c9537 --- /dev/null +++ b/.claude/skills/react-testing-library/references/anti-manual-cleanup.md @@ -0,0 +1,51 @@ +--- +title: Remove Manual cleanup Calls +impact: HIGH +impactDescription: eliminates 2-3 lines of boilerplate per test file +tags: anti, cleanup, afterEach, setup +--- + +## Remove Manual cleanup Calls + +Testing Library automatically calls `cleanup` after each test when using modern test frameworks (Jest, Vitest, Mocha with globals). Manual calls are unnecessary. + +**Incorrect (manual cleanup):** + +```tsx +import { render, cleanup } from "@testing-library/react"; + +afterEach(() => { + cleanup(); +}); + +test("renders dashboard", () => { + render(); + // ... +}); +// cleanup() is called twice - once manually, once automatically +``` + +**Correct (rely on automatic cleanup):** + +```tsx +import { render } from "@testing-library/react"; + +test("renders dashboard", () => { + render(); + // ... +}); +// cleanup() runs automatically after test +``` + +**When manual cleanup IS needed:** + +- Using a test framework without global `afterEach` (like AVA) +- Custom test runners without RTL integration + +```tsx +// Only for frameworks without automatic cleanup +import { cleanup } from "@testing-library/react"; +test.afterEach(cleanup); +``` + +Reference: [Testing Library - Cleanup](https://testing-library.com/docs/react-testing-library/api#cleanup) diff --git a/.claude/skills/react-testing-library/references/anti-redundant-roles.md b/.claude/skills/react-testing-library/references/anti-redundant-roles.md new file mode 100644 index 0000000..654361e --- /dev/null +++ b/.claude/skills/react-testing-library/references/anti-redundant-roles.md @@ -0,0 +1,61 @@ +--- +title: Avoid Adding Redundant ARIA Roles +impact: HIGH +impactDescription: maintains semantic HTML and proper accessibility +tags: anti, aria, roles, accessibility, semantic-html +--- + +## Avoid Adding Redundant ARIA Roles + +Don't add `role` attributes that elements already have implicitly. This clutters the DOM and can confuse assistive technologies. + +**Incorrect (redundant roles):** + +```tsx +render( + + + Submit + + + Home + + +); + +screen.getByRole("button", { name: /submit/i }); +// Works, but role="button" is redundant +``` + +**Correct (semantic HTML):** + +```tsx +render( + + Submit + Home + +); + +screen.getByRole("button", { name: /submit/i }); +// Works with implicit role from semantic HTML +``` + +**Implicit roles to remember:** + +| Element | Implicit Role | +| ------------------------- | ------------- | +| `` | button | +| `` | link | +| `` | navigation | +| `` | main | +| `` | banner | +| `