import { describe, it, expect, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; import { Tabs } from '@/components/Tabs'; const ITEMS = [ { id: 'schedule', label: 'Schedule' }, { id: 'description', label: 'Description' }, { id: 'simulations', label: 'Simulations', count: 5 }, ]; describe('Tabs', () => { it('renders all tab labels', () => { render(); expect(screen.getByText('Schedule')).toBeInTheDocument(); expect(screen.getByText('Description')).toBeInTheDocument(); expect(screen.getByText('Simulations')).toBeInTheDocument(); }); it('marks the active tab with aria-selected=true', () => { render(); const descBtn = screen.getByRole('tab', { name: /Description/i }); expect(descBtn).toHaveAttribute('aria-selected', 'true'); const schedBtn = screen.getByRole('tab', { name: /Schedule/i }); expect(schedBtn).toHaveAttribute('aria-selected', 'false'); }); it('applies tab-underline-active class to the active tab', () => { render(); const activeBtn = screen.getByRole('tab', { name: /Schedule/i }); expect(activeBtn).toHaveClass('tab-underline-active'); }); it('renders count pill for the simulations tab', () => { render(); const pill = screen.getByText('5'); expect(pill).toHaveClass('tab-count-pill'); }); it('applies tab-count-pill-active when the tab with count is active', () => { render(); const pill = screen.getByText('5'); expect(pill).toHaveClass('tab-count-pill-active'); }); it('calls onChange with the tab id when clicked', () => { const onChange = vi.fn(); render(); fireEvent.click(screen.getByRole('tab', { name: /Description/i })); expect(onChange).toHaveBeenCalledWith('description'); }); it('sets aria-controls and id on each tab button', () => { render(); const schedBtn = screen.getByRole('tab', { name: /Schedule/i }); expect(schedBtn).toHaveAttribute('id', 'tab-schedule'); expect(schedBtn).toHaveAttribute('aria-controls', 'tabpanel-schedule'); const simsBtn = screen.getByRole('tab', { name: /Simulations/i }); expect(simsBtn).toHaveAttribute('id', 'tab-simulations'); expect(simsBtn).toHaveAttribute('aria-controls', 'tabpanel-simulations'); }); it('ArrowRight moves focus to the next tab', () => { const onChange = vi.fn(); render(); fireEvent.keyDown(screen.getByRole('tab', { name: /Schedule/i }), { key: 'ArrowRight' }); expect(onChange).toHaveBeenCalledWith('description'); }); it('ArrowLeft wraps around to the last tab', () => { const onChange = vi.fn(); render(); fireEvent.keyDown(screen.getByRole('tab', { name: /Schedule/i }), { key: 'ArrowLeft' }); expect(onChange).toHaveBeenCalledWith('simulations'); }); it('has no rounded-md, transition-*, or shadow-* on tab buttons (brutalism)', () => { render(); for (const btn of screen.getAllByRole('tab')) { expect(btn).not.toHaveClass('rounded-md'); expect(btn.className).not.toMatch(/transition-/); expect(btn.className).not.toMatch(/shadow-/); } }); }); describe('Tabs — disabled state', () => { const ITEMS_WITH_DISABLED = [ { id: 'a', label: 'Alpha' }, { id: 'b', label: 'Beta', disabled: true }, { id: 'c', label: 'Gamma' }, ]; it('click on disabled tab does not fire onChange', () => { const onChange = vi.fn(); render(); fireEvent.click(screen.getByRole('tab', { name: /Beta/i })); expect(onChange).not.toHaveBeenCalled(); }); it('has aria-disabled="true" on disabled tab', () => { render(); const btn = screen.getByRole('tab', { name: /Beta/i }); expect(btn).toHaveAttribute('aria-disabled', 'true'); }); it('applies tab-underline-disabled class on disabled tab', () => { render(); const btn = screen.getByRole('tab', { name: /Beta/i }); expect(btn).toHaveClass('tab-underline-disabled'); }); it('ArrowRight skips disabled tab (a → c, skipping b)', () => { const onChange = vi.fn(); render(); fireEvent.keyDown(screen.getByRole('tab', { name: /Alpha/i }), { key: 'ArrowRight' }); expect(onChange).toHaveBeenCalledWith('c'); }); it('ArrowLeft skips disabled tab (c → a, skipping b)', () => { const onChange = vi.fn(); render(); fireEvent.keyDown(screen.getByRole('tab', { name: /Gamma/i }), { key: 'ArrowLeft' }); expect(onChange).toHaveBeenCalledWith('a'); }); });