MenuRouterTest.php 17.7 KB
Newer Older
1
2
3
4
<?php

/**
 * @file
5
 * Definition of Drupal\system\Tests\Menu\MenuRouterTest.
6
7
8
9
10
11
12
 */

namespace Drupal\system\Tests\Menu;

use Drupal\simpletest\WebTestBase;

/**
13
 * Tests menu router and default menu link functionality.
14
15
 *
 * @group Menu
16
 */
17
class MenuRouterTest extends WebTestBase {
18
19
20
21
22
23

  /**
   * Modules to enable.
   *
   * @var array
   */
24
  public static $modules = array('block', 'menu_test', 'test_page_test');
25

26
27
28
29
30
31
32
33
34
35
36
37
38
39
  /**
   * Name of the administrative theme to use for tests.
   *
   * @var string
   */
  protected $admin_theme;

  /**
   * Name of the default theme to use for tests.
   *
   * @var string
   */
  protected $default_theme;

40
41
  function setUp() {
    // Enable dummy module that implements hook_menu.
42
    parent::setUp();
43

44
    $this->drupalPlaceBlock('system_menu_block:tools');
45
46
  }

47
48
49
50
51
52
53
54
  /**
   * Tests menu integration.
   */
  public function testMenuIntegration() {
    $this->doTestTitleMenuCallback();
    $this->doTestMenuOptionalPlaceholders();
    $this->doTestMenuOnRoute();
    $this->doTestMenuName();
55
    $this->doTestMenuLinkDefaultsAlter();
56
57
58
59
60
61
62
63
    $this->doTestMenuItemTitlesCases();
    $this->doTestMenuLinkMaintain();
    $this->doTestMenuLinkOptions();
    $this->doTestMenuItemHooks();
    $this->doTestHookMenuIntegration();
    $this->doTestExoticPath();
  }

64
65
66
  /**
   * Test local tasks with route placeholders.
   */
67
  protected function doTestHookMenuIntegration() {
68
69
70
71
72
73
74
75
76
77
78
79
80
    // Generate base path with random argument.
    $base_path = 'foo/' . $this->randomName(8);
    $this->drupalGet($base_path);
    // Confirm correct controller activated.
    $this->assertText('test1');
    // Confirm local task links are displayed.
    $this->assertLink('Local task A');
    $this->assertLink('Local task B');
    // Confirm correct local task href.
    $this->assertLinkByHref(url($base_path));
    $this->assertLinkByHref(url($base_path . '/b'));
  }

81
82
83
  /**
   * Test title callback set to FALSE.
   */
84
  protected function doTestTitleCallbackFalse() {
85
    $this->drupalGet('test-page');
86
87
    $this->assertText('A title with @placeholder', 'Raw text found on the page');
    $this->assertNoText(t('A title with @placeholder', array('@placeholder' => 'some other text')), 'Text with placeholder substitutions not found.');
88
89
90
91
92
  }

