mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 23:14:37 +00:00
140 lines
4.7 KiB
TypeScript
140 lines
4.7 KiB
TypeScript
import { render, screen } from '@testing-library/react';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
|
|
import Button from './Button';
|
|
|
|
describe('Button', () => {
|
|
it('renders children and defaults to primary/md with type=button', () => {
|
|
render(<Button>Send</Button>);
|
|
const btn = screen.getByRole('button', { name: 'Send' });
|
|
expect(btn).toBeInTheDocument();
|
|
expect(btn).toHaveAttribute('type', 'button');
|
|
expect(btn.className).toMatch(/bg-primary-500/);
|
|
expect(btn.className).toMatch(/h-9/);
|
|
expect(btn.className).toMatch(/focus-visible:ring-primary-500\/25/);
|
|
});
|
|
|
|
it('applies secondary variant classes', () => {
|
|
render(<Button variant="secondary">Cancel</Button>);
|
|
const btn = screen.getByRole('button', { name: 'Cancel' });
|
|
// Migrated to semantic surface/line tokens (themeable).
|
|
expect(btn.className).toMatch(/border-line-strong/);
|
|
expect(btn.className).toMatch(/bg-surface/);
|
|
});
|
|
|
|
it('applies tertiary variant classes', () => {
|
|
render(<Button variant="tertiary">Skip</Button>);
|
|
const btn = screen.getByRole('button', { name: 'Skip' });
|
|
expect(btn.className).toMatch(/bg-transparent/);
|
|
expect(btn.className).toMatch(/text-content-secondary/);
|
|
});
|
|
|
|
it('renders a filled coral button for primary + tone="danger"', () => {
|
|
render(
|
|
<Button variant="primary" tone="danger">
|
|
Delete
|
|
</Button>
|
|
);
|
|
const btn = screen.getByRole('button', { name: 'Delete' });
|
|
expect(btn.className).toMatch(/bg-coral-500/);
|
|
expect(btn.className).toMatch(/text-content-inverted/);
|
|
});
|
|
|
|
it('renders a coral outline for secondary + tone="danger"', () => {
|
|
render(
|
|
<Button variant="secondary" tone="danger">
|
|
Remove
|
|
</Button>
|
|
);
|
|
const btn = screen.getByRole('button', { name: 'Remove' });
|
|
expect(btn.className).toMatch(/text-coral-600/);
|
|
expect(btn.className).toMatch(/border-coral-300\/50/);
|
|
expect(btn.className).toMatch(/hover:bg-coral-50/);
|
|
});
|
|
|
|
it('renders coral text for tertiary + tone="danger"', () => {
|
|
render(
|
|
<Button variant="tertiary" tone="danger">
|
|
Discard
|
|
</Button>
|
|
);
|
|
const btn = screen.getByRole('button', { name: 'Discard' });
|
|
expect(btn.className).toMatch(/text-coral-600/);
|
|
expect(btn.className).not.toMatch(/border-coral/);
|
|
});
|
|
|
|
it('squares the footprint for iconOnly (no horizontal padding)', () => {
|
|
render(
|
|
<Button iconOnly size="md" aria-label="Close">
|
|
<span data-testid="icon" />
|
|
</Button>
|
|
);
|
|
const btn = screen.getByRole('button', { name: 'Close' });
|
|
expect(btn.className).toMatch(/h-9/);
|
|
expect(btn.className).toMatch(/w-9/);
|
|
expect(btn.className).not.toMatch(/px-4/);
|
|
});
|
|
|
|
it('honors size=xl classes', () => {
|
|
render(
|
|
<Button size="xl" variant="primary">
|
|
Open
|
|
</Button>
|
|
);
|
|
const btn = screen.getByRole('button', { name: 'Open' });
|
|
expect(btn.className).toMatch(/h-14/);
|
|
expect(btn.className).toMatch(/rounded-xl/);
|
|
});
|
|
|
|
it('honors size=xs classes', () => {
|
|
render(<Button size="xs">tiny</Button>);
|
|
const btn = screen.getByRole('button', { name: 'tiny' });
|
|
expect(btn.className).toMatch(/h-6/);
|
|
expect(btn.className).toMatch(/text-xs/);
|
|
});
|
|
|
|
it('merges extra className', () => {
|
|
render(<Button className="w-full">Wide</Button>);
|
|
const btn = screen.getByRole('button', { name: 'Wide' });
|
|
expect(btn.className).toMatch(/w-full/);
|
|
});
|
|
|
|
it('exposes a stable analytics identifier without forwarding the prop name', () => {
|
|
render(<Button analyticsId="flows-run">Run</Button>);
|
|
const btn = screen.getByRole('button', { name: 'Run' });
|
|
expect(btn).toHaveAttribute('data-analytics-id', 'flows-run');
|
|
expect(btn).not.toHaveAttribute('analyticsId');
|
|
});
|
|
|
|
it('respects disabled: does not fire onClick and has disabled attr', () => {
|
|
const onClick = vi.fn();
|
|
render(
|
|
<Button disabled onClick={onClick}>
|
|
No
|
|
</Button>
|
|
);
|
|
const btn = screen.getByRole('button', { name: 'No' });
|
|
expect(btn).toBeDisabled();
|
|
btn.click();
|
|
expect(onClick).not.toHaveBeenCalled();
|
|
expect(btn.className).toMatch(/disabled:opacity-40/);
|
|
});
|
|
|
|
it('fires onClick when enabled', () => {
|
|
const onClick = vi.fn();
|
|
render(<Button onClick={onClick}>Go</Button>);
|
|
screen.getByRole('button', { name: 'Go' }).click();
|
|
expect(onClick).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('renders leading and trailing icons', () => {
|
|
render(
|
|
<Button leadingIcon={<span data-testid="lead" />} trailingIcon={<span data-testid="trail" />}>
|
|
Label
|
|
</Button>
|
|
);
|
|
expect(screen.getByTestId('lead')).toBeInTheDocument();
|
|
expect(screen.getByTestId('trail')).toBeInTheDocument();
|
|
});
|
|
});
|