Skip to content
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
2 changes: 2 additions & 0 deletions packages/block-library/src/private-apis.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
*/
import { default as BlockKeyboardShortcuts } from './block-keyboard-shortcuts';
import { NAVIGATION_OVERLAY_TEMPLATE_PART_AREA } from './navigation/constants';
import { NavigationLinkUI } from './navigation/edit/navigation-link-ui';
import { lock } from './lock-unlock';

/**
Expand All @@ -12,4 +13,5 @@ export const privateApis = {};
lock( privateApis, {
BlockKeyboardShortcuts,
NAVIGATION_OVERLAY_TEMPLATE_PART_AREA,
NavigationLinkUI,
} );
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useMemo } from '@wordpress/element';
import { useSelect } from '@wordpress/data';
import { BlockEditorProvider } from '@wordpress/block-editor';
import { createBlock } from '@wordpress/blocks';
import { __experimentalFetchLinkSuggestions as fetchLinkSuggestions } from '@wordpress/core-data';

/**
* Internal dependencies
Expand All @@ -24,6 +25,14 @@ export default function NavigationMenuEditor( { navigationMenuId } ) {
};
}, [] );

const settings = useMemo( () => {
return {
...storedSettings,
__experimentalFetchLinkSuggestions: ( search, searchOptions ) =>
fetchLinkSuggestions( search, searchOptions, storedSettings ),
};
}, [ storedSettings ] );

const blocks = useMemo( () => {
if ( ! navigationMenuId ) {
return [];
Expand All @@ -38,7 +47,7 @@ export default function NavigationMenuEditor( { navigationMenuId } ) {

return (
<BlockEditorProvider
settings={ storedSettings }
settings={ settings }
value={ blocks }
onChange={ noop }
onInput={ noop }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useDispatch, useSelect } from '@wordpress/data';
import { createBlock } from '@wordpress/blocks';
import { useCallback } from '@wordpress/element';
import { store as coreStore } from '@wordpress/core-data';
import { privateApis as blockLibraryPrivateApis } from '@wordpress/block-library';

/**
* Internal dependencies
Expand All @@ -18,6 +19,7 @@ import { unlock } from '../../lock-unlock';
import LeafMoreMenu from './leaf-more-menu';

const { PrivateListView } = unlock( blockEditorPrivateApis );
const { NavigationLinkUI } = unlock( blockLibraryPrivateApis );

// Needs to be kept in sync with the query used at packages/block-library/src/page-list/edit.js.
const MAX_PAGE_COUNT = 100;
Expand Down Expand Up @@ -101,7 +103,8 @@ export default function NavigationMenuContent( { rootClientId } ) {
rootClientId={ listViewRootClientId }
onSelect={ offCanvasOnselect }
blockSettingsMenu={ LeafMoreMenu }
showAppender={ false }
showAppender
additionalBlockContent={ NavigationLinkUI }
isExpanded
/>
) }
Expand Down
178 changes: 178 additions & 0 deletions test/e2e/specs/site-editor/navigation-sidebar-list-view.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
/**
* WordPress dependencies
*/
const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );

