Skip to content

Commit ce2a651

Browse files
ockhamgzioloMamaduka
authored
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]>
1 parent ed4df79 commit ce2a651

File tree

2 files changed

+363
-0
lines changed

2 files changed

+363
-0
lines changed
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
/**
3+
* Plugin Name: Gutenberg Test Block Hooks
4+
* Plugin URI: https://github.com/WordPress/gutenberg
5+
* Author: Gutenberg Team
6+
*
7+
* @package gutenberg-test-block-hooks
8+
*/
9+
10+
defined( 'ABSPATH' ) || exit;
11+
12+
function gutenberg_test_insert_hooked_blocks( $hooked_blocks, $position, $anchor_block, $context ) {
13+
if ( ! $context instanceof WP_Post ) {
14+
return $hooked_blocks;
15+
}
16+
17+
if (
18+
( 'core/heading' === $anchor_block && 'after' === $position ) ||
19+
( 'core/post-content' === $anchor_block && 'last_child' === $position ) ||
20+
( 'core/block' === $anchor_block && 'last_child' === $position )
21+
) {
22+
$hooked_blocks[] = 'core/paragraph';
23+
}
24+
25+
if ( 'core/navigation' === $anchor_block && 'first_child' === $position ) {
26+
$hooked_blocks[] = 'core/home-link';
27+
}
28+
29+
if ( 'core/navigation-link' === $anchor_block && 'after' === $position ) {
30+
$hooked_blocks[] = 'core/page-list';
31+
}
32+
33+
return $hooked_blocks;
34+
}
35+
add_filter( 'hooked_block_types', 'gutenberg_test_insert_hooked_blocks', 10, 4 );
36+
37+
function gutenberg_test_set_hooked_block_inner_html( $hooked_block, $hooked_block_type, $relative_position, $anchor_block ) {
38+
if (
39+
( 'core/heading' === $anchor_block['blockName'] && 'after' === $relative_position ) ||
40+
( 'core/post-content' === $anchor_block['blockName'] && 'last_child' === $relative_position ) ||
41+
( 'core/block' === $anchor_block['blockName'] && 'last_child' === $relative_position )
42+
) {
43+
$hooked_block['attrs'] = array(
44+
'className' => "hooked-block-{$relative_position}-" . str_replace( 'core/', '', $anchor_block['blockName'] ),
45+
);
46+
$hooked_block['innerContent'] = array(
47+
sprintf(
48+
'<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>',
49+
$hooked_block['attrs']['className'],
50+
$relative_position,
51+
$anchor_block['blockName']
52+
),
53+
);
54+
}
55+
56+
return $hooked_block;
57+
}
58+
add_filter( 'hooked_block_core/paragraph', 'gutenberg_test_set_hooked_block_inner_html', 10, 4 );
59+
60+
function gutenberg_register_wp_ignored_hooked_blocks_meta() {
61+
register_post_meta(
62+
'post',
63+
'_wp_ignored_hooked_blocks',
64+
array(
65+
'show_in_rest' => true,
66+
'single' => true,
67+
'type' => 'string',
68+
'auth_callback' => function () {
69+
return current_user_can( 'edit_posts' );
70+
},
71+
)
72+
);
73+
}
74+
add_action( 'rest_api_init', 'gutenberg_register_wp_ignored_hooked_blocks_meta' );
Lines changed: 289 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,289 @@
1+
/**
2+
* WordPress dependencies
3+
*/
4+
const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );
5+
6+
const dummyBlockContent = `<!-- wp:heading -->
7+
<h2 class="wp-block-heading">This is a dummy heading</h2>
8+
<!-- /wp:heading -->
9+
<!-- wp:paragraph {"className":"dummy-paragraph"} -->
10+
<p class="dummy-paragraph">This is a dummy paragraph.</p>
11+
<!-- /wp:paragraph -->`;
12+
13+
const getHookedBlockClassName = ( relativePosition, anchorBlock ) =>
14+
`hooked-block-${ relativePosition }-${ anchorBlock.replace(
15+
'core/',
16+
''
17+
) }`;
18+
19+
const getHookedBlockContent = ( relativePosition, anchorBlock ) =>
20+
`This block was inserted by the Block Hooks API in the ${ relativePosition } position next to the ${ anchorBlock } anchor block.`;
21+
22+
test.describe( 'Block Hooks API', () => {
23+
[
24+
{
25+
name: 'Post Content',
26+
postType: 'post',
27+
blockType: 'core/post-content',
28+
createMethod: 'createPost',
29+
},
30+
{
31+
name: 'Synced Pattern',
32+
postType: 'wp_block',
33+
blockType: 'core/block',
34+
createMethod: 'createBlock',
35+
},
36+
].forEach( ( { name, postType, blockType, createMethod } ) => {
37+
test.describe( `Hooked blocks in ${ name }`, () => {
38+
let postObject, containerPost;
39+
test.beforeAll( async ( { requestUtils } ) => {
40+
postObject = await requestUtils[ createMethod ]( {
41+
title: name,
42+
status: 'publish',
43+
content: dummyBlockContent,
44+
} );
45+
46+
await requestUtils.activatePlugin(
47+
'gutenberg-test-block-hooks'
48+
);
49+
50+
if ( postType !== 'post' ) {
51+
// We need a container post to hold our block instance.
52+
containerPost = await requestUtils.createPost( {
53+
title: `Block Hooks in ${ name }`,
54+
status: 'publish',
55+
content: `<!-- wp:${ blockType } {"ref":${ postObject.id }} /-->`,
56+
meta: {
57+
// Prevent Block Hooks from injecting blocks into the container
58+
// post content so they won't distract from the ones injected
59+
// into the block instance.
60+
_wp_ignored_hooked_blocks: '["core/paragraph"]',
61+
},
62+
} );
63+
} else {
64+
containerPost = postObject;
65+
}
66+
} );
67+
68+
test.afterAll( async ( { requestUtils } ) => {
69+
await requestUtils.deactivatePlugin(
70+
'gutenberg-test-block-hooks'
71+
);
72+
73+
await requestUtils.deleteAllPosts();
74+
await requestUtils.deleteAllBlocks();
75+
} );
76+
77+
test( `should insert hooked blocks into ${ name } on frontend`, async ( {
78+
page,
79+
} ) => {
80+
await page.goto( `/?p=${ containerPost.id }` );
81+
await expect(
82+
page.locator( '.entry-content > *' )
83+
).toHaveClass( [
84+
'wp-block-heading',
85+
getHookedBlockClassName( 'after', 'core/heading' ),
86+
'dummy-paragraph',
87+
getHookedBlockClassName( 'last_child', blockType ),
88+
] );
89+
} );
90+
91+
test( `should insert hooked blocks into ${ name } in editor and respect changes made there`, async ( {
92+
admin,
93+
editor,
94+
page,
95+
} ) => {
96+
const expectedHookedBlockAfterHeading = {
97+
name: 'core/paragraph',
98+
attributes: {
99+
className: getHookedBlockClassName(
100+
'after',
101+
'core/heading'
102+
),
103+
},
104+
};
105+
106+
const expectedHookedBlockLastChild = {
107+
name: 'core/paragraph',
108+
attributes: {
109+
className: getHookedBlockClassName(
110+
'last_child',
111+
blockType
112+
),
113+
},
114+
};
115+
116+
await admin.editPost( postObject.id );
117+
await expect
118+
.poll( editor.getBlocks )
119+
.toMatchObject( [
120+
{ name: 'core/heading' },
121+
expectedHookedBlockAfterHeading,
122+
{ name: 'core/paragraph' },
123+
expectedHookedBlockLastChild,
124+
] );
125+
126+
const hookedBlock = editor.canvas.getByText(
127+
getHookedBlockContent( 'last_child', blockType )
128+
);
129+
await editor.selectBlocks( hookedBlock );
130+
await editor.clickBlockToolbarButton( 'Move up' );
131+
132+
// Save updated post.
133+
const saveButton = page
134+
.getByRole( 'region', { name: 'Editor top bar' } )
135+
.getByRole( 'button', { name: 'Save', exact: true } );
136+
await saveButton.click();
137+
await page
138+
.getByRole( 'button', { name: 'Dismiss this notice' } )
139+
.filter( { hasText: 'updated' } )
140+
.waitFor();
141+
142+
// Reload and verify that the new position of the hooked block has been persisted.
143+
await page.reload();
144+
await expect
145+
.poll( editor.getBlocks )
146+
.toMatchObject( [
147+
{ name: 'core/heading' },
148+
expectedHookedBlockAfterHeading,
149+
expectedHookedBlockLastChild,
150+
{ name: 'core/paragraph' },
151+
] );
152+
153+
// Verify that the frontend reflects the changes made in the editor.
154+
await page.goto( `/?p=${ containerPost.id }` );
155+
await expect(
156+
page.locator( '.entry-content > *' )
157+
).toHaveClass( [
158+
'wp-block-heading',
159+
getHookedBlockClassName( 'after', 'core/heading' ),
160+
getHookedBlockClassName( 'last_child', blockType ),
161+
'dummy-paragraph',
162+
] );
163+
} );
164+
} );
165+
} );
166+
167+
test.describe( 'Hooked blocks in Navigation Menu', () => {
168+
let postObject, containerPost;
169+
test.beforeAll( async ( { requestUtils } ) => {
170+
postObject = await requestUtils.createNavigationMenu( {
171+
title: 'Navigation Menu',
172+
status: 'publish',
173+
content:
174+
'<!-- wp:navigation-link {"label":"wordpress.org","url":"https://wordpress.org","kind":"custom"} /-->',
175+
} );
176+
177+
await requestUtils.activatePlugin( 'gutenberg-test-block-hooks' );
178+
179+
// We need a container to hold our Navigation block instance.
180+
// We create a page (instead of a post) so that it will also
181+
// populate the Page List block, which is one of the hooked blocks
182+
// we use in our testing.
183+
containerPost = await requestUtils.createPage( {
184+
title: 'Block Hooks in Navigation Menu',
185+
status: 'publish',
186+
content: `<!-- wp:navigation {"ref":${ postObject.id }} /-->`,
187+
} );
188+
} );
189+
190+
test.afterAll( async ( { requestUtils } ) => {
191+
await requestUtils.deactivatePlugin( 'gutenberg-test-block-hooks' );
192+
193+
await requestUtils.deleteAllPages();
194+
await requestUtils.deleteAllMenus();
195+
} );
196+
197+
test( 'should insert hooked blocks into Navigation Menu on frontend', async ( {
198+
page,
199+
} ) => {
200+
await page.goto( `/?p=${ containerPost.id }` );
201+
await expect(
202+
page.locator( '.wp-block-navigation__container > *' )
203+
).toHaveClass( [
204+
'wp-block-navigation-item wp-block-home-link',
205+
' wp-block-navigation-item wp-block-navigation-link',
206+
'wp-block-page-list',
207+
] );
208+
} );
209+
210+
test( 'should insert hooked blocks into Navigation Menu in editor and respect changes made there', async ( {
211+
admin,
212+
editor,
213+
page,
214+
} ) => {
215+
await admin.visitSiteEditor( {
216+
postId: postObject.id,
217+
postType: 'wp_navigation',
218+
canvas: 'edit',
219+
} );
220+
221+
// Since the Navigation block is a controlled block, we need
222+
// to specify its client ID when calling `getBlocks`.
223+
let navigationBlock = editor.canvas.getByRole( 'document', {
224+
name: 'Block: Navigation',
225+
} );
226+
let navigationClientId =
227+
await navigationBlock.getAttribute( 'data-block' );
228+
229+
await expect
230+
.poll( () =>
231+
editor.getBlocks( {
232+
clientId: navigationClientId,
233+
} )
234+
)
235+
.toMatchObject( [
236+
{ name: 'core/home-link' },
237+
{ name: 'core/navigation-link' },
238+
{ name: 'core/page-list' },
239+
] );
240+
241+
const hookedBlock = editor.canvas.getByRole( 'document', {
242+
name: 'Block: Home Link',
243+
} );
244+
await editor.selectBlocks( hookedBlock );
245+
await editor.clickBlockToolbarButton( 'Move right' );
246+
247+
// Save updated post.
248+
const saveButton = page
249+
.getByRole( 'region', { name: 'Editor top bar' } )
250+
.getByRole( 'button', { name: 'Save', exact: true } );
251+
await saveButton.click();
252+
await page
253+
.getByRole( 'button', { name: 'Dismiss this notice' } )
254+
.filter( { hasText: 'updated' } )
255+
.waitFor();
256+
257+
// Reload and verify that the new position of the hooked block has been persisted.
258+
await page.reload();
259+
260+
navigationBlock = editor.canvas.getByRole( 'document', {
261+
name: 'Block: Navigation',
262+
} );
263+
navigationClientId =
264+
await navigationBlock.getAttribute( 'data-block' );
265+
266+
await expect
267+
.poll( () =>
268+
editor.getBlocks( {
269+
clientId: navigationClientId,
270+
} )
271+
)
272+
.toMatchObject( [
273+
{ name: 'core/navigation-link' },
274+
{ name: 'core/home-link' },
275+
{ name: 'core/page-list' },
276+
] );
277+
278+
// Verify that the frontend reflects the changes made in the editor.
279+
await page.goto( `/?p=${ containerPost.id }` );
280+
await expect(
281+
page.locator( '.wp-block-navigation__container > *' )
282+
).toHaveClass( [
283+
' wp-block-navigation-item wp-block-navigation-link',
284+
'wp-block-navigation-item wp-block-home-link',
285+
'wp-block-page-list',
286+
] );
287+
} );
288+
} );
289+
} );

0 commit comments

Comments
 (0)