Skip to content

Commit ebb55c4

Browse files
test: add iframe-aware e2e coverage for Otter arrow-key navigation
Move the arrow-key navigation test out of section.spec.js (where it queried the top document and found no blocks under the WP 7.0 iframed canvas) into a dedicated, canvas-aware spec covering sibling navigation up/down, multi-hop presses, no-sibling boundaries, and the modifier-key guard. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 8540a25 commit ebb55c4

2 files changed

Lines changed: 166 additions & 65 deletions

File tree

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/**
2+
* WordPress dependencies
3+
*/
4+
import { test, expect } from '@wordpress/e2e-test-utils-playwright';
5+
6+
/**
7+
* Internal dependencies
8+
*/
9+
import { waitForEditorReady } from '../helpers/editor';
10+
11+
/**
12+
* Reset the editor with a known set of blocks and return their client IDs.
13+
*
14+
* The keyboard-navigation plugin only acts on top-level Otter blocks, so the
15+
* returned IDs are everything we need to drive and assert selection from the
16+
* data store (which lives in the top window, not the iframed canvas).
17+
*
18+
* @param {import('@playwright/test').Page} page The page.
19+
* @param {Function} recipe A function (serialized and run in the browser) returning an array of blocks. It receives `createBlock`.
20+
* @return {Promise<string[]>} The client IDs of the top-level blocks, in order.
21+
*/
22+
const resetWithBlocks = ( page, recipe ) =>
23+
page.evaluate( ( recipeSource ) => {
24+
const build = new Function( 'createBlock', `return (${ recipeSource })( createBlock );` );
25+
const blocks = build( window.wp.blocks.createBlock );
26+
27+
window.wp.data.dispatch( 'core/block-editor' ).resetBlocks( blocks );
28+
29+
return blocks.map( ( block ) => block.clientId );
30+
}, recipe.toString() );
31+
32+
const getSelectedClientId = ( page ) =>
33+
page.evaluate( () => window.wp.data.select( 'core/block-editor' ).getSelectedBlockClientId() );
34+
35+
const waitForSelectedClientId = ( page, clientId ) =>
36+
page.waitForFunction(
37+
( expected ) => expected === window.wp.data.select( 'core/block-editor' ).getSelectedBlockClientId(),
38+
clientId
39+
);
40+
41+
/**
42+
* Select a block in the canvas the same way a user would, so the keydown
43+
* originates from inside the editor-canvas iframe where the plugin listens.
44+
*
45+
* @param {import('@wordpress/e2e-test-utils-playwright').Editor} editor The editor utils.
46+
* @param {import('@playwright/test').Page} page The page.
47+
* @param {string} clientId The block client ID.
48+
* @return {Promise<void>}
49+
*/
50+
const selectCanvasBlock = async( editor, page, clientId ) => {
51+
await editor.selectBlocks( editor.canvas.locator( `[data-block="${ clientId }"]` ) );
52+
await waitForSelectedClientId( page, clientId );
53+
};
54+
55+
test.describe( 'Keyboard navigation for Otter blocks', () => {
56+
test.beforeEach( async({ admin, page }) => {
57+
await admin.createNewPost();
58+
await waitForEditorReady( page );
59+
});
60+
61+
test( 'ArrowDown selects the next sibling block', async({ editor, page }) => {
62+
const [ , sectionId, afterId ] = await resetWithBlocks( page, ( createBlock ) => [
63+
createBlock( 'core/paragraph', { content: 'Before' }),
64+
createBlock( 'themeisle-blocks/advanced-columns', {}, [
65+
createBlock( 'themeisle-blocks/advanced-column', {}, [
66+
createBlock( 'core/paragraph', { content: 'Inside' })
67+
])
68+
]),
69+
createBlock( 'core/paragraph', { content: 'After' })
70+
]);
71+
72+
await selectCanvasBlock( editor, page, sectionId );
73+
74+
await page.keyboard.press( 'ArrowDown' );
75+
76+
await waitForSelectedClientId( page, afterId );
77+
});
78+
79+
test( 'ArrowUp selects the previous sibling block', async({ editor, page }) => {
80+
const [ beforeId, sectionId ] = await resetWithBlocks( page, ( createBlock ) => [
81+
createBlock( 'core/paragraph', { content: 'Before' }),
82+
createBlock( 'themeisle-blocks/advanced-columns', {}, [
83+
createBlock( 'themeisle-blocks/advanced-column', {}, [
84+
createBlock( 'core/paragraph', { content: 'Inside' })
85+
])
86+
]),
87+
createBlock( 'core/paragraph', { content: 'After' })
88+
]);
89+
90+
await selectCanvasBlock( editor, page, sectionId );
91+
92+
await page.keyboard.press( 'ArrowUp' );
93+
94+
await waitForSelectedClientId( page, beforeId );
95+
});
96+
97+
test( 'navigates down and back up between two adjacent Otter blocks', async({ editor, page }) => {
98+
const [ firstId, secondId ] = await resetWithBlocks( page, ( createBlock ) => [
99+
createBlock( 'themeisle-blocks/advanced-heading', { content: 'First' }),
100+
createBlock( 'themeisle-blocks/advanced-heading', { content: 'Second' })
101+
]);
102+
103+
await selectCanvasBlock( editor, page, firstId );
104+
105+
await page.keyboard.press( 'ArrowDown' );
106+
await waitForSelectedClientId( page, secondId );
107+
108+
await page.keyboard.press( 'ArrowUp' );
109+
await waitForSelectedClientId( page, firstId );
110+
});
111+
112+
test( 'navigates across several Otter blocks with repeated presses', async({ editor, page }) => {
113+
const [ firstId, secondId, thirdId ] = await resetWithBlocks( page, ( createBlock ) => [
114+
createBlock( 'themeisle-blocks/advanced-heading', { content: 'One' }),
115+
createBlock( 'themeisle-blocks/advanced-heading', { content: 'Two' }),
116+
createBlock( 'themeisle-blocks/advanced-heading', { content: 'Three' })
117+
]);
118+
119+
await selectCanvasBlock( editor, page, firstId );
120+
121+
await page.keyboard.press( 'ArrowDown' );
122+
await waitForSelectedClientId( page, secondId );
123+
124+
await page.keyboard.press( 'ArrowDown' );
125+
await waitForSelectedClientId( page, thirdId );
126+
127+
await page.keyboard.press( 'ArrowUp' );
128+
await waitForSelectedClientId( page, secondId );
129+
});
130+
131+
test( 'keeps the selection when there is no sibling in the pressed direction', async({ editor, page }) => {
132+
// The Otter block is the last top-level block, so ArrowDown has no
133+
// sibling to move to and the plugin must leave the selection untouched.
134+
const [ , lastId ] = await resetWithBlocks( page, ( createBlock ) => [
135+
createBlock( 'core/paragraph', { content: 'Before' }),
136+
createBlock( 'themeisle-blocks/advanced-heading', { content: 'Last' })
137+
]);
138+
139+
await selectCanvasBlock( editor, page, lastId );
140+
141+
await page.keyboard.press( 'ArrowDown' );
142+
143+
// Give any (incorrect) deferred selectBlock a couple of frames to run.
144+
await page.evaluate( () => new Promise( ( resolve ) => window.requestAnimationFrame( () => window.requestAnimationFrame( resolve ) ) ) );
145+
146+
expect( await getSelectedClientId( page ) ).toBe( lastId );
147+
});
148+
149+
test( 'does not navigate when a modifier key is held', async({ editor, page }) => {
150+
const [ firstId ] = await resetWithBlocks( page, ( createBlock ) => [
151+
createBlock( 'themeisle-blocks/advanced-heading', { content: 'First' }),
152+
createBlock( 'themeisle-blocks/advanced-heading', { content: 'Second' })
153+
]);
154+
155+
await selectCanvasBlock( editor, page, firstId );
156+
157+
// Alt+ArrowDown is not a block-navigation shortcut and the plugin bails
158+
// out on any modifier, so the Otter block must stay selected.
159+
await page.keyboard.press( 'Alt+ArrowDown' );
160+
161+
await page.evaluate( () => new Promise( ( resolve ) => window.requestAnimationFrame( () => window.requestAnimationFrame( resolve ) ) ) );
162+
163+
expect( await getSelectedClientId( page ) ).toBe( firstId );
164+
});
165+
});

