From bd3019cfac13decc75cf89e3a5e66ebdb0b845f7 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Mar 2022 18:03:36 +0100 Subject: [PATCH 1/6] add internal FocusSentinel component This component will allow you to catch the focus and forward it to a new element. The catch is that it will retry to do that because sometimes components won't be available yet. E.g.: We want to focus the first Tab component if it is rendered inside the Dialog. However, a Tab will register itself in the next tick, triggering a re-render and only then will it be `selected`. This is a bit too late for the FocusTrap component. The FocusSentinel should fix this by catching the focus, and forwarding it to the correct component. Once that is done, it will remove itself from the DOM tree so that you can't ever focus that element anymore. This should fix potential `` and `` behaviour. --- .../src/internal/focus-sentinel.tsx | 46 +++++++++++++++++ .../src/internal/focus-sentinel.ts | 50 +++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 packages/@headlessui-react/src/internal/focus-sentinel.tsx create mode 100644 packages/@headlessui-vue/src/internal/focus-sentinel.ts diff --git a/packages/@headlessui-react/src/internal/focus-sentinel.tsx b/packages/@headlessui-react/src/internal/focus-sentinel.tsx new file mode 100644 index 0000000000..06659cbb2a --- /dev/null +++ b/packages/@headlessui-react/src/internal/focus-sentinel.tsx @@ -0,0 +1,46 @@ +import React, { useState, FocusEvent as ReactFocusEvent } from 'react' + +import { VisuallyHidden } from './visually-hidden' + +interface FocusSentinelProps { + onFocus(): boolean +} + +export function FocusSentinel({ onFocus }: FocusSentinelProps) { + let [enabled, setEnabled] = useState(true) + + if (!enabled) return null + + return ( + { + event.preventDefault() + let frame: ReturnType + + let tries = 50 + function forwardFocus() { + // Prevent infinite loops + if (tries-- <= 0) { + if (frame) cancelAnimationFrame(frame) + return + } + + // Try to move focus to the correct element. This depends on the implementation + // of `onFocus` of course since it would be different for each place we use it in. + if (onFocus()) { + setEnabled(false) + cancelAnimationFrame(frame) + return + } + + // Retry + frame = requestAnimationFrame(forwardFocus) + } + + frame = requestAnimationFrame(forwardFocus) + }} + /> + ) +} diff --git a/packages/@headlessui-vue/src/internal/focus-sentinel.ts b/packages/@headlessui-vue/src/internal/focus-sentinel.ts new file mode 100644 index 0000000000..c574861958 --- /dev/null +++ b/packages/@headlessui-vue/src/internal/focus-sentinel.ts @@ -0,0 +1,50 @@ +import { h, ref, defineComponent } from 'vue' + +import { VisuallyHidden } from './visually-hidden' + +export let FocusSentinel = defineComponent({ + props: { + onFocus: { + type: Function, + required: true, + }, + }, + setup(props) { + let enabled = ref(true) + + return () => { + if (!enabled.value) return null + + return h(VisuallyHidden, { + as: 'button', + type: 'button', + onFocus(event: FocusEvent) { + event.preventDefault() + let frame: ReturnType + + let tries = 50 + function forwardFocus() { + // Prevent infinite loops + if (tries-- <= 0) { + if (frame) cancelAnimationFrame(frame) + return + } + + // Try to move focus to the correct element. This depends on the implementation + // of `onFocus` of course since it would be different for each place we use it in. + if (props.onFocus()) { + enabled.value = false + cancelAnimationFrame(frame) + return + } + + // Retry + frame = requestAnimationFrame(forwardFocus) + } + + frame = requestAnimationFrame(forwardFocus) + }, + }) + } + }, +}) From 711ec6693076baa1d2ea5b19993d0e3582944d67 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Mar 2022 18:06:16 +0100 Subject: [PATCH 2/6] find the selectedIndex asap --- packages/@headlessui-react/src/components/tabs/tabs.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/@headlessui-react/src/components/tabs/tabs.tsx b/packages/@headlessui-react/src/components/tabs/tabs.tsx index c5595325f5..8620450cc2 100644 --- a/packages/@headlessui-react/src/components/tabs/tabs.tsx +++ b/packages/@headlessui-react/src/components/tabs/tabs.tsx @@ -169,7 +169,7 @@ let Tabs = forwardRefWithAs(function Tabs { + useIsoMorphicEffect(() => { if (state.tabs.length <= 0) return if (selectedIndex === null && state.selectedIndex !== null) return From 07e930d53dac1694001cca7787533d9bbd879746 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Mar 2022 18:06:45 +0100 Subject: [PATCH 3/6] use the FocusSentinel and forward it to the correct Tab --- .../src/components/tabs/tabs.tsx | 14 +++++++ .../src/components/tabs/tabs.ts | 42 ++++++++++++++----- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/packages/@headlessui-react/src/components/tabs/tabs.tsx b/packages/@headlessui-react/src/components/tabs/tabs.tsx index 8620450cc2..10b249517d 100644 --- a/packages/@headlessui-react/src/components/tabs/tabs.tsx +++ b/packages/@headlessui-react/src/components/tabs/tabs.tsx @@ -28,6 +28,7 @@ import { useIsoMorphicEffect } from '../../hooks/use-iso-morphic-effect' import { useSyncRefs } from '../../hooks/use-sync-refs' import { useResolveButtonType } from '../../hooks/use-resolve-button-type' import { useLatestValue } from '../../hooks/use-latest-value' +import { FocusSentinel } from '../../internal/focus-sentinel' interface StateDefinition { selectedIndex: number | null @@ -160,6 +161,7 @@ let Tabs = forwardRefWithAs(function Tabs ({ selectedIndex: state.selectedIndex }), [state.selectedIndex]) let onChangeRef = useLatestValue(onChange || (() => {})) + let stableTabsRef = useLatestValue(state.tabs) useEffect(() => { dispatch({ type: ActionTypes.SetOrientation, orientation }) @@ -229,6 +231,18 @@ let Tabs = forwardRefWithAs(function Tabs + { + for (let tab of stableTabsRef.current) { + if (tab.current?.tabIndex === 0) { + tab.current?.focus() + return true + } + } + + return false + }} + /> {render({ props: { ref: tabsRef, ...passThroughProps }, slot, diff --git a/packages/@headlessui-vue/src/components/tabs/tabs.ts b/packages/@headlessui-vue/src/components/tabs/tabs.ts index 1ce70cc16a..7d940a8c60 100644 --- a/packages/@headlessui-vue/src/components/tabs/tabs.ts +++ b/packages/@headlessui-vue/src/components/tabs/tabs.ts @@ -1,14 +1,18 @@ import { + Fragment, + computed, defineComponent, - ref, - provide, + h, inject, onMounted, onUnmounted, - computed, + provide, + ref, + watchEffect, + + // Types InjectionKey, Ref, - watchEffect, } from 'vue' import { Features, render, omit } from '../../utils/render' @@ -18,6 +22,7 @@ import { dom } from '../../utils/dom' import { match } from '../../utils/match' import { focusIn, Focus } from '../../utils/focus-management' import { useResolveButtonType } from '../../hooks/use-resolve-button-type' +import { FocusSentinel } from '../../internal/focus-sentinel' type StateDefinition = { // State @@ -132,13 +137,28 @@ export let TabGroup = defineComponent({ return () => { let slot = { selectedIndex: selectedIndex.value } - return render({ - props: omit(props, ['selectedIndex', 'defaultIndex', 'manual', 'vertical', 'onChange']), - slot, - slots, - attrs, - name: 'TabGroup', - }) + return h(Fragment, [ + h(FocusSentinel, { + onFocus: () => { + for (let tab of tabs.value) { + let el = dom(tab) + if (el?.tabIndex === 0) { + el.focus() + return true + } + } + + return false + }, + }), + render({ + props: omit(props, ['selectedIndex', 'defaultIndex', 'manual', 'vertical', 'onChange']), + slot, + slots, + attrs, + name: 'TabGroup', + }), + ]) } }, }) From 5d1f42dd72f221eb048726d0ac80074805576908 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Mar 2022 18:07:02 +0100 Subject: [PATCH 4/6] add example Tab in Dialog example --- .../pages/combinations/tabs-in-dialog.tsx | 31 +++++++++++++++++++ .../combinations/tabs-in-dialog.vue | 31 +++++++++++++++++++ packages/playground-vue/src/routes.json | 11 +++++++ 3 files changed, 73 insertions(+) create mode 100644 packages/playground-react/pages/combinations/tabs-in-dialog.tsx create mode 100644 packages/playground-vue/src/components/combinations/tabs-in-dialog.vue diff --git a/packages/playground-react/pages/combinations/tabs-in-dialog.tsx b/packages/playground-react/pages/combinations/tabs-in-dialog.tsx new file mode 100644 index 0000000000..bca77d568c --- /dev/null +++ b/packages/playground-react/pages/combinations/tabs-in-dialog.tsx @@ -0,0 +1,31 @@ +import { useState } from 'react' +import { Dialog, Tab } from '@headlessui/react' + +export default function App() { + let [open, setOpen] = useState(false) + + return ( + <> + + + +
+
+ + + Tab 1 + Tab 2 + Tab 3 + + + Panel 1 + Panel 2 + Panel 3 + + +
+
+
+ + ) +} diff --git a/packages/playground-vue/src/components/combinations/tabs-in-dialog.vue b/packages/playground-vue/src/components/combinations/tabs-in-dialog.vue new file mode 100644 index 0000000000..ccab3dbf80 --- /dev/null +++ b/packages/playground-vue/src/components/combinations/tabs-in-dialog.vue @@ -0,0 +1,31 @@ + + + diff --git a/packages/playground-vue/src/routes.json b/packages/playground-vue/src/routes.json index e7c841e1fb..6beebe9bf8 100644 --- a/packages/playground-vue/src/routes.json +++ b/packages/playground-vue/src/routes.json @@ -3,6 +3,17 @@ "path": "/", "component": "./components/Home.vue" }, + { + "name": "Combinations", + "path": "/combinations", + "children": [ + { + "name": "Tabs in Dialog", + "path": "/combinations/tabs-in-dialog", + "component": "./components/combinations/tabs-in-dialog.vue" + } + ] + }, { "name": "Combobox", "path": "/combobox", From 1fc5b08000f69dd76a0381a0aaf8edfb0acc201a Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Mar 2022 18:25:58 +0100 Subject: [PATCH 5/6] suppress console warnings Because we are firing `setState` calls within the component, React is yelling at us for not using `act(() => { ... })`. Welp, not going to add those calls inside the component just for tests... --- .../src/components/tabs/tabs.test.tsx | 3463 +++++++++-------- 1 file changed, 1826 insertions(+), 1637 deletions(-) diff --git a/packages/@headlessui-react/src/components/tabs/tabs.test.tsx b/packages/@headlessui-react/src/components/tabs/tabs.test.tsx index d824817405..cb3a3e3b8b 100644 --- a/packages/@headlessui-react/src/components/tabs/tabs.test.tsx +++ b/packages/@headlessui-react/src/components/tabs/tabs.test.tsx @@ -33,320 +33,236 @@ describe('safeguards', () => { }) ) - it('should be possible to render Tab.Group without crashing', async () => { - render( - - - Tab 1 - Tab 2 - Tab 3 - - - - Content 1 - Content 2 - Content 3 - - - ) + it( + 'should be possible to render Tab.Group without crashing', + suppressConsoleLogs(async () => { + render( + + + Tab 1 + Tab 2 + Tab 3 + - assertTabs({ active: 0 }) - }) + + Content 1 + Content 2 + Content 3 + + + ) + + assertTabs({ active: 0 }) + }) + ) }) describe('Rendering', () => { - it('should be possible to render the Tab.Panels first, then the Tab.List', async () => { - render( - - - Content 1 - Content 2 - Content 3 - - - - Tab 1 - Tab 2 - Tab 3 - - - ) - - assertTabs({ active: 0 }) - }) - - it('should guarantee the order of DOM nodes when performing actions', async () => { - function Example() { - let [hide, setHide] = useState(false) - - return ( - <> - - - - Tab 1 - {!hide && Tab 2} - Tab 3 - + it( + 'should be possible to render the Tab.Panels first, then the Tab.List', + suppressConsoleLogs(async () => { + render( + + + Content 1 + Content 2 + Content 3 + - - Content 1 - {!hide && Content 2} - Content 3 - - - + + Tab 1 + Tab 2 + Tab 3 + + ) - } - - render() - - 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 }) - }) + assertTabs({ active: 0 }) + }) + ) - describe('`renderProps`', () => { - it('should expose the `selectedIndex` on the `Tab.Group` component', async () => { - render( - - {(data) => ( - <> -
{JSON.stringify(data)}
+ it( + 'should guarantee the order of DOM nodes when performing actions', + suppressConsoleLogs(async () => { + function Example() { + let [hide, setHide] = useState(false) + return ( + <> + + Tab 1 - Tab 2 + {!hide && Tab 2} Tab 3 Content 1 - Content 2 + {!hide && 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 `Tab.List` component', async () => { - render( - - - {(data) => ( - <> -
{JSON.stringify(data)}
- Tab 1 - Tab 2 - Tab 3 - - )} -
+ render() - - Content 1 - Content 2 - Content 3 - -
- ) + await click(getByText('toggle')) // Remove Tab 2 + await click(getByText('toggle')) // Re-add Tab 2 - expect(document.getElementById('exposed')).toHaveTextContent( - JSON.stringify({ selectedIndex: 0 }) - ) + await press(Keys.Tab) + assertTabs({ active: 0 }) - await click(getByText('Tab 2')) + await press(Keys.ArrowRight) + assertTabs({ active: 1 }) - expect(document.getElementById('exposed')).toHaveTextContent( - JSON.stringify({ selectedIndex: 1 }) - ) + await press(Keys.ArrowRight) + assertTabs({ active: 2 }) }) + ) - it('should expose the `selectedIndex` on the `Tab.Panels` component', async () => { - render( - - - Tab 1 - Tab 2 - Tab 3 - - - + describe('`renderProps`', () => { + it( + 'should expose the `selectedIndex` on the `Tab.Group` component', + suppressConsoleLogs(async () => { + render( + {(data) => ( <>
{JSON.stringify(data)}
- Content 1 - Content 2 - Content 3 + + + Tab 1 + Tab 2 + Tab 3 + + + + Content 1 + Content 2 + Content 3 + )} -
-
- ) + + ) - expect(document.getElementById('exposed')).toHaveTextContent( - JSON.stringify({ selectedIndex: 0 }) - ) + expect(document.getElementById('exposed')).toHaveTextContent( + JSON.stringify({ selectedIndex: 0 }) + ) - await click(getByText('Tab 2')) + await click(getByText('Tab 2')) - expect(document.getElementById('exposed')).toHaveTextContent( - JSON.stringify({ selectedIndex: 1 }) - ) - }) + expect(document.getElementById('exposed')).toHaveTextContent( + JSON.stringify({ selectedIndex: 1 }) + ) + }) + ) - it('should expose the `selected` state on the `Tab` components', async () => { - render( - - - - {(data) => ( - <> -
{JSON.stringify(data)}
- Tab 1 - - )} -
- - {(data) => ( - <> -
{JSON.stringify(data)}
- Tab 2 - - )} -
- + it( + 'should expose the `selectedIndex` on the `Tab.List` component', + suppressConsoleLogs(async () => { + render( + + {(data) => ( <> -
{JSON.stringify(data)}
- Tab 3 +
{JSON.stringify(data)}
+ Tab 1 + Tab 2 + Tab 3 )} -
-
+ - - Content 1 - Content 2 - Content 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 }) - ) + expect(document.getElementById('exposed')).toHaveTextContent( + JSON.stringify({ selectedIndex: 0 }) + ) - await click(getTabs()[1]) + await click(getByText('Tab 2')) - 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 }) - ) - }) + expect(document.getElementById('exposed')).toHaveTextContent( + JSON.stringify({ selectedIndex: 1 }) + ) + }) + ) - it('should expose the `selected` state on the `Tab.Panel` components', async () => { - render( - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should expose the `selectedIndex` on the `Tab.Panels` component', + suppressConsoleLogs(async () => { + render( + + + Tab 1 + Tab 2 + Tab 3 + - - - {(data) => ( - <> -
{JSON.stringify(data)}
- Content 1 - - )} -
- - {(data) => ( - <> -
{JSON.stringify(data)}
- Content 2 - - )} -
- + {(data) => ( <> -
{JSON.stringify(data)}
- Content 3 +
{JSON.stringify(data)}
+ Content 1 + Content 2 + 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 }) - ) + expect(document.getElementById('exposed')).toHaveTextContent( + JSON.stringify({ selectedIndex: 0 }) + ) - await click(getByText('Tab 2')) + 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 }) - ) - }) - }) + expect(document.getElementById('exposed')).toHaveTextContent( + JSON.stringify({ selectedIndex: 1 }) + ) + }) + ) - describe('`defaultIndex`', () => { - it('should jump to the nearest tab when the defaultIndex is out of bounds (-2)', async () => { - render( - <> - + it( + 'should expose the `selected` state on the `Tab` components', + suppressConsoleLogs(async () => { + render( + - Tab 1 - Tab 2 - Tab 3 + + {(data) => ( + <> +
{JSON.stringify(data)}
+ Tab 1 + + )} +
+ + {(data) => ( + <> +
{JSON.stringify(data)}
+ Tab 2 + + )} +
+ + {(data) => ( + <> +
{JSON.stringify(data)}
+ Tab 3 + + )} +
@@ -355,23 +271,37 @@ describe('Rendering', () => { Content 3
+ ) - - - ) - - assertActiveElement(document.body) + 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 press(Keys.Tab) + await click(getTabs()[1]) - assertTabs({ active: 0 }) - assertActiveElement(getByText('Tab 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 jump to the nearest tab when the defaultIndex is out of bounds (+5)', async () => { - render( - <> - + it( + 'should expose the `selected` state on the `Tab.Panel` components', + suppressConsoleLogs(async () => { + render( + Tab 1 Tab 2 @@ -379,89 +309,98 @@ describe('Rendering', () => { - Content 1 - Content 2 - Content 3 - - - - - - ) - - 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( - <> - - - Tab 1 - Tab 2 - Tab 3 - - - - Content 1 - Content 2 - Content 3 + + {(data) => ( + <> +
{JSON.stringify(data)}
+ Content 1 + + )} +
+ + {(data) => ( + <> +
{JSON.stringify(data)}
+ Content 2 + + )} +
+ + {(data) => ( + <> +
{JSON.stringify(data)}
+ Content 3 + + )} +
+ ) - - - ) - - assertActiveElement(document.body) - - await press(Keys.Tab) + 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 }) + ) - assertTabs({ active: 1 }) - assertActiveElement(getByText('Tab 2')) - }) + await click(getByText('Tab 2')) - it('should jump to the next available tab when the defaultIndex is a disabled tab and wrap around', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + 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 }) + ) + }) + ) + }) - - Content 1 - Content 2 - Content 3 - - + describe('`defaultIndex`', () => { + it( + 'should jump to the nearest tab when the defaultIndex is out of bounds (-2)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - - ) + + Content 1 + Content 2 + Content 3 + + - assertActiveElement(document.body) + + + ) - await press(Keys.Tab) + assertActiveElement(document.body) - assertTabs({ active: 0 }) - assertActiveElement(getByText('Tab 1')) - }) + await press(Keys.Tab) - it('should not change the Tab if the defaultIndex changes', async () => { - function Example() { - let [defaultIndex, setDefaultIndex] = useState(1) + assertTabs({ active: 0 }) + assertActiveElement(getByText('Tab 1')) + }) + ) - return ( + it( + 'should jump to the nearest tab when the defaultIndex is out of bounds (+5)', + suppressConsoleLogs(async () => { + render( <> - + Tab 1 Tab 2 @@ -476,53 +415,60 @@ describe('Rendering', () => { - ) - } - - render() - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) + await press(Keys.Tab) - assertTabs({ active: 1 }) - assertActiveElement(getByText('Tab 2')) + assertTabs({ active: 2 }) + assertActiveElement(getByText('Tab 3')) + }) + ) - await click(getByText('Tab 3')) + it( + 'should jump to the next available tab when the defaultIndex is a disabled tab', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - assertTabs({ active: 2 }) - assertActiveElement(getByText('Tab 3')) + + Content 1 + Content 2 + Content 3 + + - // Change default index - await click(getByText('change')) + + + ) - // Nothing should change... - assertTabs({ active: 2 }) - }) - }) + assertActiveElement(document.body) - describe('`selectedIndex`', () => { - it('should be possible to change active tab controlled and uncontrolled', async () => { - let handleChange = jest.fn() + await press(Keys.Tab) - function ControlledTabs() { - let [selectedIndex, setSelectedIndex] = useState(0) + assertTabs({ active: 1 }) + assertActiveElement(getByText('Tab 2')) + }) + ) - return ( + it( + 'should jump to the next available tab when the defaultIndex is a disabled tab and wrap around', + suppressConsoleLogs(async () => { + render( <> - { - setSelectedIndex(value) - handleChange(value) - }} - > + Tab 1 Tab 2 - Tab 3 + Tab 3 @@ -533,1488 +479,1793 @@ describe('Rendering', () => { - ) - } - render() + assertActiveElement(document.body) - assertActiveElement(document.body) + await press(Keys.Tab) - // test uncontrolled behaviour - await click(getByText('Tab 2')) - expect(handleChange).toHaveBeenCalledTimes(1) - expect(handleChange).toHaveBeenNthCalledWith(1, 1) - assertTabs({ active: 1 }) + assertTabs({ active: 0 }) + assertActiveElement(getByText('Tab 1')) + }) + ) - // test controlled behaviour - await click(getByText('setSelectedIndex')) - assertTabs({ active: 2 }) + it( + 'should not change the Tab if the defaultIndex changes', + suppressConsoleLogs(async () => { + function Example() { + let [defaultIndex, setDefaultIndex] = useState(1) - // test uncontrolled behaviour again - await click(getByText('Tab 2')) - expect(handleChange).toHaveBeenCalledTimes(2) - expect(handleChange).toHaveBeenNthCalledWith(2, 1) - assertTabs({ active: 1 }) - }) + return ( + <> + + + Tab 1 + Tab 2 + Tab 3 + + + + Content 1 + Content 2 + Content 3 + + + + + + + ) + } - it('should jump to the nearest tab when the selectedIndex is out of bounds (-2)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + render() - - Content 1 - Content 2 - Content 3 - - + assertActiveElement(document.body) - - - ) + await press(Keys.Tab) - assertActiveElement(document.body) + assertTabs({ active: 1 }) + assertActiveElement(getByText('Tab 2')) - await press(Keys.Tab) + await click(getByText('Tab 3')) - assertTabs({ active: 0 }) - assertActiveElement(getByText('Tab 1')) - }) + assertTabs({ active: 2 }) + assertActiveElement(getByText('Tab 3')) - it('should jump to the nearest tab when the selectedIndex is out of bounds (+5)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + // Change default index + await click(getByText('change')) - - Content 1 - Content 2 - Content 3 - - + // Nothing should change... + assertTabs({ active: 2 }) + }) + ) + }) - - - ) + describe('`selectedIndex`', () => { + it( + 'should be possible to change active tab controlled and uncontrolled', + suppressConsoleLogs(async () => { + let handleChange = jest.fn() - assertActiveElement(document.body) + function ControlledTabs() { + let [selectedIndex, setSelectedIndex] = useState(0) - await press(Keys.Tab) + return ( + <> + { + setSelectedIndex(value) + handleChange(value) + }} + > + + Tab 1 + Tab 2 + Tab 3 + + + + Content 1 + Content 2 + Content 3 + + + + + + + ) + } - assertTabs({ active: 2 }) - assertActiveElement(getByText('Tab 3')) - }) + render() - it('should jump to the next available tab when the selectedIndex is a disabled tab', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + assertActiveElement(document.body) - - Content 1 - Content 2 - Content 3 - - + // test uncontrolled behaviour + await click(getByText('Tab 2')) + expect(handleChange).toHaveBeenCalledTimes(1) + expect(handleChange).toHaveBeenNthCalledWith(1, 1) + assertTabs({ active: 1 }) - - - ) + // test controlled behaviour + await click(getByText('setSelectedIndex')) + assertTabs({ active: 2 }) - assertActiveElement(document.body) + // test uncontrolled behaviour again + await click(getByText('Tab 2')) + expect(handleChange).toHaveBeenCalledTimes(2) + expect(handleChange).toHaveBeenNthCalledWith(2, 1) + assertTabs({ active: 1 }) + }) + ) - await press(Keys.Tab) + it( + 'should jump to the nearest tab when the selectedIndex is out of bounds (-2)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - assertTabs({ active: 1 }) - assertActiveElement(getByText('Tab 2')) - }) + + Content 1 + Content 2 + Content 3 + + - it('should jump to the next available tab when the selectedIndex is a disabled tab and wrap around', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + + + ) - - Content 1 - Content 2 - Content 3 - - + assertActiveElement(document.body) - - - ) + await press(Keys.Tab) - assertActiveElement(document.body) + assertTabs({ active: 0 }) + assertActiveElement(getByText('Tab 1')) + }) + ) - await press(Keys.Tab) + it( + 'should jump to the nearest tab when the selectedIndex is out of bounds (+5)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - assertTabs({ active: 0 }) - assertActiveElement(getByText('Tab 1')) - }) + + Content 1 + Content 2 + Content 3 + + - it('should prefer selectedIndex over defaultIndex', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + + + ) - - Content 1 - Content 2 - Content 3 - - + assertActiveElement(document.body) - - - ) + await press(Keys.Tab) - assertActiveElement(document.body) + assertTabs({ active: 2 }) + assertActiveElement(getByText('Tab 3')) + }) + ) - await press(Keys.Tab) + it( + 'should jump to the next available tab when the selectedIndex is a disabled tab', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - assertTabs({ active: 0 }) - assertActiveElement(getByText('Tab 1')) - }) - }) + + Content 1 + Content 2 + Content 3 + + - describe(`'Tab'`, () => { - describe('`type` attribute', () => { - it('should set the `type` to "button" by default', async () => { - render( - - - Trigger - - + + ) - expect(getTabs()[0]).toHaveAttribute('type', 'button') - }) + assertActiveElement(document.body) - it('should not set the `type` to "button" if it already contains a `type`', async () => { - render( - - - Trigger - - - ) + await press(Keys.Tab) - expect(getTabs()[0]).toHaveAttribute('type', 'submit') + assertTabs({ active: 1 }) + assertActiveElement(getByText('Tab 2')) }) + ) - it('should set the `type` to "button" when using the `as` prop which resolves to a "button"', async () => { - let CustomButton = React.forwardRef((props, ref) => ( - + ) - expect(getTabs()[0]).toHaveAttribute('type', 'button') + assertActiveElement(document.body) + + await press(Keys.Tab) + + assertTabs({ active: 0 }) + assertActiveElement(getByText('Tab 1')) }) + ) - it('should not set the type if the "as" prop is not a "button"', async () => { + it( + 'should prefer selectedIndex over defaultIndex', + suppressConsoleLogs(async () => { render( - - - Trigger - - + <> + + + Tab 1 + Tab 2 + Tab 3 + + + + Content 1 + Content 2 + Content 3 + + + + + ) - expect(getTabs()[0]).not.toHaveAttribute('type') + assertActiveElement(document.body) + + await press(Keys.Tab) + + assertTabs({ active: 0 }) + assertActiveElement(getByText('Tab 1')) }) + ) + }) - it('should not set the `type` to "button" when using the `as` prop which resolves to a "div"', async () => { - let CustomButton = React.forwardRef((props, ref) => ( -
- )) + describe(`'Tab'`, () => { + describe('`type` attribute', () => { + it( + 'should set the `type` to "button" by default', + suppressConsoleLogs(async () => { + render( + + + Trigger + + + ) - render( - - - Trigger - - - ) + expect(getTabs()[0]).toHaveAttribute('type', 'button') + }) + ) - expect(getTabs()[0]).not.toHaveAttribute('type') - }) + it( + 'should not set the `type` to "button" if it already contains a `type`', + suppressConsoleLogs(async () => { + render( + + + Trigger + + + ) + + expect(getTabs()[0]).toHaveAttribute('type', 'submit') + }) + ) + + it( + 'should set the `type` to "button" when using the `as` prop which resolves to a "button"', + suppressConsoleLogs(async () => { + let CustomButton = React.forwardRef((props, ref) => ( + - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) + await press(Keys.Tab) - assertTabs({ active: 0 }) - assertActiveElement(getByText('Tab 1')) + assertTabs({ active: 0 }) + assertActiveElement(getByText('Tab 1')) - await press(Keys.Tab) - assertActiveElement(getByText('Content 1')) + await press(Keys.Tab) + assertActiveElement(getByText('Content 1')) - await press(Keys.Tab) - assertActiveElement(getByText('after')) + await press(Keys.Tab) + assertActiveElement(getByText('after')) - await press(shift(Keys.Tab)) - assertActiveElement(getByText('Content 1')) + await press(shift(Keys.Tab)) + assertActiveElement(getByText('Content 1')) - await press(shift(Keys.Tab)) - assertActiveElement(getByText('Tab 1')) - }) + await press(shift(Keys.Tab)) + assertActiveElement(getByText('Tab 1')) + }) + ) - it('should be possible to tab to the default index tab', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should be possible to tab to the default index tab', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) + await press(Keys.Tab) - assertTabs({ active: 1 }) - assertActiveElement(getByText('Tab 2')) + assertTabs({ active: 1 }) + assertActiveElement(getByText('Tab 2')) - await press(Keys.Tab) - assertActiveElement(getByText('Content 2')) + await press(Keys.Tab) + assertActiveElement(getByText('Content 2')) - await press(Keys.Tab) - assertActiveElement(getByText('after')) + await press(Keys.Tab) + assertActiveElement(getByText('after')) - await press(shift(Keys.Tab)) - assertActiveElement(getByText('Content 2')) + await press(shift(Keys.Tab)) + assertActiveElement(getByText('Content 2')) - await press(shift(Keys.Tab)) - assertActiveElement(getByText('Tab 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( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should be possible to go to the next item (activation = `auto`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 0 }) + await press(Keys.Tab) + assertTabs({ active: 0 }) - await press(Keys.ArrowRight) - assertTabs({ active: 1 }) + await press(Keys.ArrowRight) + assertTabs({ active: 1 }) - await press(Keys.ArrowRight) - assertTabs({ active: 2 }) - }) + await press(Keys.ArrowRight) + assertTabs({ active: 2 }) + }) + ) - it('should be possible to go to the next item (activation = `manual`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should be possible to go to the next item (activation = `manual`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 0 }) + 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: 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: 1 }) + await press(Keys.Enter) + assertTabs({ active: 2 }) + }) + ) - it('should wrap around at the end (activation = `auto`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should wrap around at the end (activation = `auto`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 0 }) + await press(Keys.Tab) + assertTabs({ active: 0 }) - await press(Keys.ArrowRight) - assertTabs({ active: 1 }) + await press(Keys.ArrowRight) + assertTabs({ active: 1 }) - await press(Keys.ArrowRight) - assertTabs({ active: 2 }) + await press(Keys.ArrowRight) + assertTabs({ active: 2 }) - await press(Keys.ArrowRight) - assertTabs({ active: 0 }) + await press(Keys.ArrowRight) + assertTabs({ active: 0 }) - await press(Keys.ArrowRight) - assertTabs({ active: 1 }) - }) + await press(Keys.ArrowRight) + assertTabs({ active: 1 }) + }) + ) - it('should wrap around at the end (activation = `manual`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should wrap around at the end (activation = `manual`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 0 }) + 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: 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: 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: 2 }) + await press(Keys.Enter) + assertTabs({ active: 0 }) - await press(Keys.ArrowRight) - assertTabs({ active: 0 }) - await press(Keys.Enter) - assertTabs({ active: 1 }) - }) + 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( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should not be possible to go right when in vertical mode (activation = `auto`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 0, orientation: 'vertical' }) + await press(Keys.Tab) + assertTabs({ active: 0, orientation: 'vertical' }) - await press(Keys.ArrowRight) - // no-op - 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( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should not be possible to go right when in vertical mode (activation = `manual`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 0, orientation: 'vertical' }) + 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' }) - }) + 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( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should be possible to go to the previous item (activation = `auto`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 2 }) + await press(Keys.Tab) + assertTabs({ active: 2 }) - await press(Keys.ArrowLeft) - assertTabs({ active: 1 }) + await press(Keys.ArrowLeft) + assertTabs({ active: 1 }) - await press(Keys.ArrowLeft) - assertTabs({ active: 0 }) - }) + await press(Keys.ArrowLeft) + assertTabs({ active: 0 }) + }) + ) - it('should be possible to go to the previous item (activation = `manual`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should be possible to go to the previous item (activation = `manual`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 2 }) + 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: 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: 1 }) + await press(Keys.Enter) + assertTabs({ active: 0 }) + }) + ) - it('should wrap around at the beginning (activation = `auto`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should wrap around at the beginning (activation = `auto`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 2 }) + await press(Keys.Tab) + assertTabs({ active: 2 }) - await press(Keys.ArrowLeft) - assertTabs({ active: 1 }) + await press(Keys.ArrowLeft) + assertTabs({ active: 1 }) - await press(Keys.ArrowLeft) - assertTabs({ active: 0 }) + await press(Keys.ArrowLeft) + assertTabs({ active: 0 }) - await press(Keys.ArrowLeft) - assertTabs({ active: 2 }) + await press(Keys.ArrowLeft) + assertTabs({ active: 2 }) - await press(Keys.ArrowLeft) - assertTabs({ active: 1 }) - }) + await press(Keys.ArrowLeft) + assertTabs({ active: 1 }) + }) + ) - it('should wrap around at the beginning (activation = `manual`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should wrap around at the beginning (activation = `manual`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 2 }) + 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: 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: 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: 0 }) + await press(Keys.Enter) + assertTabs({ active: 2 }) - await press(Keys.ArrowLeft) - assertTabs({ active: 2 }) - await press(Keys.Enter) - assertTabs({ active: 1 }) - }) + 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( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should not be possible to go left when in vertical mode (activation = `auto`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 0, orientation: 'vertical' }) + await press(Keys.Tab) + assertTabs({ active: 0, orientation: 'vertical' }) - await press(Keys.ArrowLeft) - // no-op - 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( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should not be possible to go left when in vertical mode (activation = `manual`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 0, orientation: 'vertical' }) + await press(Keys.Tab) + assertTabs({ active: 0, orientation: 'vertical' }) - await press(Keys.ArrowLeft) - assertTabs({ active: 0, orientation: 'vertical' }) - await press(Keys.Enter) + await press(Keys.ArrowLeft) + assertTabs({ active: 0, orientation: 'vertical' }) + await press(Keys.Enter) - // no-op - assertTabs({ active: 0, orientation: 'vertical' }) - }) + // no-op + assertTabs({ active: 0, orientation: 'vertical' }) + }) + ) }) describe('`ArrowDown` key', () => { - it('should be possible to go to the next item (activation = `auto`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - - - - Content 1 - Content 2 - Content 3 - - + it( + 'should be possible to go to the next item (activation = `auto`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - - ) + + Content 1 + Content 2 + Content 3 + + - assertActiveElement(document.body) + + + ) - await press(Keys.Tab) - assertTabs({ active: 0, orientation: 'vertical' }) + assertActiveElement(document.body) - await press(Keys.ArrowDown) - assertTabs({ active: 1, orientation: 'vertical' }) + await press(Keys.Tab) + assertTabs({ active: 0, orientation: 'vertical' }) - await press(Keys.ArrowDown) - assertTabs({ active: 2, orientation: 'vertical' }) - }) + await press(Keys.ArrowDown) + assertTabs({ active: 1, orientation: 'vertical' }) - it('should be possible to go to the next item (activation = `manual`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + await press(Keys.ArrowDown) + assertTabs({ active: 2, orientation: 'vertical' }) + }) + ) - - Content 1 - Content 2 - Content 3 - - + it( + 'should be possible to go to the next item (activation = `manual`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - - ) + + Content 1 + Content 2 + Content 3 + + - assertActiveElement(document.body) + + + ) - await press(Keys.Tab) - assertTabs({ active: 0, orientation: 'vertical' }) + assertActiveElement(document.body) - await press(Keys.ArrowDown) - assertTabs({ active: 0, orientation: 'vertical' }) - await press(Keys.Enter) - assertTabs({ active: 1, orientation: 'vertical' }) + await press(Keys.Tab) + assertTabs({ active: 0, 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: 0, orientation: 'vertical' }) + await press(Keys.Enter) + assertTabs({ active: 1, orientation: 'vertical' }) - it('should wrap around at the end (activation = `auto`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + await press(Keys.ArrowDown) + assertTabs({ active: 1, orientation: 'vertical' }) + await press(Keys.Enter) + assertTabs({ active: 2, orientation: 'vertical' }) + }) + ) - - Content 1 - Content 2 - Content 3 - - + it( + 'should wrap around at the end (activation = `auto`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - - ) + + Content 1 + Content 2 + Content 3 + + - assertActiveElement(document.body) + + + ) - await press(Keys.Tab) - assertTabs({ active: 0, orientation: 'vertical' }) + assertActiveElement(document.body) - await press(Keys.ArrowDown) - assertTabs({ active: 1, orientation: 'vertical' }) + await press(Keys.Tab) + assertTabs({ active: 0, orientation: 'vertical' }) - await press(Keys.ArrowDown) - assertTabs({ active: 2, orientation: 'vertical' }) + await press(Keys.ArrowDown) + assertTabs({ active: 1, orientation: 'vertical' }) - await press(Keys.ArrowDown) - assertTabs({ active: 0, orientation: 'vertical' }) + await press(Keys.ArrowDown) + assertTabs({ active: 2, orientation: 'vertical' }) - await press(Keys.ArrowDown) - assertTabs({ active: 1, orientation: 'vertical' }) - }) + await press(Keys.ArrowDown) + assertTabs({ active: 0, orientation: 'vertical' }) - it('should wrap around at the end (activation = `manual`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + await press(Keys.ArrowDown) + assertTabs({ active: 1, orientation: 'vertical' }) + }) + ) - - Content 1 - Content 2 - Content 3 - - + it( + 'should wrap around at the end (activation = `manual`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - - ) + + Content 1 + Content 2 + Content 3 + + - 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' }) - }) + assertActiveElement(document.body) - it('should not be possible to go down when in horizontal mode (activation = `auto`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + await press(Keys.Tab) + assertTabs({ active: 0, orientation: 'vertical' }) - - Content 1 - Content 2 - Content 3 - - + 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' }) - assertActiveElement(document.body) + await press(Keys.ArrowDown) + assertTabs({ active: 2, orientation: 'vertical' }) + await press(Keys.Enter) + assertTabs({ active: 0, orientation: 'vertical' }) - await press(Keys.Tab) - assertTabs({ active: 0 }) + await press(Keys.ArrowDown) + assertTabs({ active: 0, orientation: 'vertical' }) + await press(Keys.Enter) + assertTabs({ active: 1, orientation: 'vertical' }) + }) + ) - await press(Keys.ArrowDown) - // no-op - assertTabs({ active: 0 }) - }) + it( + 'should not be possible to go down when in horizontal mode (activation = `auto`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - it('should not be possible to go down when in horizontal mode (activation = `manual`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + + Content 1 + Content 2 + Content 3 + + - - Content 1 - Content 2 - Content 3 - - + + + ) - - - ) + assertActiveElement(document.body) - assertActiveElement(document.body) + await press(Keys.Tab) + assertTabs({ active: 0 }) - await press(Keys.Tab) - assertTabs({ active: 0 }) + await press(Keys.ArrowDown) + // no-op + assertTabs({ active: 0 }) + }) + ) - await press(Keys.ArrowDown) - assertTabs({ active: 0 }) - await press(Keys.Enter) + it( + 'should not be possible to go down when in horizontal mode (activation = `manual`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - // no-op - assertTabs({ active: 0 }) - }) - }) + + Content 1 + Content 2 + Content 3 + + - describe('`ArrowUp` key', () => { - it('should be possible to go to the previous item (activation = `auto`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + + + ) - - Content 1 - Content 2 - Content 3 - - + assertActiveElement(document.body) - - - ) + await press(Keys.Tab) + assertTabs({ active: 0 }) - assertActiveElement(document.body) + await press(Keys.ArrowDown) + assertTabs({ active: 0 }) + await press(Keys.Enter) - await press(Keys.Tab) - assertTabs({ active: 2, orientation: 'vertical' }) + // no-op + assertTabs({ active: 0 }) + }) + ) + }) - await press(Keys.ArrowUp) - assertTabs({ active: 1, orientation: 'vertical' }) + describe('`ArrowUp` key', () => { + it( + 'should be possible to go to the previous item (activation = `auto`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - await press(Keys.ArrowUp) - assertTabs({ active: 0, orientation: 'vertical' }) - }) + + Content 1 + Content 2 + Content 3 + + - it('should be possible to go to the previous item (activation = `manual`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + + + ) - - Content 1 - Content 2 - Content 3 - - + assertActiveElement(document.body) - - - ) + await press(Keys.Tab) + assertTabs({ active: 2, orientation: 'vertical' }) - assertActiveElement(document.body) + await press(Keys.ArrowUp) + assertTabs({ active: 1, orientation: 'vertical' }) - await press(Keys.Tab) - assertTabs({ active: 2, orientation: 'vertical' }) + await press(Keys.ArrowUp) + assertTabs({ active: 0, orientation: 'vertical' }) + }) + ) - await press(Keys.ArrowUp) - assertTabs({ active: 2, orientation: 'vertical' }) - await press(Keys.Enter) - assertTabs({ active: 1, orientation: 'vertical' }) + it( + 'should be possible to go to the previous item (activation = `manual`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - await press(Keys.ArrowUp) - assertTabs({ active: 1, orientation: 'vertical' }) - await press(Keys.Enter) - assertTabs({ active: 0, orientation: 'vertical' }) - }) + + Content 1 + Content 2 + Content 3 + + - it('should wrap around at the beginning (activation = `auto`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + + + ) - - Content 1 - Content 2 - Content 3 - - + assertActiveElement(document.body) - - - ) + await press(Keys.Tab) + assertTabs({ active: 2, orientation: 'vertical' }) - assertActiveElement(document.body) + await press(Keys.ArrowUp) + assertTabs({ active: 2, orientation: 'vertical' }) + await press(Keys.Enter) + assertTabs({ active: 1, orientation: 'vertical' }) - await press(Keys.Tab) - assertTabs({ active: 2, 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: 1, orientation: 'vertical' }) + it( + 'should wrap around at the beginning (activation = `auto`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - await press(Keys.ArrowUp) - assertTabs({ active: 0, orientation: 'vertical' }) + + Content 1 + Content 2 + Content 3 + + - await press(Keys.ArrowUp) - assertTabs({ active: 2, orientation: 'vertical' }) + + + ) - await press(Keys.ArrowUp) - assertTabs({ active: 1, orientation: 'vertical' }) - }) + assertActiveElement(document.body) - it('should wrap around at the beginning (activation = `manual`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + await press(Keys.Tab) + assertTabs({ active: 2, orientation: 'vertical' }) - - Content 1 - Content 2 - Content 3 - - + await press(Keys.ArrowUp) + assertTabs({ active: 1, orientation: 'vertical' }) - - - ) + await press(Keys.ArrowUp) + assertTabs({ active: 0, orientation: 'vertical' }) - assertActiveElement(document.body) + await press(Keys.ArrowUp) + assertTabs({ active: 2, orientation: 'vertical' }) - 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' }) - }) + await press(Keys.ArrowUp) + assertTabs({ active: 1, orientation: 'vertical' }) + }) + ) - it('should not be possible to go left when in vertical mode (activation = `auto`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should wrap around at the beginning (activation = `manual`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 0 }) + await press(Keys.Tab) + assertTabs({ active: 2, orientation: 'vertical' }) - await press(Keys.ArrowUp) - // no-op - assertTabs({ active: 0 }) - }) + 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 = `manual`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + await press(Keys.ArrowUp) + assertTabs({ active: 1, orientation: 'vertical' }) + await press(Keys.Enter) + assertTabs({ active: 0, orientation: 'vertical' }) - - Content 1 - Content 2 - Content 3 - - + 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' }) + }) + ) - assertActiveElement(document.body) + it( + 'should not be possible to go left when in vertical mode (activation = `auto`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - await press(Keys.Tab) - assertTabs({ active: 0 }) + + Content 1 + Content 2 + Content 3 + + - await press(Keys.ArrowUp) - assertTabs({ active: 0 }) - await press(Keys.Enter) + + + ) - // no-op - assertTabs({ active: 0 }) - }) + 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`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + + + + Content 1 + Content 2 + Content 3 + + + + + + ) + + 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( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should be possible to go to the first focusable item (activation = `auto`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 1 }) + await press(Keys.Tab) + assertTabs({ active: 1 }) - await press(Keys.Home) - assertTabs({ active: 0 }) - }) + await press(Keys.Home) + assertTabs({ active: 0 }) + }) + ) - it('should be possible to go to the first focusable item (activation = `manual`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should be possible to go to the first focusable item (activation = `manual`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 1 }) + await press(Keys.Tab) + assertTabs({ active: 1 }) - await press(Keys.Home) - assertTabs({ active: 1 }) - await press(Keys.Enter) - assertTabs({ active: 0 }) - }) + 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( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should be possible to go to the first focusable item (activation = `auto`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 1 }) + await press(Keys.Tab) + assertTabs({ active: 1 }) - await press(Keys.PageUp) - assertTabs({ active: 0 }) - }) + await press(Keys.PageUp) + assertTabs({ active: 0 }) + }) + ) - it('should be possible to go to the first focusable item (activation = `manual`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should be possible to go to the first focusable item (activation = `manual`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + + + + Content 1 + Content 2 + Content 3 + + + + + + ) + + 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`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + + + + Content 1 + Content 2 + Content 3 + + + + + + ) + + 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`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 1 }) + await press(Keys.Tab) + assertTabs({ active: 1 }) - await press(Keys.PageUp) - assertTabs({ active: 1 }) - await press(Keys.Enter) - assertTabs({ active: 0 }) - }) + await press(Keys.End) + assertTabs({ active: 1 }) + await press(Keys.Enter) + assertTabs({ active: 2 }) + }) + ) }) - describe('`End` key', () => { - it('should be possible to go to the first focusable item (activation = `auto`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + describe('`PageDown` key', () => { + it( + 'should be possible to go to the first focusable item (activation = `auto`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 1 }) + await press(Keys.Tab) + assertTabs({ active: 1 }) - await press(Keys.End) - assertTabs({ active: 2 }) - }) + await press(Keys.PageDown) + assertTabs({ active: 2 }) + }) + ) - it('should be possible to go to the first focusable item (activation = `manual`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + it( + 'should be possible to go to the first focusable item (activation = `manual`)', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 1 }) + await press(Keys.Tab) + assertTabs({ active: 1 }) - await press(Keys.End) - assertTabs({ active: 1 }) - await press(Keys.Enter) - assertTabs({ active: 2 }) - }) + await press(Keys.PageDown) + 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( - <> - - - Tab 1 - Tab 2 - Tab 3 - + describe('`Enter` key', () => { + it( + 'should be possible to activate the focused tab', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - Content 1 - Content 2 - Content 3 - - + + Content 1 + Content 2 + Content 3 + + - - - ) + + + ) - assertActiveElement(document.body) + assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 1 }) + getByText('Tab 3')?.focus() - await press(Keys.PageDown) - assertTabs({ active: 2 }) - }) + assertActiveElement(getByText('Tab 3')) + assertTabs({ active: 0 }) - it('should be possible to go to the first focusable item (activation = `manual`)', async () => { - render( - <> - - - Tab 1 - Tab 2 - Tab 3 - + await press(Keys.Enter) + assertTabs({ active: 2 }) + }) + ) + }) - - Content 1 - Content 2 - Content 3 - - + describe('`Space` key', () => { + it( + 'should be possible to activate the focused tab', + suppressConsoleLogs(async () => { + render( + <> + + + Tab 1 + Tab 2 + Tab 3 + - - - ) + + Content 1 + Content 2 + Content 3 + + - assertActiveElement(document.body) + + + ) - await press(Keys.Tab) - assertTabs({ active: 1 }) + assertActiveElement(document.body) - await press(Keys.PageDown) - assertTabs({ active: 1 }) - await press(Keys.Enter) - assertTabs({ active: 2 }) - }) + getByText('Tab 3')?.focus() + + assertActiveElement(getByText('Tab 3')) + assertTabs({ active: 0 }) + + await press(Keys.Space) + assertTabs({ active: 2 }) + }) + ) }) +}) - describe('`Enter` key', () => { - it('should be possible to activate the focused tab', async () => { +describe('Mouse interactions', () => { + it( + 'should be possible to click on a tab to focus it', + suppressConsoleLogs(async () => { render( <> - + Tab 1 Tab 2 @@ -2033,24 +2284,28 @@ describe('Keyboard interactions', () => { ) assertActiveElement(document.body) + await press(Keys.Tab) + assertTabs({ active: 1 }) - getByText('Tab 3')?.focus() - - assertActiveElement(getByText('Tab 3')) + await click(getByText('Tab 1')) assertTabs({ active: 0 }) - await press(Keys.Enter) + await click(getByText('Tab 3')) assertTabs({ active: 2 }) + + await click(getByText('Tab 2')) + assertTabs({ active: 1 }) }) - }) + ) - describe('`Space` key', () => { - it('should be possible to activate the focused tab', async () => { + it( + 'should be a no-op when clicking on a disabled tab', + suppressConsoleLogs(async () => { render( <> - + - Tab 1 + Tab 1 Tab 2 Tab 3 @@ -2067,23 +2322,24 @@ describe('Keyboard interactions', () => { ) assertActiveElement(document.body) + await press(Keys.Tab) + assertTabs({ active: 1 }) - getByText('Tab 3')?.focus() - - assertActiveElement(getByText('Tab 3')) - assertTabs({ active: 0 }) - - await press(Keys.Space) - assertTabs({ active: 2 }) + await click(getByText('Tab 1')) + // No-op, Tab 2 is still active + assertTabs({ active: 1 }) }) - }) + ) }) -describe('Mouse interactions', () => { - it('should be possible to click on a tab to focus it', async () => { +it( + 'should trigger the `onChange` when the tab changes', + suppressConsoleLogs(async () => { + let changes = jest.fn() + render( <> - + Tab 1 Tab 2 @@ -2101,83 +2357,16 @@ describe('Mouse interactions', () => { ) - assertActiveElement(document.body) - await press(Keys.Tab) - assertTabs({ active: 1 }) - - await click(getByText('Tab 1')) - assertTabs({ active: 0 }) - + await click(getByText('Tab 2')) 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( - <> - - - Tab 1 - Tab 2 - Tab 3 - - - - Content 1 - Content 2 - Content 3 - - - - - - ) - - 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 `onChange` when the tab changes', async () => { - let changes = jest.fn() - - render( - <> - - - 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).toHaveBeenCalledTimes(4) - expect(changes).toHaveBeenNthCalledWith(1, 1) - expect(changes).toHaveBeenNthCalledWith(2, 2) - expect(changes).toHaveBeenNthCalledWith(3, 1) - expect(changes).toHaveBeenNthCalledWith(4, 0) -}) + expect(changes).toHaveBeenNthCalledWith(1, 1) + expect(changes).toHaveBeenNthCalledWith(2, 2) + expect(changes).toHaveBeenNthCalledWith(3, 1) + expect(changes).toHaveBeenNthCalledWith(4, 0) + }) +) From 1cb1d0ae69882648f256578ef789542d09ca0f29 Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Thu, 10 Mar 2022 19:00:11 +0100 Subject: [PATCH 6/6] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 393dbb1651..8b8e88707d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Remove `focus()` from Listbox Option ([#1218](https://github.com/tailwindlabs/headlessui/pull/1218)) - Improve some internal code ([#1221](https://github.com/tailwindlabs/headlessui/pull/1221)) - Use `ownerDocument` instead of `document` ([#1158](https://github.com/tailwindlabs/headlessui/pull/1158)) +- Ensure focus trap, Tabs and Dialog play well together ([#1231](https://github.com/tailwindlabs/headlessui/pull/1231)) ### Added @@ -52,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Don’t drop initial character when searching in Combobox ([#1223](https://github.com/tailwindlabs/headlessui/pull/1223)) - Use `ownerDocument` instead of `document` ([#1158](https://github.com/tailwindlabs/headlessui/pull/1158)) - Re-expose `el` ([#1230](https://github.com/tailwindlabs/headlessui/pull/1230)) +- Ensure focus trap, Tabs and Dialog play well together ([#1231](https://github.com/tailwindlabs/headlessui/pull/1231)) ### Added