Skip to main content

Testing

A pragmatic testing setup for apps built with Aidash Components. Unit tests with Vitest + React Testing Library, accessibility assertions with axe, and end-to-end coverage with Playwright.

Which layer?
Unit tests are cheap and catch logic bugs. Integration tests catch component composition mistakes. E2E tests catch real user flows breaking. Aim for the pyramid: many units, some integrations, a handful of E2Es.

Setup

Install the toolchain, then wire up two config files.

1. Install

Terminal
bash
pnpm add -D vitest @testing-library/react @testing-library/user-event @testing-library/jest-dom jsdom @vitest/ui

2. Vitest config

vitest.config.ts
tsx
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';

export default defineConfig({
  plugins: [react()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: ['./vitest.setup.ts'],
  },
});

3. Setup file

vitest.setup.ts
tsx
import '@testing-library/jest-dom/vitest';
import { afterEach } from 'vitest';
import { cleanup } from '@testing-library/react';

afterEach(cleanup);

Unit tests

Render a component in isolation, assert on what the user sees. React Testing Library's queryByRole is the workhorse.

button.test.tsx
tsx
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { AidashButton } from '@/components/aidash/button';

describe('AidashButton', () => {
  it('renders children', () => {
    render(<AidashButton>Save</AidashButton>);
    expect(screen.getByRole('button', { name: 'Save' })).toBeInTheDocument();
  });

  it('fires onClick when pressed', async () => {
    const user = userEvent.setup();
    const onClick = vi.fn();
    render(<AidashButton onClick={onClick}>Save</AidashButton>);
    await user.click(screen.getByRole('button'));
    expect(onClick).toHaveBeenCalledOnce();
  });

  it('is disabled when loading', () => {
    render(<AidashButton loading>Saving...</AidashButton>);
    expect(screen.getByRole('button')).toBeDisabled();
  });
});
Tip
Query by role or label, not by class or test-id. Roles are how screen readers see your UI — if your test can find it, an AT user can too.

Accessibility tests

Automated a11y checks catch the low-hanging violations. Combine with manual testing for the rest.

dialog.test.tsx
tsx
import { axe } from 'vitest-axe';
import { render } from '@testing-library/react';
import { AidashDialog } from '@/components/aidash/dialog';

it('has no accessibility violations', async () => {
  const { container } = render(
    <AidashDialog open onClose={() => {}}>
      <h2>Title</h2>
      <p>Body</p>
    </AidashDialog>
  );
  expect(await axe(container)).toHaveNoViolations();
});

End-to-end tests

Playwright drives real browsers. Cover the two or three flows that must never break — sign-up, checkout, primary CTA.

login.spec.ts
tsx
import { test, expect } from '@playwright/test';

test('login flow succeeds', async ({ page }) => {
  await page.goto('/login');
  await page.getByLabel('Email').fill('user@example.com');
  await page.getByLabel('Password').fill('password123');
  await page.getByRole('button', { name: 'Sign in' }).click();

  await expect(page).toHaveURL('/dashboard');
  await expect(page.getByRole('heading', { name: 'Welcome' })).toBeVisible();
});

Visual regression

Playwright can also snapshot pages and diff them across runs. Great for catching accidental UI drift.

visual.spec.ts
tsx
test('button variants', async ({ page }) => {
  await page.goto('/docs/components/button');
  await expect(page.getByTestId('variants-preview')).toHaveScreenshot('button-variants.png');
});

Also see