Security update for Core, with self-updated composer
[yaffs-website] / web / core / modules / settings_tray / tests / src / FunctionalJavascript / SettingsTrayBlockFormTest.php
1 <?php
2
3 namespace Drupal\Tests\settings_tray\FunctionalJavascript;
4
5 use Drupal\block\Entity\Block;
6 use Drupal\block_content\Entity\BlockContent;
7 use Drupal\block_content\Entity\BlockContentType;
8 use Drupal\menu_link_content\Entity\MenuLinkContent;
9 use Drupal\settings_tray_test\Plugin\Block\SettingsTrayFormAnnotationIsClassBlock;
10 use Drupal\settings_tray_test\Plugin\Block\SettingsTrayFormAnnotationNoneBlock;
11 use Drupal\system\Entity\Menu;
12 use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait;
13 use Drupal\user\Entity\Role;
14 use Drupal\user\RoleInterface;
15
16 /**
17  * Testing opening and saving block forms in the off-canvas dialog.
18  *
19  * @group settings_tray
20  */
21 class SettingsTrayBlockFormTest extends SettingsTrayJavascriptTestBase {
22
23   use ContextualLinkClickTrait;
24
25   const TOOLBAR_EDIT_LINK_SELECTOR = '#toolbar-bar div.contextual-toolbar-tab button';
26
27   const LABEL_INPUT_SELECTOR = 'input[data-drupal-selector="edit-settings-label"]';
28
29   /**
30    * {@inheritdoc}
31    */
32   public static $modules = [
33     'node',
34     'block',
35     'system',
36     'breakpoint',
37     'toolbar',
38     'contextual',
39     'settings_tray',
40     'search',
41     'block_content',
42     'settings_tray_test',
43     // Add test module to override CSS pointer-events properties because they
44     // cause test failures.
45     'settings_tray_test_css',
46     'settings_tray_test',
47     'menu_link_content',
48     'menu_ui',
49   ];
50
51   /**
52    * {@inheritdoc}
53    */
54   protected function setUp() {
55     parent::setUp();
56
57     $this->createBlockContentType('basic', TRUE);
58     $block_content = $this->createBlockContent('Custom Block', 'basic', TRUE);
59     $user = $this->createUser([
60       'administer blocks',
61       'access contextual links',
62       'access toolbar',
63       'administer nodes',
64       'search content',
65     ]);
66     $this->drupalLogin($user);
67     $this->placeBlock('block_content:' . $block_content->uuid(), ['id' => 'custom']);
68   }
69
70   /**
71    * Tests opening off-canvas dialog by click blocks and elements in the blocks.
72    *
73    * @dataProvider providerTestBlocks
74    */
75   public function testBlocks($theme, $block_plugin, $new_page_text, $element_selector, $label_selector, $button_text, $toolbar_item, $permissions) {
76     if ($permissions) {
77       $this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), $permissions);
78     }
79
80     $web_assert = $this->assertSession();
81     $page = $this->getSession()->getPage();
82     $this->enableTheme($theme);
83     $block = $this->placeBlock($block_plugin);
84     $block_selector = str_replace('_', '-', $this->getBlockSelector($block));
85     $block_id = $block->id();
86     $this->drupalGet('user');
87
88     $link = $page->find('css', "$block_selector .contextual-links li a");
89     $this->assertEquals('Quick edit', $link->getText(), "'Quick edit' is the first contextual link for the block.");
90     $this->assertContains("/admin/structure/block/manage/$block_id/off-canvas?destination=user/2", $link->getAttribute('href'));
91
92     if (isset($toolbar_item)) {
93       // Check that you can open a toolbar tray and it will be closed after
94       // entering edit mode.
95       if ($element = $page->find('css', "#toolbar-administration a.is-active")) {
96         // If a tray was open from page load close it.
97         $element->click();
98         $this->waitForNoElement("#toolbar-administration a.is-active");
99       }
100       $page->find('css', $toolbar_item)->click();
101       $this->assertElementVisibleAfterWait('css', "{$toolbar_item}.is-active");
102     }
103     $this->enableEditMode();
104     if (isset($toolbar_item)) {
105       $this->waitForNoElement("{$toolbar_item}.is-active");
106     }
107     $this->openBlockForm($block_selector);
108     switch ($block_plugin) {
109       case 'system_powered_by_block':
110         // Confirm "Display Title" is not checked.
111         $web_assert->checkboxNotChecked('settings[label_display]');
112         // Confirm Title is not visible.
113         $this->assertEquals($this->isLabelInputVisible(), FALSE, 'Label is not visible');
114         $page->checkField('settings[label_display]');
115         $this->assertEquals($this->isLabelInputVisible(), TRUE, 'Label is visible');
116         // Fill out form, save the form.
117         $page->fillField('settings[label]', $new_page_text);
118
119         break;
120
121       case 'system_branding_block':
122         // Fill out form, save the form.
123         $page->fillField('settings[site_information][site_name]', $new_page_text);
124         break;
125
126       case 'settings_tray_test_class':
127         $web_assert->elementExists('css', '[data-drupal-selector="edit-settings-some-setting"]');
128         break;
129     }
130
131     if (isset($new_page_text)) {
132       $page->pressButton($button_text);
133       // Make sure the changes are present.
134       $new_page_text_locator = "$block_selector $label_selector:contains($new_page_text)";
135       $this->assertElementVisibleAfterWait('css', $new_page_text_locator);
136       // The page is loaded with the new change but make sure page is
137       // completely loaded.
138       $this->assertPageLoadComplete();
139     }
140
141     $this->openBlockForm($block_selector);
142
143     $this->disableEditMode();
144     // Canvas should close when editing module is closed.
145     $this->waitForOffCanvasToClose();
146
147     $this->enableEditMode();
148
149     // Open block form by clicking a element inside the block.
150     // This confirms that default action for links and form elements is
151     // suppressed.
152     $this->openBlockForm("$block_selector {$element_selector}", $block_selector);
153     $web_assert->elementTextContains('css', '.contextual-toolbar-tab button', 'Editing');
154     $web_assert->elementAttributeContains('css', '.dialog-off-canvas__main-canvas', 'class', 'js-settings-tray-edit-mode');
155     // Simulate press the Escape key.
156     $this->getSession()->executeScript('jQuery("body").trigger(jQuery.Event("keyup", { keyCode: 27 }));');
157     $this->waitForOffCanvasToClose();
158     $this->getSession()->wait(100);
159     $this->assertEditModeDisabled();
160     $web_assert->elementTextContains('css', '#drupal-live-announce', 'Exited edit mode.');
161     $web_assert->elementTextNotContains('css', '.contextual-toolbar-tab button', 'Editing');
162     $web_assert->elementAttributeNotContains('css', '.dialog-off-canvas__main-canvas', 'class', 'js-settings-tray-edit-mode');
163   }
164
165   /**
166    * Dataprovider for testBlocks().
167    */
168   public function providerTestBlocks() {
169     $blocks = [];
170     foreach ($this->getTestThemes() as $theme) {
171       $blocks += [
172         "$theme: block-powered" => [
173           'theme' => $theme,
174           'block_plugin' => 'system_powered_by_block',
175           'new_page_text' => 'Can you imagine anyone showing the label on this block',
176           'element_selector' => 'span a',
177           'label_selector' => 'h2',
178           'button_text' => 'Save Powered by Drupal',
179           'toolbar_item' => '#toolbar-item-user',
180           NULL,
181         ],
182         "$theme: block-branding" => [
183           'theme' => $theme,
184           'block_plugin' => 'system_branding_block',
185           'new_page_text' => 'The site that will live a very short life',
186           'element_selector' => "a[rel='home']:last-child",
187           'label_selector' => "a[rel='home']:last-child",
188           'button_text' => 'Save Site branding',
189           'toolbar_item' => '#toolbar-item-administration',
190           ['administer site configuration'],
191         ],
192         "$theme: block-search" => [
193           'theme' => $theme,
194           'block_plugin' => 'search_form_block',
195           'new_page_text' => NULL,
196           'element_selector' => '#edit-submit',
197           'label_selector' => 'h2',
198           'button_text' => 'Save Search form',
199           'toolbar_item' => NULL,
200           NULL,
201         ],
202         // This is the functional JS test coverage accompanying
203         // \Drupal\Tests\settings_tray\Functional\SettingsTrayTest::testPossibleAnnotations().
204         "$theme: " . SettingsTrayFormAnnotationIsClassBlock::class => [
205           'theme' => $theme,
206           'block_plugin' => 'settings_tray_test_class',
207           'new_page_text' => NULL,
208           'element_selector' => 'span',
209           'label_selector' => NULL,
210           'button_text' => NULL,
211           'toolbar_item' => NULL,
212           NULL,
213         ],
214         // This is the functional JS test coverage accompanying
215         // \Drupal\Tests\settings_tray\Functional\SettingsTrayTest::testPossibleAnnotations().
216         "$theme: " . SettingsTrayFormAnnotationNoneBlock::class => [
217           'theme' => $theme,
218           'block_plugin' => 'settings_tray_test_none',
219           'new_page_text' => NULL,
220           'element_selector' => 'span',
221           'label_selector' => NULL,
222           'button_text' => NULL,
223           'toolbar_item' => NULL,
224           NULL,
225         ],
226       ];
227     }
228
229     return $blocks;
230   }
231
232   /**
233    * Enables edit mode by pressing edit button in the toolbar.
234    */
235   protected function enableEditMode() {
236     $this->pressToolbarEditButton();
237     $this->assertEditModeEnabled();
238   }
239
240   /**
241    * Disables edit mode by pressing edit button in the toolbar.
242    */
243   protected function disableEditMode() {
244     $this->assertSession()->assertWaitOnAjaxRequest();
245     $this->pressToolbarEditButton();
246     $this->assertEditModeDisabled();
247   }
248
249   /**
250    * Asserts that Off-Canvas block form is valid.
251    */
252   protected function assertOffCanvasBlockFormIsValid() {
253     $web_assert = $this->assertSession();
254     // Confirm that Block title display label has been changed.
255     $web_assert->elementTextContains('css', '.form-item-settings-label-display label', 'Display block title');
256     // Confirm Block title label is shown if checkbox is checked.
257     if ($this->getSession()->getPage()->find('css', 'input[name="settings[label_display]"]')->isChecked()) {
258       $this->assertEquals($this->isLabelInputVisible(), TRUE, 'Label is visible');
259       $web_assert->elementTextContains('css', '.form-item-settings-label label', 'Block title');
260     }
261     else {
262       $this->assertEquals($this->isLabelInputVisible(), FALSE, 'Label is not visible');
263     }
264
265     // Check that common block form elements exist.
266     $web_assert->elementExists('css', static::LABEL_INPUT_SELECTOR);
267     $web_assert->elementExists('css', 'input[data-drupal-selector="edit-settings-label-display"]');
268     // Check that advanced block form elements do not exist.
269     $web_assert->elementNotExists('css', 'input[data-drupal-selector="edit-visibility-request-path-pages"]');
270     $web_assert->elementNotExists('css', 'select[data-drupal-selector="edit-region"]');
271   }
272
273   /**
274    * Open block form by clicking the element found with a css selector.
275    *
276    * @param string $block_selector
277    *   A css selector selects the block or an element within it.
278    * @param string $contextual_link_container
279    *   The element that contains the contextual links. If none provide the
280    *   $block_selector will be used.
281    */
282   protected function openBlockForm($block_selector, $contextual_link_container = '') {
283     if (!$contextual_link_container) {
284       $contextual_link_container = $block_selector;
285     }
286     // Ensure that contextual link element is present because this is required
287     // to open the off-canvas dialog in edit mode.
288     $contextual_link = $this->assertSession()->waitForElement('css', "$contextual_link_container .contextual-links a");
289     $this->assertNotEmpty($contextual_link);
290     // When page first loads Edit Mode is not triggered until first contextual
291     // link is added.
292     $this->assertElementVisibleAfterWait('css', '.dialog-off-canvas__main-canvas.js-settings-tray-edit-mode');
293     // Ensure that all other Ajax activity is completed.
294     $this->assertSession()->assertWaitOnAjaxRequest();
295     $this->click($block_selector);
296     $this->waitForOffCanvasToOpen();
297     $this->assertOffCanvasBlockFormIsValid();
298   }
299
300   /**
301    * Tests QuickEdit links behavior.
302    */
303   public function testQuickEditLinks() {
304     $this->container->get('module_installer')->install(['quickedit']);
305     $this->grantPermissions(Role::load(RoleInterface::AUTHENTICATED_ID), ['access in-place editing']);
306     $quick_edit_selector = '#quickedit-entity-toolbar';
307     $node_selector = '[data-quickedit-entity-id="node/1"]';
308     $body_selector = '[data-quickedit-field-id="node/1/body/en/full"]';
309     $web_assert = $this->assertSession();
310     // Create a Content type and two test nodes.
311     $this->createContentType(['type' => 'page']);
312     $auth_role = Role::load(Role::AUTHENTICATED_ID);
313     $this->grantPermissions($auth_role, [
314       'edit any page content',
315       'access content',
316     ]);
317     $node = $this->createNode(
318       [
319         'title' => 'Page One',
320         'type' => 'page',
321         'body' => [
322           [
323             'value' => 'Regular NODE body for the test.',
324             'format' => 'plain_text',
325           ],
326         ],
327       ]
328     );
329     $page = $this->getSession()->getPage();
330     $block_plugin = 'system_powered_by_block';
331
332     foreach ($this->getTestThemes() as $theme) {
333
334       $this->enableTheme($theme);
335
336       $block = $this->placeBlock($block_plugin);
337       $block_selector = str_replace('_', '-', $this->getBlockSelector($block));
338       // Load the same page twice.
339       foreach ([1, 2] as $page_load_times) {
340         $this->drupalGet('node/' . $node->id());
341         // The 2nd page load we should already be in edit mode.
342         if ($page_load_times == 1) {
343           $this->enableEditMode();
344         }
345         // In Edit mode clicking field should open QuickEdit toolbar.
346         $page->find('css', $body_selector)->click();
347         $this->assertElementVisibleAfterWait('css', $quick_edit_selector);
348
349         $this->disableEditMode();
350         // Exiting Edit mode should close QuickEdit toolbar.
351         $web_assert->elementNotExists('css', $quick_edit_selector);
352         // When not in Edit mode QuickEdit toolbar should not open.
353         $page->find('css', $body_selector)->click();
354         $web_assert->elementNotExists('css', $quick_edit_selector);
355         $this->enableEditMode();
356         $this->openBlockForm($block_selector);
357         $page->find('css', $body_selector)->click();
358         $this->assertElementVisibleAfterWait('css', $quick_edit_selector);
359         // Off-canvas dialog should be closed when opening QuickEdit toolbar.
360         $this->waitForOffCanvasToClose();
361
362         $this->openBlockForm($block_selector);
363         // QuickEdit toolbar should be closed when opening Off-canvas dialog.
364         $web_assert->elementNotExists('css', $quick_edit_selector);
365       }
366       // Check using contextual links to invoke QuickEdit and open the tray.
367       $this->drupalGet('node/' . $node->id());
368       $web_assert->assertWaitOnAjaxRequest();
369       $this->disableEditMode();
370       // Open QuickEdit toolbar before going into Edit mode.
371       $this->clickContextualLink($node_selector, "Quick edit");
372       $this->assertElementVisibleAfterWait('css', $quick_edit_selector);
373       // Open off-canvas and enter Edit mode via contextual link.
374       $this->clickContextualLink($block_selector, "Quick edit");
375       $this->waitForOffCanvasToOpen();
376       // QuickEdit toolbar should be closed when opening off-canvas dialog.
377       $web_assert->elementNotExists('css', $quick_edit_selector);
378       // Open QuickEdit toolbar via contextual link while in Edit mode.
379       $this->clickContextualLink($node_selector, "Quick edit", FALSE);
380       $this->waitForOffCanvasToClose();
381       $this->assertElementVisibleAfterWait('css', $quick_edit_selector);
382       $this->disableEditMode();
383     }
384   }
385
386   /**
387    * Tests enabling and disabling Edit Mode.
388    */
389   public function testEditModeEnableDisable() {
390     foreach ($this->getTestThemes() as $theme) {
391       $this->enableTheme($theme);
392       $block = $this->placeBlock('system_powered_by_block');
393       foreach (['contextual_link', 'toolbar_link'] as $enable_option) {
394         $this->drupalGet('user');
395         $this->assertEditModeDisabled();
396         switch ($enable_option) {
397           // Enable Edit mode.
398           case 'contextual_link':
399             $this->clickContextualLink($this->getBlockSelector($block), "Quick edit");
400             $this->waitForOffCanvasToOpen();
401             $this->assertEditModeEnabled();
402             break;
403
404           case 'toolbar_link':
405             $this->enableEditMode();
406             break;
407         }
408         $this->disableEditMode();
409
410         // Make another page request to ensure Edit mode is still disabled.
411         $this->drupalGet('user');
412         $this->assertEditModeDisabled();
413         // Make sure on this page request it also re-enables and disables
414         // correctly.
415         $this->enableEditMode();
416         $this->disableEditMode();
417       }
418     }
419   }
420
421   /**
422    * Assert that edit mode has been properly enabled.
423    */
424   protected function assertEditModeEnabled() {
425     $web_assert = $this->assertSession();
426     // No contextual triggers should be hidden.
427     $web_assert->elementNotExists('css', '.contextual .trigger.visually-hidden');
428     // The toolbar edit button should read "Editing".
429     $web_assert->elementContains('css', static::TOOLBAR_EDIT_LINK_SELECTOR, 'Editing');
430     // The main canvas element should have the "js-settings-tray-edit-mode" class.
431     $web_assert->elementExists('css', '.dialog-off-canvas__main-canvas.js-settings-tray-edit-mode');
432   }
433
434   /**
435    * Assert that edit mode has been properly disabled.
436    */
437   protected function assertEditModeDisabled() {
438     $web_assert = $this->assertSession();
439     // Contextual triggers should be hidden.
440     $web_assert->elementExists('css', '.contextual .trigger.visually-hidden');
441     // No contextual triggers should be not hidden.
442     $web_assert->elementNotExists('css', '.contextual .trigger:not(.visually-hidden)');
443     // The toolbar edit button should read "Edit".
444     $web_assert->elementContains('css', static::TOOLBAR_EDIT_LINK_SELECTOR, 'Edit');
445     // The main canvas element should NOT have the "js-settings-tray-edit-mode"
446     // class.
447     $web_assert->elementNotExists('css', '.dialog-off-canvas__main-canvas.js-settings-tray-edit-mode');
448   }
449
450   /**
451    * Press the toolbar Edit button provided by the contextual module.
452    */
453   protected function pressToolbarEditButton() {
454     $this->assertSession()->waitForElement('css', '[data-contextual-id] .contextual-links a');
455     $edit_button = $this->getSession()
456       ->getPage()
457       ->find('css', static::TOOLBAR_EDIT_LINK_SELECTOR);
458     $edit_button->press();
459   }
460
461   /**
462    * Creates a custom block.
463    *
464    * @param bool|string $title
465    *   (optional) Title of block. When no value is given uses a random name.
466    *   Defaults to FALSE.
467    * @param string $bundle
468    *   (optional) Bundle name. Defaults to 'basic'.
469    * @param bool $save
470    *   (optional) Whether to save the block. Defaults to TRUE.
471    *
472    * @return \Drupal\block_content\Entity\BlockContent
473    *   Created custom block.
474    */
475   protected function createBlockContent($title = FALSE, $bundle = 'basic', $save = TRUE) {
476     $title = $title ?: $this->randomName();
477     $block_content = BlockContent::create([
478       'info' => $title,
479       'type' => $bundle,
480       'langcode' => 'en',
481       'body' => [
482         'value' => 'The name "llama" was adopted by European settlers from native Peruvians.',
483         'format' => 'plain_text',
484       ],
485     ]);
486     if ($block_content && $save === TRUE) {
487       $block_content->save();
488     }
489     return $block_content;
490   }
491
492   /**
493    * Creates a custom block type (bundle).
494    *
495    * @param string $label
496    *   The block type label.
497    * @param bool $create_body
498    *   Whether or not to create the body field.
499    *
500    * @return \Drupal\block_content\Entity\BlockContentType
501    *   Created custom block type.
502    */
503   protected function createBlockContentType($label, $create_body = FALSE) {
504     $bundle = BlockContentType::create([
505       'id' => $label,
506       'label' => $label,
507       'revision' => FALSE,
508     ]);
509     $bundle->save();
510     if ($create_body) {
511       block_content_add_body_field($bundle->id());
512     }
513     return $bundle;
514   }
515
516   /**
517    * Tests that contextual links in custom blocks are changed.
518    *
519    * "Quick edit" is quickedit.module link.
520    * "Quick edit settings" is settings_tray.module link.
521    */
522   public function testCustomBlockLinks() {
523     $this->container->get('module_installer')->install(['quickedit']);
524     $this->grantPermissions(Role::load(RoleInterface::AUTHENTICATED_ID), ['access in-place editing']);
525     $this->drupalGet('user');
526     $page = $this->getSession()->getPage();
527     $links = $page->findAll('css', "#block-custom .contextual-links li a");
528     $link_labels = [];
529     /** @var \Behat\Mink\Element\NodeElement $link */
530     foreach ($links as $link) {
531       $link_labels[$link->getAttribute('href')] = $link->getText();
532     }
533     $href = array_search('Quick edit', $link_labels);
534     $this->assertEquals('', $href);
535     $href = array_search('Quick edit settings', $link_labels);
536     $this->assertTrue(strstr($href, '/admin/structure/block/manage/custom/off-canvas?destination=user/2') !== FALSE);
537   }
538
539   /**
540    * Gets the block CSS selector.
541    *
542    * @param \Drupal\block\Entity\Block $block
543    *   The block.
544    *
545    * @return string
546    *   The CSS selector.
547    */
548   public function getBlockSelector(Block $block) {
549     return '#block-' . $block->id();
550   }
551
552   /**
553    * Determines if the label input is visible.
554    *
555    * @return bool
556    *   TRUE if the label is visible, FALSE if it is not.
557    */
558   protected function isLabelInputVisible() {
559     return $this->getSession()->getPage()->find('css', static::LABEL_INPUT_SELECTOR)->isVisible();
560   }
561
562   /**
563    * Tests access to block forms with related configuration is correct.
564    */
565   public function testBlockConfigAccess() {
566     $page = $this->getSession()->getPage();
567     $web_assert = $this->assertSession();
568
569     // Confirm that System Branding block does not expose Site Name field
570     // without permission.
571     $block = $this->placeBlock('system_branding_block');
572     $this->drupalGet('user');
573     $this->enableEditMode();
574     $this->openBlockForm($this->getBlockSelector($block));
575     // The site name field should not appear because the user doesn't have
576     // permission.
577     $web_assert->fieldNotExists('settings[site_information][site_name]');
578     $page->pressButton('Save Site branding');
579     $this->assertElementVisibleAfterWait('css', 'div:contains(The block configuration has been saved)');
580     $web_assert->assertWaitOnAjaxRequest();
581     // Confirm we did not save changes to the configuration.
582     $this->assertEquals('Drupal', \Drupal::configFactory()->getEditable('system.site')->get('name'));
583
584     $this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), ['administer site configuration']);
585     $this->drupalGet('user');
586     $this->openBlockForm($this->getBlockSelector($block));
587     // The site name field should appear because the user does have permission.
588     $web_assert->fieldExists('settings[site_information][site_name]');
589
590     // Confirm that the Menu block does not expose menu configuration without
591     // permission.
592     // Add a link or the menu will not render.
593     $menu_link_content = MenuLinkContent::create([
594       'title' => 'This is on the menu',
595       'menu_name' => 'main',
596       'link' => ['uri' => 'route:<front>'],
597     ]);
598     $menu_link_content->save();
599     $this->assertNotEmpty($menu_link_content->isEnabled());
600     $menu_without_overrides = \Drupal::configFactory()->getEditable('system.menu.main')->get();
601     $block = $this->placeBlock('system_menu_block:main');
602     $this->drupalGet('user');
603     $web_assert->pageTextContains('This is on the menu');
604     $this->openBlockForm($this->getBlockSelector($block));
605     // Edit menu form should not appear because the user doesn't have
606     // permission.
607     $web_assert->pageTextNotContains('Edit menu');
608     $page->pressButton('Save Main navigation');
609     $this->assertElementVisibleAfterWait('css', 'div:contains(The block configuration has been saved)');
610     $web_assert->assertWaitOnAjaxRequest();
611     // Confirm we did not save changes to the menu or the menu link.
612     $this->assertEquals($menu_without_overrides, \Drupal::configFactory()->getEditable('system.menu.main')->get());
613     $menu_link_content = MenuLinkContent::load($menu_link_content->id());
614     $this->assertNotEmpty($menu_link_content->isEnabled());
615     // Confirm menu is still on the page.
616     $this->drupalGet('user');
617     $web_assert->pageTextContains('This is on the menu');
618
619
620     $this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), ['administer menu']);
621     $this->drupalGet('user');
622     $web_assert->pageTextContains('This is on the menu');
623     $this->openBlockForm($this->getBlockSelector($block));
624     // Edit menu form should appear because the user does have permission.
625     $web_assert->pageTextContains('Edit menu');
626   }
627
628   /**
629    * Test that validation errors appear in the off-canvas dialog.
630    */
631   public function testValidationMessages() {
632     $page = $this->getSession()->getPage();
633     $web_assert = $this->assertSession();
634     foreach ($this->getTestThemes() as $theme) {
635       $this->enableTheme($theme);
636       $block = $this->placeBlock('settings_tray_test_validation');
637       $this->drupalGet('user');
638       $this->enableEditMode();
639       $this->openBlockForm($this->getBlockSelector($block));
640       $page->pressButton('Save Block with validation error');
641       $web_assert->assertWaitOnAjaxRequest();
642       // The settings_tray_test_validation test plugin form always has a
643       // validation error.
644       $web_assert->elementContains('css', '#drupal-off-canvas', 'Sorry system error. Please save again');
645       $this->disableEditMode();
646       $block->delete();
647     }
648   }
649
650 }