-
Notifications
You must be signed in to change notification settings - Fork 4.9k
Expand file tree
/
Copy pathadding-widgets.test.js
More file actions
353 lines (286 loc) · 10.1 KB
/
Copy pathadding-widgets.test.js
File metadata and controls
353 lines (286 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
/**
* WordPress dependencies
*/
import {
visitAdminPage,
deactivatePlugin,
activatePlugin,
activateTheme,
} from '@wordpress/e2e-test-utils';
/**
* External dependencies
*/
import { groupBy, mapValues } from 'lodash';
/** @typedef {import('puppeteer').ElementHandle} ElementHandle */
describe( 'Widgets screen', () => {
beforeEach( async () => {
await visitAdminPage( 'themes.php', 'page=gutenberg-widgets' );
// Wait for the widget areas to load.
await page.waitForSelector(
'[aria-label="Block: Widget Area"][role="group"]'
);
} );
afterEach( async () => {
await cleanupWidgets();
} );
beforeAll( async () => {
// TODO: Ideally we can bundle our test theme directly in the repo.
await activateTheme( 'twentytwenty' );
await deactivatePlugin(
'gutenberg-test-plugin-disables-the-css-animations'
);
await cleanupWidgets();
} );
afterAll( async () => {
await activatePlugin(
'gutenberg-test-plugin-disables-the-css-animations'
);
await activateTheme( 'twentytwentyone' );
} );
async function getParagraphBlockInGlobalInserter() {
await page.click(
'button[aria-pressed="false"][aria-label="Add block"]'
);
const blockLibrary = await page.waitForSelector(
'[aria-label="Block Library"][role="region"]'
);
// Check that there are categorizations in the inserter (#26329).
const categoryHeader = await blockLibrary.$$( 'h2' );
expect( categoryHeader.length > 0 ).toBe( true );
const [ addParagraphBlock ] = await blockLibrary.$x(
'//*[@role="option"][*[text()="Paragraph"]]'
);
return addParagraphBlock;
}
async function expectInsertionPointIndicatorToBeBelowLastBlock(
widgetArea
) {
const childBlocks = await widgetArea.$$( '[data-block]' );
const lastBlock = childBlocks[ childBlocks.length - 1 ];
const lastBlockBoundingBox = await lastBlock.boundingBox();
// TODO: Probably need a more accessible way to select this, maybe a test ID or data attribute.
const insertionPointIndicator = await page.$(
'.block-editor-block-list__insertion-point-indicator'
);
const insertionPointIndicatorBoundingBox = await insertionPointIndicator.boundingBox();
expect(
insertionPointIndicatorBoundingBox.y > lastBlockBoundingBox.y
).toBe( true );
}
it( 'Should insert content using the global inserter', async () => {
const widgetAreas = await page.$$(
'[aria-label="Block: Widget Area"][role="group"]'
);
const [ firstWidgetArea ] = widgetAreas;
let addParagraphBlock = await getParagraphBlockInGlobalInserter();
await addParagraphBlock.hover();
// FIXME: The insertion point indicator is not showing when the widget area has no blocks.
// await expectInsertionPointIndicatorToBeBelowLastBlock(
// firstWidgetArea
// );
await addParagraphBlock.click();
const addedParagraphBlockInFirstWidgetArea = await firstWidgetArea.$(
'[data-block][data-type="core/paragraph"][aria-label^="Empty block"]'
);
expect(
await addedParagraphBlockInFirstWidgetArea.evaluate(
( node ) => node === document.activeElement
)
).toBe( true );
await page.keyboard.type( 'First Paragraph' );
addParagraphBlock = await getParagraphBlockInGlobalInserter();
await addParagraphBlock.hover();
await expectInsertionPointIndicatorToBeBelowLastBlock(
firstWidgetArea
);
await addParagraphBlock.click();
await page.keyboard.type( 'Second Paragraph' );
/**
* FIXME: There seems to have a bug when saving the widgets
*/
// await secondWidgetArea.click();
// addParagraphBlock = await getParagraphBlockInGlobalInserter();
// await addParagraphBlock.hover();
// // FIXME: The insertion point indicator is not showing when the widget area has no blocks.
// // await expectInsertionPointIndicatorToBeBelowLastBlock(
// // secondWidgetArea
// // );
// await addParagraphBlock.click();
// const addedParagraphBlockInSecondWidgetArea = await secondWidgetArea.$(
// '[data-block][data-type="core/paragraph"][aria-label^="Empty block"]'
// );
// expect(
// await addedParagraphBlockInSecondWidgetArea.evaluate(
// ( node ) => node === document.activeElement
// )
// ).toBe( true );
// await page.keyboard.type( 'Third Paragraph' );
await saveWidgets();
const serializedWidgetAreas = await getSerializedWidgetAreas();
expect( serializedWidgetAreas ).toMatchInlineSnapshot( `
Object {
"sidebar-1": "<div class=\\"widget widget_block\\"><div class=\\"widget-content\\">
<p>First Paragraph</p>
</div></div>
<div class=\\"widget widget_block\\"><div class=\\"widget-content\\">
<p>Second Paragraph</p>
</div></div>",
}
` );
} );
it( 'Should insert content using the inline inserter', async () => {
const firstWidgetArea = await page.$(
'[aria-label="Block: Widget Area"][role="group"]'
);
// Scroll to the end of the first widget area.
await firstWidgetArea.evaluate( ( node ) =>
node.scrollIntoView( { block: 'end' } )
);
const firstWidgetAreaBoundingBox = await firstWidgetArea.boundingBox();
// Click near the end of the widget area to select it.
await page.mouse.click(
firstWidgetAreaBoundingBox.x + firstWidgetAreaBoundingBox.width / 2,
firstWidgetAreaBoundingBox.y +
firstWidgetAreaBoundingBox.height -
10
);
// Aria selectors cannot select buttons with the aria-haspopup property, fallback to CSS selector.
const inlineInserterButton = await page.waitForSelector(
'button[aria-label="Add block"][aria-haspopup="true"]'
);
await inlineInserterButton.click();
const inlineQuickInserter = await page.waitForSelector(
'[aria-label="Blocks"][role="listbox"]'
);
const [ paragraphBlock ] = await inlineQuickInserter.$x(
'//*[@role="option"][*[text()="Paragraph"]]'
);
await paragraphBlock.click();
const firstParagraphBlock = await firstWidgetArea.$(
'[data-block][data-type="core/paragraph"][aria-label^="Empty block"]'
);
expect(
await firstParagraphBlock.evaluate(
( node ) => node === document.activeElement
)
).toBe( true );
await page.keyboard.type( 'First Paragraph' );
await page.keyboard.press( 'Enter' );
await page.keyboard.type( 'Second Paragraph' );
const secondParagraphBlock = await page.evaluateHandle(
() => document.activeElement
);
expect( secondParagraphBlock ).not.toBe( firstParagraphBlock );
const secondParagraphBlockBoundingBox = await secondParagraphBlock.boundingBox();
// Click outside the block to move the focus back to the widget area.
await page.mouse.click(
secondParagraphBlockBoundingBox.x +
firstWidgetAreaBoundingBox.width / 2,
secondParagraphBlockBoundingBox.y +
secondParagraphBlockBoundingBox.height +
10
);
// Hover above the last block to trigger the inline inserter between blocks.
await page.mouse.move(
secondParagraphBlockBoundingBox.x +
secondParagraphBlockBoundingBox.width / 2,
secondParagraphBlockBoundingBox.y - 10
);
const inserterButton = await page.waitForSelector(
'button[aria-label="Add block"][aria-haspopup="true"]'
);
await inserterButton.click();
// TODO: The query should be rewritten with role and label.
const inserterSearchBox = await page.waitForSelector(
'input[type="search"][placeholder="Search for a block"]'
);
expect(
await inserterSearchBox.evaluate(
( node ) => node === document.activeElement
)
).toBe( true );
await page.keyboard.type( 'Heading' );
const inserterListBox = await page.$(
'[role="listbox"][aria-label="Blocks"]'
);
const [ headingBlockOption ] = await inserterListBox.$x(
'//*[@role="option"][*[text()="Heading"]]'
);
await headingBlockOption.click();
// Get the added heading block as second last block.
const addedHeadingBlock = await secondParagraphBlock.evaluateHandle(
( node ) => node.previousSibling
);
expect(
await addedHeadingBlock.evaluate(
( node ) => node === document.activeElement
)
).toBe( true );
await page.keyboard.type( 'My Heading' );
const addedHeadingBlockSnapshot = await page.accessibility.snapshot( {
root: addedHeadingBlock,
} );
expect( addedHeadingBlockSnapshot.name ).toBe( 'Block: Heading' );
expect( addedHeadingBlockSnapshot.level ).toBe( 2 );
expect( addedHeadingBlockSnapshot.value ).toBe( 'My Heading' );
await saveWidgets();
const serializedWidgetAreas = await getSerializedWidgetAreas();
expect( serializedWidgetAreas ).toMatchInlineSnapshot( `
Object {
"sidebar-1": "<div class=\\"widget widget_block\\"><div class=\\"widget-content\\">
<p>First Paragraph</p>
</div></div>
<div class=\\"widget widget_block\\"><div class=\\"widget-content\\">
<h2>My Heading</h2>
</div></div>
<div class=\\"widget widget_block\\"><div class=\\"widget-content\\">
<p>Second Paragraph</p>
</div></div>",
}
` );
} );
} );
async function saveWidgets() {
const [ updateButton ] = await page.$x( '//button[text()="Update"]' );
await updateButton.click();
await page.waitForXPath( '//*[text()="Widgets saved."]' );
// FIXME: The snackbar above is enough for the widget areas to get saved,
// but not enough for the widgets to get saved.
await page.waitForTimeout( 500 );
}
async function getSerializedWidgetAreas() {
const widgets = await page.evaluate( () =>
wp.data.select( 'core' ).getWidgets()
);
const serializedWidgetAreas = mapValues(
groupBy( widgets, 'sidebar' ),
( sidebarWidgets ) =>
sidebarWidgets
.map( ( widget ) => widget.rendered )
.filter( Boolean )
.join( '\n' )
);
return serializedWidgetAreas;
}
/**
* TODO: Deleting widgets in the new widgets screen seems to be unreliable.
* We visit the old widgets screen to delete them.
* Refactor this to use real interactions in the new widgets screen once the bug is fixed.
*/
async function cleanupWidgets() {
await visitAdminPage( 'widgets.php' );
await page.evaluate( () => {
const deleteButtons = document.querySelectorAll(
'#widgets-right .widget button.widget-control-remove'
);
deleteButtons.forEach( ( deleteButton ) => deleteButton.click() );
} );
await page.click( '#inactive-widgets-control-remove' );
await page.waitForFunction(
() =>
document.querySelectorAll( '.widgets-sortables .widget' ).length ===
0
);
// No idea why we need this, but just asserting the widgets are gone seems to be not enough.
await page.waitForTimeout( 500 );
}