dde95027bd497d7b0dc33adfec19b547b45309f4
[yaffs-website] / web / core / modules / menu_ui / src / Tests / MenuTest.php
1 <?php
2
3 namespace Drupal\menu_ui\Tests;
4
5 use Drupal\block\Entity\Block;
6 use Drupal\Component\Serialization\Json;
7 use Drupal\Component\Utility\Unicode;
8 use Drupal\Core\Menu\MenuLinkInterface;
9 use Drupal\Core\Url;
10 use Drupal\menu_link_content\Entity\MenuLinkContent;
11 use Drupal\system\Entity\Menu;
12 use Drupal\node\Entity\Node;
13 use Drupal\node\NodeInterface;
14
15 /**
16  * Add a custom menu, add menu links to the custom menu and Tools menu, check
17  * their data, and delete them using the UI.
18  *
19  * @group menu_ui
20  */
21 class MenuTest extends MenuWebTestBase {
22
23   /**
24    * Modules to enable.
25    *
26    * @var array
27    */
28   public static $modules = ['node', 'block', 'contextual', 'help', 'path', 'test_page_test'];
29
30   /**
31    * A user with administration rights.
32    *
33    * @var \Drupal\user\UserInterface
34    */
35   protected $adminUser;
36
37   /**
38    * An authenticated user.
39    *
40    * @var \Drupal\user\UserInterface
41    */
42   protected $authenticatedUser;
43
44   /**
45    * Array of placed menu blocks keyed by block ID.
46    *
47    * @var array
48    */
49   protected $blockPlacements;
50
51   /**
52    * A test menu.
53    *
54    * @var \Drupal\system\Entity\Menu
55    */
56   protected $menu;
57
58   /**
59    * An array of test menu links.
60    *
61    * @var \Drupal\menu_link_content\MenuLinkContentInterface[]
62    */
63   protected $items;
64
65   protected function setUp() {
66     parent::setUp();
67
68     $this->drupalPlaceBlock('page_title_block');
69
70     $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
71
72     // Create users.
73     $this->adminUser = $this->drupalCreateUser(['access administration pages', 'administer blocks', 'administer menu', 'create article content']);
74     $this->authenticatedUser = $this->drupalCreateUser([]);
75   }
76
77   /**
78    * Tests menu functionality using the admin and user interfaces.
79    */
80   public function testMenu() {
81     // Log in the user.
82     $this->drupalLogin($this->adminUser);
83     $this->items = [];
84
85     $this->menu = $this->addCustomMenu();
86     $this->doMenuTests();
87     $this->doTestMenuBlock();
88     $this->addInvalidMenuLink();
89     $this->addCustomMenuCRUD();
90
91     // Verify that the menu links rebuild is idempotent and leaves the same
92     // number of links in the table.
93     /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
94     $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
95     $before_count = $menu_link_manager->countMenuLinks(NULL);
96     $menu_link_manager->rebuild();
97     $after_count = $menu_link_manager->countMenuLinks(NULL);
98     $this->assertIdentical($before_count, $after_count, 'MenuLinkManager::rebuild() does not add more links');
99     // Do standard user tests.
100     // Log in the user.
101     $this->drupalLogin($this->authenticatedUser);
102     $this->verifyAccess(403);
103
104     foreach ($this->items as $item) {
105       // Menu link URIs are stored as 'internal:/node/$nid'.
106       $node = Node::load(str_replace('internal:/node/', '', $item->link->uri));
107       $this->verifyMenuLink($item, $node);
108     }
109
110     // Log in the administrator.
111     $this->drupalLogin($this->adminUser);
112
113     // Verify delete link exists and reset link does not exist.
114     $this->drupalGet('admin/structure/menu/manage/' . $this->menu->id());
115     $this->assertLinkByHref(Url::fromRoute('entity.menu_link_content.delete_form', ['menu_link_content' => $this->items[0]->id()])->toString());
116     $this->assertNoLinkByHref(Url::fromRoute('menu_ui.link_reset', ['menu_link_plugin' => $this->items[0]->getPluginId()])->toString());
117     // Check delete and reset access.
118     $this->drupalGet('admin/structure/menu/item/' . $this->items[0]->id() . '/delete');
119     $this->assertResponse(200);
120     $this->drupalGet('admin/structure/menu/link/' . $this->items[0]->getPluginId() . '/reset');
121     $this->assertResponse(403);
122
123     // Delete menu links.
124     foreach ($this->items as $item) {
125       $this->deleteMenuLink($item);
126     }
127
128     // Delete custom menu.
129     $this->deleteCustomMenu();
130
131     // Modify and reset a standard menu link.
132     $instance = $this->getStandardMenuLink();
133     $old_weight = $instance->getWeight();
134     // Edit the static menu link.
135     $edit = [];
136     $edit['weight'] = 10;
137     $id = $instance->getPluginId();
138     $this->drupalPostForm("admin/structure/menu/link/$id/edit", $edit, t('Save'));
139     $this->assertResponse(200);
140     $this->assertText('The menu link has been saved.');
141     $menu_link_manager->resetDefinitions();
142
143     $instance = $menu_link_manager->createInstance($instance->getPluginId());
144     $this->assertEqual($edit['weight'], $instance->getWeight(), 'Saving an existing link updates the weight.');
145     $this->resetMenuLink($instance, $old_weight);
146   }
147
148   /**
149    * Adds a custom menu using CRUD functions.
150    */
151   public function addCustomMenuCRUD() {
152     // Add a new custom menu.
153     $menu_name = substr(hash('sha256', $this->randomMachineName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI);
154     $label = $this->randomMachineName(16);
155
156     $menu = Menu::create([
157       'id' => $menu_name,
158       'label' => $label,
159       'description' => 'Description text',
160     ]);
161     $menu->save();
162
163     // Assert the new menu.
164     $this->drupalGet('admin/structure/menu/manage/' . $menu_name);
165     $this->assertRaw($label, 'Custom menu was added.');
166
167     // Edit the menu.
168     $new_label = $this->randomMachineName(16);
169     $menu->set('label', $new_label);
170     $menu->save();
171     $this->drupalGet('admin/structure/menu/manage/' . $menu_name);
172     $this->assertRaw($new_label, 'Custom menu was edited.');
173   }
174
175   /**
176    * Creates a custom menu.
177    *
178    * @return \Drupal\system\Entity\Menu
179    *   The custom menu that has been created.
180    */
181   public function addCustomMenu() {
182     // Try adding a menu using a menu_name that is too long.
183     $this->drupalGet('admin/structure/menu/add');
184     $menu_name = substr(hash('sha256', $this->randomMachineName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI + 1);
185     $label = $this->randomMachineName(16);
186     $edit = [
187       'id' => $menu_name,
188       'description' => '',
189       'label' => $label,
190     ];
191     $this->drupalPostForm('admin/structure/menu/add', $edit, t('Save'));
192
193     // Verify that using a menu_name that is too long results in a validation
194     // message.
195     $this->assertRaw(t('@name cannot be longer than %max characters but is currently %length characters long.', [
196       '@name' => t('Menu name'),
197       '%max' => MENU_MAX_MENU_NAME_LENGTH_UI,
198       '%length' => Unicode::strlen($menu_name),
199     ]));
200
201     // Change the menu_name so it no longer exceeds the maximum length.
202     $menu_name = substr(hash('sha256', $this->randomMachineName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI);
203     $edit['id'] = $menu_name;
204     $this->drupalPostForm('admin/structure/menu/add', $edit, t('Save'));
205
206     // Verify that no validation error is given for menu_name length.
207     $this->assertNoRaw(t('@name cannot be longer than %max characters but is currently %length characters long.', [
208       '@name' => t('Menu name'),
209       '%max' => MENU_MAX_MENU_NAME_LENGTH_UI,
210       '%length' => Unicode::strlen($menu_name),
211     ]));
212     // Verify that the confirmation message is displayed.
213     $this->assertRaw(t('Menu %label has been added.', ['%label' => $label]));
214     $this->drupalGet('admin/structure/menu');
215     $this->assertText($label, 'Menu created');
216
217     // Confirm that the custom menu block is available.
218     $this->drupalGet('admin/structure/block/list/' . $this->config('system.theme')->get('default'));
219     $this->clickLinkPartialName('Place block');
220     $this->assertText($label);
221
222     // Enable the block.
223     $block = $this->drupalPlaceBlock('system_menu_block:' . $menu_name);
224     $this->blockPlacements[$menu_name] = $block->id();
225     return Menu::load($menu_name);
226   }
227
228   /**
229    * Deletes the locally stored custom menu.
230    *
231    * This deletes the custom menu that is stored in $this->menu and performs
232    * tests on the menu delete user interface.
233    */
234   public function deleteCustomMenu() {
235     $menu_name = $this->menu->id();
236     $label = $this->menu->label();
237
238     // Delete custom menu.
239     $this->drupalPostForm("admin/structure/menu/manage/$menu_name/delete", [], t('Delete'));
240     $this->assertResponse(200);
241     $this->assertRaw(t('The menu %title has been deleted.', ['%title' => $label]), 'Custom menu was deleted');
242     $this->assertNull(Menu::load($menu_name), 'Custom menu was deleted');
243     // Test if all menu links associated with the menu were removed from
244     // database.
245     $result = entity_load_multiple_by_properties('menu_link_content', ['menu_name' => $menu_name]);
246     $this->assertFalse($result, 'All menu links associated with the custom menu were deleted.');
247
248     // Make sure there's no delete button on system menus.
249     $this->drupalGet('admin/structure/menu/manage/main');
250     $this->assertNoRaw('edit-delete', 'The delete button was not found');
251
252     // Try to delete the main menu.
253     $this->drupalGet('admin/structure/menu/manage/main/delete');
254     $this->assertText(t('You are not authorized to access this page.'));
255   }
256
257   /**
258    * Tests menu functionality.
259    */
260   public function doMenuTests() {
261     $menu_name = $this->menu->id();
262
263     // Test the 'Add link' local action.
264     $this->drupalGet(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name]));
265
266     $this->clickLink(t('Add link'));
267     $link_title = $this->randomString();
268     $this->drupalPostForm(NULL, ['link[0][uri]' => '/', 'title[0][value]' => $link_title], t('Save'));
269     $this->assertUrl(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name]));
270     // Test the 'Edit' operation.
271     $this->clickLink(t('Edit'));
272     $this->assertFieldByName('title[0][value]', $link_title);
273     $link_title = $this->randomString();
274     $this->drupalPostForm(NULL, ['title[0][value]' => $link_title], t('Save'));
275     $this->assertUrl(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name]));
276     // Test the 'Delete' operation.
277     $this->clickLink(t('Delete'));
278     $this->assertRaw(t('Are you sure you want to delete the custom menu link %item?', ['%item' => $link_title]));
279     $this->drupalPostForm(NULL, [], t('Delete'));
280     $this->assertUrl(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name]));
281
282     // Add nodes to use as links for menu links.
283     $node1 = $this->drupalCreateNode(['type' => 'article']);
284     $node2 = $this->drupalCreateNode(['type' => 'article']);
285     $node3 = $this->drupalCreateNode(['type' => 'article']);
286     $node4 = $this->drupalCreateNode(['type' => 'article']);
287     // Create a node with an alias.
288     $node5 = $this->drupalCreateNode([
289       'type' => 'article',
290       'path' => [
291         'alias' => '/node5',
292       ],
293     ]);
294
295     // Verify add link button.
296     $this->drupalGet('admin/structure/menu');
297     $this->assertLinkByHref('admin/structure/menu/manage/' . $menu_name . '/add', 0, "The add menu link button URL is correct");
298
299     // Verify form defaults.
300     $this->doMenuLinkFormDefaultsTest();
301
302     // Add menu links.
303     $item1 = $this->addMenuLink('', '/node/' . $node1->id(), $menu_name, TRUE);
304     $item2 = $this->addMenuLink($item1->getPluginId(), '/node/' . $node2->id(), $menu_name, FALSE);
305     $item3 = $this->addMenuLink($item2->getPluginId(), '/node/' . $node3->id(), $menu_name);
306
307     // Hierarchy
308     // <$menu_name>
309     // - item1
310     // -- item2
311     // --- item3
312
313     $this->assertMenuLink($item1->getPluginId(), [
314       'children' => [$item2->getPluginId(), $item3->getPluginId()],
315       'parents' => [$item1->getPluginId()],
316       // We assert the language code here to make sure that the language
317       // selection element degrades gracefully without the Language module.
318       'langcode' => 'en',
319     ]);
320     $this->assertMenuLink($item2->getPluginId(), [
321       'children' => [$item3->getPluginId()],
322       'parents' => [$item2->getPluginId(), $item1->getPluginId()],
323       // See above.
324       'langcode' => 'en',
325     ]);
326     $this->assertMenuLink($item3->getPluginId(), [
327       'children' => [],
328       'parents' => [$item3->getPluginId(), $item2->getPluginId(), $item1->getPluginId()],
329       // See above.
330       'langcode' => 'en',
331     ]);
332
333     // Verify menu links.
334     $this->verifyMenuLink($item1, $node1);
335     $this->verifyMenuLink($item2, $node2, $item1, $node1);
336     $this->verifyMenuLink($item3, $node3, $item2, $node2);
337
338     // Add more menu links.
339     $item4 = $this->addMenuLink('', '/node/' . $node4->id(), $menu_name);
340     $item5 = $this->addMenuLink($item4->getPluginId(), '/node/' . $node5->id(), $menu_name);
341     // Create a menu link pointing to an alias.
342     $item6 = $this->addMenuLink($item4->getPluginId(), '/node5', $menu_name, TRUE, '0');
343
344     // Hierarchy
345     // <$menu_name>
346     // - item1
347     // -- item2
348     // --- item3
349     // - item4
350     // -- item5
351     // -- item6
352
353     $this->assertMenuLink($item4->getPluginId(), [
354       'children' => [$item5->getPluginId(), $item6->getPluginId()],
355       'parents' => [$item4->getPluginId()],
356       // See above.
357       'langcode' => 'en',
358     ]);
359     $this->assertMenuLink($item5->getPluginId(), [
360       'children' => [],
361       'parents' => [$item5->getPluginId(), $item4->getPluginId()],
362       'langcode' => 'en',
363     ]);
364     $this->assertMenuLink($item6->getPluginId(), [
365       'children' => [],
366       'parents' => [$item6->getPluginId(), $item4->getPluginId()],
367       'route_name' => 'entity.node.canonical',
368       'route_parameters' => ['node' => $node5->id()],
369       'url' => '',
370       // See above.
371       'langcode' => 'en',
372     ]);
373
374     // Modify menu links.
375     $this->modifyMenuLink($item1);
376     $this->modifyMenuLink($item2);
377
378     // Toggle menu links.
379     $this->toggleMenuLink($item1);
380     $this->toggleMenuLink($item2);
381
382     // Move link and verify that descendants are updated.
383     $this->moveMenuLink($item2, $item5->getPluginId(), $menu_name);
384     // Hierarchy
385     // <$menu_name>
386     // - item1
387     // - item4
388     // -- item5
389     // --- item2
390     // ---- item3
391     // -- item6
392
393     $this->assertMenuLink($item1->getPluginId(), [
394       'children' => [],
395       'parents' => [$item1->getPluginId()],
396       // See above.
397       'langcode' => 'en',
398     ]);
399     $this->assertMenuLink($item4->getPluginId(), [
400       'children' => [$item5->getPluginId(), $item6->getPluginId(), $item2->getPluginId(), $item3->getPluginId()],
401       'parents' => [$item4->getPluginId()],
402       // See above.
403       'langcode' => 'en',
404     ]);
405
406     $this->assertMenuLink($item5->getPluginId(), [
407       'children' => [$item2->getPluginId(), $item3->getPluginId()],
408       'parents' => [$item5->getPluginId(), $item4->getPluginId()],
409       // See above.
410       'langcode' => 'en',
411     ]);
412     $this->assertMenuLink($item2->getPluginId(), [
413       'children' => [$item3->getPluginId()],
414       'parents' => [$item2->getPluginId(), $item5->getPluginId(), $item4->getPluginId()],
415       // See above.
416       'langcode' => 'en',
417     ]);
418     $this->assertMenuLink($item3->getPluginId(), [
419       'children' => [],
420       'parents' => [$item3->getPluginId(), $item2->getPluginId(), $item5->getPluginId(), $item4->getPluginId()],
421       // See above.
422       'langcode' => 'en',
423     ]);
424
425     // Add 102 menu links with increasing weights, then make sure the last-added
426     // item's weight doesn't get changed because of the old hardcoded delta=50.
427     $items = [];
428     for ($i = -50; $i <= 51; $i++) {
429       $items[$i] = $this->addMenuLink('', '/node/' . $node1->id(), $menu_name, TRUE, strval($i));
430     }
431     $this->assertMenuLink($items[51]->getPluginId(), ['weight' => '51']);
432
433     // Disable a link and then re-enable the link via the overview form.
434     $this->disableMenuLink($item1);
435     $edit = [];
436     $edit['links[menu_plugin_id:' . $item1->getPluginId() . '][enabled]'] = TRUE;
437     $this->drupalPostForm('admin/structure/menu/manage/' . $item1->getMenuName(), $edit, t('Save'));
438
439     // Mark item2, item4 and item5 as expanded.
440     // This is done in order to show them on the frontpage.
441     $item2->expanded->value = 1;
442     $item2->save();
443     $item4->expanded->value = 1;
444     $item4->save();
445     $item5->expanded->value = 1;
446     $item5->save();
447
448     // Verify in the database.
449     $this->assertMenuLink($item1->getPluginId(), ['enabled' => 1]);
450
451     // Add an external link.
452     $item7 = $this->addMenuLink('', 'https://www.drupal.org', $menu_name);
453     $this->assertMenuLink($item7->getPluginId(), ['url' => 'https://www.drupal.org']);
454
455     // Add <front> menu item.
456     $item8 = $this->addMenuLink('', '/', $menu_name);
457     $this->assertMenuLink($item8->getPluginId(), ['route_name' => '<front>']);
458     $this->drupalGet('');
459     $this->assertResponse(200);
460     // Make sure we get routed correctly.
461     $this->clickLink($item8->getTitle());
462     $this->assertResponse(200);
463
464     // Check invalid menu link parents.
465     $this->checkInvalidParentMenuLinks();
466
467     // Save menu links for later tests.
468     $this->items[] = $item1;
469     $this->items[] = $item2;
470   }
471
472   /**
473    * Ensures that the proper default values are set when adding a menu link
474    */
475   protected function doMenuLinkFormDefaultsTest() {
476     $this->drupalGet("admin/structure/menu/manage/tools/add");
477     $this->assertResponse(200);
478
479     $this->assertFieldByName('title[0][value]', '');
480     $this->assertFieldByName('link[0][uri]', '');
481
482     $this->assertNoFieldChecked('edit-expanded-value');
483     $this->assertFieldChecked('edit-enabled-value');
484
485     $this->assertFieldByName('description[0][value]', '');
486     $this->assertFieldByName('weight[0][value]', 0);
487   }
488
489   /**
490    * Adds and removes a menu link with a query string and fragment.
491    */
492   public function testMenuQueryAndFragment() {
493     $this->drupalLogin($this->adminUser);
494
495     // Make a path with query and fragment on.
496     $path = '/test-page?arg1=value1&arg2=value2';
497     $item = $this->addMenuLink('', $path);
498
499     $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit');
500     $this->assertFieldByName('link[0][uri]', $path, 'Path is found with both query and fragment.');
501
502     // Now change the path to something without query and fragment.
503     $path = '/test-page';
504     $this->drupalPostForm('admin/structure/menu/item/' . $item->id() . '/edit', ['link[0][uri]' => $path], t('Save'));
505     $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit');
506     $this->assertFieldByName('link[0][uri]', $path, 'Path no longer has query or fragment.');
507
508     // Use <front>#fragment and ensure that saving it does not lose its content.
509     $path = '<front>?arg1=value#fragment';
510     $item = $this->addMenuLink('', $path);
511
512     $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit');
513     $this->assertFieldByName('link[0][uri]', $path, 'Path is found with both query and fragment.');
514
515     $this->drupalPostForm('admin/structure/menu/item/' . $item->id() . '/edit', [], t('Save'));
516
517     $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit');
518     $this->assertFieldByName('link[0][uri]', $path, 'Path is found with both query and fragment.');
519   }
520
521   /**
522    * Tests renaming the built-in menu.
523    */
524   public function testSystemMenuRename() {
525     $this->drupalLogin($this->adminUser);
526     $edit = [
527       'label' => $this->randomMachineName(16),
528     ];
529     $this->drupalPostForm('admin/structure/menu/manage/main', $edit, t('Save'));
530
531     // Make sure menu shows up with new name in block addition.
532     $default_theme = $this->config('system.theme')->get('default');
533     $this->drupalget('admin/structure/block/list/' . $default_theme);
534     $this->clickLinkPartialName('Place block');
535     $this->assertText($edit['label']);
536   }
537
538   /**
539    * Tests that menu items pointing to unpublished nodes are editable.
540    */
541   public function testUnpublishedNodeMenuItem() {
542     $this->drupalLogin($this->drupalCreateUser(['access administration pages', 'administer blocks', 'administer menu', 'create article content', 'bypass node access']));
543     // Create an unpublished node.
544     $node = $this->drupalCreateNode([
545       'type' => 'article',
546       'status' => NodeInterface::NOT_PUBLISHED,
547     ]);
548
549     $item = $this->addMenuLink('', '/node/' . $node->id());
550     $this->modifyMenuLink($item);
551
552     // Test that a user with 'administer menu' but without 'bypass node access'
553     // cannot see the menu item.
554     $this->drupalLogout();
555     $this->drupalLogin($this->adminUser);
556     $this->drupalGet('admin/structure/menu/manage/' . $item->getMenuName());
557     $this->assertNoText($item->getTitle(), "Menu link pointing to unpublished node is only visible to users with 'bypass node access' permission");
558     // The cache contexts associated with the (in)accessible menu links are
559     // bubbled. See DefaultMenuLinkTreeManipulators::menuLinkCheckAccess().
560     $this->assertCacheContext('user.permissions');
561   }
562
563   /**
564    * Tests the contextual links on a menu block.
565    */
566   public function testBlockContextualLinks() {
567     $this->drupalLogin($this->drupalCreateUser(['administer menu', 'access contextual links', 'administer blocks']));
568     $custom_menu = $this->addCustomMenu();
569     $this->addMenuLink('', '/', $custom_menu->id());
570     $block = $this->drupalPlaceBlock('system_menu_block:' . $custom_menu->id(), ['label' => 'Custom menu', 'provider' => 'system']);
571     $this->drupalGet('test-page');
572
573     $id = 'block:block=' . $block->id() . ':langcode=en|menu:menu=' . $custom_menu->id() . ':langcode=en';
574     // @see \Drupal\contextual\Tests\ContextualDynamicContextTest:assertContextualLinkPlaceHolder()
575     $this->assertRaw('<div data-contextual-id="' . $id . '"></div>', format_string('Contextual link placeholder with id @id exists.', ['@id' => $id]));
576
577     // Get server-rendered contextual links.
578     // @see \Drupal\contextual\Tests\ContextualDynamicContextTest:renderContextualLinks()
579     $post = ['ids[0]' => $id];
580     $response = $this->drupalPost('contextual/render', 'application/json', $post, ['query' => ['destination' => 'test-page']]);
581     $this->assertResponse(200);
582     $json = Json::decode($response);
583     $this->assertIdentical($json[$id], '<ul class="contextual-links"><li class="block-configure"><a href="' . base_path() . 'admin/structure/block/manage/' . $block->id() . '">Configure block</a></li><li class="entitymenuedit-form"><a href="' . base_path() . 'admin/structure/menu/manage/' . $custom_menu->id() . '">Edit menu</a></li></ul>');
584   }
585
586   /**
587    * Adds a menu link using the UI.
588    *
589    * @param string $parent
590    *   Optional parent menu link id.
591    * @param string $path
592    *   The path to enter on the form. Defaults to the front page.
593    * @param string $menu_name
594    *   Menu name. Defaults to 'tools'.
595    * @param bool $expanded
596    *   Whether or not this menu link is expanded. Setting this to TRUE should
597    *   test whether it works when we do the authenticatedUser tests. Defaults
598    *   to FALSE.
599    * @param string $weight
600    *   Menu weight. Defaults to 0.
601    *
602    * @return \Drupal\menu_link_content\Entity\MenuLinkContent
603    *   A menu link entity.
604    */
605   public function addMenuLink($parent = '', $path = '/', $menu_name = 'tools', $expanded = FALSE, $weight = '0') {
606     // View add menu link page.
607     $this->drupalGet("admin/structure/menu/manage/$menu_name/add");
608     $this->assertResponse(200);
609
610     $title = '!link_' . $this->randomMachineName(16);
611     $edit = [
612       'link[0][uri]' => $path,
613       'title[0][value]' => $title,
614       'description[0][value]' => '',
615       'enabled[value]' => 1,
616       'expanded[value]' => $expanded,
617       'menu_parent' => $menu_name . ':' . $parent,
618       'weight[0][value]' => $weight,
619     ];
620
621     // Add menu link.
622     $this->drupalPostForm(NULL, $edit, t('Save'));
623     $this->assertResponse(200);
624     $this->assertText('The menu link has been saved.');
625
626     $menu_links = entity_load_multiple_by_properties('menu_link_content', ['title' => $title]);
627
628     $menu_link = reset($menu_links);
629     $this->assertTrue($menu_link, 'Menu link was found in database.');
630     $this->assertMenuLink($menu_link->getPluginId(), ['menu_name' => $menu_name, 'children' => [], 'parent' => $parent]);
631
632     return $menu_link;
633   }
634
635   /**
636    * Attempts to add menu link with invalid path or no access permission.
637    */
638   public function addInvalidMenuLink() {
639     foreach (['access' => '/admin/people/permissions'] as $type => $link_path) {
640       $edit = [
641         'link[0][uri]' => $link_path,
642         'title[0][value]' => 'title',
643       ];
644       $this->drupalPostForm("admin/structure/menu/manage/{$this->menu->id()}/add", $edit, t('Save'));
645       $this->assertRaw(t("The path '@link_path' is inaccessible.", ['@link_path' => $link_path]), 'Menu link was not created');
646     }
647   }
648
649   /**
650    * Tests that parent options are limited by depth when adding menu links.
651    */
652   public function checkInvalidParentMenuLinks() {
653     $last_link = NULL;
654     $created_links = [];
655
656     // Get the max depth of the tree.
657     $menu_link_tree = \Drupal::service('menu.link_tree');
658     $max_depth = $menu_link_tree->maxDepth();
659
660     // Create a maximum number of menu links, each a child of the previous.
661     for ($i = 0; $i <= $max_depth - 1; $i++) {
662       $parent = $last_link ? 'tools:' . $last_link->getPluginId() : 'tools:';
663       $title = 'title' . $i;
664       $edit = [
665         'link[0][uri]' => '/',
666         'title[0][value]' => $title,
667         'menu_parent' => $parent,
668         'description[0][value]' => '',
669         'enabled[value]' => 1,
670         'expanded[value]' => FALSE,
671         'weight[0][value]' => '0',
672       ];
673       $this->drupalPostForm("admin/structure/menu/manage/{$this->menu->id()}/add", $edit, t('Save'));
674       $menu_links = entity_load_multiple_by_properties('menu_link_content', ['title' => $title]);
675       $last_link = reset($menu_links);
676       $created_links[]  = 'tools:' . $last_link->getPluginId();
677     }
678
679     // The last link cannot be a parent in the new menu link form.
680     $this->drupalGet('admin/structure/menu/manage/admin/add');
681     $value = 'tools:' . $last_link->getPluginId();
682     $this->assertNoOption('edit-menu-parent', $value, 'The invalid option is not there.');
683
684     // All but the last link can be parents in the new menu link form.
685     array_pop($created_links);
686     foreach ($created_links as $key => $link) {
687       $this->assertOption('edit-menu-parent', $link, 'The valid option number ' . ($key + 1) . ' is there.');
688     }
689   }
690
691   /**
692    * Verifies a menu link using the UI.
693    *
694    * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
695    *   Menu link.
696    * @param object $item_node
697    *   Menu link content node.
698    * @param \Drupal\menu_link_content\Entity\MenuLinkContent $parent
699    *   Parent menu link.
700    * @param object $parent_node
701    *   Parent menu link content node.
702    */
703   public function verifyMenuLink(MenuLinkContent $item, $item_node, MenuLinkContent $parent = NULL, $parent_node = NULL) {
704     // View home page.
705     $this->drupalGet('');
706     $this->assertResponse(200);
707
708     // Verify parent menu link.
709     if (isset($parent)) {
710       // Verify menu link.
711       $title = $parent->getTitle();
712       $this->assertLink($title, 0, 'Parent menu link was displayed');
713
714       // Verify menu link link.
715       $this->clickLink($title);
716       $title = $parent_node->label();
717       $this->assertTitle(t("@title | Drupal", ['@title' => $title]), 'Parent menu link link target was correct');
718     }
719
720     // Verify menu link.
721     $title = $item->getTitle();
722     $this->assertLink($title, 0, 'Menu link was displayed');
723
724     // Verify menu link link.
725     $this->clickLink($title);
726     $title = $item_node->label();
727     $this->assertTitle(t("@title | Drupal", ['@title' => $title]), 'Menu link link target was correct');
728   }
729
730   /**
731    * Changes the parent of a menu link using the UI.
732    *
733    * @param \Drupal\menu_link_content\MenuLinkContentInterface $item
734    *   The menu link item to move.
735    * @param int $parent
736    *   The id of the new parent.
737    * @param string $menu_name
738    *   The menu the menu link will be moved to.
739    */
740   public function moveMenuLink(MenuLinkContent $item, $parent, $menu_name) {
741     $mlid = $item->id();
742
743     $edit = [
744       'menu_parent' => $menu_name . ':' . $parent,
745     ];
746     $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
747     $this->assertResponse(200);
748   }
749
750   /**
751    * Modifies a menu link using the UI.
752    *
753    * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
754    *   Menu link entity.
755    */
756   public function modifyMenuLink(MenuLinkContent $item) {
757     $item->title->value = $this->randomMachineName(16);
758
759     $mlid = $item->id();
760     $title = $item->getTitle();
761
762     // Edit menu link.
763     $edit = [];
764     $edit['title[0][value]'] = $title;
765     $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
766     $this->assertResponse(200);
767     $this->assertText('The menu link has been saved.');
768     // Verify menu link.
769     $this->drupalGet('admin/structure/menu/manage/' . $item->getMenuName());
770     $this->assertText($title, 'Menu link was edited');
771   }
772
773   /**
774    * Resets a standard menu link using the UI.
775    *
776    * @param \Drupal\Core\Menu\MenuLinkInterface $menu_link
777    *   The Menu link.
778    * @param int $old_weight
779    *   Original title for menu link.
780    */
781   public function resetMenuLink(MenuLinkInterface $menu_link, $old_weight) {
782     // Reset menu link.
783     $this->drupalPostForm("admin/structure/menu/link/{$menu_link->getPluginId()}/reset", [], t('Reset'));
784     $this->assertResponse(200);
785     $this->assertRaw(t('The menu link was reset to its default settings.'), 'Menu link was reset');
786
787     // Verify menu link.
788     $instance = \Drupal::service('plugin.manager.menu.link')->createInstance($menu_link->getPluginId());
789     $this->assertEqual($old_weight, $instance->getWeight(), 'Resets to the old weight.');
790   }
791
792   /**
793    * Deletes a menu link using the UI.
794    *
795    * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
796    *   Menu link.
797    */
798   public function deleteMenuLink(MenuLinkContent $item) {
799     $mlid = $item->id();
800     $title = $item->getTitle();
801
802     // Delete menu link.
803     $this->drupalPostForm("admin/structure/menu/item/$mlid/delete", [], t('Delete'));
804     $this->assertResponse(200);
805     $this->assertRaw(t('The menu link %title has been deleted.', ['%title' => $title]), 'Menu link was deleted');
806
807     // Verify deletion.
808     $this->drupalGet('');
809     $this->assertNoText($title, 'Menu link was deleted');
810   }
811
812   /**
813    * Alternately disables and enables a menu link.
814    *
815    * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
816    *   Menu link.
817    */
818   public function toggleMenuLink(MenuLinkContent $item) {
819     $this->disableMenuLink($item);
820
821     // Verify menu link is absent.
822     $this->drupalGet('');
823     $this->assertNoText($item->getTitle(), 'Menu link was not displayed');
824     $this->enableMenuLink($item);
825
826     // Verify menu link is displayed.
827     $this->drupalGet('');
828     $this->assertText($item->getTitle(), 'Menu link was displayed');
829   }
830
831   /**
832    * Disables a menu link.
833    *
834    * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
835    *   Menu link.
836    */
837   public function disableMenuLink(MenuLinkContent $item) {
838     $mlid = $item->id();
839     $edit['enabled[value]'] = FALSE;
840     $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
841
842     // Unlike most other modules, there is no confirmation message displayed.
843     // Verify in the database.
844     $this->assertMenuLink($item->getPluginId(), ['enabled' => 0]);
845   }
846
847   /**
848    * Enables a menu link.
849    *
850    * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item
851    *   Menu link.
852    */
853   public function enableMenuLink(MenuLinkContent $item) {
854     $mlid = $item->id();
855     $edit['enabled[value]'] = TRUE;
856     $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save'));
857
858     // Verify in the database.
859     $this->assertMenuLink($item->getPluginId(), ['enabled' => 1]);
860   }
861
862   /**
863    * Tests if administrative users other than user 1 can access the menu parents
864    * AJAX callback.
865    */
866   public function testMenuParentsJsAccess() {
867     $admin = $this->drupalCreateUser(['administer menu']);
868     $this->drupalLogin($admin);
869     // Just check access to the callback overall, the POST data is irrelevant.
870     $this->drupalGetAjax('admin/structure/menu/parents');
871     $this->assertResponse(200);
872
873     // Do standard user tests.
874     // Log in the user.
875     $this->drupalLogin($this->authenticatedUser);
876     $this->drupalGetAjax('admin/structure/menu/parents');
877     $this->assertResponse(403);
878   }
879
880   /**
881    * Returns standard menu link.
882    *
883    * @return \Drupal\Core\Menu\MenuLinkInterface
884    *   A menu link plugin.
885    */
886   private function getStandardMenuLink() {
887     // Retrieve menu link id of the Log out menu link, which will always be on
888     // the front page.
889     /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
890     $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
891     $instance = $menu_link_manager->getInstance(['id' => 'user.logout']);
892
893     $this->assertTrue((bool) $instance, 'Standard menu link was loaded');
894     return $instance;
895   }
896
897   /**
898    * Verifies the logged in user has the desired access to various menu pages.
899    *
900    * @param int $response
901    *   (optional) The expected HTTP response code. Defaults to 200.
902    */
903   private function verifyAccess($response = 200) {
904     // View menu help page.
905     $this->drupalGet('admin/help/menu');
906     $this->assertResponse($response);
907     if ($response == 200) {
908       $this->assertText(t('Menu'), 'Menu help was displayed');
909     }
910
911     // View menu build overview page.
912     $this->drupalGet('admin/structure/menu');
913     $this->assertResponse($response);
914     if ($response == 200) {
915       $this->assertText(t('Menus'), 'Menu build overview page was displayed');
916     }
917
918     // View tools menu customization page.
919     $this->drupalGet('admin/structure/menu/manage/' . $this->menu->id());
920     $this->assertResponse($response);
921     if ($response == 200) {
922       $this->assertText(t('Tools'), 'Tools menu page was displayed');
923     }
924
925     // View menu edit page for a static link.
926     $item = $this->getStandardMenuLink();
927     $this->drupalGet('admin/structure/menu/link/' . $item->getPluginId() . '/edit');
928     $this->assertResponse($response);
929     if ($response == 200) {
930       $this->assertText(t('Edit menu item'), 'Menu edit page was displayed');
931     }
932
933     // View add menu page.
934     $this->drupalGet('admin/structure/menu/add');
935     $this->assertResponse($response);
936     if ($response == 200) {
937       $this->assertText(t('Menus'), 'Add menu page was displayed');
938     }
939   }
940
941   /**
942    * Tests menu block settings.
943    */
944   protected function doTestMenuBlock() {
945     $menu_id = $this->menu->id();
946     $block_id = $this->blockPlacements[$menu_id];
947     $this->drupalGet('admin/structure/block/manage/' . $block_id);
948     $this->drupalPostForm(NULL, [
949       'settings[depth]' => 3,
950       'settings[level]' => 2,
951     ], t('Save block'));
952     $block = Block::load($block_id);
953     $settings = $block->getPlugin()->getConfiguration();
954     $this->assertEqual($settings['depth'], 3);
955     $this->assertEqual($settings['level'], 2);
956     // Reset settings.
957     $block->getPlugin()->setConfigurationValue('depth', 0);
958     $block->getPlugin()->setConfigurationValue('level', 1);
959     $block->save();
960   }
961
962 }