@wordpress/e2e-tests 3.0.1-next.33ec3857e2.0 → 3.0.4

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 (59) hide show
  1. package/CHANGELOG.md +2 -1
  2. package/LICENSE.md +1 -1
  3. package/README.md +31 -19
  4. package/config/is-gutenberg-plugin.js +6 -0
  5. package/jest.config.js +1 -1
  6. package/jest.performance.config.js +1 -1
  7. package/mu-plugins/enable-templates-ui.php +24 -0
  8. package/package.json +9 -9
  9. package/plugins/plugins-api/error-boundary.js +11 -0
  10. package/plugins/plugins-error-boundary.php +27 -0
  11. package/plugins/query-block.php +2 -2
  12. package/specs/editor/blocks/__snapshots__/image.test.js.snap +6 -6
  13. package/specs/editor/blocks/__snapshots__/navigation.test.js.snap +4 -2
  14. package/specs/editor/blocks/__snapshots__/spacer.test.js.snap +1 -1
  15. package/specs/editor/blocks/classic.test.js +5 -2
  16. package/specs/editor/blocks/cover.test.js +7 -3
  17. package/specs/editor/blocks/gallery.test.js +6 -1
  18. package/specs/editor/blocks/heading.test.js +1 -11
  19. package/specs/editor/blocks/navigation.test.js +279 -240
  20. package/specs/editor/plugins/__snapshots__/plugins-api.test.js.snap +2 -2
  21. package/specs/editor/plugins/block-variations.test.js +3 -3
  22. package/specs/editor/plugins/iframed-inline-styles.test.js +0 -6
  23. package/specs/editor/plugins/iframed-multiple-block-stylesheets.test.js +0 -4
  24. package/specs/editor/plugins/plugins-api.test.js +30 -0
  25. package/specs/editor/plugins/templates.test.js +1 -7
  26. package/specs/editor/various/__snapshots__/block-editor-keyboard-shortcuts.test.js.snap +38 -24
  27. package/specs/editor/various/__snapshots__/inserting-blocks.test.js.snap +1 -1
  28. package/specs/editor/various/__snapshots__/keep-styles-on-block-transforms.test.js.snap +35 -0
  29. package/specs/editor/various/block-editor-keyboard-shortcuts.test.js +43 -3
  30. package/specs/editor/various/font-size-picker.test.js +57 -11
  31. package/specs/editor/various/fullscreen-mode.test.js +1 -1
  32. package/specs/editor/various/keep-styles-on-block-transforms.test.js +81 -0
  33. package/specs/editor/various/post-editor-template-mode.test.js +1 -1
  34. package/specs/editor/various/post-visibility.test.js +54 -0
  35. package/specs/editor/various/preview.test.js +66 -1
  36. package/specs/editor/various/reusable-blocks.test.js +52 -5
  37. package/specs/editor/various/style-variation.test.js +9 -5
  38. package/specs/editor/various/undo.test.js +21 -0
  39. package/specs/performance/site-editor.test.js +3 -4
  40. package/specs/site-editor/document-settings.test.js +12 -14
  41. package/specs/site-editor/multi-entity-editing.test.js +14 -16
  42. package/specs/site-editor/multi-entity-saving.test.js +18 -27
  43. package/specs/site-editor/settings-sidebar.test.js +7 -12
  44. package/specs/site-editor/site-editor-export.test.js +9 -10
  45. package/specs/site-editor/site-editor-inserter.test.js +7 -9
  46. package/specs/site-editor/style-variations.test.js +211 -0
  47. package/specs/site-editor/template-part.test.js +14 -22
  48. package/specs/site-editor/template-revert.test.js +31 -37
  49. package/specs/widgets/customizing-widgets.test.js +3 -23
  50. package/specs/widgets/editing-widgets.test.js +36 -12
  51. package/themes/style-variations/block-templates/index.html +11 -0
  52. package/themes/style-variations/index.php +0 -0
  53. package/themes/style-variations/style.css +15 -0
  54. package/themes/style-variations/styles/pink.json +33 -0
  55. package/themes/style-variations/styles/yellow.json +12 -0
  56. package/themes/style-variations/theme.json +8 -0
  57. package/config/gutenberg-phase.js +0 -9
  58. package/specs/editor/various/__snapshots__/style-variation.test.js.snap +0 -7
  59. package/specs/site-editor/utils.js +0 -153
