From react-native-web
Provides Jest and React Native Testing Library patterns for testing React Native Web apps, including component testing, utilities, configuration, and best practices.
npx claudepluginhub thebushidocollective/han --plugin react-native-webThis skill is limited to using the following tools:
Comprehensive testing patterns for React Native Web applications using Jest and React Native Testing Library.
Writes, reviews, and fixes React Native component tests using @testing-library/react-native v13 (sync) and v14 (async). Covers render, queries, userEvent, fireEvent, waitFor, Jest matchers, and Expo setup.
Writes, reviews, and fixes React Native component tests using @testing-library/react-native v13 (React 18, sync) and v14 (React 19+, async). Covers render, screen queries, userEvent, fireEvent, waitFor.
Provides Jest testing patterns including factory functions, module mocking, custom renders with providers, and TDD workflows for React Native unit tests.
Share bugs, ideas, or general feedback.
Comprehensive testing patterns for React Native Web applications using Jest and React Native Testing Library.
The standard testing library for React Native components:
import { render, screen, fireEvent } from '@testing-library/react-native';
import { Button } from './Button';
describe('Button', () => {
it('calls onPress when pressed', () => {
const onPress = jest.fn();
render(<Button title="Click me" onPress={onPress} />);
const button = screen.getByText('Click me');
fireEvent.press(button);
expect(onPress).toHaveBeenCalledTimes(1);
});
});
Configure Jest for React Native Web:
// jest.config.js
module.exports = {
preset: 'react-native',
moduleNameMapper: {
'^react-native$': 'react-native-web',
},
transformIgnorePatterns: [
'node_modules/(?!(react-native|@react-native|react-native-web)/)',
],
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
};
Common testing utilities and helpers:
import { render, RenderOptions } from '@testing-library/react-native';
import { ReactElement } from 'react';
import { ThemeProvider } from './theme';
interface CustomRenderOptions extends Omit<RenderOptions, 'wrapper'> {
theme?: Theme;
}
export function renderWithProviders(
ui: ReactElement,
{ theme = defaultTheme, ...options }: CustomRenderOptions = {}
) {
return render(
<ThemeProvider value={theme}>
{ui}
</ThemeProvider>,
options
);
}
✅ Test user interactions and behavior:
import { render, screen, fireEvent, waitFor } from '@testing-library/react-native';
import { LoginForm } from './LoginForm';
describe('LoginForm', () => {
it('submits form with valid credentials', async () => {
const onSubmit = jest.fn();
render(<LoginForm onSubmit={onSubmit} />);
const emailInput = screen.getByPlaceholderText('Email');
const passwordInput = screen.getByPlaceholderText('Password');
const submitButton = screen.getByText('Login');
fireEvent.changeText(emailInput, 'user@example.com');
fireEvent.changeText(passwordInput, 'password123');
fireEvent.press(submitButton);
await waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
email: 'user@example.com',
password: 'password123',
});
});
});
it('shows error for invalid email', async () => {
render(<LoginForm onSubmit={jest.fn()} />);
const emailInput = screen.getByPlaceholderText('Email');
const submitButton = screen.getByText('Login');
fireEvent.changeText(emailInput, 'invalid-email');
fireEvent.press(submitButton);
await waitFor(() => {
expect(screen.getByText('Invalid email address')).toBeTruthy();
});
});
});
✅ Use waitFor for async operations:
import { render, screen, waitFor } from '@testing-library/react-native';
import { UserProfile } from './UserProfile';
describe('UserProfile', () => {
it('loads and displays user data', async () => {
const mockUser = { id: '1', name: 'John Doe', email: 'john@example.com' };
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve(mockUser),
})
) as jest.Mock;
render(<UserProfile userId="1" />);
// Check loading state
expect(screen.getByTestId('loading-indicator')).toBeTruthy();
// Wait for data to load
await waitFor(() => {
expect(screen.getByText('John Doe')).toBeTruthy();
expect(screen.getByText('john@example.com')).toBeTruthy();
});
expect(screen.queryByTestId('loading-indicator')).toBeNull();
});
});
✅ Mock navigation and other dependencies:
import { render, screen, fireEvent } from '@testing-library/react-native';
// Mock navigation
const mockNavigate = jest.fn();
jest.mock('@react-navigation/native', () => ({
useNavigation: () => ({
navigate: mockNavigate,
}),
}));
describe('HomeScreen', () => {
it('navigates to details on item press', () => {
render(<HomeScreen />);
const item = screen.getByText('Item 1');
fireEvent.press(item);
expect(mockNavigate).toHaveBeenCalledWith('Details', { id: '1' });
});
});
import { renderHook, act } from '@testing-library/react-hooks';
import { useCounter } from './useCounter';
describe('useCounter', () => {
it('increments counter', () => {
const { result } = renderHook(() => useCounter());
expect(result.current.count).toBe(0);
act(() => {
result.current.increment();
});
expect(result.current.count).toBe(1);
});
it('decrements counter', () => {
const { result } = renderHook(() => useCounter(5));
act(() => {
result.current.decrement();
});
expect(result.current.count).toBe(4);
});
});
import { render, screen } from '@testing-library/react-native';
import { AuthProvider } from './auth-context';
import { ProtectedScreen } from './ProtectedScreen';
describe('ProtectedScreen', () => {
it('shows content when authenticated', () => {
const mockUser = { id: '1', name: 'John' };
render(
<AuthProvider initialUser={mockUser}>
<ProtectedScreen />
</AuthProvider>
);
expect(screen.getByText('Welcome, John')).toBeTruthy();
});
it('shows login prompt when not authenticated', () => {
render(
<AuthProvider initialUser={null}>
<ProtectedScreen />
</AuthProvider>
);
expect(screen.getByText('Please log in')).toBeTruthy();
});
});
import { render } from '@testing-library/react-native';
import { Card } from './Card';
describe('Card', () => {
it('matches snapshot', () => {
const { toJSON } = render(
<Card title="Test Card" description="Test description" />
);
expect(toJSON()).toMatchSnapshot();
});
});
import { render, screen, fireEvent, waitFor } from '@testing-library/react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { HomeScreen } from './HomeScreen';
import { DetailsScreen } from './DetailsScreen';
const Stack = createNativeStackNavigator();
function TestApp() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
describe('Navigation Flow', () => {
it('navigates from home to details', async () => {
render(<TestApp />);
// On Home screen
expect(screen.getByText('Home Screen')).toBeTruthy();
// Navigate to Details
const item = screen.getByText('View Details');
fireEvent.press(item);
// Wait for Details screen
await waitFor(() => {
expect(screen.getByText('Details Screen')).toBeTruthy();
});
});
});
describe('ContactForm', () => {
it('validates all fields before submit', async () => {
const onSubmit = jest.fn();
render(<ContactForm onSubmit={onSubmit} />);
const submitButton = screen.getByText('Submit');
fireEvent.press(submitButton);
await waitFor(() => {
expect(screen.getByText('Name is required')).toBeTruthy();
expect(screen.getByText('Email is required')).toBeTruthy();
expect(onSubmit).not.toHaveBeenCalled();
});
});
});
describe('ItemsList', () => {
it('renders all items', () => {
const items = [
{ id: '1', title: 'Item 1' },
{ id: '2', title: 'Item 2' },
{ id: '3', title: 'Item 3' },
];
render(<ItemsList items={items} />);
items.forEach(item => {
expect(screen.getByText(item.title)).toBeTruthy();
});
});
it('handles empty state', () => {
render(<ItemsList items={[]} />);
expect(screen.getByText('No items found')).toBeTruthy();
});
});
describe('Button accessibility', () => {
it('has correct accessibility props', () => {
render(<Button title="Submit" onPress={jest.fn()} />);
const button = screen.getByRole('button');
expect(button).toHaveAccessibilityState({ disabled: false });
expect(button).toHaveAccessibilityHint('Submits the form');
});
it('is disabled when loading', () => {
render(<Button title="Submit" onPress={jest.fn()} loading />);
const button = screen.getByRole('button');
expect(button).toHaveAccessibilityState({ disabled: true, busy: true });
});
});
❌ Don't test implementation details:
// Bad - testing internal state
expect(component.state.count).toBe(5);
// Good - test observable behavior
expect(screen.getByText('Count: 5')).toBeTruthy();
❌ Don't use querySelector or DOM methods:
// Bad
const element = container.querySelector('.button');
// Good
const button = screen.getByRole('button');
❌ Don't create overly coupled tests:
// Bad - too specific
expect(screen.getByText('Submit')).toHaveStyle({
backgroundColor: '#007AFF',
paddingHorizontal: 16
});
// Good - test behavior
const button = screen.getByText('Submit');
expect(button).toBeTruthy();
fireEvent.press(button);
expect(mockSubmit).toHaveBeenCalled();
❌ Don't forget to clean up:
// Bad
afterEach(() => {
// No cleanup
});
// Good
afterEach(() => {
jest.clearAllMocks();
cleanup();
});