From c3684b52f6a434de52d621e0e03655153311b440 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 21 Mar 2023 16:49:40 +1000 Subject: [PATCH 1/3] Add unit test for incorrect menu order --- .../components/src/tools-panel/test/index.tsx | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/packages/components/src/tools-panel/test/index.tsx b/packages/components/src/tools-panel/test/index.tsx index 2a4fdbdf0aa96e..04f93c2f0ba814 100644 --- a/packages/components/src/tools-panel/test/index.tsx +++ b/packages/components/src/tools-panel/test/index.tsx @@ -954,6 +954,92 @@ describe( 'ToolsPanel', () => { expect( items[ 1 ] ).toHaveTextContent( 'Item 2' ); } ); + it( 'should maintain menu item order', async () => { + const InconsistentItems = () => ( + + false } + isShownByDefault + > +
Item 1
+
+ false } + isShownByDefault + > +
Item 2
+
+
+ ); + + const { rerender } = render( + + + + false } + isShownByDefault + > +
Item 3
+
+
+ + + +
+ ); + + // Open dropdown menu. + const user = userEvent.setup(); + let menuButton = getMenuButton(); + await user.click( menuButton ); + + // Confirm all the existing menu items are present and in the + // expected order. + let menuItems = await screen.findAllByRole( 'menuitemcheckbox' ); + + expect( menuItems.length ).toEqual( 3 ); + expect( menuItems[ 0 ] ).toHaveTextContent( 'Item 1' ); + expect( menuItems[ 1 ] ).toHaveTextContent( 'Item 2' ); + expect( menuItems[ 2 ] ).toHaveTextContent( 'Item 3' ); + + // Close the dropdown menu. + await user.click( menuButton ); + + rerender( + + + + false } + isShownByDefault + > +
Item 3
+
+
+ + + +
+ ); + + // Reopen dropdown menu. + menuButton = getMenuButton(); + await user.click( menuButton ); + + // Confirm the menu item order has been maintained. + menuItems = await screen.findAllByRole( 'menuitemcheckbox' ); + + expect( menuItems.length ).toEqual( 3 ); + expect( menuItems[ 0 ] ).toHaveTextContent( 'Item 1' ); + expect( menuItems[ 1 ] ).toHaveTextContent( 'Item 2' ); + expect( menuItems[ 2 ] ).toHaveTextContent( 'Item 3' ); + } ); + it( 'should not trigger callback when fill has not updated yet when panel has', () => { // Fill provided controls can update independently to the panel. // A `panelId` prop was added to both panels and items From 377b721dacab07041723e1ee3f025aff34ace2f5 Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 21 Mar 2023 16:50:19 +1000 Subject: [PATCH 2/3] Try fixing ToolsPanel menu order --- .../src/tools-panel/tools-panel/hook.ts | 48 +++++++++++++++++-- packages/components/src/tools-panel/types.ts | 1 + 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/packages/components/src/tools-panel/tools-panel/hook.ts b/packages/components/src/tools-panel/tools-panel/hook.ts index 65e1e7bd761b45..35d4c06279419d 100644 --- a/packages/components/src/tools-panel/tools-panel/hook.ts +++ b/packages/components/src/tools-panel/tools-panel/hook.ts @@ -30,7 +30,9 @@ const generateMenuItems = ( { panelItems, shouldReset, currentMenuItems, + menuItemOrder, }: ToolsPanelMenuItemsConfig ) => { + const newMenuItems: ToolsPanelMenuItems = { default: {}, optional: {} }; const menuItems: ToolsPanelMenuItems = { default: {}, optional: {} }; panelItems.forEach( ( { hasValue, isShownByDefault, label } ) => { @@ -42,7 +44,31 @@ const generateMenuItems = ( { const existingItemValue = currentMenuItems?.[ group ]?.[ label ]; const value = existingItemValue ? existingItemValue : hasValue(); - menuItems[ group ][ label ] = shouldReset ? false : value; + newMenuItems[ group ][ label ] = shouldReset ? false : value; + } ); + + // Loop the known, previously registered items first to maintain menu order. + menuItemOrder.forEach( ( key ) => { + if ( newMenuItems.default.hasOwnProperty( key ) ) { + menuItems.default[ key ] = newMenuItems.default[ key ]; + } + + if ( newMenuItems.optional.hasOwnProperty( key ) ) { + menuItems.optional[ key ] = newMenuItems.optional[ key ]; + } + } ); + + // Loop newMenuItems object adding any that aren't in the known items order. + Object.keys( newMenuItems.default ).forEach( ( key ) => { + if ( ! menuItems.default.hasOwnProperty( key ) ) { + menuItems.default[ key ] = newMenuItems.default[ key ]; + } + } ); + + Object.keys( newMenuItems.optional ).forEach( ( key ) => { + if ( ! menuItems.optional.hasOwnProperty( key ) ) { + menuItems.optional[ key ] = newMenuItems.optional[ key ]; + } } ); return menuItems; @@ -82,12 +108,14 @@ export function useToolsPanel( // Allow panel items to register themselves. const [ panelItems, setPanelItems ] = useState< ToolsPanelItem[] >( [] ); + const [ menuItemOrder, setMenuItemOrder ] = useState< string[] >( [] ); const [ resetAllFilters, setResetAllFilters ] = useState< ResetAllFilter[] >( [] ); const registerPanelItem = useCallback( ( item: ToolsPanelItem ) => { + // Add item to panel items. setPanelItems( ( items ) => { const newItems = [ ...items ]; // If an item with this label has already been registered, remove it @@ -101,8 +129,18 @@ export function useToolsPanel( } return [ ...newItems, item ]; } ); + + // Track the initial order of item registration. This is used for + // maintaining menu item order later. + setMenuItemOrder( ( items ) => { + if ( items.includes( item.label ) ) { + return items; + } + + return [ ...items, item.label ]; + } ); }, - [ setPanelItems ] + [ setPanelItems, setMenuItemOrder ] ); // Panels need to deregister on unmount to avoid orphans in menu state. @@ -160,10 +198,11 @@ export function useToolsPanel( panelItems, shouldReset: false, currentMenuItems: prevState, + menuItemOrder, } ); return items; } ); - }, [ panelItems, setMenuItems ] ); + }, [ panelItems, setMenuItems, menuItemOrder ] ); // Force a menu item to be checked. // This is intended for use with default panel items. They are displayed @@ -267,10 +306,11 @@ export function useToolsPanel( // Turn off display of all non-default items. const resetMenuItems = generateMenuItems( { panelItems, + menuItemOrder, shouldReset: true, } ); setMenuItems( resetMenuItems ); - }, [ panelItems, resetAllFilters, resetAll, setMenuItems ] ); + }, [ panelItems, resetAllFilters, resetAll, setMenuItems, menuItemOrder ] ); // Assist ItemGroup styling when there are potentially hidden placeholder // items by identifying first & last items that are toggled on for display. diff --git a/packages/components/src/tools-panel/types.ts b/packages/components/src/tools-panel/types.ts index 2657175caad7a5..183ed3254fe80a 100644 --- a/packages/components/src/tools-panel/types.ts +++ b/packages/components/src/tools-panel/types.ts @@ -187,4 +187,5 @@ export type ToolsPanelMenuItemsConfig = { panelItems: ToolsPanelItem[]; shouldReset: boolean; currentMenuItems?: ToolsPanelMenuItems; + menuItemOrder: string[]; }; From ad62a2420968ca9db98a894143db40c9dfb2c5ba Mon Sep 17 00:00:00 2001 From: Aaron Robertshaw <60436221+aaronrobertshaw@users.noreply.github.com> Date: Tue, 21 Mar 2023 17:45:03 +1000 Subject: [PATCH 3/3] Add changelog --- packages/components/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/components/CHANGELOG.md b/packages/components/CHANGELOG.md index 01b8e032ff4f1b..d3647667d79605 100644 --- a/packages/components/CHANGELOG.md +++ b/packages/components/CHANGELOG.md @@ -15,6 +15,7 @@ ### Bug Fix - `InputControl`: Fix misaligned textarea input control ([#49116](https://github.com/WordPress/gutenberg/pull/49116)). +- `ToolsPanel`: Ensure consistency in menu item order ([#49222](https://github.com/WordPress/gutenberg/pull/49222)). ## 23.6.0 (2023-03-15)