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>
});

Class naming mode

If you use hashed or atomic class naming in tests, call resetClassNaming() alongside any stylesheet reset (for example reset() from typestyles) in beforeEach so naming options do not leak between files. Assertions that depend on exact class strings may need snapshots or prefix-based checks when mode is not semantic.

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

  1. Test behavior, not classes - Focus on what users see and interact with
  2. Use semantic queries - getByRole, getByText over class name selectors
  3. Visual regression for styles - Use Chromatic/Percy for CSS testing
  4. E2E for computed styles - Use Playwright/Cypress to test actual rendering
  5. Snapshot sparingly - Only snapshot class names if you're maintaining a library
  6. 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.