Testing
Testing strategies for typestyles components
Testing
Testing components styled with typestyles is straightforward. Since typestyles generates regular CSS class names, you can use standard testing tools without any special setup.
Unit testing components
Basic component testing
Since typestyles returns regular class names, test your components the same way you'd test any React/Vue/Svelte component:
// Button.tsx
import { styles } from 'typestyles';
const button = styles.create('button', {
base: { padding: '8px 16px' },
primary: { backgroundColor: '#0066ff' },
large: { fontSize: '18px' },
});
export function Button({ variant, size, children }) {
return <button className={button('base', variant, size)}>{children}</button>;
}
// Button.test.tsx
import { render, screen } from '@testing-library/react';
import { Button } from './Button';
describe('Button', () => {
it('renders with base class', () => {
render(<Button>Click me</Button>);
const button = screen.getByRole('button');
expect(button).toHaveClass('button-base');
});
it('applies variant classes', () => {
render(<Button variant="primary">Click me</Button>);
const button = screen.getByRole('button');
expect(button).toHaveClass('button-base');
expect(button).toHaveClass('button-primary');
});
it('applies multiple variants', () => {
render(
<Button variant="primary" size="large">
Click me
</Button>,
);
const button = screen.getByRole('button');
expect(button).toHaveClass('button-base');
expect(button).toHaveClass('button-primary');
expect(button).toHaveClass('button-large');
});
});
Testing conditional classes
When variants are applied conditionally, test both states:
function Button({ isLoading, children }) {
return (
<button className={button('base', isLoading && 'loading')}>
{isLoading ? 'Loading...' : children}
</button>
);
}
// Button.test.tsx
it('applies loading state', () => {
render(<Button isLoading>Click me</Button>);
const button = screen.getByRole('button');
expect(button).toHaveClass('button-base');
expect(button).toHaveClass('button-loading');
});
it('does not apply loading class when not loading', () => {
render(<Button>Click me</Button>);
const button = screen.getByRole('button');
expect(button).toHaveClass('button-base');
expect(button).not.toHaveClass('button-loading');
});
Testing with React Testing Library
Prefer semantic queries
Instead of testing for specific class names, test for accessible properties when possible:
// ✅ Good - test user-visible behavior
it('disables button when loading', () => {
render(<Button isLoading>Click me</Button>);
const button = screen.getByRole('button');
expect(button).toBeDisabled();
expect(button).toHaveTextContent('Loading...');
});
// Alternative if you need to verify styles
it('has loading styles', () => {
render(<Button isLoading>Click me</Button>);
const button = screen.getByRole('button');
// Verify the class is applied (CSS testing is separate)
expect(button).toHaveClass('button-loading');
});
Testing style snapshots
If you want to ensure class names don't change unexpectedly, use snapshots:
it('matches snapshot', () => {
const { container } = render(<Button variant="primary">Click me</Button>);
expect(container.firstChild).toMatchSnapshot();
// Snapshot: <button class="button-base button-primary">Click me</button>
});
CSS testing strategies
Don't test CSS output directly
CSS output is an implementation detail. Test user-facing behavior instead:
// ❌ Avoid - testing implementation details
it('has padding of 8px', () => {
const { container } = render(<Button>Click</Button>);
expect(container.firstChild).toHaveStyle({ padding: '8px 16px' });
});
// ✅ Better - test that the component renders correctly
it('renders as a button', () => {
render(<Button>Click</Button>);
expect(screen.getByRole('button')).toBeInTheDocument();
});
// ✅ Or use visual regression tests for styles
Visual regression testing
For CSS testing, use visual regression tools like Chromatic, Percy, or Storybook's visual testing:
// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
component: Button,
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {
args: {
variant: 'primary',
children: 'Primary Button',
},
};
export const Secondary: Story = {
args: {
variant: 'secondary',
children: 'Secondary Button',
},
};
export const Large: Story = {
args: {
variant: 'primary',
size: 'large',
children: 'Large Button',
},
};
Run visual regression tests in CI to catch unintended style changes.
Testing tokens
Token value tests
Test that tokens generate the expected CSS:
// tokens.test.ts
import { tokens } from 'typestyles';
describe('tokens', () => {
it('creates color token references', () => {
const color = tokens.create('color', {
primary: '#0066ff',
});
expect(color.primary).toBe('var(--color-primary)');
});
it('creates spacing token references', () => {
const space = tokens.create('space', {
sm: '8px',
md: '16px',
});
expect(space.sm).toBe('var(--space-sm)');
expect(space.md).toBe('var(--space-md)');
});
});
Note: Testing the actual CSS output requires DOM access (see integration tests below).
Integration testing
Testing with actual styles
For integration tests, you might want to verify that styles are actually applied:
// setupTests.ts or jest.setup.ts
// Ensure styles are injected in test environment
import 'typestyles';
// For JSDOM-based tests, you may need to mock or stub some CSS APIs
Object.defineProperty(window, 'CSS', { value: { supports: () => true } });
Testing computed styles
import { render } from '@testing-library/react';
import { Button } from './Button';
it('applies correct background color', () => {
const { getByRole } = render(<Button variant="primary">Click</Button>);
const button = getByRole('button');
// Get computed styles
const styles = window.getComputedStyle(button);
// Note: This may not work in JSDOM as it doesn't fully compute CSS
// Better to use visual regression or E2E tests for this
expect(styles.backgroundColor).toBeDefined();
});
⚠️ Note: JSDOM doesn't fully support CSS custom properties or computed styles. For testing actual styles, use a real browser with Playwright or Cypress.
E2E testing
Playwright example
// button.spec.ts
import { test, expect } from '@playwright/test';
test('button has correct styles', async ({ page }) => {
await page.goto('/button-demo');
const button = page.locator('button').first();
// Check computed styles
await expect(button).toHaveCSS('padding', '8px 16px');
await expect(button).toHaveCSS('background-color', 'rgb(0, 102, 255)');
// Check class names
await expect(button).toHaveClass(/button-base/);
await expect(button).toHaveClass(/button-primary/);
});
test('button hover state', async ({ page }) => {
await page.goto('/button-demo');
const button = page.locator('button').first();
// Hover and check styles
await button.hover();
await expect(button).toHaveCSS('background-color', 'rgb(0, 82, 204)');
});
Cypress example
// button.cy.ts
describe('Button', () => {
it('has correct classes', () => {
cy.visit('/button-demo');
cy.get('button')
.first()
.should('have.class', 'button-base')
.and('have.class', 'button-primary');
});
it('has correct computed styles', () => {
cy.visit('/button-demo');
cy.get('button')
.first()
.should('have.css', 'padding', '8px 16px')
.and('have.css', 'background-color', 'rgb(0, 102, 255)');
});
});
Testing SSR
When testing SSR-rendered components, ensure styles are collected:
// ssr.test.ts
import { collectStyles } from 'typestyles/server';
import { renderToString } from 'react-dom/server';
import { App } from './App';
describe('SSR', () => {
it('collects styles during render', () => {
const { html, css } = collectStyles(() => renderToString(<App />));
// HTML should contain class names
expect(html).toContain('button-base');
// CSS should contain the styles
expect(css).toContain('.button-base');
expect(css).toContain('padding');
});
it('includes CSS custom properties', () => {
const { css } = collectStyles(() => renderToString(<App />));
expect(css).toContain('--color-primary');
expect(css).toContain('--space-md');
});
});
Snapshot testing class names
For library authors or design systems, you might want to snapshot the generated class names:
// styles.test.ts
import { styles } from 'typestyles';
describe('Button styles', () => {
it('generates consistent class names', () => {
const button = styles.create('button', {
base: { padding: '8px' },
primary: { color: 'blue' },
});
expect(button('base')).toMatchInlineSnapshot(`"button-base"`);
expect(button('base', 'primary')).toMatchInlineSnapshot(`"button-base button-primary"`);
});
});
Mocking typestyles (if needed)
In rare cases, you might want to mock typestyles:
// __mocks__/typestyles.ts
export const styles = {
create: (namespace: string, definitions: Record<string, any>) => {
return (...variants: (string | false | undefined)[]) => {
return variants
.filter(Boolean)
.map((v) => `${namespace}-${v}`)
.join(' ');
};
},
};
export const tokens = {
create: (namespace: string, values: Record<string, string>) => {
return Object.fromEntries(
Object.keys(values).map((key) => [key, `var(--${namespace}-${key})`]),
);
},
use: (namespace: string) => {
return new Proxy(
{},
{
get: (_, key: string) => `var(--${namespace}-${key})`,
},
);
},
createTheme: (name: string) => `theme-${name}`,
};
However, mocking is usually unnecessary since typestyles has minimal side effects in tests.
Best practices
- Test behavior, not classes - Focus on what users see and interact with
- Use semantic queries -
getByRole,getByTextover class name selectors - Visual regression for styles - Use Chromatic/Percy for CSS testing
- E2E for computed styles - Use Playwright/Cypress to test actual rendering
- Snapshot sparingly - Only snapshot class names if you're maintaining a library
- Don't test implementation - CSS output is an implementation detail
Example test suite
Here's a complete example test suite for a component:
// Card.test.tsx
import { render, screen } from '@testing-library/react';
import { Card } from './Card';
describe('Card', () => {
it('renders children', () => {
render(<Card>Content</Card>);
expect(screen.getByText('Content')).toBeInTheDocument();
});
it('has correct ARIA role', () => {
render(<Card>Content</Card>);
expect(screen.getByRole('article')).toBeInTheDocument();
});
it('applies elevated variant', () => {
render(<Card elevated>Content</Card>);
const card = screen.getByRole('article');
expect(card).toHaveClass('card-base');
expect(card).toHaveClass('card-elevated');
});
it('applies interactive variant', () => {
render(<Card interactive>Content</Card>);
const card = screen.getByRole('article');
expect(card).toHaveClass('card-base');
expect(card).toHaveClass('card-interactive');
});
it('can combine variants', () => {
render(
<Card elevated interactive>
Content
</Card>,
);
const card = screen.getByRole('article');
expect(card).toHaveClass('card-base');
expect(card).toHaveClass('card-elevated');
expect(card).toHaveClass('card-interactive');
});
it('matches snapshot', () => {
const { container } = render(
<Card elevated>
<h2>Title</h2>
<p>Description</p>
</Card>,
);
expect(container.firstChild).toMatchSnapshot();
});
});
This approach tests:
- Component rendering
- Accessibility
- Variant application
- Combination of variants
- Snapshot for unexpected changes
All without worrying about the actual CSS values, which should be tested via visual regression or E2E tests.