diff --git a/packages/e2e-test-utils-playwright/src/request-utils/index.ts b/packages/e2e-test-utils-playwright/src/request-utils/index.ts index 5c6f849f867514..7ee56cbb744f9b 100644 --- a/packages/e2e-test-utils-playwright/src/request-utils/index.ts +++ b/packages/e2e-test-utils-playwright/src/request-utils/index.ts @@ -21,6 +21,7 @@ import { activateTheme } from './themes'; import { deleteAllBlocks } from './blocks'; import { createComment, deleteAllComments } from './comments'; import { createPost, deleteAllPosts } from './posts'; +import { createMenu, deleteAllMenus } from './menus'; import { resetPreferences } from './preferences'; import { getSiteSettings, updateSiteSettings } from './site-settings'; import { deleteAllWidgets, addWidgetBlock } from './widgets'; @@ -125,6 +126,8 @@ class RequestUtils { deleteAllBlocks = deleteAllBlocks; createPost = createPost.bind( this ); deleteAllPosts = deleteAllPosts.bind( this ); + createMenu = createMenu.bind( this ); + deleteAllMenus = deleteAllMenus; createComment = createComment.bind( this ); deleteAllComments = deleteAllComments.bind( this ); deleteAllWidgets = deleteAllWidgets.bind( this ); diff --git a/packages/e2e-test-utils-playwright/src/request-utils/menus.ts b/packages/e2e-test-utils-playwright/src/request-utils/menus.ts new file mode 100644 index 00000000000000..6809bc5af1c8b1 --- /dev/null +++ b/packages/e2e-test-utils-playwright/src/request-utils/menus.ts @@ -0,0 +1,179 @@ +/** + * Internal dependencies + */ +import type { RequestUtils } from './index'; + +const MENUS_ENDPOINT = '/wp/v2/menus'; +const MENU_ITEMS_ENDPOINT = '/wp/v2/menu-items'; + +export interface Menu { + name: string; +} + +export interface MenuItem { + id: number; + title: string; + status: 'publish' | 'future' | 'draft' | 'pending' | 'private'; + object: 'page'; + menu_order: number; +} + +export interface Post { + id: number; + content: string; + status: 'publish' | 'future' | 'draft' | 'pending' | 'private'; + title: { + raw: string; + rendered: string; + }; +} + +export interface ObjectRequests extends Post { + method?: string; + path: string; + headers?: Record< string, string | string[] >; + link: string; +} + +const menuItemObjectRequests = { + post: ( menuItem: MenuItem ) => ( { + path: '/wp/v2/posts', + method: 'POST', + data: { + title: menuItem.title, + status: 'publish', + }, + } ), + page: ( menuItem: MenuItem ) => ( { + path: '/wp/v2/pages', + method: 'POST', + data: { + title: menuItem.title, + status: 'publish', + }, + } ), +}; + +const menuItemObjectMatchers = { + post: ( menuItem: MenuItem, post: Post ) => + menuItem.title === post.title.raw, + page: ( menuItem: MenuItem, page: Post ) => + menuItem.title === page.title.raw, +}; + +/** + * Reset user preferences + * + */ +export async function deleteAllMenus( this: RequestUtils ) { + const menus = await this.rest( { path: MENUS_ENDPOINT } ); + + if ( ! menus?.length ) return; + + await this.batchRest( + menus.map( ( menu: MenuItem ) => ( { + method: 'DELETE', + path: `${ MENUS_ENDPOINT }/${ menu.id }?force=true`, + } ) ) + ); +} + +/** + * Create menus and all linked resources for the menu using the REST API. + * + * @param {} this RequestUtils. + * @param {Object} menu Rest payload for the menu + * @param {?Array} menuItems Data for any menu items to be created. + */ +export async function createMenu( + this: RequestUtils, + menu: Menu, + menuItems: MenuItem[] +) { + // Step 1. Create the menu. + const menuResponse = await this.rest( { + method: 'POST', + path: MENUS_ENDPOINT, + data: menu, + } ); + + if ( ! menuItems?.length ) { + return; + } + + // Step 2. Create all the pages/posts/categories etc. that menu items + // are linked to. These items don't support rest batching so create them + // using individual requests. + const objectRequests = menuItems + .map( ( menuItem: MenuItem ) => { + const getRequest = menuItemObjectRequests[ menuItem.object ]; + if ( ! getRequest ) { + return undefined; + } + return getRequest( menuItem ); + } ) + .filter( ( request ) => !! request ); + const objectResponses: ObjectRequests[] = []; + for ( const objectRequest of objectRequests ) { + if ( ! objectRequest ) continue; + const objectResponse = await this.rest( objectRequest ); + objectResponses.push( objectResponse ); + } + + // Step 3. Create the initial menu items without assigned parents. We need + // the ids of all the menu items first before being able to assign the + // correct id of the parent. + /*const menuItemsResponse =*/ await this.batchRest( + menuItems.map( ( menuItem: MenuItem ) => { + // If the menu item is linked to an 'object', get the id for that + // object. + const objectMatcher = menuItemObjectMatchers[ menuItem.object ]; + let object; + if ( objectMatcher ) { + object = objectResponses.find( ( objectResponse ) => { + return objectMatcher( menuItem, objectResponse ); + } ); + } + + return { + method: 'POST', + path: MENU_ITEMS_ENDPOINT, + body: { + menus: menuResponse.id, + object_id: object?.id, + url: object?.link, + ...menuItem, + parent: undefined, + }, + }; + } ) + ); + + // Step 4. Make another menu item request to assign parents. + + /*await this.batchRest( + menuItems + .map( ( menuItem: MenuItem, index: number ) => { + // In the fixture data, the parent corresponds to the + // index in the array, dereference that to find the actual + // menu item id. + const fixtureParentIndex = menuItem?.parent !== undefined; + + // Skip any menu items that are top level. + if ( ! fixtureParentIndex ) { + return undefined; + } + + const parent = menuItemsResponse[ menuItem.parent ].body.id; + const menuItemResponse = menuItemsResponse[ index ]; + const menuItemId = menuItemResponse.body.id; + + return { + method: 'PUT', + path: `${ MENU_ITEMS_ENDPOINT }/${ menuItemId }`, + body: { ...menuItemResponse.body, parent }, + }; + } ) + .filter( ( request ) => !! request ) + );*/ +} diff --git a/test/e2e/specs/editor/blocks/fixtures/menu-items-request-fixture.js b/test/e2e/specs/editor/blocks/fixtures/menu-items-request-fixture.js new file mode 100644 index 00000000000000..c59786fc5426a1 --- /dev/null +++ b/test/e2e/specs/editor/blocks/fixtures/menu-items-request-fixture.js @@ -0,0 +1,84 @@ +module.exports = [ + { + title: 'Home', + url: 'http://localhost:8889/', + menu_order: 1, + }, + { + title: 'About', + type: 'post_type', + object: 'page', + menu_order: 2, + }, + { + title: 'Our team', + type: 'post_type', + object: 'page', + menu_order: 3, + parent: 1, + }, + { + title: 'Shop', + type: 'post_type', + object: 'page', + menu_order: 4, + }, + { + title: 'Winter apparel', + type: 'post_type', + object: 'page', + menu_order: 5, + parent: 3, + }, + { + title: 'Chunky socks', + type: 'post_type', + object: 'page', + menu_order: 6, + parent: 4, + }, + { + title: 'Hideous hats', + type: 'post_type', + object: 'page', + menu_order: 7, + parent: 4, + }, + { + title: 'Glorious gloves', + type: 'post_type', + object: 'page', + menu_order: 8, + parent: 4, + }, + { + title: 'Jazzy Jumpers', + type: 'post_type', + object: 'page', + menu_order: 9, + parent: 4, + }, + { + title: 'Shipping', + type: 'post_type', + object: 'page', + menu_order: 10, + }, + { + title: 'Contact Us', + type: 'post_type', + object: 'page', + menu_order: 11, + }, + { + title: 'WordPress.org', + url: 'https://wordpress.org', + menu_order: 12, + }, + { + title: 'Google', + url: 'https://google.com', + menu_order: 13, + parent: 11, + }, +]; diff --git a/test/e2e/specs/editor/blocks/navigation.spec.js b/test/e2e/specs/editor/blocks/navigation.spec.js index 89a84c519656ee..acd8de02c1066a 100644 --- a/test/e2e/specs/editor/blocks/navigation.spec.js +++ b/test/e2e/specs/editor/blocks/navigation.spec.js @@ -3,6 +3,17 @@ */ const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); +/** + * Internal dependencies + */ +const menuItemsFixture = require( './fixtures/menu-items-request-fixture.js' ); + +test.use( { + navBlockUtils: async ( { page, requestUtils }, use ) => { + await use( new NavigationBlockUtils( { page, requestUtils } ) ); + }, +} ); + test.describe( 'As a user I want the navigation block to fallback to the best possible default', () => { @@ -10,12 +21,24 @@ test.describe( await requestUtils.activateTheme( 'emptytheme' ); } ); - test.beforeEach( async ( { admin } ) => { - await admin.createNewPost(); + test.beforeEach( async ( { admin, requestUtils } ) => { + await Promise.all( [ + requestUtils.deleteAllPosts(), + requestUtils.deleteAllPosts( 'pages' ), + requestUtils.deleteAllPosts( 'navigation' ), + requestUtils.deleteAllMenus(), + admin.createNewPost(), + ] ); } ); test.afterAll( async ( { requestUtils } ) => { - await requestUtils.activateTheme( 'twentytwentyone' ); + await Promise.all( [ + requestUtils.deleteAllPosts(), + requestUtils.deleteAllPosts( 'pages' ), + requestUtils.deleteAllPosts( 'navigation' ), + requestUtils.deleteAllMenus(), + requestUtils.activateTheme( 'twentytwentyone' ), + ] ); } ); test.afterEach( async ( { requestUtils } ) => { @@ -48,5 +71,46 @@ test.describe( ` ); } ); + + test( 'default to my most recently created menu', async ( { + editor, + requestUtils, + } ) => { + //Create a menu + await requestUtils.createMenu( + { name: 'Test Menu 1' }, + menuItemsFixture + ); + //insert navigation block + //check the markup of the block + //check the block in the list view? + //check the block in the frontend? + await editor.page.pause(); + } ); } ); + +class NavigationBlockUtils { + constructor( { editor, page, requestUtils } ) { + this.editor = editor; + this.page = page; + this.requestUtils = requestUtils; + } + + /** + * Create a navigation menu + * + * @param {Object} menuData navigation menu post data. + * @return {string} Menu content. + */ + async createNavigationMenu( menuData ) { + return this.requestUtils.rest( { + method: 'POST', + path: `/wp/v2/navigation/`, + data: { + status: 'publish', + ...menuData, + }, + } ); + } +}