drupalGet($goto); } // Compare paths with actual breadcrumb. $parts = $this->getParts(); $pass = TRUE; foreach ($trail as $path => $title) { $url = url($path); $part = array_shift($parts); $pass = ($pass && $part['href'] === $url && $part['text'] === check_plain($title)); } // No parts must be left, or an expected "Home" will always pass. $pass = ($pass && empty($parts)); $this->assertTrue($pass, format_string('Breadcrumb %parts found on @path.', array( '%parts' => implode(' » ', $trail), '@path' => $this->getUrl(), ))); // Additionally assert page title, if given. if (isset($page_title)) { $this->assertTitle(strtr('@title | Drupal', array('@title' => $page_title))); } // Additionally assert active trail in a menu tree output, if given. if ($tree) { end($tree); $active_link_path = key($tree); $active_link_title = array_pop($tree); $xpath = ''; if ($tree) { $i = 0; foreach ($tree as $link_path => $link_title) { $part_xpath = (!$i ? '//' : '/following-sibling::ul/descendant::'); $part_xpath .= 'li[contains(@class, :class)]/a[contains(@href, :href) and contains(text(), :title)]'; $part_args = array( ':class' => 'active-trail', ':href' => url($link_path), ':title' => $link_title, ); $xpath .= $this->buildXPathQuery($part_xpath, $part_args); $i++; } $elements = $this->xpath($xpath); $this->assertTrue(!empty($elements), 'Active trail to current page was found in menu tree.'); // Append prefix for active link asserted below. $xpath .= '/following-sibling::ul/descendant::'; } else { $xpath .= '//'; } $xpath_last_active = ($last_active ? 'and contains(@class, :class-active)' : ''); $xpath .= 'li[contains(@class, :class-trail)]/a[contains(@href, :href) ' . $xpath_last_active . 'and contains(text(), :title)]'; $args = array( ':class-trail' => 'active-trail', ':class-active' => 'active', ':href' => url($active_link_path), ':title' => $active_link_title, ); $elements = $this->xpath($xpath, $args); $this->assertTrue(!empty($elements), format_string('Active link %title was found in menu tree, including active trail links %tree.', array( '%title' => $active_link_title, '%tree' => implode(' » ', $tree), ))); } } /** * Returns the breadcrumb contents of the current page in the internal browser. */ protected function getParts() { $parts = array(); $elements = $this->xpath('//div[@class="breadcrumb"]/a'); if (!empty($elements)) { foreach ($elements as $element) { $parts[] = array( 'text' => (string) $element, 'href' => (string) $element['href'], 'title' => (string) $element['title'], ); } } return $parts; } } class MenuRouterTestCase extends DrupalWebTestCase { public static function getInfo() { return array( 'name' => 'Menu router', 'description' => 'Tests menu router and hook_menu() functionality.', 'group' => 'Menu', ); } function setUp() { // Enable dummy module that implements hook_menu. parent::setUp('menu_test'); // Make the tests below more robust by explicitly setting the default theme // and administrative theme that they expect. theme_enable(array('bartik')); variable_set('theme_default', 'bartik'); variable_set('admin_theme', 'seven'); } /** * Test title callback set to FALSE. */ function testTitleCallbackFalse() { $this->drupalGet('node'); $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.'); } /** * Tests page title of MENU_CALLBACKs. */ function testTitleMenuCallback() { // 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')); } /** * Test the theme callback when it is set to use an administrative theme. */ function testThemeCallbackAdministrative() { $this->drupalGet('menu-test/theme-callback/use-admin-theme'); $this->assertText('Custom theme: seven. Actual theme: seven.', 'The administrative theme can be correctly set in a theme callback.'); $this->assertRaw('seven/style.css', "The administrative theme's CSS appears on the page."); } /** * Test that the theme callback is properly inherited. */ function testThemeCallbackInheritance() { $this->drupalGet('menu-test/theme-callback/use-admin-theme/inheritance'); $this->assertText('Custom theme: seven. Actual theme: seven. Theme callback inheritance is being tested.', 'Theme callback inheritance correctly uses the administrative theme.'); $this->assertRaw('seven/style.css', "The administrative theme's CSS appears on the page."); } /** * Test that 'page callback', 'file' and 'file path' keys are properly * inherited from parent menu paths. */ function testFileInheritance() { $this->drupalGet('admin/config/development/file-inheritance'); $this->assertText('File inheritance test description', 'File inheritance works.'); } /** * Test path containing "exotic" characters. */ function testExoticPath() { $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().'); } /** * Test the theme callback when the site is in maintenance mode. */ function testThemeCallbackMaintenanceMode() { variable_set('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'); $this->assertText('Custom theme: seven. Actual theme: seven.', 'The theme callback system is correctly triggered for an administrator when the site is in maintenance mode.'); $this->assertRaw('seven/style.css', "The administrative theme's CSS appears on the page."); } /** * Make sure the maintenance mode can be bypassed using hook_menu_site_status_alter(). * * @see hook_menu_site_status_alter(). */ function testMaintenanceModeLoginPaths() { variable_set('maintenance_mode', TRUE); $offline_message = t('@site is currently under maintenance. We should be back shortly. Thank you for your patience.', array('@site' => variable_get('site_name', 'Drupal'))); $this->drupalLogout(); $this->drupalGet('node'); $this->assertText($offline_message); $this->drupalGet('menu_login_callback'); $this->assertText('This is menu_login_callback().', t('Maintenance mode can be bypassed through hook_login_paths().')); } /** * Test that an authenticated user hitting 'user/login' gets redirected to * 'user' and 'user/register' gets redirected to the user edit page. */ function testAuthUserUserLogin() { $loggedInUser = $this->drupalCreateUser(array()); $this->drupalLogin($loggedInUser); $this->drupalGet('user/login'); // Check that we got to 'user'. $this->assertTrue($this->url == url('user', array('absolute' => TRUE)), "Logged-in user redirected to q=user on accessing q=user/login"); // user/register should redirect to user/UID/edit. $this->drupalGet('user/register'); $this->assertTrue($this->url == url('user/' . $this->loggedInUser->uid . '/edit', array('absolute' => TRUE)), "Logged-in user redirected to q=user/UID/edit on accessing q=user/register"); } /** * Test the theme callback when it is set to use an optional theme. */ function testThemeCallbackOptionalTheme() { // Request a theme that is not enabled. $this->drupalGet('menu-test/theme-callback/use-stark-theme'); $this->assertText('Custom theme: NONE. Actual theme: bartik.', 'The theme callback system falls back on the default theme when a theme that is not enabled is requested.'); $this->assertRaw('bartik/css/style.css', "The default theme's CSS appears on the page."); // Now enable the theme and request it again. theme_enable(array('stark')); $this->drupalGet('menu-test/theme-callback/use-stark-theme'); $this->assertText('Custom theme: stark. Actual theme: stark.', 'The theme callback system uses an optional theme once it has been enabled.'); $this->assertRaw('stark/layout.css', "The optional theme's CSS appears on the page."); } /** * Test the theme callback when it is set to use a theme that does not exist. */ function testThemeCallbackFakeTheme() { $this->drupalGet('menu-test/theme-callback/use-fake-theme'); $this->assertText('Custom theme: NONE. Actual theme: bartik.', 'The theme callback system falls back on the default theme when a theme that does not exist is requested.'); $this->assertRaw('bartik/css/style.css', "The default theme's CSS appears on the page."); } /** * Test the theme callback when no theme is requested. */ function testThemeCallbackNoThemeRequested() { $this->drupalGet('menu-test/theme-callback/no-theme-requested'); $this->assertText('Custom theme: NONE. Actual theme: bartik.', 'The theme callback system falls back on the default theme when no theme is requested.'); $this->assertRaw('bartik/css/style.css', "The default theme's CSS appears on the page."); } /** * Test that hook_custom_theme() can control the theme of a page. */ function testHookCustomTheme() { // Trigger hook_custom_theme() to dynamically request the Stark theme for // the requested page. variable_set('menu_test_hook_custom_theme_name', 'stark'); theme_enable(array('stark')); // Visit a page that does not implement a theme callback. The above request // should be honored. $this->drupalGet('menu-test/no-theme-callback'); $this->assertText('Custom theme: stark. Actual theme: stark.', 'The result of hook_custom_theme() is used as the theme for the current page.'); $this->assertRaw('stark/layout.css', "The Stark theme's CSS appears on the page."); } /** * Test that the theme callback wins out over hook_custom_theme(). */ function testThemeCallbackHookCustomTheme() { // Trigger hook_custom_theme() to dynamically request the Stark theme for // the requested page. variable_set('menu_test_hook_custom_theme_name', 'stark'); theme_enable(array('stark')); // The menu "theme callback" should take precedence over a value set in // hook_custom_theme(). $this->drupalGet('menu-test/theme-callback/use-admin-theme'); $this->assertText('Custom theme: seven. Actual theme: seven.', 'The result of hook_custom_theme() does not override what was set in a theme callback.'); $this->assertRaw('seven/style.css', "The Seven theme's CSS appears on the page."); } /** * Tests for menu_link_maintain(). */ function testMenuLinkMaintain() { $admin_user = $this->drupalCreateUser(array('administer site configuration')); $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-1'); 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. db_update('menu_links') ->fields(array('menu_name' => 'main-menu')) ->condition('link_title', 'Menu link #1-1') ->condition('customized', 0) ->condition('module', 'menu_test') ->execute(); menu_cache_clear('main-menu'); // Load front page. $this->drupalGet('node'); $this->assertLink(t('Menu link #1'), 0, 'Found menu link #1'); $this->assertLink(t('Menu link #1-1'), 0, 'Found menu link #1-1'); $this->assertLink(t('Menu link #2'), 0, 'Found 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(t('Menu link updated'), 0, 'Found updated menu link'); $this->assertNoLink(t('Menu link #1'), 0, 'Not found menu link #1'); $this->assertNoLink(t('Menu link #1'), 0, 'Not found menu link #1-1'); $this->assertLink(t('Menu link #2'), 0, 'Found 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(t('Menu link updated'), 0, 'Not found deleted menu link'); $this->assertNoLink(t('Menu link #1'), 0, 'Not found menu link #1'); $this->assertNoLink(t('Menu link #1'), 0, 'Not found menu link #1-1'); $this->assertLink(t('Menu link #2'), 0, 'Found menu link #2'); } /** * Test menu_get_names(). */ function testMenuGetNames() { // Create three menu items. for ($i = 0; $i < 3; $i++) { $menu_link = array( 'link_title' => 'Menu link #' . $i, 'link_path' => 'menu_test/' . $i, 'module' => 'menu_test', 'menu_name' => 'menu_test_' . $i, ); menu_link_save($menu_link); } drupal_static_reset('menu_get_names'); // Verify that the menu names are correctly reported by menu_get_names(). $menu_names = menu_get_names(); $this->pass(implode(' | ', $menu_names)); for ($i = 0; $i < 3; $i++) { $this->assertTrue(in_array('menu_test_' . $i, $menu_names), t('Expected menu name %expected is returned.', array('%expected' => 'menu_test_' . $i))); } } /** * Tests for menu_name parameter for hook_menu(). */ function testMenuName() { $admin_user = $this->drupalCreateUser(array('administer site configuration')); $this->drupalLogin($admin_user); $sql = "SELECT menu_name FROM {menu_links} WHERE router_path = 'menu_name_test'"; $name = db_query($sql)->fetchField(); $this->assertEqual($name, 'original', 'Menu name is "original".'); // Change the menu_name parameter in menu_test.module, then force a menu // rebuild. menu_test_menu_name('changed'); menu_rebuild(); $sql = "SELECT menu_name FROM {menu_links} WHERE router_path = 'menu_name_test'"; $name = db_query($sql)->fetchField(); $this->assertEqual($name, 'changed', 'Menu name was successfully changed after rebuild.'); } /** * Tests for menu hierarchy. */ function testMenuHierarchy() { $parent_link = db_query('SELECT * FROM {menu_links} WHERE link_path = :link_path', array(':link_path' => 'menu-test/hierarchy/parent'))->fetchAssoc(); $child_link = db_query('SELECT * FROM {menu_links} WHERE link_path = :link_path', array(':link_path' => 'menu-test/hierarchy/parent/child'))->fetchAssoc(); $unattached_child_link = db_query('SELECT * FROM {menu_links} WHERE link_path = :link_path', array(':link_path' => 'menu-test/hierarchy/parent/child2/child'))->fetchAssoc(); $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.'); } /** * Tests menu link depth and parents of local tasks and menu callbacks. */ function testMenuHidden() { // Verify links for one dynamic argument. $links = db_select('menu_links', 'ml') ->fields('ml') ->condition('ml.router_path', 'menu-test/hidden/menu%', 'LIKE') ->orderBy('ml.router_path') ->execute() ->fetchAllAssoc('router_path', PDO::FETCH_ASSOC); $parent = $links['menu-test/hidden/menu']; $depth = $parent['depth'] + 1; $plid = $parent['mlid']; $link = $links['menu-test/hidden/menu/list']; $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid))); $link = $links['menu-test/hidden/menu/add']; $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid))); $link = $links['menu-test/hidden/menu/settings']; $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid))); $link = $links['menu-test/hidden/menu/manage/%']; $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid))); $parent = $links['menu-test/hidden/menu/manage/%']; $depth = $parent['depth'] + 1; $plid = $parent['mlid']; $link = $links['menu-test/hidden/menu/manage/%/list']; $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid))); $link = $links['menu-test/hidden/menu/manage/%/add']; $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid))); $link = $links['menu-test/hidden/menu/manage/%/edit']; $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid))); $link = $links['menu-test/hidden/menu/manage/%/delete']; $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid))); // Verify links for two dynamic arguments. $links = db_select('menu_links', 'ml') ->fields('ml') ->condition('ml.router_path', 'menu-test/hidden/block%', 'LIKE') ->orderBy('ml.router_path') ->execute() ->fetchAllAssoc('router_path', PDO::FETCH_ASSOC); $parent = $links['menu-test/hidden/block']; $depth = $parent['depth'] + 1; $plid = $parent['mlid']; $link = $links['menu-test/hidden/block/list']; $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid))); $link = $links['menu-test/hidden/block/add']; $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid))); $link = $links['menu-test/hidden/block/manage/%/%']; $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid))); $parent = $links['menu-test/hidden/block/manage/%/%']; $depth = $parent['depth'] + 1; $plid = $parent['mlid']; $link = $links['menu-test/hidden/block/manage/%/%/configure']; $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid))); $link = $links['menu-test/hidden/block/manage/%/%/delete']; $this->assertEqual($link['depth'], $depth, format_string('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, format_string('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid))); } /** * Test menu_get_item() with empty ancestors. */ function testMenuGetItemNoAncestors() { variable_set('menu_masks', array()); $this->drupalGet(''); } /** * Test menu_set_item(). */ function testMenuSetItem() { $item = menu_get_item('node'); $this->assertEqual($item['path'], 'node', "Path from menu_get_item('node') is equal to 'node'", 'menu'); // Modify the path for the item then save it. $item['path'] = 'node_test'; $item['href'] = 'node_test'; menu_set_item('node', $item); $compare_item = menu_get_item('node'); $this->assertEqual($compare_item, $item, 'Modified menu item is equal to newly retrieved menu item.', 'menu'); } /** * Test menu maintenance hooks. */ function testMenuItemHooks() { // Create an item. menu_link_maintain('menu_test', 'insert', 'menu_test_maintain/4', 'Menu link #4'); $this->assertEqual(menu_test_static_variable(), 'insert', 'hook_menu_link_insert() fired correctly'); // Update the item. menu_link_maintain('menu_test', 'update', 'menu_test_maintain/4', 'Menu link updated'); $this->assertEqual(menu_test_static_variable(), 'update', 'hook_menu_link_update() fired correctly'); // Delete the item. menu_link_maintain('menu_test', 'delete', 'menu_test_maintain/4', ''); $this->assertEqual(menu_test_static_variable(), 'delete', 'hook_menu_link_delete() fired correctly'); } /** * Test menu link 'options' storage and rendering. */ function testMenuLinkOptions() { // Create a menu link with options. $menu_link = array( 'link_title' => 'Menu link options test', 'link_path' => 'node', 'module' => 'menu_test', 'options' => array( 'attributes' => array( 'title' => 'Test title attribute', ), 'query' => array( 'testparam' => 'testvalue', ), ), ); menu_link_save($menu_link); // Load front page. $this->drupalGet('node'); $this->assertRaw('title="Test title attribute"', 'Title attribute of a menu link renders.'); $this->assertRaw('testparam=testvalue', 'Query parameter added to menu link.'); } /** * Tests the possible ways to set the title for menu items. * Also tests that menu item titles work with string overrides. */ function testMenuItemTitlesCases() { // Build array with string overrides. $test_data = array( 1 => array('Example title - Case 1' => 'Alternative example title - Case 1'), 2 => array('Example @sub1 - Case @op2' => 'Alternative example @sub1 - Case @op2'), 3 => array('Example title' => 'Alternative example title'), 4 => array('Example title' => 'Alternative example title'), ); foreach ($test_data as $case_no => $override) { $this->menuItemTitlesCasesHelper($case_no); variable_set('locale_custom_strings_en', array('' => $override)); $this->menuItemTitlesCasesHelper($case_no, TRUE); variable_set('locale_custom_strings_en', array()); } } /** * Get a URL and assert the title given a case number. If override is true, * the title is asserted to begin with "Alternative". */ private function menuItemTitlesCasesHelper($case_no, $override = FALSE) { $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; $this->assertTitle($asserted_title . ' | Drupal', format_string('Menu title is: %title.', array('%title' => $asserted_title)), 'Menu'); } /** * 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(); } /** * Tests inheritance of 'load arguments'. */ function testMenuLoadArgumentsInheritance() { $expected = array( 'menu-test/arguments/%/%' => array( 2 => array('menu_test_argument_load' => array(3)), 3 => NULL, ), // Arguments are inherited to normal children. 'menu-test/arguments/%/%/default' => array( 2 => array('menu_test_argument_load' => array(3)), 3 => NULL, ), // Arguments are inherited to tab children. 'menu-test/arguments/%/%/task' => array( 2 => array('menu_test_argument_load' => array(3)), 3 => NULL, ), // Arguments are only inherited to the same loader functions. 'menu-test/arguments/%/%/common-loader' => array( 2 => array('menu_test_argument_load' => array(3)), 3 => 'menu_test_other_argument_load', ), // Arguments are not inherited to children not using the same loader // function. 'menu-test/arguments/%/%/different-loaders-1' => array( 2 => NULL, 3 => 'menu_test_argument_load', ), 'menu-test/arguments/%/%/different-loaders-2' => array( 2 => 'menu_test_other_argument_load', 3 => NULL, ), 'menu-test/arguments/%/%/different-loaders-3' => array( 2 => NULL, 3 => NULL, ), // Explicit loader arguments should not be overriden by parent. 'menu-test/arguments/%/%/explicit-arguments' => array( 2 => array('menu_test_argument_load' => array()), 3 => NULL, ), ); foreach ($expected as $router_path => $load_functions) { $router_item = $this->menuLoadRouter($router_path); $this->assertIdentical(unserialize($router_item['load_functions']), $load_functions, format_string('Expected load functions for router %router_path' , array('%router_path' => $router_path))); } } } /** * Tests for menu links. */ class MenuLinksUnitTestCase extends DrupalWebTestCase { // Use the lightweight testing profile for this test. protected $profile = 'testing'; public static function getInfo() { return array( 'name' => 'Menu links', 'description' => 'Test handling of menu links hierarchies.', 'group' => 'Menu', ); } /** * Create a simple hierarchy of links. */ function createLinkHierarchy($module = 'menu_test') { // First remove all the menu links. db_truncate('menu_links')->execute(); // Then create a simple link hierarchy: // - $parent // - $child-1 // - $child-1-1 // - $child-1-2 // - $child-2 $base_options = array( 'link_title' => 'Menu link test', 'module' => $module, 'menu_name' => 'menu_test', ); $links['parent'] = $base_options + array( 'link_path' => 'menu-test/parent', ); menu_link_save($links['parent']); $links['child-1'] = $base_options + array( 'link_path' => 'menu-test/parent/child-1', 'plid' => $links['parent']['mlid'], ); menu_link_save($links['child-1']); $links['child-1-1'] = $base_options + array( 'link_path' => 'menu-test/parent/child-1/child-1-1', 'plid' => $links['child-1']['mlid'], ); menu_link_save($links['child-1-1']); $links['child-1-2'] = $base_options + array( 'link_path' => 'menu-test/parent/child-1/child-1-2', 'plid' => $links['child-1']['mlid'], ); menu_link_save($links['child-1-2']); $links['child-2'] = $base_options + array( 'link_path' => 'menu-test/parent/child-2', 'plid' => $links['parent']['mlid'], ); menu_link_save($links['child-2']); return $links; } /** * Assert that at set of links is properly parented. */ function assertMenuLinkParents($links, $expected_hierarchy) { foreach ($expected_hierarchy as $child => $parent) { $mlid = $links[$child]['mlid']; $plid = $parent ? $links[$parent]['mlid'] : 0; $menu_link = menu_link_load($mlid); menu_link_save($menu_link); $this->assertEqual($menu_link['plid'], $plid, format_string('Menu link %mlid has parent of %plid, expected %expected_plid.', array('%mlid' => $mlid, '%plid' => $menu_link['plid'], '%expected_plid' => $plid))); } } /** * Test automatic reparenting of menu links. */ function testMenuLinkReparenting($module = 'menu_test') { // Check the initial hierarchy. $links = $this->createLinkHierarchy($module); $expected_hierarchy = array( 'parent' => FALSE, 'child-1' => 'parent', 'child-1-1' => 'child-1', 'child-1-2' => 'child-1', 'child-2' => 'parent', ); $this->assertMenuLinkParents($links, $expected_hierarchy); // Start over, and move child-1 under child-2, and check that all the // childs of child-1 have been moved too. $links = $this->createLinkHierarchy($module); $links['child-1']['plid'] = $links['child-2']['mlid']; menu_link_save($links['child-1']); $expected_hierarchy = array( 'parent' => FALSE, 'child-1' => 'child-2', 'child-1-1' => 'child-1', 'child-1-2' => 'child-1', 'child-2' => 'parent', ); $this->assertMenuLinkParents($links, $expected_hierarchy); // Start over, and delete child-1, and check that the children of child-1 // have been reassigned to the parent. menu_link_delete() will cowardly // refuse to delete a menu link defined by the system module, so skip the // test in that case. if ($module != 'system') { $links = $this->createLinkHierarchy($module); menu_link_delete($links['child-1']['mlid']); $expected_hierarchy = array( 'parent' => FALSE, 'child-1-1' => 'parent', 'child-1-2' => 'parent', 'child-2' => 'parent', ); $this->assertMenuLinkParents($links, $expected_hierarchy); } // Start over, forcefully delete child-1 from the database, simulating a // database crash. Check that the children of child-1 have been reassigned // to the parent, going up on the old path hierarchy stored in each of the // links. $links = $this->createLinkHierarchy($module); // Don't do that at home. db_delete('menu_links') ->condition('mlid', $links['child-1']['mlid']) ->execute(); $expected_hierarchy = array( 'parent' => FALSE, 'child-1-1' => 'parent', 'child-1-2' => 'parent', 'child-2' => 'parent', ); $this->assertMenuLinkParents($links, $expected_hierarchy); // Start over, forcefully delete the parent from the database, simulating a // database crash. Check that the children of parent are now top-level. $links = $this->createLinkHierarchy($module); // Don't do that at home. db_delete('menu_links') ->condition('mlid', $links['parent']['mlid']) ->execute(); $expected_hierarchy = array( 'child-1-1' => 'child-1', 'child-1-2' => 'child-1', 'child-2' => FALSE, ); $this->assertMenuLinkParents($links, $expected_hierarchy); } /** * Test automatic reparenting of menu links derived from menu routers. */ function testMenuLinkRouterReparenting() { // Run all the standard parenting tests on menu links derived from // menu routers. $this->testMenuLinkReparenting('system'); // Additionnaly, test reparenting based on path. $links = $this->createLinkHierarchy('system'); // Move child-1-2 has a child of child-2, making the link hierarchy // inconsistent with the path hierarchy. $links['child-1-2']['plid'] = $links['child-2']['mlid']; menu_link_save($links['child-1-2']); // Check the new hierarchy. $expected_hierarchy = array( 'parent' => FALSE, 'child-1' => 'parent', 'child-1-1' => 'child-1', 'child-2' => 'parent', 'child-1-2' => 'child-2', ); $this->assertMenuLinkParents($links, $expected_hierarchy); // Now delete 'parent' directly from the database, simulating a database // crash. 'child-1' and 'child-2' should get moved to the // top-level. // Don't do that at home. db_delete('menu_links') ->condition('mlid', $links['parent']['mlid']) ->execute(); $expected_hierarchy = array( 'child-1' => FALSE, 'child-1-1' => 'child-1', 'child-2' => FALSE, 'child-1-2' => 'child-2', ); $this->assertMenuLinkParents($links, $expected_hierarchy); // Now delete 'child-2' directly from the database, simulating a database // crash. 'child-1-2' will get reparented under 'child-1' based on its // path. // Don't do that at home. db_delete('menu_links') ->condition('mlid', $links['child-2']['mlid']) ->execute(); $expected_hierarchy = array( 'child-1' => FALSE, 'child-1-1' => 'child-1', 'child-1-2' => 'child-1', ); $this->assertMenuLinkParents($links, $expected_hierarchy); } } /** * Tests rebuilding the menu by setting 'menu_rebuild_needed.' */ class MenuRebuildTestCase extends DrupalWebTestCase { public static function getInfo() { return array( 'name' => 'Menu rebuild test', 'description' => 'Test rebuilding of menu.', 'group' => 'Menu', ); } /** * Test if the 'menu_rebuild_needed' variable triggers a menu_rebuild() call. */ function testMenuRebuildByVariable() { // Check if 'admin' path exists. $admin_exists = db_query('SELECT path from {menu_router} WHERE path = :path', array(':path' => 'admin'))->fetchField(); $this->assertEqual($admin_exists, 'admin', "The path 'admin/' exists prior to deleting."); // Delete the path item 'admin', and test that the path doesn't exist in the database. $delete = db_delete('menu_router') ->condition('path', 'admin') ->execute(); $admin_exists = db_query('SELECT path from {menu_router} WHERE path = :path', array(':path' => 'admin'))->fetchField(); $this->assertFalse($admin_exists, "The path 'admin/' has been deleted and doesn't exist in the database."); // Now we enable the rebuild variable and trigger menu_execute_active_handler() // to rebuild the menu item. Now 'admin' should exist. variable_set('menu_rebuild_needed', TRUE); // menu_execute_active_handler() should trigger the rebuild. $this->drupalGet(''); $admin_exists = db_query('SELECT path from {menu_router} WHERE path = :path', array(':path' => 'admin'))->fetchField(); $this->assertEqual($admin_exists, 'admin', "The menu has been rebuilt, the path 'admin' now exists again."); } } /** * Menu tree data related tests. */ class MenuTreeDataTestCase extends DrupalUnitTestCase { /** * Dummy link structure acceptable for menu_tree_data(). */ var $links = array( 1 => array('mlid' => 1, 'depth' => 1), 2 => array('mlid' => 2, 'depth' => 1), 3 => array('mlid' => 3, 'depth' => 2), 4 => array('mlid' => 4, 'depth' => 3), 5 => array('mlid' => 5, 'depth' => 1), ); public static function getInfo() { return array( 'name' => 'Menu tree generation', 'description' => 'Tests recursive menu tree generation functions.', 'group' => 'Menu', ); } /** * Validate the generation of a proper menu tree hierarchy. */ function testMenuTreeData() { $tree = menu_tree_data($this->links); // Validate that parent items #1, #2, and #5 exist on the root level. $this->assertSameLink($this->links[1], $tree[1]['link'], 'Parent item #1 exists.'); $this->assertSameLink($this->links[2], $tree[2]['link'], 'Parent item #2 exists.'); $this->assertSameLink($this->links[5], $tree[5]['link'], 'Parent item #5 exists.'); // Validate that child item #4 exists at the correct location in the hierarchy. $this->assertSameLink($this->links[4], $tree[2]['below'][3]['below'][4]['link'], 'Child item #4 exists in the hierarchy.'); } /** * Check that two menu links are the same by comparing the mlid. * * @param $link1 * A menu link item. * @param $link2 * A menu link item. * @param $message * The message to display along with the assertion. * @return * TRUE if the assertion succeeded, FALSE otherwise. */ protected function assertSameLink($link1, $link2, $message = '') { return $this->assert($link1['mlid'] == $link2['mlid'], $message ? $message : 'First link is identical to second link'); } } /** * Menu tree output related tests. */ class MenuTreeOutputTestCase extends DrupalWebTestCase { /** * Dummy link structure acceptable for menu_tree_output(). */ var $tree_data = array( '1'=> array( 'link' => array( 'menu_name' => 'main-menu', 'mlid' => 1, 'hidden'=>0, 'has_children' => 1, 'title' => 'Item 1', 'in_active_trail' => 1, 'access'=>1, 'href' => 'a', 'localized_options' => array('attributes' => array('title' =>'')) ), 'below' => array( '2' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 2, 'hidden'=>0, 'has_children' => 1, 'title' => 'Item 2', 'in_active_trail' => 1, 'access'=>1, 'href' => 'a/b', 'localized_options' => array('attributes' => array('title' =>'')) ), 'below' => array( '3' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 3, 'hidden'=>0, 'has_children' => 0, 'title' => 'Item 3', 'in_active_trail' => 0, 'access'=>1, 'href' => 'a/b/c', 'localized_options' => array('attributes' => array('title' =>'')) ), 'below' => array() ), '4' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 4, 'hidden'=>0, 'has_children' => 0, 'title' => 'Item 4', 'in_active_trail' => 0, 'access'=>1, 'href' => 'a/b/d', 'localized_options' => array('attributes' => array('title' =>'')) ), 'below' => array() ) ) ) ) ), '5' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 5, 'hidden'=>1, 'has_children' => 0, 'title' => 'Item 5', 'in_active_trail' => 0, 'access'=>1, 'href' => 'e', 'localized_options' => array('attributes' => array('title' =>'')) ), 'below' => array( ) ), '6' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 6, 'hidden'=>0, 'has_children' => 0, 'title' => 'Item 6', 'in_active_trail' => 0, 'access'=>0, 'href' => 'f', 'localized_options' => array('attributes' => array('title' =>'')) ), 'below' => array( ) ), '7' => array('link' => array( 'menu_name' => 'main-menu', 'mlid' => 7, 'hidden'=>0, 'has_children' => 0, 'title' => 'Item 7', 'in_active_trail' => 0, 'access'=>1, 'href' => 'g', 'localized_options' => array('attributes' => array('title' =>'')) ), 'below' => array( ) ) ); public static function getInfo() { return array( 'name' => 'Menu tree output', 'description' => 'Tests menu tree output functions.', 'group' => 'Menu', ); } function setUp() { parent::setUp(); } /** * Validate the generation of a proper menu tree output. */ function testMenuTreeData() { $output = menu_tree_output($this->tree_data); // Validate that the - in main-menu is changed into an underscore $this->assertEqual($output['1']['#theme'], 'menu_link__main_menu', 'Hyphen is changed to an underscore on menu_link'); $this->assertEqual($output['#theme_wrappers'][0], 'menu_tree__main_menu', 'Hyphen is changed to an underscore on menu_tree wrapper'); // Looking for child items in the data $this->assertEqual( $output['1']['#below']['2']['#href'], 'a/b', 'Checking the href on a child item'); $this->assertTrue( in_array('active-trail',$output['1']['#below']['2']['#attributes']['class']) , 'Checking the active trail class'); // Validate that the hidden and no access items are missing $this->assertFalse( isset($output['5']), 'Hidden item should be missing'); $this->assertFalse( isset($output['6']), 'False access should be missing'); // Item 7 is after a couple hidden items. Just to make sure that 5 and 6 are skipped and 7 still included $this->assertTrue( isset($output['7']), 'Item after hidden items is present'); } } /** * Menu breadcrumbs related tests. */ class MenuBreadcrumbTestCase extends MenuWebTestCase { public static function getInfo() { return array( 'name' => 'Breadcrumbs', 'description' => 'Tests breadcrumbs functionality.', 'group' => 'Menu', ); } function setUp() { $modules = func_get_args(); if (isset($modules[0]) && is_array($modules[0])) { $modules = $modules[0]; } $modules[] = 'menu_test'; parent::setUp($modules); $perms = array_keys(module_invoke_all('permission')); $this->admin_user = $this->drupalCreateUser($perms); $this->drupalLogin($this->admin_user); // This test puts menu links in the Navigation menu and then tests for // their presence on the page, so we need to ensure that the Navigation // block will be displayed in all active themes. db_update('block') ->fields(array( // Use a region that is valid for all themes. 'region' => 'content', 'status' => 1, )) ->condition('module', 'system') ->condition('delta', 'navigation') ->execute(); } /** * Tests breadcrumbs on node and administrative paths. */ function testBreadCrumbs() { // Prepare common base breadcrumb elements. $home = array('' => 'Home'); $admin = $home + array('admin' => t('Administration')); $config = $admin + array('admin/config' => t('Configuration')); $type = 'article'; $langcode = LANGUAGE_NONE; // Verify breadcrumbs for default local tasks. $expected = array( 'menu-test' => t('Menu test root'), ); $title = t('Breadcrumbs test: Local tasks'); $trail = $home + $expected; $tree = $expected + array( 'menu-test/breadcrumb/tasks' => $title, ); $this->assertBreadcrumb('menu-test/breadcrumb/tasks', $trail, $title, $tree); $this->assertBreadcrumb('menu-test/breadcrumb/tasks/first', $trail, $title, $tree); $this->assertBreadcrumb('menu-test/breadcrumb/tasks/first/first', $trail, $title, $tree); $trail += array( 'menu-test/breadcrumb/tasks' => t('Breadcrumbs test: Local tasks'), ); $this->assertBreadcrumb('menu-test/breadcrumb/tasks/first/second', $trail, $title, $tree); $this->assertBreadcrumb('menu-test/breadcrumb/tasks/second', $trail, $title, $tree); $this->assertBreadcrumb('menu-test/breadcrumb/tasks/second/first', $trail, $title, $tree); $trail += array( 'menu-test/breadcrumb/tasks/second' => t('Second'), ); $this->assertBreadcrumb('menu-test/breadcrumb/tasks/second/second', $trail, $title, $tree); // Verify Taxonomy administration breadcrumbs. $trail = $admin + array( 'admin/structure' => t('Structure'), ); $this->assertBreadcrumb('admin/structure/taxonomy', $trail); $trail += array( 'admin/structure/taxonomy' => t('Taxonomy'), ); $this->assertBreadcrumb('admin/structure/taxonomy/tags', $trail); $trail += array( 'admin/structure/taxonomy/tags' => t('Tags'), ); $this->assertBreadcrumb('admin/structure/taxonomy/tags/edit', $trail); $this->assertBreadcrumb('admin/structure/taxonomy/tags/fields', $trail); $this->assertBreadcrumb('admin/structure/taxonomy/tags/add', $trail); // Verify Menu administration breadcrumbs. $trail = $admin + array( 'admin/structure' => t('Structure'), ); $this->assertBreadcrumb('admin/structure/menu', $trail); $trail += array( 'admin/structure/menu' => t('Menus'), ); $this->assertBreadcrumb('admin/structure/menu/manage/navigation', $trail); $trail += array( 'admin/structure/menu/manage/navigation' => t('Navigation'), ); $this->assertBreadcrumb("admin/structure/menu/item/6/edit", $trail); $this->assertBreadcrumb('admin/structure/menu/manage/navigation/edit', $trail); $this->assertBreadcrumb('admin/structure/menu/manage/navigation/add', $trail); // Verify Node administration breadcrumbs. $trail = $admin + array( 'admin/structure' => t('Structure'), 'admin/structure/types' => t('Content types'), ); $this->assertBreadcrumb('admin/structure/types/add', $trail); $this->assertBreadcrumb("admin/structure/types/manage/$type", $trail); $trail += array( "admin/structure/types/manage/$type" => t('Article'), ); $this->assertBreadcrumb("admin/structure/types/manage/$type/fields", $trail); $this->assertBreadcrumb("admin/structure/types/manage/$type/display", $trail); $trail_teaser = $trail + array( "admin/structure/types/manage/$type/display" => t('Manage display'), ); $this->assertBreadcrumb("admin/structure/types/manage/$type/display/teaser", $trail_teaser); $this->assertBreadcrumb("admin/structure/types/manage/$type/comment/fields", $trail); $this->assertBreadcrumb("admin/structure/types/manage/$type/comment/display", $trail); $this->assertBreadcrumb("admin/structure/types/manage/$type/delete", $trail); $trail += array( "admin/structure/types/manage/$type/fields" => t('Manage fields'), ); $this->assertBreadcrumb("admin/structure/types/manage/$type/fields/body", $trail); $trail += array( "admin/structure/types/manage/$type/fields/body" => t('Body'), ); $this->assertBreadcrumb("admin/structure/types/manage/$type/fields/body/widget-type", $trail); // Verify Filter text format administration breadcrumbs. $format = db_query_range("SELECT format, name FROM {filter_format}", 1, 1)->fetch(); $format_id = $format->format; $trail = $config + array( 'admin/config/content' => t('Content authoring'), ); $this->assertBreadcrumb('admin/config/content/formats', $trail); $trail += array( 'admin/config/content/formats' => t('Text formats'), ); $this->assertBreadcrumb('admin/config/content/formats/add', $trail); $this->assertBreadcrumb("admin/config/content/formats/$format_id", $trail); $trail += array( "admin/config/content/formats/$format_id" => $format->name, ); $this->assertBreadcrumb("admin/config/content/formats/$format_id/disable", $trail); // Verify node breadcrumbs (without menu link). $node1 = $this->drupalCreateNode(); $nid1 = $node1->nid; $trail = $home; $this->assertBreadcrumb("node/$nid1", $trail); // Also verify that the node does not appear elsewhere (e.g., menu trees). $this->assertNoLink($node1->title); // The node itself should not be contained in the breadcrumb on the default // local task, since there is no difference between both pages. $this->assertBreadcrumb("node/$nid1/view", $trail); // Also verify that the node does not appear elsewhere (e.g., menu trees). $this->assertNoLink($node1->title); $trail += array( "node/$nid1" => $node1->title, ); $this->assertBreadcrumb("node/$nid1/edit", $trail); // Verify that breadcrumb on node listing page contains "Home" only. $trail = array(); $this->assertBreadcrumb('node', $trail); // Verify node breadcrumbs (in menu). // Do this separately for Main menu and Navigation menu, since only the // latter is a preferred menu by default. // @todo Also test all themes? Manually testing led to the suspicion that // breadcrumbs may differ, possibly due to template.php overrides. $menus = array('main-menu', 'navigation'); // Alter node type menu settings. variable_set("menu_options_$type", $menus); variable_set("menu_parent_$type", 'navigation:0'); foreach ($menus as $menu) { // Create a parent node in the current menu. $title = $this->randomName(); $node2 = $this->drupalCreateNode(array( 'type' => $type, 'title' => $title, 'menu' => array( 'enabled' => 1, 'link_title' => 'Parent ' . $title, 'description' => '', 'menu_name' => $menu, 'plid' => 0, ), )); $nid2 = $node2->nid; $trail = $home; $tree = array( "node/$nid2" => $node2->menu['link_title'], ); $this->assertBreadcrumb("node/$nid2", $trail, $node2->title, $tree); // The node itself should not be contained in the breadcrumb on the // default local task, since there is no difference between both pages. $this->assertBreadcrumb("node/$nid2/view", $trail, $node2->title, $tree); $trail += array( "node/$nid2" => $node2->menu['link_title'], ); $this->assertBreadcrumb("node/$nid2/edit", $trail); // Create a child node in the current menu. $title = $this->randomName(); $node3 = $this->drupalCreateNode(array( 'type' => $type, 'title' => $title, 'menu' => array( 'enabled' => 1, 'link_title' => 'Child ' . $title, 'description' => '', 'menu_name' => $menu, 'plid' => $node2->menu['mlid'], ), )); $nid3 = $node3->nid; $this->assertBreadcrumb("node/$nid3", $trail, $node3->title, $tree, FALSE); // The node itself should not be contained in the breadcrumb on the // default local task, since there is no difference between both pages. $this->assertBreadcrumb("node/$nid3/view", $trail, $node3->title, $tree, FALSE); $trail += array( "node/$nid3" => $node3->menu['link_title'], ); $tree += array( "node/$nid3" => $node3->menu['link_title'], ); $this->assertBreadcrumb("node/$nid3/edit", $trail); // Verify that node listing page still contains "Home" only. $trail = array(); $this->assertBreadcrumb('node', $trail); if ($menu == 'navigation') { $parent = $node2; $child = $node3; } } // Create a Navigation menu link for 'node', move the last parent node menu // link below it, and verify a full breadcrumb for the last child node. $menu = 'navigation'; $edit = array( 'link_title' => 'Root', 'link_path' => 'node', ); $this->drupalPost("admin/structure/menu/manage/$menu/add", $edit, t('Save')); $link = db_query('SELECT * FROM {menu_links} WHERE link_title = :title', array(':title' => 'Root'))->fetchAssoc(); $edit = array( 'menu[parent]' => $link['menu_name'] . ':' . $link['mlid'], ); $this->drupalPost("node/{$parent->nid}/edit", $edit, t('Save')); $expected = array( "node" => $link['link_title'], ); $trail = $home + $expected; $tree = $expected + array( "node/{$parent->nid}" => $parent->menu['link_title'], ); $this->assertBreadcrumb(NULL, $trail, $parent->title, $tree); $trail += array( "node/{$parent->nid}" => $parent->menu['link_title'], ); $tree += array( "node/{$child->nid}" => $child->menu['link_title'], ); $this->assertBreadcrumb("node/{$child->nid}", $trail, $child->title, $tree); // Add a taxonomy term/tag to last node, and add a link for that term to the // Navigation menu. $tags = array( 'Drupal' => array(), 'Breadcrumbs' => array(), ); $edit = array( "field_tags[$langcode]" => implode(',', array_keys($tags)), ); $this->drupalPost("node/{$parent->nid}/edit", $edit, t('Save')); // Put both terms into a hierarchy Drupal » Breadcrumbs. Required for both // the menu links and the terms itself, since taxonomy_term_page() resets // the breadcrumb based on taxonomy term hierarchy. $parent_tid = 0; foreach ($tags as $name => $null) { $terms = taxonomy_term_load_multiple(NULL, array('name' => $name)); $term = reset($terms); $tags[$name]['term'] = $term; if ($parent_tid) { $edit = array( 'parent[]' => array($parent_tid), ); $this->drupalPost("taxonomy/term/{$term->tid}/edit", $edit, t('Save')); } $parent_tid = $term->tid; } $parent_mlid = 0; foreach ($tags as $name => $data) { $term = $data['term']; $edit = array( 'link_title' => "$name link", 'link_path' => "taxonomy/term/{$term->tid}", 'parent' => "$menu:{$parent_mlid}", ); $this->drupalPost("admin/structure/menu/manage/$menu/add", $edit, t('Save')); $tags[$name]['link'] = db_query('SELECT * FROM {menu_links} WHERE link_title = :title AND link_path = :href', array( ':title' => $edit['link_title'], ':href' => $edit['link_path'], ))->fetchAssoc(); $tags[$name]['link']['link_path'] = $edit['link_path']; $parent_mlid = $tags[$name]['link']['mlid']; } // Verify expected breadcrumbs for menu links. $trail = $home; $tree = array(); foreach ($tags as $name => $data) { $term = $data['term']; $link = $data['link']; $tree += array( $link['link_path'] => $link['link_title'], ); $this->assertBreadcrumb($link['link_path'], $trail, $term->name, $tree); $this->assertRaw(check_plain($parent->title), 'Tagged node found.'); // Additionally make sure that this link appears only once; i.e., the // untranslated menu links automatically generated from menu router items // ('taxonomy/term/%') should never be translated and appear in any menu // other than the breadcrumb trail. $elements = $this->xpath('//div[@id=:menu]/descendant::a[@href=:href]', array( ':menu' => 'block-system-navigation', ':href' => url($link['link_path']), )); $this->assertTrue(count($elements) == 1, "Link to {$link['link_path']} appears only once."); // Next iteration should expect this tag as parent link. // Note: Term name, not link name, due to taxonomy_term_page(). $trail += array( $link['link_path'] => $term->name, ); } // Verify breadcrumbs on user and user/%. // We need to log back in and out below, and cannot simply grant the // 'administer users' permission, since user_page() makes your head explode. user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array( 'access user profiles', )); $this->drupalLogout(); // Verify breadcrumb on front page. $this->assertBreadcrumb('', array()); // Verify breadcrumb on user pages (without menu link) for anonymous user. $trail = $home; $this->assertBreadcrumb('user', $trail, t('User account')); $this->assertBreadcrumb('user/' . $this->admin_user->uid, $trail, $this->admin_user->name); // Verify breadcrumb on user pages (without menu link) for registered users. $this->drupalLogin($this->admin_user); $trail = $home; $this->assertBreadcrumb('user', $trail, $this->admin_user->name); $this->assertBreadcrumb('user/' . $this->admin_user->uid, $trail, $this->admin_user->name); $trail += array( 'user/' . $this->admin_user->uid => $this->admin_user->name, ); $this->assertBreadcrumb('user/' . $this->admin_user->uid . '/edit', $trail, $this->admin_user->name); // Create a second user to verify breadcrumb on user pages again. $this->web_user = $this->drupalCreateUser(array( 'administer users', 'access user profiles', )); $this->drupalLogin($this->web_user); // Verify correct breadcrumb and page title on another user's account pages // (without menu link). $trail = $home; $this->assertBreadcrumb('user/' . $this->admin_user->uid, $trail, $this->admin_user->name); $trail += array( 'user/' . $this->admin_user->uid => $this->admin_user->name, ); $this->assertBreadcrumb('user/' . $this->admin_user->uid . '/edit', $trail, $this->admin_user->name); // Verify correct breadcrumb and page title when viewing own user account // pages (without menu link). $trail = $home; $this->assertBreadcrumb('user/' . $this->web_user->uid, $trail, $this->web_user->name); $trail += array( 'user/' . $this->web_user->uid => $this->web_user->name, ); $this->assertBreadcrumb('user/' . $this->web_user->uid . '/edit', $trail, $this->web_user->name); // Add a Navigation menu links for 'user' and $this->admin_user. // Although it may be faster to manage these links via low-level API // functions, there's a lot that can go wrong in doing so. $this->drupalLogin($this->admin_user); $edit = array( 'link_title' => 'User', 'link_path' => 'user', ); $this->drupalPost("admin/structure/menu/manage/$menu/add", $edit, t('Save')); $link_user = db_query('SELECT * FROM {menu_links} WHERE link_title = :title AND link_path = :href', array( ':title' => $edit['link_title'], ':href' => $edit['link_path'], ))->fetchAssoc(); $edit = array( 'link_title' => $this->admin_user->name . ' link', 'link_path' => 'user/' . $this->admin_user->uid, ); $this->drupalPost("admin/structure/menu/manage/$menu/add", $edit, t('Save')); $link_admin_user = db_query('SELECT * FROM {menu_links} WHERE link_title = :title AND link_path = :href', array( ':title' => $edit['link_title'], ':href' => $edit['link_path'], ))->fetchAssoc(); // Verify expected breadcrumbs for the two separate links. $this->drupalLogout(); $trail = $home; $tree = array( $link_user['link_path'] => $link_user['link_title'], ); $this->assertBreadcrumb('user', $trail, $link_user['link_title'], $tree); $tree = array( $link_admin_user['link_path'] => $link_admin_user['link_title'], ); $this->assertBreadcrumb('user/' . $this->admin_user->uid, $trail, $link_admin_user['link_title'], $tree); $this->drupalLogin($this->admin_user); $trail += array( $link_admin_user['link_path'] => $link_admin_user['link_title'], ); $this->assertBreadcrumb('user/' . $this->admin_user->uid . '/edit', $trail, $link_admin_user['link_title'], $tree, FALSE); // Move 'user/%' below 'user' and verify again. $edit = array( 'parent' => "$menu:{$link_user['mlid']}", ); $this->drupalPost("admin/structure/menu/item/{$link_admin_user['mlid']}/edit", $edit, t('Save')); $this->drupalLogout(); $trail = $home; $tree = array( $link_user['link_path'] => $link_user['link_title'], ); $this->assertBreadcrumb('user', $trail, $link_user['link_title'], $tree); $trail += array( $link_user['link_path'] => $link_user['link_title'], ); $tree += array( $link_admin_user['link_path'] => $link_admin_user['link_title'], ); $this->assertBreadcrumb('user/' . $this->admin_user->uid, $trail, $link_admin_user['link_title'], $tree); $this->drupalLogin($this->admin_user); $trail += array( $link_admin_user['link_path'] => $link_admin_user['link_title'], ); $this->assertBreadcrumb('user/' . $this->admin_user->uid . '/edit', $trail, $link_admin_user['link_title'], $tree, FALSE); // Create an only slightly privileged user being able to access site reports // but not administration pages. $this->web_user = $this->drupalCreateUser(array( 'access site reports', )); $this->drupalLogin($this->web_user); // Verify that we can access recent log entries, there is a corresponding // page title, and that the breadcrumb is empty (because the user is not // able to access "Administer", so the trail cannot recurse into it). $trail = array(); $this->assertBreadcrumb('admin', $trail, t('Access denied')); $this->assertResponse(403); $trail = $home; $this->assertBreadcrumb('admin/reports', $trail, t('Reports')); $this->assertNoResponse(403); $this->assertBreadcrumb('admin/reports/dblog', $trail, t('Recent log messages')); $this->assertNoResponse(403); } } /** * Tests active menu trails. */ class MenuTrailTestCase extends MenuWebTestCase { public static function getInfo() { return array( 'name' => 'Active trail', 'description' => 'Tests active menu trails and alteration functionality.', 'group' => 'Menu', ); } function setUp() { $modules = func_get_args(); if (isset($modules[0]) && is_array($modules[0])) { $modules = $modules[0]; } $modules[] = 'menu_test'; parent::setUp($modules); $this->admin_user = $this->drupalCreateUser(array('administer site configuration', 'access administration pages')); $this->drupalLogin($this->admin_user); // This test puts menu links in the Navigation menu and then tests for // their presence on the page, so we need to ensure that the Navigation // block will be displayed in all active themes. db_update('block') ->fields(array( // Use a region that is valid for all themes. 'region' => 'content', 'status' => 1, )) ->condition('module', 'system') ->condition('delta', 'navigation') ->execute(); // This test puts menu links in the Management menu and then tests for // their presence on the page, so we need to ensure that the Management // block will be displayed in all active themes. db_update('block') ->fields(array( // Use a region that is valid for all themes. 'region' => 'content', 'status' => 1, )) ->condition('module', 'system') ->condition('delta', 'management') ->execute(); } /** * Tests active trails are properly affected by menu_tree_set_path(). */ function testMenuTreeSetPath() { $home = array('' => 'Home'); $config_tree = array( 'admin' => t('Administration'), 'admin/config' => t('Configuration'), ); $config = $home + $config_tree; // The menu_test_menu_tree_set_path system variable controls whether or not // the menu_test_menu_trail_callback() callback (used by all paths in these // tests) issues an overriding call to menu_trail_set_path(). $test_menu_path = array( 'menu_name' => 'management', 'path' => 'admin/config/system/site-information', ); $breadcrumb = $home + array( 'menu-test' => t('Menu test root'), ); $tree = array( 'menu-test' => t('Menu test root'), 'menu-test/menu-trail' => t('Menu trail - Case 1'), ); // Test the tree generation for the Navigation menu. variable_del('menu_test_menu_tree_set_path'); $this->assertBreadcrumb('menu-test/menu-trail', $breadcrumb, t('Menu trail - Case 1'), $tree); // Override the active trail for the Management tree; it should not affect // the Navigation tree. variable_set('menu_test_menu_tree_set_path', $test_menu_path); $this->assertBreadcrumb('menu-test/menu-trail', $breadcrumb, t('Menu trail - Case 1'), $tree); $breadcrumb = $config + array( 'admin/config/development' => t('Development'), ); $tree = $config_tree + array( 'admin/config/development' => t('Development'), 'admin/config/development/menu-trail' => t('Menu trail - Case 2'), ); $override_breadcrumb = $config + array( 'admin/config/system' => t('System'), 'admin/config/system/site-information' => t('Site information'), ); $override_tree = $config_tree + array( 'admin/config/system' => t('System'), 'admin/config/system/site-information' => t('Site information'), ); // Test the tree generation for the Management menu. variable_del('menu_test_menu_tree_set_path'); $this->assertBreadcrumb('admin/config/development/menu-trail', $breadcrumb, t('Menu trail - Case 2'), $tree); // Override the active trail for the Management tree; it should affect the // breadcrumbs and Management tree. variable_set('menu_test_menu_tree_set_path', $test_menu_path); $this->assertBreadcrumb('admin/config/development/menu-trail', $override_breadcrumb, t('Menu trail - Case 2'), $override_tree); } /** * Tests that the active trail works correctly on custom 403 and 404 pages. */ function testCustom403And404Pages() { // Set the custom 403 and 404 pages we will use. variable_set('site_403', 'menu-test/custom-403-page'); variable_set('site_404', 'menu-test/custom-404-page'); // Define the paths we'll visit to trigger 403 and 404 responses during // this test, and the expected active trail for each case. $paths = array( 403 => 'admin/config', 404 => $this->randomName(), ); // For the 403 page, the initial trail during the Drupal bootstrap should // include the page that the user is trying to visit, while the final trail // should reflect the custom 403 page that the user was redirected to. $expected_trail[403]['initial'] = array( '' => 'Home', 'admin/config' => 'Configuration', ); $expected_trail[403]['final'] = array( '' => 'Home', 'menu-test' => 'Menu test root', 'menu-test/custom-403-page' => 'Custom 403 page', ); // For the 404 page, the initial trail during the Drupal bootstrap should // only contain the link back to "Home" (since the page the user is trying // to visit doesn't have any menu items associated with it), while the // final trail should reflect the custom 404 page that the user was // redirected to. $expected_trail[404]['initial'] = array( '' => 'Home', ); $expected_trail[404]['final'] = array( '' => 'Home', 'menu-test' => 'Menu test root', 'menu-test/custom-404-page' => 'Custom 404 page', ); // Visit each path as an anonymous user so that we will actually get a 403 // on admin/config. $this->drupalLogout(); foreach (array(403, 404) as $status_code) { // Before visiting the page, trigger the code in the menu_test module // that will record the active trail (so we can check it in this test). variable_set('menu_test_record_active_trail', TRUE); $this->drupalGet($paths[$status_code]); $this->assertResponse($status_code); // Check that the initial trail (during the Drupal bootstrap) matches // what we expect. $initial_trail = variable_get('menu_test_active_trail_initial', array()); $this->assertEqual(count($initial_trail), count($expected_trail[$status_code]['initial']), format_string('The initial active trail for a @status_code page contains the expected number of items (expected: @expected, found: @found).', array( '@status_code' => $status_code, '@expected' => count($expected_trail[$status_code]['initial']), '@found' => count($initial_trail), ))); foreach (array_keys($expected_trail[$status_code]['initial']) as $index => $path) { $this->assertEqual($initial_trail[$index]['href'], $path, format_string('Element number @number of the initial active trail for a @status_code page contains the correct path (expected: @expected, found: @found)', array( '@number' => $index + 1, '@status_code' => $status_code, '@expected' => $path, '@found' => $initial_trail[$index]['href'], ))); } // Check that the final trail (after the user has been redirected to the // custom 403/404 page) matches what we expect. $final_trail = variable_get('menu_test_active_trail_final', array()); $this->assertEqual(count($final_trail), count($expected_trail[$status_code]['final']), format_string('The final active trail for a @status_code page contains the expected number of items (expected: @expected, found: @found).', array( '@status_code' => $status_code, '@expected' => count($expected_trail[$status_code]['final']), '@found' => count($final_trail), ))); foreach (array_keys($expected_trail[$status_code]['final']) as $index => $path) { $this->assertEqual($final_trail[$index]['href'], $path, format_string('Element number @number of the final active trail for a @status_code page contains the correct path (expected: @expected, found: @found)', array( '@number' => $index + 1, '@status_code' => $status_code, '@expected' => $path, '@found' => $final_trail[$index]['href'], ))); } // Check that the breadcrumb displayed on the final custom 403/404 page // matches what we expect. (The last item of the active trail represents // the current page, which is not supposed to appear in the breadcrumb, // so we need to remove it from the array before checking.) array_pop($expected_trail[$status_code]['final']); $this->assertBreadcrumb(NULL, $expected_trail[$status_code]['final']); } } }