Skip to main content

unit-tests

Jest + React Testing Library best practices for Wonder Blocks unit tests. Use when creating or editing `.test.ts` / `.test.tsx` files.

Informações da origem

Repositório
Khan/wonder-blocks
Última atividade na origem
7 de maio de 2026 às 17:42
Idioma detectado do SKILL.md
inglês
Estrelas
163
Forks
12

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
unit-tests
description
Jest + React Testing Library best practices for Wonder Blocks unit tests. Use when creating or editing `.test.ts` / `.test.tsx` files.
# Jest Testing Best Practices This guide covers testing patterns and best practices for Jest and React Testing Library in the Wonder Blocks codebase. ## Core Testing Principles ### ⚠️ Critical Setup Rules **Test Workflow Priority:** - ✅ **ALWAYS fix failing tests BEFORE fixing linting errors** - ✅ **Focus on underlying errors, not `Unhandled console.error call` messages** - ⚠️ When tests fail with `Unhandled console.error call`, look for the **root cause error** (e.g., `ReferenceError: window is not defined`) - ⚠️ The console.error messages are symptoms, not the actual problem - fix the underlying issue **File Structure:** - ✅ Name test files with `.test.ts` or `.test.tsx` suffix - ✅ Place in `__tests__/` directory OR colocate with source files (follow local conventions) **Test Framework Setup:** - ✅ Additional matchers from React Testing Library (RTL) and `jest-extended` are available - ✅ Use `describe`/`it` pattern for test organization - ✅ Use `globalThis` prefix when accessing global objects - ✅ **Prioritize testing non-trivial business logic** over trivial implementations ### Arrange-Act-Assert Pattern **⚠️ ALWAYS use this three-section structure:** ```typescript describe("Calculator", () => { it("should add two numbers correctly", () => { // Arrange const a = 5; const b = 3; // Act const result = add(a, b); // Assert expect(result).toBe(8); }); }); ``` **Rules:** - ✅ **ALWAYS divide tests** into Arrange, Act, Assert sections with comments - ✅ **Each section gets exactly one comment label** (`// Arrange`, `// Act`, `// Assert`) — no additional comments within a section - ❌ **NEVER combine sections** (e.g., don't write `// Act & Assert`) - ❌ **NEVER use multiple Act or Assert sections** in a single test (split into separate tests instead) - ❌ **NEVER remove Arrange, Act, Assert comments** **Exception - Testing Thrown Errors:** When testing errors, use an `underTest` variable in the Act section: ```typescript it("should throw an error when input is invalid", () => { // Arrange const invalidInput = "invalid"; // Act const underTest = () => { processInput(invalidInput); }; // Assert expect(underTest).toThrow("Invalid input"); }); ``` ### Be Concise and Avoid Over-Testing **⚠️ Focus on what matters - don't overdo it:** **DO Test:** - ✅ **Non-trivial business logic** - Complex calculations, data transformations, validation rules - ✅ **User interactions** - Click handlers, form submissions, keyboard navigation - ✅ **Accessibility** - ARIA attributes, keyboard support, focus management - ✅ **Edge cases and error conditions** - Null values, empty states, error handling - ✅ **Integration points** - API calls, event callbacks, state changes - ✅ **Bug fixes** - Add a test that reproduces the bug to prevent regressions **DON'T Test:** - ❌ **Trivial implementations** - Simple getters/setters, pass-through functions - ❌ **Style-only props** - Visual appearance is covered by visual regression tests in Storybook - ❌ **Third-party libraries** - Assume they work; test your usage of them - ❌ **Implementation details** - Internal state that doesn't affect output/behavior - ❌ **Additional logic in tests** - Use existing utility functions instead of reimplementing logic in tests ```typescript // ❌ DON'T: Testing style-only props (use visual regression tests instead) it("should apply primary color when kind is primary", () => { render(<Button kind="primary" />); expect(screen.getByRole("button")).toHaveStyle({ backgroundColor: "blue" }); }); // ✅ DO: Test meaningful behavior it("should call onClick when clicked", async () => { // Arrange const handleClick = jest.fn(); render(<Button onClick={handleClick}>Click me</Button>); // Act await userEvent.click(screen.getByRole("button")); // Assert expect(handleClick).toHaveBeenCalledTimes(1); }); // ✅ DO: Test non-trivial logic it("should validate email format and return error message", () => { // Arrange const invalidEmail = "not-an-email"; // Act const result = validateEmail(invalidEmail); // Assert expect(result).toBe("Please enter a valid email address"); }); ``` **Key Principles:** - ✅ **Test behavior, not implementation** - Focus on what the component does, not how - ✅ **Prioritize critical paths** - Test the most important user flows first - ✅ **Keep tests simple and readable** - Each test should have a clear, single purpose - ✅ **Don't add logic to tests** - Tests should only test the component/function; use existing utility functions from the codebase instead of reimplementing logic in tests - ✅ **Use visual regression tests for styling** - Storybook snapshot tests handle visual appearance - ✅ **Balance coverage with maintainability** - More tests ≠ better tests ### Assertions **Best Practices:** - ✅ **Use specific matchers** when possible (e.g., `toBe`, `toEqual`, `toHaveBeenCalledWith`) - ✅ **Prefer explicit assertions** over implicit ones - ✅ Use semantic matchers from RTL: `toBeInTheDocument()`, `toBeVisible()`, `toHaveAttribute()` - ❌ **Avoid Jest snapshots** (`.toMatchSnapshot()`, `.toMatchInlineSnapshot()`) - use Chromatic + Storybook for visual regression tests, or use specific attribute assertions instead ### One Expect Per Test **⚠️ Each test should have exactly one `expect`.** If you need to assert multiple things, split them into separate tests. Multiple assertions hide which behavior actually broke when the test fails. ### Parameterized Tests with `it.each` **When to use:** Testing the same logic with multiple input/output combinations **✅ DO: Use `it.each` for data-driven tests** ```typescript describe("Calculator", () => { it.each([ [2, 3, 5], [0, 0, 0], [-1, 1, 0], [10, -5, 5], ])("should add %i and %i to equal %i", (a, b, expected) => { // Arrange // (inputs come from it.each) // Act const result = add(a, b); // Assert expect(result).toBe(expected); }); }); ``` **Benefits:** - ✅ **Reduces Duplication**: Test same logic with different inputs - ✅ **Clear Test Names**: Each test shows specific values being tested - ✅ **Easy to Extend**: Simply add new arrays to test data - ✅ **Better Coverage**: Test edge cases and boundary conditions efficiently - ✅ **Comprehensive Testing**: Essential for testing all prop combinations and states in Wonder Blocks components ## Mocking and Spying ### ⚠️ Critical Rules - ALWAYS Follow These 1. **NEVER mock `console.error`** - This hides real implementation issues and errors 2. **ALWAYS use `jest.spyOn()` to create spies** - Never treat the original function as though it were a spy 3. **Store spy return values in variables ONLY when asserting on them** - Avoids unused variable linter errors 4. **NEVER mock outside of tests** - Even if it means code duplication, keep mocks inside test cases ### Method Spying - Correct Pattern **✅ DO: Use jest.spyOn and store the result when asserting** ```typescript import * as SomeFile from "./some-file.ts"; describe("MyComponent", () => { it("should call someMethod with correct args", () => { // Arrange // Store spy because we'll assert on it later const spy = jest.spyOn(SomeFile, "someMethod").mockReturnValue(mockValue); // Act myFunction(); // Assert expect(spy).toHaveBeenCalledWith(expectedArgs); }); }); ``` **❌ DON'T: Treat the original function as a spy without jest.spyOn()** ```typescript // ❌ WRONG - This will fail because someMethod is not a spy import * as SomeFile from "./some-file.ts"; describe("MyComponent", () => { it("should call someMethod", () => { // Act myFunction(); // Assert expect(SomeFile.someMethod).toHaveBeenCalled(); // ❌ ERROR! Not a spy }); }); ``` ### When to Store Spies in Variables **Spies serve two purposes:** 1. **Mocking behavior** - Replace function implementation or return value 2. **Verification** - Assert the function was called with correct arguments **✅ Mocking only (no variable needed):** ```typescript it("should process user data", () => { // Arrange // Mock the API call to return test data, but don't store it jest.spyOn(API, "fetchUser").mockResolvedValue(mockUserData); // Act const result = processUserProfile(); // Assert // We're testing processUserProfile's logic, not that fetchUser was called expect(result.displayName).toBe("John Doe"); // No spy variable = no unused variable linter error }); ``` **✅ Mocking AND verification (store in variable):** ```typescript it("should call analytics when button is clicked", () => { // Arrange // Store the spy because we'll assert on it const trackEventSpy = jest .spyOn(Analytics, "trackEvent") .mockReturnValue(undefined); // Act userEvent.click(screen.getByRole("button")); // Assert // We're testing that the analytics call happens correctly expect(trackEventSpy).toHaveBeenCalledWith("button_click", { buttonId: "submit", }); }); ``` **Key point:** Only store the spy in a variable if you're going to assert on it. This avoids unused variable linter errors while still allowing you to verify calls when needed. ### Common Spy Patterns **Mock only (no variable):** ```typescript // When you only need to control the return value jest.spyOn(module, "functionName").mockReturnValue(mockValue); jest.spyOn(module, "asyncFunction").mockResolvedValue(mockValue); jest.spyOn(module, "asyncFunction").mockRejectedValue(new Error("Test error")); ``` **Mock and verify (store in variable):** ```typescript // When you need to assert the spy was called const spy = jest.spyOn(module, "functionName").mockReturnValue(mockValue); // ... later in Assert section: expect(spy).toHaveBeenCalledWith(expectedArgs); ``` **Spy with mock implementation:** ```typescript // Store only if you'll verify it was called const spy = jest.spyOn(module, "functionName").mockImplementation((arg) => { return processedValue; }); ``` ### Mocking Guidelines **DO:** - ✅ Use `jest.spyOn()` for mocking functions and tracking calls - ✅ Store spy return values in variables ONLY when you need to assert on them - ✅ Chain `.mockReturnValue()` or similar directly on `jest.spyOn()` when only mocking behavior - ✅ Keep mocks and spies inside test cases when possible - ✅ Use mocking and spies to isolate the code under test at boundaries with other code - ✅ Clean up spies after tests (Jest does this automatically with `clearAllMocks`) **DON'T:** - ❌ Never mock `console.error` - this hides real implementation issues - ❌ Never treat original functions as spies without `jest.spyOn()` - ❌ Never store spies in variables if you won't assert on them (causes unused variable linter errors) - ❌ Avoid mocking outside of tests, even if it means code duplication ### Hook Testing **✅ Use `renderHook`:** ```typescript import {renderHook} from "@testing-library/react"; // Direct for simple hooks const {result} = renderHook(() => useMyHook(params)); ``` ### User Interactions **✅ ALWAYS use `userEvent` for interactions:** ```typescript import userEvent from "@testing-library/user-event"; // ✅ DO: Use userEvent (realistic, includes focus/blur/typing) await userEvent.click(screen.getByRole("button")); await userEvent.type(screen.getByRole("textbox"), "hello"); // ❌ DON'T: Use fireEvent (low-level, less realistic) fireEvent.click(button); ``` ### Browser Behavior and jsdom Limitations **⚠️ jsdom does not fully implement all browser behaviors.** Common limitations include: `getBoundingClientRect()`, scroll positions, `offsetWidth`/`offsetHeight`, clipboard API, CSS animations, and Intersection/Resize Observers. **✅ Mock browser APIs when testing in unit tests:** ```typescript // Mock scrollIntoView const scrollIntoViewMock = jest.fn(); Element.prototype.scrollIntoView = scrollIntoViewMock; // Mock getBoundingClientRect jest.spyOn(Element.prototype, "getBoundingClientRect").mockReturnValue({ top: 100, left: 100, bottom: 200, right: 200, width: 100, height: 100, x: 100, y: 100, toJSON: () => {}, }); ``` **✅ Use Storybook interaction tests** for behavior that's difficult to mock accurately (scroll, layout, clipboard, complex focus management). See `.agents/skills/storybook/SKILL.md` for details. ### Element Selection **Query priority (use in this order):** 1. ✅ **Semantic queries** (best): `getByRole`, `getByLabelText`, `getByText` 2. ✅ **Test IDs** (fallback): `getByTestId` with `data-testid` attribute 3. ❌ **NEVER use** CSS selectors, direct DOM traversal, or raw node access **❌ NEVER access DOM nodes directly.** Always use Testing Library queries. Direct node access couples tests to implementation structure, not behavior. ```typescript // ✅ DO: Testing Library queries screen.getByRole("button", {name: /submit/i}); screen.getByLabelText("Email address"); screen.findByText("Welcome back"); screen.getByTestId("custom-widget");
Ver no GitHub
Este SKILL.md e muito grande, entao o SkillsMP mostra aqui apenas a primeira secao. Ver no GitHub