  /**
   * Tests page title of MENU_CALLBACKs.
   */
93
  protected function doTestTitleMenuCallback() {
94
95
96
97
98
99
100
101
    // Verify that the menu router item title is not visible.
    $this->drupalGet('');
    $this->assertNoText(t('Menu Callback Title'));
    // Verify that the menu router item title is output as page title.
    $this->drupalGet('menu_callback_title');
    $this->assertText(t('Menu Callback Title'));
  }

102
103
104
  /**
   * Tests menu item descriptions.
   */
105
  protected function doTestDescriptionMenuItems() {
106
107
108
109
110
    // Verify that the menu router item title is output as page title.
    $this->drupalGet('menu_callback_description');
    $this->assertText(t('Menu item description text'));
  }

111
112
113
  /**
   * Tests for menu_link_maintain().
   */
114
  protected function doTestMenuLinkMaintain() {
115
    $admin_user = $this->drupalCreateUser(array('administer site configuration'));
116
117
118
119
120
121
122
123
    $this->drupalLogin($admin_user);

    // Create three menu items.
    menu_link_maintain('menu_test', 'insert', 'menu_test_maintain/1', 'Menu link #1');
    menu_link_maintain('menu_test', 'insert', 'menu_test_maintain/1', 'Menu link #1-main');
    menu_link_maintain('menu_test', 'insert', 'menu_test_maintain/2', 'Menu link #2');

    // Move second link to the main-menu, to test caching later on.
124
125
126
127
128
    $menu_links_to_update = entity_load_multiple_by_properties('menu_link', array('link_title' => 'Menu link #1-main', 'customized' => 0, 'module' => 'menu_test'));
    foreach ($menu_links_to_update as $menu_link) {
      $menu_link->menu_name = 'main';
      $menu_link->save();
    }
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155

    // Load front page.
    $this->drupalGet('');
    $this->assertLink('Menu link #1');
    $this->assertLink('Menu link #1-main');
    $this->assertLink('Menu link #2');

    // Rename all links for the given path.
    menu_link_maintain('menu_test', 'update', 'menu_test_maintain/1', 'Menu link updated');
    // Load a different page to be sure that we have up to date information.
    $this->drupalGet('menu_test_maintain/1');
    $this->assertLink('Menu link updated');
    $this->assertNoLink('Menu link #1');
    $this->assertNoLink('Menu link #1-main');
    $this->assertLink('Menu link #2');

    // Delete all links for the given path.
    menu_link_maintain('menu_test', 'delete', 'menu_test_maintain/1', '');
    // Load a different page to be sure that we have up to date information.
    $this->drupalGet('menu_test_maintain/2');
    $this->assertNoLink('Menu link updated');
    $this->assertNoLink('Menu link #1');
    $this->assertNoLink('Menu link #1-main');
    $this->assertLink('Menu link #2');
  }

  /**
156
   * Tests for menu_name parameter for default menu links.
157
   */
158
  protected function doTestMenuName() {
159
160
161
    $admin_user = $this->drupalCreateUser(array('administer site configuration'));
    $this->drupalLogin($admin_user);

162
    $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu_name_test'));
163
164
    $menu_link = reset($menu_links);
    $this->assertEqual($menu_link->menu_name, 'original', 'Menu name is "original".');
165
166
167
168

    // Change the menu_name parameter in menu_test.module, then force a menu
    // rebuild.
    menu_test_menu_name('changed');
169
    \Drupal::service('router.builder')->rebuild();
170

171
    $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu_name_test'));
172
173
    $menu_link = reset($menu_links);
    $this->assertEqual($menu_link->menu_name, 'changed', 'Menu name was successfully changed after rebuild.');
174
175
  }

176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
  /**
   * Tests menu links added in hook_menu_link_defaults_alter().
   */
  protected function doTestMenuLinkDefaultsAlter() {
    // Check that machine name does not need to be defined since it is already
    // set as the key of each menu link.
    $menu_links = entity_load_multiple_by_properties('menu_link', array('route_name' => 'menu_test.custom'));
    $menu_link = reset($menu_links);
    $this->assertEqual($menu_link->machine_name, 'menu_test.custom', 'Menu links added at hook_menu_link_defaults_alter() obtain the machine name from the $links key.');
    // Make sure that rebuilding the menu tree does not produce duplicates of
    // links added by hook_menu_link_defaults_alter().
    \Drupal::service('router.builder')->rebuild();
    $this->drupalGet('menu-test');
    $this->assertUniqueText('Custom link', 'Menu links added by hook_menu_link_defaults_alter() do not duplicate after a menu rebuild.');
  }

192
193
194
  /**
   * Tests for menu hierarchy.
   */
195
  protected function doTestMenuHierarchy() {
196
197
198
199
200
201
    $parent_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu-test/hierarchy/parent'));
    $parent_link = reset($parent_links);
    $child_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu-test/hierarchy/parent/child'));
    $child_link = reset($child_links);
    $unattached_child_links = entity_load_multiple_by_properties('menu_link', array('link_path' => 'menu-test/hierarchy/parent/child2/child'));
    $unattached_child_link = reset($unattached_child_links);
202

203
204
    $this->assertEqual($child_link['plid'], $parent_link['mlid'], 'The parent of a directly attached child is correct.');
    $this->assertEqual($unattached_child_link['plid'], $parent_link['mlid'], 'The parent of a non-directly attached child is correct.');
205
206
207
208
209
  }

