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/ui2. 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();
});Not a substitute
axe catches ~30% of accessibility issues automatically. Keyboard testing, screen reader testing, and colour-contrast review still need to happen manually.
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');
});