import * as React from 'react' import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { describe, it, expect, vi } from 'vitest' import { axe } from 'vitest-axe' import { Tabs, TabsList, TabsTrigger, TabsContent } from './tabs' function Demo( props: Omit, 'children'> & { secondDisabled?: boolean onTabClick?: (value: string) => void } = {}, ) { const { secondDisabled = false, onTabClick, ...tabsProps } = props return ( onTabClick?.('one')}> One onTabClick?.('two')}> Two onTabClick?.('three')}> Three Panel One Panel Two Panel Three ) } describe('Tabs', () => { it('renders composition with default-active panel visible', () => { render() expect(screen.getByRole('tablist')).toHaveAttribute('data-slot', 'tabs-list') const triggers = screen.getAllByRole('tab') expect(triggers).toHaveLength(3) expect(triggers[0]).toHaveAttribute('aria-selected', 'true') expect(triggers[1]).toHaveAttribute('aria-selected', 'false') expect(screen.getByText('Panel One')).toBeVisible() // Inactive panels are unmounted by default (`keepMounted` is false). expect(screen.queryByText('Panel Two')).toBeNull() }) it('keeps inactive panels in the DOM when keepMounted is set on TabsContent', () => { render( One Two Panel One Panel Two , ) const inactive = screen.getByText('Panel Two') expect(inactive).toBeInTheDocument() expect(inactive).not.toBeVisible() }) it('switches the active panel on click', async () => { const user = userEvent.setup() render() await user.click(screen.getByRole('tab', { name: 'Two' })) expect(screen.getByRole('tab', { name: 'Two' })).toHaveAttribute('aria-selected', 'true') expect(screen.getByRole('tab', { name: 'One' })).toHaveAttribute('aria-selected', 'false') expect(screen.getByText('Panel Two')).toBeVisible() }) it('honors defaultValue', () => { render() expect(screen.getByRole('tab', { name: 'Three' })).toHaveAttribute('aria-selected', 'true') expect(screen.getByText('Panel Three')).toBeVisible() }) it('supports controlled mode', async () => { const user = userEvent.setup() const onValueChange = vi.fn() function Controlled() { const [value, setValue] = React.useState('one') return ( { onValueChange(next) setValue(next as string) }} /> ) } render() expect(screen.getByRole('tab', { name: 'One' })).toHaveAttribute('aria-selected', 'true') await user.click(screen.getByRole('tab', { name: 'Two' })) expect(onValueChange).toHaveBeenCalledWith('two') expect(screen.getByRole('tab', { name: 'Two' })).toHaveAttribute('aria-selected', 'true') }) it('navigates horizontally via Arrow keys and activates on Enter', async () => { const user = userEvent.setup() render() const first = screen.getByRole('tab', { name: 'One' }) first.focus() await user.keyboard('{ArrowRight}') expect(screen.getByRole('tab', { name: 'Two' })).toHaveFocus() await user.keyboard('{Enter}') expect(screen.getByRole('tab', { name: 'Two' })).toHaveAttribute('aria-selected', 'true') }) it('navigates vertically via ArrowDown when orientation="vertical"', async () => { const user = userEvent.setup() render() expect(screen.getByRole('tablist')).toHaveAttribute('aria-orientation', 'vertical') const first = screen.getByRole('tab', { name: 'One' }) first.focus() await user.keyboard('{ArrowDown}') expect(screen.getByRole('tab', { name: 'Two' })).toHaveFocus() }) it('does not activate disabled triggers on click', async () => { const user = userEvent.setup() const onTabClick = vi.fn() render() await user.click(screen.getByRole('tab', { name: 'Two' })) expect(onTabClick).not.toHaveBeenCalled() expect(screen.getByRole('tab', { name: 'One' })).toHaveAttribute('aria-selected', 'true') }) it('applies data-slot attributes on each sub-component', () => { render() expect(screen.getByRole('tablist').closest('[data-slot="tabs"]')).not.toBeNull() expect(screen.getByRole('tablist')).toHaveAttribute('data-slot', 'tabs-list') expect(screen.getAllByRole('tab')[0]).toHaveAttribute('data-slot', 'tabs-trigger') expect(screen.getByText('Panel One').closest('[data-slot="tabs-content"]')).not.toBeNull() }) it('throws when a sub-component is rendered outside ', () => { const spy = vi.spyOn(console, 'error').mockImplementation(() => {}) expect(() => render(x)).toThrow(/must be rendered inside /) spy.mockRestore() }) it('has no accessibility violations', async () => { const { container } = render() expect(await axe(container)).toHaveNoViolations() }) })