Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Auto set first Navigation Menu as current if only 1 exists #39880

Merged
merged 18 commits into from
Apr 5, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages/block-library/src/navigation/edit/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -270,6 +270,7 @@ function Navigation( {
const [ overlayMenuPreview, setOverlayMenuPreview ] = useState( false );

const {
hasResolvedNavigationMenus,
isNavigationMenuResolved,
isNavigationMenuMissing,
navigationMenus,
Expand All @@ -283,7 +284,24 @@ function Navigation( {
hasResolvedCanUserCreateNavigationMenu,
} = useNavigationMenu( ref );

// Attempt to retrieve and prioritize any existing navigation menu unless
// a specific ref is allocated or the user is explicitly creating a new menu. The aim is
// for the block to "just work" from a user perspective using existing data.
useEffect( () => {
if (
isCreatingNavigationMenu ||
ref ||
! navigationMenus?.length ||
navigationMenus?.length > 1
) {
return;
}

setRef( navigationMenus[ 0 ].id );
}, [ navigationMenus ] );

const navRef = useRef();

const isDraftNavigationMenu = navigationMenu?.status === 'draft';

const {
Expand All @@ -309,6 +327,7 @@ function Navigation( {
! ref &&
! isCreatingNavigationMenu &&
! isConvertingClassicMenu &&
hasResolvedNavigationMenus &&
( ! hasUncontrolledInnerBlocks || isWithinUnassignedArea );

const isEntityAvailable =
Expand All @@ -321,6 +340,7 @@ function Navigation( {
// - there is a ref attribute pointing to a Navigation Post
// - the Navigation Post isn't available (hasn't resolved) yet.
const isLoading =
! hasResolvedNavigationMenus ||
isCreatingNavigationMenu ||
isConvertingClassicMenu ||
!! ( ref && ! isEntityAvailable && ! isConvertingClassicMenu );
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ export default function useNavigationMenu( ref ) {
! ref ||
( hasResolvedNavigationMenu && ! rawNavigationMenu ),
canSwitchNavigationMenu,
isResolvingNavigationMenus: isResolving(
'getEntityRecords',
navigationMenuMultipleArgs
),
hasResolvedNavigationMenus: hasFinishedResolution(
'getEntityRecords',
navigationMenuMultipleArgs
Expand Down
185 changes: 158 additions & 27 deletions packages/e2e-tests/specs/editor/blocks/navigation.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
openPreviewPage,
ensureSidebarOpened,
__experimentalRest as rest,
__experimentalBatch as batch,
publishPost,
createUser,
loginUser,
Expand Down Expand Up @@ -111,6 +112,29 @@ async function mockSearchResponse( items ) {
] );
}

async function forceSelectNavigationBlock() {
const navBlock = await waitForBlock( 'Navigation' );

if ( ! navBlock ) {
return;
}

await page.evaluate( () => {
const blocks = wp.data.select( 'core/block-editor' ).getBlocks();
const navigationBlock = blocks.find(
( block ) => block.name === 'core/navigation'
);

if ( ! navigationBlock ) {
return;
}

return wp.data
.dispatch( 'core/block-editor' )
.selectBlock( navigationBlock?.clientId, 0 );
} );
}

/**
* Interacts with the LinkControl to perform a search and select a returned suggestion
*
Expand Down Expand Up @@ -196,7 +220,6 @@ async function populateNavWithOneItem() {
const PLACEHOLDER_ACTIONS_CLASS = 'wp-block-navigation-placeholder__actions';
const PLACEHOLDER_ACTIONS_XPATH = `//*[contains(@class, '${ PLACEHOLDER_ACTIONS_CLASS }')]`;
const START_EMPTY_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Start empty']`;
const SELECT_MENU_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Select Menu']`;

/**
* Delete all items for the given REST resources using the REST API.
Expand Down Expand Up @@ -225,6 +248,17 @@ async function deleteAll( endpoints ) {
}
}

async function resetNavBlockToInitialState() {
const selectMenuDropdown = await page.waitForSelector(
'[aria-label="Select Menu"]'
);
await selectMenuDropdown.click();
const newMenuButton = await page.waitForXPath(
'//span[text()="Create new menu"]'
);
newMenuButton.click();
}

/**
* Replace unique ids in nav block content, since these won't be consistent
* between test runs.
Expand Down Expand Up @@ -317,9 +351,17 @@ describe( 'Navigation', () => {
} );

describe( 'loading states', () => {
it( 'does not show a loading indicator if there is no ref to a Navigation post', async () => {
it( 'does not show a loading indicator if there is no ref to a Navigation post and Nav Menus have loaded', async () => {
await createNewPost();

// Insert an empty block to trigger resolution of Nav Menu items.
await insertBlock( 'Navigation' );
await waitForBlock( 'Navigation' );
await page.waitForXPath( START_EMPTY_XPATH );

// Now we have Nav Menu items resolved. Continue to assert.
await clickOnMoreMenuItem( 'Code editor' );

const codeEditorInput = await page.waitForSelector(
'.editor-post-text-editor'
);
Expand Down Expand Up @@ -412,7 +454,7 @@ describe( 'Navigation', () => {
match: ( request ) =>
request.url().includes( `rest_route` ) &&
request.url().includes( `navigation` ) &&
request.url().includes( testNavId ),
request.url().includes( `${ testNavId }?` ),
onRequestMatch: ( request ) => {
// The Promise simulates a REST API request whose resolultion
// the test has full control over.
Expand Down Expand Up @@ -873,17 +915,6 @@ describe( 'Navigation', () => {
const NAV_ENTITY_SELECTOR =
'//div[@class="entities-saved-states__panel"]//label//strong[contains(text(), "Navigation")]';

async function resetNavBlockToInitialState() {
const selectMenuDropdown = await page.waitForSelector(
'[aria-label="Select Menu"]'
);
await selectMenuDropdown.click();
const newMenuButton = await page.waitForXPath(
'//span[text()="Create new menu"]'
);
newMenuButton.click();
}

it( 'respects the nesting level', async () => {
await createNewPost();

Expand Down Expand Up @@ -993,12 +1024,16 @@ describe( 'Navigation', () => {
await publishButton.click();

// A success notice should show up.
await page.waitForSelector( '.components-snackbar' );
await page.waitForXPath(
`//*[contains(@class, 'components-snackbar__content')][ text()="Post published." ]`
);

// Now try inserting another Link block via the quick inserter.
await page.click( 'nav[aria-label="Block: Navigation"]' );
// await page.click( 'nav[aria-label="Block: Navigation"]' );
await forceSelectNavigationBlock();

await resetNavBlockToInitialState();

const startEmptyButton2 = await page.waitForXPath(
START_EMPTY_XPATH
);
Expand Down Expand Up @@ -1218,19 +1253,10 @@ describe( 'Navigation', () => {

await createNewPost();

// At this point the block will automatically pick the first Navigation Menu
// which will be the one created by the Admin User.
await insertBlock( 'Navigation' );

// Select the Navigation post created by the Admin earlier
// in the test.
const navigationPostCreatedByAdminName = 'Navigation';

const dropdown = await page.waitForXPath( SELECT_MENU_XPATH );
await dropdown.click();
const theOption = await page.waitForXPath(
`//*[contains(@class, 'components-menu-item__item')][ text()="${ navigationPostCreatedByAdminName }" ]`
);
await theOption.click();

// Make sure the snackbar error shows up.
await page.waitForXPath(
`//*[contains(@class, 'components-snackbar__content')][ text()="You do not have permission to edit this Menu. Any changes made will not be saved." ]`
Expand Down Expand Up @@ -1267,4 +1293,109 @@ describe( 'Navigation', () => {
expect( console ).toHaveErrored();
} );
} );

describe( 'Initial block insertion state', () => {
async function createNavigationMenu( menu = {} ) {
return rest( {
method: 'POST',
path: '/wp/v2/navigation',
data: {
status: 'publish',
...menu,
},
} );
}

afterEach( async () => {
const navMenusEndpoint = '/wp/v2/navigation';
const allNavMenus = await rest( { path: navMenusEndpoint } );

if ( ! allNavMenus?.length ) {
return;
}

return batch(
allNavMenus.map( ( menu ) => ( {
method: 'DELETE',
path: `${ navMenusEndpoint }/${ menu.id }?force=true`,
} ) )
);
} );

it( 'automatically uses the first Navigation Menu if only one is available', async () => {
await createNavigationMenu( {
title: 'Example Navigation',
content:
'<!-- wp:navigation-link {"label":"WordPress","type":"custom","url":"http://www.wordpress.org/","kind":"custom","isTopLevelLink":true} /-->',
} );

await createNewPost();

await insertBlock( 'Navigation' );

await waitForBlock( 'Navigation' );

const innerLinkBlock = await waitForBlock( 'Custom Link' );

const linkText = await innerLinkBlock.$eval(
'[aria-label="Navigation link text"]',
( element ) => {
return element.innerText;
}
);

expect( linkText ).toBe( 'WordPress' );
} );

it( 'does not automatically use first Navigation Menu if more than one exists', async () => {
await createNavigationMenu( {
title: 'Example Navigation',
content:
'<!-- wp:navigation-link {"label":"WordPress","type":"custom","url":"http://www.wordpress.org/","kind":"custom","isTopLevelLink":true} /-->',
} );

await createNavigationMenu( {
title: 'Second Example Navigation',
content:
'<!-- wp:navigation-link {"label":"WordPress","type":"custom","url":"http://www.wordpress.org/","kind":"custom","isTopLevelLink":true} /-->',
} );

await createNewPost();

await insertBlock( 'Navigation' );

await waitForBlock( 'Navigation' );

await page.waitForXPath( START_EMPTY_XPATH );
} );

it( 'allows users to manually create new empty menu when block has automatically selected the first available Navigation Menu', async () => {
await createNavigationMenu( {
title: 'Example Navigation',
content:
'<!-- wp:navigation-link {"label":"WordPress","type":"custom","url":"http://www.wordpress.org/","kind":"custom","isTopLevelLink":true} /-->',
} );

await createNewPost();

await insertBlock( 'Navigation' );

await waitForBlock( 'Navigation' );

await waitForBlock( 'Custom Link' );

// Reset the nav block to create a new entity.
await resetNavBlockToInitialState();

const startEmptyButton = await page.waitForXPath(
START_EMPTY_XPATH
);
await startEmptyButton.click();

// Wait for Navigation creation of empty Navigation to complete.
await page.waitForXPath(
'//*[contains(@class, "components-snackbar")]/*[text()="Navigation Menu successfully created."]'
);
} );
} );
} );