--- name: uui-unit-testing description: Helps write unit tests for UUI components using Jest, jsdom, and @epam/uui-test-utils. Use when writing unit tests for UUI components, updating snapshots, running Jest tests, or working with test utilities. --- # UUI Unit Testing UUI uses Jest with jsdom for unit testing React components. Tests are located in `__tests__` folders within each package. ## Test File Location Test files follow the pattern: `**/__tests__/**/*.test.{js,ts,tsx}` **Examples:** - `uui/components/buttons/__tests__/Button.test.tsx` - `uui/components/inputs/__tests__/NumericInput.test.tsx` - `uui-core/src/hooks/__tests__/useVirtualList.test.tsx` ## Test Commands **Run all tests:** ```bash yarn test ``` **Run tests in watch mode:** ```bash yarn test-watch ``` **Run a single test file:** ```bash yarn test -- --testPathPattern="Button" ``` Use `--testPathPattern` to match file path, or `--testNamePattern="should render"` to match test names. **Update snapshots:** ```bash yarn test-update ``` **Generate test coverage report:** ```bash yarn test-report ``` Coverage report saves to `.reports/unit-tests` folder. **TypeScript type checking:** ```bash yarn test-typecheck ``` **Windows workaround:** If encountering test errors on Windows, use reduced worker count: ```bash yarn test --maxWorkers=2 --testTimeout=10000 ``` You can increase `maxWorkers` up to 4 if needed. ## Using @epam/uui-test-utils The `@epam/uui-test-utils` package provides helpers for testing UUI components with proper context. ### Key Exports | Export | Purpose | |--------|---------| | `renderSnapshotWithContextAsync` | Render with UUI context, returns `DocumentFragment` for snapshots | | `renderWithContextAsync` | Render with UUI context for interaction testing | | `renderHookWithContextAsync` | Test custom hooks with UUI context | | `setupComponentForTest` | Setup component with mocks and `setProps` helper | | `screen` | Re-exported from `@testing-library/react` | | `userEvent` | Re-exported from `@testing-library/user-event` | | `fireEvent` | Re-exported from `@testing-library/react` | | `SvgMock` | Mock for SVG icon imports in snapshots | | `mockReactPortalsForSnapshots` | Mock portals for snapshot tests | | `delay`, `delayAct` | Async timing utilities for tests | ### renderSnapshotWithContextAsync Renders component with UUI context and returns a `DocumentFragment` (via `asFragment()`) for snapshot matching: ```typescript import { renderSnapshotWithContextAsync } from '@epam/uui-test-utils'; import { NumericInput } from '../NumericInput'; it('should be rendered with minimum props', async () => { const tree = await renderSnapshotWithContextAsync( ); expect(tree).toMatchSnapshot(); }); ``` ### setupComponentForTest Use for testing **controlled components** (e.g. `value` + `onValueChange`). Provides `setProps` to update props without unmounting, and `mocks` for callback assertions. Call `context.current.setProperty(name, value)` in callback mocks to simulate controlled updates: ```typescript import { setupComponentForTest, screen, fireEvent } from '@epam/uui-test-utils'; import { TextInput } from '@epam/uui'; interface TestComponentProps { value?: string; onValueChange?: (value: string) => void; } const { mocks, setProps } = await setupComponentForTest( (context) => ({ value: 'initial', onValueChange: jest.fn().mockImplementation((newValue) => { context.current.setProperty('value', newValue); }), }), (props) => , ); const input = screen.queryByRole('textbox'); fireEvent.change(input, { target: { value: 'updated' } }); expect(mocks.onValueChange).toHaveBeenLastCalledWith('updated'); setProps({ value: 'external' }); // Update props without unmounting ``` ### renderHookWithContextAsync Renders a hook with UUI context. Returns `{ result, rerender, unmount, svc }`: - **result** — current hook return value (use `result.current`) - **svc** — UUI services (api, modals, router, etc.) for mocking or assertions - **rerender**, **unmount** — same as `renderHook` from Testing Library ```typescript import { renderHookWithContextAsync, act } from '@epam/uui-test-utils'; it('should use DataSource view', async () => { const dataSource = useArrayDataSource({ items: [...], getId: (i) => i.id }, []); const { result, svc } = await renderHookWithContextAsync(() => dataSource.useView({}, () => {}, {}) ); const rows = result.current.getVisibleRows(); expect(rows).toHaveLength(5); // svc.api, svc.uuiModals, etc. available for mocks }); it('should test custom hook with services', async () => { const { result, svc } = await renderHookWithContextAsync(useMyHook); act(() => result.current.doSomething()); expect(svc.uuiAnalytics.sendEvent).toHaveBeenCalled(); }); ``` ### renderWithContextAsync Renders component with UUI context for interaction testing: ```typescript import { renderWithContextAsync, screen, userEvent } from '@epam/uui-test-utils'; import { Tag } from '../Tag'; it('should call onClear callback', async () => { const onClearMock = jest.fn(); await renderWithContextAsync( ); const clearButton = await screen.findByRole('button', { name: /remove tag/i }); await userEvent.click(clearButton); expect(onClearMock).toHaveBeenCalled(); }); ``` ## Test Structure ### Basic Snapshot Test ```typescript import React from 'react'; import { ComponentName } from '../ComponentName'; import { renderSnapshotWithContextAsync } from '@epam/uui-test-utils'; describe('ComponentName', () => { describe('snapshots', () => { it('should be rendered with minimum props', async () => { const tree = await renderSnapshotWithContextAsync(); expect(tree).toMatchSnapshot(); }); it('should be rendered with maximum props', async () => { const tree = await renderSnapshotWithContextAsync( ); expect(tree).toMatchSnapshot(); }); }); }); ``` ### Interaction Test ```typescript import { renderWithContextAsync, screen, userEvent } from '@epam/uui-test-utils'; it('should handle user interaction', async () => { const onActionMock = jest.fn(); await renderWithContextAsync( ); const button = await screen.findByRole('button'); await userEvent.click(button); expect(onActionMock).toHaveBeenCalled(); }); ``` ## Snapshot Update Workflow When UI changes require snapshot updates: 1. **Run tests** to see which snapshots fail: ```bash yarn test ``` 2. **Review changes** - verify the visual changes are intentional 3. **Update snapshots**: ```bash yarn test-update ``` 4. **Commit updated snapshots** along with component changes **Important:** Always review snapshot diffs before updating. Snapshots should reflect intentional UI changes, not accidental regressions. ## Test Environment - **Test framework:** Jest - **Test environment:** jsdom (for React components), node (for build scripts) - **Test utilities:** `@epam/uui-test-utils` for component testing helpers ## Test Coverage Test coverage is collected from: - `uui-core` - `uui-components` - `uui` - `epam-promo` - `epam-electric` - `loveship` Run `yarn test-report` to generate coverage reports. ## Common Patterns ### Mocking Portals for Snapshots Use the built-in helper instead of manual mocking: ```typescript import { mockReactPortalsForSnapshots, renderSnapshotWithContextAsync } from '@epam/uui-test-utils'; describe('ComponentWithPortal', () => { mockReactPortalsForSnapshots(); it('should render correctly', async () => { const tree = await renderSnapshotWithContextAsync(); expect(tree).toMatchSnapshot(); }); }); ``` ### Testing with Icons Use `SvgMock` from test utils for icon props in snapshots: ```typescript import { SvgMock, renderSnapshotWithContextAsync } from '@epam/uui-test-utils'; it('should render with icon', async () => { const tree = await renderSnapshotWithContextAsync( ); expect(tree).toMatchSnapshot(); }); ``` For specific icon imports (when behavior depends on the icon): ```typescript // @ts-expect-error import { ReactComponent as CalendarIcon } from '@epam-assets/icons/action-calendar-fill.svg'; ``` ## Best Practices 1. **Add tests for bug fixes** - Prevent regressions 2. **Add tests for new functionality** - Ensure components work as expected 3. **Update snapshots when UI changes** - Keep snapshots current 4. **Run full test suite before committing** - `yarn test` 5. **Use descriptive test names** - Make failures easy to understand 6. **Test user interactions** - Not just rendering, but behavior ## References - Test utilities: `@epam/uui-test-utils` package - Example snapshot test: `uui/components/inputs/__tests__/NumericInput.test.tsx` - Example interaction test: `uui/components/widgets/__tests__/Tag.test.tsx` - Example setupComponentForTest: `app/src/docs/_examples/testing/__tests__/testComponent.test.tsx` - Example renderHookWithContextAsync: `uui-core/src/data/forms/__tests__/useForm.test.tsx` - Testing documentation: https://uui.epam.com/documents?id=testing-getting-started&mode=doc&isSkin=null&category=testing