import React from 'react'
import {
fireEvent,
getAllByRole,
getByRole,
queryByRole,
render,
screen,
} from '@testing-library/react'
import { Tab, Tabs, TabsProps } from './Tabs'
const defaultProps: TabsProps = {
activeTabId: 'tab1',
tabs: [
{
id: 'tab1',
label: 'Tab 1',
panelContent:
Content for first tab
,
},
{
id: 'tab2',
label: 'Tab 2',
panelContent: Content for second tab
,
},
{
id: 'tab3',
label: 'Tab 3',
panelContent: Content for third tab3
,
},
],
}
function createProps(props: Partial = {}): TabsProps {
return { ...defaultProps, ...props }
}
describe('Rendering testing', () => {
it('should render the tabs', () => {
const props = createProps()
render()
expect(screen.getAllByRole('tab')).toHaveLength(3)
props.tabs.forEach((tab: Tab) => {
expect(screen.getByRole('tab', { name: tab.label })).toHaveTextContent(tab.label)
})
})
it('should render the active tab content', () => {
const props = createProps()
render()
expect(screen.getAllByRole('tabpanel')).toHaveLength(1)
expect(screen.getByRole('tabpanel', { name: props.tabs[0].label })).toHaveTextContent(
'Content for first tab',
)
})
it('should render the icon and label', () => {
const props: TabsProps = {
activeTabId: '1',
tabs: [
{
id: '1',
label: 'With icon',
panelContent: ,
icon: ,
},
],
}
render()
const tab = screen.getByRole('tab', { name: 'With icon' })
expect(tab).toHaveTextContent('With icon')
expect(getByRole(tab, 'img', { name: 'image' })).toBeInTheDocument()
})
it('should only render the icon when showIconOnly is true', () => {
const props: TabsProps = {
activeTabId: '1',
tabs: [
{
id: '1',
label: 'With icon',
panelContent: ,
icon: ,
showIconOnly: true,
},
],
}
render()
const tab = screen.getByRole('tab', { name: 'With icon' })
expect(tab).toHaveTextContent('')
expect(getByRole(tab, 'img', { name: 'image' })).toBeInTheDocument()
})
it('Should render the second line', () => {
const props: TabsProps = {
activeTabId: '1',
tabs: [
{
id: '1',
label: 'With second line',
panelContent: ,
secondLine: 'Second line',
},
],
}
render()
const tab = screen.getByRole('tab', { name: 'With second line' })
expect(tab).toHaveTextContent('With second lineSecond line')
})
it('Should call onChange when a tab is selected', () => {
const props = createProps({ onChange: jest.fn() })
render()
fireEvent.click(screen.getByRole('tab', { name: 'Tab 2' }))
expect(props.onChange).toHaveBeenCalledWith('tab2')
})
it('Should update the tabpanel when a tab is selected', () => {
const props = createProps()
render()
fireEvent.click(screen.getByRole('tab', { name: props.tabs[1].label }))
expect(screen.getByRole('tabpanel', { name: props.tabs[1].label })).toHaveTextContent(
'Content for second tab',
)
})
})
describe('ARIA roles', () => {
it('Should generate proper ARIA roles', () => {
const props = createProps()
render()
// Verify we have a tablist role element.
const tablist = screen.getByRole('tablist')
expect(tablist).toBeInTheDocument()
// Verify we have correct tabs inside the tablist role element.
const tabs = getAllByRole(tablist, 'tab')
expect(tabs).toHaveLength(3)
expect(tabs[0]).toHaveAttribute('aria-controls', 'tab1_panel')
expect(tabs[1]).toHaveAttribute('aria-controls', 'tab2_panel')
expect(tabs[2]).toHaveAttribute('aria-controls', 'tab3_panel')
// Verify we have one tabpanel.
const tabpanel = screen.getByRole('tabpanel', { name: 'Tab 1' })
// Verify the tabpanel generated id (that should match the aria-controls
// of the selected tab.
expect(tabpanel).toHaveAttribute('id', 'tab1_panel')
// Verify the tabpanel is not in the tablist or in a tab.
expect(queryByRole(tablist, 'tabpanel')).not.toBeInTheDocument()
// // Verify the selected tab ARIA state
expect(screen.getByRole('tab', { name: 'Tab 1', selected: true })).toBeInTheDocument()
})
it('Should update ARIA attributes after switching tab', () => {
const props = createProps()
render()
// Verify that initial tab 1 is selected
const tabs = screen.getAllByRole('tab')
// Verify aria-selected
expect(tabs[0]).toHaveAttribute('aria-selected', 'true')
expect(tabs[1]).toHaveAttribute('aria-selected', 'false')
expect(tabs[2]).toHaveAttribute('aria-selected', 'false')
// Verify tabIndexes
expect(tabs[0]).toHaveAttribute('tabindex', '0')
expect(tabs[1]).toHaveAttribute('tabindex', '-1')
expect(tabs[2]).toHaveAttribute('tabindex', '-1')
// Verify that the current tabpanel id is correct.
expect(screen.getByRole('tabpanel')).toHaveAttribute('id', 'tab1_panel')
// Simulate a click on tab 2.
fireEvent.click(tabs[1])
// Verify that now tab 2 is selected
// Verify aria-selected
expect(tabs[0]).toHaveAttribute('aria-selected', 'false')
expect(tabs[1]).toHaveAttribute('aria-selected', 'true')
expect(tabs[2]).toHaveAttribute('aria-selected', 'false')
// Verify tabIndexes
expect(tabs[0]).toHaveAttribute('tabindex', '-1')
expect(tabs[1]).toHaveAttribute('tabindex', '0')
expect(tabs[2]).toHaveAttribute('tabindex', '-1')
// Verify that the current tabpanel id is correct.
expect(screen.getByRole('tabpanel')).toHaveAttribute('id', 'tab2_panel')
})
})
describe('Keyboard navigation', () => {
it('Should left/right keyboard navigate', () => {
const props = createProps()
render()
screen.getByRole('tab', { name: 'Tab 1', selected: true }).focus()
// Simulate a right arrow keydown.
fireEvent.keyDown(document.activeElement, { key: 'ArrowRight' })
// Tab 2 is now selected.
expect(screen.getByRole('tab', { name: 'Tab 2', selected: true })).toHaveFocus()
// Simulate a right arrow keydown.
fireEvent.keyDown(document.activeElement, { key: 'ArrowRight' })
// Tab 3 is now selected.
expect(screen.getByRole('tab', { name: 'Tab 3', selected: true })).toHaveFocus()
// Simulate a left arrow keydown.
fireEvent.keyDown(document.activeElement, { key: 'ArrowLeft' })
// Tab 2 is now selected.
expect(screen.getByRole('tab', { name: 'Tab 2', selected: true })).toHaveFocus()
})
it('should loop to the last tab when going left starting from the first tab', () => {
const props = createProps()
render()
// Verify that first tab is selected.
screen.getByRole('tab', { name: 'Tab 1', selected: true }).focus()
// Simulate a left arrow keydown.
fireEvent.keyDown(document.activeElement, { key: 'ArrowLeft' })
// Last tab is now selected.
expect(screen.getByRole('tab', { name: 'Tab 3', selected: true })).toHaveFocus()
})
it('should loop to the first tab when going right starting from the last tab', () => {
// Set-up the last tab to be selected ('tab3')
const props = createProps({ activeTabId: 'tab3' })
render()
// Verify that last tab on the right is selected.
screen.getByRole('tab', { name: 'Tab 3', selected: true }).focus()
// Simulate a right arrow keydown.
fireEvent.keyDown(document.activeElement, { key: 'ArrowRight' })
// First tab is now selected.
expect(screen.getByRole('tab', { name: 'Tab 1', selected: true })).toHaveFocus()
})
it('Should do nothing when tabbing', () => {
const props = createProps()
render()
// Verify that first tab is selected.
screen.getByRole('tab', { name: 'Tab 1', selected: true }).focus()
// Simulate a Tab keydown: The focus will just move outside the tablist and not
// select another tab in the tablist.
fireEvent.keyDown(document.activeElement, { key: 'Tab' })
// Verify first tab is still selected.
expect(screen.getByRole('tab', { name: 'Tab 1', selected: true })).toBeInTheDocument()
})
})