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 { Navigation, NavigationList, NavigationItem } from './navigation'
import { type NavigationActiveLink, type NavigationSize } from './navigation-context'
import { NavigationLink } from './navigation-link'
function renderBasic(props?: { activeLink?: NavigationActiveLink; size?: NavigationSize }) {
return render(
Home
About
Contact
,
)
}
describe('Navigation', () => {
it('renders as a with role and accessible name', () => {
renderBasic()
const nav = screen.getByRole('navigation', { name: 'Main' })
expect(nav.tagName).toBe('NAV')
expect(nav).toHaveAttribute('data-slot', 'navigation')
})
it('defaults to horizontal orientation via data-orientation', () => {
renderBasic()
const nav = screen.getByRole('navigation', { name: 'Main' })
expect(nav).not.toHaveAttribute('aria-orientation')
expect(nav).toHaveAttribute('data-orientation', 'horizontal')
})
it('marks vertical orientation with data-orientation', () => {
render(
A
,
)
const nav = screen.getByRole('navigation', { name: 'Side' })
expect(nav).toHaveAttribute('data-orientation', 'vertical')
})
it('renders for the list and for items', () => {
renderBasic()
const list = screen.getByRole('list')
expect(list.tagName).toBe('UL')
expect(list).toHaveAttribute('data-slot', 'navigation-list')
expect(screen.getAllByRole('listitem')).toHaveLength(3)
})
it('activeLink matches the link with the corresponding value', () => {
renderBasic({ activeLink: 'about' })
const active = screen.getByRole('link', { current: 'page' })
expect(active).toHaveTextContent('About')
expect(active).toHaveAttribute('data-active')
})
it('per-link active={true} overrides activeLink mismatch', () => {
render(
About
,
)
const link = screen.getByRole('link', { name: 'About' })
expect(link).toHaveAttribute('aria-current', 'page')
})
it('per-link active={false} overrides activeLink match', () => {
render(
Home
,
)
const link = screen.getByRole('link', { name: 'Home' })
expect(link).not.toHaveAttribute('aria-current')
expect(link).not.toHaveAttribute('data-active')
})
it('disabled links carry aria-disabled and data-disabled', () => {
render(
Disabled
,
)
const link = screen.getByRole('link', { name: 'Disabled' })
expect(link).toHaveAttribute('aria-disabled', 'true')
expect(link).toHaveAttribute('data-disabled')
})
it('forwards className alongside variant classes on the link', () => {
render(
One
,
)
const link = screen.getByRole('link', { name: 'One' })
expect(link.className).toContain('my-link')
})
it('renders the link as a different element via render prop', () => {
const onClick = vi.fn()
render(
}>Btn
,
)
const el = screen.getByRole('button', { name: 'Btn' })
expect(el.tagName).toBe('BUTTON')
})
it('fires onClick on the link', async () => {
const user = userEvent.setup()
const onClick = vi.fn()
render(
Click
,
)
await user.click(screen.getByRole('link', { name: 'Click' }))
expect(onClick).toHaveBeenCalledOnce()
})
it('renders NavigationLink standalone without a Navigation root', () => {
render(Solo )
const link = screen.getByRole('link', { name: 'Solo' })
expect(link).toHaveAttribute('data-slot', 'navigation-link')
})
it('indicator variant wraps children in a label slot and renders an indicator slot', () => {
render(
One
,
)
const link = screen.getByRole('link', { name: 'One' })
expect(link.querySelector('[data-slot="navigation-link-indicator"]')).not.toBeNull()
const label = link.querySelector('[data-slot="navigation-link-label"]')
expect(label).not.toBeNull()
expect(label).toHaveTextContent('One')
})
it('indicator variant accepts a custom indicator node', () => {
render(
}>
Item
,
)
expect(screen.getByTestId('custom-indicator')).toBeInTheDocument()
})
it('stamps data-orientation="horizontal" on links by default', () => {
renderBasic()
for (const link of screen.getAllByRole('link')) {
expect(link).toHaveAttribute('data-orientation', 'horizontal')
}
})
it('propagates vertical orientation to links via context', () => {
render(
A
,
)
expect(screen.getByRole('link', { name: 'A' })).toHaveAttribute('data-orientation', 'vertical')
})
it('NavigationLink orientation prop overrides context', () => {
render(
A
,
)
expect(screen.getByRole('link', { name: 'A' })).toHaveAttribute('data-orientation', 'vertical')
})
it('has no accessibility violations', async () => {
const { container } = renderBasic({ activeLink: 'home' })
expect(await axe(container)).toHaveNoViolations()
})
})