Skip to content
Merged
96 changes: 96 additions & 0 deletions packages/react/src/__tests__/layout/Accordion.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { Accordion } from '../../components/layout/Accordion/Accordion';
import { Collapse } from '../../components/layout/Collapse/Collapse';

describe('Accordion', () => {
it('renders collapse children', () => {
render(
<Accordion>
<Collapse title="Panel 1">Content 1</Collapse>
<Collapse title="Panel 2">Content 2</Collapse>
</Accordion>,
);
expect(screen.getByText('Panel 1')).toBeInTheDocument();
expect(screen.getByText('Panel 2')).toBeInTheDocument();
});

it('applies join classes by default', () => {
const { container } = render(
<Accordion>
<Collapse title="Panel 1">Content 1</Collapse>
</Accordion>,
);
expect(container.firstChild).toHaveClass('join', 'join-vertical');
});

it('opens default panels', () => {
const { container } = render(
<Accordion defaultOpenIndices={[0]}>
<Collapse title="Panel 1">Content 1</Collapse>
<Collapse title="Panel 2">Content 2</Collapse>
</Accordion>,
);
const collapses = container.querySelectorAll('.collapse');
expect(collapses[0]).toHaveClass('collapse-open');
expect(collapses[1]).toHaveClass('collapse-close');
});

it('calls onOpenChange when toggling', () => {
const onOpenChange = vi.fn();
const { container } = render(
<Accordion onOpenChange={onOpenChange}>
<Collapse title="Panel 1">Content 1</Collapse>
<Collapse title="Panel 2">Content 2</Collapse>
</Accordion>,
);
const inputs = container.querySelectorAll('input');
fireEvent.click(inputs[0]);
expect(onOpenChange).toHaveBeenCalledWith([0]);
});

it('single mode closes other panels when opening one', () => {
const onOpenChange = vi.fn();
const { container } = render(
<Accordion defaultOpenIndices={[0]} onOpenChange={onOpenChange}>
<Collapse title="Panel 1">Content 1</Collapse>
<Collapse title="Panel 2">Content 2</Collapse>
</Accordion>,
);
const inputs = container.querySelectorAll('input');
fireEvent.click(inputs[1]);
expect(onOpenChange).toHaveBeenCalledWith([1]);
});

it('multiple mode keeps other panels open', () => {
const onOpenChange = vi.fn();
const { container } = render(
<Accordion multiple defaultOpenIndices={[0]} onOpenChange={onOpenChange}>
<Collapse title="Panel 1">Content 1</Collapse>
<Collapse title="Panel 2">Content 2</Collapse>
</Accordion>,
);
const inputs = container.querySelectorAll('input');
fireEvent.click(inputs[1]);
expect(onOpenChange).toHaveBeenCalledWith([0, 1]);
});

it('removes join with join=false', () => {
const { container } = render(
<Accordion join={false}>
<Collapse title="Panel 1">Content 1</Collapse>
</Accordion>,
);
expect(container.firstChild).not.toHaveClass('join');
});

it('forwards ref', () => {
const ref = vi.fn();
render(
<Accordion ref={ref}>
<Collapse title="Panel 1">Content 1</Collapse>
</Accordion>,
);
expect(ref).toHaveBeenCalled();
});
});
93 changes: 93 additions & 0 deletions packages/react/src/__tests__/layout/Card.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { Card } from '../../components/layout/Card/Card';