  /**
   * Test menu maintenance hooks.
   */
210
  protected function doTestMenuItemHooks() {
211
212
    // Create an item.
    menu_link_maintain('menu_test', 'insert', 'menu_test_maintain/4', 'Menu link #4');
213
    $this->assertEqual(menu_test_static_variable(), 'insert', 'hook_menu_link_insert() fired correctly');
214
215
    // Update the item.
    menu_link_maintain('menu_test', 'update', 'menu_test_maintain/4', 'Menu link updated');
216
    $this->assertEqual(menu_test_static_variable(), 'update', 'hook_menu_link_update() fired correctly');
217
218
    // Delete the item.
    menu_link_maintain('menu_test', 'delete', 'menu_test_maintain/4', '');
219
    $this->assertEqual(menu_test_static_variable(), 'delete', 'hook_menu_link_delete() fired correctly');
220
221
222
223
224
  }

  /**
   * Test menu link 'options' storage and rendering.
   */
225
  protected function doTestMenuLinkOptions() {
226
    // Create a menu link with options.
227
    $menu_link = entity_create('menu_link', array(
228
      'link_title' => 'Menu link options test',
229
      'link_path' => 'test-page',
230
231
232
233
234
235
236
237
238
      'module' => 'menu_test',
      'options' => array(
        'attributes' => array(
          'title' => 'Test title attribute',
        ),
        'query' => array(
          'testparam' => 'testvalue',
        ),
      ),
239
    ));
240
241
242
    menu_link_save($menu_link);

    // Load front page.
243
    $this->drupalGet('test-page');
244
245
    $this->assertRaw('title="Test title attribute"', 'Title attribute of a menu link renders.');
    $this->assertRaw('testparam=testvalue', 'Query parameter added to menu link.');
246
247
248
249
250
251
  }

  /**
   * Tests the possible ways to set the title for menu items.
   * Also tests that menu item titles work with string overrides.
   */
252
  protected function doTestMenuItemTitlesCases() {
253
254
255
256

    // Build array with string overrides.
    $test_data = array(
      1 => array('Example title - Case 1' => 'Alternative example title - Case 1'),
257
      2 => array('Example title' => 'Alternative example title'),
258
259
260
261
262
      3 => array('Example title' => 'Alternative example title'),
    );

    foreach ($test_data as $case_no => $override) {
      $this->menuItemTitlesCasesHelper($case_no);
263
264
265
      $this->addCustomTranslations('en', array('' => $override));
      $this->writeCustomTranslations();

266
      $this->menuItemTitlesCasesHelper($case_no, TRUE);
267
268
      $this->addCustomTranslations('en', array());
      $this->writeCustomTranslations();
269
270
271
272
    }
  }

  /**
273
   * Get a URL and assert the title given a case number. If override is true,
274
275
   * the title is asserted to begin with "Alternative".
   */
276
  protected function menuItemTitlesCasesHelper($case_no, $override = FALSE) {
277
278
279
    $this->drupalGet('menu-title-test/case' . $case_no);
    $this->assertResponse(200);
    $asserted_title = $override ? 'Alternative example title - Case ' . $case_no : 'Example title - Case ' . $case_no;
280
    $this->assertTitle($asserted_title . ' | Drupal', format_string('Menu title is: %title.', array('%title' => $asserted_title)), 'Menu');
281
282
283
284
285
286
287
288
289
  }