src/blocks/test/e2e/blocks/section.spec.js

Lines changed: 1 addition & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -13,71 +13,7 @@ test.describe( 'Section Block', () => {
1313
await admin.createNewPost();
1414
});
1515

16-
test( 'can navigate between blocks using arrow keys', async({ editor, page }) => {
17-
await page.waitForFunction( () => window?.wp?.blocks && window?.wp?.data );
18-
await page.evaluate( () => {
19-
const { createBlock } = window.wp.blocks;
20-
21-
const blocks = [
22-
createBlock( 'core/paragraph', { content: 'Before' } ),
23-
createBlock(
24-
'themeisle-blocks/advanced-columns',
25-
{},
26-
[
27-
createBlock(
28-
'themeisle-blocks/advanced-column',
29-
{},
30-
[ createBlock( 'core/paragraph', { content: 'Inside' } ) ]
31-
)
32-
]
33-
),
34-
createBlock( 'core/paragraph', { content: 'After' } )
35-
];
36-
37-
window.wp.data.dispatch( 'core/block-editor' ).resetBlocks( blocks );
38-
});
39-
40-
const rootBlocks = page.locator( '.block-editor-block-list__layout.is-root-container' );
41-
42-
const sectionBlock = rootBlocks.locator(
43-
'> .block-editor-block-list__block[data-type="themeisle-blocks/advanced-columns"]'
44-
).first();
45-
46-
const paragraphs = rootBlocks.locator(
47-
'> .block-editor-block-list__block[data-type="core/paragraph"]'
48-
);
49-
50-
await expect( paragraphs ).toHaveCount( 2 );
51-
52-
const firstParagraph = paragraphs.first();
53-
const lastParagraph = paragraphs.nth( 1 );
54-
55-
const firstParagraphClientId = await firstParagraph.getAttribute( 'data-block' );
56-
const lastParagraphClientId = await lastParagraph.getAttribute( 'data-block' );
57-
58-
expect( firstParagraphClientId ).toBeTruthy();
59-
expect( lastParagraphClientId ).toBeTruthy();
60-
61-
await editor.selectBlocks( sectionBlock );
62-
await page.waitForFunction( () => {
63-
return 'themeisle-blocks/advanced-columns' === window.wp.data.select( 'core/block-editor' )?.getSelectedBlock?.()?.name;
64-
});
65-
66-
await page.keyboard.press( 'ArrowDown' );
67-
await page.waitForFunction( ( expectedClientId ) => {
68-
return expectedClientId === window.wp.data.select( 'core/block-editor' )?.getSelectedBlockClientId?.();
69-
}, lastParagraphClientId );
70-
71-
await editor.selectBlocks( sectionBlock );
72-
await page.waitForFunction( () => {
73-
return 'themeisle-blocks/advanced-columns' === window.wp.data.select( 'core/block-editor' )?.getSelectedBlock?.()?.name;
74-
});
75-
76-
await page.keyboard.press( 'ArrowUp' );
77-
await page.waitForFunction( ( expectedClientId ) => {
78-
return expectedClientId === window.wp.data.select( 'core/block-editor' )?.getSelectedBlockClientId?.();
79-
}, firstParagraphClientId );
80-
});
16+
// Arrow-key navigation for Otter blocks is covered in keyboard-navigation.spec.js.
8117

8218
test( 'can be created by typing "/section"', async({ editor, page }) => {
8319

0 commit comments

Comments
 (0)