diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 44e453668955a..049863f7bee05 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -28,6 +28,10 @@ - `CheckboxControl`: Remove ability for label prop to be false ([#58339](https://github.com/WordPress/gutenberg/pull/58339)). +### Internal + +- `Composite`: Removing Reakit `Composite` implementation ([#58620](https://github.com/WordPress/gutenberg/pull/58620)). + ## 25.16.0 (2024-01-24) ### Enhancements diff --git a/packages/components/src/composite/index.ts b/packages/components/src/composite/index.ts index f0dd4ef8995b5..aa06a6adf36ef 100644 --- a/packages/components/src/composite/index.ts +++ b/packages/components/src/composite/index.ts @@ -1,4 +1,7 @@ -// Until we migrate away from Reakit, the 'unstable' -// implementation remains the default. +// Originally this pointed at a Reakit implementation of +// `Composite`, but we are removing Reakit entirely from the +// codebase. We will continue to support the Reakit API +// through the 'legacy' version, which uses Ariakit under +// the hood. -export * from './unstable'; +export * from './legacy'; diff --git a/packages/components/src/composite/legacy/index.tsx b/packages/components/src/composite/legacy/index.tsx index 06a82461be9a1..5c5c674b5086b 100644 --- a/packages/components/src/composite/legacy/index.tsx +++ b/packages/components/src/composite/legacy/index.tsx @@ -12,7 +12,6 @@ * WordPress dependencies */ import { forwardRef } from '@wordpress/element'; -import deprecated from '@wordpress/deprecated'; /** * Internal dependencies @@ -99,14 +98,6 @@ type CompositeComponentProps = CompositeState & | ComponentProps< typeof Current.CompositeRow > ); -function showDeprecationMessage( previous?: string, next?: string ) { - if ( previous ) { - deprecated( `wp.components.__unstable${ previous }`, { - alternative: `wp.components.${ next || previous }`, - } ); - } -} - function mapLegacyStatePropsToComponentProps( legacyProps: CompositeStateProps ): CompositeComponentProps { @@ -128,8 +119,6 @@ function proxyComposite< C extends Component >( ): CompositeComponent< C > { const displayName = ProxiedComponent.displayName; const Component = ( legacyProps: CompositeStateProps ) => { - showDeprecationMessage( displayName ); - const { store, ...rest } = mapLegacyStatePropsToComponentProps( legacyProps ); const props = rest as ComponentProps< C >; @@ -175,8 +164,6 @@ export const CompositeItem = proxyComposite( Current.CompositeItem, { export function useCompositeState( legacyStateOptions: LegacyStateOptions = {} ): CompositeState { - showDeprecationMessage( 'UseCompositeState', 'useCompositeStore' ); - const { baseId, currentId: defaultActiveId, diff --git a/packages/components/src/composite/legacy/stories/index.story.tsx b/packages/components/src/composite/legacy/stories/index.story.tsx index a11e4838125d9..e46d656a16810 100644 --- a/packages/components/src/composite/legacy/stories/index.story.tsx +++ b/packages/components/src/composite/legacy/stories/index.story.tsx @@ -15,7 +15,7 @@ import { import { UseCompositeStatePlaceholder, transform } from './utils'; const meta: Meta< typeof UseCompositeStatePlaceholder > = { - title: 'Components/Composite (Legacy)', + title: 'Components/Composite', component: UseCompositeStatePlaceholder, subcomponents: { Composite, diff --git a/packages/components/src/composite/legacy/test/index.tsx b/packages/components/src/composite/legacy/test/index.tsx index dd57d6373b2bf..ee6654f9fd369 100644 --- a/packages/components/src/composite/legacy/test/index.tsx +++ b/packages/components/src/composite/legacy/test/index.tsx @@ -41,8 +41,6 @@ type InitialState = Parameters< typeof useCompositeState >[ 0 ]; type CompositeState = ReturnType< typeof useCompositeState >; type CompositeStateProps = CompositeState | { state: CompositeState }; -const warningsIssued = new Map(); - async function renderAndValidate( ...args: Parameters< typeof render > ) { const view = render( ...args ); await waitFor( () => { @@ -180,20 +178,6 @@ describe.each( [ ); renderAndValidate( ); - // Using the legacy composite components issues a deprecation - // warning, but only on the first usage. As such, we only - // expect `console` to warn once; any further rendering - // should not warn. - const warningKey = 'single tab stop'; - if ( warningsIssued.get( warningKey ) ) { - // eslint-disable-next-line jest/no-conditional-expect - expect( console ).not.toHaveWarned(); - } else { - // eslint-disable-next-line jest/no-conditional-expect - expect( console ).toHaveWarned(); - warningsIssued.set( warningKey, true ); - } - await press.Tab(); expect( screen.getByText( 'Before' ) ).toHaveFocus(); await press.Tab(); @@ -450,20 +434,6 @@ describe.each( [ const { itemA1, itemA2, itemA3, itemB1, itemB2, itemC1, itemC3 } = useTwoDimensionalTest(); - // Using the legacy composite components issues a deprecation - // warning, but only on the first usage. As such, we only - // expect `console` to warn once; any further rendering - // should not warn. - const warningKey = 'directions'; - if ( warningsIssued.get( warningKey ) ) { - // eslint-disable-next-line jest/no-conditional-expect - expect( console ).not.toHaveWarned(); - } else { - // eslint-disable-next-line jest/no-conditional-expect - expect( console ).toHaveWarned(); - warningsIssued.set( warningKey, true ); - } - await press.Tab(); expect( itemA1 ).toHaveFocus(); await press.ArrowUp(); diff --git a/packages/components/src/composite/unstable/index.ts b/packages/components/src/composite/unstable/index.ts deleted file mode 100644 index f2a195091dff9..0000000000000 --- a/packages/components/src/composite/unstable/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Composite is a component that may contain navigable items represented by - * CompositeItem. It's inspired by the WAI-ARIA Composite Role and implements - * all the keyboard navigation mechanisms to ensure that there's only one - * tab stop for the whole Composite element. This means that it can behave as - * a roving tabindex or aria-activedescendant container. - * - * @see https://reakit.io/docs/composite/ - * - * This is now entirely deprecated in favor of [current](../current), which is - * based on Ariakit. A [legacy](../legacy) implementation of this API has been - * created for backwards-compatibility, which will eventually replace this. - */ -/* eslint-disable-next-line no-restricted-imports */ -export { - Composite, - CompositeGroup, - CompositeItem, - useCompositeState, -} from 'reakit'; - -/* eslint-disable-next-line no-restricted-imports */ -export type { CompositeStateReturn as CompositeState } from 'reakit'; diff --git a/packages/components/src/composite/unstable/stories/index.story.tsx b/packages/components/src/composite/unstable/stories/index.story.tsx deleted file mode 100644 index 54543e80fc480..0000000000000 --- a/packages/components/src/composite/unstable/stories/index.story.tsx +++ /dev/null @@ -1,151 +0,0 @@ -/** - * External dependencies - */ -import type { Meta, StoryFn } from '@storybook/react'; - -/** - * Internal dependencies - */ -import { - Composite, - CompositeGroup, - CompositeItem, - useCompositeState, -} from '..'; - -import Legacy from '../../legacy/stories/index.story'; -import { UseCompositeStatePlaceholder, transform } from './utils'; - -Composite.displayName = 'Composite'; -CompositeGroup.displayName = 'CompositeGroup'; -CompositeItem.displayName = 'CompositeItem'; - -const meta: Meta< typeof UseCompositeStatePlaceholder > = { - title: 'Components/Composite', - component: UseCompositeStatePlaceholder, - subcomponents: { - // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 - Composite, - // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 - CompositeGroup, - // @ts-expect-error - See https://github.com/storybookjs/storybook/issues/23170 - CompositeItem, - }, - argTypes: { ...Legacy.argTypes }, - parameters: { - docs: { - canvas: { sourceState: 'shown' }, - source: { transform }, - }, - }, - decorators: [ - // Because of the way Reakit caches state, this is a hack to make sure - // stories update when config is changed. - ( Story ) => ( -
- -
- ), - ], -}; -export default meta; - -export const TwoDimensionsWithStateProp: StoryFn< - typeof UseCompositeStatePlaceholder -> = ( initialState ) => { - const state = useCompositeState( initialState ); - - return ( - - - Item A1 - Item A2 - Item A3 - - - Item B1 - Item B2 - Item B3 - - - Item C1 - Item C2 - Item C3 - - - ); -}; -TwoDimensionsWithStateProp.args = {}; - -export const TwoDimensionsWithSpreadProps: StoryFn< - typeof UseCompositeStatePlaceholder -> = ( initialState ) => { - const state = useCompositeState( initialState ); - - return ( - - - Item A1 - Item A2 - Item A3 - - - Item B1 - Item B2 - Item B3 - - - Item C1 - Item C2 - Item C3 - - - ); -}; -TwoDimensionsWithSpreadProps.args = {}; - -export const OneDimensionWithStateProp: StoryFn< - typeof UseCompositeStatePlaceholder -> = ( initialState ) => { - const state = useCompositeState( initialState ); - - return ( - - Item 1 - Item 2 - Item 3 - Item 4 - Item 5 - - ); -}; -OneDimensionWithStateProp.args = {}; - -export const OneDimensionWithSpreadProps: StoryFn< - typeof UseCompositeStatePlaceholder -> = ( initialState ) => { - const state = useCompositeState( initialState ); - - return ( - - Item 1 - Item 2 - Item 3 - Item 4 - Item 5 - - ); -}; -OneDimensionWithSpreadProps.args = {}; diff --git a/packages/components/src/composite/unstable/stories/utils.tsx b/packages/components/src/composite/unstable/stories/utils.tsx deleted file mode 100644 index ed79e5ae85044..0000000000000 --- a/packages/components/src/composite/unstable/stories/utils.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/** - * External dependencies - */ -import type { StoryContext } from '@storybook/react'; - -/** - * Internal dependencies - */ -import type { CompositeState as FullCompositeState } from '..'; - -type CompositeState = Pick< - FullCompositeState, - 'baseId' | 'rtl' | 'orientation' | 'currentId' | 'loop' | 'wrap' | 'shift' ->; - -export function UseCompositeStatePlaceholder( props: CompositeState ) { - return ( -
- { Object.entries( props ).map( ( [ name, value ] ) => ( - <> -
{ name }
-
{ JSON.stringify( value ) }
- - ) ) } -
- ); -} -UseCompositeStatePlaceholder.displayName = 'useCompositeState'; - -export function transform( code: string, context: StoryContext ) { - // The output generated by Storybook for these components is - // messy, so we apply this transform to make it more useful - // for anyone reading the docs. - const config = ` ${ JSON.stringify( context.args, null, 2 ) } `; - const state = config.replace( ' {} ', '' ); - return [ - // Include a setup line, showing how to make use of - // `useCompositeState` to convert state options into - // a composite state option. - `const state = useCompositeState(${ state });`, - '', - 'return (', - ' ' + - code - // The generated output includes a full dump of everything - // in the state; the reader probably isn't interested in - // what that looks like, so instead we drop all of that - // in favor of the state generated above. - .replaceAll( /state=\{\{[\s\S]*?\}\}/g, 'state={ state }' ) - // The previous line only works for `state={ state }`, and - // doesn't replace spread props, so we do that separately. - .replaceAll( '=>', '' ) - .replaceAll( /baseId=[^>]+?(\s*>)/g, ( _, close ) => { - return `{ ...state }${ close }`; - } ) - // Now we tidy the output by removing any unnecessary - // whitespace... - .replaceAll( //g, ( match ) => - match.replaceAll( /\s+\s/g, ' ' ) - ) - // ...including around children... - .replaceAll( - / >\s+([\w\s]*?)\s+<\//g, - ( _, value ) => `>${ value }', '}>' ) - // Finally we indent everything to make it more readable. - .replaceAll( /\n/g, '\n ' ), - ');', - ].join( '\n' ); -} diff --git a/packages/components/src/composite/unstable/test/index.tsx b/packages/components/src/composite/unstable/test/index.tsx deleted file mode 100644 index bb565f63f0cda..0000000000000 --- a/packages/components/src/composite/unstable/test/index.tsx +++ /dev/null @@ -1,543 +0,0 @@ -/** - * External dependencies - */ -import { render, screen } from '@testing-library/react'; -import { press } from '@ariakit/test'; - -/** - * Internal dependencies - */ -import { - Composite, - CompositeGroup, - CompositeItem, - useCompositeState, -} from '..'; - -type InitialState = Parameters< typeof useCompositeState >[ 0 ]; -type CompositeState = ReturnType< typeof useCompositeState >; -type CompositeStateProps = CompositeState | { state: CompositeState }; - -function getKeys( rtl: boolean ) { - return { - previous: rtl ? 'ArrowRight' : 'ArrowLeft', - next: rtl ? 'ArrowLeft' : 'ArrowRight', - first: 'Home', - last: 'End', - }; -} - -function OneDimensionalTest( props: CompositeStateProps ) { - return ( - - Item 1 - Item 2 - Item 3 - - ); -} - -function getOneDimensionalItems() { - return { - item1: screen.getByText( 'Item 1' ), - item2: screen.getByText( 'Item 2' ), - item3: screen.getByText( 'Item 3' ), - }; -} - -function TwoDimensionalTest( props: CompositeStateProps ) { - return ( - - - Item A1 - Item A2 - Item A3 - - - Item B1 - Item B2 - Item B3 - - - Item C1 - Item C2 - Item C3 - - - ); -} - -function getTwoDimensionalItems() { - return { - itemA1: screen.getByText( 'Item A1' ), - itemA2: screen.getByText( 'Item A2' ), - itemA3: screen.getByText( 'Item A3' ), - itemB1: screen.getByText( 'Item B1' ), - itemB2: screen.getByText( 'Item B2' ), - itemB3: screen.getByText( 'Item B3' ), - itemC1: screen.getByText( 'Item C1' ), - itemC2: screen.getByText( 'Item C2' ), - itemC3: screen.getByText( 'Item C3' ), - }; -} - -function ShiftTest( props: CompositeStateProps ) { - return ( - - - Item A1 - - - Item B1 - Item B2 - - - Item C1 - - Item C2 - - - - ); -} - -function getShiftTestItems() { - return { - itemA1: screen.getByText( 'Item A1' ), - itemB1: screen.getByText( 'Item B1' ), - itemB2: screen.getByText( 'Item B2' ), - itemC1: screen.getByText( 'Item C1' ), - itemC2: screen.getByText( 'Item C2' ), - }; -} - -describe.each( [ - [ - 'With "spread" state', - ( initialState?: InitialState ) => useCompositeState( initialState ), - ], - [ - 'With `state` prop', - ( initialState?: InitialState ) => ( { - state: useCompositeState( initialState ), - } ), - ], -] )( '%s', ( __, useProps ) => { - test( 'Renders as a single tab stop', async () => { - const Test = () => ( - <> - - - - - ); - render( ); - - await press.Tab(); - expect( screen.getByText( 'Before' ) ).toHaveFocus(); - await press.Tab(); - expect( screen.getByText( 'Item 1' ) ).toHaveFocus(); - await press.Tab(); - expect( screen.getByText( 'After' ) ).toHaveFocus(); - await press.ShiftTab(); - expect( screen.getByText( 'Item 1' ) ).toHaveFocus(); - } ); - - test( 'Excludes disabled items', async () => { - const Test = () => { - const props = useProps(); - return ( - - Item 1 - - Item 2 - - Item 3 - - ); - }; - render( ); - - const { item1, item2, item3 } = getOneDimensionalItems(); - - expect( item2 ).toBeDisabled(); - - await press.Tab(); - expect( item1 ).toHaveFocus(); - await press.ArrowDown(); - expect( item2 ).not.toHaveFocus(); - expect( item3 ).toHaveFocus(); - } ); - - test( 'Includes focusable disabled items', async () => { - const Test = () => { - const props = useProps(); - return ( - - Item 1 - - Item 2 - - Item 3 - - ); - }; - render( ); - const { item1, item2, item3 } = getOneDimensionalItems(); - - expect( item2 ).toBeEnabled(); - expect( item2 ).toHaveAttribute( 'aria-disabled', 'true' ); - - await press.Tab(); - expect( item1 ).toHaveFocus(); - await press.ArrowDown(); - expect( item2 ).toHaveFocus(); - expect( item3 ).not.toHaveFocus(); - } ); - - test( 'Supports `baseId`', async () => { - const Test = () => ( - - ); - render( ); - const { item1, item2, item3 } = getOneDimensionalItems(); - - expect( item1.id ).toMatch( 'test-id-1' ); - expect( item2.id ).toMatch( 'test-id-2' ); - expect( item3.id ).toMatch( 'test-id-3' ); - } ); - - test( 'Supports `currentId`', async () => { - const Test = () => ( - - ); - render( ); - const { item2 } = getOneDimensionalItems(); - - await press.Tab(); - expect( item2 ).toHaveFocus(); - } ); -} ); - -describe.each( [ - [ 'When LTR', false ], - [ 'When RTL', true ], -] )( '%s', ( _when, rtl ) => { - const { previous, next, first, last } = getKeys( rtl ); - - function useOneDimensionalTest( initialState?: InitialState ) { - const Test = () => ( - - ); - render( ); - return getOneDimensionalItems(); - } - - function useTwoDimensionalTest( initialState?: InitialState ) { - const Test = () => ( - - ); - render( ); - return getTwoDimensionalItems(); - } - - function useShiftTest( shift: boolean ) { - const Test = () => ( - - ); - render( ); - return getShiftTestItems(); - } - - describe( 'In one dimension', () => { - test( 'All directions work with no orientation', async () => { - const { item1, item2, item3 } = useOneDimensionalTest(); - - await press.Tab(); - expect( item1 ).toHaveFocus(); - await press.ArrowDown(); - expect( item2 ).toHaveFocus(); - await press.ArrowDown(); - expect( item3 ).toHaveFocus(); - await press.ArrowDown(); - expect( item3 ).toHaveFocus(); - await press.ArrowUp(); - expect( item2 ).toHaveFocus(); - await press.ArrowUp(); - expect( item1 ).toHaveFocus(); - await press.ArrowUp(); - expect( item1 ).toHaveFocus(); - await press( next ); - expect( item2 ).toHaveFocus(); - await press( next ); - expect( item3 ).toHaveFocus(); - await press( previous ); - expect( item2 ).toHaveFocus(); - await press( previous ); - expect( item1 ).toHaveFocus(); - await press.End(); - expect( item3 ).toHaveFocus(); - await press.Home(); - expect( item1 ).toHaveFocus(); - await press.PageDown(); - expect( item3 ).toHaveFocus(); - await press.PageUp(); - expect( item1 ).toHaveFocus(); - } ); - - test( 'Only left/right work with horizontal orientation', async () => { - const { item1, item2, item3 } = useOneDimensionalTest( { - orientation: 'horizontal', - } ); - - await press.Tab(); - expect( item1 ).toHaveFocus(); - await press.ArrowDown(); - expect( item1 ).toHaveFocus(); - await press( next ); - expect( item2 ).toHaveFocus(); - await press( next ); - expect( item3 ).toHaveFocus(); - await press.ArrowUp(); - expect( item3 ).toHaveFocus(); - await press( previous ); - expect( item2 ).toHaveFocus(); - await press( previous ); - expect( item1 ).toHaveFocus(); - await press.End(); - expect( item3 ).toHaveFocus(); - await press.Home(); - expect( item1 ).toHaveFocus(); - await press.PageDown(); - expect( item3 ).toHaveFocus(); - await press.PageUp(); - expect( item1 ).toHaveFocus(); - } ); - - test( 'Only up/down work with vertical orientation', async () => { - const { item1, item2, item3 } = useOneDimensionalTest( { - orientation: 'vertical', - } ); - - await press.Tab(); - expect( item1 ).toHaveFocus(); - await press( next ); - expect( item1 ).toHaveFocus(); - await press.ArrowDown(); - expect( item2 ).toHaveFocus(); - await press.ArrowDown(); - expect( item3 ).toHaveFocus(); - await press( previous ); - expect( item3 ).toHaveFocus(); - await press.ArrowUp(); - expect( item2 ).toHaveFocus(); - await press.ArrowUp(); - expect( item1 ).toHaveFocus(); - await press.End(); - expect( item3 ).toHaveFocus(); - await press.Home(); - expect( item1 ).toHaveFocus(); - await press.PageDown(); - expect( item3 ).toHaveFocus(); - await press.PageUp(); - expect( item1 ).toHaveFocus(); - } ); - - test( 'Focus wraps with loop enabled', async () => { - const { item1, item2, item3 } = useOneDimensionalTest( { - loop: true, - } ); - - await press.Tab(); - expect( item1 ).toHaveFocus(); - await press.ArrowDown(); - expect( item2 ).toHaveFocus(); - await press.ArrowDown(); - expect( item3 ).toHaveFocus(); - await press.ArrowDown(); - expect( item1 ).toHaveFocus(); - await press.ArrowUp(); - expect( item3 ).toHaveFocus(); - await press( next ); - expect( item1 ).toHaveFocus(); - await press( previous ); - expect( item3 ).toHaveFocus(); - } ); - } ); - - describe( 'In two dimensions', () => { - test( 'All directions work as standard', async () => { - const { itemA1, itemA2, itemA3, itemB1, itemB2, itemC1, itemC3 } = - useTwoDimensionalTest(); - - await press.Tab(); - expect( itemA1 ).toHaveFocus(); - await press.ArrowUp(); - expect( itemA1 ).toHaveFocus(); - await press( previous ); - expect( itemA1 ).toHaveFocus(); - await press.ArrowDown(); - expect( itemB1 ).toHaveFocus(); - await press( next ); - expect( itemB2 ).toHaveFocus(); - await press.ArrowUp(); - expect( itemA2 ).toHaveFocus(); - await press( previous ); - expect( itemA1 ).toHaveFocus(); - await press( last ); - expect( itemA3 ).toHaveFocus(); - await press.PageDown(); - expect( itemC3 ).toHaveFocus(); - await press( next ); - expect( itemC3 ).toHaveFocus(); - await press.ArrowDown(); - expect( itemC3 ).toHaveFocus(); - await press( first ); - expect( itemC1 ).toHaveFocus(); - await press.PageUp(); - expect( itemA1 ).toHaveFocus(); - await press.End( null, { ctrlKey: true } ); - expect( itemC3 ).toHaveFocus(); - await press.Home( null, { ctrlKey: true } ); - expect( itemA1 ).toHaveFocus(); - } ); - - test( 'Focus wraps around rows/columns with loop enabled', async () => { - const { itemA1, itemA2, itemA3, itemB1, itemC1, itemC3 } = - useTwoDimensionalTest( { loop: true } ); - - await press.Tab(); - expect( itemA1 ).toHaveFocus(); - await press( next ); - expect( itemA2 ).toHaveFocus(); - await press( next ); - expect( itemA3 ).toHaveFocus(); - await press( next ); - expect( itemA1 ).toHaveFocus(); - await press.ArrowDown(); - expect( itemB1 ).toHaveFocus(); - await press.ArrowDown(); - expect( itemC1 ).toHaveFocus(); - await press.ArrowDown(); - expect( itemA1 ).toHaveFocus(); - await press( previous ); - expect( itemA3 ).toHaveFocus(); - await press.ArrowUp(); - expect( itemC3 ).toHaveFocus(); - } ); - - test( 'Focus moves between rows/columns with wrap enabled', async () => { - const { itemA1, itemA2, itemA3, itemB1, itemC1, itemC3 } = - useTwoDimensionalTest( { wrap: true } ); - - await press.Tab(); - expect( itemA1 ).toHaveFocus(); - await press( next ); - expect( itemA2 ).toHaveFocus(); - await press( next ); - expect( itemA3 ).toHaveFocus(); - await press( next ); - expect( itemB1 ).toHaveFocus(); - await press.ArrowDown(); - expect( itemC1 ).toHaveFocus(); - await press.ArrowDown(); - expect( itemA2 ).toHaveFocus(); - await press( previous ); - expect( itemA1 ).toHaveFocus(); - await press( previous ); - expect( itemA1 ).toHaveFocus(); - await press.ArrowUp(); - expect( itemA1 ).toHaveFocus(); - await press.End( itemA1, { ctrlKey: true } ); - expect( itemC3 ).toHaveFocus(); - await press( next ); - expect( itemC3 ).toHaveFocus(); - await press.ArrowDown(); - expect( itemC3 ).toHaveFocus(); - } ); - - test( 'Focus wraps around start/end with loop and wrap enabled', async () => { - const { itemA1, itemC3 } = useTwoDimensionalTest( { - loop: true, - wrap: true, - } ); - - await press.Tab(); - expect( itemA1 ).toHaveFocus(); - await press( previous ); - expect( itemC3 ).toHaveFocus(); - await press.ArrowDown(); - expect( itemA1 ).toHaveFocus(); - await press.ArrowUp(); - expect( itemC3 ).toHaveFocus(); - await press( next ); - expect( itemA1 ).toHaveFocus(); - } ); - - test( 'Focus shifts if vertical neighbour unavailable when shift enabled', async () => { - const { itemA1, itemB1, itemB2, itemC1 } = useShiftTest( true ); - - await press.Tab(); - expect( itemA1 ).toHaveFocus(); - await press.ArrowDown(); - expect( itemB1 ).toHaveFocus(); - await press( next ); - expect( itemB2 ).toHaveFocus(); - await press.ArrowUp(); - // A2 doesn't exist - expect( itemA1 ).toHaveFocus(); - await press.ArrowDown(); - expect( itemB1 ).toHaveFocus(); - await press( next ); - expect( itemB2 ).toHaveFocus(); - await press.ArrowDown(); - // C2 is disabled - expect( itemC1 ).toHaveFocus(); - } ); - - test( 'Focus does not shift if vertical neighbour unavailable when shift not enabled', async () => { - const { itemA1, itemB1, itemB2 } = useShiftTest( false ); - - await press.Tab(); - expect( itemA1 ).toHaveFocus(); - await press.ArrowDown(); - expect( itemB1 ).toHaveFocus(); - await press( next ); - expect( itemB2 ).toHaveFocus(); - await press.ArrowUp(); - // A2 doesn't exist - expect( itemB2 ).toHaveFocus(); - await press.ArrowDown(); - // C2 is disabled - expect( itemB2 ).toHaveFocus(); - } ); - } ); -} );