  /**
   * Load the router for a given path.
   */
  protected function menuLoadRouter($router_path) {
    return db_query('SELECT * FROM {menu_router} WHERE path = :path', array(':path' => $router_path))->fetchAssoc();
  }

290
291
292
  /**
   * Test menu links that have optional placeholders.
   */
293
  protected function doTestMenuOptionalPlaceholders() {
294
295
296
297
298
299
300
301
302
    $this->drupalGet('menu-test/optional');
    $this->assertResponse(200);
    $this->assertText('Sometimes there is no placeholder.');

    $this->drupalGet('menu-test/optional/foobar');
    $this->assertResponse(200);
    $this->assertText("Sometimes there is a placeholder: 'foobar'.");
  }

303
304
305
  /**
   * Tests a menu on a router page.
   */
306
  protected function doTestMenuOnRoute() {
307
    \Drupal::moduleHandler()->install(array('router_test'));
308
    \Drupal::service('router.builder')->rebuild();
309
    $this->resetAll();
310
311
312
313
314
315
316
317

    $this->drupalGet('router_test/test2');
    $this->assertLinkByHref('menu_no_title_callback');
    $this->assertLinkByHref('menu-title-test/case1');
    $this->assertLinkByHref('menu-title-test/case2');
    $this->assertLinkByHref('menu-title-test/case3');
  }

318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
  /**
   * Test path containing "exotic" characters.
   */
  protected function doTestExoticPath() {
    $path = "menu-test/ -._~!$'\"()*@[]?&+%#,;=:" . // "Special" ASCII characters.
      "%23%25%26%2B%2F%3F" . // Characters that look like a percent-escaped string.
      "éøïвβ中國書۞"; // Characters from various non-ASCII alphabets.
    $this->drupalGet($path);
    $this->assertRaw('This is menu_test_callback().');
  }

