import { isInaccessible } from '@testing-library/react'
import userEvent, { PointerEventsCheckLevel } from '@testing-library/user-event'
import { render } from '~/src/utils/test'
import {
TabAction,
TabActions,
TabContent,
TabItem,
TabItems,
TabList,
Tabs,
} from './Tabs'
import { type TabListProps, type TabsProps } from './Tabs.types'
const VALUE1 = 'One'
const VALUE2 = 'Two'
const TAB1 = 'Tab1'
const TAB2 = 'Tab2'
const CONTENT1 = 'Content one'
const CONTENT2 = 'Content two'
const ACTION1 = 'Action1'
const ACTION2 = 'Action2'
type RenderTabsProps = {
tabsProps?: TabsProps
tabListProps?: TabListProps
}
describe('Tabs', () => {
const renderTabs = (
{ tabsProps, tabListProps }: RenderTabsProps = {
tabListProps: {
size: 'm',
},
}
) =>
render(
{TAB1}
{TAB2}
{ACTION1}
{ACTION2}
{CONTENT1}
{CONTENT2}
)
let user: ReturnType
beforeEach(() => {
user = userEvent.setup({
pointerEventsCheck: PointerEventsCheckLevel.Never,
})
})
describe('Accessibility check > ', () => {
it('element rendered by Tabs component should be accessible', () => {
const { container } = renderTabs()
expect(isInaccessible(container)).toBe(false)
})
})
describe('Tab List', () => {
describe('ARIA', () => {
it('should have \'role="tablist"\' attribute.', () => {
const { getByRole } = renderTabs()
expect(getByRole('tablist')).toBeInTheDocument()
})
it('should have \'aria-orientation="horizontal"\' attribute.', () => {
const { getByRole } = renderTabs()
expect(getByRole('tablist')).toHaveAttribute(
'aria-orientation',
'horizontal'
)
})
})
describe('Data Attributes', () => {
it('should have \'data-orientation="horizontal"\' attribute.', () => {
const { getByRole } = renderTabs()
expect(getByRole('tablist')).toHaveAttribute(
'data-orientation',
'horizontal'
)
})
})
it('should call onValueChange handler when user clicks different tabs', async () => {
const onValueChange = jest.fn()
const { getByRole } = renderTabs({
tabsProps: {
defaultValue: TAB1,
onValueChange,
},
})
const tab1 = getByRole('tab', { name: TAB1 })
const tab2 = getByRole('tab', { name: TAB2 })
await user.click(tab2)
expect(onValueChange).toHaveBeenCalledTimes(1)
await user.click(tab1)
expect(onValueChange).toHaveBeenCalledTimes(2)
await user.click(tab1)
expect(onValueChange).toHaveBeenCalledTimes(2)
})
})
describe('Tab Item', () => {
describe('ARIA', () => {
it('should have \'role="tab"\' attribute.', () => {
const { getByRole } = renderTabs()
expect(getByRole('tab', { name: TAB1 })).toBeInTheDocument()
})
it('should have \'aria-selected="true"\' attribute if selected.', () => {
const { getByRole } = renderTabs()
expect(getByRole('tab', { name: TAB1 })).toHaveAttribute(
'aria-selected',
'true'
)
})
it('should have \'aria-selected="false"\' attribute if not selected.', () => {
const { getByRole } = renderTabs()
expect(getByRole('tab', { name: TAB2 })).toHaveAttribute(
'aria-selected',
'false'
)
})
it("should have 'aria-controls' attribute the same as its associated tab content's id", () => {
const { getByRole } = renderTabs()
expect(getByRole('tab', { name: TAB1 })).toHaveAttribute(
'aria-controls',
getByRole('tabpanel', { name: TAB1 }).id
)
})
})
describe('Data Attributes', () => {
it("should have proper 'data-state' attribute.", () => {
const { getByRole } = renderTabs()
expect(getByRole('tab', { name: TAB1 })).toHaveAttribute(
'data-state',
'active'
)
expect(getByRole('tab', { name: TAB2 })).toHaveAttribute(
'data-state',
'inactive'
)
})
})
describe('Keyboard Navigation', () => {
const assertTab1IsActive = (
getByRole: ReturnType['getByRole']
) => {
expect(getByRole('tab', { name: TAB1 })).toHaveAttribute(
'data-state',
'active'
)
expect(getByRole('tab', { name: TAB2 })).toHaveAttribute(
'data-state',
'inactive'
)
expect(getByRole('tabpanel', { name: TAB1 })).toHaveAttribute(
'data-state',
'active'
)
}
const assertTab2IsActive = (
getByRole: ReturnType['getByRole']
) => {
expect(getByRole('tab', { name: TAB1 })).toHaveAttribute(
'data-state',
'inactive'
)
expect(getByRole('tab', { name: TAB2 })).toHaveAttribute(
'data-state',
'active'
)
expect(getByRole('tabpanel', { name: TAB2 })).toHaveAttribute(
'data-state',
'active'
)
}
it('can control by arrow right and left key (on automatic activation mode)', async () => {
const { getByRole } = renderTabs()
await user.click(getByRole('tab', { name: TAB1 }))
assertTab1IsActive(getByRole)
await user.keyboard('{arrowright}')
assertTab2IsActive(getByRole)
await user.keyboard('{arrowright}')
assertTab1IsActive(getByRole)
await user.keyboard('{arrowleft}')
assertTab2IsActive(getByRole)
await user.keyboard('{arrowleft}')
assertTab1IsActive(getByRole)
})
it('can control by arrow right and left key with space or enter key (on manual activation mode)', async () => {
const { getByRole } = renderTabs({
tabsProps: { activationMode: 'manual' },
})
await user.click(getByRole('tab', { name: TAB1 }))
assertTab1IsActive(getByRole)
await user.keyboard('{arrowright}')
assertTab1IsActive(getByRole)
await user.keyboard(' ')
assertTab2IsActive(getByRole)
await user.keyboard('{arrowright}')
assertTab2IsActive(getByRole)
await user.keyboard('{enter}')
assertTab1IsActive(getByRole)
await user.keyboard('{arrowleft}')
assertTab1IsActive(getByRole)
await user.keyboard('{enter}')
assertTab2IsActive(getByRole)
})
})
it('can move focus by tab between tab item, tab action, and tab content.', async () => {
const { getByRole } = renderTabs()
const tabItem1 = getByRole('tab', { name: TAB1 })
const tabItem2 = getByRole('tab', { name: TAB2 })
const tabAction = getByRole('link')
const tabContent = getByRole('tabpanel', { name: TAB1 })
await user.click(getByRole('tab', { name: TAB1 }))
expect(document.activeElement).toBe(tabItem1)
await user.tab()
expect(document.activeElement).toBe(tabAction)
await user.tab()
expect(document.activeElement).toBe(tabContent)
await user.tab({ shift: true })
expect(document.activeElement).toBe(tabAction)
await user.tab({ shift: true })
expect(document.activeElement).toBe(tabItem1)
await user.keyboard('{end}')
expect(document.activeElement).toBe(tabItem2)
await user.keyboard('{home}')
expect(document.activeElement).toBe(tabItem1)
})
})
describe('Tab Actions', () => {
describe('ARIA', () => {
it('should have \'role="toolbar"\' attribute.', () => {
const { getByRole } = renderTabs()
expect(
getByRole('toolbar', { name: 'More actions' })
).toBeInTheDocument()
})
})
describe('Keyboard Navigation', () => {
it('can control by arrow right and left key', async () => {
const { getByRole } = renderTabs()
const tabItem1 = getByRole('link')
const tabItem2 = getByRole('button', { name: ACTION2 })
await user.click(getByRole('link'))
expect(document.activeElement).toBe(tabItem1)
await user.keyboard('{arrowright}')
expect(document.activeElement).toBe(tabItem2)
await user.keyboard('{arrowleft}')
expect(document.activeElement).toBe(tabItem1)
await user.keyboard('{arrowleft}')
expect(document.activeElement).toBe(tabItem2)
})
})
})
describe('Tab Content', () => {
describe('ARIA', () => {
it('should have \'role="tabpanel"\' attribute.', () => {
const { getByRole } = renderTabs()
expect(getByRole('tabpanel')).toBeInTheDocument()
})
})
describe('Data Attributes', () => {
it('should have \'data-state\'="active" when associated tab is selected.', () => {
const { getByRole } = renderTabs()
expect(getByRole('tabpanel', { name: TAB1 })).toHaveAttribute(
'data-state',
'active'
)
})
})
})
})