'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', t('Raw text found on the page')); $this->assertNoText(t('A title with @placeholder', array('@placeholder' => 'some other text')), t('Text with placeholder substitutions not found.')); } /** * 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.', t('The administrative theme can be correctly set in a theme callback.')); $this->assertRaw('seven/style.css', t("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.', t('Theme callback inheritance correctly uses the administrative theme.')); $this->assertRaw('seven/style.css', t("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', t('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', t("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.', t('The theme callback system is correctly triggered for an administrator when the site is in maintenance mode.')); $this->assertRaw('seven/style.css', t("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)), t("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)), t("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.', t('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', t("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.', t('The theme callback system uses an optional theme once it has been enabled.')); $this->assertRaw('stark/layout.css', t("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.', t('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', t("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.', t('The theme callback system falls back on the default theme when no theme is requested.')); $this->assertRaw('bartik/css/style.css', t("The default theme's CSS appears on the page.")); } /** * Test that the result of hook_custom_theme() overrides the theme callback. */ 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'); // Request a page whose theme callback returns the Seven theme. Since Stark // is not a currently enabled theme, our above request should be ignored, // and Seven should still be used. $this->drupalGet('menu-test/theme-callback/use-admin-theme'); $this->assertText('Custom theme: seven. Actual theme: seven.', t('The result of hook_custom_theme() does not override a theme callback when it returns a theme that is not enabled.')); $this->assertRaw('seven/style.css', t("The Seven theme's CSS appears on the page.")); // Now enable the Stark theme and request the same page as above. This // time, we expect hook_custom_theme() to prevail. theme_enable(array('stark')); $this->drupalGet('menu-test/theme-callback/use-admin-theme'); $this->assertText('Custom theme: stark. Actual theme: stark.', t('The result of hook_custom_theme() overrides what was set in a theme callback.')); $this->assertRaw('stark/layout.css', t("The Stark 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, t('Found updated menu link')); $this->assertNoLink(t('Menu link #1'), 0, t('Not found menu link #1')); $this->assertNoLink(t('Menu link #1'), 0, t('Not found menu link #1-1')); $this->assertLink(t('Menu link #2'), 0, t('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, t('Not found deleted menu link')); $this->assertNoLink(t('Menu link #1'), 0, t('Not found menu link #1')); $this->assertNoLink(t('Menu link #1'), 0, t('Not found menu link #1-1')); $this->assertLink(t('Menu link #2'), 0, t('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', t('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', t('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'], t('The parent of a directly attached child is correct.')); $this->assertEqual($unattached_child_link['plid'], $parent_link['mlid'], t('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, t('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, t('%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, t('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, t('%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, t('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, t('%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, t('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, t('%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, t('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, t('%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, t('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, t('%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, t('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, t('%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, t('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, t('%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, t('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, t('%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, t('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, t('%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, t('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, t('%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, t('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, t('%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, t('%path depth @link_depth is equal to @depth.', array('%path' => $link['router_path'], '@link_depth' => $link['depth'], '@depth' => $depth))); $this->assertEqual($link['plid'], $plid, t('%path plid @link_plid is equal to @plid.', array('%path' => $link['router_path'], '@link_plid' => $link['plid'], '@plid' => $plid))); } /** * Test menu_set_item(). */ function testMenuSetItem() { $item = menu_get_item('node'); $this->assertEqual($item['path'], 'node', t("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, t('Modified menu item is equal to newly retrieved menu item.'), 'menu'); } /** * Test menu maintainance 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', t('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', t('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', t('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"', t('Title attribute of a menu link renders.')); $this->assertRaw('testparam=testvalue', t('Query parameter added to menu link.')); } } /** * 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', t("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, t("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', t("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'], t('Parent item #1 exists.')); $this->assertSameLink($this->links[2], $tree[2]['link'], t('Parent item #2 exists.')); $this->assertSameLink($this->links[5], $tree[5]['link'], t('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'], t('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 : t('First link is identical to second link')); } }