From e0c7d7bd6565f3d1f01ac30c4ebc4082905f755e Mon Sep 17 00:00:00 2001 From: Adam Zielinski Date: Wed, 15 Jun 2022 14:44:20 +0200 Subject: [PATCH] Split useNavigationMenu into bite-size functions and add unit tests (#41139) Co-authored-by: Dave Smith --- .../navigation/test/use-navigation-menu.js | 236 ++++++++++++++++++ .../src/navigation/use-navigation-menu.js | 234 +++++++++++------ .../specs/editor/blocks/navigation.test.js | 17 +- 3 files changed, 399 insertions(+), 88 deletions(-) create mode 100644 packages/block-library/src/navigation/test/use-navigation-menu.js diff --git a/packages/block-library/src/navigation/test/use-navigation-menu.js b/packages/block-library/src/navigation/test/use-navigation-menu.js new file mode 100644 index 00000000000000..2c42bd2ad3ffc6 --- /dev/null +++ b/packages/block-library/src/navigation/test/use-navigation-menu.js @@ -0,0 +1,236 @@ +/** + * WordPress dependencies + */ +import { createRegistry, useSelect } from '@wordpress/data'; +import { store as coreStore } from '@wordpress/core-data'; + +/** + * Internal dependencies + */ +import useNavigationMenu from '../use-navigation-menu'; + +function createRegistryWithStores() { + // Create a registry and register used stores. + const registry = createRegistry(); + registry.register( coreStore ); + + const navigationConfig = { + kind: 'postType', + name: 'wp_navigation', + baseURL: '/wp/v2/navigation', + rawAttributes: [ 'title', 'excerpt', 'content' ], + }; + // Register post type entity. + registry.dispatch( coreStore ).addEntities( [ navigationConfig ] ); + return registry; +} + +jest.mock( '@wordpress/data/src/components/use-select', () => { + // This allows us to tweak the returned value on each test. + const mock = jest.fn(); + return mock; +} ); + +function resolveRecords( registry, menus ) { + const dispatch = registry.dispatch( coreStore ); + dispatch.startResolution( 'getEntityRecords', [ + 'postType', + 'wp_navigation', + { per_page: -1, status: 'publish' }, + ] ); + dispatch.finishResolution( 'getEntityRecords', [ + 'postType', + 'wp_navigation', + { per_page: -1, status: 'publish' }, + ] ); + dispatch.receiveEntityRecords( 'postType', 'wp_navigation', menus, { + per_page: -1, + status: 'publish', + } ); +} + +function resolveCreatePermission( registry, allowed ) { + const dispatch = registry.dispatch( coreStore ); + dispatch.receiveUserPermission( 'create/navigation', allowed ); + dispatch.startResolution( 'canUser', [ 'create', 'navigation' ] ); + dispatch.finishResolution( 'canUser', [ 'create', 'navigation' ] ); +} + +function resolveUpdatePermission( registry, ref, allowed ) { + const dispatch = registry.dispatch( coreStore ); + dispatch.receiveUserPermission( `update/navigation/${ ref }`, allowed ); + dispatch.startResolution( 'canUser', [ 'update', 'navigation', ref ] ); + dispatch.finishResolution( 'canUser', [ 'update', 'navigation', ref ] ); +} + +function resolveDeletePermission( registry, ref, allowed ) { + const dispatch = registry.dispatch( coreStore ); + dispatch.receiveUserPermission( `delete/navigation/${ ref }`, allowed ); + dispatch.startResolution( 'canUser', [ 'delete', 'navigation', ref ] ); + dispatch.finishResolution( 'canUser', [ 'delete', 'navigation', ref ] ); +} + +describe( 'useNavigationMenus', () => { + const navigationMenu1 = { id: 1, title: 'Menu 1', status: 'publish' }; + const navigationMenu2 = { id: 2, title: 'Menu 2', status: 'publish' }; + const navigationMenu3 = { id: 3, title: 'Menu 3', status: 'publish' }; + const navigationMenus = [ + navigationMenu1, + navigationMenu2, + navigationMenu3, + ]; + + let registry; + beforeEach( () => { + registry = createRegistryWithStores(); + useSelect.mockImplementation( ( fn ) => fn( registry.select ) ); + } ); + + it( 'Should return no information when no data is resolved', () => { + expect( useNavigationMenu() ).toEqual( { + navigationMenus: null, + canSwitchNavigationMenu: false, + canUserCreateNavigationMenu: false, + canUserDeleteNavigationMenu: false, + canUserUpdateNavigationMenu: false, + hasResolvedCanUserCreateNavigationMenu: false, + hasResolvedCanUserDeleteNavigationMenu: false, + hasResolvedCanUserUpdateNavigationMenu: false, + hasResolvedNavigationMenus: false, + isNavigationMenuMissing: true, + isNavigationMenuResolved: false, + isResolvingCanUserCreateNavigationMenu: false, + isResolvingNavigationMenus: false, + } ); + } ); + + it( 'Should return information about all menus when ref is missing', () => { + resolveRecords( registry, navigationMenus ); + resolveCreatePermission( registry, true ); + expect( useNavigationMenu() ).toEqual( { + navigationMenus, + canSwitchNavigationMenu: true, + canUserCreateNavigationMenu: true, + canUserDeleteNavigationMenu: false, + canUserUpdateNavigationMenu: false, + hasResolvedCanUserCreateNavigationMenu: true, + hasResolvedCanUserDeleteNavigationMenu: false, + hasResolvedCanUserUpdateNavigationMenu: false, + hasResolvedNavigationMenus: true, + isNavigationMenuMissing: true, + isNavigationMenuResolved: false, + isResolvingCanUserCreateNavigationMenu: false, + isResolvingNavigationMenus: false, + } ); + } ); + + it( 'Should return information about a specific menu when ref is given', () => { + resolveRecords( registry, navigationMenus ); + expect( useNavigationMenu( 1 ) ).toEqual( { + navigationMenu: navigationMenu1, + navigationMenus, + canSwitchNavigationMenu: true, + canUserCreateNavigationMenu: false, + canUserDeleteNavigationMenu: false, + canUserUpdateNavigationMenu: false, + hasResolvedCanUserCreateNavigationMenu: false, + hasResolvedCanUserDeleteNavigationMenu: false, + hasResolvedCanUserUpdateNavigationMenu: false, + hasResolvedNavigationMenus: true, + isNavigationMenuMissing: false, + isNavigationMenuResolved: false, + isResolvingCanUserCreateNavigationMenu: false, + isResolvingNavigationMenus: false, + } ); + } ); + + it( 'Should return null for the menu when menu status is "draft"', () => { + const navigationMenuDraft = { id: 4, title: 'Menu 3', status: 'draft' }; + const testMenus = [ ...navigationMenus, navigationMenuDraft ]; + resolveRecords( registry, testMenus ); + expect( useNavigationMenu( 4 ) ).toEqual( { + navigationMenu: null, + navigationMenus: testMenus, + canSwitchNavigationMenu: true, + canUserCreateNavigationMenu: false, + canUserDeleteNavigationMenu: false, + canUserUpdateNavigationMenu: false, + hasResolvedCanUserCreateNavigationMenu: false, + hasResolvedCanUserDeleteNavigationMenu: false, + hasResolvedCanUserUpdateNavigationMenu: false, + hasResolvedNavigationMenus: true, + isNavigationMenuMissing: false, + isNavigationMenuResolved: false, + isResolvingCanUserCreateNavigationMenu: false, + isResolvingNavigationMenus: false, + } ); + } ); + + it( 'Should return correct permissions (create, update)', () => { + resolveRecords( registry, navigationMenus ); + resolveCreatePermission( registry, true ); + resolveUpdatePermission( registry, 1, true ); + expect( useNavigationMenu( 1 ) ).toEqual( { + navigationMenu: navigationMenu1, + navigationMenus, + canSwitchNavigationMenu: true, + canUserCreateNavigationMenu: true, + canUserDeleteNavigationMenu: false, + canUserUpdateNavigationMenu: true, + hasResolvedCanUserCreateNavigationMenu: true, + hasResolvedCanUserDeleteNavigationMenu: false, + hasResolvedCanUserUpdateNavigationMenu: true, + hasResolvedNavigationMenus: true, + isNavigationMenuMissing: false, + isNavigationMenuResolved: false, + isResolvingCanUserCreateNavigationMenu: false, + isResolvingNavigationMenus: false, + } ); + } ); + + it( 'Should return correct permissions (delete only)', () => { + resolveRecords( registry, navigationMenus ); + resolveDeletePermission( registry, 1, true ); + expect( useNavigationMenu( 1 ) ).toEqual( { + navigationMenu: navigationMenu1, + navigationMenus, + canSwitchNavigationMenu: true, + canUserCreateNavigationMenu: false, + canUserDeleteNavigationMenu: true, + canUserUpdateNavigationMenu: false, + hasResolvedCanUserCreateNavigationMenu: false, + hasResolvedCanUserDeleteNavigationMenu: true, + hasResolvedCanUserUpdateNavigationMenu: false, + hasResolvedNavigationMenus: true, + isNavigationMenuMissing: false, + isNavigationMenuResolved: false, + isResolvingCanUserCreateNavigationMenu: false, + isResolvingNavigationMenus: false, + } ); + } ); + + it( 'Should return correct permissions (no permissions)', () => { + const requestedMenu = navigationMenu1; + // Note the "delete" permission is resolved for menu 2, but we're requesting + // the details of menu 1. + resolveDeletePermission( registry, navigationMenu2, true ); + resolveRecords( registry, navigationMenus ); + + expect( useNavigationMenu( requestedMenu.id ) ).toEqual( { + navigationMenu: requestedMenu, + navigationMenus, + canSwitchNavigationMenu: true, + canUserCreateNavigationMenu: false, + canUserDeleteNavigationMenu: false, + canUserUpdateNavigationMenu: false, + hasResolvedCanUserCreateNavigationMenu: false, + hasResolvedCanUserDeleteNavigationMenu: false, + hasResolvedCanUserUpdateNavigationMenu: false, + hasResolvedNavigationMenus: true, + isNavigationMenuMissing: false, + isNavigationMenuResolved: false, + isResolvingCanUserCreateNavigationMenu: false, + isResolvingNavigationMenus: false, + } ); + } ); +} ); diff --git a/packages/block-library/src/navigation/use-navigation-menu.js b/packages/block-library/src/navigation/use-navigation-menu.js index 356f9a2f97f986..cf9498f6d200da 100644 --- a/packages/block-library/src/navigation/use-navigation-menu.js +++ b/packages/block-library/src/navigation/use-navigation-menu.js @@ -8,93 +8,163 @@ export default function useNavigationMenu( ref ) { return useSelect( ( select ) => { const { - getEntityRecord, - getEditedEntityRecord, - getEntityRecords, - hasFinishedResolution, - isResolving, - canUser, - } = select( coreStore ); - - const navigationMenuSingleArgs = [ - 'postType', - 'wp_navigation', - ref, - ]; - const rawNavigationMenu = ref - ? getEntityRecord( ...navigationMenuSingleArgs ) - : null; - let navigationMenu = ref - ? getEditedEntityRecord( ...navigationMenuSingleArgs ) - : null; - - // getEditedEntityRecord will return the post regardless of status. - // Therefore if the found post is not published then we should ignore it. - if ( navigationMenu?.status !== 'publish' ) { - navigationMenu = null; - } - - const hasResolvedNavigationMenu = ref - ? hasFinishedResolution( - 'getEditedEntityRecord', - navigationMenuSingleArgs - ) - : false; - - const navigationMenuMultipleArgs = [ - 'postType', - 'wp_navigation', - { per_page: -1, status: 'publish' }, - ]; - const navigationMenus = getEntityRecords( - ...navigationMenuMultipleArgs - ); - - const canSwitchNavigationMenu = ref - ? navigationMenus?.length > 1 - : navigationMenus?.length > 0; + navigationMenus, + isResolvingNavigationMenus, + hasResolvedNavigationMenus, + } = selectNavigationMenus( select, ref ); - return { - isNavigationMenuResolved: hasResolvedNavigationMenu, - isNavigationMenuMissing: - ! ref || - ( hasResolvedNavigationMenu && ! rawNavigationMenu ), - canSwitchNavigationMenu, - isResolvingNavigationMenus: isResolving( - 'getEntityRecords', - navigationMenuMultipleArgs - ), - hasResolvedNavigationMenus: hasFinishedResolution( - 'getEntityRecords', - navigationMenuMultipleArgs - ), + const { navigationMenu, + isNavigationMenuResolved, + isNavigationMenuMissing, + } = selectExistingMenu( select, ref ); + + const { + canUserCreateNavigationMenu, + isResolvingCanUserCreateNavigationMenu, + hasResolvedCanUserCreateNavigationMenu, + } = selectMenuCreatePermissions( select ); + + const { + canUserUpdateNavigationMenu, + hasResolvedCanUserUpdateNavigationMenu, + } = selectMenuUpdatePermissions( select, ref ); + + const { + canUserDeleteNavigationMenu, + hasResolvedCanUserDeleteNavigationMenu, + } = selectMenuDeletePermissions( select, ref ); + + return { navigationMenus, - canUserUpdateNavigationMenu: ref - ? canUser( 'update', 'navigation', ref ) - : undefined, - hasResolvedCanUserUpdateNavigationMenu: hasFinishedResolution( - 'canUser', - [ 'update', 'navigation', ref ] - ), - canUserDeleteNavigationMenu: ref - ? canUser( 'delete', 'navigation', ref ) - : undefined, - hasResolvedCanUserDeleteNavigationMenu: hasFinishedResolution( - 'canUser', - [ 'delete', 'navigation', ref ] - ), - canUserCreateNavigationMenu: canUser( 'create', 'navigation' ), - isResolvingCanUserCreateNavigationMenu: isResolving( - 'canUser', - [ 'create', 'navigation' ] - ), - hasResolvedCanUserCreateNavigationMenu: hasFinishedResolution( - 'canUser', - [ 'create', 'navigation' ] - ), + isResolvingNavigationMenus, + hasResolvedNavigationMenus, + + navigationMenu, + isNavigationMenuResolved, + isNavigationMenuMissing, + + canUserCreateNavigationMenu, + isResolvingCanUserCreateNavigationMenu, + hasResolvedCanUserCreateNavigationMenu, + + canUserUpdateNavigationMenu, + hasResolvedCanUserUpdateNavigationMenu, + + canUserDeleteNavigationMenu, + hasResolvedCanUserDeleteNavigationMenu, + + canSwitchNavigationMenu: ref + ? navigationMenus?.length > 1 + : navigationMenus?.length > 0, }; }, [ ref ] ); } + +function selectNavigationMenus( select ) { + const { getEntityRecords, hasFinishedResolution, isResolving } = + select( coreStore ); + + const args = [ + 'postType', + 'wp_navigation', + { per_page: -1, status: 'publish' }, + ]; + return { + navigationMenus: getEntityRecords( ...args ), + isResolvingNavigationMenus: isResolving( 'getEntityRecords', args ), + hasResolvedNavigationMenus: hasFinishedResolution( + 'getEntityRecords', + args + ), + }; +} + +function selectExistingMenu( select, ref ) { + if ( ! ref ) { + return { + isNavigationMenuResolved: false, + isNavigationMenuMissing: true, + }; + } + + const { getEntityRecord, getEditedEntityRecord, hasFinishedResolution } = + select( coreStore ); + + const args = [ 'postType', 'wp_navigation', ref ]; + const navigationMenu = getEntityRecord( ...args ); + const editedNavigationMenu = getEditedEntityRecord( ...args ); + const hasResolvedNavigationMenu = hasFinishedResolution( + 'getEditedEntityRecord', + args + ); + + return { + isNavigationMenuResolved: hasResolvedNavigationMenu, + isNavigationMenuMissing: hasResolvedNavigationMenu && ! navigationMenu, + + // getEditedEntityRecord will return the post regardless of status. + // Therefore if the found post is not published then we should ignore it. + navigationMenu: + editedNavigationMenu.status === 'publish' + ? editedNavigationMenu + : null, + }; +} + +function selectMenuCreatePermissions( select ) { + const { hasFinishedResolution, isResolving, canUser } = select( coreStore ); + + const args = [ 'create', 'navigation' ]; + return { + canUserCreateNavigationMenu: !! canUser( ...args ), + isResolvingCanUserCreateNavigationMenu: !! isResolving( + 'canUser', + args + ), + hasResolvedCanUserCreateNavigationMenu: !! hasFinishedResolution( + 'canUser', + args + ), + }; +} + +function selectMenuUpdatePermissions( select, ref ) { + if ( ! ref ) { + return { + canUserUpdateNavigationMenu: false, + hasResolvedCanUserUpdateNavigationMenu: false, + }; + } + + const { hasFinishedResolution, canUser } = select( coreStore ); + const args = [ 'update', 'navigation', ref ]; + return { + canUserUpdateNavigationMenu: !! canUser( ...args ), + hasResolvedCanUserUpdateNavigationMenu: !! hasFinishedResolution( + 'canUser', + args + ), + }; +} + +function selectMenuDeletePermissions( select, ref ) { + if ( ! ref ) { + return { + canUserDeleteNavigationMenu: false, + hasResolvedCanUserDeleteNavigationMenu: false, + }; + } + + const { hasFinishedResolution, canUser } = select( coreStore ); + const args = [ 'delete', 'navigation', ref ]; + return { + canUserDeleteNavigationMenu: !! canUser( ...args ), + hasResolvedCanUserDeleteNavigationMenu: !! hasFinishedResolution( + 'canUser', + args + ), + }; +} diff --git a/packages/e2e-tests/specs/editor/blocks/navigation.test.js b/packages/e2e-tests/specs/editor/blocks/navigation.test.js index 71effc7b96652a..332bd1e380eb5d 100644 --- a/packages/e2e-tests/specs/editor/blocks/navigation.test.js +++ b/packages/e2e-tests/specs/editor/blocks/navigation.test.js @@ -399,11 +399,14 @@ describe( 'Navigation', () => { // relying on variable factors such as network conditions. await setUpResponseMocking( [ { - match: ( request ) => - request.method() === 'GET' && - request.url().includes( `rest_route` ) && - request.url().includes( `navigation` ) && - request.url().includes( testNavId ), + match: ( request ) => { + return ( + [ 'GET', 'OPTIONS' ].includes( request.method() ) && + decodeURIComponent( request.url() ).includes( + `navigation/${ testNavId }` + ) + ); + }, onRequestMatch: ( request ) => { // The Promise simulates a REST API request whose resolultion // the test has full control over. @@ -417,7 +420,9 @@ describe( 'Navigation', () => { }, }, ] ); - + /* +Expected mock function not to be called but it was called with: ["POST", "http://localhost:8889/wp-admin/admin-ajax.php", "http://localhost:8889/wp-admin/admin-ajax.php"],["GET", "http://localhost:8889/wp-admin/post-new.php", "http://localhost:8889/wp-admin/post-new.php"],["GET", "http://localhost:8889/wp-includes/js/mediaelement/mediaelementplayer-legacy.min.css?ver=4.2.16", "http://localhost:8889/wp-includes/js/mediaelement/mediaelementplayer-legacy.min.css?ver=4.2.16"],["GET", "http://localhost:8889/wp-includes/js/mediaelement/wp-mediaelement.min.css?ver=6.1-alpha-53506", "http://localhost:8889/wp-includes/js/mediaelement/wp-mediaelement.min.css?ver=6.1-alpha-53506"],["GET", "http://localhost:8889/wp-includes/js/imgareaselect/imgareaselect.css?ver=0.9.8", "http://localhost:8889/wp-includes/js/imgareaselect/imgareaselect.css?ver=0.9.8"],["GET", "http://localhost:8889/wp-content/plugins/gutenberg/build/components/style.css?ver=1655290402", "http://localhost:8889/wp-content/plugins/gutenberg/build/components/style.css?ver=1655290402"],["GET", "http://localhost:8889/wp-content/plugins/gutenberg/build/block-editor/style.css?ver=1655290402", "http://localhost:8889/wp-content/plugins/gutenberg/build/block-editor/style.css?ver=1655290402"],["GET", "http://localhost:8889/wp-content/plugins/gutenberg/build/nux/style.css?ver=1655290402", "http://localhost:8889/wp-content/plugins/gutenberg/build/nux/style.css?ver=1655290402"],["GET", "http://localhost:8889/wp-content/plugins/gutenberg/build/reusable-blocks/style.css?ver=1655290402", "http://localhost:8889/wp-content/plugins/gutenberg/build/reusable-blocks/style.css?ver=1655290402"],["GET", "http://localhost:8889/wp-content/plugins/gutenberg/build/editor/style.css?ver=1655290402", "http://localhost:8889/wp-content/plugins/gutenberg/build/editor/style.css?ver=1655290402"],["GET", "http://localhost:8889/wp-content/plugins/gutenberg/build/block-library/reset.css?ver=1655290402", "http://localhost:8889/wp-content/plugins/gutenberg/build/block-library/reset.css?ver=1655290402"],["GET", "http://localhost:8889/wp-content/plugins/gutenberg/build/block-library/style.css?ver=1655290402", "http://localhost:8889/wp-content/plugins/gutenberg/build/block-library/style.css?ver=1655290402"],["GET", "http://localhost:8889/wp-content/plugins/gutenberg/build/edit-post/classic.css?ver=1655290402", "http://localhost:8889/wp-content/plugins/gutenberg/build/edit-post/classic.css?ver=1655290402"],["GET", "http://localhost:8889/wp-content/plugins/gutenberg/build/block-library/editor.css?ver=1655290402", "http://localhost:8889/wp-content/plugins/gutenberg/build/block-library/editor.css?ver=1655290402"],["GET", "http://localhost:8889/wp-content/plugins/gutenberg/build/edit-post/style.css?ver=1655290402", "http://localhost:8889/wp-content/plugins/gutenberg/build/edit-post/style.css?ver=1655290402"],["GET", "http://localhost:8889/wp-content/plugins/gutenberg/build/block-directory/style.css?ver=1655290402", "http://localhost:8889/wp-content/plugins/gutenberg/build/block-directory/style.css?ver=1655290402"],["GET", "http://localhost:8889/wp-content/plugins/gutenberg/build/format-library/style.css?ver=1655290402", "http://localhost:8889/wp-content/plugins/gutenberg/build/format-library/style.css?ver=1655290402"],["GET", "http://localhost:8889/wp-content/themes/twentytwentyone/assets/css/custom-color-overrides.css?ver=1.6", "http://localhost:8889/wp-content/themes/twentytwentyone/assets/css/custom-color-overrides.css?ver=1.6"],["GET", "http://localhost:8889/wp-content/plugins/gutenberg/build/block-library/theme.css?ver=1655290402", "http://localhost:8889/wp-content/plugins/gutenberg/build/block-library/theme.css?ver=1655290402"],["GET", "http://localhost:8889/wp-content/plugins/gutenberg/build/blob/index.min.js?ver=bccaf46e493181a8db9a", "http://localhost:8889/wp-content/plugins/gutenberg/build/blob/index.min.js?ver=bccaf46e493181a8db9a"],["GET", "http://localhost:8889/wp-content/plugins/gutenberg/build/autop/index.min.js?ver=b1a2f86387be4fa46f89", "http://loca + */ await createNewPost(); await clickOnMoreMenuItem( 'Code editor' ); const codeEditorInput = await page.waitForSelector(