From e7b0aa20a522faf6431a833e08463bdc77c199e9 Mon Sep 17 00:00:00 2001
From: chad1008 <13856531+chad1008@users.noreply.github.com>
Date: Thu, 6 Jul 2023 14:39:44 -0400
Subject: [PATCH 12/27] apply Ariakit internals to existing TabPanel component
---
packages/components/src/tab-panel/index.tsx | 212 ++++++++++++--------
packages/components/src/tab-panel/types.ts | 11 +-
2 files changed, 125 insertions(+), 98 deletions(-)
diff --git a/packages/components/src/tab-panel/index.tsx b/packages/components/src/tab-panel/index.tsx
index 20063b2315dd39..d6a95da1e751f5 100644
--- a/packages/components/src/tab-panel/index.tsx
+++ b/packages/components/src/tab-panel/index.tsx
@@ -1,7 +1,8 @@
/**
* External dependencies
*/
-import classnames from 'classnames';
+import * as Ariakit from '@ariakit/react';
+import cx from 'classnames';
import type { ForwardedRef } from 'react';
/**
@@ -9,39 +10,20 @@ import type { ForwardedRef } from 'react';
*/
import {
forwardRef,
- useState,
useEffect,
useLayoutEffect,
useCallback,
} from '@wordpress/element';
-import { useInstanceId } from '@wordpress/compose';
+import { useInstanceId, usePrevious } from '@wordpress/compose';
/**
* Internal dependencies
*/
-import { NavigableMenu } from '../navigable-container';
+
import Button from '../button';
-import type { TabButtonProps, TabPanelProps } from './types';
+import type { TabPanelProps } from './types';
import type { WordPressComponentProps } from '../ui/context';
-const TabButton = ( {
- tabId,
- children,
- selected,
- ...rest
-}: TabButtonProps ) => (
-
-);
-
/**
* TabPanel is an ARIA-compliant tabpanel.
*
@@ -92,26 +74,76 @@ const UnforwardedTabPanel = (
ref: ForwardedRef< any >
) => {
const instanceId = useInstanceId( TabPanel, 'tab-panel' );
- const [ selected, setSelected ] = useState< string >();
- const handleTabSelection = useCallback(
- ( tabKey: string ) => {
- setSelected( tabKey );
- onSelect?.( tabKey );
+ const prependInstanceId = useCallback(
+ ( tabName: string | undefined ) => {
+ if ( typeof tabName === 'undefined' ) {
+ return;
+ }
+ return `${ instanceId }-${ tabName }`;
},
- [ onSelect ]
+ [ instanceId ]
);
- // Simulate a click on the newly focused tab, which causes the component
- // to show the `tab-panel` associated with the clicked tab.
- const activateTabAutomatically = (
- _childIndex: number,
- child: HTMLElement
- ) => {
- child.click();
- };
- const selectedTab = tabs.find( ( { name } ) => name === selected );
- const selectedId = `${ instanceId }-${ selectedTab?.name ?? 'none' }`;
+ // Separate the actual tab name from the instance ID. This is
+ // necessary because Ariakit internally uses the element ID when
+ // a new tab is selected, but our implementation looks specifically
+ // for the tab name to be passed to the `onSelect` callback.
+ const extractTabName = useCallback( ( id: string | undefined | null ) => {
+ if ( typeof id === 'undefined' || id === null ) {
+ return;
+ }
+ return id.match( /^tab-panel-[0-9]*-(.*)/ )?.[ 1 ];
+ }, [] );
+
+ const tabStore = Ariakit.useTabStore( {
+ setSelectedId: ( newTabValue ) => {
+ if ( typeof newTabValue === 'undefined' || newTabValue === null ) {
+ return;
+ }
+
+ const newTab = tabs.find(
+ ( t ) => prependInstanceId( t.name ) === newTabValue
+ );
+ if ( newTab?.disabled || newTab === selectedTab ) {
+ return;
+ }
+
+ const simplifiedTabName = extractTabName( newTabValue );
+ if ( typeof simplifiedTabName === 'undefined' ) {
+ return;
+ }
+
+ onSelect?.( simplifiedTabName );
+ },
+ orientation,
+ selectOnMove,
+ defaultSelectedId: prependInstanceId( initialTabName ),
+ } );
+
+ const selectedTabName = extractTabName( tabStore.useState( 'selectedId' ) );
+
+ const setTabStoreSelectedId = useCallback(
+ ( tabName: string ) => {
+ tabStore.setState( 'selectedId', prependInstanceId( tabName ) );
+ },
+ [ prependInstanceId, tabStore ]
+ );
+
+ const selectedTab = tabs.find( ( { name } ) => name === selectedTabName );
+
+ const previousSelectedTabName = usePrevious( selectedTabName );
+
+ // Ensure `onSelect` is called when the initial tab is selected.
+ useEffect( () => {
+ if (
+ previousSelectedTabName !== selectedTabName &&
+ selectedTabName === initialTabName &&
+ !! selectedTabName
+ ) {
+ onSelect?.( selectedTabName );
+ }
+ }, [ selectedTabName, initialTabName, onSelect, previousSelectedTabName ] );
// Handle selecting the initial tab.
useLayoutEffect( () => {
@@ -119,25 +151,31 @@ const UnforwardedTabPanel = (
if ( selectedTab ) {
return;
}
-
const initialTab = tabs.find( ( tab ) => tab.name === initialTabName );
-
// Wait for the denoted initial tab to be declared before making a
// selection. This ensures that if a tab is declared lazily it can
// still receive initial selection.
if ( initialTabName && ! initialTab ) {
return;
}
-
if ( initialTab && ! initialTab.disabled ) {
// Select the initial tab if it's not disabled.
- handleTabSelection( initialTab.name );
+ setTabStoreSelectedId( initialTab.name );
} else {
- // Fallback to the first enabled tab when the initial is disabled.
+ // Fallback to the first enabled tab when the initial tab is
+ // disabled or it can't be found.
const firstEnabledTab = tabs.find( ( tab ) => ! tab.disabled );
- if ( firstEnabledTab ) handleTabSelection( firstEnabledTab.name );
+ if ( firstEnabledTab ) {
+ setTabStoreSelectedId( firstEnabledTab.name );
+ }
}
- }, [ tabs, selectedTab, initialTabName, handleTabSelection ] );
+ }, [
+ tabs,
+ selectedTab,
+ initialTabName,
+ instanceId,
+ setTabStoreSelectedId,
+ ] );
// Handle the currently selected tab becoming disabled.
useEffect( () => {
@@ -145,60 +183,58 @@ const UnforwardedTabPanel = (
if ( ! selectedTab?.disabled ) {
return;
}
-
const firstEnabledTab = tabs.find( ( tab ) => ! tab.disabled );
-
// If the currently selected tab becomes disabled, select the first enabled tab.
// (if there is one).
if ( firstEnabledTab ) {
- handleTabSelection( firstEnabledTab.name );
+ setTabStoreSelectedId( firstEnabledTab.name );
}
- }, [ tabs, selectedTab?.disabled, handleTabSelection ] );
+ }, [ tabs, selectedTab?.disabled, setTabStoreSelectedId, instanceId ] );
return (
-
- { tabs.map( ( tab ) => (
- {
+ return (
+
}
- ) }
- tabId={ `${ instanceId }-${ tab.name }` }
- aria-controls={ `${ instanceId }-${ tab.name }-view` }
- selected={ tab.name === selected }
- key={ tab.name }
- onClick={ () => handleTabSelection( tab.name ) }
- disabled={ tab.disabled }
- label={ tab.icon && tab.title }
- icon={ tab.icon }
- showTooltip={ !! tab.icon }
- >
- { ! tab.icon && tab.title }
-
- ) ) }
-
- { selectedTab && (
-
+ { ! tab.icon && tab.title }
+
+ );
+ } ) }
+
+ { tabs.map( ( tab ) => (
+
- { children( selectedTab ) }
-
- ) }
+ { children( tab ) }
+
+ ) ) }
);
};
diff --git a/packages/components/src/tab-panel/types.ts b/packages/components/src/tab-panel/types.ts
index 4bef866923ebca..1f4dc7c677483a 100644
--- a/packages/components/src/tab-panel/types.ts
+++ b/packages/components/src/tab-panel/types.ts
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
-import type { MouseEvent, ReactNode } from 'react';
+import type { ReactNode } from 'react';
/**
* Internal dependencies
@@ -31,15 +31,6 @@ type Tab = {
disabled?: boolean;
} & Record< any, any >;
-export type TabButtonProps = {
- children: ReactNode;
- label?: string;
- onClick: ( event: MouseEvent ) => void;
- selected: boolean;
- showTooltip?: boolean;
- tabId: string;
-} & Pick< Tab, 'className' | 'icon' | 'disabled' >;
-
export type TabPanelProps = {
/**
* The class name to add to the active tab.
From 6195305e481f02b4b2af0777e0eb3cbba61b27ae Mon Sep 17 00:00:00 2001
From: chad1008 <13856531+chad1008@users.noreply.github.com>
Date: Thu, 6 Jul 2023 14:51:53 -0400
Subject: [PATCH 13/27] update TabPanel unit tests to work with Ariakit
internals
---
.../components/src/tab-panel/test/index.tsx | 214 +++++++++---------
1 file changed, 108 insertions(+), 106 deletions(-)
diff --git a/packages/components/src/tab-panel/test/index.tsx b/packages/components/src/tab-panel/test/index.tsx
index 16f88ee8a41e98..859ee28f772026 100644
--- a/packages/components/src/tab-panel/test/index.tsx
+++ b/packages/components/src/tab-panel/test/index.tsx
@@ -34,7 +34,8 @@ const TABS = [
},
];
-const getSelectedTab = () => screen.getByRole( 'tab', { selected: true } );
+const getSelectedTab = async () =>
+ await screen.findByRole( 'tab', { selected: true } );
let originalGetClientRects: () => DOMRectList;
@@ -62,7 +63,7 @@ describe.each( [
} );
describe( 'Accessibility and semantics', () => {
- test( 'should use the correct aria attributes', () => {
+ it( 'should use the correct aria attributes', async () => {
const panelRenderFunction = jest.fn();
render(
@@ -71,7 +72,7 @@ describe.each( [
const tabList = screen.getByRole( 'tablist' );
const allTabs = screen.getAllByRole( 'tab' );
- const selectedTabPanel = screen.getByRole( 'tabpanel' );
+ const selectedTabPanel = await screen.findByRole( 'tabpanel' );
expect( tabList ).toBeVisible();
expect( tabList ).toHaveAttribute(
@@ -94,7 +95,7 @@ describe.each( [
);
} );
- test( 'should display a tooltip when hovering tabs provided with an icon', async () => {
+ it( 'should display a tooltip when hovering tabs provided with an icon', async () => {
const user = userEvent.setup();
const panelRenderFunction = jest.fn();
@@ -138,7 +139,7 @@ describe.each( [
}
} );
- test( 'should display a tooltip when moving the selection via the keyboard on tabs provided with an icon', async () => {
+ it( 'should display a tooltip when moving the selection via the keyboard on tabs provided with an icon', async () => {
const user = userEvent.setup();
const mockOnSelect = jest.fn();
@@ -165,10 +166,10 @@ describe.each( [
);
- expect( getSelectedTab() ).not.toHaveTextContent( 'Alpha' );
+ expect( await getSelectedTab() ).not.toHaveTextContent( 'Alpha' );
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
- await expect( getSelectedTab() ).not.toHaveFocus();
+ await expect( await getSelectedTab() ).not.toHaveFocus();
// Tab to focus the tablist. Make sure alpha is focused, and that the
// corresponding tooltip is shown.
@@ -176,7 +177,7 @@ describe.each( [
await user.keyboard( '[Tab]' );
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
expect( screen.getByText( 'Alpha' ) ).toBeInTheDocument();
- await expect( getSelectedTab() ).toHaveFocus();
+ await expect( await getSelectedTab() ).toHaveFocus();
// Move selection with arrow keys. Make sure beta is focused, and that
// the corresponding tooltip is shown.
@@ -185,7 +186,7 @@ describe.each( [
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
expect( screen.getByText( 'Beta' ) ).toBeInTheDocument();
- await expect( getSelectedTab() ).toHaveFocus();
+ await expect( await getSelectedTab() ).toHaveFocus();
// Move selection with arrow keys. Make sure gamma is focused, and that
// the corresponding tooltip is shown.
@@ -194,7 +195,7 @@ describe.each( [
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
expect( screen.getByText( 'Gamma' ) ).toBeInTheDocument();
- await expect( getSelectedTab() ).toHaveFocus();
+ await expect( await getSelectedTab() ).toHaveFocus();
// Move selection with arrow keys. Make sure beta is focused, and that
// the corresponding tooltip is shown.
@@ -203,7 +204,7 @@ describe.each( [
expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
expect( screen.getByText( 'Beta' ) ).toBeInTheDocument();
- await expect( getSelectedTab() ).toHaveFocus();
+ await expect( await getSelectedTab() ).toHaveFocus();
} );
} );
@@ -215,11 +216,10 @@ describe.each( [
);
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
expect(
- screen.getByRole( 'tabpanel', { name: 'Alpha' } )
+ await screen.findByRole( 'tabpanel', { name: 'Alpha' } )
).toBeInTheDocument();
- expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 0 ] );
} );
it( 'should fall back to first enabled tab if the active tab is removed', async () => {
@@ -239,12 +239,12 @@ describe.each( [
onSelect={ mockOnSelect }
/>
);
- expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
} );
} );
describe( 'With `initialTabName`', () => {
- it( 'should render the tab set by initialTabName prop', () => {
+ it( 'should render the tab set by initialTabName prop', async () => {
render(
);
- expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
} );
it( 'should not select a tab when `initialTabName` does not match any known tab', () => {
@@ -273,8 +273,7 @@ describe.each( [
// No tabpanel should be rendered either
expect( screen.queryByRole( 'tabpanel' ) ).not.toBeInTheDocument();
} );
-
- it( 'should not change tabs when initialTabName is changed', () => {
+ it( 'should not change tabs when initialTabName is changed', async () => {
const { rerender } = render(
);
- expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
} );
it( 'should fall back to the tab associated to `initialTabName` if the currently active tab is removed', async () => {
@@ -307,12 +306,12 @@ describe.each( [
/>
);
- expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) );
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
@@ -325,12 +324,12 @@ describe.each( [
/>
);
- expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
+ expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
} );
- it( 'should have no active tabs when the tab associated to `initialTabName` is removed while being the active tab', () => {
+ it( 'should have no active tabs when the tab associated to `initialTabName` is removed while being the active tab', async () => {
const mockOnSelect = jest.fn();
const { rerender } = render(
@@ -342,7 +341,7 @@ describe.each( [
/>
);
- expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
@@ -362,7 +361,7 @@ describe.each( [
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
} );
- it( 'waits for the tab with the `initialTabName` to be present in the `tabs` array before selecting it', () => {
+ it( 'waits for the tab with the `initialTabName` to be present in the `tabs` array before selecting it', async () => {
const mockOnSelect = jest.fn();
const { rerender } = render(
);
- expect( getSelectedTab() ).toHaveTextContent( 'Delta' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Delta' );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'delta' );
} );
} );
@@ -433,7 +432,7 @@ describe.each( [
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
} );
- it( 'should select first enabled tab when the initial tab is disabled', () => {
+ it( 'should select first enabled tab when the initial tab is disabled', async () => {
const mockOnSelect = jest.fn();
const { rerender } = render(
@@ -452,7 +451,7 @@ describe.each( [
// As alpha (first tab) is disabled,
// the first enabled tab should be gamma.
- expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
// Re-enable all tabs
rerender(
@@ -465,10 +464,10 @@ describe.each( [
// Even if the initial tab becomes enabled again, the selected tab doesn't
// change.
- expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
} );
- it( 'should select first enabled tab when the tab associated to `initialTabName` is disabled', () => {
+ it( 'should select first enabled tab when the tab associated to `initialTabName` is disabled', async () => {
const mockOnSelect = jest.fn();
const { rerender } = render(
@@ -487,7 +486,7 @@ describe.each( [
// As alpha (first tab), and beta (the initial tab), are both
// disabled the first enabled tab should be gamma.
- expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
// Re-enable all tabs
rerender(
@@ -501,10 +500,10 @@ describe.each( [
// Even if the initial tab becomes enabled again, the selected tab doesn't
// change.
- expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
} );
- it( 'should select the first enabled tab when the selected tab becomes disabled', () => {
+ it( 'should select the first enabled tab when the selected tab becomes disabled', async () => {
const mockOnSelect = jest.fn();
const { rerender } = render(
);
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
@@ -531,7 +530,7 @@ describe.each( [
/>
);
- expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
@@ -543,12 +542,12 @@ describe.each( [
/>
);
- expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
} );
- it( 'should select the first enabled tab when the tab associated to `initialTabName` becomes disabled while being the active tab', () => {
+ it( 'should select the first enabled tab when the tab associated to `initialTabName` becomes disabled while being the active tab', async () => {
const mockOnSelect = jest.fn();
const { rerender } = render(
@@ -560,7 +559,7 @@ describe.each( [
/>
);
- expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
@@ -577,7 +576,7 @@ describe.each( [
/>
);
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
@@ -590,7 +589,7 @@ describe.each( [
/>
);
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
} );
} );
@@ -610,31 +609,28 @@ describe.each( [
);
// Alpha is the initially selected tab
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
expect(
- screen.getByRole( 'tabpanel', { name: 'Alpha' } )
+ await screen.findByRole( 'tabpanel', { name: 'Alpha' } )
).toBeInTheDocument();
- expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 0 ] );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
// Click on Beta, make sure beta is the selected tab
await user.click( screen.getByRole( 'tab', { name: 'Beta' } ) );
- expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
expect(
screen.getByRole( 'tabpanel', { name: 'Beta' } )
).toBeInTheDocument();
- expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 1 ] );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
// Click on Alpha, make sure beta is the selected tab
await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) );
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
expect(
screen.getByRole( 'tabpanel', { name: 'Alpha' } )
).toBeInTheDocument();
- expect( panelRenderFunction ).toHaveBeenLastCalledWith( TABS[ 0 ] );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
} );
@@ -654,24 +650,24 @@ describe.each( [
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
// Tab to focus the tablist. Make sure alpha is focused.
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( getSelectedTab() ).not.toHaveFocus();
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ await expect( await getSelectedTab() ).not.toHaveFocus();
await user.keyboard( '[Tab]' );
- await expect( getSelectedTab() ).toHaveFocus();
+ await expect( await getSelectedTab() ).toHaveFocus();
// Navigate forward with arrow keys and make sure the Beta tab is
// selected automatically.
await user.keyboard( '[ArrowRight]' );
- expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
- await expect( getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
+ await expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
// Navigate backwards with arrow keys. Make sure alpha is
// selected automatically.
await user.keyboard( '[ArrowLeft]' );
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ await expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
} );
@@ -692,24 +688,24 @@ describe.each( [
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
// Tab to focus the tablist. Make sure Alpha is focused.
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( getSelectedTab() ).not.toHaveFocus();
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ await expect( await getSelectedTab() ).not.toHaveFocus();
await user.keyboard( '[Tab]' );
- await expect( getSelectedTab() ).toHaveFocus();
+ await expect( await getSelectedTab() ).toHaveFocus();
// Navigate backwards with arrow keys and make sure that the Gamma tab
// (the last tab) is selected automatically.
await user.keyboard( '[ArrowLeft]' );
- expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
- await expect( getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
+ await expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
// Navigate forward with arrow keys. Make sure alpha (the first tab) is
// selected automatically.
await user.keyboard( '[ArrowRight]' );
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ await expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
} );
@@ -730,22 +726,22 @@ describe.each( [
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
// Tab to focus the tablist. Make sure alpha is focused.
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( getSelectedTab() ).not.toHaveFocus();
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ await expect( await getSelectedTab() ).not.toHaveFocus();
await user.keyboard( '[Tab]' );
- await expect( getSelectedTab() ).toHaveFocus();
+ await expect( await getSelectedTab() ).toHaveFocus();
// Press the arrow up key, nothing happens.
await user.keyboard( '[ArrowUp]' );
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ await expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
// Press the arrow down key, nothing happens
await user.keyboard( '[ArrowDown]' );
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ await expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
@@ -766,38 +762,38 @@ describe.each( [
);
// Make sure alpha is still focused.
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ await expect( await getSelectedTab() ).toHaveFocus();
// Navigate forward with arrow keys and make sure the Beta tab is
// selected automatically.
await user.keyboard( '[ArrowDown]' );
- expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
- await expect( getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
+ await expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
// Navigate backwards with arrow keys. Make sure alpha is
// selected automatically.
await user.keyboard( '[ArrowUp]' );
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ await expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
// Navigate backwards with arrow keys. Make sure alpha is
// selected automatically.
await user.keyboard( '[ArrowUp]' );
- expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
- await expect( getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
+ await expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
// Navigate backwards with arrow keys. Make sure alpha is
// selected automatically.
await user.keyboard( '[ArrowDown]' );
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ await expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 5 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
} );
@@ -826,10 +822,10 @@ describe.each( [
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
// Tab to focus the tablist. Make sure Alpha is focused.
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( getSelectedTab() ).not.toHaveFocus();
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ await expect( await getSelectedTab() ).not.toHaveFocus();
await user.keyboard( '[Tab]' );
- await expect( getSelectedTab() ).toHaveFocus();
+ await expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
// Press the right arrow key three times. Since the delta tab is disabled:
@@ -838,7 +834,7 @@ describe.each( [
// `mockOnSelect` function gets called only twice (and not three times)
// - it will receive focus, when using arrow keys
await user.keyboard( '[ArrowRight][ArrowRight][ArrowRight]' );
- expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
await expect(
screen.getByRole( 'tab', { name: 'Delta' } )
).toHaveFocus();
@@ -846,18 +842,20 @@ describe.each( [
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
// Navigate backwards with arrow keys. The gamma tab receives focus.
+ // The `mockOnSelect` callback doesn't fire, since the gamma tab was
+ // already selected.
await user.keyboard( '[ArrowLeft]' );
- expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
- await expect( getSelectedTab() ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
+ await expect( await getSelectedTab() ).toHaveFocus();
+ expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
- // Click on on the disabled tab. Compared to using arrow keys to move the
+ // Click on the disabled tab. Compared to using arrow keys to move the
// focus, disabled tabs ignore pointer clicks — and therefore, they don't
// receive focus, nor they cause the `mockOnSelect` function to fire.
await user.click( screen.getByRole( 'tab', { name: 'Delta' } ) );
- expect( getSelectedTab() ).toHaveTextContent( 'Gamma' );
- await expect( getSelectedTab() ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
+ await expect( await getSelectedTab() ).toHaveFocus();
+ expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
} );
it( 'switches to manual tab activation when the `selectOnMove` prop is set to `false`', async () => {
@@ -873,47 +871,51 @@ describe.each( [
/>
);
- // onSelect gets called on the initial render.
+ // onSelect gets called on the initial render with the default
+ // selected tab.
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
// Click on Alpha and make sure it is selected.
+ // onSelect shouldn't fire since the selected tab didn't change.
await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
+ expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
// Navigate forward with arrow keys. Make sure Beta is focused, but
// that the tab selection happens only when pressing the spacebar
// or enter key.
await user.keyboard( '[ArrowRight]' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
- expect( screen.getByRole( 'tab', { name: 'Beta' } ) ).toHaveFocus();
+ expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
+ expect(
+ await screen.findByRole( 'tab', { name: 'Beta' } )
+ ).toHaveFocus();
await user.keyboard( '[Enter]' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
+ expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
// Navigate forward with arrow keys. Make sure Gamma (last tab) is
// focused, but that tab selection happens only when pressing the
// spacebar or enter key.
await user.keyboard( '[ArrowRight]' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
+ expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
expect(
screen.getByRole( 'tab', { name: 'Gamma' } )
).toHaveFocus();
await user.keyboard( '[Space]' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
+ expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
} );
} );
describe( 'Tab Attributes', () => {
- it( "should apply the tab's `className` to the tab button", () => {
+ it( "should apply the tab's `className` to the tab button", async () => {
render(
undefined } /> );
- expect( screen.getByRole( 'tab', { name: 'Alpha' } ) ).toHaveClass(
- 'alpha-class'
- );
+ expect(
+ await screen.findByRole( 'tab', { name: 'Alpha' } )
+ ).toHaveClass( 'alpha-class' );
expect( screen.getByRole( 'tab', { name: 'Beta' } ) ).toHaveClass(
'beta-class'
);
@@ -935,8 +937,8 @@ describe.each( [
);
// Make sure that only the selected tab has the active class
- expect( getSelectedTab() ).toHaveTextContent( 'Alpha' );
- expect( getSelectedTab() ).toHaveClass( activeClass );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
+ expect( await getSelectedTab() ).toHaveClass( activeClass );
screen
.getAllByRole( 'tab', { selected: false } )
.forEach( ( unselectedTab ) => {
@@ -947,8 +949,8 @@ describe.each( [
await user.click( screen.getByRole( 'tab', { name: 'Beta' } ) );
// Make sure that only the selected tab has the active class
- expect( getSelectedTab() ).toHaveTextContent( 'Beta' );
- expect( getSelectedTab() ).toHaveClass( activeClass );
+ expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
+ expect( await getSelectedTab() ).toHaveClass( activeClass );
screen
.getAllByRole( 'tab', { selected: false } )
.forEach( ( unselectedTab ) => {
From 68fcf7e6e0cf179cf32aae58e2e55e16ae5b3fc2 Mon Sep 17 00:00:00 2001
From: chad1008 <13856531+chad1008@users.noreply.github.com>
Date: Wed, 12 Jul 2023 13:17:45 -0400
Subject: [PATCH 14/27] remove temporary separate/new component files
---
packages/components/src/tabs/index.tsx | 234 -----
.../components/src/tabs/stories/index.tsx | 116 ---
packages/components/src/tabs/test/index.tsx | 961 ------------------
3 files changed, 1311 deletions(-)
delete mode 100644 packages/components/src/tabs/index.tsx
delete mode 100644 packages/components/src/tabs/stories/index.tsx
delete mode 100644 packages/components/src/tabs/test/index.tsx
diff --git a/packages/components/src/tabs/index.tsx b/packages/components/src/tabs/index.tsx
deleted file mode 100644
index 3613c07200a948..00000000000000
--- a/packages/components/src/tabs/index.tsx
+++ /dev/null
@@ -1,234 +0,0 @@
-/**
- * External dependencies
- */
-import * as Ariakit from '@ariakit/react';
-import cx from 'classnames';
-
-/**
- * WordPress dependencies
- */
-import { useCallback, useEffect, useLayoutEffect } from '@wordpress/element';
-
-/**
- * Internal dependencies
- */
-import Button from '../button';
-import type { TabPanelProps } from '../tab-panel/types';
-import { useInstanceId, usePrevious } from '@wordpress/compose';
-
-/**
- * Tabs is an ARIA-compliant tabpanel.
- *
- * Tabs organizes content across different screens, data sets, and interactions.
- * It has two sections: a list of tabs, and the view to show when tabs are chosen.
- *
- * ```jsx
- * import { Tabs } from '@wordpress/components';
- *
- * const onSelect = ( tabName ) => {
- * console.log( 'Selecting tab', tabName );
- * };
- *
- * const MyTabs = () => (
- *
- * { ( tab ) => { tab.title }
}
- *
- * );
- * ```
- */
-
-export const TabPanel = ( props: TabPanelProps ) => {
- const {
- tabs,
- children,
- onSelect,
- className,
- orientation = 'horizontal',
- selectOnMove = true,
- initialTabName,
- activeClass = 'is-active',
- } = props;
-
- const instanceId = useInstanceId( TabPanel, 'tab-panel' );
-
- const prependInstanceId = useCallback(
- ( tabName: string | undefined ) => {
- if ( typeof tabName === 'undefined' ) {
- return;
- }
- return `${ instanceId }-${ tabName }`;
- },
- [ instanceId ]
- );
-
- // Separate the actual tab name from the instance ID. This is
- // necessary because Ariakit internally uses the element ID when
- // a new tab is selected, but our implementation looks specifically
- // for the tab name to be passed to the `onSelect` callback.
- const extractTabName = useCallback( ( id: string | undefined | null ) => {
- if ( typeof id === 'undefined' || id === null ) {
- return;
- }
- return id.match( /^tab-panel-[0-9]*-(.*)/ )?.[ 1 ];
- }, [] );
-
- const tabStore = Ariakit.useTabStore( {
- setSelectedId: ( newTabValue ) => {
- if ( typeof newTabValue === 'undefined' || newTabValue === null ) {
- return;
- }
-
- const newTab = tabs.find(
- ( t ) => prependInstanceId( t.name ) === newTabValue
- );
- if ( newTab?.disabled || newTab === selectedTab ) {
- return;
- }
-
- const simplifiedTabName = extractTabName( newTabValue );
- if ( typeof simplifiedTabName === 'undefined' ) {
- return;
- }
-
- onSelect?.( simplifiedTabName );
- },
- orientation,
- selectOnMove,
- defaultSelectedId: prependInstanceId( initialTabName ),
- } );
-
- const selectedTabName = extractTabName( tabStore.useState( 'selectedId' ) );
-
- const setTabStoreSelectedId = useCallback(
- ( tabName: string ) => {
- tabStore.setState( 'selectedId', prependInstanceId( tabName ) );
- },
- [ prependInstanceId, tabStore ]
- );
-
- const selectedTab = tabs.find( ( { name } ) => name === selectedTabName );
-
- const previousSelectedTabName = usePrevious( selectedTabName );
-
- // Ensure `onSelect` is called when the initial tab is selected.
- useEffect( () => {
- if (
- previousSelectedTabName !== selectedTabName &&
- selectedTabName === initialTabName &&
- !! selectedTabName
- ) {
- onSelect?.( selectedTabName );
- }
- }, [ selectedTabName, initialTabName, onSelect, previousSelectedTabName ] );
-
- // Handle selecting the initial tab.
- useLayoutEffect( () => {
- // If there's a selected tab, don't override it.
- if ( selectedTab ) {
- return;
- }
- const initialTab = tabs.find( ( tab ) => tab.name === initialTabName );
- // Wait for the denoted initial tab to be declared before making a
- // selection. This ensures that if a tab is declared lazily it can
- // still receive initial selection.
- if ( initialTabName && ! initialTab ) {
- return;
- }
- if ( initialTab && ! initialTab.disabled ) {
- // Select the initial tab if it's not disabled.
- setTabStoreSelectedId( initialTab.name );
- } else {
- // Fallback to the first enabled tab when the initial tab is
- // disabled or it can't be found.
- const firstEnabledTab = tabs.find( ( tab ) => ! tab.disabled );
- if ( firstEnabledTab ) {
- setTabStoreSelectedId( firstEnabledTab.name );
- }
- }
- }, [
- tabs,
- selectedTab,
- initialTabName,
- instanceId,
- setTabStoreSelectedId,
- ] );
-
- // Handle the currently selected tab becoming disabled.
- useEffect( () => {
- // This effect only runs when the selected tab is defined and becomes disabled.
- if ( ! selectedTab?.disabled ) {
- return;
- }
- const firstEnabledTab = tabs.find( ( tab ) => ! tab.disabled );
- // If the currently selected tab becomes disabled, select the first enabled tab.
- // (if there is one).
- if ( firstEnabledTab ) {
- setTabStoreSelectedId( firstEnabledTab.name );
- }
- }, [ tabs, selectedTab?.disabled, setTabStoreSelectedId, instanceId ] );
-
- return (
-
-
- { tabs.map( ( tab ) => {
- return (
-
- }
- >
- { ! tab.icon && tab.title }
-
- );
- } ) }
-
- { tabs.map( ( tab ) => (
-
- { children( tab ) }
-
- ) ) }
-
- );
-};
-
-export default TabPanel;
diff --git a/packages/components/src/tabs/stories/index.tsx b/packages/components/src/tabs/stories/index.tsx
deleted file mode 100644
index ce7b590b73493e..00000000000000
--- a/packages/components/src/tabs/stories/index.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-/**
- * External dependencies
- */
-import type { ComponentMeta, ComponentStory } from '@storybook/react';
-
-/**
- * WordPress dependencies
- */
-import { wordpress, more, link } from '@wordpress/icons';
-
-/**
- * Internal dependencies
- */
-import TabPanel from '..';
-import Popover from '../../popover';
-import { Provider as SlotFillProvider } from '../../slot-fill';
-
-const meta: ComponentMeta< typeof TabPanel > = {
- title: 'Components/TabPanel v2',
- component: TabPanel,
- parameters: {
- actions: { argTypesRegex: '^on.*' },
- controls: { expanded: true },
- docs: { source: { state: 'open' } },
- },
-};
-export default meta;
-
-const Template: ComponentStory< typeof TabPanel > = ( props ) => {
- return ;
-};
-
-export const Default = Template.bind( {} );
-Default.args = {
- children: ( tab ) => Selected tab: { tab.title }
,
- tabs: [
- {
- name: 'tab1',
- title: 'Tab 1',
- },
- {
- name: 'tab2',
- title: 'Tab 2',
- },
- ],
-};
-
-export const DisabledTab = Template.bind( {} );
-DisabledTab.args = {
- children: ( tab ) => Selected tab: { tab.title }
,
- tabs: [
- {
- name: 'tab1',
- title: 'Tab 1',
- disabled: true,
- },
- {
- name: 'tab2',
- title: 'Tab 2',
- },
- {
- name: 'tab3',
- title: 'Tab 3',
- },
- ],
-};
-
-// SlotFillTemplate is used to ensure the icon's tooltips are not rendered
-// inline, as that would cause them to inherit the tab's opacity.
-const SlotFillTemplate: ComponentStory< typeof TabPanel > = ( props ) => {
- return (
-
-
- { /* @ts-expect-error The 'Slot' component hasn't been typed yet. */ }
-
-
- );
-};
-
-export const WithTabIconsAndTooltips = SlotFillTemplate.bind( {} );
-WithTabIconsAndTooltips.args = {
- children: ( tab ) => Selected tab: { tab.title }
,
- tabs: [
- {
- name: 'tab1',
- title: 'Tab 1',
- icon: wordpress,
- },
- {
- name: 'tab2',
- title: 'Tab 2',
- icon: link,
- },
- {
- name: 'tab3',
- title: 'Tab 3',
- icon: more,
- },
- ],
-};
-
-export const ManualActivation = Template.bind( {} );
-ManualActivation.args = {
- children: ( tab ) => Selected tab: { tab.title }
,
- tabs: [
- {
- name: 'tab1',
- title: 'Tab 1',
- },
- {
- name: 'tab2',
- title: 'Tab 2',
- },
- ],
- selectOnMove: false,
-};
diff --git a/packages/components/src/tabs/test/index.tsx b/packages/components/src/tabs/test/index.tsx
deleted file mode 100644
index 859ee28f772026..00000000000000
--- a/packages/components/src/tabs/test/index.tsx
+++ /dev/null
@@ -1,961 +0,0 @@
-/**
- * External dependencies
- */
-import { render, screen, waitFor } from '@testing-library/react';
-import userEvent from '@testing-library/user-event';
-
-/**
- * WordPress dependencies
- */
-import { wordpress, category, media } from '@wordpress/icons';
-
-/**
- * Internal dependencies
- */
-import TabPanel from '..';
-import Popover from '../../popover';
-import { Provider as SlotFillProvider } from '../../slot-fill';
-
-const TABS = [
- {
- name: 'alpha',
- title: 'Alpha',
- className: 'alpha-class',
- },
- {
- name: 'beta',
- title: 'Beta',
- className: 'beta-class',
- },
- {
- name: 'gamma',
- title: 'Gamma',
- className: 'gamma-class',
- },
-];
-
-const getSelectedTab = async () =>
- await screen.findByRole( 'tab', { selected: true } );
-
-let originalGetClientRects: () => DOMRectList;
-
-describe.each( [
- [ 'uncontrolled', TabPanel ],
- // The controlled component tests will be added once we certify the
- // uncontrolled component's behaviour on trunk.
- // [ 'controlled', TabPanel ],
-] )( 'TabPanel %s', ( ...modeAndComponent ) => {
- const [ , Component ] = modeAndComponent;
-
- beforeAll( () => {
- originalGetClientRects = window.HTMLElement.prototype.getClientRects;
- // Mocking `getClientRects()` is necessary to pass a check performed by
- // the `focus.tabbable.find()` and by the `focus.focusable.find()` functions
- // from the `@wordpress/dom` package.
- // @ts-expect-error We're not trying to comply to the DOM spec, only mocking
- window.HTMLElement.prototype.getClientRects = function () {
- return [ 'trick-jsdom-into-having-size-for-element-rect' ];
- };
- } );
-
- afterAll( () => {
- window.HTMLElement.prototype.getClientRects = originalGetClientRects;
- } );
-
- describe( 'Accessibility and semantics', () => {
- it( 'should use the correct aria attributes', async () => {
- const panelRenderFunction = jest.fn();
-
- render(
-
- );
-
- const tabList = screen.getByRole( 'tablist' );
- const allTabs = screen.getAllByRole( 'tab' );
- const selectedTabPanel = await screen.findByRole( 'tabpanel' );
-
- expect( tabList ).toBeVisible();
- expect( tabList ).toHaveAttribute(
- 'aria-orientation',
- 'horizontal'
- );
-
- expect( allTabs ).toHaveLength( TABS.length );
-
- // The selected `tab` aria-controls the active `tabpanel`,
- // which is `aria-labelledby` the selected `tab`.
- expect( selectedTabPanel ).toBeVisible();
- expect( allTabs[ 0 ] ).toHaveAttribute(
- 'aria-controls',
- selectedTabPanel.getAttribute( 'id' )
- );
- expect( selectedTabPanel ).toHaveAttribute(
- 'aria-labelledby',
- allTabs[ 0 ].getAttribute( 'id' )
- );
- } );
-
- it( 'should display a tooltip when hovering tabs provided with an icon', async () => {
- const user = userEvent.setup();
-
- const panelRenderFunction = jest.fn();
-
- const TABS_WITH_ICON = [
- { ...TABS[ 0 ], icon: wordpress },
- { ...TABS[ 1 ], icon: category },
- { ...TABS[ 2 ], icon: media },
- ];
-
- render(
- // In order for the tooltip to display properly, there needs to be
- // `Popover.Slot` in which the `Popover` renders outside of the
- // `TabPanel` component, otherwise the tooltip renders inline.
-
-
- { /* @ts-expect-error The 'Slot' component hasn't been typed yet. */ }
-
-
- );
-
- const allTabs = screen.getAllByRole( 'tab' );
-
- for ( let i = 0; i < allTabs.length; i++ ) {
- expect(
- screen.queryByText( TABS_WITH_ICON[ i ].title )
- ).not.toBeInTheDocument();
-
- await user.hover( allTabs[ i ] );
-
- await waitFor( () =>
- expect(
- screen.getByText( TABS_WITH_ICON[ i ].title )
- ).toBeVisible()
- );
-
- await user.unhover( allTabs[ i ] );
- }
- } );
-
- it( 'should display a tooltip when moving the selection via the keyboard on tabs provided with an icon', async () => {
- const user = userEvent.setup();
-
- const mockOnSelect = jest.fn();
- const panelRenderFunction = jest.fn();
-
- const TABS_WITH_ICON = [
- { ...TABS[ 0 ], icon: wordpress },
- { ...TABS[ 1 ], icon: category },
- { ...TABS[ 2 ], icon: media },
- ];
-
- render(
- // In order for the tooltip to display properly, there needs to be
- // `Popover.Slot` in which the `Popover` renders outside of the
- // `TabPanel` component, otherwise the tooltip renders inline.
-
-
- { /* @ts-expect-error The 'Slot' component hasn't been typed yet. */ }
-
-
- );
-
- expect( await getSelectedTab() ).not.toHaveTextContent( 'Alpha' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
- await expect( await getSelectedTab() ).not.toHaveFocus();
-
- // Tab to focus the tablist. Make sure alpha is focused, and that the
- // corresponding tooltip is shown.
- expect( screen.queryByText( 'Alpha' ) ).not.toBeInTheDocument();
- await user.keyboard( '[Tab]' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
- expect( screen.getByText( 'Alpha' ) ).toBeInTheDocument();
- await expect( await getSelectedTab() ).toHaveFocus();
-
- // Move selection with arrow keys. Make sure beta is focused, and that
- // the corresponding tooltip is shown.
- expect( screen.queryByText( 'Beta' ) ).not.toBeInTheDocument();
- await user.keyboard( '[ArrowRight]' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
- expect( screen.getByText( 'Beta' ) ).toBeInTheDocument();
- await expect( await getSelectedTab() ).toHaveFocus();
-
- // Move selection with arrow keys. Make sure gamma is focused, and that
- // the corresponding tooltip is shown.
- expect( screen.queryByText( 'Gamma' ) ).not.toBeInTheDocument();
- await user.keyboard( '[ArrowRight]' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
- expect( screen.getByText( 'Gamma' ) ).toBeInTheDocument();
- await expect( await getSelectedTab() ).toHaveFocus();
-
- // Move selection with arrow keys. Make sure beta is focused, and that
- // the corresponding tooltip is shown.
- expect( screen.queryByText( 'Beta' ) ).not.toBeInTheDocument();
- await user.keyboard( '[ArrowLeft]' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
- expect( screen.getByText( 'Beta' ) ).toBeInTheDocument();
- await expect( await getSelectedTab() ).toHaveFocus();
- } );
- } );
-
- describe( 'Without `initialTabName`', () => {
- it( 'should render first tab', async () => {
- const panelRenderFunction = jest.fn();
-
- render(
-
- );
-
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- expect(
- await screen.findByRole( 'tabpanel', { name: 'Alpha' } )
- ).toBeInTheDocument();
- } );
-
- it( 'should fall back to first enabled tab if the active tab is removed', async () => {
- const mockOnSelect = jest.fn();
- const { rerender } = render(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- rerender(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
- expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
- } );
- } );
-
- describe( 'With `initialTabName`', () => {
- it( 'should render the tab set by initialTabName prop', async () => {
- render(
- undefined }
- />
- );
-
- expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
- } );
-
- it( 'should not select a tab when `initialTabName` does not match any known tab', () => {
- render(
- undefined }
- />
- );
-
- // No tab should be selected i.e. it doesn't fall back to first tab.
- expect(
- screen.queryByRole( 'tab', { selected: true } )
- ).not.toBeInTheDocument();
-
- // No tabpanel should be rendered either
- expect( screen.queryByRole( 'tabpanel' ) ).not.toBeInTheDocument();
- } );
- it( 'should not change tabs when initialTabName is changed', async () => {
- const { rerender } = render(
- undefined }
- />
- );
-
- rerender(
- undefined }
- />
- );
-
- expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
- } );
-
- it( 'should fall back to the tab associated to `initialTabName` if the currently active tab is removed', async () => {
- const user = userEvent.setup();
- const mockOnSelect = jest.fn();
-
- const { rerender } = render(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
-
- await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) );
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
-
- rerender(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
- } );
-
- it( 'should have no active tabs when the tab associated to `initialTabName` is removed while being the active tab', async () => {
- const mockOnSelect = jest.fn();
-
- const { rerender } = render(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
-
- rerender(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- expect( screen.getAllByRole( 'tab' ) ).toHaveLength( 2 );
- expect(
- screen.queryByRole( 'tab', { selected: true } )
- ).not.toBeInTheDocument();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
- } );
-
- it( 'waits for the tab with the `initialTabName` to be present in the `tabs` array before selecting it', async () => {
- const mockOnSelect = jest.fn();
- const { rerender } = render(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- // There should be no selected tab yet.
- expect(
- screen.queryByRole( 'tab', { selected: true } )
- ).not.toBeInTheDocument();
-
- rerender(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- expect( await getSelectedTab() ).toHaveTextContent( 'Delta' );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'delta' );
- } );
- } );
-
- describe( 'Disabled Tab', () => {
- it( 'should disable the tab when `disabled` is `true`', async () => {
- const user = userEvent.setup();
- const mockOnSelect = jest.fn();
-
- render(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- expect(
- screen.getByRole( 'tab', { name: 'Delta' } )
- ).toHaveAttribute( 'aria-disabled', 'true' );
-
- // onSelect gets called on the initial render.
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
-
- // onSelect should not be called since the disabled tab is
- // highlighted, but not selected.
- await user.keyboard( '[ArrowLeft]' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
- } );
-
- it( 'should select first enabled tab when the initial tab is disabled', async () => {
- const mockOnSelect = jest.fn();
-
- const { rerender } = render(
- {
- if ( tab.name !== 'alpha' ) {
- return tab;
- }
- return { ...tab, disabled: true };
- } ) }
- children={ () => undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- // As alpha (first tab) is disabled,
- // the first enabled tab should be gamma.
- expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
-
- // Re-enable all tabs
- rerender(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- // Even if the initial tab becomes enabled again, the selected tab doesn't
- // change.
- expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
- } );
-
- it( 'should select first enabled tab when the tab associated to `initialTabName` is disabled', async () => {
- const mockOnSelect = jest.fn();
-
- const { rerender } = render(
- {
- if ( tab.name === 'gamma' ) {
- return tab;
- }
- return { ...tab, disabled: true };
- } ) }
- initialTabName="beta"
- children={ () => undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- // As alpha (first tab), and beta (the initial tab), are both
- // disabled the first enabled tab should be gamma.
- expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
-
- // Re-enable all tabs
- rerender(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- // Even if the initial tab becomes enabled again, the selected tab doesn't
- // change.
- expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
- } );
-
- it( 'should select the first enabled tab when the selected tab becomes disabled', async () => {
- const mockOnSelect = jest.fn();
- const { rerender } = render(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
-
- rerender(
- {
- if ( tab.name === 'alpha' ) {
- return { ...tab, disabled: true };
- }
- return tab;
- } ) }
- children={ () => undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
-
- rerender(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
- } );
-
- it( 'should select the first enabled tab when the tab associated to `initialTabName` becomes disabled while being the active tab', async () => {
- const mockOnSelect = jest.fn();
-
- const { rerender } = render(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
-
- rerender(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
-
- rerender(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
- } );
- } );
-
- describe( 'Tab Activation', () => {
- it( 'defaults to automatic tab activation (pointer clicks)', async () => {
- const user = userEvent.setup();
- const panelRenderFunction = jest.fn();
- const mockOnSelect = jest.fn();
-
- render(
-
- );
-
- // Alpha is the initially selected tab
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- expect(
- await screen.findByRole( 'tabpanel', { name: 'Alpha' } )
- ).toBeInTheDocument();
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
-
- // Click on Beta, make sure beta is the selected tab
- await user.click( screen.getByRole( 'tab', { name: 'Beta' } ) );
-
- expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
- expect(
- screen.getByRole( 'tabpanel', { name: 'Beta' } )
- ).toBeInTheDocument();
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
-
- // Click on Alpha, make sure beta is the selected tab
- await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) );
-
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- expect(
- screen.getByRole( 'tabpanel', { name: 'Alpha' } )
- ).toBeInTheDocument();
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
- } );
-
- it( 'defaults to automatic tab activation (arrow keys)', async () => {
- const user = userEvent.setup();
- const mockOnSelect = jest.fn();
-
- render(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- // onSelect gets called on the initial render.
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
-
- // Tab to focus the tablist. Make sure alpha is focused.
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).not.toHaveFocus();
- await user.keyboard( '[Tab]' );
- await expect( await getSelectedTab() ).toHaveFocus();
-
- // Navigate forward with arrow keys and make sure the Beta tab is
- // selected automatically.
- await user.keyboard( '[ArrowRight]' );
- expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
- await expect( await getSelectedTab() ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
-
- // Navigate backwards with arrow keys. Make sure alpha is
- // selected automatically.
- await user.keyboard( '[ArrowLeft]' );
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
- } );
-
- it( 'wraps around the last/first tab when using arrow keys', async () => {
- const user = userEvent.setup();
- const mockOnSelect = jest.fn();
-
- render(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- // onSelect gets called on the initial render.
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
-
- // Tab to focus the tablist. Make sure Alpha is focused.
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).not.toHaveFocus();
- await user.keyboard( '[Tab]' );
- await expect( await getSelectedTab() ).toHaveFocus();
-
- // Navigate backwards with arrow keys and make sure that the Gamma tab
- // (the last tab) is selected automatically.
- await user.keyboard( '[ArrowLeft]' );
- expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
- await expect( await getSelectedTab() ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
-
- // Navigate forward with arrow keys. Make sure alpha (the first tab) is
- // selected automatically.
- await user.keyboard( '[ArrowRight]' );
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
- } );
-
- it( 'should not move tab selection when pressing the up/down arrow keys, unless the orientation is changed to `vertical`', async () => {
- const user = userEvent.setup();
- const mockOnSelect = jest.fn();
-
- const { rerender } = render(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- // onSelect gets called on the initial render.
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
-
- // Tab to focus the tablist. Make sure alpha is focused.
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).not.toHaveFocus();
- await user.keyboard( '[Tab]' );
- await expect( await getSelectedTab() ).toHaveFocus();
-
- // Press the arrow up key, nothing happens.
- await user.keyboard( '[ArrowUp]' );
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
-
- // Press the arrow down key, nothing happens
- await user.keyboard( '[ArrowDown]' );
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
-
- // Change orientation to `vertical`. When the orientation is vertical,
- // left/right arrow keys are replaced by up/down arrow keys.
- rerender(
- undefined }
- onSelect={ mockOnSelect }
- orientation="vertical"
- />
- );
-
- expect( screen.getByRole( 'tablist' ) ).toHaveAttribute(
- 'aria-orientation',
- 'vertical'
- );
-
- // Make sure alpha is still focused.
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).toHaveFocus();
-
- // Navigate forward with arrow keys and make sure the Beta tab is
- // selected automatically.
- await user.keyboard( '[ArrowDown]' );
- expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
- await expect( await getSelectedTab() ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
-
- // Navigate backwards with arrow keys. Make sure alpha is
- // selected automatically.
- await user.keyboard( '[ArrowUp]' );
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
-
- // Navigate backwards with arrow keys. Make sure alpha is
- // selected automatically.
- await user.keyboard( '[ArrowUp]' );
- expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
- await expect( await getSelectedTab() ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
-
- // Navigate backwards with arrow keys. Make sure alpha is
- // selected automatically.
- await user.keyboard( '[ArrowDown]' );
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 5 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
- } );
-
- it( 'should move focus on a tab even if disabled with arrow key, but not with pointer clicks', async () => {
- const user = userEvent.setup();
- const mockOnSelect = jest.fn();
-
- render(
- undefined }
- onSelect={ mockOnSelect }
- />
- );
-
- // onSelect gets called on the initial render.
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
-
- // Tab to focus the tablist. Make sure Alpha is focused.
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).not.toHaveFocus();
- await user.keyboard( '[Tab]' );
- await expect( await getSelectedTab() ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
-
- // Press the right arrow key three times. Since the delta tab is disabled:
- // - it won't be selected. The gamma tab will be selected instead, since
- // it was the tab that was last selected before delta. Therefore, the
- // `mockOnSelect` function gets called only twice (and not three times)
- // - it will receive focus, when using arrow keys
- await user.keyboard( '[ArrowRight][ArrowRight][ArrowRight]' );
- expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
- await expect(
- screen.getByRole( 'tab', { name: 'Delta' } )
- ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
-
- // Navigate backwards with arrow keys. The gamma tab receives focus.
- // The `mockOnSelect` callback doesn't fire, since the gamma tab was
- // already selected.
- await user.keyboard( '[ArrowLeft]' );
- expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
- await expect( await getSelectedTab() ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
-
- // Click on the disabled tab. Compared to using arrow keys to move the
- // focus, disabled tabs ignore pointer clicks — and therefore, they don't
- // receive focus, nor they cause the `mockOnSelect` function to fire.
- await user.click( screen.getByRole( 'tab', { name: 'Delta' } ) );
- expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
- await expect( await getSelectedTab() ).toHaveFocus();
- expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
- } );
-
- it( 'switches to manual tab activation when the `selectOnMove` prop is set to `false`', async () => {
- const user = userEvent.setup();
- const mockOnSelect = jest.fn();
-
- render(
- undefined }
- onSelect={ mockOnSelect }
- selectOnMove={ false }
- />
- );
-
- // onSelect gets called on the initial render with the default
- // selected tab.
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
-
- // Click on Alpha and make sure it is selected.
- // onSelect shouldn't fire since the selected tab didn't change.
- await user.click( screen.getByRole( 'tab', { name: 'Alpha' } ) );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
-
- // Navigate forward with arrow keys. Make sure Beta is focused, but
- // that the tab selection happens only when pressing the spacebar
- // or enter key.
- await user.keyboard( '[ArrowRight]' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
- expect(
- await screen.findByRole( 'tab', { name: 'Beta' } )
- ).toHaveFocus();
-
- await user.keyboard( '[Enter]' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
-
- // Navigate forward with arrow keys. Make sure Gamma (last tab) is
- // focused, but that tab selection happens only when pressing the
- // spacebar or enter key.
- await user.keyboard( '[ArrowRight]' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
- expect(
- screen.getByRole( 'tab', { name: 'Gamma' } )
- ).toHaveFocus();
-
- await user.keyboard( '[Space]' );
- expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
- expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
- } );
- } );
-
- describe( 'Tab Attributes', () => {
- it( "should apply the tab's `className` to the tab button", async () => {
- render( undefined } /> );
-
- expect(
- await screen.findByRole( 'tab', { name: 'Alpha' } )
- ).toHaveClass( 'alpha-class' );
- expect( screen.getByRole( 'tab', { name: 'Beta' } ) ).toHaveClass(
- 'beta-class'
- );
- expect( screen.getByRole( 'tab', { name: 'Gamma' } ) ).toHaveClass(
- 'gamma-class'
- );
- } );
-
- it( 'should apply the `activeClass` to the selected tab', async () => {
- const user = userEvent.setup();
- const activeClass = 'my-active-tab';
-
- render(
- undefined }
- />
- );
-
- // Make sure that only the selected tab has the active class
- expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- expect( await getSelectedTab() ).toHaveClass( activeClass );
- screen
- .getAllByRole( 'tab', { selected: false } )
- .forEach( ( unselectedTab ) => {
- expect( unselectedTab ).not.toHaveClass( activeClass );
- } );
-
- // Click the 'Beta' tab
- await user.click( screen.getByRole( 'tab', { name: 'Beta' } ) );
-
- // Make sure that only the selected tab has the active class
- expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
- expect( await getSelectedTab() ).toHaveClass( activeClass );
- screen
- .getAllByRole( 'tab', { selected: false } )
- .forEach( ( unselectedTab ) => {
- expect( unselectedTab ).not.toHaveClass( activeClass );
- } );
- } );
- } );
-} );
From b9e019c40c5e85029759e0eb65a88334b0976094 Mon Sep 17 00:00:00 2001
From: Chad Chadbourne <13856531+chad1008@users.noreply.github.com>
Date: Thu, 13 Jul 2023 15:35:54 -0400
Subject: [PATCH 15/27] Update
packages/components/src/tab-panel/stories/index.tsx
Co-authored-by: Lena Morita
---
packages/components/src/tab-panel/stories/index.tsx | 12 +-----------
1 file changed, 1 insertion(+), 11 deletions(-)
diff --git a/packages/components/src/tab-panel/stories/index.tsx b/packages/components/src/tab-panel/stories/index.tsx
index 2e140e139e653a..b6a247d99ebdfb 100644
--- a/packages/components/src/tab-panel/stories/index.tsx
+++ b/packages/components/src/tab-panel/stories/index.tsx
@@ -99,16 +99,6 @@ WithTabIconsAndTooltips.args = {
export const ManualActivation = Template.bind( {} );
ManualActivation.args = {
- children: ( tab ) => Selected tab: { tab.title }
,
- tabs: [
- {
- name: 'tab1',
- title: 'Tab 1',
- },
- {
- name: 'tab2',
- title: 'Tab 2',
- },
- ],
+ ...Default.args,
selectOnMove: false,
};
From 37c014be2fd6fa4d737743f89b63b8e71b566c73 Mon Sep 17 00:00:00 2001
From: chad1008 <13856531+chad1008@users.noreply.github.com>
Date: Thu, 13 Jul 2023 16:24:39 -0400
Subject: [PATCH 16/27] update CHANGELOG entry
---
packages/components/CHANGELOG.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index dc6254912e767e..593ca9c6400faf 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -16,6 +16,10 @@
- `Toolbar`: Fix toolbar items not being tabbable on the first render. ([#52613](https://github.com/WordPress/gutenberg/pull/52613))
- `FormTokenField`: Fix token overflow when moving cursor left or right. ([#52662](https://github.com/WordPress/gutenberg/pull/52662))
+### Internal
+
+- `TabPanel`: Introduce a new version of `TabPanel` with updated internals while maintaining the same functionality and API surface ([#52133](https://github.com/WordPress/gutenberg/pull/52133)).
+
## 25.3.0 (2023-07-05)
### Enhancements
@@ -44,10 +48,6 @@
- `ItemGroup`: Update button focus state styles to be inline with other button focus states in the editor. ([#51576](https://github.com/WordPress/gutenberg/pull/51576)).
- `ItemGroup`: Update button focus state styles to target `:focus-visible` rather than `:focus`. ([#51787](https://github.com/WordPress/gutenberg/pull/51787)).
-### Experimental
-
-- `Tabs`: Create a new version of `TabPanel` with updated internals, while maintaining the same functionality and API surface ([#52133](https://github.com/WordPress/gutenberg/pull/52133)).
-
### Bug Fix
- `Popover`: Allow legitimate 0 positions to update popover position ([#51320](https://github.com/WordPress/gutenberg/pull/51320)).
From c89aedd1d1edbbad3dea3de598ad6ac8b63e2553 Mon Sep 17 00:00:00 2001
From: chad1008 <13856531+chad1008@users.noreply.github.com>
Date: Thu, 13 Jul 2023 16:39:48 -0400
Subject: [PATCH 17/27] restore package-lock.json
---
package-lock.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package-lock.json b/package-lock.json
index 34aa841cea3d5c..12ebe3296be944 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -29267,7 +29267,7 @@
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
- "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=",
+ "integrity": "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==",
"dev": true
},
"code-point-at": {
From 582afa58cd3cb9cbdb9d56866d470fa6d634e3f1 Mon Sep 17 00:00:00 2001
From: chad1008 <13856531+chad1008@users.noreply.github.com>
Date: Thu, 13 Jul 2023 16:44:25 -0400
Subject: [PATCH 18/27] remove unnecessary `await`s
---
.../components/src/tab-panel/test/index.tsx | 54 +++++++++----------
1 file changed, 27 insertions(+), 27 deletions(-)
diff --git a/packages/components/src/tab-panel/test/index.tsx b/packages/components/src/tab-panel/test/index.tsx
index 859ee28f772026..25ef03e9db4aa0 100644
--- a/packages/components/src/tab-panel/test/index.tsx
+++ b/packages/components/src/tab-panel/test/index.tsx
@@ -169,7 +169,7 @@ describe.each( [
expect( await getSelectedTab() ).not.toHaveTextContent( 'Alpha' );
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
- await expect( await getSelectedTab() ).not.toHaveFocus();
+ expect( await getSelectedTab() ).not.toHaveFocus();
// Tab to focus the tablist. Make sure alpha is focused, and that the
// corresponding tooltip is shown.
@@ -177,7 +177,7 @@ describe.each( [
await user.keyboard( '[Tab]' );
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
expect( screen.getByText( 'Alpha' ) ).toBeInTheDocument();
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
// Move selection with arrow keys. Make sure beta is focused, and that
// the corresponding tooltip is shown.
@@ -186,7 +186,7 @@ describe.each( [
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
expect( screen.getByText( 'Beta' ) ).toBeInTheDocument();
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
// Move selection with arrow keys. Make sure gamma is focused, and that
// the corresponding tooltip is shown.
@@ -195,7 +195,7 @@ describe.each( [
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
expect( screen.getByText( 'Gamma' ) ).toBeInTheDocument();
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
// Move selection with arrow keys. Make sure beta is focused, and that
// the corresponding tooltip is shown.
@@ -204,7 +204,7 @@ describe.each( [
expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
expect( screen.getByText( 'Beta' ) ).toBeInTheDocument();
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
} );
} );
@@ -651,15 +651,15 @@ describe.each( [
// Tab to focus the tablist. Make sure alpha is focused.
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).not.toHaveFocus();
+ expect( await getSelectedTab() ).not.toHaveFocus();
await user.keyboard( '[Tab]' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
// Navigate forward with arrow keys and make sure the Beta tab is
// selected automatically.
await user.keyboard( '[ArrowRight]' );
expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
@@ -667,7 +667,7 @@ describe.each( [
// selected automatically.
await user.keyboard( '[ArrowLeft]' );
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
} );
@@ -689,15 +689,15 @@ describe.each( [
// Tab to focus the tablist. Make sure Alpha is focused.
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).not.toHaveFocus();
+ expect( await getSelectedTab() ).not.toHaveFocus();
await user.keyboard( '[Tab]' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
// Navigate backwards with arrow keys and make sure that the Gamma tab
// (the last tab) is selected automatically.
await user.keyboard( '[ArrowLeft]' );
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
@@ -705,7 +705,7 @@ describe.each( [
// selected automatically.
await user.keyboard( '[ArrowRight]' );
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
} );
@@ -727,21 +727,21 @@ describe.each( [
// Tab to focus the tablist. Make sure alpha is focused.
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).not.toHaveFocus();
+ expect( await getSelectedTab() ).not.toHaveFocus();
await user.keyboard( '[Tab]' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
// Press the arrow up key, nothing happens.
await user.keyboard( '[ArrowUp]' );
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
// Press the arrow down key, nothing happens
await user.keyboard( '[ArrowDown]' );
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
@@ -763,13 +763,13 @@ describe.each( [
// Make sure alpha is still focused.
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
// Navigate forward with arrow keys and make sure the Beta tab is
// selected automatically.
await user.keyboard( '[ArrowDown]' );
expect( await getSelectedTab() ).toHaveTextContent( 'Beta' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 2 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'beta' );
@@ -777,7 +777,7 @@ describe.each( [
// selected automatically.
await user.keyboard( '[ArrowUp]' );
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
@@ -785,7 +785,7 @@ describe.each( [
// selected automatically.
await user.keyboard( '[ArrowUp]' );
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 4 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'gamma' );
@@ -793,7 +793,7 @@ describe.each( [
// selected automatically.
await user.keyboard( '[ArrowDown]' );
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 5 );
expect( mockOnSelect ).toHaveBeenLastCalledWith( 'alpha' );
} );
@@ -823,9 +823,9 @@ describe.each( [
// Tab to focus the tablist. Make sure Alpha is focused.
expect( await getSelectedTab() ).toHaveTextContent( 'Alpha' );
- await expect( await getSelectedTab() ).not.toHaveFocus();
+ expect( await getSelectedTab() ).not.toHaveFocus();
await user.keyboard( '[Tab]' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 1 );
// Press the right arrow key three times. Since the delta tab is disabled:
@@ -835,7 +835,7 @@ describe.each( [
// - it will receive focus, when using arrow keys
await user.keyboard( '[ArrowRight][ArrowRight][ArrowRight]' );
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
- await expect(
+ expect(
screen.getByRole( 'tab', { name: 'Delta' } )
).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
@@ -846,7 +846,7 @@ describe.each( [
// already selected.
await user.keyboard( '[ArrowLeft]' );
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
// Click on the disabled tab. Compared to using arrow keys to move the
@@ -854,7 +854,7 @@ describe.each( [
// receive focus, nor they cause the `mockOnSelect` function to fire.
await user.click( screen.getByRole( 'tab', { name: 'Delta' } ) );
expect( await getSelectedTab() ).toHaveTextContent( 'Gamma' );
- await expect( await getSelectedTab() ).toHaveFocus();
+ expect( await getSelectedTab() ).toHaveFocus();
expect( mockOnSelect ).toHaveBeenCalledTimes( 3 );
} );
From c97cbee816d2ad67f24eb4720d31f9e843ad253a Mon Sep 17 00:00:00 2001
From: chad1008 <13856531+chad1008@users.noreply.github.com>
Date: Thu, 13 Jul 2023 16:47:28 -0400
Subject: [PATCH 19/27] move `extractTabName` out of component
---
packages/components/src/tab-panel/index.tsx | 22 ++++++++++-----------
1 file changed, 11 insertions(+), 11 deletions(-)
diff --git a/packages/components/src/tab-panel/index.tsx b/packages/components/src/tab-panel/index.tsx
index d6a95da1e751f5..571362f8f07139 100644
--- a/packages/components/src/tab-panel/index.tsx
+++ b/packages/components/src/tab-panel/index.tsx
@@ -24,6 +24,17 @@ import Button from '../button';
import type { TabPanelProps } from './types';
import type { WordPressComponentProps } from '../ui/context';
+// Separate the actual tab name from the instance ID. This is
+// necessary because Ariakit internally uses the element ID when
+// a new tab is selected, but our implementation looks specifically
+// for the tab name to be passed to the `onSelect` callback.
+const extractTabName = ( id: string | undefined | null ) => {
+ if ( typeof id === 'undefined' || id === null ) {
+ return;
+ }
+ return id.match( /^tab-panel-[0-9]*-(.*)/ )?.[ 1 ];
+};
+
/**
* TabPanel is an ARIA-compliant tabpanel.
*
@@ -85,17 +96,6 @@ const UnforwardedTabPanel = (
[ instanceId ]
);
- // Separate the actual tab name from the instance ID. This is
- // necessary because Ariakit internally uses the element ID when
- // a new tab is selected, but our implementation looks specifically
- // for the tab name to be passed to the `onSelect` callback.
- const extractTabName = useCallback( ( id: string | undefined | null ) => {
- if ( typeof id === 'undefined' || id === null ) {
- return;
- }
- return id.match( /^tab-panel-[0-9]*-(.*)/ )?.[ 1 ];
- }, [] );
-
const tabStore = Ariakit.useTabStore( {
setSelectedId: ( newTabValue ) => {
if ( typeof newTabValue === 'undefined' || newTabValue === null ) {
From 00d2eaaa503c68b5788afc27b95383144e5f1ec4 Mon Sep 17 00:00:00 2001
From: chad1008 <13856531+chad1008@users.noreply.github.com>
Date: Mon, 24 Jul 2023 14:28:44 -0400
Subject: [PATCH 20/27] restore original component lazy loading
---
packages/components/src/tab-panel/index.tsx | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/packages/components/src/tab-panel/index.tsx b/packages/components/src/tab-panel/index.tsx
index 571362f8f07139..a9e66e076fbc70 100644
--- a/packages/components/src/tab-panel/index.tsx
+++ b/packages/components/src/tab-panel/index.tsx
@@ -190,7 +190,6 @@ const UnforwardedTabPanel = (
setTabStoreSelectedId( firstEnabledTab.name );
}
}, [ tabs, selectedTab?.disabled, setTabStoreSelectedId, instanceId ] );
-
return (
- { tabs.map( ( tab ) => (
+ { selectedTab && (
- { children( tab ) }
+ { children( selectedTab ) }
- ) ) }
+ ) }
);
};
From 4b7d7bb324b4ca7dd91254fa3e0bc48f56da8281 Mon Sep 17 00:00:00 2001
From: chad1008 <13856531+chad1008@users.noreply.github.com>
Date: Mon, 24 Jul 2023 14:33:48 -0400
Subject: [PATCH 21/27] update CHANGELOG entry
---
packages/components/CHANGELOG.md | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md
index 593ca9c6400faf..e441f8a01f92b5 100644
--- a/packages/components/CHANGELOG.md
+++ b/packages/components/CHANGELOG.md
@@ -2,6 +2,10 @@
## Unreleased
+### Internal
+
+- `TabPanel`: Introduce a new version of `TabPanel` with updated internals while maintaining the same functionality and API surface ([#52133](https://github.com/WordPress/gutenberg/pull/52133)).
+
## 25.4.0 (2023-07-20)
### Enhancements
@@ -16,10 +20,6 @@
- `Toolbar`: Fix toolbar items not being tabbable on the first render. ([#52613](https://github.com/WordPress/gutenberg/pull/52613))
- `FormTokenField`: Fix token overflow when moving cursor left or right. ([#52662](https://github.com/WordPress/gutenberg/pull/52662))
-### Internal
-
-- `TabPanel`: Introduce a new version of `TabPanel` with updated internals while maintaining the same functionality and API surface ([#52133](https://github.com/WordPress/gutenberg/pull/52133)).
-
## 25.3.0 (2023-07-05)
### Enhancements
From aa1bfc2ad70a9b00924023baacde01b22385da50 Mon Sep 17 00:00:00 2001
From: chad1008 <13856531+chad1008@users.noreply.github.com>
Date: Mon, 24 Jul 2023 15:54:57 -0400
Subject: [PATCH 22/27] up editor preferences modal unit tests
---
packages/components/src/tab-panel/index.tsx | 3 +++
.../preferences-modal/test/__snapshots__/index.js.snap | 5 +++++
.../src/components/preferences-modal/test/index.js | 8 ++++----
3 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/packages/components/src/tab-panel/index.tsx b/packages/components/src/tab-panel/index.tsx
index a9e66e076fbc70..2635e0c4b4b9da 100644
--- a/packages/components/src/tab-panel/index.tsx
+++ b/packages/components/src/tab-panel/index.tsx
@@ -210,6 +210,9 @@ const UnforwardedTabPanel = (
}
) }
disabled={ tab.disabled }
+ aria-controls={ `${ prependInstanceId(
+ tab.name
+ ) }-view` }
render={