3 namespace Drupal\Tests\system\Functional\Menu;
6 use Drupal\Tests\BrowserTestBase;
9 * Tests menu router and default menu link functionality.
13 class MenuRouterTest extends BrowserTestBase {
20 public static $modules = ['block', 'menu_test', 'test_page_test'];
23 * Name of the administrative theme to use for tests.
27 protected $adminTheme;
30 * Name of the default theme to use for tests.
34 protected $defaultTheme;
36 protected function setUp() {
37 // Enable dummy module that implements hook_menu.
40 $this->drupalPlaceBlock('system_menu_block:tools');
41 $this->drupalPlaceBlock('local_tasks_block');
42 $this->drupalPlaceBlock('page_title_block');
46 * Tests menu integration.
48 public function testMenuIntegration() {
49 $this->doTestTitleMenuCallback();
50 $this->doTestMenuOptionalPlaceholders();
51 $this->doTestMenuHierarchy();
52 $this->doTestMenuOnRoute();
53 $this->doTestMenuName();
54 $this->doTestMenuLinksDiscoveredAlter();
55 $this->doTestHookMenuIntegration();
56 $this->doTestExoticPath();
60 * Test local tasks with route placeholders.
62 protected function doTestHookMenuIntegration() {
63 // Generate base path with random argument.
64 $machine_name = $this->randomMachineName(8);
65 $base_path = 'foo/' . $machine_name;
66 $this->drupalGet($base_path);
67 // Confirm correct controller activated.
68 $this->assertText('test1');
69 // Confirm local task links are displayed.
70 $this->assertLink('Local task A');
71 $this->assertLink('Local task B');
72 $this->assertNoLink('Local task C');
73 $this->assertEscaped("<script>alert('Welcome to the jungle!')</script>", ENT_QUOTES, 'UTF-8');
74 // Confirm correct local task href.
75 $this->assertLinkByHref(Url::fromRoute('menu_test.router_test1', ['bar' => $machine_name])->toString());
76 $this->assertLinkByHref(Url::fromRoute('menu_test.router_test2', ['bar' => $machine_name])->toString());
80 * Test title callback set to FALSE.
82 protected function doTestTitleCallbackFalse() {
83 $this->drupalGet('test-page');
84 $this->assertText('A title with @placeholder', 'Raw text found on the page');
85 $this->assertNoText(t('A title with @placeholder', ['@placeholder' => 'some other text']), 'Text with placeholder substitutions not found.');
89 * Tests page title of MENU_CALLBACKs.
91 protected function doTestTitleMenuCallback() {
92 // Verify that the menu router item title is not visible.
94 $this->assertNoText(t('Menu Callback Title'));
95 // Verify that the menu router item title is output as page title.
96 $this->drupalGet('menu_callback_title');
97 $this->assertText(t('Menu Callback Title'));
101 * Tests menu item descriptions.
103 protected function doTestDescriptionMenuItems() {
104 // Verify that the menu router item title is output as page title.
105 $this->drupalGet('menu_callback_description');
106 $this->assertText(t('Menu item description text'));
110 * Tests for menu_name parameter for default menu links.
112 protected function doTestMenuName() {
113 $admin_user = $this->drupalCreateUser(['administer site configuration']);
114 $this->drupalLogin($admin_user);
115 /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
116 $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
117 $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.menu_name_test');
118 $menu_link = reset($menu_links);
119 $this->assertEqual($menu_link->getMenuName(), 'original', 'Menu name is "original".');
121 // Change the menu_name parameter in menu_test.module, then force a menu
123 menu_test_menu_name('changed');
124 $menu_link_manager->rebuild();
126 $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.menu_name_test');
127 $menu_link = reset($menu_links);
128 $this->assertEqual($menu_link->getMenuName(), 'changed', 'Menu name was successfully changed after rebuild.');
132 * Tests menu links added in hook_menu_links_discovered_alter().
134 protected function doTestMenuLinksDiscoveredAlter() {
135 // Check that machine name does not need to be defined since it is already
136 // set as the key of each menu link.
137 /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
138 $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
139 $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.custom');
140 $menu_link = reset($menu_links);
141 $this->assertEqual($menu_link->getPluginId(), 'menu_test.custom', 'Menu links added at hook_menu_links_discovered_alter() obtain the machine name from the $links key.');
142 // Make sure that rebuilding the menu tree does not produce duplicates of
143 // links added by hook_menu_links_discovered_alter().
144 \Drupal::service('router.builder')->rebuild();
145 $this->drupalGet('menu-test');
146 $this->assertUniqueText('Custom link', 'Menu links added by hook_menu_links_discovered_alter() do not duplicate after a menu rebuild.');
150 * Tests for menu hierarchy.
152 protected function doTestMenuHierarchy() {
153 /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
154 $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
155 $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.hierarchy_parent');
156 $parent_link = reset($menu_links);
157 $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.hierarchy_parent_child');
158 $child_link = reset($menu_links);
159 $menu_links = $menu_link_manager->loadLinksByRoute('menu_test.hierarchy_parent_child2');
160 $unattached_child_link = reset($menu_links);
161 $this->assertEqual($child_link->getParent(), $parent_link->getPluginId(), 'The parent of a directly attached child is correct.');
162 $this->assertEqual($unattached_child_link->getParent(), $child_link->getPluginId(), 'The parent of a non-directly attached child is correct.');
166 * Test menu links that have optional placeholders.
168 protected function doTestMenuOptionalPlaceholders() {
169 $this->drupalGet('menu-test/optional');
170 $this->assertResponse(200);
171 $this->assertText('Sometimes there is no placeholder.');
173 $this->drupalGet('menu-test/optional/foobar');
174 $this->assertResponse(200);
175 $this->assertText("Sometimes there is a placeholder: 'foobar'.");
179 * Tests a menu on a router page.
181 protected function doTestMenuOnRoute() {
182 \Drupal::service('module_installer')->install(['router_test']);
183 \Drupal::service('router.builder')->rebuild();
186 $this->drupalGet('router_test/test2');
187 $this->assertLinkByHref('menu_no_title_callback');
188 $this->assertLinkByHref('menu-title-test/case1');
189 $this->assertLinkByHref('menu-title-test/case2');
190 $this->assertLinkByHref('menu-title-test/case3');
194 * Test path containing "exotic" characters.
196 protected function doTestExoticPath() {
197 // "Special" ASCII characters.
199 "menu-test/ -._~!$'\"()*@[]?&+%#,;=:" .
200 // Characters that look like a percent-escaped string.
201 "%23%25%26%2B%2F%3F" .
202 // Characters from various non-ASCII alphabets.
204 $this->drupalGet($path);
205 $this->assertRaw('This is the menuTestCallback content.');
206 $this->assertNoText(t('The website encountered an unexpected error. Please try again later.'));
210 * Make sure the maintenance mode can be bypassed using an EventSubscriber.
212 * @see \Drupal\menu_test\EventSubscriber\MaintenanceModeSubscriber::onKernelRequestMaintenance()
214 public function testMaintenanceModeLoginPaths() {
215 $this->container->get('state')->set('system.maintenance_mode', TRUE);
217 $offline_message = t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', ['@site' => $this->config('system.site')->get('name')]);
218 $this->drupalGet('test-page');
219 $this->assertText($offline_message);
220 $this->drupalGet('menu_login_callback');
221 $this->assertText('This is TestControllers::testLogin.', 'Maintenance mode can be bypassed using an event subscriber.');
223 $this->container->get('state')->set('system.maintenance_mode', FALSE);
227 * Test that an authenticated user hitting 'user/login' gets redirected to
228 * 'user' and 'user/register' gets redirected to the user edit page.
230 public function testAuthUserUserLogin() {
231 $web_user = $this->drupalCreateUser([]);
232 $this->drupalLogin($web_user);
234 $this->drupalGet('user/login');
235 // Check that we got to 'user'.
236 $this->assertUrl($this->loggedInUser->url('canonical', ['absolute' => TRUE]));
238 // user/register should redirect to user/UID/edit.
239 $this->drupalGet('user/register');
240 $this->assertUrl($this->loggedInUser->url('edit-form', ['absolute' => TRUE]));
244 * Tests theme integration.
246 public function testThemeIntegration() {
247 $this->defaultTheme = 'bartik';
248 $this->adminTheme = 'seven';
250 $theme_handler = $this->container->get('theme_handler');
251 $theme_handler->install([$this->defaultTheme, $this->adminTheme]);
252 $this->config('system.theme')
253 ->set('default', $this->defaultTheme)
254 ->set('admin', $this->adminTheme)
257 $this->doTestThemeCallbackMaintenanceMode();
259 $this->doTestThemeCallbackFakeTheme();
261 $this->doTestThemeCallbackAdministrative();
263 $this->doTestThemeCallbackNoThemeRequested();
265 $this->doTestThemeCallbackOptionalTheme();
269 * Test the theme negotiation when it is set to use an administrative theme.
271 protected function doTestThemeCallbackAdministrative() {
272 $this->drupalGet('menu-test/theme-callback/use-admin-theme');
273 $this->assertText('Active theme: seven. Actual theme: seven.', 'The administrative theme can be correctly set in a theme negotiation.');
274 $this->assertRaw('seven/css/base/elements.css', "The administrative theme's CSS appears on the page.");
278 * Test the theme negotiation when the site is in maintenance mode.
280 protected function doTestThemeCallbackMaintenanceMode() {
281 $this->container->get('state')->set('system.maintenance_mode', TRUE);
283 // For a regular user, the fact that the site is in maintenance mode means
284 // we expect the theme callback system to be bypassed entirely.
285 $this->drupalGet('menu-test/theme-callback/use-admin-theme');
286 $this->assertRaw('bartik/css/base/elements.css', "The maintenance theme's CSS appears on the page.");
288 // An administrator, however, should continue to see the requested theme.
289 $admin_user = $this->drupalCreateUser(['access site in maintenance mode']);
290 $this->drupalLogin($admin_user);
291 $this->drupalGet('menu-test/theme-callback/use-admin-theme');
292 $this->assertText('Active theme: seven. Actual theme: seven.', 'The theme negotiation system is correctly triggered for an administrator when the site is in maintenance mode.');
293 $this->assertRaw('seven/css/base/elements.css', "The administrative theme's CSS appears on the page.");
295 $this->container->get('state')->set('system.maintenance_mode', FALSE);
299 * Test the theme negotiation when it is set to use an optional theme.
301 protected function doTestThemeCallbackOptionalTheme() {
302 // Request a theme that is not installed.
303 $this->drupalGet('menu-test/theme-callback/use-test-theme');
304 $this->assertText('Active theme: bartik. Actual theme: bartik.', 'The theme negotiation system falls back on the default theme when a theme that is not installed is requested.');
305 $this->assertRaw('bartik/css/base/elements.css', "The default theme's CSS appears on the page.");
307 // Now install the theme and request it again.
308 $theme_handler = $this->container->get('theme_handler');
309 $theme_handler->install(['test_theme']);
311 $this->drupalGet('menu-test/theme-callback/use-test-theme');
312 $this->assertText('Active theme: test_theme. Actual theme: test_theme.', 'The theme negotiation system uses an optional theme once it has been installed.');
313 $this->assertRaw('test_theme/kitten.css', "The optional theme's CSS appears on the page.");
315 $theme_handler->uninstall(['test_theme']);
319 * Test the theme negotiation when it is set to use a theme that does not exist.
321 protected function doTestThemeCallbackFakeTheme() {
322 $this->drupalGet('menu-test/theme-callback/use-fake-theme');
323 $this->assertText('Active theme: bartik. Actual theme: bartik.', 'The theme negotiation system falls back on the default theme when a theme that does not exist is requested.');
324 $this->assertRaw('bartik/css/base/elements.css', "The default theme's CSS appears on the page.");
328 * Test the theme negotiation when no theme is requested.
330 protected function doTestThemeCallbackNoThemeRequested() {
331 $this->drupalGet('menu-test/theme-callback/no-theme-requested');
332 $this->assertText('Active theme: bartik. Actual theme: bartik.', 'The theme negotiation system falls back on the default theme when no theme is requested.');
333 $this->assertRaw('bartik/css/base/elements.css', "The default theme's CSS appears on the page.");