styled-components provides testing utilities for finding and testing styled components in various testing environments. These utilities enable effective testing of styled components without relying on implementation details.
The test utilities are provided as a separate package and enable finding styled components by their unique identifiers rather than class names or other implementation-specific details.
// From 'styled-components/test-utils'
function enzymeFind(
wrapper: ReactWrapper,
styledComponent: IStyledComponent<'web', any>
): ReactWrapper;
function find(
element: Element,
styledComponent: IStyledComponent<'web', any>
): Element | null;
function findAll(
element: Element,
styledComponent: IStyledComponent<'web', any>
): NodeListOf<Element>;import { mount } from 'enzyme';
import { enzymeFind } from 'styled-components/test-utils';
import 'jest-styled-components';
const Button = styled.button`
background-color: #007bff;
color: white;
padding: 10px 20px;
`;
const Card = styled.div`
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
`;
describe('Styled Components', () => {
it('should find styled components in enzyme wrapper', () => {
const TestComponent = () => (
<Card>
<Button>Click me</Button>
</Card>
);
const wrapper = mount(<TestComponent />);
// Find styled components using enzymeFind
const cardElement = enzymeFind(wrapper, Card);
const buttonElement = enzymeFind(wrapper, Button);
expect(cardElement).toHaveLength(1);
expect(buttonElement).toHaveLength(1);
expect(buttonElement.text()).toBe('Click me');
});
it('should test styled component props', () => {
const TestButton = styled.button<{ primary?: boolean }>`
background-color: ${props => props.primary ? '#007bff' : '#6c757d'};
`;
const wrapper = mount(<TestButton primary>Test</TestButton>);
const buttonElement = enzymeFind(wrapper, TestButton);
expect(buttonElement.prop('primary')).toBe(true);
});
});import { ThemeProvider } from 'styled-components';
const theme = {
colors: {
primary: '#007bff',
secondary: '#6c757d'
}
};
const ThemedButton = styled.button`
background-color: ${props => props.theme.colors.primary};
`;
describe('Themed Components', () => {
it('should render with theme', () => {
const wrapper = mount(
<ThemeProvider theme={theme}>
<ThemedButton>Themed Button</ThemedButton>
</ThemeProvider>
);
const button = enzymeFind(wrapper, ThemedButton);
expect(button).toHaveLength(1);
// Test computed styles
expect(button).toHaveStyleRule('background-color', '#007bff');
});
});import { render, screen } from '@testing-library/react';
import { find, findAll } from 'styled-components/test-utils';
const NavigationItem = styled.li`
padding: 8px 16px;
cursor: pointer;
&:hover {
background-color: #f8f9fa;
}
`;
const Navigation = styled.ul`
list-style: none;
padding: 0;
margin: 0;
`;
describe('DOM Testing', () => {
it('should find styled components in DOM', () => {
const TestNav = () => (
<Navigation>
<NavigationItem>Home</NavigationItem>
<NavigationItem>About</NavigationItem>
<NavigationItem>Contact</NavigationItem>
</Navigation>
);
const { container } = render(<TestNav />);
// Find single styled component
const nav = find(container, Navigation);
expect(nav).toBeInTheDocument();
// Find all instances of styled component
const navItems = findAll(container, NavigationItem);
expect(navItems).toHaveLength(3);
// Test content
expect(navItems[0]).toHaveTextContent('Home');
expect(navItems[1]).toHaveTextContent('About');
expect(navItems[2]).toHaveTextContent('Contact');
});
});const ListItem = styled.li`
border-bottom: 1px solid #eee;
padding: 12px;
`;
describe('Vanilla DOM Testing', () => {
it('should work with vanilla DOM methods', () => {
// Create DOM structure
document.body.innerHTML = `
<div id="test-container">
<!-- Styled components will be rendered here -->
</div>
`;
const container = document.getElementById('test-container');
const root = createRoot(container);
const TestList = () => (
<ul>
<ListItem>Item 1</ListItem>
<ListItem>Item 2</ListItem>
</ul>
);
act(() => {
root.render(<TestList />);
});
// Use find and findAll utilities
const firstItem = find(container, ListItem);
const allItems = findAll(container, ListItem);
expect(firstItem.textContent).toBe('Item 1');
expect(allItems.length).toBe(2);
});
});import 'jest-styled-components';
const StyledButton = styled.button<{
variant?: 'primary' | 'secondary';
size?: 'small' | 'large'
}>`
padding: ${props => props.size === 'small' ? '4px 8px' : '12px 24px'};
background-color: ${props => {
switch (props.variant) {
case 'primary': return '#007bff';
case 'secondary': return '#6c757d';
default: return 'transparent';
}
}};
border: ${props => props.variant ? 'none' : '1px solid #ccc'};
`;
describe('Style Rule Testing', () => {
it('should have correct default styles', () => {
const { container } = render(<StyledButton>Default</StyledButton>);
const button = find(container, StyledButton);
expect(button).toHaveStyleRule('padding', '12px 24px');
expect(button).toHaveStyleRule('background-color', 'transparent');
expect(button).toHaveStyleRule('border', '1px solid #ccc');
});
it('should have correct primary variant styles', () => {
const { container } = render(
<StyledButton variant="primary" size="small">Primary</StyledButton>
);
const button = find(container, StyledButton);
expect(button).toHaveStyleRule('padding', '4px 8px');
expect(button).toHaveStyleRule('background-color', '#007bff');
expect(button).toHaveStyleRule('border', 'none');
});
it('should have hover styles', () => {
const HoverButton = styled.button`
background-color: #007bff;
&:hover {
background-color: #0056b3;
}
`;
const { container } = render(<HoverButton>Hover me</HoverButton>);
const button = find(container, HoverButton);
expect(button).toHaveStyleRule('background-color', '#007bff');
expect(button).toHaveStyleRule('background-color', '#0056b3', {
modifier: ':hover'
});
});
});const ResponsiveComponent = styled.div`
padding: 16px;
@media (max-width: 768px) {
padding: 8px;
}
@media (min-width: 1024px) {
padding: 32px;
}
`;
describe('Media Query Testing', () => {
it('should have responsive styles', () => {
const { container } = render(<ResponsiveComponent>Content</ResponsiveComponent>);
const component = find(container, ResponsiveComponent);
expect(component).toHaveStyleRule('padding', '16px');
expect(component).toHaveStyleRule('padding', '8px', {
media: '(max-width: 768px)'
});
expect(component).toHaveStyleRule('padding', '32px', {
media: '(min-width: 1024px)'
});
});
});const Card = styled.div`
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
`;
const CardTitle = styled.h3`
margin: 0 0 12px 0;
color: #333;
font-size: 18px;
`;
const CardContent = styled.p`
margin: 0;
color: #666;
line-height: 1.5;
`;
describe('Snapshot Testing', () => {
it('should match snapshot', () => {
const TestCard = () => (
<Card>
<CardTitle>Card Title</CardTitle>
<CardContent>Card content goes here</CardContent>
</Card>
);
const { container } = render(<TestCard />);
expect(container.firstChild).toMatchSnapshot();
});
it('should match styled component snapshot', () => {
const tree = renderer
.create(<Card><CardTitle>Title</CardTitle></Card>)
.toJSON();
expect(tree).toMatchSnapshot();
});
});const themes = {
light: {
colors: { background: '#ffffff', text: '#333333' }
},
dark: {
colors: { background: '#333333', text: '#ffffff' }
}
};
const ThemedDiv = styled.div`
background-color: ${props => props.theme.colors.background};
color: ${props => props.theme.colors.text};
`;
describe('Theme Snapshots', () => {
it.each(Object.entries(themes))('should match %s theme snapshot', (themeName, theme) => {
const tree = renderer
.create(
<ThemeProvider theme={theme}>
<ThemedDiv>Themed content</ThemedDiv>
</ThemeProvider>
)
.toJSON();
expect(tree).toMatchSnapshot(`themed-div-${themeName}`);
});
});const ExpensiveComponent = styled.div`
/* Complex styles that might impact performance */
background: linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%);
box-shadow: 0 3px 5px 2px rgba(255, 105, 135, .3);
transform: rotate(${props => props.rotation || 0}deg);
transition: all 0.3s ease;
`;
describe('Performance Testing', () => {
it('should render quickly', () => {
const startTime = performance.now();
const { container } = render(
<div>
{Array.from({ length: 100 }, (_, i) => (
<ExpensiveComponent key={i} rotation={i * 3.6}>
Item {i}
</ExpensiveComponent>
))}
</div>
);
const endTime = performance.now();
const renderTime = endTime - startTime;
expect(renderTime).toBeLessThan(100); // Should render in less than 100ms
expect(findAll(container, ExpensiveComponent)).toHaveLength(100);
});
});const mockTheme = {
colors: { primary: '#007bff' },
spacing: { md: '16px' }
};
const getButtonStyles = (props) => `
background-color: ${props.theme.colors.primary};
padding: ${props.theme.spacing.md};
`;
const TestableButton = styled.button`
${getButtonStyles}
`;
describe('Style Function Testing', () => {
it('should call style function with correct props', () => {
const stylesSpy = jest.fn(getButtonStyles);
const SpiedButton = styled.button`
${stylesSpy}
`;
render(
<ThemeProvider theme={mockTheme}>
<SpiedButton>Test</SpiedButton>
</ThemeProvider>
);
expect(stylesSpy).toHaveBeenCalledWith(
expect.objectContaining({
theme: mockTheme
})
);
});
});// Custom test utilities for styled-components
export function renderWithTheme(component: React.ReactElement, theme = defaultTheme) {
return render(
<ThemeProvider theme={theme}>
{component}
</ThemeProvider>
);
}
export function findStyledComponent<T extends IStyledComponent<'web', any>>(
container: HTMLElement,
StyledComponent: T
): HTMLElement | null {
return find(container, StyledComponent);
}
export function findAllStyledComponents<T extends IStyledComponent<'web', any>>(
container: HTMLElement,
StyledComponent: T
): NodeListOf<HTMLElement> {
return findAll(container, StyledComponent);
}
// Usage in tests
describe('Custom Utilities', () => {
it('should use custom render helper', () => {
const { container } = renderWithTheme(<ThemedButton>Test</ThemedButton>);
const button = findStyledComponent(container, ThemedButton);
expect(button).toBeInTheDocument();
expect(button).toHaveStyleRule('background-color', '#007bff');
});
});// test-setup.js
import 'jest-styled-components';
import { configure } from 'enzyme';
import Adapter from '@wojtekmaj/enzyme-adapter-react-17';
configure({ adapter: new Adapter() });
// Global test utilities
global.renderWithTheme = (component, theme = defaultTheme) => {
return render(
<ThemeProvider theme={theme}>
{component}
</ThemeProvider>
);
};
// Mock console warnings in tests
const originalConsoleWarn = console.warn;
beforeEach(() => {
console.warn = jest.fn();
});
afterEach(() => {
console.warn = originalConsoleWarn;
});