describe('Card', () => {
it('renders children', () => {
render(<Card>Card content</Card>);
expect(screen.getByText('Card content')).toBeInTheDocument();
});

it('renders title', () => {
render(<Card title="My Card">Content</Card>);
expect(screen.getByText('My Card')).toBeInTheDocument();
expect(screen.getByText('My Card').tagName).toBe('H2');
});

it('renders subtitle', () => {
render(<Card title="Title" subtitle="Subtitle">Content</Card>);
expect(screen.getByText('Subtitle')).toBeInTheDocument();
});

it('renders custom header', () => {
render(<Card header={<span>Custom Header</span>}>Content</Card>);
expect(screen.getByText('Custom Header')).toBeInTheDocument();
});

it('renders footer/actions', () => {
render(
<Card footer={<button>Save</button>}>Content</Card>,
);
expect(screen.getByText('Save')).toBeInTheDocument();
});

it('renders menu slot', () => {
render(
<Card title="Title" menu={<button>Menu</button>}>Content</Card>,
);
expect(screen.getByText('Menu')).toBeInTheDocument();
});

it('renders figure', () => {
render(
<Card figure={<img src="test.jpg" alt="Test" />}>Content</Card>,
);
expect(screen.getByAltText('Test')).toBeInTheDocument();
});

it('applies shadow by default', () => {
const { container } = render(<Card>Content</Card>);
expect(container.firstChild).toHaveClass('shadow-xl');
});

it('removes shadow with noShadow', () => {
const { container } = render(<Card noShadow>Content</Card>);
expect(container.firstChild).not.toHaveClass('shadow-xl');
});

it('applies bordered class', () => {
const { container } = render(<Card bordered>Content</Card>);
expect(container.firstChild).toHaveClass('card-bordered');
});

it('applies compact class', () => {
const { container } = render(<Card compact>Content</Card>);
expect(container.firstChild).toHaveClass('card-compact');
});

it('applies glass effect', () => {
const { container } = render(<Card glass>Content</Card>);
expect(container.firstChild).toHaveClass('glass');
});

it('applies card-side for horizontal figures', () => {
const { container } = render(
<Card figure={<img src="test.jpg" alt="Test" />} figurePosition="left">
Content
</Card>,
);
expect(container.firstChild).toHaveClass('card-side');
});

it('renders as a link when link prop provided', () => {
const { container } = render(<Card link="/test">Content</Card>);
expect(container.firstChild?.nodeName).toBe('A');
expect(container.firstChild).toHaveAttribute('href', '/test');
});

it('forwards ref', () => {
const ref = vi.fn();
render(<Card ref={ref}>Content</Card>);
expect(ref).toHaveBeenCalled();
});
});
98 changes: 98 additions & 0 deletions packages/react/src/__tests__/layout/Collapse.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { render, screen, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { Collapse } from '../../components/layout/Collapse/Collapse';

describe('Collapse', () => {
it('renders title', () => {
render(<Collapse title="Click me">Content</Collapse>);
expect(screen.getByText('Click me')).toBeInTheDocument();
});

it('renders content', () => {
render(<Collapse title="Title">Hidden content</Collapse>);
expect(screen.getByText('Hidden content')).toBeInTheDocument();
});

it('defaults to closed state', () => {
const { container } = render(<Collapse title="Title">Content</Collapse>);
expect(container.firstChild).toHaveClass('collapse-close');
});

it('opens when defaultOpen is true', () => {
const { container } = render(
<Collapse title="Title" defaultOpen>Content</Collapse>,
);
expect(container.firstChild).toHaveClass('collapse-open');
});

it('toggles on checkbox click (uncontrolled)', () => {
const { container } = render(<Collapse title="Title">Content</Collapse>);
const checkbox = container.querySelector('input[type="checkbox"]');
expect(checkbox).not.toBeNull();
fireEvent.click(checkbox!);
expect(container.firstChild).toHaveClass('collapse-open');
});

it('sets aria-expanded on the input', () => {
const { container } = render(<Collapse title="Title" defaultOpen>Content</Collapse>);
const checkbox = container.querySelector('input[type="checkbox"]');
expect(checkbox).toHaveAttribute('aria-expanded', 'true');
});

it('calls onOpenChange callback', () => {
const onOpenChange = vi.fn();
const { container } = render(
<Collapse title="Title" onOpenChange={onOpenChange}>Content</Collapse>,
);
const checkbox = container.querySelector('input[type="checkbox"]')!;
fireEvent.click(checkbox);
expect(onOpenChange).toHaveBeenCalledWith(true);
});

it('respects controlled open prop', () => {
const { container } = render(
<Collapse title="Title" open={true}>Content</Collapse>,
);
expect(container.firstChild).toHaveClass('collapse-open');
});

it('applies arrow icon class by default', () => {
const { container } = render(<Collapse title="Title">Content</Collapse>);
expect(container.firstChild).toHaveClass('collapse-arrow');
});

it('applies plus icon class', () => {
const { container } = render(
<Collapse title="Title" icon="plus">Content</Collapse>,
);
expect(container.firstChild).toHaveClass('collapse-plus');
});

it('applies border when bordered', () => {
const { container } = render(
<Collapse title="Title" bordered>Content</Collapse>,
);
expect(container.firstChild).toHaveClass('border');
});

it('renders radio input when name is provided', () => {
const { container } = render(
<Collapse title="Title" name="group">Content</Collapse>,
);
expect(container.querySelector('input[type="radio"]')).toBeInTheDocument();
});

it('applies disabled styling', () => {
const { container } = render(
<Collapse title="Title" disabled>Content</Collapse>,
);
expect(container.firstChild).toHaveClass('opacity-50');
expect(container.querySelector('input')).toBeDisabled();
});

it('forwards ref', () => {
const ref = vi.fn();
render(<Collapse ref={ref} title="Title">Content</Collapse>);
expect(ref).toHaveBeenCalled();
});
});
50 changes: 50 additions & 0 deletions packages/react/src/__tests__/layout/Divider.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { render, screen } from '@testing-library/react';
import { describe, it, expect, vi } from 'vitest';
import { Divider } from '../../components/layout/Divider/Divider';

describe('Divider', () => {
it('renders a horizontal separator by default', () => {
render(<Divider />);
const el = screen.getByRole('separator');
expect(el).toHaveAttribute('aria-orientation', 'horizontal');
expect(el).toHaveClass('divider');
});

it('renders vertical orientation', () => {
render(<Divider vertical />);
const el = screen.getByRole('separator');
expect(el).toHaveAttribute('aria-orientation', 'vertical');
expect(el).toHaveClass('divider-vertical');
});

it('renders label text', () => {
render(<Divider label="OR" />);
expect(screen.getByText('OR')).toBeInTheDocument();
});

it('renders children as content', () => {
render(<Divider>Section Break</Divider>);
expect(screen.getByText('Section Break')).toBeInTheDocument();
});

it('applies color variant', () => {
render(<Divider color="primary" />);
expect(screen.getByRole('separator')).toHaveClass('divider-primary');
});

it('applies label position', () => {
render(<Divider label="Start" labelPosition="start" />);
expect(screen.getByRole('separator')).toHaveClass('divider-start');
});

it('applies custom className', () => {
render(<Divider className="my-custom" />);
expect(screen.getByRole('separator')).toHaveClass('my-custom');
});

it('forwards ref', () => {
const ref = vi.fn();
render(<Divider ref={ref} />);
expect(ref).toHaveBeenCalled();
});
});
Loading