-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
E2E Tests: Add Block Hooks Test Coverage (#69044)
Add end-to-end test coverage for Block Hooks; specifically for insertion into post content, synced patterns, and Navigation blocks. The tests check that hooked blocks are inserted correctly on the frontend, and that any changes made in the editor are persisted and respected. They cover both "normal" insertion (i.e. before or after a given anchor block), and first/last child insertion with the containing block serving as the anchor block (which is when the corresponding post object's post meta is used to store `_wp_ignored_hooked_blocks` data). Co-authored-by: ockham <[email protected]> Co-authored-by: gziolo <[email protected]> Co-authored-by: Mamaduka <[email protected]>
- Loading branch information
1 parent
ed4df79
commit ce2a651
Showing
2 changed files
with
363 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
<?php | ||
/** | ||
* Plugin Name: Gutenberg Test Block Hooks | ||
* Plugin URI: https://github.com/WordPress/gutenberg | ||
* Author: Gutenberg Team | ||
* | ||
* @package gutenberg-test-block-hooks | ||
*/ | ||
|
||
defined( 'ABSPATH' ) || exit; | ||
|
||
function gutenberg_test_insert_hooked_blocks( $hooked_blocks, $position, $anchor_block, $context ) { | ||
if ( ! $context instanceof WP_Post ) { | ||
return $hooked_blocks; | ||
} | ||
|
||
if ( | ||
( 'core/heading' === $anchor_block && 'after' === $position ) || | ||
( 'core/post-content' === $anchor_block && 'last_child' === $position ) || | ||
( 'core/block' === $anchor_block && 'last_child' === $position ) | ||
) { | ||
$hooked_blocks[] = 'core/paragraph'; | ||
} | ||
|
||
if ( 'core/navigation' === $anchor_block && 'first_child' === $position ) { | ||
$hooked_blocks[] = 'core/home-link'; | ||
} | ||
|
||
if ( 'core/navigation-link' === $anchor_block && 'after' === $position ) { | ||
$hooked_blocks[] = 'core/page-list'; | ||
} | ||
|
||
return $hooked_blocks; | ||
} | ||
add_filter( 'hooked_block_types', 'gutenberg_test_insert_hooked_blocks', 10, 4 ); | ||
|
||
function gutenberg_test_set_hooked_block_inner_html( $hooked_block, $hooked_block_type, $relative_position, $anchor_block ) { | ||
if ( | ||
( 'core/heading' === $anchor_block['blockName'] && 'after' === $relative_position ) || | ||
( 'core/post-content' === $anchor_block['blockName'] && 'last_child' === $relative_position ) || | ||
( 'core/block' === $anchor_block['blockName'] && 'last_child' === $relative_position ) | ||
) { | ||
$hooked_block['attrs'] = array( | ||
'className' => "hooked-block-{$relative_position}-" . str_replace( 'core/', '', $anchor_block['blockName'] ), | ||
); | ||
$hooked_block['innerContent'] = array( | ||
sprintf( | ||
'<p class="%1$s">This block was inserted by the Block Hooks API in the <code>%2$s</code> position next to the <code>%3$s</code> anchor block.</p>', | ||
$hooked_block['attrs']['className'], | ||
$relative_position, | ||
$anchor_block['blockName'] | ||
), | ||
); | ||
} | ||
|
||
return $hooked_block; | ||
} | ||
add_filter( 'hooked_block_core/paragraph', 'gutenberg_test_set_hooked_block_inner_html', 10, 4 ); | ||
|
||
function gutenberg_register_wp_ignored_hooked_blocks_meta() { | ||
register_post_meta( | ||
'post', | ||
'_wp_ignored_hooked_blocks', | ||
array( | ||
'show_in_rest' => true, | ||
'single' => true, | ||
'type' => 'string', | ||
'auth_callback' => function () { | ||
return current_user_can( 'edit_posts' ); | ||
}, | ||
) | ||
); | ||
} | ||
add_action( 'rest_api_init', 'gutenberg_register_wp_ignored_hooked_blocks_meta' ); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,289 @@ | ||
/** | ||
* WordPress dependencies | ||
*/ | ||
const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' ); | ||
|
||
const dummyBlockContent = `<!-- wp:heading --> | ||
<h2 class="wp-block-heading">This is a dummy heading</h2> | ||
<!-- /wp:heading --> | ||
<!-- wp:paragraph {"className":"dummy-paragraph"} --> | ||
<p class="dummy-paragraph">This is a dummy paragraph.</p> | ||
<!-- /wp:paragraph -->`; | ||
|
||
const getHookedBlockClassName = ( relativePosition, anchorBlock ) => | ||
`hooked-block-${ relativePosition }-${ anchorBlock.replace( | ||
'core/', | ||
'' | ||
) }`; | ||
|
||
const getHookedBlockContent = ( relativePosition, anchorBlock ) => | ||
`This block was inserted by the Block Hooks API in the ${ relativePosition } position next to the ${ anchorBlock } anchor block.`; | ||
|
||
test.describe( 'Block Hooks API', () => { | ||
[ | ||
{ | ||
name: 'Post Content', | ||
postType: 'post', | ||
blockType: 'core/post-content', | ||
createMethod: 'createPost', | ||
}, | ||
{ | ||
name: 'Synced Pattern', | ||
postType: 'wp_block', | ||
blockType: 'core/block', | ||
createMethod: 'createBlock', | ||
}, | ||
].forEach( ( { name, postType, blockType, createMethod } ) => { | ||
test.describe( `Hooked blocks in ${ name }`, () => { | ||
let postObject, containerPost; | ||
test.beforeAll( async ( { requestUtils } ) => { | ||
postObject = await requestUtils[ createMethod ]( { | ||
title: name, | ||
status: 'publish', | ||
content: dummyBlockContent, | ||
} ); | ||
|
||
await requestUtils.activatePlugin( | ||
'gutenberg-test-block-hooks' | ||
); | ||
|
||
if ( postType !== 'post' ) { | ||
// We need a container post to hold our block instance. | ||
containerPost = await requestUtils.createPost( { | ||
title: `Block Hooks in ${ name }`, | ||
status: 'publish', | ||
content: `<!-- wp:${ blockType } {"ref":${ postObject.id }} /-->`, | ||
meta: { | ||
// Prevent Block Hooks from injecting blocks into the container | ||
// post content so they won't distract from the ones injected | ||
// into the block instance. | ||
_wp_ignored_hooked_blocks: '["core/paragraph"]', | ||
}, | ||
} ); | ||
} else { | ||
containerPost = postObject; | ||
} | ||
} ); | ||
|
||
test.afterAll( async ( { requestUtils } ) => { | ||
await requestUtils.deactivatePlugin( | ||
'gutenberg-test-block-hooks' | ||
); | ||
|
||
await requestUtils.deleteAllPosts(); | ||
await requestUtils.deleteAllBlocks(); | ||
} ); | ||
|
||
test( `should insert hooked blocks into ${ name } on frontend`, async ( { | ||
page, | ||
} ) => { | ||
await page.goto( `/?p=${ containerPost.id }` ); | ||
await expect( | ||
page.locator( '.entry-content > *' ) | ||
).toHaveClass( [ | ||
'wp-block-heading', | ||
getHookedBlockClassName( 'after', 'core/heading' ), | ||
'dummy-paragraph', | ||
getHookedBlockClassName( 'last_child', blockType ), | ||
] ); | ||
} ); | ||
|
||
test( `should insert hooked blocks into ${ name } in editor and respect changes made there`, async ( { | ||
admin, | ||
editor, | ||
page, | ||
} ) => { | ||
const expectedHookedBlockAfterHeading = { | ||
name: 'core/paragraph', | ||
attributes: { | ||
className: getHookedBlockClassName( | ||
'after', | ||
'core/heading' | ||
), | ||
}, | ||
}; | ||
|
||
const expectedHookedBlockLastChild = { | ||
name: 'core/paragraph', | ||
attributes: { | ||
className: getHookedBlockClassName( | ||
'last_child', | ||
blockType | ||
), | ||
}, | ||
}; | ||
|
||
await admin.editPost( postObject.id ); | ||
await expect | ||
.poll( editor.getBlocks ) | ||
.toMatchObject( [ | ||
{ name: 'core/heading' }, | ||
expectedHookedBlockAfterHeading, | ||
{ name: 'core/paragraph' }, | ||
expectedHookedBlockLastChild, | ||
] ); | ||
|
||
const hookedBlock = editor.canvas.getByText( | ||
getHookedBlockContent( 'last_child', blockType ) | ||
); | ||
await editor.selectBlocks( hookedBlock ); | ||
await editor.clickBlockToolbarButton( 'Move up' ); | ||
|
||
// Save updated post. | ||
const saveButton = page | ||
.getByRole( 'region', { name: 'Editor top bar' } ) | ||
.getByRole( 'button', { name: 'Save', exact: true } ); | ||
await saveButton.click(); | ||
await page | ||
.getByRole( 'button', { name: 'Dismiss this notice' } ) | ||
.filter( { hasText: 'updated' } ) | ||
.waitFor(); | ||
|
||
// Reload and verify that the new position of the hooked block has been persisted. | ||
await page.reload(); | ||
await expect | ||
.poll( editor.getBlocks ) | ||
.toMatchObject( [ | ||
{ name: 'core/heading' }, | ||
expectedHookedBlockAfterHeading, | ||
expectedHookedBlockLastChild, | ||
{ name: 'core/paragraph' }, | ||
] ); | ||
|
||
// Verify that the frontend reflects the changes made in the editor. | ||
await page.goto( `/?p=${ containerPost.id }` ); | ||
await expect( | ||
page.locator( '.entry-content > *' ) | ||
).toHaveClass( [ | ||
'wp-block-heading', | ||
getHookedBlockClassName( 'after', 'core/heading' ), | ||
getHookedBlockClassName( 'last_child', blockType ), | ||
'dummy-paragraph', | ||
] ); | ||
} ); | ||
} ); | ||
} ); | ||
|
||
test.describe( 'Hooked blocks in Navigation Menu', () => { | ||
let postObject, containerPost; | ||
test.beforeAll( async ( { requestUtils } ) => { | ||
postObject = await requestUtils.createNavigationMenu( { | ||
title: 'Navigation Menu', | ||
status: 'publish', | ||
content: | ||
'<!-- wp:navigation-link {"label":"wordpress.org","url":"https://wordpress.org","kind":"custom"} /-->', | ||
} ); | ||
|
||
await requestUtils.activatePlugin( 'gutenberg-test-block-hooks' ); | ||
|
||
// We need a container to hold our Navigation block instance. | ||
// We create a page (instead of a post) so that it will also | ||
// populate the Page List block, which is one of the hooked blocks | ||
// we use in our testing. | ||
containerPost = await requestUtils.createPage( { | ||
title: 'Block Hooks in Navigation Menu', | ||
status: 'publish', | ||
content: `<!-- wp:navigation {"ref":${ postObject.id }} /-->`, | ||
} ); | ||
} ); | ||
|
||
test.afterAll( async ( { requestUtils } ) => { | ||
await requestUtils.deactivatePlugin( 'gutenberg-test-block-hooks' ); | ||
|
||
await requestUtils.deleteAllPages(); | ||
await requestUtils.deleteAllMenus(); | ||
} ); | ||
|
||
test( 'should insert hooked blocks into Navigation Menu on frontend', async ( { | ||
page, | ||
} ) => { | ||
await page.goto( `/?p=${ containerPost.id }` ); | ||
await expect( | ||
page.locator( '.wp-block-navigation__container > *' ) | ||
).toHaveClass( [ | ||
'wp-block-navigation-item wp-block-home-link', | ||
' wp-block-navigation-item wp-block-navigation-link', | ||
'wp-block-page-list', | ||
] ); | ||
} ); | ||
|
||
test( 'should insert hooked blocks into Navigation Menu in editor and respect changes made there', async ( { | ||
admin, | ||
editor, | ||
page, | ||
} ) => { | ||
await admin.visitSiteEditor( { | ||
postId: postObject.id, | ||
postType: 'wp_navigation', | ||
canvas: 'edit', | ||
} ); | ||
|
||
// Since the Navigation block is a controlled block, we need | ||
// to specify its client ID when calling `getBlocks`. | ||
let navigationBlock = editor.canvas.getByRole( 'document', { | ||
name: 'Block: Navigation', | ||
} ); | ||
let navigationClientId = | ||
await navigationBlock.getAttribute( 'data-block' ); | ||
|
||
await expect | ||
.poll( () => | ||
editor.getBlocks( { | ||
clientId: navigationClientId, | ||
} ) | ||
) | ||
.toMatchObject( [ | ||
{ name: 'core/home-link' }, | ||
{ name: 'core/navigation-link' }, | ||
{ name: 'core/page-list' }, | ||
] ); | ||
|
||
const hookedBlock = editor.canvas.getByRole( 'document', { | ||
name: 'Block: Home Link', | ||
} ); | ||
await editor.selectBlocks( hookedBlock ); | ||
await editor.clickBlockToolbarButton( 'Move right' ); | ||
|
||
// Save updated post. | ||
const saveButton = page | ||
.getByRole( 'region', { name: 'Editor top bar' } ) | ||
.getByRole( 'button', { name: 'Save', exact: true } ); | ||
await saveButton.click(); | ||
await page | ||
.getByRole( 'button', { name: 'Dismiss this notice' } ) | ||
.filter( { hasText: 'updated' } ) | ||
.waitFor(); | ||
|
||
// Reload and verify that the new position of the hooked block has been persisted. | ||
await page.reload(); | ||
|
||
navigationBlock = editor.canvas.getByRole( 'document', { | ||
name: 'Block: Navigation', | ||
} ); | ||
navigationClientId = | ||
await navigationBlock.getAttribute( 'data-block' ); | ||
|
||
await expect | ||
.poll( () => | ||
editor.getBlocks( { | ||
clientId: navigationClientId, | ||
} ) | ||
) | ||
.toMatchObject( [ | ||
{ name: 'core/navigation-link' }, | ||
{ name: 'core/home-link' }, | ||
{ name: 'core/page-list' }, | ||
] ); | ||
|
||
// Verify that the frontend reflects the changes made in the editor. | ||
await page.goto( `/?p=${ containerPost.id }` ); | ||
await expect( | ||
page.locator( '.wp-block-navigation__container > *' ) | ||
).toHaveClass( [ | ||
' wp-block-navigation-item wp-block-navigation-link', | ||
'wp-block-navigation-item wp-block-home-link', | ||
'wp-block-page-list', | ||
] ); | ||
} ); | ||
} ); | ||
} ); |