@@ -1,7 +1,14 @@
1
+ /**
2
+ * External dependencies
3
+ */
4
+ import { uniqueId } from 'lodash';
5
+
1
6
  /**
2
7
  * WordPress dependencies
3
8
  */
4
9
  import {
10
+ clickButton,
11
+ clickOnMoreMenuItem,
5
12
  createJSONResponse,
6
13
  createNewPost,
7
14
  createMenu as createClassicMenu,
@@ -12,12 +19,15 @@ import {
12
19
  saveDraft,
13
20
  showBlockToolbar,
14
21
  openPreviewPage,
15
- selectBlockByClientId,
16
- getAllBlocks,
17
22
  ensureSidebarOpened,
18
23
  __experimentalRest as rest,
19
24
  publishPost,
25
+ createUser,
26
+ loginUser,
27
+ deleteUser,
28
+ switchUserToAdmin,
20
29
  } from '@wordpress/e2e-test-utils';
30
+ import { addQueryArgs } from '@wordpress/url';
21
31
 
22
32
  /**
23
33
  * Internal dependencies
@@ -26,6 +36,7 @@ import menuItemsFixture from '../fixtures/menu-items-request-fixture.json';
26
36
 
27
37
  const POSTS_ENDPOINT = '/wp/v2/posts';
28
38
  const PAGES_ENDPOINT = '/wp/v2/pages';
39
+ const DRAFT_PAGES_ENDPOINT = [ PAGES_ENDPOINT, { status: 'draft' } ];
29
40
  const NAVIGATION_MENUS_ENDPOINT = '/wp/v2/navigation';
30
41
 
31
42
  async function mockSearchResponse( items ) {
@@ -63,7 +74,10 @@ async function updateActiveNavigationLink( { url, label, type } ) {
63
74
  };
64
75
 
65
76
  if ( url ) {
66
- await page.type( 'input[placeholder="Search or type url"]', url );
77
+ const input = await page.waitForSelector(
78
+ 'input[placeholder="Search or type url"]'
79
+ );
80
+ await input.type( url );
67
81
 
68
82
  const suggestionPath = `//button[contains(@class, 'block-editor-link-control__search-item') and contains(@class, '${ typeClasses[ type ] }')]/span/span[@class='block-editor-link-control__search-item-title']/mark[text()="${ url }"]`;
69
83
 
@@ -110,22 +124,7 @@ async function selectClassicMenu( optionText ) {
110
124
  const PLACEHOLDER_ACTIONS_CLASS = 'wp-block-navigation-placeholder__actions';
111
125
  const PLACEHOLDER_ACTIONS_XPATH = `//*[contains(@class, '${ PLACEHOLDER_ACTIONS_CLASS }')]`;
112
126
  const START_EMPTY_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Start empty']`;
113
- const ADD_ALL_PAGES_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Add all pages']`;
114
-
115
- async function turnResponsivenessOn() {
116
- const blocks = await getAllBlocks();
117
-
118
- await selectBlockByClientId( blocks[ 0 ].clientId );
119
- await ensureSidebarOpened();
120
-
121
- const [ responsivenessToggleButton ] = await page.$x(
122
- '//label[text()[contains(.,"Enable responsive menu")]]'
123
- );
124
-
125
- await responsivenessToggleButton.click();
126
-
127
- await saveDraft();
128
- }
127
+ const SELECT_MENU_XPATH = `${ PLACEHOLDER_ACTIONS_XPATH }//button[text()='Select menu']`;
129
128
 
130
129
  /**
131
130
  * Delete all items for the given REST resources using the REST API.
@@ -133,8 +132,17 @@ async function turnResponsivenessOn() {
133
132
  * @param {*} endpoints The endpoints of the resources to delete.
134
133
  */
135
134
  async function deleteAll( endpoints ) {
136
- for ( const path of endpoints ) {
137
- const items = await rest( { path } );
135
+ for ( const endpoint of endpoints ) {
136
+ const defaultArgs = { per_page: -1 };
137
+ const isArrayEndpoint = Array.isArray( endpoint );
138
+ const path = isArrayEndpoint ? endpoint[ 0 ] : endpoint;
139
+ const args = isArrayEndpoint
140
+ ? { ...defaultArgs, ...endpoint[ 1 ] }
141
+ : defaultArgs;
142
+
143
+ const items = await rest( {
144
+ path: addQueryArgs( path, args ),
145
+ } );
138
146
 
139
147
  for ( const item of items ) {
140
148
  await rest( {
@@ -145,24 +153,6 @@ async function deleteAll( endpoints ) {
145
153
  }
146
154
  }
147
155
 
148
- /**
149
- * Create a set of pages using the REST API.
150
- *
151
- * @param {Array} pages An array of page objects.
152
- */
153
- async function createPages( pages ) {
154
- for ( const page of pages ) {
155
- await rest( {
156
- method: 'POST',
157
- path: PAGES_ENDPOINT,
158
- data: {
159
- status: 'publish',
160
- ...page,
161
- },
162
- } );
163
- }
164
- }
165
-
166
156
  /**
167
157
  * Replace unique ids in nav block content, since these won't be consistent
168
158
  * between test runs.
@@ -192,6 +182,10 @@ async function getNavigationMenuRawContent() {
192
182
  return navigationBlock.attributes.ref;
193
183
  } );
194
184
 
185
+ if ( ! menuRef ) {
186
+ throw 'getNavigationMenuRawContent was unable to find a ref attribute on the first navigation block';
187
+ }
188
+
195
189
  const response = await rest( {
196
190
  method: 'GET',
197
191
  path: `/wp/v2/navigation/${ menuRef }?context=edit`,
@@ -203,10 +197,23 @@ async function getNavigationMenuRawContent() {
203
197
  // Disable reason - these tests are to be re-written.
204
198
  // eslint-disable-next-line jest/no-disabled-tests
205
199
  describe( 'Navigation', () => {
200
+ const contributorUsername = uniqueId( 'contributoruser_' );
201
+ let contributorPassword;
202
+
203
+ beforeAll( async () => {
204
+ // Creation of the contributor user **MUST** be at the top level describe block
205
+ // otherwise this test will become unstable. This action only happens once
206
+ // so there is no huge performance hit.
207
+ contributorPassword = await createUser( contributorUsername, {
208
+ role: 'contributor',
209
+ } );
210
+ } );
211
+
206
212
  beforeEach( async () => {
207
213
  await deleteAll( [
208
214
  POSTS_ENDPOINT,
209
215
  PAGES_ENDPOINT,
216
+ DRAFT_PAGES_ENDPOINT,
210
217
  NAVIGATION_MENUS_ENDPOINT,
211
218
  ] );
212
219
  await deleteAllClassicMenus();
@@ -220,43 +227,17 @@ describe( 'Navigation', () => {
220
227
  await deleteAll( [
221
228
  POSTS_ENDPOINT,
222
229
  PAGES_ENDPOINT,
230
+ DRAFT_PAGES_ENDPOINT,
223
231
  NAVIGATION_MENUS_ENDPOINT,
224
232
  ] );
225
233
  await deleteAllClassicMenus();
234
+
235
+ // As per the creation in the beforeAll() above, this
236
+ // action must be done at the root level describe() block.
237
+ await deleteUser( contributorUsername );
226
238
  } );
227
239
 
228
240
  describe( 'placeholder', () => {
229
- it( 'allows a navigation block to be created using existing pages', async () => {
230
- await createPages( [
231
- {
232
- title: 'About',
233
- menu_order: 0,
234
- },
235
- {
236
- title: 'Contact Us',
237
- menu_order: 1,
238
- },
239
- {
240
- title: 'FAQ',
241
- menu_order: 2,
242
- },
243
- ] );
244
-
245
- await createNewPost();
246
-
247
- // Add the navigation block.
248
- await insertBlock( 'Navigation' );
249
- const allPagesButton = await page.waitForXPath(
250
- ADD_ALL_PAGES_XPATH
251
- );
252
- await allPagesButton.click();
253
-
254
- // Wait for the page list block to be present
255
- await page.waitForSelector( 'div[aria-label="Block: Page List"]' );
256
-
257
- expect( await getNavigationMenuRawContent() ).toMatchSnapshot();
258
- } );
259
-
260
241
  it( 'allows a navigation block to be created from existing menus', async () => {
261
242
  await createClassicMenu( { name: 'Test Menu 1' } );
262
243
  await createClassicMenu(
@@ -438,52 +419,53 @@ describe( 'Navigation', () => {
438
419
  expect( await getNavigationMenuRawContent() ).toMatchSnapshot();
439
420
  } );
440
421
 
441
- // URL details endpoint is throwing a 404, which causes this test to fail.
442
- it.skip( 'allows pages to be created from the navigation block and their links added to menu', async () => {
422
+ it( 'allows pages to be created from the navigation block and their links added to menu', async () => {
443
423
  await createNewPost();
444
424
  await insertBlock( 'Navigation' );
445
425
  const startEmptyButton = await page.waitForXPath( START_EMPTY_XPATH );
446
426
  await startEmptyButton.click();
447
-
448
427
  const appender = await page.waitForSelector(
449
428
  '.wp-block-navigation .block-list-appender'
450
429
  );
451
430
  await appender.click();
452
431
 
453
432
  // Wait for URL input to be focused
454
- await page.waitForSelector(
455
- 'input.block-editor-url-input__input:focus'
456
- );
457
-
458
433
  // Insert name for the new page.
459
- await page.type(
460
- 'input[placeholder="Search or type url"]',
461
- 'A really long page name that will not exist'
462
- );
463
-
464
- // Wait for URL input to be focused
465
- await page.waitForSelector(
434
+ const pageTitle = 'A really long page name that will not exist';
435
+ const input = await page.waitForSelector(
466
436
  'input.block-editor-url-input__input:focus'
467
437
  );
468
-
469
- // Wait for the create button to appear and click it.
470
- await page.waitForSelector(
438
+ await input.type( pageTitle );
439
+
440
+ // When creating a page, the URLControl makes a request to the
441
+ // url-details endpoint to fetch information about the page.
442
+ // Because the draft is inaccessible publicly, this request
443
+ // returns a 404 response. Wait for the response and expect
444
+ // the error to have occurred.
445
+ const createPageButton = await page.waitForSelector(
471
446
  '.block-editor-link-control__search-create'
472
447
  );
473
-
474
- const createPageButton = await page.$(
475
- '.block-editor-link-control__search-create'
448
+ const responsePromise = page.waitForResponse(
449
+ ( response ) =>
450
+ response.url().includes( 'url-details' ) &&
451
+ response.status() === 404
476
452
  );
453
+ const createPagePromise = createPageButton.click();
454
+ await Promise.all( [ responsePromise, createPagePromise ] );
477
455
 
478
- await createPageButton.click();
479
-
480
- const draftLink = await page.waitForSelector(
481
- '.wp-block-navigation-item__content'
456
+ // Creating a draft is async, so wait for a sign of completion. In this
457
+ // case the link that shows in the URL popover once a link is added.
458
+ await page.waitForXPath(
459
+ `//a[contains(@class, "block-editor-link-control__search-item-title") and contains(., "${ pageTitle }")]`
482
460
  );
483
- await draftLink.click();
461
+
462
+ await publishPost();
484
463
 
485
464
  // Expect a Navigation Block with a link for "A really long page name that will not exist".
486
465
  expect( await getNavigationMenuRawContent() ).toMatchSnapshot();
466
+ expect( console ).toHaveErroredWith(
467
+ 'Failed to load resource: the server responded with a status of 404 (Not Found)'
468
+ );
487
469
  } );
488
470
 
489
471
  it( 'renders buttons for the submenu opener elements when the block is set to open on click instead of hover', async () => {
@@ -564,125 +546,41 @@ describe( 'Navigation', () => {
564
546
  expect( quickInserter ).toBeTruthy();
565
547
  } );
566
548
 
567
- // The following tests are unstable, roughly around when https://github.com/WordPress/wordpress-develop/pull/1412
568
- // landed. The block manually tests well, so let's skip to unblock other PRs and immediately follow up. cc @vcanales
569
- it.skip( 'loads frontend code only if the block is present', async () => {
570
- // Mock the response from the Pages endpoint. This is done so that the pages returned are always
571
- // consistent and to test the feature more rigorously than the single default sample page.
572
- // await mockPagesResponse( [
573
- // {
574
- // title: 'Home',
575
- // slug: 'home',
576
- // },
577
- // {
578
- // title: 'About',
579
- // slug: 'about',
580
- // },
581
- // {
582
- // title: 'Contact Us',
583
- // slug: 'contact',
584
- // },
585
- // ] );
586
-
587
- // Create first block at the start in order to enable preview.
588
- await insertBlock( 'Navigation' );
589
- await saveDraft();
590
-
591
- const previewPage = await openPreviewPage();
592
- const isScriptLoaded = await previewPage.evaluate(
593
- () =>
594
- null !==
595
- document.querySelector(
596
- 'script[src*="navigation/view.min.js"]'
597
- )
549
+ it( 'supports navigation blocks that have inner blocks within their markup and converts them to wp_navigation posts', async () => {
550
+ // Insert 'old-school' inner blocks via the code editor.
551
+ await createNewPost();
552
+ await clickOnMoreMenuItem( 'Code editor' );
553
+ const codeEditorInput = await page.waitForSelector(
554
+ '.editor-post-text-editor'
598
555
  );
599
-
600
- expect( isScriptLoaded ).toBe( false );
601
-
602
- const allPagesButton = await page.waitForXPath( ADD_ALL_PAGES_XPATH );
603
- await allPagesButton.click();
604
- await insertBlock( 'Navigation' );
605
- const allPagesButton2 = await page.waitForXPath( ADD_ALL_PAGES_XPATH );
606
- await allPagesButton2.click();
607
- await turnResponsivenessOn();
608
-
609
- await previewPage.reload( {
610
- waitFor: [ 'networkidle0', 'domcontentloaded' ],
611
- } );
612
-
613
- /*
614
- Count instances of the tag to make sure that it's been loaded only once,
615
- regardless of the number of navigation blocks present.
616
- */
617
- const tagCount = await previewPage.evaluate(
618
- () =>
619
- Array.from(
620
- document.querySelectorAll(
621
- 'script[src*="navigation/view.min.js"]'
622
- )
623
- ).length
556
+ await codeEditorInput.click();
557
+ const markup =
558
+ '<!-- wp:navigation --><!-- wp:page-list /--><!-- /wp:navigation -->';
559
+ await page.keyboard.type( markup );
560
+ await clickButton( 'Exit code editor' );
561
+ const navBlock = await page.waitForSelector(
562
+ 'nav[aria-label="Block: Navigation"]'
624
563
  );
564
+ // Select the block to convert to a wp_navigation and publish.
565
+ // The select menu button shows up when saving is complete.
566
+ await navBlock.click();
567
+ await page.waitForSelector( 'button[aria-label="Select Menu"]' );
568
+ await publishPost();
625
569
 
626
- expect( tagCount ).toBe( 1 );
570
+ // Check that the wp_navigation post has the page list block.
571
+ expect( await getNavigationMenuRawContent() ).toMatchSnapshot();
627
572
  } );
628
573
 
629
- it.skip( 'loads frontend code only if responsiveness is turned on', async () => {
630
- // await mockPagesResponse( [
631
- // {
632
- // title: 'Home',
633
- // slug: 'home',
634
- // },
635
- // {
636
- // title: 'About',
637
- // slug: 'about',
638
- // },
639
- // {
640
- // title: 'Contact Us',
641
- // slug: 'contact',
642
- // },
643
- // ] );
644
-
645
- await insertBlock( 'Navigation' );
646
- await saveDraft();
647
-
648
- const previewPage = await openPreviewPage();
649
- let isScriptLoaded = await previewPage.evaluate(
650
- () =>
651
- null !==
652
- document.querySelector(
653
- 'script[src*="navigation/view.min.js"]'
654
- )
655
- );
656
-
657
- expect( isScriptLoaded ).toBe( false );
658
-
659
- const allPagesButton = await page.waitForXPath( ADD_ALL_PAGES_XPATH );
660
- await allPagesButton.click();
574
+ describe( 'Creating and restarting', () => {
575
+ const NAV_ENTITY_SELECTOR =
576
+ '//div[@class="entities-saved-states__panel"]//label//strong[contains(text(), "Navigation")]';
661
577
 
662
- await turnResponsivenessOn();
663
-
664
- await previewPage.reload( {
665
- waitFor: [ 'networkidle0', 'domcontentloaded' ],
666
- } );
667
-
668
- isScriptLoaded = await previewPage.evaluate(
669
- () =>
670
- null !==
671
- document.querySelector(
672
- 'script[src*="navigation/view.min.js"]'
673
- )
674
- );
675
-
676
- expect( isScriptLoaded ).toBe( true );
677
- } );
678
-
679
- describe.skip( 'Creating and restarting', () => {
680
578
  async function populateNavWithOneItem() {
681
579
  // Add a Link block first.
682
- await page.waitForSelector(
580
+ const appender = await page.waitForSelector(
683
581
  '.wp-block-navigation .block-list-appender'
684
582
  );
685
- await page.click( '.wp-block-navigation .block-list-appender' );
583
+ await appender.click();
686
584
  // Add a link to the Link block.
687
585
  await updateActiveNavigationLink( {
688
586
  url: 'https://wordpress.org',
@@ -692,62 +590,88 @@ describe( 'Navigation', () => {
692
590
  }
693
591
 
694
592
  async function resetNavBlockToInitialState() {
695
- await page.waitForSelector( '[aria-label="Select Menu"]' );
696
- await page.click( '[aria-label="Select Menu"]' );
697
-
698
- await page.waitForXPath( '//span[text()="Create new menu"]' );
699
- const newMenuButton = await page.$x(
593
+ const selectMenuDropdown = await page.waitForSelector(
594
+ '[aria-label="Select Menu"]'
595
+ );
596
+ await selectMenuDropdown.click();
597
+ const newMenuButton = await page.waitForXPath(
700
598
  '//span[text()="Create new menu"]'
701
599
  );
702
- newMenuButton[ 0 ].click();
600
+ newMenuButton.click();
703
601
  }
704
602
 
705
- it( 'only update a single entity currently linked with the block', async () => {
706
- // Mock the response from the Pages endpoint. This is done so that the pages returned are always
707
- // consistent and to test the feature more rigorously than the single default sample page.
708
- // await mockPagesResponse( [
709
- // {
710
- // title: 'Home',
711
- // slug: 'home',
712
- // },
713
- // {
714
- // title: 'About',
715
- // slug: 'about',
716
- // },
717
- // {
718
- // title: 'Contact Us',
719
- // slug: 'contact',
720
- // },
721
- // ] );
603
+ it( 'does not retain uncontrolled inner blocks when creating a new entity', async () => {
604
+ await createNewPost();
605
+ await clickOnMoreMenuItem( 'Code editor' );
606
+ const codeEditorInput = await page.waitForSelector(
607
+ '.editor-post-text-editor'
608
+ );
609
+ await codeEditorInput.click();
610
+ const markup =
611
+ '<!-- wp:navigation --><!-- wp:page-list /--><!-- /wp:navigation -->';
612
+ await page.keyboard.type( markup );
613
+ await clickButton( 'Exit code editor' );
614
+ const navBlock = await page.waitForSelector(
615
+ 'nav[aria-label="Block: Navigation"]'
616
+ );
617
+
618
+ // Select the block to convert to a wp_navigation and publish.
619
+ // The select menu button shows up when saving is complete.
620
+ await navBlock.click();
621
+ await page.waitForSelector( 'button[aria-label="Select Menu"]' );
622
+
623
+ // Reset the nav block to create a new entity.
624
+ await resetNavBlockToInitialState();
625
+ const startEmptyButton = await page.waitForXPath(
626
+ START_EMPTY_XPATH
627
+ );
628
+ await startEmptyButton.click();
629
+ await populateNavWithOneItem();
630
+
631
+ // Confirm that only the last menu entity was updated.
632
+ const publishPanelButton2 = await page.waitForSelector(
633
+ '.editor-post-publish-button__button:not([aria-disabled="true"])'
634
+ );
635
+ await publishPanelButton2.click();
636
+
637
+ await page.waitForXPath( NAV_ENTITY_SELECTOR );
638
+ expect( await page.$x( NAV_ENTITY_SELECTOR ) ).toHaveLength( 1 );
639
+ } );
722
640
 
641
+ it( 'only updates a single entity currently linked with the block', async () => {
642
+ await createNewPost();
723
643
  await insertBlock( 'Navigation' );
644
+
724
645
  const startEmptyButton = await page.waitForXPath(
725
646
  START_EMPTY_XPATH
726
647
  );
727
648
  await startEmptyButton.click();
728
649
  await populateNavWithOneItem();
729
650
 
730
- // Let's confirm that the menu entity was updated.
731
- await page.waitForSelector(
651
+ // Confirm that the menu entity was updated.
652
+ const publishPanelButton = await page.waitForSelector(
732
653
  '.editor-post-publish-panel__toggle:not([aria-disabled="true"])'
733
654
  );
734
- await page.click( '.editor-post-publish-panel__toggle' );
655
+ await publishPanelButton.click();
735
656
 
736
- const NAV_ENTITY_SELECTOR =
737
- '//div[@class="entities-saved-states__panel"]//label//strong[contains(text(), "Navigation")]';
738
657
  await page.waitForXPath( NAV_ENTITY_SELECTOR );
739
658
  expect( await page.$x( NAV_ENTITY_SELECTOR ) ).toHaveLength( 1 );
740
659
 
741
660
  // Publish the post
742
- await page.click( '.editor-entities-saved-states__save-button' );
743
- await page.waitForSelector( '.editor-post-publish-button' );
744
- await page.click( '.editor-post-publish-button' );
661
+ const entitySaveButton = await page.waitForSelector(
662
+ '.editor-entities-saved-states__save-button'
663
+ );
664
+ await entitySaveButton.click();
665
+ const publishButton = await page.waitForSelector(
666
+ '.editor-post-publish-button:not([aria-disabled="true"])'
667
+ );
668
+ await publishButton.click();
745
669
 
746
- // A success notice should show up
670
+ // A success notice should show up.
747
671
  await page.waitForSelector( '.components-snackbar' );
748
672
 
749
673
  // Now try inserting another Link block via the quick inserter.
750
- await page.focus( '.wp-block-navigation' );
674
+ await page.click( 'nav[aria-label="Block: Navigation"]' );
751
675
 
752
676
  await resetNavBlockToInitialState();
753
677
  const startEmptyButton2 = await page.waitForXPath(
@@ -756,14 +680,129 @@ describe( 'Navigation', () => {
756
680
  await startEmptyButton2.click();
757
681
  await populateNavWithOneItem();
758
682
 
759
- // Let's confirm that only the last menu entity was updated.
760
- await page.waitForSelector(
683
+ // Confirm that only the last menu entity was updated.
684
+ const publishPanelButton2 = await page.waitForSelector(
761
685
  '.editor-post-publish-button__button:not([aria-disabled="true"])'
762
686
  );
763
- await page.click( '.editor-post-publish-button__button' );
687
+ await publishPanelButton2.click();
764
688
 
765
689
  await page.waitForXPath( NAV_ENTITY_SELECTOR );
766
690
  expect( await page.$x( NAV_ENTITY_SELECTOR ) ).toHaveLength( 1 );
767
691
  } );
768
692
  } );
693
+
694
+ it( 'does not load the frontend script if no navigation blocks are present', async () => {
695
+ await createNewPost();
696
+ await insertBlock( 'Paragraph' );
697
+ await page.waitForSelector( 'p[data-title="Paragraph"]:focus' );
698
+ await page.keyboard.type( 'Hello' );
699
+
700
+ const previewPage = await openPreviewPage();
701
+ await previewPage.bringToFront();
702
+ await previewPage.waitForNetworkIdle();
703
+
704
+ const isScriptLoaded = await previewPage.evaluate(
705
+ () =>
706
+ null !==
707
+ document.querySelector(
708
+ 'script[src*="navigation/view.min.js"]'
709
+ )
710
+ );
711
+
712
+ expect( isScriptLoaded ).toBe( false );
713
+ } );
714
+
715
+ it( 'loads the frontend script only once even when multiple navigation blocks are present', async () => {
716
+ await createNewPost();
717
+ await insertBlock( 'Navigation' );
718
+ await insertBlock( 'Navigation' );
719
+
720
+ const previewPage = await openPreviewPage();
721
+ await previewPage.bringToFront();
722
+ await previewPage.waitForNetworkIdle();
723
+
724
+ const tagCount = await previewPage.evaluate(
725
+ () =>
726
+ document.querySelectorAll(
727
+ 'script[src*="navigation/view.min.js"]'
728
+ ).length
729
+ );
730
+
731
+ expect( tagCount ).toBe( 1 );
732
+ } );
733
+
734
+ describe( 'Permission based restrictions', () => {
735
+ afterEach( async () => {
736
+ await switchUserToAdmin();
737
+ } );
738
+
739
+ it( 'shows a warning if user does not have permission to edit or update navigation menus', async () => {
740
+ await createNewPost();
741
+ await insertBlock( 'Navigation' );
742
+
743
+ const startEmptyButton = await page.waitForXPath(
744
+ START_EMPTY_XPATH
745
+ );
746
+
747
+ // This creates an empty Navigation post type entity.
748
+ await startEmptyButton.click();
749
+
750
+ // Publishing the Post ensures the Navigation entity is saved.
751
+ // The Post itself is irrelevant.
752
+ await publishPost();
753
+
754
+ // Switch to a Contributor role user - they should not have
755
+ // permission to update Navigation menus.
756
+ await loginUser( contributorUsername, contributorPassword );
757
+
758
+ await createNewPost();
759
+
760
+ await insertBlock( 'Navigation' );
761
+
762
+ // Select the Navigation post created by the Admin early
763
+ // in the test.
764
+ const navigationPostCreatedByAdminName = 'Navigation';
765
+ const dropdown = await page.waitForXPath( SELECT_MENU_XPATH );
766
+ await dropdown.click();
767
+ const theOption = await page.waitForXPath(
768
+ `//*[contains(@class, 'components-menu-item__item')][ text()="${ navigationPostCreatedByAdminName }" ]`
769
+ );
770
+ await theOption.click();
771
+
772
+ // Make sure the snackbar error shows up
773
+ await page.waitForXPath(
774
+ `//*[contains(@class, 'components-snackbar__content')][ text()="You do not have permission to edit this Menu. Any changes made will not be saved." ]`
775
+ );
776
+
777
+ // Expect a console 403 for request to Navigation Areas for lower permission users.
778
+ // This is because reading requires the `edit_theme_options` capability
779
+ // which the Contributor level user does not have.
780
+ // See: https://github.com/WordPress/gutenberg/blob/4cedaf0c4abb0aeac4bfd4289d63e9889efe9733/lib/class-wp-rest-block-navigation-areas-controller.php#L81-L91.
781
+ // Todo: removed once Nav Areas are removed from the Gutenberg Plugin.
782
+ expect( console ).toHaveErrored();
783
+ } );
784
+
785
+ it( 'shows a warning if user does not have permission to create navigation menus', async () => {
786
+ const noticeText =
787
+ 'You do not have permission to create Navigation Menus.';
788
+ // Switch to a Contributor role user - they should not have
789
+ // permission to update Navigations.
790
+ await loginUser( contributorUsername, contributorPassword );
791
+
792
+ await createNewPost();
793
+ await insertBlock( 'Navigation' );
794
+
795
+ // Make sure the snackbar error shows up
796
+ await page.waitForXPath(
797
+ `//*[contains(@class, 'components-snackbar__content')][ text()="${ noticeText }" ]`
798
+ );
799
+
800
+ // Expect a console 403 for request to Navigation Areas for lower permission users.
801
+ // This is because reading requires the `edit_theme_options` capability
802
+ // which the Contributor level user does not have.
803
+ // See: https://github.com/WordPress/gutenberg/blob/4cedaf0c4abb0aeac4bfd4289d63e9889efe9733/lib/class-wp-rest-block-navigation-areas-controller.php#L81-L91.
804
+ // Todo: removed once Nav Areas are removed from the Gutenberg Plugin.
805
+ expect( console ).toHaveErrored();
806
+ } );
807
+ } );
769
808
  } );