@pie-lib/test-utils
v2.0.2
Published
Testing utilities for pie-lib packages with React Testing Library and MUI support
Downloads
4,328
Keywords
Readme
@pie-lib/test-utils
Testing utilities for pie-lib packages with React Testing Library and MUI support.
Installation
This package is typically used as a dev dependency:
yarn add -D @pie-lib/test-utilsFeatures
- ✅ React Testing Library utilities
- ✅ MUI Theme Provider wrapper for tests
- ✅ Multiple provider composition
- ✅ Custom theme creation for testing
- ✅ All RTL exports in one place
- ✅ userEvent and jest-dom matchers included
- ✅ Keyboard helpers for keyCode-based components
- ✅ Web component testing utilities (light DOM only)
Usage
Basic Rendering with MUI Theme
import { renderWithTheme, screen } from '@pie-lib/test-utils';
import { Button } from '@mui/material';
test('renders a button', () => {
renderWithTheme(<Button>Click me</Button>);
expect(screen.getByRole('button')).toBeInTheDocument();
});Custom Theme
import { renderWithTheme, createTestTheme } from '@pie-lib/test-utils';
test('renders with dark theme', () => {
const darkTheme = createTestTheme({
palette: { mode: 'dark' }
});
renderWithTheme(<MyComponent />, { theme: darkTheme });
// ... assertions
});Multiple Providers
import { renderWithProviders } from '@pie-lib/test-utils';
test('renders with multiple providers', () => {
const ReduxProvider = ({ children }) => (
<Provider store={store}>{children}</Provider>
);
renderWithProviders(<MyComponent />, {
providers: [ReduxProvider]
});
// ... assertions
});User Interactions
import { renderWithTheme, screen, userEvent } from '@pie-lib/test-utils';
test('handles button click', async () => {
const onClick = jest.fn();
const user = userEvent.setup();
renderWithTheme(<Button onClick={onClick}>Click</Button>);
await user.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalledTimes(1);
});Keyboard Interactions
NEW: Helper functions for testing keyboard events, especially useful for legacy components checking event.keyCode:
import { render, screen, pressKey, Keys, typeAndSubmit } from '@pie-lib/test-utils';
test('submits on Enter key', async () => {
const onSubmit = jest.fn();
render(<SearchInput onSubmit={onSubmit} />);
const input = screen.getByRole('textbox');
await typeAndSubmit(input, 'search term');
expect(onSubmit).toHaveBeenCalledWith('search term');
});
test('handles Escape key', () => {
const onCancel = jest.fn();
render(<Modal onCancel={onCancel} />);
const modal = screen.getByRole('dialog');
pressKey(modal, Keys.ESCAPE);
expect(onCancel).toHaveBeenCalled();
});Async Operations
import { renderWithTheme, screen, waitFor } from '@pie-lib/test-utils';
test('loads data', async () => {
renderWithTheme(<AsyncComponent />);
// Wait for element to appear
const element = await screen.findByText('Loaded');
expect(element).toBeInTheDocument();
// Or use waitFor
await waitFor(() => {
expect(screen.getByText('Loaded')).toBeInTheDocument();
});
});Web Component Testing
Note: Utilities for testing custom elements with light DOM (no Shadow DOM). Standard React Testing Library queries work directly on these components.
import { renderWebComponent, dispatchCustomEvent, screen } from '@pie-lib/test-utils';
test('interacts with web component', async () => {
const element = await renderWebComponent('my-custom-element', {
'data-testid': 'my-element',
value: '42'
});
// Query using standard RTL queries (light DOM)
const button = screen.getByRole('button');
await user.click(button);
// Dispatch custom events
dispatchCustomEvent(element, 'value-changed', { newValue: '100' });
});API Reference
React Component Rendering
renderWithTheme(ui, options)
Renders a component wrapped with MUI ThemeProvider.
Parameters:
ui- React component to renderoptions.theme- Custom MUI theme (optional, defaults to Material-UI default)options...- Additional options passed to RTL'srender()
Returns: RTL render result
renderWithProviders(ui, options)
Renders a component with MUI ThemeProvider and additional custom providers.
Parameters:
ui- React component to renderoptions.theme- Custom MUI theme (optional)options.providers- Array of provider components to wrap (optional)options...- Additional options passed to RTL'srender()
Returns: RTL render result
createTestTheme(themeOptions)
Creates a MUI theme for testing.
Parameters:
themeOptions- MUI theme configuration object
Returns: MUI theme object
waitForRemoval(callback)
Alias for waitForElementToBeRemoved from RTL.
Re-exports
This package re-exports everything from:
@testing-library/react(render, screen, waitFor, etc.)@testing-library/user-event(asuserEvent)@testing-library/jest-dom(for TypeScript support)
Keyboard Helpers API
Keys
Constants for common keyboard key codes:
import { Keys } from '@pie-lib/test-utils';
Keys.ENTER // 13
Keys.ESCAPE // 27
Keys.SPACE // 32
Keys.TAB // 9
Keys.ARROW_DOWN // 40
Keys.ARROW_UP // 38
// ... and morepressKey(element, keyCode, type, options)
Simulate keyboard event with keyCode (useful for legacy components):
import { pressKey, Keys } from '@pie-lib/test-utils';
// Press Enter
pressKey(input, Keys.ENTER);
// Press Escape with keyup event
pressKey(dialog, Keys.ESCAPE, 'keyup');
// Press Ctrl+Enter
pressKey(input, Keys.ENTER, 'keydown', { ctrlKey: true });Why? userEvent.type() with {Enter} doesn't work well with components checking event.keyCode.
typeAndSubmit(element, text)
Type text and press Enter (common pattern):
import { typeAndSubmit } from '@pie-lib/test-utils';
await typeAndSubmit(input, 'search query');
// Types "search query" and presses EnterclearAndType(element, text)
Clear input and type new text:
import { clearAndType } from '@pie-lib/test-utils';
await clearAndType(input, 'new value');navigateWithKeys(element, steps, direction)
Navigate with arrow keys:
import { navigateWithKeys } from '@pie-lib/test-utils';
// Navigate down 3 items
navigateWithKeys(listbox, 3, 'vertical');
// Navigate left 2 items
navigateWithKeys(tabs, -2, 'horizontal');Web Component Helpers API
Note: These utilities are for light DOM web components only (no Shadow DOM). For Shadow DOM components, use standard DOM queries or other testing libraries.
waitForCustomElement(tagName, timeout)
Wait for custom element to be defined:
import { waitForCustomElement } from '@pie-lib/test-utils';
await waitForCustomElement('my-component');
await waitForCustomElement('pie-chart', 5000); // 5 second timeoutrenderWebComponent(tagName, attributes, properties, container)
Render web component and wait for it to be ready:
import { renderWebComponent } from '@pie-lib/test-utils';
const element = await renderWebComponent('pie-chart',
{ type: 'bar', 'data-testid': 'chart' }, // attributes
{ onDataChange: jest.fn() } // properties
);dispatchCustomEvent(element, eventName, detail, options)
Dispatch custom event on element:
import { dispatchCustomEvent } from '@pie-lib/test-utils';
dispatchCustomEvent(chart, 'data-changed', { value: [1, 2, 3] });waitForEvent(element, eventName, timeout)
Wait for custom event to be fired:
import { waitForEvent } from '@pie-lib/test-utils';
const promise = waitForEvent(component, 'ready');
component.initialize();
const event = await promise;
expect(event.detail).toEqual({ initialized: true });isCustomElementDefined(tagName)
Check if a custom element is defined:
import { isCustomElementDefined } from '@pie-lib/test-utils';
if (isCustomElementDefined('pie-chart')) {
// Element is ready to use
}createCustomElement(tagName, props)
Create and configure a custom element:
import { createCustomElement } from '@pie-lib/test-utils';
const chart = createCustomElement('pie-chart', {
data: [1, 2, 3],
type: 'bar'
});
document.body.appendChild(chart);Migration from Enzyme
Before (Enzyme)
import { shallow } from 'enzyme';
const wrapper = shallow(<Component />);
expect(wrapper.find('button')).toHaveLength(1);
wrapper.find('button').simulate('click');After (React Testing Library)
import { renderWithTheme, screen, userEvent } from '@pie-lib/test-utils';
renderWithTheme(<Component />);
const button = screen.getByRole('button');
expect(button).toBeInTheDocument();
await userEvent.click(button);Query Priority
Use queries in this order (best to worst):
- getByRole - Best for accessibility
- getByLabelText - Good for form fields
- getByPlaceholderText - Forms when label not available
- getByText - Non-interactive content
- getByDisplayValue - Current form values
- getByAltText - Images
- getByTitle - When other options don't work
- getByTestId - Last resort
Common Patterns
Testing Forms
import { renderWithTheme, screen, userEvent } from '@pie-lib/test-utils';
test('submits form', async () => {
const onSubmit = jest.fn();
const user = userEvent.setup();
renderWithTheme(<LoginForm onSubmit={onSubmit} />);
await user.type(screen.getByLabelText('Username'), 'user123');
await user.type(screen.getByLabelText('Password'), 'pass123');
await user.click(screen.getByRole('button', { name: /submit/i }));
expect(onSubmit).toHaveBeenCalledWith({
username: 'user123',
password: 'pass123'
});
});Testing Conditional Rendering
test('shows error message', async () => {
renderWithTheme(<Component />);
// Initially not present
expect(screen.queryByText('Error')).not.toBeInTheDocument();
// Trigger error
await userEvent.click(screen.getByRole('button'));
// Now appears
expect(await screen.findByText('Error occurred')).toBeInTheDocument();
});Testing with Custom Props
const defaultProps = {
title: 'Test',
onChange: jest.fn()
};
test('renders with defaults', () => {
renderWithTheme(<Component {...defaultProps} />);
expect(screen.getByText('Test')).toBeInTheDocument();
});
test('renders with overrides', () => {
renderWithTheme(<Component {...defaultProps} title="Override" />);
expect(screen.getByText('Override')).toBeInTheDocument();
});Tips
- Use
screenqueries - More readable than destructuring from render - Prefer
findByfor async - Built-in waiting - Use
queryByfor non-existence - Won't throw if not found - Test behavior, not implementation - Don't test internal state
- Use
userEventoverfireEvent- More realistic interactions
