import { act, render } from "@testing-library/svelte"; import { suppressConsoleLogs } from "$lib/test-utils/suppress-console-logs"; import TestRenderer from "$lib/test-utils/TestRenderer.svelte"; import { Tab, TabGroup, TabList, TabPanel, TabPanels } from "."; import { assertActiveElement, assertTabs, getByText, getTabs } from "$lib/test-utils/accessibility-assertions"; import { click, Keys, press, shift } from "$lib/test-utils/interactions"; import Button from "$lib/internal/elements/Button.svelte"; import svelte from "svelte-inline-compile"; import { writable } from "svelte/store"; let mockId = 0; jest.mock('../../hooks/use-id', () => { return { useId: jest.fn(() => ++mockId), } }) beforeEach(() => mockId = 0) beforeAll(() => { // jest.spyOn(window, 'requestAnimationFrame').mockImplementation(setImmediate as any) // jest.spyOn(window, 'cancelAnimationFrame').mockImplementation(clearImmediate as any) }) afterAll(() => jest.restoreAllMocks()) describe('safeguards', () => { it.each([ ['TabList', TabList], ['Tab', Tab], ['TabPanels', TabPanels], ['TabPanel', TabPanel], ])( 'should error when we are using a <%s /> without a parent component', suppressConsoleLogs((name, Component) => { expect(() => render(Component)).toThrowError( `<${name} /> is missing a parent component.` ) }) ) it('should be possible to render TabGroup without crashing', async () => { render( TestRenderer, { allProps: [ [TabGroup, {}, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], ] }) assertTabs({ active: 0 }) }) }) describe('Rendering', () => { it('should be possible to render the TabPanels first, then the TabList', async () => { render( TestRenderer, { allProps: [ [TabGroup, {}, [ [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], ]], ] }) assertTabs({ active: 0 }) }) it('should guarantee the order of DOM nodes when performing actions', async () => { render(svelte` Tab 1 {#if !hide} Tab 2 {/if} Tab 3 Content 1 {#if !hide} Content 2 {/if} Content 3 `) await click(getByText('toggle')) // Remove Tab 2 await click(getByText('toggle')) // Re-add Tab 2 await press(Keys.Tab) assertTabs({ active: 0 }) await press(Keys.ArrowRight) assertTabs({ active: 1 }) await press(Keys.ArrowRight) assertTabs({ active: 2 }) }) describe('`slot props`', () => { it('should expose the `selectedIndex` on the `TabGroup` component', async () => { render(svelte`
{JSON.stringify({ selectedIndex })}
Tab 1 Tab 2 Tab 3 Content 1 Content 2 Content 3
`) expect(document.getElementById('exposed')).toHaveTextContent( JSON.stringify({ selectedIndex: 0 }) ) await click(getByText('Tab 2')) expect(document.getElementById('exposed')).toHaveTextContent( JSON.stringify({ selectedIndex: 1 }) ) }) it('should expose the `selectedIndex` on the `TabList` component', async () => { render(svelte`
{ JSON.stringify({ selectedIndex }) }
Tab 1 Tab 2 Tab 3
Content 1 Content 2 Content 3
`) expect(document.getElementById('exposed')).toHaveTextContent( JSON.stringify({ selectedIndex: 0 }) ) await click(getByText('Tab 2')) expect(document.getElementById('exposed')).toHaveTextContent( JSON.stringify({ selectedIndex: 1 }) ) }) it('should expose the `selectedIndex` on the `TabPanels` component', async () => { render(svelte` Tab 1 Tab 2 Tab 3
{ JSON.stringify({ selectedIndex }) }
Content 1 Content 2 Content 3
`) expect(document.getElementById('exposed')).toHaveTextContent( JSON.stringify({ selectedIndex: 0 }) ) await click(getByText('Tab 2')) expect(document.getElementById('exposed')).toHaveTextContent( JSON.stringify({ selectedIndex: 1 }) ) }) it('should expose the `selected` state on the `Tab` components', async () => { render(svelte`
{JSON.stringify({selected})}
Tab 1
{JSON.stringify({selected})}
Tab 2
{JSON.stringify({selected})}
Tab 3
Content 1 Content 2 Content 3
`) expect(document.querySelector('[data-tab="0"]')).toHaveTextContent( JSON.stringify({ selected: true }) ) expect(document.querySelector('[data-tab="1"]')).toHaveTextContent( JSON.stringify({ selected: false }) ) expect(document.querySelector('[data-tab="2"]')).toHaveTextContent( JSON.stringify({ selected: false }) ) await click(getTabs()[1]) expect(document.querySelector('[data-tab="0"]')).toHaveTextContent( JSON.stringify({ selected: false }) ) expect(document.querySelector('[data-tab="1"]')).toHaveTextContent( JSON.stringify({ selected: true }) ) expect(document.querySelector('[data-tab="2"]')).toHaveTextContent( JSON.stringify({ selected: false }) ) }) it('should expose the `selected` state on the `TabPanel` components', async () => { render(svelte` Tab 1 Tab 2 Tab 3
{JSON.stringify({ selected })}
Content 1
{JSON.stringify({ selected })}
Content 2
{JSON.stringify({ selected })}
Content 3
`) expect(document.querySelector('[data-panel="0"]')).toHaveTextContent( JSON.stringify({ selected: true }) ) expect(document.querySelector('[data-panel="1"]')).toHaveTextContent( JSON.stringify({ selected: false }) ) expect(document.querySelector('[data-panel="2"]')).toHaveTextContent( JSON.stringify({ selected: false }) ) await click(getByText('Tab 2')) expect(document.querySelector('[data-panel="0"]')).toHaveTextContent( JSON.stringify({ selected: false }) ) expect(document.querySelector('[data-panel="1"]')).toHaveTextContent( JSON.stringify({ selected: true }) ) expect(document.querySelector('[data-panel="2"]')).toHaveTextContent( JSON.stringify({ selected: false }) ) }) }) describe('`defaultIndex`', () => { it('should jump to the nearest tab when the defaultIndex is out of bounds (-2)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: -2 }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0 }) assertActiveElement(getByText('Tab 1')) }) it('should jump to the nearest tab when the defaultIndex is out of bounds (+5)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 5 }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 2 }) assertActiveElement(getByText('Tab 3')) }) it('should jump to the next available tab when the defaultIndex is a disabled tab', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 0 }, [ [TabList, {}, [ [Tab, { disabled: true }, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 1 }) assertActiveElement(getByText('Tab 2')) }) it('should jump to the next available tab when the defaultIndex is a disabled tab and wrap around', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 2 }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, { disabled: true }, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0 }) assertActiveElement(getByText('Tab 1')) }) }) describe(`'Tab'`, () => { describe('`type` attribute', () => { it('should set the `type` to "button" by default', async () => { render( TestRenderer, { allProps: [ [TabGroup, {}, [ [TabList, {}, [ [Tab, {}, "Trigger"], ]], ]], ] }) expect(getTabs()[0]).toHaveAttribute('type', 'button') }) it('should not set the `type` to "button" if it already contains a `type`', async () => { render( TestRenderer, { allProps: [ [TabGroup, {}, [ [TabList, {}, [ [Tab, { type: "submit" }, "Trigger"], ]], ]], ] }) expect(getTabs()[0]).toHaveAttribute('type', 'submit') }) it('should not set the type if the "as" prop is not a "button"', async () => { render( TestRenderer, { allProps: [ [TabGroup, {}, [ [TabList, {}, [ [Tab, { as: "div" }, "Trigger"], ]], ]], ] }) expect(getTabs()[0]).not.toHaveAttribute('type') }) }) it('should guarantee the tab order after a few unmounts', async () => { let showFirst = writable(false); render(svelte` {#if $showFirst} Tab 1 {/if} Tab 2 Tab 3 `) let tabs = getTabs() expect(tabs).toHaveLength(2) // Make the first tab active await press(Keys.Tab) // Verify that the first tab is active assertTabs({ active: 0 }) // Now add a new tab dynamically await act(() => showFirst.set(true)); // New tab should be treated correctly tabs = getTabs() expect(tabs).toHaveLength(3) // Active tab should now be second assertTabs({ active: 1 }) // We should be able to go to the first tab await press(Keys.Home) assertTabs({ active: 0 }) // And the last one await press(Keys.End) assertTabs({ active: 2 }) }) }) }) describe('Keyboard interactions', () => { describe('`Tab` key', () => { it('should be possible to tab to the default initial first tab', async () => { render( TestRenderer, { allProps: [ [TabGroup, {}, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0 }) assertActiveElement(getByText('Tab 1')) await press(Keys.Tab) assertActiveElement(getByText('Content 1')) await press(Keys.Tab) assertActiveElement(getByText('after')) await press(shift(Keys.Tab)) assertActiveElement(getByText('Content 1')) await press(shift(Keys.Tab)) assertActiveElement(getByText('Tab 1')) }) it('should be possible to tab to the default index tab', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 1 }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 1 }) assertActiveElement(getByText('Tab 2')) await press(Keys.Tab) assertActiveElement(getByText('Content 2')) await press(Keys.Tab) assertActiveElement(getByText('after')) await press(shift(Keys.Tab)) assertActiveElement(getByText('Content 2')) await press(shift(Keys.Tab)) assertActiveElement(getByText('Tab 2')) }) }) describe('`ArrowRight` key', () => { it('should be possible to go to the next item (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, {}, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0 }) await press(Keys.ArrowRight) assertTabs({ active: 1 }) await press(Keys.ArrowRight) assertTabs({ active: 2 }) }) it('should be possible to go to the next item (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0 }) await press(Keys.ArrowRight) assertTabs({ active: 0 }) await press(Keys.Enter) assertTabs({ active: 1 }) await press(Keys.ArrowRight) assertTabs({ active: 1 }) await press(Keys.Enter) assertTabs({ active: 2 }) }) it('should wrap around at the end (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, {}, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0 }) await press(Keys.ArrowRight) assertTabs({ active: 1 }) await press(Keys.ArrowRight) assertTabs({ active: 2 }) await press(Keys.ArrowRight) assertTabs({ active: 0 }) await press(Keys.ArrowRight) assertTabs({ active: 1 }) }) it('should wrap around at the end (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0 }) await press(Keys.ArrowRight) assertTabs({ active: 0 }) await press(Keys.Enter) assertTabs({ active: 1 }) await press(Keys.ArrowRight) assertTabs({ active: 1 }) await press(Keys.Enter) assertTabs({ active: 2 }) await press(Keys.ArrowRight) assertTabs({ active: 2 }) await press(Keys.Enter) assertTabs({ active: 0 }) await press(Keys.ArrowRight) assertTabs({ active: 0 }) await press(Keys.Enter) assertTabs({ active: 1 }) }) it('should not be possible to go right when in vertical mode (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { vertical: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.ArrowRight) // no-op assertTabs({ active: 0, orientation: 'vertical' }) }) it('should not be possible to go right when in vertical mode (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { vertical: true, manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.ArrowRight) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.Enter) // no-op assertTabs({ active: 0, orientation: 'vertical' }) }) }) describe('`ArrowLeft` key', () => { it('should be possible to go to the previous item (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 2 }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 2 }) await press(Keys.ArrowLeft) assertTabs({ active: 1 }) await press(Keys.ArrowLeft) assertTabs({ active: 0 }) }) it('should be possible to go to the previous item (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 2, manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 2 }) await press(Keys.ArrowLeft) assertTabs({ active: 2 }) await press(Keys.Enter) assertTabs({ active: 1 }) await press(Keys.ArrowLeft) assertTabs({ active: 1 }) await press(Keys.Enter) assertTabs({ active: 0 }) }) it('should wrap around at the beginning (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 2 }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 2 }) await press(Keys.ArrowLeft) assertTabs({ active: 1 }) await press(Keys.ArrowLeft) assertTabs({ active: 0 }) await press(Keys.ArrowLeft) assertTabs({ active: 2 }) await press(Keys.ArrowLeft) assertTabs({ active: 1 }) }) it('should wrap around at the beginning (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 2, manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 2 }) await press(Keys.ArrowLeft) assertTabs({ active: 2 }) await press(Keys.Enter) assertTabs({ active: 1 }) await press(Keys.ArrowLeft) assertTabs({ active: 1 }) await press(Keys.Enter) assertTabs({ active: 0 }) await press(Keys.ArrowLeft) assertTabs({ active: 0 }) await press(Keys.Enter) assertTabs({ active: 2 }) await press(Keys.ArrowLeft) assertTabs({ active: 2 }) await press(Keys.Enter) assertTabs({ active: 1 }) }) it('should not be possible to go left when in vertical mode (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { vertical: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.ArrowLeft) // no-op assertTabs({ active: 0, orientation: 'vertical' }) }) it('should not be possible to go left when in vertical mode (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { vertical: true, manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.ArrowLeft) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.Enter) // no-op assertTabs({ active: 0, orientation: 'vertical' }) }) }) describe('`ArrowDown` key', () => { it('should be possible to go to the next item (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { vertical: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.ArrowDown) assertTabs({ active: 1, orientation: 'vertical' }) await press(Keys.ArrowDown) assertTabs({ active: 2, orientation: 'vertical' }) }) it('should be possible to go to the next item (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { vertical: true, manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.ArrowDown) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.Enter) assertTabs({ active: 1, orientation: 'vertical' }) await press(Keys.ArrowDown) assertTabs({ active: 1, orientation: 'vertical' }) await press(Keys.Enter) assertTabs({ active: 2, orientation: 'vertical' }) }) it('should wrap around at the end (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { vertical: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.ArrowDown) assertTabs({ active: 1, orientation: 'vertical' }) await press(Keys.ArrowDown) assertTabs({ active: 2, orientation: 'vertical' }) await press(Keys.ArrowDown) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.ArrowDown) assertTabs({ active: 1, orientation: 'vertical' }) }) it('should wrap around at the end (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { vertical: true, manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.ArrowDown) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.Enter) assertTabs({ active: 1, orientation: 'vertical' }) await press(Keys.ArrowDown) assertTabs({ active: 1, orientation: 'vertical' }) await press(Keys.Enter) assertTabs({ active: 2, orientation: 'vertical' }) await press(Keys.ArrowDown) assertTabs({ active: 2, orientation: 'vertical' }) await press(Keys.Enter) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.ArrowDown) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.Enter) assertTabs({ active: 1, orientation: 'vertical' }) }) it('should not be possible to go down when in horizontal mode (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, {}, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0 }) await press(Keys.ArrowDown) // no-op assertTabs({ active: 0 }) }) it('should not be possible to go down when in horizontal mode (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0 }) await press(Keys.ArrowDown) assertTabs({ active: 0 }) await press(Keys.Enter) // no-op assertTabs({ active: 0 }) }) }) describe('`ArrowUp` key', () => { it('should be possible to go to the previous item (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 2, vertical: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 2, orientation: 'vertical' }) await press(Keys.ArrowUp) assertTabs({ active: 1, orientation: 'vertical' }) await press(Keys.ArrowUp) assertTabs({ active: 0, orientation: 'vertical' }) }) it('should be possible to go to the previous item (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 2, vertical: true, manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 2, orientation: 'vertical' }) await press(Keys.ArrowUp) assertTabs({ active: 2, orientation: 'vertical' }) await press(Keys.Enter) assertTabs({ active: 1, orientation: 'vertical' }) await press(Keys.ArrowUp) assertTabs({ active: 1, orientation: 'vertical' }) await press(Keys.Enter) assertTabs({ active: 0, orientation: 'vertical' }) }) it('should wrap around at the beginning (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 2, vertical: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 2, orientation: 'vertical' }) await press(Keys.ArrowUp) assertTabs({ active: 1, orientation: 'vertical' }) await press(Keys.ArrowUp) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.ArrowUp) assertTabs({ active: 2, orientation: 'vertical' }) await press(Keys.ArrowUp) assertTabs({ active: 1, orientation: 'vertical' }) }) it('should wrap around at the beginning (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 2, vertical: true, manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 2, orientation: 'vertical' }) await press(Keys.ArrowUp) assertTabs({ active: 2, orientation: 'vertical' }) await press(Keys.Enter) assertTabs({ active: 1, orientation: 'vertical' }) await press(Keys.ArrowUp) assertTabs({ active: 1, orientation: 'vertical' }) await press(Keys.Enter) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.ArrowUp) assertTabs({ active: 0, orientation: 'vertical' }) await press(Keys.Enter) assertTabs({ active: 2, orientation: 'vertical' }) await press(Keys.ArrowUp) assertTabs({ active: 2, orientation: 'vertical' }) await press(Keys.Enter) assertTabs({ active: 1, orientation: 'vertical' }) }) it('should not be possible to go left when in vertical mode (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, {}, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0 }) await press(Keys.ArrowUp) // no-op assertTabs({ active: 0 }) }) it('should not be possible to go left when in vertical mode (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 0 }) await press(Keys.ArrowUp) assertTabs({ active: 0 }) await press(Keys.Enter) // no-op assertTabs({ active: 0 }) }) }) describe('`Home` key', () => { it('should be possible to go to the first focusable item (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 1 }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 1 }) await press(Keys.Home) assertTabs({ active: 0 }) }) it('should be possible to go to the first focusable item (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 1, manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 1 }) await press(Keys.Home) assertTabs({ active: 1 }) await press(Keys.Enter) assertTabs({ active: 0 }) }) }) describe('`PageUp` key', () => { it('should be possible to go to the first focusable item (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 1 }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 1 }) await press(Keys.PageUp) assertTabs({ active: 0 }) }) it('should be possible to go to the first focusable item (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 1, manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 1 }) await press(Keys.PageUp) assertTabs({ active: 1 }) await press(Keys.Enter) assertTabs({ active: 0 }) }) }) describe('`End` key', () => { it('should be possible to go to the first focusable item (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 1 }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 1 }) await press(Keys.End) assertTabs({ active: 2 }) }) it('should be possible to go to the first focusable item (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 1, manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 1 }) await press(Keys.End) assertTabs({ active: 1 }) await press(Keys.Enter) assertTabs({ active: 2 }) }) }) describe('`PageDown` key', () => { it('should be possible to go to the first focusable item (activation = `auto`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 1 }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 1 }) await press(Keys.PageDown) assertTabs({ active: 2 }) }) it('should be possible to go to the first focusable item (activation = `manual`)', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 1, manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 1 }) await press(Keys.PageDown) assertTabs({ active: 1 }) await press(Keys.Enter) assertTabs({ active: 2 }) }) }) describe('`Enter` key', () => { it('should be possible to activate the focused tab', async () => { render( TestRenderer, { allProps: [ [TabGroup, { manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) getByText('Tab 3')?.focus() assertActiveElement(getByText('Tab 3')) assertTabs({ active: 0 }) await press(Keys.Enter) assertTabs({ active: 2 }) }) }) describe('`Space` key', () => { it('should be possible to activate the focused tab', async () => { render( TestRenderer, { allProps: [ [TabGroup, { manual: true }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) getByText('Tab 3')?.focus() assertActiveElement(getByText('Tab 3')) assertTabs({ active: 0 }) await press(Keys.Space) assertTabs({ active: 2 }) }) }) }) describe('Mouse interactions', () => { it('should be possible to click on a tab to focus it', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 1 }, [ [TabList, {}, [ [Tab, {}, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 1 }) await click(getByText('Tab 1')) assertTabs({ active: 0 }) await click(getByText('Tab 3')) assertTabs({ active: 2 }) await click(getByText('Tab 2')) assertTabs({ active: 1 }) }) it('should be a no-op when clicking on a disabled tab', async () => { render( TestRenderer, { allProps: [ [TabGroup, { defaultIndex: 1 }, [ [TabList, {}, [ [Tab, { disabled: true }, "Tab 1"], [Tab, {}, "Tab 2"], [Tab, {}, "Tab 3"], ]], [TabPanels, {}, [ [TabPanel, {}, "Content 1"], [TabPanel, {}, "Content 2"], [TabPanel, {}, "Content 3"], ]], ]], [Button, {}, "after"], ] }) assertActiveElement(document.body) await press(Keys.Tab) assertTabs({ active: 1 }) await click(getByText('Tab 1')) // No-op, Tab 2 is still active assertTabs({ active: 1 }) }) }) it('should trigger the `on:change` when the tab changes', async () => { let changes = jest.fn() render(svelte` changes(e.detail)}> Tab 1 Tab 2 Tab 3 Content 1 Content 2 Content 3 `); await click(getByText('Tab 2')) await click(getByText('Tab 3')) await click(getByText('Tab 2')) await click(getByText('Tab 1')) expect(changes).toHaveBeenCalledTimes(4) expect(changes).toHaveBeenNthCalledWith(1, 1) expect(changes).toHaveBeenNthCalledWith(2, 2) expect(changes).toHaveBeenNthCalledWith(3, 1) expect(changes).toHaveBeenNthCalledWith(4, 0) })