@wordpress/e2e-tests 3.0.7 → 3.0.10-next.a55ed9455a.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/config/setup-performance-test.js +1 -1
  2. package/config/setup-test-framework.js +1 -1
  3. package/package.json +7 -7
  4. package/specs/editor/blocks/__snapshots__/heading.test.js.snap +8 -8
  5. package/specs/editor/blocks/__snapshots__/navigation.test.js.snap +16 -16
  6. package/specs/editor/blocks/__snapshots__/quote.test.js.snap +10 -10
  7. package/specs/editor/blocks/classic.test.js +2 -2
  8. package/specs/editor/blocks/cover.test.js +17 -17
  9. package/specs/editor/blocks/image.test.js +4 -3
  10. package/specs/editor/blocks/list.test.js +1 -1
  11. package/specs/editor/blocks/navigation.test.js +392 -65
  12. package/specs/editor/blocks/table.test.js +1 -1
  13. package/specs/editor/plugins/align-hook.test.js +10 -10
  14. package/specs/editor/plugins/block-context.test.js +1 -1
  15. package/specs/editor/plugins/block-directory-add.test.js +14 -14
  16. package/specs/editor/plugins/custom-post-types.test.js +1 -1
  17. package/specs/editor/plugins/custom-taxonomies.test.js +3 -3
  18. package/specs/editor/plugins/meta-attribute-block.test.js +1 -1
  19. package/specs/editor/plugins/meta-boxes.test.js +3 -3
  20. package/specs/editor/plugins/templates.test.js +1 -1
  21. package/specs/editor/plugins/wp-editor-meta-box.test.js +1 -1
  22. package/specs/editor/various/__snapshots__/block-grouping.test.js.snap +4 -4
  23. package/specs/editor/various/__snapshots__/inserting-blocks.test.js.snap +2 -2
  24. package/specs/editor/various/__snapshots__/keep-styles-on-block-transforms.test.js.snap +3 -3
  25. package/specs/editor/various/autosave.test.js +19 -19
  26. package/specs/editor/various/block-deletion.test.js +5 -5
  27. package/specs/editor/various/block-grouping.test.js +29 -12
  28. package/specs/editor/various/block-hierarchy-navigation.test.js +5 -5
  29. package/specs/editor/various/change-detection.test.js +3 -3
  30. package/specs/editor/various/compatibility-classic-editor.test.js +1 -1
  31. package/specs/editor/various/copy-cut-paste-whole-blocks.test.js +7 -7
  32. package/specs/editor/various/datepicker.test.js +1 -1
  33. package/specs/editor/various/duplicating-blocks.test.js +3 -3
  34. package/specs/editor/various/editor-modes.test.js +5 -5
  35. package/specs/editor/various/inserting-blocks.test.js +6 -6
  36. package/specs/editor/various/invalid-block.test.js +4 -4
  37. package/specs/editor/various/is-typing.test.js +8 -8
  38. package/specs/editor/various/keyboard-navigable-blocks.test.js +2 -2
  39. package/specs/editor/various/links.test.js +90 -90
  40. package/specs/editor/various/list-view.test.js +142 -1
  41. package/specs/editor/various/manage-reusable-blocks.test.js +5 -5
  42. package/specs/editor/various/multi-block-selection.test.js +86 -2
  43. package/specs/editor/various/navigable-toolbar.test.js +1 -1
  44. package/specs/editor/various/new-post-default-content.test.js +2 -2
  45. package/specs/editor/various/new-post.test.js +1 -1
  46. package/specs/editor/various/nux.test.js +11 -11
  47. package/specs/editor/various/popovers.test.js +3 -3
  48. package/specs/editor/various/post-editor-template-mode.test.js +189 -11
  49. package/specs/editor/various/post-visibility.test.js +1 -1
  50. package/specs/editor/various/preferences.test.js +1 -1
  51. package/specs/editor/various/preview.test.js +8 -4
  52. package/specs/editor/various/publish-button.test.js +2 -2
  53. package/specs/editor/various/publishing.test.js +50 -1
  54. package/specs/editor/various/reusable-blocks.test.js +31 -31
  55. package/specs/editor/various/rich-text.test.js +1 -1
  56. package/specs/editor/various/splitting-merging.test.js +6 -6
  57. package/specs/editor/various/style-variation.test.js +2 -2
  58. package/specs/editor/various/switch-to-draft.test.js +256 -0
  59. package/specs/editor/various/taxonomies.test.js +1 -1
  60. package/specs/editor/various/toolbar-roving-tabindex.test.js +1 -1
  61. package/specs/editor/various/writing-flow.test.js +85 -20
  62. package/specs/experiments/navigation-editor.test.js +168 -3
  63. package/specs/performance/post-editor.test.js +7 -7
  64. package/specs/performance/site-editor.test.js +6 -7
  65. package/specs/site-editor/document-settings.test.js +8 -8
  66. package/specs/site-editor/multi-entity-editing.test.js +6 -6
  67. package/specs/site-editor/multi-entity-saving.test.js +5 -4
  68. package/specs/site-editor/settings-sidebar.test.js +5 -5
  69. package/specs/site-editor/site-editor-export.test.js +3 -3
  70. package/specs/site-editor/site-editor-inserter.test.js +3 -3
  71. package/specs/site-editor/template-part.test.js +27 -26
  72. package/specs/site-editor/template-revert.test.js +12 -22
  73. package/specs/widgets/customizing-widgets.test.js +14 -9
  74. package/specs/widgets/editing-widgets.test.js +8 -7
