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' ) }) }) }) })