This guide covers testing practices for the Mangrove component library. For development setup and commands, see DEVELOPMENT.md.
We use multiple testing approaches to ensure component quality:
- Unit tests - Jest for component logic and behavior
- Visual tests - Chromatic for visual regression testing
- Accessibility tests - Built into our test suite
- Manual testing - Storybook for interactive testing
Test files should be placed in __tests__ directories within component folders:
stories/Components/Button/
├── Button.jsx
├── Button.stories.jsx
└── __tests__/
└── Button.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from '../Button';
describe('Button', () => {
it('renders with text', () => {
render(<Button>Click me</Button>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});
it('handles click events', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick}>Click me</Button>);
fireEvent.click(screen.getByText('Click me'));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});# Run all tests
yarn test
# Run tests in watch mode
yarn test:watch
# Run tests for a specific file
yarn test Button.test.jsx
# Generate coverage report
yarn test:coverage
# Run tests with verbose output
yarn test --verbose-
Test behavior, not implementation
- Focus on what users see and do
- Avoid testing internal state or methods
-
Use Testing Library queries appropriately
- Prefer
getByRole,getByLabelText,getByText - Avoid
getByTestIdunless necessary
- Prefer
-
Keep tests isolated
- Each test should be independent
- Use
beforeEachfor common setup
-
Test edge cases
- Empty states
- Error conditions
- Loading states
- Boundary values
it('applies custom className', () => {
render(<Button className="custom-class">Test</Button>);
expect(screen.getByRole('button')).toHaveClass('custom-class');
});it('shows tab content when clicked', () => {
render(<Tab tabdata={tabdata} />);
const tab = screen.getByText('Tab title 2');
fireEvent.click(tab);
expect(screen.getByText(/Sendai Framework/)).toBeInTheDocument();
});it('loads data on mount', async () => {
render(<DataComponent />);
expect(screen.getByText('Loading...')).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText('Data loaded')).toBeInTheDocument();
});
});Visual testing is handled automatically through our CI/CD pipeline.
- Chromatic runs on PRs and main branch pushes
- Visual changes are highlighted for review
- Changes on main are auto-accepted as baseline
- Skip with
[skip chromatic]in commit message
We use jest-axe for automated accessibility testing. The toHaveNoViolations matcher is globally configured in jest.setup.js, so you only need to import axe:
import { axe } from 'jest-axe';
it('has no accessibility violations', async () => {
const { container } = render(<Button>Click me</Button>);
const results = await axe(container);
expect(results).toHaveNoViolations();
});-
Keyboard navigation
- Tab through all interactive elements
- Ensure focus indicators are visible
- Test keyboard shortcuts
-
Screen reader testing
- Use NVDA (Windows) or VoiceOver (Mac)
- Verify announcements make sense
- Check form labels and descriptions
-
Color contrast
- Use browser DevTools
- Verify WCAG AA compliance
- Test with different color blindness simulations
// Mock an external library
jest.mock('react-leaflet', () => ({
MapContainer: ({ children }) => <div>{children}</div>,
TileLayer: () => <div>Tile Layer</div>,
}));import { rest } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
rest.get('/api/data', (req, res, ctx) => {
return res(ctx.json({ data: 'test' }));
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());While we don't enforce strict coverage thresholds, aim for:
- 80% statement coverage
- 70% branch coverage
- 100% coverage for utility functions
- Focus on critical paths
View coverage reports:
yarn test:coverage
open coverage/lcov-report/index.html-
Component not rendering
- Check import paths
- Verify props are correct
- Use
screen.debug()to see output
-
Element not found
- Use
screen.debug()to see current DOM - Check query method is appropriate
- Verify element is actually rendered
- Use
-
Async issues
- Use
waitForfor async updates - Increase timeout if needed
- Check promises are properly handled
- Use
// Print current DOM
screen.debug();
// Print specific element
screen.debug(screen.getByRole('button'));
// Use testing playground
screen.logTestingPlaygroundURL();Tests run automatically on:
- Every push to a PR
- Before merging to main
- During release builds
Failed tests will block merging and deployment.
- Test files:
ComponentName.test.jsx - Test suites: Use
describeblocks - Test cases: Start with "it" or "test"
- Be descriptive but concise
describe('ComponentName', () => {
describe('rendering', () => {
it('renders without crashing', () => {});
it('displays correct content', () => {});
});
describe('interactions', () => {
it('handles user input', () => {});
it('responds to events', () => {});
});
describe('edge cases', () => {
it('handles empty data', () => {});
it('shows error state', () => {});
});
});For performance-sensitive components:
import { render } from '@testing-library/react';
import { measureRender } from './test-utils';
it('renders efficiently', () => {
const renderTime = measureRender(() => {
render(<LargeList items={manyItems} />);
});
expect(renderTime).toBeLessThan(100); // ms
});- Component guide — step-by-step tutorial for building a component
- Review checklist — pre-submission component checklist
- Hydration authoring — adding Drupal integration (next step after testing)