@@ -26,6 +26,7 @@ import {
26
26
  loginUser,
27
27
  deleteUser,
28
28
  switchUserToAdmin,
29
+ clickBlockToolbarButton,
29
30
  } from '@wordpress/e2e-test-utils';
30
31
  import { addQueryArgs } from '@wordpress/url';
31
32
 
@@ -83,15 +84,15 @@ async function updateActiveNavigationLink( { url, label, type } ) {
83
84
 
84
85
  // Wait for the autocomplete suggestion item to appear.
85
86
  await page.waitForXPath( suggestionPath );
86
- // Set the suggestion
87
+ // Set the suggestion.
87
88
  const suggestion = await page.waitForXPath( suggestionPath );
88
89
 
89
- // Select it (so we're clicking the right one, even if it's further down the list)
90
+ // Select it (so we're clicking the right one, even if it's further down the list).
90
91
  await suggestion.click();
91
92
  }
92
93
 
93
94
  if ( label ) {
94
- // Wait for rich text editor input to be focused before we start typing the label
95
+ // Wait for rich text editor input to be focused before we start typing the label.
95
96
  await page.waitForSelector( ':focus.rich-text' );
96
97
 
97
98
  // With https://github.com/WordPress/gutenberg/pull/19686, we're auto-selecting the label if the label is URL-ish.
@@ -119,12 +120,31 @@ async function selectClassicMenu( optionText ) {
119
120
  `//*[contains(@class, 'components-menu-item__item')][ text()="${ optionText }" ]`
120
121
  );
121
122
  await theOption.click();
123
+
124
+ await page.waitForResponse(
125
+ ( response ) =>
126
+ response.url().includes( 'menu-items' ) && response.status() === 200
127
+ );
128
+ }
129
+
130
+ async function populateNavWithOneItem() {
131
+ // Add a Link block first.
132
+ const appender = await page.waitForSelector(
133
+ '.wp-block-navigation .block-list-appender'
134
+ );
135
+ await appender.click();
136
+ // Add a link to the Link block.
137
+ await updateActiveNavigationLink( {
138
+ url: 'https://wordpress.org',
139
+ label: 'WP',
140
+ type: 'url',
141
+ } );
122
142
  }
123
143
 
124
144
  const PLACEHOLDER_ACTIONS_CLASS = 'wp-block-navigation-placeholder__actions';
125
145
  const PLACEHOLDER_ACTIONS_XPATH = `//*[contains(@class, '${ PLACEHOLDER_ACTIONS_CLASS }')]`;
126
146
  const START_EMPTY_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Start empty']`;
127
- const SELECT_MENU_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Select menu']`;
147
+ const SELECT_MENU_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Select Menu']`;
128
148
 