  /**
   * Make sure the maintenance mode can be bypassed using an EventSubscriber.
   *
   * @see \Drupal\menu_test\EventSubscriber\MaintenanceModeSubscriber::onKernelRequestMaintenance().
   */
  public function testMaintenanceModeLoginPaths() {
    $this->container->get('state')->set('system.maintenance_mode', TRUE);

    $offline_message = t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => \Drupal::config('system.site')->get('name')));
    $this->drupalGet('test-page');
    $this->assertText($offline_message);
    $this->drupalGet('menu_login_callback');
    $this->assertText('This is TestControllers::testLogin.', 'Maintenance mode can be bypassed using an event subscriber.');

    $this->container->get('state')->set('system.maintenance_mode', FALSE);
  }

  /**
   * Test that an authenticated user hitting 'user/login' gets redirected to
   * 'user' and 'user/register' gets redirected to the user edit page.
   */
  public function testAuthUserUserLogin() {
    $web_user = $this->drupalCreateUser(array());
    $this->drupalLogin($web_user);

    $this->drupalGet('user/login');
    // Check that we got to 'user'.
    $this->assertTrue($this->url == url('user/' . $this->loggedInUser->id(), array('absolute' => TRUE)), "Logged-in user redirected to user on accessing user/login");

    // user/register should redirect to user/UID/edit.
    $this->drupalGet('user/register');
    $this->assertTrue($this->url == url('user/' . $this->loggedInUser->id() . '/edit', array('absolute' => TRUE)), "Logged-in user redirected to user/UID/edit on accessing user/register");
  }

  /**
   * Tests theme integration.
   */
  public function testThemeIntegration() {
367
368
369
370
371
372
373
374
375
376
377
    $this->default_theme = 'bartik';
    $this->admin_theme = 'seven';

    $theme_handler = $this->container->get('theme_handler');
    $theme_handler->enable(array($this->default_theme, $this->admin_theme));
    $this->container->get('config.factory')->get('system.theme')
      ->set('default', $this->default_theme)
      ->set('admin', $this->admin_theme)
      ->save();
    $theme_handler->disable(array('stark'));

378
379
380
381
382
383
384
385
386
387
388
389
    $this->doTestThemeCallbackMaintenanceMode();

    $this->doTestThemeCallbackFakeTheme();

    $this->doTestThemeCallbackAdministrative();

    $this->doTestThemeCallbackNoThemeRequested();

    $this->doTestThemeCallbackOptionalTheme();
  }

  /**
390
   * Test the theme negotiation when it is set to use an administrative theme.
391
392
393
   */
  protected function doTestThemeCallbackAdministrative() {
    $this->drupalGet('menu-test/theme-callback/use-admin-theme');
394
    $this->assertText('Active theme: seven. Actual theme: seven.', 'The administrative theme can be correctly set in a theme negotiation.');
395
    $this->assertRaw('seven/css/style.css', "The administrative theme's CSS appears on the page.");
396
397
398
  }

  /**
399
   * Test the theme negotiation when the site is in maintenance mode.
400
401
402
403
404
405
406
407
408
409
410
411
412
   */
  protected function doTestThemeCallbackMaintenanceMode() {
    $this->container->get('state')->set('system.maintenance_mode', TRUE);

    // For a regular user, the fact that the site is in maintenance mode means
    // we expect the theme callback system to be bypassed entirely.
    $this->drupalGet('menu-test/theme-callback/use-admin-theme');
    $this->assertRaw('bartik/css/style.css', "The maintenance theme's CSS appears on the page.");

    // An administrator, however, should continue to see the requested theme.
    $admin_user = $this->drupalCreateUser(array('access site in maintenance mode'));
    $this->drupalLogin($admin_user);
    $this->drupalGet('menu-test/theme-callback/use-admin-theme');
413
    $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.');
414
    $this->assertRaw('seven/css/style.css', "The administrative theme's CSS appears on the page.");
415
416
417
418
419

    $this->container->get('state')->set('system.maintenance_mode', FALSE);
  }

  /**
420
   * Test the theme negotiation when it is set to use an optional theme.
421
422
423
424
   */
  protected function doTestThemeCallbackOptionalTheme() {
    // Request a theme that is not enabled.
    $this->drupalGet('menu-test/theme-callback/use-stark-theme');
425
    $this->assertText('Active theme: bartik. Actual theme: bartik.', 'The theme negotiation system falls back on the default theme when a theme that is not enabled is requested.');
426
427
428
    $this->assertRaw('bartik/css/style.css', "The default theme's CSS appears on the page.");

    // Now enable the theme and request it again.
429
430
431
    $theme_handler = $this->container->get('theme_handler');
    $theme_handler->enable(array('stark'));

432
    $this->drupalGet('menu-test/theme-callback/use-stark-theme');
433
    $this->assertText('Active theme: stark. Actual theme: stark.', 'The theme negotiation system uses an optional theme once it has been enabled.');
434
    $this->assertRaw('stark/css/layout.css', "The optional theme's CSS appears on the page.");
435
436

    $theme_handler->disable(array('stark'));
437
438
439
  }

  /**
440
   * Test the theme negotiation when it is set to use a theme that does not exist.
441
442
443
   */
  protected function doTestThemeCallbackFakeTheme() {
    $this->drupalGet('menu-test/theme-callback/use-fake-theme');
444
    $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.');
445
446
447
448
    $this->assertRaw('bartik/css/style.css', "The default theme's CSS appears on the page.");
  }

  /**
449
   * Test the theme negotiation when no theme is requested.
450
451
452
   */
  protected function doTestThemeCallbackNoThemeRequested() {
    $this->drupalGet('menu-test/theme-callback/no-theme-requested');
453
    $this->assertText('Active theme: bartik. Actual theme: bartik.', 'The theme negotiation system falls back on the default theme when no theme is requested.');
454
455
456
    $this->assertRaw('bartik/css/style.css', "The default theme's CSS appears on the page.");
  }

457
}