import React from 'react'; import { describe, expect, test, vi } from 'vitest'; import { render, screen, within, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { CheckboxDemo } from '../src/demos/CheckboxDemo'; import { SwitchDemo } from '../src/demos/SwitchDemo'; import { TextAreaDemo } from '../src/demos/TextAreaDemo'; import { LabelDemo } from '../src/demos/LabelDemo'; import { SearchInputDemo } from '../src/demos/SearchInputDemo'; import { TabsDemo } from '../src/demos/TabsDemo'; import { DrawerDemo } from '../src/demos/DrawerDemo'; import { DropdownMenuDemo } from '../src/demos/DropdownMenuDemo'; describe('Checkbox demo', () => { test('checkbox toggles between checked and unchecked', async () => { const user = userEvent.setup(); render(); const checkbox = screen.getByLabelText('Checked') as HTMLInputElement; expect(checkbox.checked).toBe(true); await user.click(checkbox); expect(screen.getByLabelText('Unchecked')).toBeInTheDocument(); }); test('indeterminate state shows dash/minus icon', () => { render(); const input = screen.getByLabelText('Indeterminate') as HTMLInputElement; expect(input.indeterminate).toBe(true); }); test('label is clickable and toggles checkbox', async () => { const user = userEvent.setup(); render(); const checkbox = screen.getByLabelText('Checked') as HTMLInputElement; await user.click(screen.getByText('Checked')); expect(checkbox.checked).toBe(false); }); test('disabled checkbox cannot be toggled', async () => { const user = userEvent.setup(); render(); const checkbox = screen.getByLabelText('Disabled') as HTMLInputElement; await user.click(checkbox); expect(checkbox.checked).toBe(false); }); }); describe('Switch demo', () => { test('switch toggles between on and off', async () => { const user = userEvent.setup(); render(); const switchEl = screen.getAllByRole('switch')[0] as HTMLButtonElement; await user.click(switchEl); expect(switchEl.getAttribute('aria-checked')).toBe('false'); }); test('size variants render at different scales', () => { render(); const switches = screen.getAllByRole('switch'); const sm = switches[1]; const lg = switches[3]; expect(Number(sm.style.width.replace('px', ''))).toBeLessThan(Number(lg.style.width.replace('px', ''))); }); test('disabled switch cannot be toggled', async () => { const user = userEvent.setup(); render(); const disabled = screen.getAllByRole('switch')[4] as HTMLButtonElement; await user.click(disabled); expect(disabled.getAttribute('aria-checked')).toBe('true'); }); }); describe('TextArea demo', () => { test('textarea accepts multi-line input', async () => { const user = userEvent.setup(); render(); const textarea = screen.getByPlaceholderText('Write a message...') as HTMLTextAreaElement; await user.type(textarea, 'Line 1\nLine 2'); expect(textarea.value).toContain('Line 2'); }); test('rows prop changes visible rows', () => { render(); const rows2 = screen.getByPlaceholderText('2 rows') as HTMLTextAreaElement; const rows4 = screen.getByPlaceholderText('4 rows') as HTMLTextAreaElement; expect(rows2.rows).toBe(2); expect(rows4.rows).toBe(4); }); test('resize option controls resize behavior', () => { render(); const none = screen.getByPlaceholderText('No resize') as HTMLTextAreaElement; const horizontal = screen.getByPlaceholderText('Horizontal resize') as HTMLTextAreaElement; expect(none.style.resize).toBe('none'); expect(horizontal.style.resize).toBe('horizontal'); }); test('disabled textarea cannot be edited', async () => { const user = userEvent.setup(); render(); const disabled = screen.getByPlaceholderText('Disabled') as HTMLTextAreaElement; await user.type(disabled, 'Text'); expect(disabled.value).toBe(''); }); }); describe('Label demo', () => { test('label text is rendered', () => { render(); expect(screen.getByText('Email address')).toBeInTheDocument(); }); test('required indicator is shown when required=true', () => { render(); const required = screen.getByText('Company name').parentElement as HTMLElement; expect(required.textContent).toContain('*'); }); test('size variants change font size', () => { render(); const sm = screen.getByText('Small'); const lg = screen.getByText('Large'); expect(Number(sm.style.fontSize.replace('px', ''))).toBeLessThan(Number(lg.style.fontSize.replace('px', ''))); }); }); describe('SearchInput demo', () => { test('search icon is visible', () => { render(); const input = screen.getByPlaceholderText('Search accounts'); const container = input.parentElement as HTMLElement; expect(container.querySelector('svg')).toBeTruthy(); }); test('clear button appears when input has value', async () => { const user = userEvent.setup(); render(); const input = screen.getByPlaceholderText('Search accounts') as HTMLInputElement; await user.type(input, 'Query'); const container = input.parentElement as HTMLElement; expect(container.querySelector('button')).toBeTruthy(); }); test('clear button clears input value', async () => { const user = userEvent.setup(); render(); const input = screen.getByPlaceholderText('Search accounts') as HTMLInputElement; await user.type(input, 'Query'); const container = input.parentElement as HTMLElement; const clearButton = container.querySelector('button') as HTMLButtonElement; await user.click(clearButton); expect(input.value).toBe(''); }); test('loading state shows spinner instead of clear button', () => { render(); const input = screen.getByPlaceholderText('Loading results'); const container = input.parentElement as HTMLElement; expect(container.querySelector('button')).toBeNull(); expect(container.querySelector('svg')).toBeTruthy(); }); test('size variants render at different scales', () => { render(); const sm = screen.getByPlaceholderText('Small') as HTMLInputElement; const lg = screen.getByPlaceholderText('Large') as HTMLInputElement; expect(Number(sm.style.height.replace('px', ''))).toBeLessThan(Number(lg.style.height.replace('px', ''))); }); test('debounce delays onSearch callback', async () => { vi.useFakeTimers(); const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime }); render(); const input = screen.getByPlaceholderText('Debounced search') as HTMLInputElement; await user.type(input, 'abc'); expect(screen.getByText(/Debounced value: —/)).toBeInTheDocument(); vi.advanceTimersByTime(400); await waitFor(() => { expect(screen.getByText(/Debounced value: abc/)).toBeInTheDocument(); }); vi.useRealTimers(); }); }); describe('Tabs demo', () => { test('default variant shows border-bottom indicator', () => { render(); const active = screen.getAllByRole('button', { name: 'Overview' })[0]; expect(active.style.borderBottom).toContain('var(--alga-primary'); }); test('underline variant shows underline indicator', () => { render(); const active = screen.getAllByRole('button', { name: 'Overview' })[1]; expect(active.style.borderBottom).toContain('var(--alga-primary'); }); test('disabled tab cannot be selected', async () => { render(); const disabled = screen.getAllByRole('button', { name: 'Settings' })[0] as HTMLButtonElement; expect(disabled.disabled).toBe(true); }); test('tab content changes when tab is selected', async () => { const user = userEvent.setup(); render(); const details = screen.getAllByRole('button', { name: 'Details' })[0]; await user.click(details); expect(screen.getByText('Details content')).toBeInTheDocument(); }); }); describe('Drawer demo', () => { test('drawer opens from right side', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Default (400px)' })); const drawer = screen.getByRole('dialog'); expect(drawer.style.right).toBe('0px'); expect(drawer.style.width).toBe('400px'); }); test('different widths change drawer width', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Narrow (300px)' })); const drawer = screen.getByRole('dialog'); expect(drawer.style.width).toBe('300px'); }); test('title is displayed in drawer header', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Default (400px)' })); expect(screen.getByText(/Drawer \(400px\)/)).toBeInTheDocument(); }); test('close button closes drawer', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Default (400px)' })); await user.click(screen.getByRole('button', { name: 'Close drawer' })); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); test('escape key closes drawer', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Default (400px)' })); await user.keyboard('{Escape}'); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); test('overlay click closes drawer', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Default (400px)' })); const overlay = document.querySelector('[aria-hidden="true"]') as HTMLElement; await user.click(overlay); expect(screen.queryByRole('dialog')).not.toBeInTheDocument(); }); }); describe('DropdownMenu demo', () => { test('menu opens on trigger click', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Open Menu' })); expect(screen.getByRole('menu')).toBeInTheDocument(); }); test('menu items are clickable', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Open Menu' })); await user.click(screen.getByText('New item')); expect(screen.getByText(/Last action: New item/)).toBeInTheDocument(); }); test('dividers separate menu sections', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Open Menu' })); const menu = screen.getByRole('menu'); expect(menu.querySelectorAll('div').length).toBeGreaterThan(1); }); test('disabled items are not clickable', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Open Menu' })); await user.click(screen.getByText('Disabled action')); expect(screen.queryByText(/Last action: Disabled action/)).not.toBeInTheDocument(); }); test('danger items have red text', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Open Menu' })); const danger = screen.getByText('Delete item'); expect(danger.style.color).toContain('var(--alga-danger)'); }); test('right-aligned menu aligns to right edge of trigger', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Right Align' })); const menu = screen.getByRole('menu'); expect(menu.style.right).toBe('0px'); }); test('menu closes on item click', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Open Menu' })); await user.click(screen.getByText('New item')); expect(screen.queryByRole('menu')).not.toBeInTheDocument(); }); test('menu closes on outside click', async () => { const user = userEvent.setup(); render(); await user.click(screen.getByRole('button', { name: 'Open Menu' })); await user.click(document.body); expect(screen.queryByRole('menu')).not.toBeInTheDocument(); }); });