129
149
  /**
130
150
  * Delete all items for the given REST resources using the REST API.
@@ -194,6 +214,13 @@ async function getNavigationMenuRawContent() {
194
214
  return stripPageIds( response.content.raw );
195
215
  }
196
216
 
217
+ async function waitForBlock( blockName ) {
218
+ const blockSelector = `[aria-label="Editor content"][role="region"] [aria-label="Block: ${ blockName }"]`;
219
+
220
+ // Wait for a Submenu block before making assertion.
221
+ return page.waitForSelector( blockSelector );
222
+ }
223
+
197
224
  // Disable reason - these tests are to be re-written.
198
225
  // eslint-disable-next-line jest/no-disabled-tests
199
226
  describe( 'Navigation', () => {
@@ -237,49 +264,246 @@ describe( 'Navigation', () => {
237
264
  await deleteUser( contributorUsername );
238
265
  } );
239
266
 
240
- describe( 'placeholder', () => {
241
- it( 'allows a navigation block to be created from existing menus', async () => {
242
- await createClassicMenu( { name: 'Test Menu 1' } );
243
- await createClassicMenu(
244
- { name: 'Test Menu 2' },
245
- menuItemsFixture
267
+ describe( 'loading states', () => {
268
+ it( 'does not show a loading indicator if there is no ref to a Navigation post', async () => {
269
+ await createNewPost();
270
+ await clickOnMoreMenuItem( 'Code editor' );
271
+ const codeEditorInput = await page.waitForSelector(
272
+ '.editor-post-text-editor'
273
+ );
274
+
275
+ // Simulate block behaviour when loading a page containing an unconfigured Nav block
276
+ // that is not selected.
277
+ await codeEditorInput.click();
278
+ const markup = '<!-- wp:navigation /-->';
279
+ await page.keyboard.type( markup );
280
+ await clickButton( 'Exit code editor' );
281
+
282
+ // Wait for block to render...
283
+ const navBlock = await waitForBlock( 'Navigation' );
284
+
285
+ // Test specifically for the primary loading indicator because a spinner also exists
286
+ // in the hidden Placeholder component when it is loading.
287
+ const loadingSpinner = await navBlock.$(
288
+ '.wp-block-navigation__loading-indicator.components-spinner'
246
289
  );
247
290
 
291
+ // We should not see the loading state if the block has not been configured and is empty.
292
+ expect( loadingSpinner ).toBeNull();
293
+ } );
294
+
295
+ it( 'shows a loading indicator whilst ref resolves to Navigation post items', async () => {
296
+ const testNavId = 1;
297
+
298
+ let resolveNavigationRequest;
299
+
300
+ // Mock the request for the single Navigation post in order to fully
301
+ // control the resolution of the request. This will enable the ability
302
+ // to assert on how the UI responds during the API resolution without
303
+ // relying on variable factors such as network conditions.
304
+ await setUpResponseMocking( [
305
+ {
306
+ match: ( request ) =>
307
+ request.url().includes( `rest_route` ) &&
308
+ request.url().includes( `navigation` ) &&
309
+ request.url().includes( testNavId ),
310
+ onRequestMatch: () => {
311
+ // The Promise simulates a REST API request whose resolultion
312
+ // the test has full control over.
313
+ return new Promise( ( resolve ) => {
314
+ // Assign the resolution function to the var in the
315
+ // upper scope to afford control over resolution.
316
+ resolveNavigationRequest = resolve;
317
+ } );
318
+ },
319
+ },
320
+ ] );
321
+
248
322
  await createNewPost();
249
- await insertBlock( 'Navigation' );
250
- await selectClassicMenu( 'Test Menu 2' );
323
+ await clickOnMoreMenuItem( 'Code editor' );
324
+ const codeEditorInput = await page.waitForSelector(
325
+ '.editor-post-text-editor'
326
+ );
327
+ await codeEditorInput.click();
328
+
329
+ // The ID used in this `ref` is that which we mock in the request
330
+ // above to ensure we can control the resolution of the Navigation post.
331
+ const markup = `<!-- wp:navigation {"ref":${ testNavId }} /-->`;
332
+ await page.keyboard.type( markup );
333
+ await clickButton( 'Exit code editor' );
251
334
 
252
- // Wait for a navigation link block before making assertion.
253
- await page.waitForSelector( '*[aria-label="Block: Custom Link"]' );
254
- expect( await getNavigationMenuRawContent() ).toMatchSnapshot();
335
+ const navBlock = await waitForBlock( 'Navigation' );
336
+
337
+ // Check for the spinner to be present whilst loading.
338
+ await navBlock.waitForSelector( '.components-spinner' );
339
+
340
+ // Resolve the controlled mocked API request.
341
+ resolveNavigationRequest();
255
342
  } );
256
343
 
257
- it( 'creates an empty navigation block when the selected existing menu is also empty', async () => {
258
- await createClassicMenu( { name: 'Test Menu 1' } );
344
+ it( 'shows a loading indicator whilst empty Navigation menu is being created', async () => {
345
+ const testNavId = 1;
346
+
347
+ let resolveNavigationRequest;
348
+
349
+ // Mock the request for the single Navigation post in order to fully
350
+ // control the resolution of the request. This will enable the ability
351
+ // to assert on how the UI responds during the API resolution without
352
+ // relying on variable factors such as network conditions.
353
+ await setUpResponseMocking( [
354
+ {
355
+ match: ( request ) =>
356
+ request.url().includes( `rest_route` ) &&
357
+ request.url().includes( `navigation` ) &&
358
+ request.url().includes( testNavId ),
359
+ onRequestMatch: () => {
360
+ // The Promise simulates a REST API request whose resolultion
361
+ // the test has full control over.
362
+ return new Promise( ( resolve ) => {
363
+ // Assign the resolution function to the var in the
364
+ // upper scope to afford control over resolution.
365
+ resolveNavigationRequest = resolve;
366
+ } );
367
+ },
368
+ },
369
+ ] );
370
+
259
371
  await createNewPost();
260
372
  await insertBlock( 'Navigation' );
261
- await selectClassicMenu( 'Test Menu 1' );
262
373
 
263
- // Wait for the appender so that we know the navigation menu was created.
264
- await page.waitForSelector(
265
- 'nav[aria-label="Block: Navigation"] button[aria-label="Add block"]'
374
+ let navBlock = await waitForBlock( 'Navigation' );
375
+
376
+ // Create empty Navigation block with no items
377
+ const startEmptyButton = await page.waitForXPath(
378
+ START_EMPTY_XPATH
266
379
  );
267
- expect( await getNavigationMenuRawContent() ).toMatchSnapshot();
380
+ await startEmptyButton.click();
381
+
382
+ navBlock = await waitForBlock( 'Navigation' );
383
+
384
+ // Check for the spinner to be present whilst loading.
385
+ await navBlock.waitForSelector( '.components-spinner' );
386
+
387
+ // Resolve the controlled mocked API request.
388
+ resolveNavigationRequest();
268
389
  } );
390
+ } );
269
391
 
270
- it( 'does not display the options to create from pages or menus if there are none', async () => {
271
- await createNewPost();
392
+ describe( 'Placeholder', () => {
393
+ describe( 'placeholder states', () => {
394
+ it( 'shows placeholder on insertion of block', async () => {
395
+ await createNewPost();
396
+ await insertBlock( 'Navigation' );
397
+ await page.waitForXPath( START_EMPTY_XPATH );
398
+ } );
272
399
 
273
- await insertBlock( 'Navigation' );
274
- await page.waitForXPath( START_EMPTY_XPATH );
400
+ it( 'shows placeholder preview when unconfigured block is not selected', async () => {
401
+ await createNewPost();
402
+ await insertBlock( 'Navigation' );
275
403
 
276
- const placeholderActionsLength = await page.$$eval(
277
- `.${ PLACEHOLDER_ACTIONS_CLASS } button`,
278
- ( els ) => els.length
279
- );
404
+ // Check for unconfigured Placeholder state to display
405
+ await page.waitForXPath( START_EMPTY_XPATH );
406
+
407
+ // Deselect the Nav block by inserting a new block at the root level
408
+ // outside of the Nav block.
409
+ await insertBlock( 'Paragraph' );
410
+
411
+ const navBlock = await waitForBlock( 'Navigation' );
412
+
413
+ // Check Placeholder Preview is visible.
414
+ await navBlock.waitForSelector(
415
+ '.wp-block-navigation-placeholder__preview',
416
+ { visible: true }
417
+ );
418
+
419
+ // Check Placeholder Component itself is not visible.
420
+ await navBlock.waitForSelector(
421
+ '.wp-block-navigation-placeholder__controls',
422
+ { visible: false }
423
+ );
424
+ } );
425
+
426
+ it( 'shows placeholder preview when block with no menu items is not selected', async () => {
427
+ await createNewPost();
428
+ await insertBlock( 'Navigation' );
429
+
430
+ // Create empty Navigation block with no items
431
+ const startEmptyButton = await page.waitForXPath(
432
+ START_EMPTY_XPATH
433
+ );
434
+ await startEmptyButton.click();
435
+
436
+ // Wait for block to resolve
437
+ let navBlock = await waitForBlock( 'Navigation' );
438
+
439
+ // Deselect the Nav block by inserting a new block at the root level
440
+ // outside of the Nav block.
441
+ await insertBlock( 'Paragraph' );
442
+
443
+ // Aquire fresh reference to block
444
+ navBlock = await waitForBlock( 'Navigation' );
445
+
446
+ // Check Placeholder Preview is visible.
447
+ await navBlock.waitForSelector(
448
+ '.wp-block-navigation-placeholder__preview',
449
+ { visible: true }
450
+ );
451
+
452
+ // Check the block's appender is not visible.
453
+ const blockAppender = await navBlock.$(
454
+ '.block-list-appender'
455
+ );
456
+
457
+ expect( blockAppender ).toBeNull();
458
+ } );
459
+ } );
460
+
461
+ describe( 'placeholder actions', () => {
462
+ it( 'allows a navigation block to be created from existing menus', async () => {
463
+ await createClassicMenu( { name: 'Test Menu 1' } );
464
+ await createClassicMenu(
465
+ { name: 'Test Menu 2' },
466
+ menuItemsFixture
467
+ );
468
+
469
+ await createNewPost();
470
+ await insertBlock( 'Navigation' );
471
+ await selectClassicMenu( 'Test Menu 2' );
472
+
473
+ // Wait for a navigation link block before making assertion.
474
+ await page.waitForSelector(
475
+ '*[aria-label="Block: Custom Link"]'
476
+ );
477
+ expect( await getNavigationMenuRawContent() ).toMatchSnapshot();
478
+ } );
479
+
480
+ it( 'creates an empty navigation block when the selected existing menu is also empty', async () => {
481
+ await createClassicMenu( { name: 'Test Menu 1' } );
482
+ await createNewPost();
483
+ await insertBlock( 'Navigation' );
484
+ await selectClassicMenu( 'Test Menu 1' );
485
+
486
+ // Wait for the appender so that we know the navigation menu was created.
487
+ await page.waitForSelector(
488
+ 'nav[aria-label="Block: Navigation"] button[aria-label="Add block"]'
489
+ );
490
+ expect( await getNavigationMenuRawContent() ).toMatchSnapshot();
491
+ } );
492
+
493
+ it( 'does not display the options to create from existing menus if there are no existing menus', async () => {
494
+ await createNewPost();
495
+
496
+ await insertBlock( 'Navigation' );
497
+ await page.waitForXPath( START_EMPTY_XPATH );
498
+
499
+ const placeholderActionsLength = await page.$$eval(
500
+ `.${ PLACEHOLDER_ACTIONS_CLASS } button`,
501
+ ( els ) => els.length
502
+ );
280
503
 
281
- // Should only be showing "Start empty".
282
- expect( placeholderActionsLength ).toEqual( 1 );
504
+ // Should only be showing "Start empty".
505
+ expect( placeholderActionsLength ).toEqual( 1 );
506
+ } );
283
507
  } );
284
508
  } );
285
509
 
@@ -289,6 +513,11 @@ describe( 'Navigation', () => {
289
513
  const startEmptyButton = await page.waitForXPath( START_EMPTY_XPATH );
290
514
  await startEmptyButton.click();
291
515
 
516
+ // Await "success" notice.
517
+ await page.waitForXPath(
518
+ '//div[@class="components-snackbar__content"][contains(text(), "Navigation Menu successfully created.")]'
519
+ );
520
+
292
521
  const appender = await page.waitForSelector(
293
522
  '.wp-block-navigation .block-list-appender'
294
523
  );
@@ -313,7 +542,7 @@ describe( 'Navigation', () => {
313
542
  // After adding a new block, search input should be shown immediately.
314
543
  // Verify that Escape would close the popover.
315
544
  // Regression: https://github.com/WordPress/gutenberg/pull/19885
316
- // Wait for URL input to be focused
545
+ // Wait for URL input to be focused.
317
546
  await page.waitForSelector(
318
547
  'input.block-editor-url-input__input:focus'
319
548
  );
@@ -378,7 +607,7 @@ describe( 'Navigation', () => {
378
607
  );
379
608
  await appenderAgain.click();
380
609
 
381
- // Wait for URL input to be focused
610
+ // Wait for URL input to be focused.
382
611
  await page.waitForSelector(
383
612
  'input.block-editor-url-input__input:focus'
384
613
  );
@@ -393,7 +622,7 @@ describe( 'Navigation', () => {
393
622
  expect( isInURLInput ).toBe( true );
394
623
  await page.keyboard.press( 'Escape' );
395
624
 
396
- // Click the link placeholder
625
+ // Click the link placeholder.
397
626
  const placeholder = await page.waitForSelector(
398
627
  '.wp-block-navigation-link__placeholder'
399
628
  );
@@ -557,14 +786,17 @@ describe( 'Navigation', () => {
557
786
  const markup =
558
787
  '<!-- wp:navigation --><!-- wp:page-list /--><!-- /wp:navigation -->';
559
788
  await page.keyboard.type( markup );
789
+
560
790
  await clickButton( 'Exit code editor' );
561
- const navBlock = await page.waitForSelector(
562
- 'nav[aria-label="Block: Navigation"]'
563
- );
564
- // Select the block to convert to a wp_navigation and publish.
565
- // The select menu button shows up when saving is complete.
791
+
792
+ const navBlock = await waitForBlock( 'Navigation' );
793
+
794
+ // Select the block to convert to a wp_navigation.
566
795
  await navBlock.click();
567
- await page.waitForSelector( 'button[aria-label="Select Menu"]' );
796
+
797
+ // The Page List block is rendered within Navigation InnerBlocks when saving is complete.
798
+ await waitForBlock( 'Page List' );
799
+
568
800
  await publishPost();
569
801
 
570
802
  // Check that the wp_navigation post has the page list block.
@@ -575,20 +807,6 @@ describe( 'Navigation', () => {
575
807
  const NAV_ENTITY_SELECTOR =
576
808
  '//div[@class="entities-saved-states__panel"]//label//strong[contains(text(), "Navigation")]';
577
809
 
578
- async function populateNavWithOneItem() {
579
- // Add a Link block first.
580
- const appender = await page.waitForSelector(
581
- '.wp-block-navigation .block-list-appender'
582
- );
583
- await appender.click();
584
- // Add a link to the Link block.
585
- await updateActiveNavigationLink( {
586
- url: 'https://wordpress.org',
587
- label: 'WP',
588
- type: 'url',
589
- } );
590
- }
591
-
592
810
  async function resetNavBlockToInitialState() {
593
811
  const selectMenuDropdown = await page.waitForSelector(
594
812
  '[aria-label="Select Menu"]'
@@ -611,17 +829,18 @@ describe( 'Navigation', () => {
611
829
  '<!-- wp:navigation --><!-- wp:page-list /--><!-- /wp:navigation -->';
612
830
  await page.keyboard.type( markup );
613
831
  await clickButton( 'Exit code editor' );
614
- const navBlock = await page.waitForSelector(
615
- 'nav[aria-label="Block: Navigation"]'
616
- );
617
832
 
618
- // Select the block to convert to a wp_navigation and publish.
619
- // The select menu button shows up when saving is complete.
833
+ const navBlock = await waitForBlock( 'Navigation' );
834
+
835
+ // Select the block to convert to a wp_navigation.
620
836
  await navBlock.click();
621
- await page.waitForSelector( 'button[aria-label="Select Menu"]' );
837
+
838
+ // The Page List block is rendered within Navigation InnerBlocks when saving is complete.
839
+ await waitForBlock( 'Page List' );
622
840
 
623
841
  // Reset the nav block to create a new entity.
624
842
  await resetNavBlockToInitialState();
843
+
625
844
  const startEmptyButton = await page.waitForXPath(
626
845
  START_EMPTY_XPATH
627
846
  );
@@ -657,7 +876,7 @@ describe( 'Navigation', () => {
657
876
  await page.waitForXPath( NAV_ENTITY_SELECTOR );
658
877
  expect( await page.$x( NAV_ENTITY_SELECTOR ) ).toHaveLength( 1 );
659
878
 
660
- // Publish the post
879
+ // Publish the post.
661
880
  const entitySaveButton = await page.waitForSelector(
662
881
  '.editor-entities-saved-states__save-button'
663
882
  );
@@ -731,6 +950,113 @@ describe( 'Navigation', () => {
731
950
  expect( tagCount ).toBe( 1 );
732
951
  } );
733
952
 
953
+ describe( 'Submenus', () => {
954
+ it( 'shows button which converts submenu to link when submenu is not-populated (empty)', async () => {
955
+ const navSubmenuSelector = `[aria-label="Editor content"][role="region"] [aria-label="Block: Submenu"]`;
956
+
957
+ await createNewPost();
958
+ await insertBlock( 'Navigation' );
959
+
960
+ const startEmptyButton = await page.waitForXPath(
961
+ START_EMPTY_XPATH
962
+ );
963
+
964
+ await startEmptyButton.click();
965
+
966
+ await populateNavWithOneItem();
967
+
968
+ await clickBlockToolbarButton( 'Add submenu' );
969
+
970
+ await waitForBlock( 'Submenu' );
971
+
972
+ // Revert the Submenu back to a Navigation Link block.
973
+ await clickBlockToolbarButton( 'Convert to Link' );
974
+
975
+ // Check the Submenu block is no longer present.
976
+ const submenuBlock = await page.$( navSubmenuSelector );
977
+
978
+ expect( submenuBlock ).toBeFalsy();
979
+ } );
980
+
981
+ it( 'shows button to convert submenu to link in disabled state when submenu is populated', async () => {
982
+ await createNewPost();
983
+ await insertBlock( 'Navigation' );
984
+
985
+ const startEmptyButton = await page.waitForXPath(
986
+ START_EMPTY_XPATH
987
+ );
988
+
989
+ await startEmptyButton.click();
990
+
991
+ await populateNavWithOneItem();
992
+
993
+ await clickBlockToolbarButton( 'Add submenu' );
994
+
995
+ await waitForBlock( 'Submenu' );
996
+
997
+ // Add a Link block first.
998
+ const appender = await page.waitForSelector(
999
+ '[aria-label="Block: Submenu"] [aria-label="Add block"]'
1000
+ );
1001
+
1002
+ await appender.click();
1003
+
1004
+ await updateActiveNavigationLink( {
1005
+ url: 'https://make.wordpress.org/core/',
1006
+ label: 'Submenu item #1',
1007
+ type: 'url',
1008
+ } );
1009
+
1010
+ await clickBlockToolbarButton( 'Select Submenu' );
1011
+
1012
+ // Check button exists but is in disabled state.
1013
+ const disabledConvertToLinkButton = await page.$(
1014
+ '[aria-label="Block tools"] [aria-label="Convert to Link"][disabled]'
1015
+ );
1016
+
1017
+ expect( disabledConvertToLinkButton ).toBeTruthy();
1018
+ } );
1019
+
1020
+ it( 'shows button to convert submenu to link when submenu is populated with a single incomplete link item', async () => {
1021
+ // For context on why this test is required please see:
1022
+ // https://github.com/WordPress/gutenberg/pull/38203#issuecomment-1027672948.
1023
+
1024
+ await createNewPost();
1025
+ await insertBlock( 'Navigation' );
1026
+
1027
+ const startEmptyButton = await page.waitForXPath(
1028
+ START_EMPTY_XPATH
1029
+ );
1030
+
1031
+ await startEmptyButton.click();
1032
+
1033
+ await populateNavWithOneItem();
1034
+
1035
+ await clickBlockToolbarButton( 'Add submenu' );
1036
+
1037
+ await waitForBlock( 'Submenu' );
1038
+
1039
+ // Add a Link block first.
1040
+ const appender = await page.waitForSelector(
1041
+ '[aria-label="Block: Submenu"] [aria-label="Add block"]'
1042
+ );
1043
+
1044
+ await appender.click();
1045
+
1046
+ // Here we intentionally do not populate the inserted Navigation Link block.
1047
+ // Rather we immediaely click away leaving the link in a state where it has
1048
+ // no URL of label and can be considered unpopulated.
1049
+ await clickBlockToolbarButton( 'Select Submenu' );
1050
+
1051
+ // Check for non-disabled Convert to Link button.
1052
+ const convertToLinkButton = await page.$(
1053
+ '[aria-label="Block tools"] [aria-label="Convert to Link"]:not([disabled])'
1054
+ );
1055
+
1056
+ expect( convertToLinkButton ).toBeTruthy();
1057
+ } );
1058
+ } );
1059
+
734
1060
  describe( 'Permission based restrictions', () => {
735
1061
  afterEach( async () => {
736
1062
  await switchUserToAdmin();
@@ -759,9 +1085,10 @@ describe( 'Navigation', () => {
759
1085
 
760
1086
  await insertBlock( 'Navigation' );
761
1087
 
762
- // Select the Navigation post created by the Admin early
1088
+ // Select the Navigation post created by the Admin earlier
763
1089
  // in the test.
764
1090
  const navigationPostCreatedByAdminName = 'Navigation';
1091
+
765
1092
  const dropdown = await page.waitForXPath( SELECT_MENU_XPATH );
766
1093
  await dropdown.click();
767
1094
  const theOption = await page.waitForXPath(
@@ -769,7 +1096,7 @@ describe( 'Navigation', () => {
769
1096
  );
770
1097
  await theOption.click();
771
1098
 
772
- // Make sure the snackbar error shows up
1099
+ // Make sure the snackbar error shows up.
773
1100
  await page.waitForXPath(
774
1101
  `//*[contains(@class, 'components-snackbar__content')][ text()="You do not have permission to edit this Menu. Any changes made will not be saved." ]`
775
1102
  );
@@ -792,7 +1119,7 @@ describe( 'Navigation', () => {
792
1119
  await createNewPost();
793
1120
  await insertBlock( 'Navigation' );
794
1121
 
795
- // Make sure the snackbar error shows up
1122
+ // Make sure the snackbar error shows up.
796
1123
  await page.waitForXPath(
797
1124
  `//*[contains(@class, 'components-snackbar__content')][ text()="${ noticeText }" ]`
798
1125
  );
@@ -131,7 +131,7 @@ describe( 'Table', () => {
131
131
  // Expect the table to have a header, body and footer with written content.
132
132
  expect( await getEditedPostContent() ).toMatchSnapshot();
133
133
 
134
- // Toggle off the switches
134
+ // Toggle off the switches.
135
135
  await headerSwitch[ 0 ].click();
136
136
  await footerSwitch[ 0 ].click();
137
137
 
@@ -94,32 +94,32 @@ const createCorrectlyAppliesAndRemovesAlignmentTest = (
94
94
  it( 'Correctly applies the selected alignment and correctly removes the alignment', async () => {
95
95
  const BUTTON_XPATH = `//button[contains(@class,'components-dropdown-menu__menu-item')]//span[contains(text(), '${ alignLabels[ alignment ] }')]`;
96
96
 
97
- // set the specified alignment.
97
+ // Set the specified alignment.
98
98
  await insertBlock( blockName );
99
99
  await clickBlockToolbarButton( 'Align' );
100
100
  await ( await page.$x( BUTTON_XPATH ) )[ 0 ].click();
101
101
 
102
- // verify the button of the specified alignment is pressed.
102
+ // Verify the button of the specified alignment is pressed.
103
103
  await expectActiveButtonLabelToBe( alignLabels[ alignment ] );
104
104
 
105
105
  let htmlMarkup = await getEditedPostContent();
106
106
 
107
- // verify the markup of the selected alignment was generated.
107
+ // Verify the markup of the selected alignment was generated.
108
108
  expect( htmlMarkup ).toMatchSnapshot();
109
109
 
110
- // verify the markup can be correctly parsed
110
+ // Verify the markup can be correctly parsed.
111
111
  await verifyMarkupIsValid( htmlMarkup );
112
112
 
113
113
  await selectBlockByClientId( ( await getAllBlocks() )[ 0 ].clientId );
114
114
 
115
- // remove the alignment.
115
+ // Remove the alignment.
116
116
  await clickBlockToolbarButton( 'Align' );
117
117
  await ( await page.$x( BUTTON_XPATH ) )[ 0 ].click();
118
118
 
119
- // verify 'none' alignment button is in pressed state.
119
+ // Verify 'none' alignment button is in pressed state.
120
120
  await expectActiveButtonLabelToBe( alignLabels.none );
121
121
 
122
- // verify alignment markup was removed from the block.
122
+ // Verify alignment markup was removed from the block.
123
123
  htmlMarkup = await getEditedPostContent();
124
124
  expect( htmlMarkup ).toMatchSnapshot();
125
125
 
@@ -128,7 +128,7 @@ const createCorrectlyAppliesAndRemovesAlignmentTest = (
128
128
 
129
129
  await selectBlockByClientId( ( await getAllBlocks() )[ 0 ].clientId );
130
130
 
131
- // verify alignment `none` button is in pressed state after parsing the block.
131
+ // Verify alignment `none` button is in pressed state after parsing the block.
132
132
  await expectActiveButtonLabelToBe( alignLabels.none );
133
133
  } );
134
134
  };
@@ -202,7 +202,7 @@ describe( 'Align Hook Works As Expected', () => {
202
202
 
203
203
  it( 'Applies the selected alignment by default', async () => {
204
204
  await insertBlock( BLOCK_NAME );
205
- // verify the correct alignment button is pressed
205
+ // Verify the correct alignment button is pressed.
206
206
  await clickBlockToolbarButton( 'Align' );
207
207
  const selectedAlignmentControls = await page.$x(
208
208
  SELECTED_ALIGNMENT_CONTROL_SELECTOR
@@ -219,7 +219,7 @@ describe( 'Align Hook Works As Expected', () => {
219
219
 
220
220
  it( 'Can remove the default alignment and the align attribute equals none but alignnone class is not applied', async () => {
221
221
  await insertBlock( BLOCK_NAME );
222
- // remove the alignment.
222
+ // Remove the alignment.
223
223
  await clickBlockToolbarButton( 'Align' );
224
224
  const [ selectedAlignmentControl ] = await page.$x(
225
225
  SELECTED_ALIGNMENT_CONTROL_SELECTOR