test.describe( 'Navigation sidebar - list view editing', () => {
const navMenuFixture = {
title: 'Test Navigation Menu',
content:
'<!-- wp:navigation-link {"label":"Existing Item","type":"custom","url":"http://www.wordpress.org/","kind":"custom"} /-->',
};

test.beforeAll( async ( { requestUtils } ) => {
await requestUtils.activateTheme( 'emptytheme' );
await requestUtils.createPage( {
title: 'Test Page 1',
status: 'publish',
} );
await requestUtils.createPage( {
title: 'Test Page 2',
status: 'publish',
} );
} );

test.afterEach( async ( { requestUtils } ) => {
await requestUtils.deleteAllMenus();
} );

test.afterAll( async ( { requestUtils } ) => {
await Promise.all( [
requestUtils.deleteAllPages(),
requestUtils.activateTheme( 'twentytwentyone' ),
] );
} );

test.use( {
linkControl: async ( { page }, use ) => {
await use( new LinkControl( { page } ) );
},
} );

test( 'can use appender in site editor sidebar list view', async ( {
admin,
page,
requestUtils,
linkControl,
} ) => {
const createdMenu =
await requestUtils.createNavigationMenu( navMenuFixture );

// Visit the site editor in sidebar-browse mode (without canvas: 'edit')
// so that the sidebar shows the navigation menu's list view.
await admin.visitSiteEditor( {
postId: createdMenu?.id,
postType: 'wp_navigation',
} );

const listView = page.getByRole( 'treegrid', {
name: 'Block navigation structure',
} );

await expect( listView ).toBeVisible();

// Verify the existing item is shown in the sidebar list view.
await expect(
listView.getByRole( 'link', { name: 'Existing Item' } )
).toBeVisible();

// The appender button should be present to allow adding new items.
const appender = listView.getByRole( 'button', { name: 'Add page' } );
await expect( appender ).toBeVisible();

const linkControlSearch = linkControl.getLinkControlSearch();

await test.step( 'can add new menu items', async () => {
await appender.click();

// The LinkUI popover should open and immediately focus the search input.
await expect( linkControlSearch ).toBeFocused();

// Search for and select the page.
await linkControl.useLinkControlSearch( 'Test Page 2' );

// The new item should be appended after the existing item.
await expect(
listView.getByRole( 'link', { name: 'Test Page 2' } )
).toBeVisible();
} );

await test.step( 'can open and close the Link UI without losing focus', async () => {
await page.keyboard.press( 'ArrowDown' );
await expect( appender ).toBeFocused();
await page.keyboard.press( 'Enter' );
await expect( linkControlSearch ).toBeFocused();
await page.keyboard.press( 'Escape' );
await expect( linkControlSearch ).toBeHidden();
} );

await test.step( 'can create a new page', async () => {
await appender.click();

// The search input should be focused immediately.
await expect( linkControl.getLinkControlSearch() ).toBeFocused();

// Type a new page title that doesn't exist yet.
await page.keyboard.type( 'Brand New Page', { delay: 50 } );

// Tab twice to reach the "Create page" button.
await page.keyboard.press( 'Tab' );
await page.keyboard.press( 'Tab' );

const createPageButton = page.getByRole( 'button', {
name: 'Create page',
} );
await expect( createPageButton ).toBeVisible();
await expect( createPageButton ).toBeFocused();

// Open the page creation form.
await page.keyboard.press( 'Enter' );

// The title field should be pre-populated with the typed text.
const titleField = page.getByRole( 'textbox', { name: 'Title' } );
await expect( titleField ).toHaveValue( 'Brand New Page' );

// The Back button should be focused after entering the creation form.
const backButton = page.locator( '.link-ui-page-creator__back' );
await expect( backButton ).toBeFocused();

// Tab to the title field.
await page.keyboard.press( 'Tab' );
await expect( titleField ).toBeFocused();

// Tab to the Publish checkbox (on by default).
await page.keyboard.press( 'Tab' );
const publishCheckbox = page.getByRole( 'checkbox', {
name: 'Publish',
} );
await expect( publishCheckbox ).toBeFocused();
await expect( publishCheckbox ).toBeChecked();

// Tab twice more to reach the Create page button.
await page.keyboard.press( 'Tab' );
await page.keyboard.press( 'Tab' );
await expect( createPageButton ).toBeFocused();
await page.keyboard.press( 'Enter' );

// The newly created page should appear as a new item in the list view.
await expect(
listView.getByRole( 'link', { name: 'Brand New Page' } )
).toBeVisible();
} );
} );
} );

class LinkControl {
constructor( { page } ) {
this.page = page;
}

getLinkControlSearch() {
return this.page.getByRole( 'combobox', {
name: 'Search or type URL',
} );
}

async useLinkControlSearch( searchTerm ) {
await expect( this.getLinkControlSearch() ).toBeFocused();

await this.page.keyboard.type( searchTerm, { delay: 50 } );

await expect(
this.page.getByRole( 'listbox', { name: 'Search results' } )
).toBeVisible();

await this.page.keyboard.press( 'ArrowDown' );
await this.page.keyboard.press( 'Enter' );
}
}
Loading