diff --git a/core/modules/aggregator/src/Tests/AggregatorAdminTest.php b/core/modules/aggregator/tests/src/Functional/AggregatorAdminTest.php similarity index 98% rename from core/modules/aggregator/src/Tests/AggregatorAdminTest.php rename to core/modules/aggregator/tests/src/Functional/AggregatorAdminTest.php index 249817f..4540cd8 100644 --- a/core/modules/aggregator/src/Tests/AggregatorAdminTest.php +++ b/core/modules/aggregator/tests/src/Functional/AggregatorAdminTest.php @@ -1,6 +1,6 @@ install(array('bartik')); - - // Create user. - $this->adminUser = $this->drupalCreateUser(array('administer themes')); - $this->drupalLogin($this->adminUser); - } - - /** - * Tests whether the color config schema is valid. - */ - function testValidColorConfigSchema() { - $settings_path = 'admin/appearance/settings/bartik'; - $edit['scheme'] = ''; - $edit['palette[bg]'] = '#123456'; - $this->drupalPostForm($settings_path, $edit, t('Save configuration')); - } - -} diff --git a/core/modules/color/tests/src/Functional/ColorSafePreviewTest.php b/core/modules/color/tests/src/Functional/ColorSafePreviewTest.php index e80202f..e69de29 100644 --- a/core/modules/color/tests/src/Functional/ColorSafePreviewTest.php +++ b/core/modules/color/tests/src/Functional/ColorSafePreviewTest.php @@ -1,57 +0,0 @@ -bigUser = $this->drupalCreateUser(['administer themes']); - } - - /** - * Ensures color preview.html is sanitized. - */ - function testColorPreview() { - // Install the color test theme. - \Drupal::service('theme_handler')->install(['color_test_theme']); - $this->drupalLogin($this->bigUser); - - // Markup is being printed from a HTML file located in: - // core/modules/color/tests/modules/color_test/themes/color_test_theme/color/preview.html - $url = Url::fromRoute('system.theme_settings_theme', ['theme' => 'color_test_theme']); - $this->drupalGet($url); - $this->assertText('TEST COLOR PREVIEW'); - - $this->assertNoRaw(''); - $this->assertRaw('

TEST COLOR PREVIEW

'); - } - -} diff --git a/core/modules/color/tests/src/Functional/ColorTest.php b/core/modules/color/tests/src/Functional/ColorTest.php index dc44566..e69de29 100644 --- a/core/modules/color/tests/src/Functional/ColorTest.php +++ b/core/modules/color/tests/src/Functional/ColorTest.php @@ -1,231 +0,0 @@ -bigUser = $this->drupalCreateUser(array('administer themes')); - - // This tests the color module in Bartik. - $this->themes = array( - 'bartik' => array( - 'palette_input' => 'palette[bg]', - 'scheme' => 'slate', - 'scheme_color' => '#3b3b3b', - ), - 'color_test_theme' => array( - 'palette_input' => 'palette[bg]', - 'scheme' => 'custom', - 'scheme_color' => '#3b3b3b', - ), - ); - \Drupal::service('theme_handler')->install(array_keys($this->themes)); - - // Array filled with valid and not valid color values. - $this->colorTests = array( - '#000' => TRUE, - '#123456' => TRUE, - '#abcdef' => TRUE, - '#0' => FALSE, - '#00' => FALSE, - '#0000' => FALSE, - '#00000' => FALSE, - '123456' => FALSE, - '#00000g' => FALSE, - ); - } - - /** - * Tests the Color module functionality. - */ - function testColor() { - foreach ($this->themes as $theme => $test_values) { - $this->_testColor($theme, $test_values); - } - } - - /** - * Tests the Color module functionality using the given theme. - * - * @param string $theme - * The machine name of the theme being tested. - * @param array $test_values - * An associative array of test settings (i.e. 'Main background', 'Text - * color', 'Color set', etc) for the theme which being tested. - */ - function _testColor($theme, $test_values) { - $this->config('system.theme') - ->set('default', $theme) - ->save(); - $settings_path = 'admin/appearance/settings/' . $theme; - - $this->drupalLogin($this->bigUser); - $this->drupalGet($settings_path); - $this->assertResponse(200); - $this->assertUniqueText('Color set'); - $edit['scheme'] = ''; - $edit[$test_values['palette_input']] = '#123456'; - $this->drupalPostForm($settings_path, $edit, t('Save configuration')); - - $this->drupalGet(''); - $stylesheets = $this->config('color.theme.' . $theme)->get('stylesheets'); - foreach ($stylesheets as $stylesheet) { - $this->assertPattern('|' . file_url_transform_relative(file_create_url($stylesheet)) . '|', 'Make sure the color stylesheet is included in the content. (' . $theme . ')'); - $stylesheet_content = join("\n", file($stylesheet)); - $this->assertTrue(strpos($stylesheet_content, 'color: #123456') !== FALSE, 'Make sure the color we changed is in the color stylesheet. (' . $theme . ')'); - } - - $this->drupalGet($settings_path); - $this->assertResponse(200); - $edit['scheme'] = $test_values['scheme']; - $this->drupalPostForm($settings_path, $edit, t('Save configuration')); - - $this->drupalGet(''); - $stylesheets = $this->config('color.theme.' . $theme)->get('stylesheets'); - foreach ($stylesheets as $stylesheet) { - $stylesheet_content = join("\n", file($stylesheet)); - $this->assertTrue(strpos($stylesheet_content, 'color: ' . $test_values['scheme_color']) !== FALSE, 'Make sure the color we changed is in the color stylesheet. (' . $theme . ')'); - } - - // Test with aggregated CSS turned on. - $config = $this->config('system.performance'); - $config->set('css.preprocess', 1); - $config->save(); - $this->drupalGet(''); - $stylesheets = \Drupal::state()->get('drupal_css_cache_files') ?: array(); - $stylesheet_content = ''; - foreach ($stylesheets as $uri) { - $stylesheet_content .= join("\n", file(drupal_realpath($uri))); - } - $this->assertTrue(strpos($stylesheet_content, 'public://') === FALSE, 'Make sure the color paths have been translated to local paths. (' . $theme . ')'); - $config->set('css.preprocess', 0); - $config->save(); - } - - /** - * Tests whether the provided color is valid. - */ - function testValidColor() { - $this->config('system.theme') - ->set('default', 'bartik') - ->save(); - $settings_path = 'admin/appearance/settings/bartik'; - - $this->drupalLogin($this->bigUser); - $edit['scheme'] = ''; - - foreach ($this->colorTests as $color => $is_valid) { - $edit['palette[bg]'] = $color; - $this->drupalPostForm($settings_path, $edit, t('Save configuration')); - - if ($is_valid) { - $this->assertText('The configuration options have been saved.'); - } - else { - $this->assertText('You must enter a valid hexadecimal color value for Main background.'); - } - } - } - - /** - * Test whether the custom logo is used in the color preview. - */ - function testLogoSettingOverride() { - $this->drupalLogin($this->bigUser); - $edit = array( - 'default_logo' => FALSE, - 'logo_path' => 'core/misc/druplicon.png', - ); - $this->drupalPostForm('admin/appearance/settings', $edit, t('Save configuration')); - - // Ensure that the overridden logo is present in Bartik, which is colorable. - $this->drupalGet('admin/appearance/settings/bartik'); - $this->assertIdentical($GLOBALS['base_path'] . 'core/misc/druplicon.png', $this->getDrupalSettings()['color']['logo']); - } - - /** - * Test whether the scheme can be set, viewed anonymously and reset. - */ - function testOverrideAndResetScheme() { - $settings_path = 'admin/appearance/settings/bartik'; - $this->config('system.theme') - ->set('default', 'bartik') - ->save(); - - // Place branding block with site name and slogan into header region. - $this->drupalPlaceBlock('system_branding_block', ['region' => 'header']); - - $this->drupalGet(''); - $this->assertNoRaw('files/color/bartik-', 'Make sure the color logo is not being used.'); - $this->assertRaw('bartik/logo.svg', 'Make sure the original bartik logo exists.'); - - // Log in and set the color scheme to 'slate'. - $this->drupalLogin($this->bigUser); - $edit['scheme'] = 'slate'; - $this->drupalPostForm($settings_path, $edit, t('Save configuration')); - - // Visit the homepage and ensure color changes. - $this->drupalLogout(); - $this->drupalGet(''); - $this->assertRaw('files/color/bartik-', 'Make sure the color logo is being used.'); - $this->assertNoRaw('bartik/logo.svg', 'Make sure the original bartik logo does not exist.'); - - // Log in and set the color scheme back to default (delete config). - $this->drupalLogin($this->bigUser); - $edit['scheme'] = 'default'; - $this->drupalPostForm($settings_path, $edit, t('Save configuration')); - - // Log out and ensure there is no color and we have the original logo. - $this->drupalLogout(); - $this->drupalGet(''); - $this->assertNoRaw('files/color/bartik-', 'Make sure the color logo is not being used.'); - $this->assertRaw('bartik/logo.svg', 'Make sure the original bartik logo exists.'); - } - -} diff --git a/core/modules/comment/src/Tests/CommentActionsTest.php b/core/modules/comment/tests/src/Functional/CommentActionsTest.php similarity index 97% rename from core/modules/comment/src/Tests/CommentActionsTest.php rename to core/modules/comment/tests/src/Functional/CommentActionsTest.php index 220be20..4c32426 100644 --- a/core/modules/comment/src/Tests/CommentActionsTest.php +++ b/core/modules/comment/tests/src/Functional/CommentActionsTest.php @@ -1,6 +1,6 @@ uninstall(['page_cache']); - } - - /** - * Tests that Dynamic Page Cache works correctly, and verifies the edge cases. - */ - public function testDynamicPageCache() { - // Controllers returning plain response objects are ignored by Dynamic Page - // Cache. - $url = Url::fromUri('route:dynamic_page_cache_test.response'); - $this->drupalGet($url); - $this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response object returned: Dynamic Page Cache is ignoring.'); - - // Controllers returning CacheableResponseInterface (cacheable response) - // objects are handled by Dynamic Page Cache. - $url = Url::fromUri('route:dynamic_page_cache_test.cacheable_response'); - $this->drupalGet($url); - $this->assertEqual('MISS', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Cacheable response object returned: Dynamic Page Cache is active, Dynamic Page Cache MISS.'); - $this->drupalGet($url); - $this->assertEqual('HIT', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Cacheable response object returned: Dynamic Page Cache is active, Dynamic Page Cache HIT.'); - - // Controllers returning render arrays, rendered as HTML responses, are - // handled by Dynamic Page Cache. - $url = Url::fromUri('route:dynamic_page_cache_test.html'); - $this->drupalGet($url); - $this->assertEqual('MISS', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response: Dynamic Page Cache is active, Dynamic Page Cache MISS.'); - $this->drupalGet($url); - $this->assertEqual('HIT', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response: Dynamic Page Cache is active, Dynamic Page Cache HIT.'); - - // The above is the simple case, where the render array returned by the - // response contains no cache contexts. So let's now test a route/controller - // that *does* vary by a cache context whose value we can easily control: it - // varies by the 'animal' query argument. - foreach (['llama', 'piggy', 'unicorn', 'kitten'] as $animal) { - $url = Url::fromUri('route:dynamic_page_cache_test.html.with_cache_contexts', ['query' => ['animal' => $animal]]); - $this->drupalGet($url); - $this->assertRaw($animal); - $this->assertEqual('MISS', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response: Dynamic Page Cache is active, Dynamic Page Cache MISS.'); - $this->drupalGet($url); - $this->assertRaw($animal); - $this->assertEqual('HIT', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response: Dynamic Page Cache is active, Dynamic Page Cache HIT.'); - - // Finally, let's also verify that the 'dynamic_page_cache_test.html' - // route continued to see cache hits if we specify a query argument, - // because it *should* ignore it and continue to provide Dynamic Page - // Cache hits. - $url = Url::fromUri('route:dynamic_page_cache_test.html', ['query' => ['animal' => 'piglet']]); - $this->drupalGet($url); - $this->assertEqual('HIT', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response: Dynamic Page Cache is active, Dynamic Page Cache HIT.'); - } - - // Controllers returning render arrays, rendered as anything except a HTML - // response, are ignored by Dynamic Page Cache (but only because those - // wrapper formats' responses do not implement CacheableResponseInterface). - $this->drupalGet('dynamic-page-cache-test/html', array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax'))); - $this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as AJAX response: Dynamic Page Cache is ignoring.'); - $this->drupalGet('dynamic-page-cache-test/html', array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_dialog'))); - $this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as dialog response: Dynamic Page Cache is ignoring.'); - $this->drupalGet('dynamic-page-cache-test/html', array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_modal'))); - $this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as modal response: Dynamic Page Cache is ignoring.'); - - // Admin routes are ignored by Dynamic Page Cache. - $this->drupalGet('dynamic-page-cache-test/html/admin'); - $this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response returned, rendered as HTML response, admin route: Dynamic Page Cache is ignoring'); - $this->drupalGet('dynamic-page-cache-test/response/admin'); - $this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response returned, plain response, admin route: Dynamic Page Cache is ignoring'); - $this->drupalGet('dynamic-page-cache-test/cacheable-response/admin'); - $this->assertFalse($this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Response returned, cacheable response, admin route: Dynamic Page Cache is ignoring'); - - // Max-age = 0 responses are ignored by Dynamic Page Cache. - $this->drupalGet('dynamic-page-cache-test/html/uncacheable/max-age'); - $this->assertEqual('UNCACHEABLE', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response, but uncacheable: Dynamic Page Cache is running, but not caching.'); - - // 'user' cache context responses are ignored by Dynamic Page Cache. - $this->drupalGet('dynamic-page-cache-test/html/uncacheable/contexts'); - $this->assertEqual('UNCACHEABLE', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'Render array returned, rendered as HTML response, but uncacheable: Dynamic Page Cache is running, but not caching.'); - - // 'current-temperature' cache tag responses are ignored by Dynamic Page - // Cache. - $this->drupalGet('dynamic-page-cache-test/html/uncacheable/tags'); - $this->assertEqual('MISS', $this->drupalGetHeader(DynamicPageCacheSubscriber::HEADER), 'By default, Drupal has no auto-placeholdering cache tags.'); - } - -} diff --git a/core/modules/editor/src/Tests/EditorAdminTest.php b/core/modules/editor/tests/src/Functional/EditorAdminTest.php similarity index 98% rename from core/modules/editor/src/Tests/EditorAdminTest.php rename to core/modules/editor/tests/src/Functional/EditorAdminTest.php index 97d47b1..8ac2459 100644 --- a/core/modules/editor/src/Tests/EditorAdminTest.php +++ b/core/modules/editor/tests/src/Functional/EditorAdminTest.php @@ -1,19 +1,19 @@ drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - - $this->user = $this->drupalCreateUser(array('create page content', 'access content')); - $this->drupalLogin($this->user); - $this->testNode = $this->drupalCreateNode(array('type' => 'page', 'uid' => $this->user->id())); - } - - /** - * Get node read timestamps from the server for the current user. - * - * @param array $node_ids - * An array of node IDs. - * - * @return string - * The response body. - */ - protected function getNodeReadTimestamps(array $node_ids) { - // Build POST values. - $post = array(); - for ($i = 0; $i < count($node_ids); $i++) { - $post['node_ids[' . $i . ']'] = $node_ids[$i]; - } - - // Serialize POST values. - foreach ($post as $key => $value) { - // Encode according to application/x-www-form-urlencoded - // Both names and values needs to be urlencoded, according to - // http://www.w3.org/TR/html4/interact/forms.html#h-17.13.4.1 - $post[$key] = urlencode($key) . '=' . urlencode($value); - } - $post = implode('&', $post); - - // Perform HTTP request. - return $this->curlExec(array( - CURLOPT_URL => \Drupal::url('history.get_last_node_view', array(), array('absolute' => TRUE)), - CURLOPT_POST => TRUE, - CURLOPT_POSTFIELDS => $post, - CURLOPT_HTTPHEADER => array( - 'Accept: application/json', - 'Content-Type: application/x-www-form-urlencoded', - ), - )); - } - - /** - * Mark a node as read for the current user. - * - * @param int $node_id - * A node ID. - * - * @return string - * The response body. - */ - protected function markNodeAsRead($node_id) { - return $this->curlExec(array( - CURLOPT_URL => \Drupal::url('history.read_node', array('node' => $node_id), array('absolute' => TRUE)), - CURLOPT_HTTPHEADER => array( - 'Accept: application/json', - ), - )); - } - - /** - * Verifies that the history endpoints work. - */ - function testHistory() { - $nid = $this->testNode->id(); - - // Retrieve "last read" timestamp for test node, for the current user. - $response = $this->getNodeReadTimestamps(array($nid)); - $this->assertResponse(200); - $json = Json::decode($response); - $this->assertIdentical(array(1 => 0), $json, 'The node has not yet been read.'); - - // View the node. - $this->drupalGet('node/' . $nid); - $this->assertCacheContext('user.roles:authenticated'); - // JavaScript present to record the node read. - $settings = $this->getDrupalSettings(); - $libraries = explode(',', $settings['ajaxPageState']['libraries']); - $this->assertTrue(in_array('history/mark-as-read', $libraries), 'history/mark-as-read library is present.'); - $this->assertEqual([$nid => TRUE], $settings['history']['nodesToMarkAsRead'], 'drupalSettings to mark node as read are present.'); - - // Simulate JavaScript: perform HTTP request to mark node as read. - $response = $this->markNodeAsRead($nid); - $this->assertResponse(200); - $timestamp = Json::decode($response); - $this->assertTrue(is_numeric($timestamp), 'Node has been marked as read. Timestamp received.'); - - // Retrieve "last read" timestamp for test node, for the current user. - $response = $this->getNodeReadTimestamps(array($nid)); - $this->assertResponse(200); - $json = Json::decode($response); - $this->assertIdentical(array(1 => $timestamp), $json, 'The node has been read.'); - - // Failing to specify node IDs for the first endpoint should return a 404. - $this->getNodeReadTimestamps(array()); - $this->assertResponse(404); - - // Accessing either endpoint as the anonymous user should return a 403. - $this->drupalLogout(); - $this->getNodeReadTimestamps(array($nid)); - $this->assertResponse(403); - $this->getNodeReadTimestamps(array()); - $this->assertResponse(403); - $this->markNodeAsRead($nid); - $this->assertResponse(403); - } - -} diff --git a/core/modules/image/src/Tests/FileMoveTest.php b/core/modules/image/tests/src/Functional/FileMoveTest.php similarity index 92% rename from core/modules/image/src/Tests/FileMoveTest.php rename to core/modules/image/tests/src/Functional/FileMoveTest.php index 1957994..3ac543e 100644 --- a/core/modules/image/src/Tests/FileMoveTest.php +++ b/core/modules/image/tests/src/Functional/FileMoveTest.php @@ -1,9 +1,9 @@ " - */ - public function testMenuBlock() { - $url = Url::fromRoute('test_page_test.test_page'); - - // Create a Llama menu, add a link to it and place the corresponding block. - $menu = Menu::create(array( - 'id' => 'llama', - 'label' => 'Llama', - 'description' => 'Description text', - )); - $menu->save(); - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - // Move a link into the new menu. - $menu_link = $menu_link_manager->updateDefinition('test_page_test.test_page', array('menu_name' => 'llama', 'parent' => '')); - $block = $this->drupalPlaceBlock('system_menu_block:llama', array('label' => 'Llama', 'provider' => 'system', 'region' => 'footer')); - - // Prime the page cache. - $this->verifyPageCache($url, 'MISS'); - - // Verify a cache hit, but also the presence of the correct cache tags. - $expected_tags = array( - 'rendered', - 'block_view', - 'config:block_list', - 'config:block.block.' . $block->id(), - 'config:system.menu.llama', - // The cache contexts associated with the (in)accessible menu links are - // bubbled. - 'config:user.role.anonymous', - ); - $this->verifyPageCache($url, 'HIT', $expected_tags); - - // Verify that after modifying the menu, there is a cache miss. - $this->pass('Test modification of menu.', 'Debug'); - $menu->set('label', 'Awesome llama'); - $menu->save(); - $this->verifyPageCache($url, 'MISS'); - - // Verify a cache hit. - $this->verifyPageCache($url, 'HIT'); - - // Verify that after modifying the menu link weight, there is a cache miss. - $menu_link_manager->updateDefinition('test_page_test.test_page', array('weight' => -10)); - $this->pass('Test modification of menu link.', 'Debug'); - $this->verifyPageCache($url, 'MISS'); - - // Verify a cache hit. - $this->verifyPageCache($url, 'HIT'); - - // Verify that after adding a menu link, there is a cache miss. - $this->pass('Test addition of menu link.', 'Debug'); - $menu_link_2 = MenuLinkContent::create(array( - 'id' => '', - 'parent' => '', - 'title' => 'Alpaca', - 'menu_name' => 'llama', - 'link' => [[ - 'uri' => 'internal:/', - ]], - 'bundle' => 'menu_name', - )); - $menu_link_2->save(); - $this->verifyPageCache($url, 'MISS'); - - // Verify a cache hit. - $this->verifyPageCache($url, 'HIT'); - - // Verify that after resetting the first menu link, there is a cache miss. - $this->pass('Test reset of menu link.', 'Debug'); - $this->assertTrue($menu_link->isResettable(), 'First link can be reset'); - $menu_link = $menu_link_manager->resetLink($menu_link->getPluginId()); - $this->verifyPageCache($url, 'MISS'); - - // Verify a cache hit. - $this->verifyPageCache($url, 'HIT', $expected_tags); - - // Verify that after deleting the menu, there is a cache miss. - $this->pass('Test deletion of menu.', 'Debug'); - $menu->delete(); - $this->verifyPageCache($url, 'MISS'); - - // Verify a cache hit. - $this->verifyPageCache($url, 'HIT', ['config:block_list', 'config:user.role.anonymous', 'rendered']); - } - -} diff --git a/core/modules/menu_ui/src/Tests/MenuLanguageTest.php b/core/modules/menu_ui/src/Tests/MenuLanguageTest.php deleted file mode 100644 index 72865ea..0000000 --- a/core/modules/menu_ui/src/Tests/MenuLanguageTest.php +++ /dev/null @@ -1,133 +0,0 @@ -drupalLogin($this->drupalCreateUser(array('access administration pages', 'administer menu'))); - - // Add some custom languages. - foreach (array('aa', 'bb', 'cc', 'cs') as $language_code) { - ConfigurableLanguage::create(array( - 'id' => $language_code, - 'label' => $this->randomMachineName(), - ))->save(); - } - } - - /** - * Tests menu language settings and the defaults for menu link items. - */ - function testMenuLanguage() { - // Create a test menu to test the various language-related settings. - // Machine name has to be lowercase. - $menu_name = Unicode::strtolower($this->randomMachineName(16)); - $label = $this->randomString(); - $edit = array( - 'id' => $menu_name, - 'description' => '', - 'label' => $label, - 'langcode' => 'aa', - ); - $this->drupalPostForm('admin/structure/menu/add', $edit, t('Save')); - ContentLanguageSettings::loadByEntityTypeBundle('menu_link_content', 'menu_link_content') - ->setDefaultLangcode('bb') - ->setLanguageAlterable(TRUE) - ->save(); - - // Check menu language. - $this->assertOptionSelected('edit-langcode', $edit['langcode'], 'The menu language was correctly selected.'); - - // Test menu link language. - $link_path = '/'; - - // Add a menu link. - $link_title = $this->randomString(); - $edit = array( - 'title[0][value]' => $link_title, - 'link[0][uri]' => $link_path, - ); - $this->drupalPostForm("admin/structure/menu/manage/$menu_name/add", $edit, t('Save')); - // Check the link was added with the correct menu link default language. - $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $link_title)); - $menu_link = reset($menu_links); - $this->assertMenuLink($menu_link->getPluginId(), array( - 'menu_name' => $menu_name, - 'route_name' => '', - 'langcode' => 'bb', - )); - - // Edit menu link default, changing it to cc. - ContentLanguageSettings::loadByEntityTypeBundle('menu_link_content', 'menu_link_content') - ->setDefaultLangcode('cc') - ->setLanguageAlterable(TRUE) - ->save(); - - // Add a menu link. - $link_title = $this->randomString(); - $edit = array( - 'title[0][value]' => $link_title, - 'link[0][uri]' => $link_path, - ); - $this->drupalPostForm("admin/structure/menu/manage/$menu_name/add", $edit, t('Save')); - // Check the link was added with the correct new menu link default language. - $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $link_title)); - $menu_link = reset($menu_links); - $this->assertMenuLink($menu_link->getPluginId(), array( - 'menu_name' => $menu_name, - 'route_name' => '', - 'langcode' => 'cc', - )); - - // Now change the language of the new link to 'bb'. - $edit = array( - 'langcode[0][value]' => 'bb', - ); - $this->drupalPostForm('admin/structure/menu/item/' . $menu_link->id() . '/edit', $edit, t('Save')); - $this->assertMenuLink($menu_link->getPluginId(), array( - 'menu_name' => $menu_name, - 'route_name' => '', - 'langcode' => 'bb', - )); - - // Saving menu link items ends up on the edit menu page. To check the menu - // link has the correct language default on edit, go to the menu link edit - // page first. - $this->drupalGet('admin/structure/menu/item/' . $menu_link->id() . '/edit'); - // Check that the language selector has the correct default value. - $this->assertOptionSelected('edit-langcode-0-value', 'bb', 'The menu link language was correctly selected.'); - - // Edit menu to hide the language select on menu link item add. - ContentLanguageSettings::loadByEntityTypeBundle('menu_link_content', 'menu_link_content') - ->setDefaultLangcode('cc') - ->setLanguageAlterable(FALSE) - ->save(); - - // Check that the language selector is not available on menu link add page. - $this->drupalGet("admin/structure/menu/manage/$menu_name/add"); - $this->assertNoField('edit-langcode-0-value', 'The language selector field was hidden the page'); - } - -} diff --git a/core/modules/menu_ui/src/Tests/MenuLinkReorderTest.php b/core/modules/menu_ui/src/Tests/MenuLinkReorderTest.php deleted file mode 100644 index 0396f17..0000000 --- a/core/modules/menu_ui/src/Tests/MenuLinkReorderTest.php +++ /dev/null @@ -1,67 +0,0 @@ -drupalPlaceBlock('system_menu_block:main'); - - // Assert that the Home link is available. - $this->drupalGet('test-page'); - $this->assertLink('Home'); - - // The administrator user that can re-order menu links. - $this->administrator = $this->drupalCreateUser(array( - 'administer site configuration', - 'access administration pages', - 'administer menu', - )); - $this->drupalLogin($this->administrator); - - // Change the weight of the link to a non default value. - $edit = array( - 'links[menu_plugin_id:test_page_test.front_page][weight]' => -10, - ); - $this->drupalPostForm('admin/structure/menu/manage/main', $edit, t('Save')); - - // The link is still there. - $this->drupalGet('test-page'); - $this->assertLink('Home'); - - // Clear all caches. - $this->drupalPostForm('admin/config/development/performance', [], t('Clear all caches')); - - // Clearing all caches should not affect the state of the menu link. - $this->drupalGet('test-page'); - $this->assertLink('Home'); - - } - -} diff --git a/core/modules/menu_ui/src/Tests/MenuNodeTest.php b/core/modules/menu_ui/src/Tests/MenuNodeTest.php deleted file mode 100644 index 3fc8423..0000000 --- a/core/modules/menu_ui/src/Tests/MenuNodeTest.php +++ /dev/null @@ -1,341 +0,0 @@ -drupalPlaceBlock('system_menu_block:main'); - $this->drupalPlaceBlock('page_title_block'); - - $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - - $this->editor = $this->drupalCreateUser(array( - 'access administration pages', - 'administer content types', - 'administer menu', - 'create page content', - 'edit any page content', - 'delete any page content', - 'create content translations', - 'update content translations', - 'delete content translations', - 'translate any entity', - )); - $this->drupalLogin($this->editor); - } - - /** - * Test creating, editing, deleting menu links via node form widget. - */ - function testMenuNodeFormWidget() { - // Verify that cacheability metadata is bubbled from the menu link tree - // access checking that is performed when determining the "default parent - // item" options in menu_ui_form_node_type_form_alter(). The "log out" link - // adds the "user.roles:authenticated" cache context. - $this->drupalGet('admin/structure/types/manage/page'); - $this->assertCacheContext('user.roles:authenticated'); - - // Verify that the menu link title has the correct maxlength. - $max_length = \Drupal::entityManager()->getBaseFieldDefinitions('menu_link_content')['title']->getSetting('max_length'); - $this->drupalGet('node/add/page'); - $this->assertPattern('//', 'Menu link title field has correct maxlength in node add form.'); - - // Disable the default main menu, so that no menus are enabled. - $edit = array( - 'menu_options[main]' => FALSE, - ); - $this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type')); - - // Verify that no menu settings are displayed and nodes can be created. - $this->drupalGet('node/add/page'); - $this->assertText(t('Create Basic page')); - $this->assertNoText(t('Menu settings')); - $node_title = $this->randomMachineName(); - $edit = array( - 'title[0][value]' => $node_title, - 'body[0][value]' => $this->randomString(), - ); - $this->drupalPostForm(NULL, $edit, t('Save')); - $node = $this->drupalGetNodeByTitle($node_title); - $this->assertEqual($node->getTitle(), $edit['title[0][value]']); - - // Test that we cannot set a menu item from a menu that is not set as - // available. - $edit = array( - 'menu_options[tools]' => 1, - 'menu_parent' => 'main:', - ); - $this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type')); - $this->assertText(t('The selected menu item is not under one of the selected menus.')); - $this->assertNoRaw(t('The content type %name has been updated.', array('%name' => 'Basic page'))); - - // Enable Tools menu as available menu. - $edit = array( - 'menu_options[main]' => 1, - 'menu_options[tools]' => 1, - 'menu_parent' => 'main:', - ); - $this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type')); - $this->assertRaw(t('The content type %name has been updated.', array('%name' => 'Basic page'))); - - // Test that we can preview a node that will create a menu item. - $edit = array( - 'title[0][value]' => $node_title, - 'menu[enabled]' => 1, - 'menu[title]' => 'Test preview', - ); - $this->drupalPostForm('node/add/page', $edit, t('Preview')); - - // Create a node. - $node_title = $this->randomMachineName(); - $edit = array( - 'title[0][value]' => $node_title, - 'body[0][value]' => $this->randomString(), - ); - $this->drupalPostForm('node/add/page', $edit, t('Save')); - $node = $this->drupalGetNodeByTitle($node_title); - // Assert that there is no link for the node. - $this->drupalGet('test-page'); - $this->assertNoLink($node_title); - - // Edit the node, enable the menu link setting, but skip the link title. - $edit = array( - 'menu[enabled]' => 1, - ); - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); - // Assert that there is no link for the node. - $this->drupalGet('test-page'); - $this->assertNoLink($node_title); - - // Use not only the save button, but also the two special buttons: - // 'Save and publish' as well as 'Save and keep published'. - // These buttons just appear for 'administer nodes' users. - $admin_user = $this->drupalCreateUser([ - 'access administration pages', - 'administer content types', - 'administer nodes', - 'administer menu', - 'create page content', - 'edit any page content', - ]); - $this->drupalLogin($admin_user); - foreach (['Save and unpublish' => FALSE, 'Save and keep unpublished' => FALSE, 'Save and publish' => TRUE, 'Save and keep published' => TRUE] as $submit => $visible) { - $edit = [ - 'menu[enabled]' => 1, - 'menu[title]' => $node_title, - ]; - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, $submit); - // Assert that the link exists. - $this->drupalGet('test-page'); - if ($visible) { - $this->assertLink($node_title, 0, 'Found a menu link after submitted with ' . $submit); - } - else { - $this->assertNoLink($node_title, 'Found no menu link after submitted with ' . $submit); - } - } - - // Log back in as normal user. - $this->drupalLogin($this->editor); - // Edit the node and create a menu link. - $edit = array( - 'menu[enabled]' => 1, - 'menu[title]' => $node_title, - 'menu[weight]' => 17, - ); - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); - // Assert that the link exists. - $this->drupalGet('test-page'); - $this->assertLink($node_title); - - $this->drupalGet('node/' . $node->id() . '/edit'); - $this->assertFieldById('edit-menu-weight', 17, 'Menu weight correct in edit form'); - $this->assertPattern('//', 'Menu link title field has correct maxlength in node edit form.'); - - // Disable the menu link, then edit the node--the link should stay disabled. - $link_id = menu_ui_get_menu_link_defaults($node)['entity_id']; - /** @var \Drupal\menu_link_content\Entity\MenuLinkContent $link */ - $link = MenuLinkContent::load($link_id); - $link->set('enabled', FALSE); - $link->save(); - $this->drupalPostForm($node->urlInfo('edit-form'), $edit, t('Save')); - $link = MenuLinkContent::load($link_id); - $this->assertFalse($link->isEnabled(), 'Saving a node with a disabled menu link keeps the menu link disabled.'); - - // Edit the node and remove the menu link. - $edit = array( - 'menu[enabled]' => FALSE, - ); - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); - // Assert that there is no link for the node. - $this->drupalGet('test-page'); - $this->assertNoLink($node_title); - - // Add a menu link to the Administration menu. - $item = MenuLinkContent::create(array( - 'link' => [['uri' => 'entity:node/' . $node->id()]], - 'title' => $this->randomMachineName(16), - 'menu_name' => 'admin', - )); - $item->save(); - - // Assert that disabled Administration menu is not shown on the - // node/$nid/edit page. - $this->drupalGet('node/' . $node->id() . '/edit'); - $this->assertText('Provide a menu link', 'Link in not allowed menu not shown in node edit form'); - // Assert that the link is still in the Administration menu after save. - $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save')); - $link = MenuLinkContent::load($item->id()); - $this->assertTrue($link, 'Link in not allowed menu still exists after saving node'); - - // Move the menu link back to the Tools menu. - $item->menu_name->value = 'tools'; - $item->save(); - // Create a second node. - $child_node = $this->drupalCreateNode(array('type' => 'article')); - // Assign a menu link to the second node, being a child of the first one. - $child_item = MenuLinkContent::create(array( - 'link' => [['uri' => 'entity:node/' . $child_node->id()]], - 'title' => $this->randomMachineName(16), - 'parent' => $item->getPluginId(), - 'menu_name' => $item->getMenuName(), - )); - $child_item->save(); - // Edit the first node. - $this->drupalGet('node/' . $node->id() . '/edit'); - // Assert that it is not possible to set the parent of the first node to itself or the second node. - $this->assertNoOption('edit-menu-menu-parent', 'tools:' . $item->getPluginId()); - $this->assertNoOption('edit-menu-menu-parent', 'tools:' . $child_item->getPluginId()); - // Assert that unallowed Administration menu is not available in options. - $this->assertNoOption('edit-menu-menu-parent', 'admin:'); - } - - /** - * Testing correct loading and saving of menu links via node form widget in a multilingual environment. - */ - function testMultilingualMenuNodeFormWidget() { - // Setup languages. - $langcodes = array('de'); - foreach ($langcodes as $langcode) { - ConfigurableLanguage::createFromLangcode($langcode)->save(); - } - array_unshift($langcodes, \Drupal::languageManager()->getDefaultLanguage()->getId()); - - $config = \Drupal::service('config.factory')->getEditable('language.negotiation'); - // Ensure path prefix is used to determine the language. - $config->set('url.source', 'path_prefix'); - // Ensure that there's a path prefix set for english as well. - $config->set('url.prefixes.' . $langcodes[0], $langcodes[0]); - $config->save(); - - $this->rebuildContainer(); - - $languages = array(); - foreach ($langcodes as $langcode) { - $languages[$langcode] = ConfigurableLanguage::load($langcode); - } - - // Use a UI form submission to make the node type and menu link content entity translatable. - $this->drupalLogout(); - $this->drupalLogin($this->rootUser); - $edit = array( - 'entity_types[node]' => TRUE, - 'entity_types[menu_link_content]' => TRUE, - 'settings[node][page][settings][language][language_alterable]' => TRUE, - 'settings[node][page][translatable]' => TRUE, - 'settings[node][page][fields][title]' => TRUE, - 'settings[menu_link_content][menu_link_content][translatable]' => TRUE, - ); - $this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration')); - - // Log out and back in as normal user. - $this->drupalLogout(); - $this->drupalLogin($this->editor); - - // Create a node. - $node_title = $this->randomMachineName(8); - $node = Node::create([ - 'type' => 'page', - 'title' => $node_title, - 'body' => $this->randomMachineName(16), - 'uid' => $this->editor->id(), - 'status' => 1, - 'langcode' => $langcodes[0], - ]); - $node->save(); - - // Create translation. - $translated_node_title = $this->randomMachineName(8); - $node->addTranslation($langcodes[1], ['title' => $translated_node_title, 'body' => $this->randomMachineName(16), 'status' => 1]); - $node->save(); - - // Edit the node and create a menu link. - $edit = array( - 'menu[enabled]' => 1, - 'menu[title]' => $node_title, - 'menu[weight]' => 17, - ); - $options = array('language' => $languages[$langcodes[0]]); - $url = $node->toUrl('edit-form', $options); - $this->drupalPostForm($url, $edit, t('Save') . ' ' . t('(this translation)')); - - // Edit the node in a different language and translate the menu link. - $edit = array( - 'menu[enabled]' => 1, - 'menu[title]' => $translated_node_title, - 'menu[weight]' => 17, - ); - $options = array('language' => $languages[$langcodes[1]]); - $url = $node->toUrl('edit-form', $options); - $this->drupalPostForm($url, $edit, t('Save') . ' ' . t('(this translation)')); - - // Assert that the original link exists in the frontend. - $this->drupalGet('node/' . $node->id(), array('language' => $languages[$langcodes[0]])); - $this->assertLink($node_title); - - // Assert that the translated link exists in the frontend. - $this->drupalGet('node/' . $node->id(), array('language' => $languages[$langcodes[1]])); - $this->assertLink($translated_node_title); - - // Revisit the edit page in original language, check the loaded menu item title and save. - $options = array('language' => $languages[$langcodes[0]]); - $url = $node->toUrl('edit-form', $options); - $this->drupalGet($url); - $this->assertFieldById('edit-menu-title', $node_title); - $this->drupalPostForm(NULL, [], t('Save') . ' ' . t('(this translation)')); - - // Revisit the edit page of the translation and check the loaded menu item title. - $options = array('language' => $languages[$langcodes[1]]); - $url = $node->toUrl('edit-form', $options); - $this->drupalGet($url); - $this->assertFieldById('edit-menu-title', $translated_node_title); - } - -} diff --git a/core/modules/menu_ui/src/Tests/MenuTest.php b/core/modules/menu_ui/src/Tests/MenuTest.php deleted file mode 100644 index f640cc7..0000000 --- a/core/modules/menu_ui/src/Tests/MenuTest.php +++ /dev/null @@ -1,961 +0,0 @@ -drupalPlaceBlock('page_title_block'); - - $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); - - // Create users. - $this->adminUser = $this->drupalCreateUser(array('access administration pages', 'administer blocks', 'administer menu', 'create article content')); - $this->authenticatedUser = $this->drupalCreateUser(array()); - } - - /** - * Tests menu functionality using the admin and user interfaces. - */ - function testMenu() { - // Log in the user. - $this->drupalLogin($this->adminUser); - $this->items = array(); - - $this->menu = $this->addCustomMenu(); - $this->doMenuTests(); - $this->doTestMenuBlock(); - $this->addInvalidMenuLink(); - $this->addCustomMenuCRUD(); - - // Verify that the menu links rebuild is idempotent and leaves the same - // number of links in the table. - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - $before_count = $menu_link_manager->countMenuLinks(NULL); - $menu_link_manager->rebuild(); - $after_count = $menu_link_manager->countMenuLinks(NULL); - $this->assertIdentical($before_count, $after_count, 'MenuLinkManager::rebuild() does not add more links'); - // Do standard user tests. - // Log in the user. - $this->drupalLogin($this->authenticatedUser); - $this->verifyAccess(403); - - foreach ($this->items as $item) { - // Menu link URIs are stored as 'internal:/node/$nid'. - $node = Node::load(str_replace('internal:/node/', '', $item->link->uri)); - $this->verifyMenuLink($item, $node); - } - - // Log in the administrator. - $this->drupalLogin($this->adminUser); - - // Verify delete link exists and reset link does not exist. - $this->drupalGet('admin/structure/menu/manage/' . $this->menu->id()); - $this->assertLinkByHref(Url::fromRoute('entity.menu_link_content.delete_form', ['menu_link_content' => $this->items[0]->id()])->toString()); - $this->assertNoLinkByHref(Url::fromRoute('menu_ui.link_reset', ['menu_link_plugin' => $this->items[0]->getPluginId()])->toString()); - // Check delete and reset access. - $this->drupalGet('admin/structure/menu/item/' . $this->items[0]->id() . '/delete'); - $this->assertResponse(200); - $this->drupalGet('admin/structure/menu/link/' . $this->items[0]->getPluginId() . '/reset'); - $this->assertResponse(403); - - // Delete menu links. - foreach ($this->items as $item) { - $this->deleteMenuLink($item); - } - - // Delete custom menu. - $this->deleteCustomMenu(); - - // Modify and reset a standard menu link. - $instance = $this->getStandardMenuLink(); - $old_weight = $instance->getWeight(); - // Edit the static menu link. - $edit = array(); - $edit['weight'] = 10; - $id = $instance->getPluginId(); - $this->drupalPostForm("admin/structure/menu/link/$id/edit", $edit, t('Save')); - $this->assertResponse(200); - $this->assertText('The menu link has been saved.'); - $menu_link_manager->resetDefinitions(); - - $instance = $menu_link_manager->createInstance($instance->getPluginId()); - $this->assertEqual($edit['weight'], $instance->getWeight(), 'Saving an existing link updates the weight.'); - $this->resetMenuLink($instance, $old_weight); - } - - /** - * Adds a custom menu using CRUD functions. - */ - function addCustomMenuCRUD() { - // Add a new custom menu. - $menu_name = substr(hash('sha256', $this->randomMachineName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI); - $label = $this->randomMachineName(16); - - $menu = Menu::create(array( - 'id' => $menu_name, - 'label' => $label, - 'description' => 'Description text', - )); - $menu->save(); - - // Assert the new menu. - $this->drupalGet('admin/structure/menu/manage/' . $menu_name); - $this->assertRaw($label, 'Custom menu was added.'); - - // Edit the menu. - $new_label = $this->randomMachineName(16); - $menu->set('label', $new_label); - $menu->save(); - $this->drupalGet('admin/structure/menu/manage/' . $menu_name); - $this->assertRaw($new_label, 'Custom menu was edited.'); - } - - /** - * Creates a custom menu. - * - * @return \Drupal\system\Entity\Menu - * The custom menu that has been created. - */ - function addCustomMenu() { - // Try adding a menu using a menu_name that is too long. - $this->drupalGet('admin/structure/menu/add'); - $menu_name = substr(hash('sha256', $this->randomMachineName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI + 1); - $label = $this->randomMachineName(16); - $edit = array( - 'id' => $menu_name, - 'description' => '', - 'label' => $label, - ); - $this->drupalPostForm('admin/structure/menu/add', $edit, t('Save')); - - // Verify that using a menu_name that is too long results in a validation - // message. - $this->assertRaw(t('@name cannot be longer than %max characters but is currently %length characters long.', array( - '@name' => t('Menu name'), - '%max' => MENU_MAX_MENU_NAME_LENGTH_UI, - '%length' => Unicode::strlen($menu_name), - ))); - - // Change the menu_name so it no longer exceeds the maximum length. - $menu_name = substr(hash('sha256', $this->randomMachineName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI); - $edit['id'] = $menu_name; - $this->drupalPostForm('admin/structure/menu/add', $edit, t('Save')); - - // Verify that no validation error is given for menu_name length. - $this->assertNoRaw(t('@name cannot be longer than %max characters but is currently %length characters long.', array( - '@name' => t('Menu name'), - '%max' => MENU_MAX_MENU_NAME_LENGTH_UI, - '%length' => Unicode::strlen($menu_name), - ))); - // Verify that the confirmation message is displayed. - $this->assertRaw(t('Menu %label has been added.', array('%label' => $label))); - $this->drupalGet('admin/structure/menu'); - $this->assertText($label, 'Menu created'); - - // Confirm that the custom menu block is available. - $this->drupalGet('admin/structure/block/list/' . $this->config('system.theme')->get('default')); - $this->clickLinkPartialName('Place block'); - $this->assertText($label); - - // Enable the block. - $block = $this->drupalPlaceBlock('system_menu_block:' . $menu_name); - $this->blockPlacements[$menu_name] = $block->id(); - return Menu::load($menu_name); - } - - /** - * Deletes the locally stored custom menu. - * - * This deletes the custom menu that is stored in $this->menu and performs - * tests on the menu delete user interface. - */ - function deleteCustomMenu() { - $menu_name = $this->menu->id(); - $label = $this->menu->label(); - - // Delete custom menu. - $this->drupalPostForm("admin/structure/menu/manage/$menu_name/delete", array(), t('Delete')); - $this->assertResponse(200); - $this->assertRaw(t('The menu %title has been deleted.', array('%title' => $label)), 'Custom menu was deleted'); - $this->assertNull(Menu::load($menu_name), 'Custom menu was deleted'); - // Test if all menu links associated with the menu were removed from - // database. - $result = entity_load_multiple_by_properties('menu_link_content', array('menu_name' => $menu_name)); - $this->assertFalse($result, 'All menu links associated with the custom menu were deleted.'); - - // Make sure there's no delete button on system menus. - $this->drupalGet('admin/structure/menu/manage/main'); - $this->assertNoRaw('edit-delete', 'The delete button was not found'); - - // Try to delete the main menu. - $this->drupalGet('admin/structure/menu/manage/main/delete'); - $this->assertText(t('You are not authorized to access this page.')); - } - - /** - * Tests menu functionality. - */ - function doMenuTests() { - $menu_name = $this->menu->id(); - - // Test the 'Add link' local action. - $this->drupalGet(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name])); - - $this->clickLink(t('Add link')); - $link_title = $this->randomString(); - $this->drupalPostForm(NULL, array('link[0][uri]' => '/', 'title[0][value]' => $link_title), t('Save')); - $this->assertUrl(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name])); - // Test the 'Edit' operation. - $this->clickLink(t('Edit')); - $this->assertFieldByName('title[0][value]', $link_title); - $link_title = $this->randomString(); - $this->drupalPostForm(NULL, array('title[0][value]' => $link_title), t('Save')); - $this->assertUrl(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name])); - // Test the 'Delete' operation. - $this->clickLink(t('Delete')); - $this->assertRaw(t('Are you sure you want to delete the custom menu link %item?', array('%item' => $link_title))); - $this->drupalPostForm(NULL, array(), t('Delete')); - $this->assertUrl(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name])); - - // Add nodes to use as links for menu links. - $node1 = $this->drupalCreateNode(array('type' => 'article')); - $node2 = $this->drupalCreateNode(array('type' => 'article')); - $node3 = $this->drupalCreateNode(array('type' => 'article')); - $node4 = $this->drupalCreateNode(array('type' => 'article')); - // Create a node with an alias. - $node5 = $this->drupalCreateNode(array( - 'type' => 'article', - 'path' => array( - 'alias' => '/node5', - ), - )); - - // Verify add link button. - $this->drupalGet('admin/structure/menu'); - $this->assertLinkByHref('admin/structure/menu/manage/' . $menu_name . '/add', 0, "The add menu link button URL is correct"); - - // Verify form defaults. - $this->doMenuLinkFormDefaultsTest(); - - // Add menu links. - $item1 = $this->addMenuLink('', '/node/' . $node1->id(), $menu_name, TRUE); - $item2 = $this->addMenuLink($item1->getPluginId(), '/node/' . $node2->id(), $menu_name, FALSE); - $item3 = $this->addMenuLink($item2->getPluginId(), '/node/' . $node3->id(), $menu_name); - - // Hierarchy - // <$menu_name> - // - item1 - // -- item2 - // --- item3 - - $this->assertMenuLink($item1->getPluginId(), array( - 'children' => array($item2->getPluginId(), $item3->getPluginId()), - 'parents' => array($item1->getPluginId()), - // We assert the language code here to make sure that the language - // selection element degrades gracefully without the Language module. - 'langcode' => 'en', - )); - $this->assertMenuLink($item2->getPluginId(), array( - 'children' => array($item3->getPluginId()), - 'parents' => array($item2->getPluginId(), $item1->getPluginId()), - // See above. - 'langcode' => 'en', - )); - $this->assertMenuLink($item3->getPluginId(), array( - 'children' => array(), - 'parents' => array($item3->getPluginId(), $item2->getPluginId(), $item1->getPluginId()), - // See above. - 'langcode' => 'en', - )); - - // Verify menu links. - $this->verifyMenuLink($item1, $node1); - $this->verifyMenuLink($item2, $node2, $item1, $node1); - $this->verifyMenuLink($item3, $node3, $item2, $node2); - - // Add more menu links. - $item4 = $this->addMenuLink('', '/node/' . $node4->id(), $menu_name); - $item5 = $this->addMenuLink($item4->getPluginId(), '/node/' . $node5->id(), $menu_name); - // Create a menu link pointing to an alias. - $item6 = $this->addMenuLink($item4->getPluginId(), '/node5', $menu_name, TRUE, '0'); - - // Hierarchy - // <$menu_name> - // - item1 - // -- item2 - // --- item3 - // - item4 - // -- item5 - // -- item6 - - $this->assertMenuLink($item4->getPluginId(), array( - 'children' => array($item5->getPluginId(), $item6->getPluginId()), - 'parents' => array($item4->getPluginId()), - // See above. - 'langcode' => 'en', - )); - $this->assertMenuLink($item5->getPluginId(), array( - 'children' => array(), - 'parents' => array($item5->getPluginId(), $item4->getPluginId()), - 'langcode' => 'en', - )); - $this->assertMenuLink($item6->getPluginId(), array( - 'children' => array(), - 'parents' => array($item6->getPluginId(), $item4->getPluginId()), - 'route_name' => 'entity.node.canonical', - 'route_parameters' => array('node' => $node5->id()), - 'url' => '', - // See above. - 'langcode' => 'en', - )); - - // Modify menu links. - $this->modifyMenuLink($item1); - $this->modifyMenuLink($item2); - - // Toggle menu links. - $this->toggleMenuLink($item1); - $this->toggleMenuLink($item2); - - // Move link and verify that descendants are updated. - $this->moveMenuLink($item2, $item5->getPluginId(), $menu_name); - // Hierarchy - // <$menu_name> - // - item1 - // - item4 - // -- item5 - // --- item2 - // ---- item3 - // -- item6 - - $this->assertMenuLink($item1->getPluginId(), array( - 'children' => array(), - 'parents' => array($item1->getPluginId()), - // See above. - 'langcode' => 'en', - )); - $this->assertMenuLink($item4->getPluginId(), array( - 'children' => array($item5->getPluginId(), $item6->getPluginId(), $item2->getPluginId(), $item3->getPluginId()), - 'parents' => array($item4->getPluginId()), - // See above. - 'langcode' => 'en', - )); - - $this->assertMenuLink($item5->getPluginId(), array( - 'children' => array($item2->getPluginId(), $item3->getPluginId()), - 'parents' => array($item5->getPluginId(), $item4->getPluginId()), - // See above. - 'langcode' => 'en', - )); - $this->assertMenuLink($item2->getPluginId(), array( - 'children' => array($item3->getPluginId()), - 'parents' => array($item2->getPluginId(), $item5->getPluginId(), $item4->getPluginId()), - // See above. - 'langcode' => 'en', - )); - $this->assertMenuLink($item3->getPluginId(), array( - 'children' => array(), - 'parents' => array($item3->getPluginId(), $item2->getPluginId(), $item5->getPluginId(), $item4->getPluginId()), - // See above. - 'langcode' => 'en', - )); - - // Add 102 menu links with increasing weights, then make sure the last-added - // item's weight doesn't get changed because of the old hardcoded delta=50. - $items = array(); - for ($i = -50; $i <= 51; $i++) { - $items[$i] = $this->addMenuLink('', '/node/' . $node1->id(), $menu_name, TRUE, strval($i)); - } - $this->assertMenuLink($items[51]->getPluginId(), array('weight' => '51')); - - // Disable a link and then re-enable the link via the overview form. - $this->disableMenuLink($item1); - $edit = array(); - $edit['links[menu_plugin_id:' . $item1->getPluginId() . '][enabled]'] = TRUE; - $this->drupalPostForm('admin/structure/menu/manage/' . $item1->getMenuName(), $edit, t('Save')); - - // Mark item2, item4 and item5 as expanded. - // This is done in order to show them on the frontpage. - $item2->expanded->value = 1; - $item2->save(); - $item4->expanded->value = 1; - $item4->save(); - $item5->expanded->value = 1; - $item5->save(); - - // Verify in the database. - $this->assertMenuLink($item1->getPluginId(), array('enabled' => 1)); - - // Add an external link. - $item7 = $this->addMenuLink('', 'https://www.drupal.org', $menu_name); - $this->assertMenuLink($item7->getPluginId(), array('url' => 'https://www.drupal.org')); - - // Add menu item. - $item8 = $this->addMenuLink('', '/', $menu_name); - $this->assertMenuLink($item8->getPluginId(), array('route_name' => '')); - $this->drupalGet(''); - $this->assertResponse(200); - // Make sure we get routed correctly. - $this->clickLink($item8->getTitle()); - $this->assertResponse(200); - - // Check invalid menu link parents. - $this->checkInvalidParentMenuLinks(); - - // Save menu links for later tests. - $this->items[] = $item1; - $this->items[] = $item2; - } - - /** - * Ensures that the proper default values are set when adding a menu link - */ - protected function doMenuLinkFormDefaultsTest() { - $this->drupalGet("admin/structure/menu/manage/tools/add"); - $this->assertResponse(200); - - $this->assertFieldByName('title[0][value]', ''); - $this->assertFieldByName('link[0][uri]', ''); - - $this->assertNoFieldChecked('edit-expanded-value'); - $this->assertFieldChecked('edit-enabled-value'); - - $this->assertFieldByName('description[0][value]', ''); - $this->assertFieldByName('weight[0][value]', 0); - } - - /** - * Adds and removes a menu link with a query string and fragment. - */ - function testMenuQueryAndFragment() { - $this->drupalLogin($this->adminUser); - - // Make a path with query and fragment on. - $path = '/test-page?arg1=value1&arg2=value2'; - $item = $this->addMenuLink('', $path); - - $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit'); - $this->assertFieldByName('link[0][uri]', $path, 'Path is found with both query and fragment.'); - - // Now change the path to something without query and fragment. - $path = '/test-page'; - $this->drupalPostForm('admin/structure/menu/item/' . $item->id() . '/edit', array('link[0][uri]' => $path), t('Save')); - $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit'); - $this->assertFieldByName('link[0][uri]', $path, 'Path no longer has query or fragment.'); - - // Use #fragment and ensure that saving it does not lose its content. - $path = '?arg1=value#fragment'; - $item = $this->addMenuLink('', $path); - - $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit'); - $this->assertFieldByName('link[0][uri]', $path, 'Path is found with both query and fragment.'); - - $this->drupalPostForm('admin/structure/menu/item/' . $item->id() . '/edit', array(), t('Save')); - - $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit'); - $this->assertFieldByName('link[0][uri]', $path, 'Path is found with both query and fragment.'); - } - - /** - * Tests renaming the built-in menu. - */ - function testSystemMenuRename() { - $this->drupalLogin($this->adminUser); - $edit = array( - 'label' => $this->randomMachineName(16), - ); - $this->drupalPostForm('admin/structure/menu/manage/main', $edit, t('Save')); - - // Make sure menu shows up with new name in block addition. - $default_theme = $this->config('system.theme')->get('default'); - $this->drupalget('admin/structure/block/list/' . $default_theme); - $this->clickLinkPartialName('Place block'); - $this->assertText($edit['label']); - } - - /** - * Tests that menu items pointing to unpublished nodes are editable. - */ - function testUnpublishedNodeMenuItem() { - $this->drupalLogin($this->drupalCreateUser(array('access administration pages', 'administer blocks', 'administer menu', 'create article content', 'bypass node access'))); - // Create an unpublished node. - $node = $this->drupalCreateNode(array( - 'type' => 'article', - 'status' => NODE_NOT_PUBLISHED, - )); - - $item = $this->addMenuLink('', '/node/' . $node->id()); - $this->modifyMenuLink($item); - - // Test that a user with 'administer menu' but without 'bypass node access' - // cannot see the menu item. - $this->drupalLogout(); - $this->drupalLogin($this->adminUser); - $this->drupalGet('admin/structure/menu/manage/' . $item->getMenuName()); - $this->assertNoText($item->getTitle(), "Menu link pointing to unpublished node is only visible to users with 'bypass node access' permission"); - // The cache contexts associated with the (in)accessible menu links are - // bubbled. See DefaultMenuLinkTreeManipulators::menuLinkCheckAccess(). - $this->assertCacheContext('user.permissions'); - } - - /** - * Tests the contextual links on a menu block. - */ - public function testBlockContextualLinks() { - $this->drupalLogin($this->drupalCreateUser(array('administer menu', 'access contextual links', 'administer blocks'))); - $custom_menu = $this->addCustomMenu(); - $this->addMenuLink('', '/', $custom_menu->id()); - $block = $this->drupalPlaceBlock('system_menu_block:' . $custom_menu->id(), array('label' => 'Custom menu', 'provider' => 'system')); - $this->drupalGet('test-page'); - - $id = 'block:block=' . $block->id() . ':langcode=en|menu:menu=' . $custom_menu->id() . ':langcode=en'; - // @see \Drupal\contextual\Tests\ContextualDynamicContextTest:assertContextualLinkPlaceHolder() - $this->assertRaw('
', format_string('Contextual link placeholder with id @id exists.', array('@id' => $id))); - - // Get server-rendered contextual links. - // @see \Drupal\contextual\Tests\ContextualDynamicContextTest:renderContextualLinks() - $post = array('ids[0]' => $id); - $response = $this->drupalPost('contextual/render', 'application/json', $post, array('query' => array('destination' => 'test-page'))); - $this->assertResponse(200); - $json = Json::decode($response); - $this->assertIdentical($json[$id], ''); - } - - /** - * Adds a menu link using the UI. - * - * @param string $parent - * Optional parent menu link id. - * @param string $path - * The path to enter on the form. Defaults to the front page. - * @param string $menu_name - * Menu name. Defaults to 'tools'. - * @param bool $expanded - * Whether or not this menu link is expanded. Setting this to TRUE should - * test whether it works when we do the authenticatedUser tests. Defaults - * to FALSE. - * @param string $weight - * Menu weight. Defaults to 0. - * - * @return \Drupal\menu_link_content\Entity\MenuLinkContent - * A menu link entity. - */ - function addMenuLink($parent = '', $path = '/', $menu_name = 'tools', $expanded = FALSE, $weight = '0') { - // View add menu link page. - $this->drupalGet("admin/structure/menu/manage/$menu_name/add"); - $this->assertResponse(200); - - $title = '!link_' . $this->randomMachineName(16); - $edit = array( - 'link[0][uri]' => $path, - 'title[0][value]' => $title, - 'description[0][value]' => '', - 'enabled[value]' => 1, - 'expanded[value]' => $expanded, - 'menu_parent' => $menu_name . ':' . $parent, - 'weight[0][value]' => $weight, - ); - - // Add menu link. - $this->drupalPostForm(NULL, $edit, t('Save')); - $this->assertResponse(200); - $this->assertText('The menu link has been saved.'); - - $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $title)); - - $menu_link = reset($menu_links); - $this->assertTrue($menu_link, 'Menu link was found in database.'); - $this->assertMenuLink($menu_link->getPluginId(), array('menu_name' => $menu_name, 'children' => array(), 'parent' => $parent)); - - return $menu_link; - } - - /** - * Attempts to add menu link with invalid path or no access permission. - */ - function addInvalidMenuLink() { - foreach (array('access' => '/admin/people/permissions') as $type => $link_path) { - $edit = array( - 'link[0][uri]' => $link_path, - 'title[0][value]' => 'title', - ); - $this->drupalPostForm("admin/structure/menu/manage/{$this->menu->id()}/add", $edit, t('Save')); - $this->assertRaw(t("The path '@link_path' is inaccessible.", array('@link_path' => $link_path)), 'Menu link was not created'); - } - } - - /** - * Tests that parent options are limited by depth when adding menu links. - */ - function checkInvalidParentMenuLinks() { - $last_link = NULL; - $created_links = array(); - - // Get the max depth of the tree. - $menu_link_tree = \Drupal::service('menu.link_tree'); - $max_depth = $menu_link_tree->maxDepth(); - - // Create a maximum number of menu links, each a child of the previous. - for ($i = 0; $i <= $max_depth - 1; $i++) { - $parent = $last_link ? 'tools:' . $last_link->getPluginId() : 'tools:'; - $title = 'title' . $i; - $edit = array( - 'link[0][uri]' => '/', - 'title[0][value]' => $title, - 'menu_parent' => $parent, - 'description[0][value]' => '', - 'enabled[value]' => 1, - 'expanded[value]' => FALSE, - 'weight[0][value]' => '0', - ); - $this->drupalPostForm("admin/structure/menu/manage/{$this->menu->id()}/add", $edit, t('Save')); - $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => $title)); - $last_link = reset($menu_links); - $created_links[] = 'tools:' . $last_link->getPluginId(); - } - - // The last link cannot be a parent in the new menu link form. - $this->drupalGet('admin/structure/menu/manage/admin/add'); - $value = 'tools:' . $last_link->getPluginId(); - $this->assertNoOption('edit-menu-parent', $value, 'The invalid option is not there.'); - - // All but the last link can be parents in the new menu link form. - array_pop($created_links); - foreach ($created_links as $key => $link) { - $this->assertOption('edit-menu-parent', $link, 'The valid option number ' . ($key + 1) . ' is there.'); - } - } - - /** - * Verifies a menu link using the UI. - * - * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item - * Menu link. - * @param object $item_node - * Menu link content node. - * @param \Drupal\menu_link_content\Entity\MenuLinkContent $parent - * Parent menu link. - * @param object $parent_node - * Parent menu link content node. - */ - function verifyMenuLink(MenuLinkContent $item, $item_node, MenuLinkContent $parent = NULL, $parent_node = NULL) { - // View home page. - $this->drupalGet(''); - $this->assertResponse(200); - - // Verify parent menu link. - if (isset($parent)) { - // Verify menu link. - $title = $parent->getTitle(); - $this->assertLink($title, 0, 'Parent menu link was displayed'); - - // Verify menu link link. - $this->clickLink($title); - $title = $parent_node->label(); - $this->assertTitle(t("@title | Drupal", array('@title' => $title)), 'Parent menu link link target was correct'); - } - - // Verify menu link. - $title = $item->getTitle(); - $this->assertLink($title, 0, 'Menu link was displayed'); - - // Verify menu link link. - $this->clickLink($title); - $title = $item_node->label(); - $this->assertTitle(t("@title | Drupal", array('@title' => $title)), 'Menu link link target was correct'); - } - - /** - * Changes the parent of a menu link using the UI. - * - * @param \Drupal\menu_link_content\MenuLinkContentInterface $item - * The menu link item to move. - * @param int $parent - * The id of the new parent. - * @param string $menu_name - * The menu the menu link will be moved to. - */ - function moveMenuLink(MenuLinkContent $item, $parent, $menu_name) { - $mlid = $item->id(); - - $edit = array( - 'menu_parent' => $menu_name . ':' . $parent, - ); - $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save')); - $this->assertResponse(200); - } - - /** - * Modifies a menu link using the UI. - * - * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item - * Menu link entity. - */ - function modifyMenuLink(MenuLinkContent $item) { - $item->title->value = $this->randomMachineName(16); - - $mlid = $item->id(); - $title = $item->getTitle(); - - // Edit menu link. - $edit = array(); - $edit['title[0][value]'] = $title; - $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save')); - $this->assertResponse(200); - $this->assertText('The menu link has been saved.'); - // Verify menu link. - $this->drupalGet('admin/structure/menu/manage/' . $item->getMenuName()); - $this->assertText($title, 'Menu link was edited'); - } - - /** - * Resets a standard menu link using the UI. - * - * @param \Drupal\Core\Menu\MenuLinkInterface $menu_link - * The Menu link. - * @param int $old_weight - * Original title for menu link. - */ - function resetMenuLink(MenuLinkInterface $menu_link, $old_weight) { - // Reset menu link. - $this->drupalPostForm("admin/structure/menu/link/{$menu_link->getPluginId()}/reset", array(), t('Reset')); - $this->assertResponse(200); - $this->assertRaw(t('The menu link was reset to its default settings.'), 'Menu link was reset'); - - // Verify menu link. - $instance = \Drupal::service('plugin.manager.menu.link')->createInstance($menu_link->getPluginId()); - $this->assertEqual($old_weight, $instance->getWeight(), 'Resets to the old weight.'); - } - - /** - * Deletes a menu link using the UI. - * - * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item - * Menu link. - */ - function deleteMenuLink(MenuLinkContent $item) { - $mlid = $item->id(); - $title = $item->getTitle(); - - // Delete menu link. - $this->drupalPostForm("admin/structure/menu/item/$mlid/delete", array(), t('Delete')); - $this->assertResponse(200); - $this->assertRaw(t('The menu link %title has been deleted.', array('%title' => $title)), 'Menu link was deleted'); - - // Verify deletion. - $this->drupalGet(''); - $this->assertNoText($title, 'Menu link was deleted'); - } - - /** - * Alternately disables and enables a menu link. - * - * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item - * Menu link. - */ - function toggleMenuLink(MenuLinkContent $item) { - $this->disableMenuLink($item); - - // Verify menu link is absent. - $this->drupalGet(''); - $this->assertNoText($item->getTitle(), 'Menu link was not displayed'); - $this->enableMenuLink($item); - - // Verify menu link is displayed. - $this->drupalGet(''); - $this->assertText($item->getTitle(), 'Menu link was displayed'); - } - - /** - * Disables a menu link. - * - * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item - * Menu link. - */ - function disableMenuLink(MenuLinkContent $item) { - $mlid = $item->id(); - $edit['enabled[value]'] = FALSE; - $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save')); - - // Unlike most other modules, there is no confirmation message displayed. - // Verify in the database. - $this->assertMenuLink($item->getPluginId(), array('enabled' => 0)); - } - - /** - * Enables a menu link. - * - * @param \Drupal\menu_link_content\Entity\MenuLinkContent $item - * Menu link. - */ - function enableMenuLink(MenuLinkContent $item) { - $mlid = $item->id(); - $edit['enabled[value]'] = TRUE; - $this->drupalPostForm("admin/structure/menu/item/$mlid/edit", $edit, t('Save')); - - // Verify in the database. - $this->assertMenuLink($item->getPluginId(), array('enabled' => 1)); - } - - /** - * Tests if administrative users other than user 1 can access the menu parents - * AJAX callback. - */ - public function testMenuParentsJsAccess() { - $admin = $this->drupalCreateUser(array('administer menu')); - $this->drupalLogin($admin); - // Just check access to the callback overall, the POST data is irrelevant. - $this->drupalGetAjax('admin/structure/menu/parents'); - $this->assertResponse(200); - - // Do standard user tests. - // Log in the user. - $this->drupalLogin($this->authenticatedUser); - $this->drupalGetAjax('admin/structure/menu/parents'); - $this->assertResponse(403); - } - - /** - * Returns standard menu link. - * - * @return \Drupal\Core\Menu\MenuLinkInterface - * A menu link plugin. - */ - private function getStandardMenuLink() { - // Retrieve menu link id of the Log out menu link, which will always be on - // the front page. - /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */ - $menu_link_manager = \Drupal::service('plugin.manager.menu.link'); - $instance = $menu_link_manager->getInstance(['id' => 'user.logout']); - - $this->assertTrue((bool) $instance, 'Standard menu link was loaded'); - return $instance; - } - - /** - * Verifies the logged in user has the desired access to various menu pages. - * - * @param int $response - * (optional) The expected HTTP response code. Defaults to 200. - */ - private function verifyAccess($response = 200) { - // View menu help page. - $this->drupalGet('admin/help/menu'); - $this->assertResponse($response); - if ($response == 200) { - $this->assertText(t('Menu'), 'Menu help was displayed'); - } - - // View menu build overview page. - $this->drupalGet('admin/structure/menu'); - $this->assertResponse($response); - if ($response == 200) { - $this->assertText(t('Menus'), 'Menu build overview page was displayed'); - } - - // View tools menu customization page. - $this->drupalGet('admin/structure/menu/manage/' . $this->menu->id()); - $this->assertResponse($response); - if ($response == 200) { - $this->assertText(t('Tools'), 'Tools menu page was displayed'); - } - - // View menu edit page for a static link. - $item = $this->getStandardMenuLink(); - $this->drupalGet('admin/structure/menu/link/' . $item->getPluginId() . '/edit'); - $this->assertResponse($response); - if ($response == 200) { - $this->assertText(t('Edit menu item'), 'Menu edit page was displayed'); - } - - // View add menu page. - $this->drupalGet('admin/structure/menu/add'); - $this->assertResponse($response); - if ($response == 200) { - $this->assertText(t('Menus'), 'Add menu page was displayed'); - } - } - - /** - * Tests menu block settings. - */ - protected function doTestMenuBlock() { - $menu_id = $this->menu->id(); - $block_id = $this->blockPlacements[$menu_id]; - $this->drupalGet('admin/structure/block/manage/' . $block_id); - $this->drupalPostForm(NULL, [ - 'settings[depth]' => 3, - 'settings[level]' => 2, - ], t('Save block')); - $block = Block::load($block_id); - $settings = $block->getPlugin()->getConfiguration(); - $this->assertEqual($settings['depth'], 3); - $this->assertEqual($settings['level'], 2); - // Reset settings. - $block->getPlugin()->setConfigurationValue('depth', 0); - $block->getPlugin()->setConfigurationValue('level', 1); - $block->save(); - } - -} diff --git a/core/modules/menu_ui/src/Tests/MenuUninstallTest.php b/core/modules/menu_ui/src/Tests/MenuUninstallTest.php deleted file mode 100644 index b1ea03c..0000000 --- a/core/modules/menu_ui/src/Tests/MenuUninstallTest.php +++ /dev/null @@ -1,33 +0,0 @@ -uninstall(array('menu_ui')); - - \Drupal::entityManager()->getStorage('menu')->resetCache(array('admin')); - - $this->assertTrue(Menu::load('admin'), 'The \'admin\' menu still exists after uninstalling Menu UI module.'); - } - -} diff --git a/core/modules/menu_ui/src/Tests/MenuWebTestBase.php b/core/modules/menu_ui/src/Tests/MenuWebTestBase.php deleted file mode 100644 index c08fc14..0000000 --- a/core/modules/menu_ui/src/Tests/MenuWebTestBase.php +++ /dev/null @@ -1,77 +0,0 @@ -resetDefinitions(); - // Reset the static load cache. - \Drupal::entityManager()->getStorage('menu_link_content')->resetCache(); - $definition = $menu_link_manager->getDefinition($menu_plugin_id); - - $entity = NULL; - - // Pull the path from the menu link content. - if (strpos($menu_plugin_id, 'menu_link_content') === 0) { - list(, $uuid) = explode(':', $menu_plugin_id, 2); - /** @var \Drupal\menu_link_content\Entity\MenuLinkContent $entity */ - $entity = \Drupal::entityManager()->loadEntityByUuid('menu_link_content', $uuid); - } - - if (isset($expected_item['children'])) { - $child_ids = array_values($menu_link_manager->getChildIds($menu_plugin_id)); - sort($expected_item['children']); - if ($child_ids) { - sort($child_ids); - } - $this->assertEqual($expected_item['children'], $child_ids); - unset($expected_item['children']); - } - - if (isset($expected_item['parents'])) { - $parent_ids = array_values($menu_link_manager->getParentIds($menu_plugin_id)); - $this->assertEqual($expected_item['parents'], $parent_ids); - unset($expected_item['parents']); - } - - if (isset($expected_item['langcode']) && $entity) { - $this->assertEqual($entity->langcode->value, $expected_item['langcode']); - unset($expected_item['langcode']); - } - - if (isset($expected_item['enabled']) && $entity) { - $this->assertEqual($entity->enabled->value, $expected_item['enabled']); - unset($expected_item['enabled']); - } - - foreach ($expected_item as $key => $value) { - $this->assertTrue(isset($definition[$key])); - $this->assertEqual($definition[$key], $value); - } - } - -} diff --git a/core/modules/migrate_drupal/src/Tests/StubTestTrait.php b/core/modules/migrate_drupal/tests/src/Functional/StubTestTrait.php similarity index 97% rename from core/modules/migrate_drupal/src/Tests/StubTestTrait.php rename to core/modules/migrate_drupal/tests/src/Functional/StubTestTrait.php index 3bf0028..661427f 100644 --- a/core/modules/migrate_drupal/src/Tests/StubTestTrait.php +++ b/core/modules/migrate_drupal/tests/src/Functional/StubTestTrait.php @@ -1,6 +1,6 @@ drupalLogin($this->rootUser); - $this->drupalGet('upgrade'); - $this->assertResponse(200); - $this->assertText(t('Upgrade')); - - $user = $this->createUser(['administer software updates']); - $this->drupalLogin($user); - $this->drupalGet('upgrade'); - $this->assertResponse(403); - $this->assertNoText(t('Upgrade')); - } - -} diff --git a/core/modules/migrate_drupal_ui/src/Tests/MigrateUpgradeTestBase.php b/core/modules/migrate_drupal_ui/src/Tests/MigrateUpgradeTestBase.php deleted file mode 100644 index c139291..0000000 --- a/core/modules/migrate_drupal_ui/src/Tests/MigrateUpgradeTestBase.php +++ /dev/null @@ -1,196 +0,0 @@ -createMigrationConnection(); - $this->sourceDatabase = Database::getConnection('default', 'migrate_drupal_ui'); - - // Log in as user 1. Migrations in the UI can only be performed as user 1. - $this->drupalLogin($this->rootUser); - } - - /** - * Loads a database fixture into the source database connection. - * - * @param string $path - * Path to the dump file. - */ - protected function loadFixture($path) { - $default_db = Database::getConnection()->getKey(); - Database::setActiveConnection($this->sourceDatabase->getKey()); - - if (substr($path, -3) == '.gz') { - $path = 'compress.zlib://' . $path; - } - require $path; - - Database::setActiveConnection($default_db); - } - - /** - * Changes the database connection to the prefixed one. - * - * @todo Remove when we don't use global. https://www.drupal.org/node/2552791 - */ - protected function createMigrationConnection() { - $connection_info = Database::getConnectionInfo('default')['default']; - if ($connection_info['driver'] === 'sqlite') { - // Create database file in the test site's public file directory so that - // \Drupal\simpletest\TestBase::restoreEnvironment() will delete this once - // the test is complete. - $file = $this->publicFilesDirectory . '/' . $this->testId . '-migrate.db.sqlite'; - touch($file); - $connection_info['database'] = $file; - $connection_info['prefix'] = ''; - } - else { - $prefix = is_array($connection_info['prefix']) ? $connection_info['prefix']['default'] : $connection_info['prefix']; - // Simpletest uses fixed length prefixes. Create a new prefix for the - // source database. Adding to the end of the prefix ensures that - // \Drupal\simpletest\TestBase::restoreEnvironment() will remove the - // additional tables. - $connection_info['prefix'] = $prefix . '0'; - } - - Database::addConnectionInfo('migrate_drupal_ui', 'default', $connection_info); - } - - /** - * {@inheritdoc} - */ - protected function tearDown() { - Database::removeConnection('migrate_drupal_ui'); - parent::tearDown(); - } - - /** - * Executes all steps of migrations upgrade. - */ - protected function testMigrateUpgrade() { - $connection_options = $this->sourceDatabase->getConnectionOptions(); - $this->drupalGet('/upgrade'); - $this->assertText('Upgrade a site by importing it into a clean and empty new install of Drupal 8. You will lose any existing configuration once you import your site into it. See the online documentation for Drupal site upgrades for more detailed information.'); - - $this->drupalPostForm(NULL, [], t('Continue')); - $this->assertText('Provide credentials for the database of the Drupal site you want to upgrade.'); - $this->assertFieldByName('mysql[host]'); - - $driver = $connection_options['driver']; - $connection_options['prefix'] = $connection_options['prefix']['default']; - - // Use the driver connection form to get the correct options out of the - // database settings. This supports all of the databases we test against. - $drivers = drupal_get_database_types(); - $form = $drivers[$driver]->getFormOptions($connection_options); - $connection_options = array_intersect_key($connection_options, $form + $form['advanced_options']); - $edit = [ - $driver => $connection_options, - 'source_base_path' => $this->getSourceBasePath(), - ]; - if (count($drivers) !== 1) { - $edit['driver'] = $driver; - } - $edits = $this->translatePostValues($edit); - - // Ensure submitting the form with invalid database credentials gives us a - // nice warning. - $this->drupalPostForm(NULL, [$driver . '[database]' => 'wrong'] + $edits, t('Review upgrade')); - $this->assertText('Resolve the issue below to continue the upgrade.'); - - $this->drupalPostForm(NULL, $edits, t('Review upgrade')); - $this->assertResponse(200); - $this->assertText('Are you sure?'); - $this->drupalPostForm(NULL, [], t('Perform upgrade')); - $this->assertText(t('Congratulations, you upgraded Drupal!')); - - // Have to reset all the statics after migration to ensure entities are - // loadable. - $this->resetAll(); - - $expected_counts = $this->getEntityCounts(); - foreach (array_keys(\Drupal::entityTypeManager()->getDefinitions()) as $entity_type) { - $real_count = count(\Drupal::entityTypeManager()->getStorage($entity_type)->loadMultiple()); - $expected_count = isset($expected_counts[$entity_type]) ? $expected_counts[$entity_type] : 0; - $this->assertEqual($expected_count, $real_count, "Found $real_count $entity_type entities, expected $expected_count."); - } - - $version_tag = 'Drupal ' . $this->getLegacyDrupalVersion($this->sourceDatabase); - $plugin_manager = \Drupal::service('plugin.manager.migration'); - /** @var \Drupal\migrate\Plugin\Migration[] $all_migrations */ - $all_migrations = $plugin_manager->createInstancesByTag($version_tag); - foreach ($all_migrations as $migration) { - $id_map = $migration->getIdMap(); - foreach ($id_map as $source_id => $map) { - // Convert $source_id into a keyless array so that - // \Drupal\migrate\Plugin\migrate\id_map\Sql::getSourceHash() works as - // expected. - $source_id_values = array_values(unserialize($source_id)); - $row = $id_map->getRowBySource($source_id_values); - $destination = serialize($id_map->currentDestination()); - $message = "Successful migration of $source_id to $destination as part of the {$migration->id()} migration. The source row status is " . $row['source_row_status']; - // A completed migration should have maps with - // MigrateIdMapInterface::STATUS_IGNORED or - // MigrateIdMapInterface::STATUS_IMPORTED. - if ($row['source_row_status'] == MigrateIdMapInterface::STATUS_FAILED || $row['source_row_status'] == MigrateIdMapInterface::STATUS_NEEDS_UPDATE) { - $this->fail($message); - } - else { - $this->pass($message); - } - } - } - \Drupal::service('module_installer')->install(['forum']); - } - - /** - * Gets the source base path for the concrete test. - * - * @return string - * The source base path. - */ - abstract protected function getSourceBasePath(); - - /** - * Gets the expected number of entities per entity type after migration. - * - * @return int[] - * An array of expected counts keyed by entity type ID. - */ - abstract protected function getEntityCounts(); - -} diff --git a/core/modules/node/src/Tests/AssertButtonsTrait.php b/core/modules/node/tests/src/Functional/AssertButtonsTrait.php similarity index 97% rename from core/modules/node/src/Tests/AssertButtonsTrait.php rename to core/modules/node/tests/src/Functional/AssertButtonsTrait.php index 58d484d..3f3c87f 100644 --- a/core/modules/node/src/Tests/AssertButtonsTrait.php +++ b/core/modules/node/tests/src/Functional/AssertButtonsTrait.php @@ -1,6 +1,6 @@ enablePageCaching(); - } - - /** - * Test that cache tags are properly bubbled up to the page level. - */ - function testPageCacheTags() { - // Create two nodes. - $author_1 = $this->drupalCreateUser(); - $node_1 = $this->drupalCreateNode(array( - 'uid' => $author_1->id(), - 'title' => 'Node 1', - 'body' => array( - 0 => array('value' => 'Body 1', 'format' => 'basic_html'), - ), - 'promote' => NODE_PROMOTED, - )); - $author_2 = $this->drupalCreateUser(); - $node_2 = $this->drupalCreateNode(array( - 'uid' => $author_2->id(), - 'title' => 'Node 2', - 'body' => array( - 0 => array('value' => 'Body 2', 'format' => 'full_html'), - ), - 'promote' => NODE_PROMOTED, - )); - - // Place a block, but only make it visible on full node page 2. - $block = $this->drupalPlaceBlock('views_block:comments_recent-block_1', array( - 'visibility' => array( - 'request_path' => array( - 'pages' => '/node/' . $node_2->id(), - ), - ), - )); - - $cache_contexts = [ - 'languages:' . LanguageInterface::TYPE_INTERFACE, - 'route', - 'theme', - 'timezone', - 'user', - // The placed block is only visible on certain URLs through a visibility - // condition. - 'url.path', - 'url.query_args:' . MainContentViewSubscriber::WRAPPER_FORMAT, - ]; - - // Full node page 1. - $this->assertPageCacheContextsAndTags($node_1->urlInfo(), $cache_contexts, array( - 'rendered', - 'block_view', - 'config:block_list', - 'config:block.block.bartik_branding', - 'config:block.block.bartik_breadcrumbs', - 'config:block.block.bartik_content', - 'config:block.block.bartik_tools', - 'config:block.block.bartik_footer', - 'config:block.block.bartik_help', - 'config:block.block.bartik_search', - 'config:block.block.' . $block->id(), - 'config:block.block.bartik_powered', - 'config:block.block.bartik_main_menu', - 'config:block.block.bartik_account_menu', - 'config:block.block.bartik_messages', - 'config:block.block.bartik_local_actions', - 'config:block.block.bartik_local_tasks', - 'config:block.block.bartik_page_title', - 'node_view', - 'node:' . $node_1->id(), - 'user:0', - 'user:' . $author_1->id(), - 'config:filter.format.basic_html', - 'config:color.theme.bartik', - 'config:search.settings', - 'config:system.menu.account', - 'config:system.menu.tools', - 'config:system.menu.footer', - 'config:system.menu.main', - 'config:system.site', - // FinishResponseSubscriber adds this cache tag to responses that have the - // 'user.permissions' cache context for anonymous users. - 'config:user.role.anonymous', - )); - - // Render the view block adds the languages cache context. - $cache_contexts[] = 'languages:' . LanguageInterface::TYPE_CONTENT; - - // Full node page 2. - $this->assertPageCacheContextsAndTags($node_2->urlInfo(), $cache_contexts, array( - 'rendered', - 'block_view', - 'config:block_list', - 'config:block.block.bartik_branding', - 'config:block.block.bartik_breadcrumbs', - 'config:block.block.bartik_content', - 'config:block.block.bartik_tools', - 'config:block.block.bartik_help', - 'config:block.block.bartik_search', - 'config:block.block.' . $block->id(), - 'config:block.block.bartik_footer', - 'config:block.block.bartik_powered', - 'config:block.block.bartik_main_menu', - 'config:block.block.bartik_account_menu', - 'config:block.block.bartik_messages', - 'config:block.block.bartik_local_actions', - 'config:block.block.bartik_local_tasks', - 'config:block.block.bartik_page_title', - 'node_view', - 'node:' . $node_2->id(), - 'user:' . $author_2->id(), - 'config:color.theme.bartik', - 'config:filter.format.full_html', - 'config:search.settings', - 'config:system.menu.account', - 'config:system.menu.tools', - 'config:system.menu.footer', - 'config:system.menu.main', - 'config:system.site', - 'comment_list', - 'node_list', - 'config:views.view.comments_recent', - // FinishResponseSubscriber adds this cache tag to responses that have the - // 'user.permissions' cache context for anonymous users. - 'config:user.role.anonymous', - 'user:0', - )); - } - -} diff --git a/core/modules/page_cache/src/Tests/PageCacheTest.php b/core/modules/page_cache/src/Tests/PageCacheTest.php index 22b8b51..e69de29 100644 --- a/core/modules/page_cache/src/Tests/PageCacheTest.php +++ b/core/modules/page_cache/src/Tests/PageCacheTest.php @@ -1,513 +0,0 @@ -config('system.site') - ->set('name', 'Drupal') - ->set('page.front', '/test-page') - ->save(); - } - - /** - * Test that cache tags are properly persisted. - * - * Since tag based invalidation works, we know that our tag properly - * persisted. - */ - function testPageCacheTags() { - $config = $this->config('system.performance'); - $config->set('cache.page.max_age', 300); - $config->save(); - - $path = 'system-test/cache_tags_page'; - $tags = array('system_test_cache_tags_page'); - $this->drupalGet($path); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); - - // Verify a cache hit, but also the presence of the correct cache tags. - $this->drupalGet($path); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT'); - $cid_parts = array(\Drupal::url('system_test.cache_tags_page', array(), array('absolute' => TRUE)), 'html'); - $cid = implode(':', $cid_parts); - $cache_entry = \Drupal::cache('render')->get($cid); - sort($cache_entry->tags); - $expected_tags = array( - 'config:user.role.anonymous', - 'pre_render', - 'rendered', - 'system_test_cache_tags_page', - ); - $this->assertIdentical($cache_entry->tags, $expected_tags); - - Cache::invalidateTags($tags); - $this->drupalGet($path); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); - } - - /** - * Test that the page cache doesn't depend on cacheability headers. - */ - function testPageCacheTagsIndependentFromCacheabilityHeaders() { - $this->setHttpResponseDebugCacheabilityHeaders(FALSE); - - $path = 'system-test/cache_tags_page'; - $tags = array('system_test_cache_tags_page'); - $this->drupalGet($path); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); - - // Verify a cache hit, but also the presence of the correct cache tags. - $this->drupalGet($path); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT'); - $cid_parts = array(\Drupal::url('system_test.cache_tags_page', array(), array('absolute' => TRUE)), 'html'); - $cid = implode(':', $cid_parts); - $cache_entry = \Drupal::cache('render')->get($cid); - sort($cache_entry->tags); - $expected_tags = array( - 'config:user.role.anonymous', - 'pre_render', - 'rendered', - 'system_test_cache_tags_page', - ); - $this->assertIdentical($cache_entry->tags, $expected_tags); - - Cache::invalidateTags($tags); - $this->drupalGet($path); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); - } - - /** - * Tests support for different cache items with different request formats - * specified via a query parameter. - */ - function testQueryParameterFormatRequests() { - $config = $this->config('system.performance'); - $config->set('cache.page.max_age', 300); - $config->save(); - - $accept_header_cache_url = Url::fromRoute('system_test.page_cache_accept_header'); - $accept_header_cache_url_with_json = Url::fromRoute('system_test.page_cache_accept_header', ['_format' => 'json']); - - $this->drupalGet($accept_header_cache_url); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'HTML page was not yet cached.'); - $this->drupalGet($accept_header_cache_url); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'HTML page was cached.'); - $this->assertRaw('

oh hai this is html.

', 'The correct HTML response was returned.'); - - $this->drupalGet($accept_header_cache_url_with_json); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Json response was not yet cached.'); - $this->drupalGet($accept_header_cache_url_with_json); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Json response was cached.'); - $this->assertRaw('{"content":"oh hai this is json"}', 'The correct Json response was returned.'); - - // Enable REST support for nodes and hal+json. - \Drupal::service('module_installer')->install(['node', 'rest', 'hal', 'basic_auth']); - $this->drupalCreateContentType(['type' => 'article']); - $node = $this->drupalCreateNode(['type' => 'article']); - $node_uri = $node->urlInfo(); - $node_url_with_hal_json_format = $node->urlInfo('canonical')->setRouteParameter('_format', 'hal_json'); - - $this->drupalGet($node_uri); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); - $this->assertEqual($this->drupalGetHeader('Content-Type'), 'text/html; charset=UTF-8'); - $this->drupalGet($node_uri); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT'); - $this->assertEqual($this->drupalGetHeader('Content-Type'), 'text/html; charset=UTF-8'); - - // Now request a HAL page, we expect that the first request is a cache miss - // and it serves HTML. - $this->drupalGet($node_url_with_hal_json_format); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); - $this->assertEqual($this->drupalGetHeader('Content-Type'), 'application/hal+json'); - $this->drupalGet($node_url_with_hal_json_format); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT'); - $this->assertEqual($this->drupalGetHeader('Content-Type'), 'application/hal+json'); - - // Clear the page cache. After that request a HAL request, followed by an - // ordinary HTML one. - \Drupal::cache('render')->deleteAll(); - $this->drupalGet($node_url_with_hal_json_format); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); - $this->assertEqual($this->drupalGetHeader('Content-Type'), 'application/hal+json'); - $this->drupalGet($node_url_with_hal_json_format); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT'); - $this->assertEqual($this->drupalGetHeader('Content-Type'), 'application/hal+json'); - - $this->drupalGet($node_uri); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); - $this->assertEqual($this->drupalGetHeader('Content-Type'), 'text/html; charset=UTF-8'); - $this->drupalGet($node_uri); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT'); - $this->assertEqual($this->drupalGetHeader('Content-Type'), 'text/html; charset=UTF-8'); - } - - /** - * Tests support of requests with If-Modified-Since and If-None-Match headers. - */ - function testConditionalRequests() { - $config = $this->config('system.performance'); - $config->set('cache.page.max_age', 300); - $config->save(); - - // Fill the cache. - $this->drupalGet(''); - // Verify the page is not printed twice when the cache is cold. - $this->assertNoPattern('#drupalHead(''); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.'); - $etag = $this->drupalGetHeader('ETag'); - $last_modified = $this->drupalGetHeader('Last-Modified'); - - $this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified, 'If-None-Match: ' . $etag)); - $this->assertResponse(304, 'Conditional request returned 304 Not Modified.'); - - $this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DATE_RFC822, strtotime($last_modified)), 'If-None-Match: ' . $etag)); - $this->assertResponse(304, 'Conditional request with obsolete If-Modified-Since date returned 304 Not Modified.'); - - $this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DATE_RFC850, strtotime($last_modified)), 'If-None-Match: ' . $etag)); - $this->assertResponse(304, 'Conditional request with obsolete If-Modified-Since date returned 304 Not Modified.'); - - $this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified)); - // Verify the page is not printed twice when the cache is warm. - $this->assertNoPattern('#assertResponse(200, 'Conditional request without If-None-Match returned 200 OK.'); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.'); - - $this->drupalGet('', array(), array('If-Modified-Since: ' . gmdate(DateTimePlus::RFC7231, strtotime($last_modified) + 1), 'If-None-Match: ' . $etag)); - $this->assertResponse(200, 'Conditional request with new a If-Modified-Since date newer than Last-Modified returned 200 OK.'); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.'); - - $user = $this->drupalCreateUser(); - $this->drupalLogin($user); - $this->drupalGet('', array(), array('If-Modified-Since: ' . $last_modified, 'If-None-Match: ' . $etag)); - $this->assertResponse(200, 'Conditional request returned 200 OK for authenticated user.'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Absence of Page was not cached.'); - } - - /** - * Tests cache headers. - */ - function testPageCache() { - $config = $this->config('system.performance'); - $config->set('cache.page.max_age', 300); - $config->set('response.gzip', 1); - $config->save(); - - // Fill the cache. - $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar'))); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.'); - $this->assertEqual(strtolower($this->drupalGetHeader('Vary')), 'cookie,accept-encoding', 'Vary header was sent.'); - // Symfony's Response logic determines a specific order for the subvalues - // of the Cache-Control header, even if they are explicitly passed in to - // the response header bag in a different order. - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=300, public', 'Cache-Control header was sent.'); - $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.'); - $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.'); - - // Check cache. - $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar'))); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.'); - $this->assertEqual(strtolower($this->drupalGetHeader('Vary')), 'cookie,accept-encoding', 'Vary: Cookie header was sent.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=300, public', 'Cache-Control header was sent.'); - $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.'); - $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.'); - - // Check replacing default headers. - $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Expires', 'value' => 'Fri, 19 Nov 2008 05:00:00 GMT'))); - $this->assertEqual($this->drupalGetHeader('Expires'), 'Fri, 19 Nov 2008 05:00:00 GMT', 'Default header was replaced.'); - $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Vary', 'value' => 'User-Agent'))); - $this->assertEqual(strtolower($this->drupalGetHeader('Vary')), 'user-agent,accept-encoding', 'Default header was replaced.'); - - // Check that authenticated users bypass the cache. - $user = $this->drupalCreateUser(); - $this->drupalLogin($user); - $this->drupalGet('system-test/set-header', array('query' => array('name' => 'Foo', 'value' => 'bar'))); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Caching was bypassed.'); - $this->assertTrue(strpos(strtolower($this->drupalGetHeader('Vary')), 'cookie') === FALSE, 'Vary: Cookie header was not sent.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'must-revalidate, no-cache, private', 'Cache-Control header was sent.'); - $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.'); - $this->assertEqual($this->drupalGetHeader('Foo'), 'bar', 'Custom header was sent.'); - - // Until bubbling of max-age up to the response is supported, verify that - // a custom #cache max-age set on an element does not affect page max-age. - $this->drupalLogout(); - $this->drupalGet('system-test/cache_maxage_page'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=300, public'); - } - - /** - * Tests the automatic presence of the anonymous role's cache tag. - * - * The 'user.permissions' cache context ensures that if the permissions for a - * role are modified, users are not served stale render cache content. But, - * when entire responses are cached in reverse proxies, the value for the - * cache context is never calculated, causing the stale response to not be - * invalidated. Therefore, when varying by permissions and the current user is - * the anonymous user, the cache tag for the 'anonymous' role must be added. - * - * This test verifies that, and it verifies that it does not happen for other - * roles. - */ - public function testPageCacheAnonymousRolePermissions() { - $config = $this->config('system.performance'); - $config->set('cache.page.max_age', 300); - $config->save(); - - $content_url = Url::fromRoute('system_test.permission_dependent_content'); - $route_access_url = Url::fromRoute('system_test.permission_dependent_route_access'); - - // 1. anonymous user, without permission. - $this->drupalGet($content_url); - $this->assertText('Permission to pet llamas: no!'); - $this->assertCacheContext('user.permissions'); - $this->assertCacheTag('config:user.role.anonymous'); - $this->drupalGet($route_access_url); - $this->assertCacheContext('user.permissions'); - $this->assertCacheTag('config:user.role.anonymous'); - - // 2. anonymous user, with permission. - user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, ['pet llamas']); - $this->drupalGet($content_url); - $this->assertText('Permission to pet llamas: yes!'); - $this->assertCacheContext('user.permissions'); - $this->assertCacheTag('config:user.role.anonymous'); - $this->drupalGet($route_access_url); - $this->assertCacheContext('user.permissions'); - $this->assertCacheTag('config:user.role.anonymous'); - - // 3. authenticated user, without permission. - $auth_user = $this->drupalCreateUser(); - $this->drupalLogin($auth_user); - $this->drupalGet($content_url); - $this->assertText('Permission to pet llamas: no!'); - $this->assertCacheContext('user.permissions'); - $this->assertNoCacheTag('config:user.role.authenticated'); - $this->drupalGet($route_access_url); - $this->assertCacheContext('user.permissions'); - $this->assertNoCacheTag('config:user.role.authenticated'); - - // 4. authenticated user, with permission. - user_role_grant_permissions(RoleInterface::AUTHENTICATED_ID, ['pet llamas']); - $this->drupalGet($content_url); - $this->assertText('Permission to pet llamas: yes!'); - $this->assertCacheContext('user.permissions'); - $this->assertNoCacheTag('config:user.role.authenticated'); - $this->drupalGet($route_access_url); - $this->assertCacheContext('user.permissions'); - $this->assertNoCacheTag('config:user.role.authenticated'); - } - - /** - * Tests the 4xx-response cache tag is added and invalidated. - */ - function testPageCacheAnonymous403404() { - $admin_url = Url::fromRoute('system.admin'); - $invalid_url = 'foo/does_not_exist'; - $tests = [ - 403 => $admin_url, - 404 => $invalid_url, - ]; - $cache_ttl_4xx = Settings::get('cache_ttl_4xx', 3600); - foreach ($tests as $code => $content_url) { - // Anonymous user, without permissions. - $this->drupalGet($content_url); - $this->assertResponse($code); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); - $this->assertCacheTag('4xx-response'); - $this->drupalGet($content_url); - $this->assertResponse($code); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT'); - $entity_values = array( - 'name' => $this->randomMachineName(), - 'user_id' => 1, - 'field_test_text' => array( - 0 => array( - 'value' => $this->randomString(), - 'format' => 'plain_text', - ) - ), - ); - $entity = EntityTest::create($entity_values); - $entity->save(); - // Saving an entity clears 4xx cache tag. - $this->drupalGet($content_url); - $this->assertResponse($code); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); - $this->drupalGet($content_url); - $this->assertResponse($code); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT'); - // Rebuilding the router should invalidate the 4xx cache tag. - $this->container->get('router.builder')->rebuild(); - $this->drupalGet($content_url); - $this->assertResponse($code); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); - - // Ensure the 'expire' field on the cache entry uses cache_ttl_4xx. - $cache_item = \Drupal::service('cache.render')->get($this->getUrl() . ':html'); - $difference = $cache_item->expire - (int) $cache_item->created; - // Given that a second might have passed we cannot be sure that - // $difference will exactly equal the default cache_ttl_4xx setting. - // Account for any timing difference or rounding errors by ensuring the - // value is within 5 seconds. - $this->assertTrue( - $difference > $cache_ttl_4xx - 5 && - $difference < $cache_ttl_4xx + 5, - 'The cache entry expiry time uses the cache_ttl_4xx setting.' - ); - } - - // Disable 403 and 404 caching. - $settings['settings']['cache_ttl_4xx'] = (object) array( - 'value' => 0, - 'required' => TRUE, - ); - $this->writeSettings($settings); - \Drupal::service('cache.render')->deleteAll(); - - foreach ($tests as $code => $content_url) { - // Getting the 404 page twice should still result in a cache miss. - $this->drupalGet($content_url); - $this->drupalGet($content_url); - $this->assertResponse($code); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS'); - } - } - - /** - * Tests the omit_vary_cookie setting. - */ - public function testPageCacheWithoutVaryCookie() { - $config = $this->config('system.performance'); - $config->set('cache.page.max_age', 300); - $config->save(); - - $settings['settings']['omit_vary_cookie'] = (object) array( - 'value' => TRUE, - 'required' => TRUE, - ); - $this->writeSettings($settings); - - // Fill the cache. - $this->drupalGet(''); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.'); - $this->assertTrue(strpos($this->drupalGetHeader('Vary'), 'Cookie') === FALSE, 'Vary: Cookie header was not sent.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=300, public', 'Cache-Control header was sent.'); - - // Check cache. - $this->drupalGet(''); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.'); - $this->assertTrue(strpos($this->drupalGetHeader('Vary'), 'Cookie') === FALSE, 'Vary: Cookie header was not sent.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=300, public', 'Cache-Control header was sent.'); - } - - /** - * Test the setting of forms to be immutable. - */ - public function testFormImmutability() { - // Install the module that provides the test form. - $this->container->get('module_installer') - ->install(['page_cache_form_test']); - // Uninstall the page_cache module to verify that form is immutable - // regardless of the internal page cache module. - $this->container->get('module_installer')->uninstall(['page_cache']); - \Drupal::service('router.builder')->rebuild(); - - $this->drupalGet('page_cache_form_test_immutability'); - - $this->assertText("Immutable: TRUE", "Form is immutable."); - - // The immutable flag is set unconditionally by system_form_alter(), set - // a flag to tell page_cache_form_test_module_implements_alter() to disable - // that implementation. - \Drupal::state()->set('page_cache_bypass_form_immutability', TRUE); - \Drupal::moduleHandler()->resetImplementations(); - Cache::invalidateTags(['rendered']); - - $this->drupalGet('page_cache_form_test_immutability'); - - $this->assertText("Immutable: FALSE", "Form is not immutable,"); - } - - /** - * Tests cacheability of a CacheableResponse. - * - * Tests the difference between having a controller return a plain Symfony - * Response object versus returning a Response object that implements the - * CacheableResponseInterface. - */ - public function testCacheableResponseResponses() { - $config = $this->config('system.performance'); - $config->set('cache.page.max_age', 300); - $config->save(); - - // GET a URL, which would be marked as a cache miss if it were cacheable. - $this->drupalGet('/system-test/respond-reponse'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'must-revalidate, no-cache, private', 'Cache-Control header was sent'); - - // GET it again, verify it's still not cached. - $this->drupalGet('/system-test/respond-reponse'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'must-revalidate, no-cache, private', 'Cache-Control header was sent'); - - // GET a URL, which would be marked as a cache miss if it were cacheable. - $this->drupalGet('/system-test/respond-public-response'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=60, public', 'Cache-Control header was sent'); - - // GET it again, verify it's still not cached. - $this->drupalGet('/system-test/respond-public-response'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=60, public', 'Cache-Control header was sent'); - - // GET a URL, which should be marked as a cache miss. - $this->drupalGet('/system-test/respond-cacheable-reponse'); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'MISS', 'Page was not cached.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=300, public', 'Cache-Control header was sent.'); - - // GET it again, it should now be a cache hit. - $this->drupalGet('/system-test/respond-cacheable-reponse'); - $this->assertEqual($this->drupalGetHeader('X-Drupal-Cache'), 'HIT', 'Page was cached.'); - $this->assertEqual($this->drupalGetHeader('Cache-Control'), 'max-age=300, public', 'Cache-Control header was sent.'); - - // Uninstall page cache. This should flush all caches so the next call to a - // previously cached page should be a miss now. - $this->container->get('module_installer') - ->uninstall(['page_cache']); - - // GET a URL that was cached by Page Cache before, it should not be now. - $this->drupalGet('/respond-cacheable-reponse'); - $this->assertFalse($this->drupalGetHeader('X-Drupal-Cache'), 'Drupal page cache header not found.'); - } - -} diff --git a/core/modules/path/src/Tests/PathAdminTest.php b/core/modules/path/tests/src/Functional/PathAdminTest.php similarity index 98% rename from core/modules/path/src/Tests/PathAdminTest.php rename to core/modules/path/tests/src/Functional/PathAdminTest.php index 906ce64..a27840d 100644 --- a/core/modules/path/src/Tests/PathAdminTest.php +++ b/core/modules/path/tests/src/Functional/PathAdminTest.php @@ -1,6 +1,6 @@ buildRenderable(); + $original = $build; + + // Ensure the current request is a GET request so that render caching is + // active for direct rendering of views, just like for actual requests. + /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */ + $request_stack = \Drupal::service('request_stack'); + $request = new Request(); + $request->server->set('REQUEST_TIME', REQUEST_TIME); + $view->setRequest($request); + $request_stack->push($request); + $renderer->renderRoot($build); + + // Render array cache tags. + $this->pass('Checking render array cache tags.'); + sort($expected_render_array_cache_tags); + $this->assertEqual($build['#cache']['tags'], $expected_render_array_cache_tags); + $this->debugCacheTags($build['#cache']['tags'], $expected_render_array_cache_tags); + + if ($views_caching_is_enabled) { + $this->pass('Checking Views results cache item cache tags.'); + /** @var \Drupal\views\Plugin\views\cache\CachePluginBase $cache_plugin */ + $cache_plugin = $view->display_handler->getPlugin('cache'); + + // Results cache. + + // Ensure that the views query is built. + $view->build(); + $results_cache_item = \Drupal::cache('data')->get($cache_plugin->generateResultsKey()); + if (is_array($expected_results_cache)) { + $this->assertTrue($results_cache_item, 'Results cache item found.'); + if ($results_cache_item) { + sort($expected_results_cache); + $this->assertEqual($results_cache_item->tags, $expected_results_cache); + $this->debugCacheTags($results_cache_item->tags, $expected_results_cache); + } + } + else { + $this->assertFalse($results_cache_item, 'Results cache item not found.'); + } + + $this->pass('Checking Views render cache item cache tags.'); + + $original['#cache'] += ['contexts' => []]; + $original['#cache']['contexts'] = Cache::mergeContexts($original['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']); + + $render_cache_item = $render_cache->get($original); + if ($views_caching_is_enabled === TRUE) { + $this->assertTrue(!empty($render_cache_item), 'Render cache item found.'); + if ($render_cache_item) { + $this->assertEqual($render_cache_item['#cache']['tags'], $expected_render_array_cache_tags); + $this->debugCacheTags($render_cache_item['#cache']['tags'], $expected_render_array_cache_tags); + } + } + else { + $this->assertFalse($render_cache_item, 'Render cache item not found.'); + } + } + + $view->destroy(); + + $request_stack->pop(); + + return $build; + } + + /** + * Asserts a view's result & render cache items' cache tags. + * + * This method starts with a pre bubbling basic render array. + * + * @param \Drupal\views\ViewExecutable $view + * The view. + * @param string[] $expected_render_array_cache_tags + * The expected render cache tags. + * @param bool $views_caching_is_enabled + * Defines whether views output / render caching is enabled. + * + * @return array + * The render array. + */ + protected function assertViewsCacheTagsFromStaticRenderArray(ViewExecutable $view, array $expected_render_array_cache_tags, $views_caching_is_enabled) { + $original = $build = DisplayPluginBase::buildBasicRenderable($view->id(), $view->current_display ?: 'default', $view->args); + + /** @var \Drupal\Core\Render\RendererInterface $renderer */ + $renderer = \Drupal::service('renderer'); + /** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */ + $render_cache = \Drupal::service('render_cache'); + + // Ensure the current request is a GET request so that render caching is + // active for direct rendering of views, just like for actual requests. + /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */ + $request_stack = \Drupal::service('request_stack'); + $request = new Request(); + $request->server->set('REQUEST_TIME', REQUEST_TIME); + $request_stack->push($request); + $renderer->renderRoot($build); + + // Render array cache tags. + $this->pass('Checking render array cache tags.'); + sort($expected_render_array_cache_tags); + $this->assertEqual($build['#cache']['tags'], $expected_render_array_cache_tags); + $this->debugCacheTags($build['#cache']['tags'], $expected_render_array_cache_tags); + + $this->pass('Checking Views render cache item cache tags.'); + $original['#cache'] += ['contexts' => []]; + $original['#cache']['contexts'] = Cache::mergeContexts($original['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']); + + $render_cache_item = $render_cache->get($original); + if ($views_caching_is_enabled) { + $this->assertTrue(!empty($render_cache_item), 'Render cache item found.'); + if ($render_cache_item) { + $this->assertEqual($render_cache_item['#cache']['tags'], $expected_render_array_cache_tags); + $this->debugCacheTags($render_cache_item['#cache']['tags'], $expected_render_array_cache_tags); + } + } + else { + $this->assertFalse($render_cache_item, 'Render cache item not found.'); + } + + return $build; + } + +} diff --git a/core/modules/views/tests/src/Functional/DefaultViewsTest.php b/core/modules/views/tests/src/Functional/DefaultViewsTest.php new file mode 100644 index 0000000..f1bb005 --- /dev/null +++ b/core/modules/views/tests/src/Functional/DefaultViewsTest.php @@ -0,0 +1,220 @@ + array(1), + 'taxonomy_term' => array(1), + 'glossary' => array('all'), + ); + + protected function setUp() { + parent::setUp(); + + $this->drupalPlaceBlock('page_title_block'); + + // Create Basic page node type. + $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); + + $vocabulary = Vocabulary::create([ + 'name' => $this->randomMachineName(), + 'description' => $this->randomMachineName(), + 'vid' => Unicode::strtolower($this->randomMachineName()), + 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + 'help' => '', + 'nodes' => array('page' => 'page'), + 'weight' => mt_rand(0, 10), + ]); + $vocabulary->save(); + + // Create a field. + $field_name = Unicode::strtolower($this->randomMachineName()); + + $handler_settings = array( + 'target_bundles' => array( + $vocabulary->id() => $vocabulary->id(), + ), + 'auto_create' => TRUE, + ); + $this->createEntityReferenceField('node', 'page', $field_name, NULL, 'taxonomy_term', 'default', $handler_settings, FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED); + + // Create a time in the past for the archive. + $time = REQUEST_TIME - 3600; + + $this->addDefaultCommentField('node', 'page'); + + for ($i = 0; $i <= 10; $i++) { + $user = $this->drupalCreateUser(); + $term = $this->createTerm($vocabulary); + + $values = array('created' => $time, 'type' => 'page'); + $values[$field_name][]['target_id'] = $term->id(); + + // Make every other node promoted. + if ($i % 2) { + $values['promote'] = TRUE; + } + $values['body'][]['value'] = \Drupal::l('Node ' . 1, new Url('entity.node.canonical', ['node' => 1])); + + $node = $this->drupalCreateNode($values); + + $comment = array( + 'uid' => $user->id(), + 'status' => CommentInterface::PUBLISHED, + 'entity_id' => $node->id(), + 'entity_type' => 'node', + 'field_name' => 'comment' + ); + Comment::create($comment)->save(); + } + + // Some views, such as the "Who's Online" view, only return results if at + // least one user is logged in. + $account = $this->drupalCreateUser(array()); + $this->drupalLogin($account); + } + + /** + * Test that all Default views work as expected. + */ + public function testDefaultViews() { + // Get all default views. + $controller = $this->container->get('entity.manager')->getStorage('view'); + $views = $controller->loadMultiple(); + + foreach ($views as $name => $view_storage) { + $view = $view_storage->getExecutable(); + $view->initDisplay(); + foreach ($view->storage->get('display') as $display_id => $display) { + $view->setDisplay($display_id); + + // Add any args if needed. + if (array_key_exists($name, $this->viewArgMap)) { + $view->preExecute($this->viewArgMap[$name]); + } + + $this->assert(TRUE, format_string('View @view will be executed.', array('@view' => $view->storage->id()))); + $view->execute(); + + $tokens = array('@name' => $name, '@display_id' => $display_id); + $this->assertTrue($view->executed, format_string('@name:@display_id has been executed.', $tokens)); + + $count = count($view->result); + $this->assertTrue($count > 0, format_string('@count results returned', array('@count' => $count))); + $view->destroy(); + } + } + } + + /** + * Returns a new term with random properties in vocabulary $vid. + */ + function createTerm($vocabulary) { + $filter_formats = filter_formats(); + $format = array_pop($filter_formats); + $term = Term::create([ + 'name' => $this->randomMachineName(), + 'description' => $this->randomMachineName(), + // Use the first available text format. + 'format' => $format->id(), + 'vid' => $vocabulary->id(), + 'langcode' => LanguageInterface::LANGCODE_NOT_SPECIFIED, + ]); + $term->save(); + return $term; + } + + /** + * Tests the archive view. + */ + public function testArchiveView() { + // Create additional nodes compared to the one in the setup method. + // Create two nodes in the same month, and one in each following month. + $node = array( + 'created' => 280299600, // Sun, 19 Nov 1978 05:00:00 GMT + ); + $this->drupalCreateNode($node); + $this->drupalCreateNode($node); + $node = array( + 'created' => 282891600, // Tue, 19 Dec 1978 05:00:00 GMT + ); + $this->drupalCreateNode($node); + $node = array( + 'created' => 285570000, // Fri, 19 Jan 1979 05:00:00 GMT + ); + $this->drupalCreateNode($node); + + $view = Views::getView('archive'); + $view->setDisplay('page_1'); + $this->executeView($view); + $columns = array('nid', 'created_year_month', 'num_records'); + $column_map = array_combine($columns, $columns); + // Create time of additional nodes created in the setup method. + $created_year_month = date('Ym', REQUEST_TIME - 3600); + $expected_result = array( + array( + 'nid' => 1, + 'created_year_month' => $created_year_month, + 'num_records' => 11, + ), + array( + 'nid' => 15, + 'created_year_month' => 197901, + 'num_records' => 1, + ), + array( + 'nid' => 14, + 'created_year_month' => 197812, + 'num_records' => 1, + ), + array( + 'nid' => 12, + 'created_year_month' => 197811, + 'num_records' => 2, + ), + ); + $this->assertIdenticalResultset($view, $expected_result, $column_map); + + $view->storage->setStatus(TRUE); + $view->save(); + \Drupal::service('router.builder')->rebuild(); + + $this->drupalGet('archive'); + $this->assertResponse(200); + } + +} diff --git a/core/modules/views/tests/src/Functional/FieldApiDataTest.php b/core/modules/views/tests/src/Functional/FieldApiDataTest.php new file mode 100644 index 0000000..c3191eb --- /dev/null +++ b/core/modules/views/tests/src/Functional/FieldApiDataTest.php @@ -0,0 +1,138 @@ +setUpFieldStorages(1); + + // Attach the field to nodes only. + $field = array( + 'field_name' => $field_names[0], + 'entity_type' => 'node', + 'bundle' => 'page', + 'label' => 'GiraffeA" label' + ); + FieldConfig::create($field)->save(); + + // Attach the same field to a different bundle with a different label. + $this->drupalCreateContentType(['type' => 'article']); + FieldConfig::create([ + 'field_name' => $field_names[0], + 'entity_type' => 'node', + 'bundle' => 'article', + 'label' => 'GiraffeB" label' + ])->save(); + + // Now create some example nodes/users for the view result. + for ($i = 0; $i < 5; $i++) { + $edit = array( + $field_names[0] => array((array('value' => $this->randomMachineName()))), + ); + $nodes[] = $this->drupalCreateNode($edit); + } + } + + /** + * Unit testing the views data structure. + * + * We check data structure for both node and node revision tables. + */ + function testViewsData() { + $table_mapping = \Drupal::entityManager()->getStorage('node')->getTableMapping(); + $field_storage = $this->fieldStorages[0]; + $current_table = $table_mapping->getDedicatedDataTableName($field_storage); + $revision_table = $table_mapping->getDedicatedRevisionTableName($field_storage); + $data = $this->getViewsData(); + + $this->assertTrue(isset($data[$current_table])); + $this->assertTrue(isset($data[$revision_table])); + // The node field should join against node_field_data. + $this->assertTrue(isset($data[$current_table]['table']['join']['node_field_data'])); + $this->assertTrue(isset($data[$revision_table]['table']['join']['node_field_revision'])); + + $expected_join = array( + 'left_field' => 'nid', + 'field' => 'entity_id', + 'extra' => array( + array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE), + array('left_field' => 'langcode', 'field' => 'langcode'), + ), + ); + $this->assertEqual($expected_join, $data[$current_table]['table']['join']['node_field_data']); + $expected_join = array( + 'left_field' => 'vid', + 'field' => 'revision_id', + 'extra' => array( + array('field' => 'deleted', 'value' => 0, 'numeric' => TRUE), + array('left_field' => 'langcode', 'field' => 'langcode'), + ), + ); + $this->assertEqual($expected_join, $data[$revision_table]['table']['join']['node_field_revision']); + + // Test click sortable. + $this->assertTrue($data[$current_table][$field_storage->getName()]['field']['click sortable'], 'String field is click sortable.'); + // Click sort should only be on the primary field. + $this->assertTrue(empty($data[$revision_table][$field_storage->getName()]['field']['click sortable']), 'Non-primary fields are not click sortable'); + + $this->assertTrue($data[$current_table][$field_storage->getName()]['help'] instanceof MarkupInterface); + $this->assertEqual($data[$current_table][$field_storage->getName()]['help'], 'Appears in: page, article. Also known as: Content: GiraffeB" label'); + + $this->assertTrue($data[$current_table][$field_storage->getName() . '_value']['help'] instanceof MarkupInterface); + $this->assertEqual($data[$current_table][$field_storage->getName() . '_value']['help'], 'Appears in: page, article. Also known as: Content: GiraffeA" label (field_name_0)'); + + // Since each label is only used once, views_entity_field_label() will + // return a label using alphabetical sorting. + $this->assertEqual('GiraffeA" label (field_name_0)', $data[$current_table][$field_storage->getName() . '_value']['title']); + + // Attach the same field to a different bundle with a different label. + $this->drupalCreateContentType(['type' => 'news']); + FieldConfig::create([ + 'field_name' => $this->fieldStorages[0]->getName(), + 'entity_type' => 'node', + 'bundle' => 'news', + 'label' => 'GiraffeB" label' + ])->save(); + $this->container->get('views.views_data')->clear(); + $data = $this->getViewsData(); + + // Now the 'GiraffeB" label' is used twice and therefore will be + // selected by views_entity_field_label(). + $this->assertEqual('GiraffeB" label (field_name_0)', $data[$current_table][$field_storage->getName() . '_value']['title']); + $this->assertTrue($data[$current_table][$field_storage->getName()]['help'] instanceof MarkupInterface); + $this->assertEqual($data[$current_table][$field_storage->getName()]['help'], 'Appears in: page, article, news. Also known as: Content: GiraffeA" label'); + } + + /** + * Gets the views data for the field created in setUp(). + * + * @return array + */ + protected function getViewsData() { + $views_data = $this->container->get('views.views_data'); + $data = array(); + + // Check the table and the joins of the first field. + // Attached to node only. + /** @var \Drupal\Core\Entity\Sql\DefaultTableMapping $table_mapping */ + $table_mapping = \Drupal::entityManager()->getStorage('node')->getTableMapping(); + $current_table = $table_mapping->getDedicatedDataTableName($this->fieldStorages[0]); + $revision_table = $table_mapping->getDedicatedRevisionTableName($this->fieldStorages[0]); + $data[$current_table] = $views_data->get($current_table); + $data[$revision_table] = $views_data->get($revision_table); + return $data; + } + +} diff --git a/core/modules/views/tests/src/Functional/GlossaryTest.php b/core/modules/views/tests/src/Functional/GlossaryTest.php new file mode 100644 index 0000000..c465ac5 --- /dev/null +++ b/core/modules/views/tests/src/Functional/GlossaryTest.php @@ -0,0 +1,121 @@ +drupalCreateContentType(); + $nodes_per_char = array( + 'd' => 1, + 'r' => 4, + 'u' => 10, + 'p' => 2, + 'a' => 3, + 'l' => 6, + ); + $nodes_by_char = []; + foreach ($nodes_per_char as $char => $count) { + $setting = array( + 'type' => $type->id() + ); + for ($i = 0; $i < $count; $i++) { + $node = $setting; + $node['title'] = $char . $this->randomString(3); + $node = $this->drupalCreateNode($node); + $nodes_by_char[$char][] = $node; + } + } + + // Execute glossary view + $view = Views::getView('glossary'); + $view->setDisplay('attachment_1'); + $view->executeDisplay('attachment_1'); + + // Check that the amount of nodes per char. + foreach ($view->result as $item) { + $this->assertEqual($nodes_per_char[$item->title_truncated], $item->num_records); + } + + // Enable the glossary to be displayed. + $view->storage->enable()->save(); + $this->container->get('router.builder')->rebuildIfNeeded(); + $url = Url::fromRoute('view.glossary.page_1'); + + // Verify cache tags. + $this->assertPageCacheContextsAndTags( + $url, + [ + 'timezone', + 'languages:' . LanguageInterface::TYPE_CONTENT, + 'languages:' . LanguageInterface::TYPE_INTERFACE, + 'theme', + 'url', + 'user.node_grants:view', + 'user.permissions', + 'route', + ], + [ + 'config:views.view.glossary', + // Listed for letter 'a' + 'node:' . $nodes_by_char['a'][0]->id(), 'node:' . $nodes_by_char['a'][1]->id(), 'node:' . $nodes_by_char['a'][2]->id(), + // Link for letter 'd'. + 'node:1', + // Link for letter 'p'. + 'node:16', + // Link for letter 'r'. + 'node:2', + // Link for letter 'l'. + 'node:21', + // Link for letter 'u'. + 'node:6', + 'node_list', + 'user:0', + 'user_list', + 'rendered', + // FinishResponseSubscriber adds this cache tag to responses that have the + // 'user.permissions' cache context for anonymous users. + 'config:user.role.anonymous', + ] + ); + + // Check the actual page response. + $this->drupalGet($url); + $this->assertResponse(200); + foreach ($nodes_per_char as $char => $count) { + $href = Url::fromRoute('view.glossary.page_1', ['arg_0' => $char])->toString(); + $label = Unicode::strtoupper($char); + // Get the summary link for a certain character. Filter by label and href + // to ensure that both of them are correct. + $result = $this->xpath('//a[contains(@href, :href) and normalize-space(text())=:label]/..', array(':href' => $href, ':label' => $label)); + $this->assertTrue(count($result)); + // The rendered output looks like "| (count)" so let's figure out the int. + $result_count = trim(str_replace(array('|', '(', ')'), '', (string) $result[0])); + $this->assertEqual($result_count, $count, 'The expected number got rendered.'); + } + } + +} diff --git a/core/modules/views/tests/src/Functional/RenderCacheWebTest.php b/core/modules/views/tests/src/Functional/RenderCacheWebTest.php new file mode 100644 index 0000000..4b97a5f --- /dev/null +++ b/core/modules/views/tests/src/Functional/RenderCacheWebTest.php @@ -0,0 +1,75 @@ +drupalCreateContentType(['type' => 'test_type']); + $node = Node::create([ + 'title' => 'test title 1', + 'type' => $node_type->id(), + ]); + $node->save(); + $this->nodes[] = $node; + + $node = Node::create([ + 'title' => 'test title 2', + 'type' => $node_type->id(), + ]); + $node->save(); + $this->nodes[] = $node; + + $this->placeBlock('views_block:node_id_argument-block_1', ['region' => 'header']); + } + + /** + * Tests rendering caching of a views block with arguments. + */ + public function testEmptyView() { + $this->drupalGet(''); + $this->assertEqual([], $this->cssSelect('div.region-header div.views-field-title')); + + $this->drupalGet($this->nodes[0]->toUrl()); + $result = (string) $this->cssSelect('div.region-header div.views-field-title')[0]->span; + $this->assertEqual('test title 1', $result); + + $this->drupalGet($this->nodes[1]->toUrl()); + $result = (string) $this->cssSelect('div.region-header div.views-field-title')[0]->span; + $this->assertEqual('test title 2', $result); + + $this->drupalGet($this->nodes[0]->toUrl()); + $result = (string) $this->cssSelect('div.region-header div.views-field-title')[0]->span; + $this->assertEqual('test title 1', $result); + } + +} diff --git a/core/modules/views/tests/src/Functional/SearchIntegrationTest.php b/core/modules/views/tests/src/Functional/SearchIntegrationTest.php new file mode 100644 index 0000000..41d1279 --- /dev/null +++ b/core/modules/views/tests/src/Functional/SearchIntegrationTest.php @@ -0,0 +1,149 @@ +drupalCreateContentType(); + + // Add three nodes, one containing the word "pizza", one containing + // "sandwich", and one containing "cola is good with pizza". Make the + // second node link to the first. + $node['title'] = 'pizza'; + $node['body'] = array(array('value' => 'pizza')); + $node['type'] = $type->id(); + $this->drupalCreateNode($node); + + $this->drupalGet('node/1'); + $node_url = $this->getUrl(); + + $node['title'] = 'sandwich'; + $node['body'] = array(array('value' => 'sandwich with a link to first node')); + $this->drupalCreateNode($node); + + $node['title'] = 'cola'; + $node['body'] = array(array('value' => 'cola is good with pizza')); + $node['type'] = $type->id(); + $this->drupalCreateNode($node); + + // Run cron so that the search index tables are updated. + $this->cronRun(); + + // Test the various views filters by visiting their pages. + // These are in the test view 'test_search', and they just display the + // titles of the nodes in the result, as links. + + // Page with a keyword filter of 'pizza'. + $this->drupalGet('test-filter'); + $this->assertLink('pizza'); + $this->assertNoLink('sandwich'); + $this->assertLink('cola'); + + // Page with a keyword argument, various argument values. + // Verify that the correct nodes are shown, and only once. + $this->drupalGet('test-arg/pizza'); + $this->assertOneLink('pizza'); + $this->assertNoLink('sandwich'); + $this->assertOneLink('cola'); + + $this->drupalGet('test-arg/sandwich'); + $this->assertNoLink('pizza'); + $this->assertOneLink('sandwich'); + $this->assertNoLink('cola'); + + $this->drupalGet('test-arg/pizza OR sandwich'); + $this->assertOneLink('pizza'); + $this->assertOneLink('sandwich'); + $this->assertOneLink('cola'); + + $this->drupalGet('test-arg/pizza sandwich OR cola'); + $this->assertNoLink('pizza'); + $this->assertNoLink('sandwich'); + $this->assertOneLink('cola'); + + $this->drupalGet('test-arg/cola pizza'); + $this->assertNoLink('pizza'); + $this->assertNoLink('sandwich'); + $this->assertOneLink('cola'); + + $this->drupalGet('test-arg/"cola is good"'); + $this->assertNoLink('pizza'); + $this->assertNoLink('sandwich'); + $this->assertOneLink('cola'); + + // Test sorting. + $node = [ + 'title' => "Drupal's search rocks.", + 'type' => $type->id(), + ]; + $this->drupalCreateNode($node); + $node['title'] = "Drupal's search rocks really rocks!"; + $this->drupalCreateNode($node); + $this->cronRun(); + $this->drupalGet('test-arg/rocks'); + $xpath = '//div[@class="views-row"]//a'; + /** @var \SimpleXMLElement[] $results */ + $results = $this->xpath($xpath); + $this->assertEqual((string) $results[0], "Drupal's search rocks really rocks!"); + $this->assertEqual((string) $results[1], "Drupal's search rocks."); + $this->assertEscaped("Drupal's search rocks really rocks!"); + + // Test sorting with another set of titles. + $node = [ + 'title' => "Testing one two two two", + 'type' => $type->id(), + ]; + $this->drupalCreateNode($node); + $node['title'] = "Testing one one one"; + $this->drupalCreateNode($node); + $this->cronRun(); + $this->drupalGet('test-arg/one'); + $xpath = '//div[@class="views-row"]//a'; + /** @var \SimpleXMLElement[] $results */ + $results = $this->xpath($xpath); + $this->assertEqual((string) $results[0], "Testing one one one"); + $this->assertEqual((string) $results[1], "Testing one two two two"); + } + + /** + * Asserts that exactly one link exists with the given text. + * + * @param string $label + * Link label to assert. + * + * @return bool + * TRUE if the assertion succeeded, FALSE otherwise. + */ + protected function assertOneLink($label) { + $links = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label)); + $message = SafeMarkup::format('Link with label %label found once.', array('%label' => $label)); + return $this->assert(isset($links[0]) && !isset($links[1]), $message); + } + +} diff --git a/core/modules/views/tests/src/Functional/SearchMultilingualTest.php b/core/modules/views/tests/src/Functional/SearchMultilingualTest.php new file mode 100644 index 0000000..42de235 --- /dev/null +++ b/core/modules/views/tests/src/Functional/SearchMultilingualTest.php @@ -0,0 +1,81 @@ +drupalCreateUser(array('administer nodes', 'administer content types', 'administer languages', 'administer content translation', 'access content', 'search content')); + $this->drupalLogin($user); + + // Add Spanish language programmatically. + ConfigurableLanguage::createFromLangcode('es')->save(); + + // Create a content type and make it translatable. + $type = $this->drupalCreateContentType(); + $edit = array( + 'language_configuration[language_alterable]' => TRUE, + ); + $this->drupalPostForm('admin/structure/types/manage/' . $type->id(), $edit, t('Save content type')); + $edit = array( + 'entity_types[node]' => TRUE, + 'settings[node][' . $type->id() . '][translatable]' => TRUE, + 'settings[node][' . $type->id() . '][fields][title]' => TRUE, + 'settings[node][' . $type->id() . '][fields][body]' => TRUE, + ); + $this->drupalPostForm('admin/config/regional/content-language', $edit, t('Save configuration')); + \Drupal::entityManager()->clearCachedDefinitions(); + + // Add a node in English, with title "sandwich". + $values = array( + 'title' => 'sandwich', + 'type' => $type->id(), + ); + $node = $this->drupalCreateNode($values); + + // "Translate" this node into Spanish, with title "pizza". + $node->addTranslation('es', array('title' => 'pizza', 'status' => NODE_PUBLISHED)); + $node->save(); + + // Run cron so that the search index tables are updated. + $this->cronRun(); + + // Test the keyword filter by visiting the page. + // The views are in the test view 'test_search', and they just display the + // titles of the nodes in the result, as links. + + // Page with a keyword filter of 'pizza'. This should find the Spanish + // translated node, which has 'pizza' in the title, but not the English + // one, which does not have the word 'pizza' in it. + $this->drupalGet('test-filter'); + $this->assertLink('pizza', 0, 'Found translation with matching title'); + $this->assertNoLink('sandwich', 'Did not find translation with non-matching title'); + } + +} diff --git a/core/modules/views/tests/src/Functional/TestHelperPlugin.php b/core/modules/views/tests/src/Functional/TestHelperPlugin.php new file mode 100644 index 0000000..b986bbc --- /dev/null +++ b/core/modules/views/tests/src/Functional/TestHelperPlugin.php @@ -0,0 +1,52 @@ +setOptionDefaults($storage, $options, $level); + } + + /** + * Allows to set the defined options. + * + * @param array $options + * + * @return $this + */ + public function setDefinedOptions($options) { + $this->definedOptions = $options; + + return $this; + } + + /** + * {@inheritdoc} + */ + protected function defineOptions() { + // Normally we provide a limited set of options, but for testing purposes we + // make it possible to set the defined options statically. + return $this->definedOptions; + } + +} diff --git a/core/modules/views/tests/src/Functional/ViewAjaxTest.php b/core/modules/views/tests/src/Functional/ViewAjaxTest.php new file mode 100644 index 0000000..eedb992 --- /dev/null +++ b/core/modules/views/tests/src/Functional/ViewAjaxTest.php @@ -0,0 +1,64 @@ +enableViewsTestModule(); + } + + /** + * Tests an ajax view. + */ + public function testAjaxView() { + $this->drupalGet('test_ajax_view'); + + $drupal_settings = $this->getDrupalSettings(); + $this->assertTrue(isset($drupal_settings['views']['ajax_path']), 'The Ajax callback path is set in drupalSettings.'); + $this->assertEqual(count($drupal_settings['views']['ajaxViews']), 1); + $view_entry = array_keys($drupal_settings['views']['ajaxViews'])[0]; + $this->assertEqual($drupal_settings['views']['ajaxViews'][$view_entry]['view_name'], 'test_ajax_view', 'The view\'s ajaxViews array entry has the correct \'view_name\' key.'); + $this->assertEqual($drupal_settings['views']['ajaxViews'][$view_entry]['view_display_id'], 'page_1', 'The view\'s ajaxViews array entry has the correct \'view_display_id\' key.'); + + $data = array(); + $data['view_name'] = 'test_ajax_view'; + $data['view_display_id'] = 'test_ajax_view'; + + $post = array( + 'view_name' => 'test_ajax_view', + 'view_display_id' => 'page_1', + ); + $post += $this->getAjaxPageStatePostData(); + $response = $this->drupalPost('views/ajax', '', $post, ['query' => [MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']]); + $data = Json::decode($response); + + $this->assertTrue(isset($data[0]['settings']['views']['ajaxViews'])); + + // Ensure that the view insert command is part of the result. + $this->assertEqual($data[1]['command'], 'insert'); + $this->assertTrue(strpos($data[1]['selector'], '.js-view-dom-id-') === 0); + + $this->setRawContent($data[1]['data']); + $result = $this->xpath('//div[contains(@class, "views-row")]'); + $this->assertEqual(count($result), 2, 'Ensure that two items are rendered in the HTML.'); + } + +} diff --git a/core/modules/views/tests/src/Functional/ViewElementTest.php b/core/modules/views/tests/src/Functional/ViewElementTest.php new file mode 100644 index 0000000..244bf35 --- /dev/null +++ b/core/modules/views/tests/src/Functional/ViewElementTest.php @@ -0,0 +1,173 @@ +enableViewsTestModule(); + } + + /** + * Tests the rendered output and form output of a view element. + */ + public function testViewElement() { + /** @var \Drupal\Core\Render\RendererInterface $renderer */ + $renderer = $this->container->get('renderer'); + $view = Views::getView('test_view_embed'); + + // Get the render array, #embed must be FALSE since this is the default + // display. + $render = $view->buildRenderable(); + $this->assertEqual($render['#embed'], FALSE); + $this->setRawContent($renderer->renderRoot($render)); + + $xpath = $this->xpath('//div[@class="views-element-container"]'); + $this->assertTrue($xpath, 'The view container has been found in the rendered output.'); + + $xpath = $this->xpath('//div[@class="view-content"]'); + $this->assertTrue($xpath, 'The view content has been found in the rendered output.'); + // There should be 5 rows in the results. + $xpath = $this->xpath('//div[@class="view-content"]/div'); + $this->assertEqual(count($xpath), 5); + + // Test a form. + $this->drupalGet('views_test_data_element_form'); + + $xpath = $this->xpath('//div[@class="views-element-container js-form-wrapper form-wrapper"]'); + $this->assertTrue($xpath, 'The view container has been found on the form.'); + + $xpath = $this->xpath('//div[@class="view-content"]'); + $this->assertTrue($xpath, 'The view content has been found on the form.'); + // There should be 5 rows in the results. + $xpath = $this->xpath('//div[@class="view-content"]/div'); + $this->assertEqual(count($xpath), 5); + + // Add an argument and save the view. + $view->displayHandlers->get('default')->overrideOption('arguments', array( + 'age' => array( + 'default_action' => 'ignore', + 'title' => '', + 'default_argument_type' => 'fixed', + 'validate' => array( + 'type' => 'none', + 'fail' => 'not found', + ), + 'break_phrase' => FALSE, + 'not' => FALSE, + 'id' => 'age', + 'table' => 'views_test_data', + 'field' => 'age', + 'plugin_id' => 'numeric', + ) + )); + $view->save(); + + // Test the render array again. + $view = Views::getView('test_view_embed'); + $render = $view->buildRenderable(NULL, [25]); + $this->setRawContent($renderer->renderRoot($render)); + // There should be 1 row in the results, 'John' arg 25. + $xpath = $this->xpath('//div[@class="view-content"]/div'); + $this->assertEqual(count($xpath), 1); + + // Test that the form has the same expected result. + $this->drupalGet('views_test_data_element_form'); + $xpath = $this->xpath('//div[@class="view-content"]/div'); + $this->assertEqual(count($xpath), 1); + } + + /** + * Tests the rendered output and form output of a view element, using the + * embed display plugin. + */ + public function testViewElementEmbed() { + /** @var \Drupal\Core\Render\RendererInterface $renderer */ + $renderer = $this->container->get('renderer'); + $view = Views::getView('test_view_embed'); + + // Get the render array, #embed must be TRUE since this is an embed display. + $render = $view->buildRenderable('embed_1'); + $this->assertEqual($render['#embed'], TRUE); + $this->setRawContent($renderer->renderRoot($render)); + + $xpath = $this->xpath('//div[@class="views-element-container"]'); + $this->assertTrue($xpath, 'The view container has been found in the rendered output.'); + + $xpath = $this->xpath('//div[@class="view-content"]'); + $this->assertTrue($xpath, 'The view content has been found in the rendered output.'); + // There should be 5 rows in the results. + $xpath = $this->xpath('//div[@class="view-content"]/div'); + $this->assertEqual(count($xpath), 5); + + // Test a form. + $this->drupalGet('views_test_data_element_embed_form'); + + $xpath = $this->xpath('//div[@class="views-element-container js-form-wrapper form-wrapper"]'); + $this->assertTrue($xpath, 'The view container has been found on the form.'); + + $xpath = $this->xpath('//div[@class="view-content"]'); + $this->assertTrue($xpath, 'The view content has been found on the form.'); + // There should be 5 rows in the results. + $xpath = $this->xpath('//div[@class="view-content"]/div'); + $this->assertEqual(count($xpath), 5); + + // Add an argument and save the view. + $view->displayHandlers->get('default')->overrideOption('arguments', array( + 'age' => array( + 'default_action' => 'ignore', + 'title' => '', + 'default_argument_type' => 'fixed', + 'validate' => array( + 'type' => 'none', + 'fail' => 'not found', + ), + 'break_phrase' => FALSE, + 'not' => FALSE, + 'id' => 'age', + 'table' => 'views_test_data', + 'field' => 'age', + 'plugin_id' => 'numeric', + ) + )); + $view->save(); + + // Test the render array again. + $view = Views::getView('test_view_embed'); + $render = $view->buildRenderable('embed_1', [25]); + $this->setRawContent($renderer->renderRoot($render)); + // There should be 1 row in the results, 'John' arg 25. + $xpath = $this->xpath('//div[@class="view-content"]/div'); + $this->assertEqual(count($xpath), 1); + + // Test that the form has the same expected result. + $this->drupalGet('views_test_data_element_embed_form'); + $xpath = $this->xpath('//div[@class="view-content"]/div'); + $this->assertEqual(count($xpath), 1); + + // Tests the render array with an exposed filter. + $view = Views::getView('test_view_embed'); + $render = $view->buildRenderable('embed_2'); + $this->setRawContent($renderer->renderRoot($render)); + + // Ensure that the exposed form is rendered. + $this->assertEqual(1, count($this->xpath('//form[@class="views-exposed-form"]'))); + } + +} diff --git a/core/modules/views/tests/src/Functional/ViewKernelTestBase.php b/core/modules/views/tests/src/Functional/ViewKernelTestBase.php new file mode 100644 index 0000000..4f606af --- /dev/null +++ b/core/modules/views/tests/src/Functional/ViewKernelTestBase.php @@ -0,0 +1,148 @@ +installSchema('system', array('sequences')); + $this->setUpFixtures(); + + if ($import_test_views) { + ViewTestData::createTestViews(get_class($this), array('views_test_config')); + } + } + + /** + * Sets up the configuration and schema of views and views_test_data modules. + * + * Because the schema of views_test_data.module is dependent on the test + * using it, it cannot be enabled normally. + */ + protected function setUpFixtures() { + // First install the system module. Many Views have Page displays have menu + // links, and for those to work, the system menus must already be present. + $this->installConfig(array('system')); + + // Define the schema and views data variable before enabling the test module. + \Drupal::state()->set('views_test_data_schema', $this->schemaDefinition()); + \Drupal::state()->set('views_test_data_views_data', $this->viewsData()); + + $this->installConfig(array('views', 'views_test_config', 'views_test_data')); + foreach ($this->schemaDefinition() as $table => $schema) { + $this->installSchema('views_test_data', $table); + } + + \Drupal::service('router.builder')->rebuild(); + + // Load the test dataset. + $data_set = $this->dataSet(); + $query = db_insert('views_test_data') + ->fields(array_keys($data_set[0])); + foreach ($data_set as $record) { + $query->values($record); + } + $query->execute(); + } + + /** + * Orders a nested array containing a result set based on a given column. + * + * @param array $result_set + * An array of rows from a result set, with each row as an associative + * array keyed by column name. + * @param string $column + * The column name by which to sort the result set. + * @param bool $reverse + * (optional) Boolean indicating whether to sort the result set in reverse + * order. Defaults to FALSE. + * + * @return array + * The sorted result set. + */ + protected function orderResultSet($result_set, $column, $reverse = FALSE) { + $order = $reverse ? -1 : 1; + usort($result_set, function ($a, $b) use ($column, $order) { + if ($a[$column] == $b[$column]) { + return 0; + } + return $order * (($a[$column] < $b[$column]) ? -1 : 1); + }); + return $result_set; + } + + /** + * Executes a view with debugging. + * + * @param \Drupal\views\ViewExecutable $view + * The view object. + * @param array $args + * (optional) An array of the view arguments to use for the view. + */ + protected function executeView($view, array $args = array()) { + $view->setDisplay(); + $view->preExecute($args); + $view->execute(); + $verbose_message = '
Executed view: ' . ((string) $view->build_info['query']) . '
'; + if ($view->build_info['query'] instanceof SelectInterface) { + $verbose_message .= '
Arguments: ' . print_r($view->build_info['query']->getArguments(), TRUE) . '
'; + } + $this->verbose($verbose_message); + } + + /** + * Returns the schema definition. + */ + protected function schemaDefinition() { + return ViewTestData::schemaDefinition(); + } + + /** + * Returns the views data definition. + */ + protected function viewsData() { + return ViewTestData::viewsData(); + } + + /** + * Returns a very simple test dataset. + */ + protected function dataSet() { + return ViewTestData::dataSet(); + } + +} diff --git a/core/modules/views/tests/src/Functional/ViewRenderTest.php b/core/modules/views/tests/src/Functional/ViewRenderTest.php new file mode 100644 index 0000000..a9b922d --- /dev/null +++ b/core/modules/views/tests/src/Functional/ViewRenderTest.php @@ -0,0 +1,42 @@ +enableViewsTestModule(); + } + + + /** + * Tests render functionality. + */ + public function testRender() { + \Drupal::state()->set('views_render.test', 0); + + // Make sure that the rendering just calls the preprocess function once. + $view = Views::getView('test_view_render'); + $output = $view->preview(); + $this->container->get('renderer')->renderRoot($output); + + $this->assertEqual(\Drupal::state()->get('views_render.test'), 1); + } + +} diff --git a/core/modules/views/tests/src/Functional/ViewResultAssertionTrait.php b/core/modules/views/tests/src/Functional/ViewResultAssertionTrait.php new file mode 100644 index 0000000..853ab05 --- /dev/null +++ b/core/modules/views/tests/src/Functional/ViewResultAssertionTrait.php @@ -0,0 +1,147 @@ +assertIdenticalResultsetHelper($view, $expected_result, $column_map, 'assertIdentical', $message); + } + + /** + * Verifies that a result set returned by a View differs from certain values. + * + * Inverse of ViewsTestCase::assertIdenticalResultset(). + * + * @param \Drupal\views\ViewExecutable $view + * An executed View. + * @param array $expected_result + * An expected result set. + * @param array $column_map + * (optional) An associative array mapping the columns of the result set + * from the view (as keys) and the expected result set (as values). + * @param string $message + * (optional) A custom message to display with the assertion. Defaults to + * 'Non-identical result set.' + * + * @return bool + * TRUE if the assertion succeeded, or FALSE otherwise. + */ + protected function assertNotIdenticalResultset($view, $expected_result, $column_map = array(), $message = NULL) { + return $this->assertIdenticalResultsetHelper($view, $expected_result, $column_map, 'assertNotIdentical', $message); + } + + /** + * Performs View result assertions. + * + * This is a helper method for ViewTestBase::assertIdenticalResultset() and + * ViewTestBase::assertNotIdenticalResultset(). + * + * @param \Drupal\views\ViewExecutable $view + * An executed View. + * @param array $expected_result + * An expected result set. + * @param array $column_map + * An associative array mapping the columns of the result set + * from the view (as keys) and the expected result set (as values). + * @param string $assert_method + * The TestBase assertion method to use (either 'assertIdentical' or + * 'assertNotIdentical'). + * @param string $message + * (optional) The message to display with the assertion. + * + * @return bool + * TRUE if the assertion succeeded, or FALSE otherwise. + */ + protected function assertIdenticalResultsetHelper($view, $expected_result, $column_map, $assert_method, $message = NULL) { + // Convert $view->result to an array of arrays. + $result = array(); + foreach ($view->result as $key => $value) { + $row = array(); + foreach ($column_map as $view_column => $expected_column) { + if (property_exists($value, $view_column)) { + $row[$expected_column] = (string) $value->$view_column; + } + // The comparison will be done on the string representation of the value. + // For entity fields we don't have the raw value. Let's try to fetch it + // using the entity itself. + elseif (empty($value->$view_column) && isset($view->field[$expected_column]) && ($field = $view->field[$expected_column]) && $field instanceof Field) { + $column = NULL; + if (count(explode(':', $view_column)) == 2) { + $column = explode(':', $view_column)[1]; + } + $row[$expected_column] = $field->getValue($value, $column); + } + } + $result[$key] = $row; + } + + // Remove the columns we don't need from the expected result. + foreach ($expected_result as $key => $value) { + $row = array(); + foreach ($column_map as $expected_column) { + // The comparison will be done on the string representation of the value. + if (is_object($value)) { + $row[$expected_column] = (string) $value->$expected_column; + } + // This case is about fields with multiple values. + elseif (is_array($value[$expected_column])) { + foreach (array_keys($value[$expected_column]) as $delta) { + $row[$expected_column][$delta] = (string) $value[$expected_column][$delta]; + } + } + else { + $row[$expected_column] = (string) $value[$expected_column]; + } + } + $expected_result[$key] = $row; + } + + $this->verbose('
'
+      . "\n\nQuery:\n" . $view->build_info['query']
+      . "\n\nQuery arguments:\n" . var_export($view->build_info['query']->getArguments(), TRUE)
+      . "\n\nActual result:\n" . var_export($result, TRUE)
+      . "\n\nExpected result:\n" . var_export($expected_result, TRUE));
+
+    // Reset the numbering of the arrays.
+    $result = array_values($result);
+    $expected_result = array_values($expected_result);
+
+    // Do the actual comparison.
+    if (!isset($message)) {
+      $not = (strpos($assert_method, 'Not') ? 'not' : '');
+      $message = format_string("Actual result 
\n@actual\n
is $not identical to expected
\n@expected\n
", array( + '@actual' => var_export($result, TRUE), + '@expected' => var_export($expected_result, TRUE), + )); + } + return $this->$assert_method($result, $expected_result, $message); + } + +} diff --git a/core/modules/views/tests/src/Functional/ViewTestBase.php b/core/modules/views/tests/src/Functional/ViewTestBase.php new file mode 100644 index 0000000..e998bae --- /dev/null +++ b/core/modules/views/tests/src/Functional/ViewTestBase.php @@ -0,0 +1,149 @@ +set('views_test_data_schema', $this->schemaDefinition()); + \Drupal::state()->set('views_test_data_views_data', $this->viewsData()); + + \Drupal::service('module_installer')->install(array('views_test_data')); + $this->resetAll(); + $this->rebuildContainer(); + $this->container->get('module_handler')->reload(); + + // Load the test dataset. + $data_set = $this->dataSet(); + $query = db_insert('views_test_data') + ->fields(array_keys($data_set[0])); + foreach ($data_set as $record) { + $query->values($record); + } + $query->execute(); + } + + /** + * Orders a nested array containing a result set based on a given column. + * + * @param array $result_set + * An array of rows from a result set, with each row as an associative + * array keyed by column name. + * @param string $column + * The column name by which to sort the result set. + * @param bool $reverse + * (optional) Boolean indicating whether to sort the result set in reverse + * order. Defaults to FALSE. + * + * @return array + * The sorted result set. + */ + protected function orderResultSet($result_set, $column, $reverse = FALSE) { + $order = $reverse ? -1 : 1; + usort($result_set, function ($a, $b) use ($column, $order) { + if ($a[$column] == $b[$column]) { + return 0; + } + return $order * (($a[$column] < $b[$column]) ? -1 : 1); + }); + return $result_set; + } + + /** + * Asserts the existence of a button with a certain ID and label. + * + * @param string $id + * The HTML ID of the button + * @param string $label. + * The expected label for the button. + * @param string $message + * (optional) A custom message to display with the assertion. If no custom + * message is provided, the message will indicate the button label. + * + * @return bool + * TRUE if the assertion was successful, or FALSE on failure. + */ + protected function helperButtonHasLabel($id, $expected_label, $message = 'Label has the expected value: %label.') { + return $this->assertFieldById($id, $expected_label, t($message, array('%label' => $expected_label))); + } + + /** + * Executes a view with debugging. + * + * @param \Drupal\views\ViewExecutable $view + * The view object. + * @param array $args + * (optional) An array of the view arguments to use for the view. + */ + protected function executeView(ViewExecutable $view, $args = array()) { + // A view does not really work outside of a request scope, due to many + // dependencies like the current user. + $view->setDisplay(); + $view->preExecute($args); + $view->execute(); + $verbose_message = '
Executed view: ' . ((string) $view->build_info['query']) . '
'; + if ($view->build_info['query'] instanceof SelectInterface) { + $verbose_message .= '
Arguments: ' . print_r($view->build_info['query']->getArguments(), TRUE) . '
'; + } + $this->verbose($verbose_message); + } + + /** + * Returns the schema definition. + */ + protected function schemaDefinition() { + return ViewTestData::schemaDefinition(); + } + + /** + * Returns the views data definition. + */ + protected function viewsData() { + return ViewTestData::viewsData(); + } + + /** + * Returns a very simple test dataset. + */ + protected function dataSet() { + return ViewTestData::dataSet(); + } + +} diff --git a/core/modules/views/tests/src/Functional/ViewTestData.php b/core/modules/views/tests/src/Functional/ViewTestData.php new file mode 100644 index 0000000..b1c0f4c --- /dev/null +++ b/core/modules/views/tests/src/Functional/ViewTestData.php @@ -0,0 +1,271 @@ +getStorage('view'); + $module_handler = \Drupal::moduleHandler(); + foreach ($modules as $module) { + $config_dir = drupal_get_path('module', $module) . '/test_views'; + if (!is_dir($config_dir) || !$module_handler->moduleExists($module)) { + continue; + } + + $file_storage = new FileStorage($config_dir); + $available_views = $file_storage->listAll('views.view.'); + foreach ($views as $id) { + $config_name = 'views.view.' . $id; + if (in_array($config_name, $available_views)) { + $storage + ->create($file_storage->read($config_name)) + ->save(); + } + } + } + } + + // Rebuild the router once. + \Drupal::service('router.builder')->rebuild(); + } + + /** + * Returns the schema definition. + */ + public static function schemaDefinition() { + $schema['views_test_data'] = array( + 'description' => 'Basic test table for Views tests.', + 'fields' => array( + 'id' => array( + 'type' => 'serial', + 'unsigned' => TRUE, + 'not null' => TRUE, + ), + 'name' => array( + 'description' => "A person's name", + 'type' => 'varchar_ascii', + 'length' => 255, + 'not null' => TRUE, + 'default' => '', + ), + 'age' => array( + 'description' => "The person's age", + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0), + 'job' => array( + 'description' => "The person's job", + 'type' => 'varchar', + 'length' => 255, + 'not null' => TRUE, + 'default' => 'Undefined', + ), + 'created' => array( + 'description' => "The creation date of this record", + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + 'status' => array( + 'description' => "The status of this record", + 'type' => 'int', + 'unsigned' => TRUE, + 'not null' => TRUE, + 'default' => 0, + ), + ), + 'primary key' => array('id'), + 'unique keys' => array( + 'name' => array('name') + ), + 'indexes' => array( + 'ages' => array('age'), + ), + ); + return $schema; + } + + /** + * Returns the views data definition. + */ + public static function viewsData() { + // Declaration of the base table. + $data['views_test_data']['table'] = array( + 'group' => 'Views test', + 'base' => array( + 'field' => 'id', + 'title' => 'Views test data', + 'help' => 'Users who have created accounts on your site.', + ), + ); + + // Declaration of fields. + $data['views_test_data']['id'] = array( + 'title' => 'ID', + 'help' => 'The test data ID', + 'field' => array( + 'id' => 'numeric', + ), + 'argument' => array( + 'id' => 'numeric', + ), + 'filter' => array( + 'id' => 'numeric', + ), + 'sort' => array( + 'id' => 'standard', + ), + ); + $data['views_test_data']['name'] = array( + 'title' => 'Name', + 'help' => 'The name of the person', + 'field' => array( + 'id' => 'standard', + ), + 'argument' => array( + 'id' => 'string', + ), + 'filter' => array( + 'id' => 'string', + ), + 'sort' => array( + 'id' => 'standard', + ), + ); + $data['views_test_data']['age'] = array( + 'title' => 'Age', + 'help' => 'The age of the person', + 'field' => array( + 'id' => 'numeric', + ), + 'argument' => array( + 'id' => 'numeric', + ), + 'filter' => array( + 'id' => 'numeric', + ), + 'sort' => array( + 'id' => 'standard', + ), + ); + $data['views_test_data']['job'] = array( + 'title' => 'Job', + 'help' => 'The job of the person', + 'field' => array( + 'id' => 'standard', + ), + 'argument' => array( + 'id' => 'string', + ), + 'filter' => array( + 'id' => 'string', + ), + 'sort' => array( + 'id' => 'standard', + ), + ); + $data['views_test_data']['created'] = array( + 'title' => 'Created', + 'help' => 'The creation date of this record', + 'field' => array( + 'id' => 'date', + ), + 'argument' => array( + 'id' => 'date', + ), + 'filter' => array( + 'id' => 'date', + ), + 'sort' => array( + 'id' => 'date', + ), + ); + $data['views_test_data']['status'] = array( + 'title' => 'Status', + 'help' => 'The status of this record', + 'field' => array( + 'id' => 'boolean', + ), + 'filter' => array( + 'id' => 'boolean', + ), + 'sort' => array( + 'id' => 'standard', + ), + ); + return $data; + } + + /** + * Returns a very simple test dataset. + */ + public static function dataSet() { + return array( + array( + 'name' => 'John', + 'age' => 25, + 'job' => 'Singer', + 'created' => gmmktime(0, 0, 0, 1, 1, 2000), + 'status' => 1, + ), + array( + 'name' => 'George', + 'age' => 27, + 'job' => 'Singer', + 'created' => gmmktime(0, 0, 0, 1, 2, 2000), + 'status' => 0, + ), + array( + 'name' => 'Ringo', + 'age' => 28, + 'job' => 'Drummer', + 'created' => gmmktime(6, 30, 30, 1, 1, 2000), + 'status' => 1, + ), + array( + 'name' => 'Paul', + 'age' => 26, + 'job' => 'Songwriter', + 'created' => gmmktime(6, 0, 0, 1, 1, 2000), + 'status' => 0, + ), + array( + 'name' => 'Meredith', + 'age' => 30, + 'job' => 'Speaker', + 'created' => gmmktime(6, 30, 10, 1, 1, 2000), + 'status' => 1, + ), + ); + } + +} diff --git a/core/modules/views/tests/src/Functional/ViewsEscapingTest.php b/core/modules/views/tests/src/Functional/ViewsEscapingTest.php new file mode 100644 index 0000000..886cd48 --- /dev/null +++ b/core/modules/views/tests/src/Functional/ViewsEscapingTest.php @@ -0,0 +1,84 @@ +enableViewsTestModule(); + } + + /** + * Tests for incorrectly escaped markup in the views-view-fields.html.twig. + */ + public function testViewsViewFieldsEscaping() { + // Test with system theme using theme function. + $this->drupalGet('test_page_display_200'); + + // Assert that there are no escaped '<'s characters. + $this->assertNoEscaped('<'); + + // Install theme to test with template system. + \Drupal::service('theme_handler')->install(array('views_test_theme')); + + // Make base theme default then test for hook invocations. + $this->config('system.theme') + ->set('default', 'views_test_theme') + ->save(); + $this->assertEqual($this->config('system.theme')->get('default'), 'views_test_theme'); + + $this->drupalGet('test_page_display_200'); + + // Assert that we are using the correct template. + $this->assertText('force', 'The force is strong with this one'); + + // Assert that there are no escaped '<'s characters. + $this->assertNoEscaped('<'); + } + + /** + * Tests for incorrectly escaped markup in a header label on a display table. + */ + public function testViewsFieldHeaderEscaping() { + // Test with a field header label having an html element wrapper. + $this->drupalGet('test_field_header'); + + // Assert that there are no escaped '<'s characters. + $this->assertNoEscaped('<'); + + // Test with a field header label having a XSS test as a wrapper. + $this->drupalGet('test_field_header_xss'); + + // Assert that XSS test is escaped. + $this->assertNoRaw('', 'Harmful tags are escaped in header label.'); + } + +} diff --git a/core/modules/views/tests/src/Functional/ViewsFormMultipleTest.php b/core/modules/views/tests/src/Functional/ViewsFormMultipleTest.php new file mode 100644 index 0000000..cc11ca4 --- /dev/null +++ b/core/modules/views/tests/src/Functional/ViewsFormMultipleTest.php @@ -0,0 +1,60 @@ +enableViewsTestModule(); + } + + /** + * {@inheritdoc} + */ + protected function viewsData() { + $data = parent::viewsData(); + $data['views_test_data']['field_form_button_test']['field'] = [ + 'title' => t('Button test'), + 'help' => t('Adds a test form button.'), + 'id' => 'field_form_button_test', + ]; + return $data; + } + + + /** + * Tests the a page with multiple View forms in it. + */ + public function testViewsFormMultiple() { + // Get the test page. + $this->drupalGet('views_test_form_multiple'); + + $this->assertText('Test base form ID with Views forms and arguments.'); + + // Submit the forms, validate argument returned in message set by handler. + // @note There is not a way to specify a specific index for a submit button. So + // the row index returned is always the last occurrence. + $this->drupalPostForm(NULL, [], t('Test Button'), [], [], 'views-form-test-form-multiple-default-arg2'); + $this->assertText('The test button at row 4 for test_form_multiple (default) View with args: arg2 was submitted.'); + $this->drupalPostForm(NULL, [], t('Test Button'), [], [], 'views-form-test-form-multiple-default-arg1'); + $this->assertText('The test button at row 4 for test_form_multiple (default) View with args: arg1 was submitted.'); + } + +} diff --git a/core/modules/views/tests/src/Functional/ViewsTemplateTest.php b/core/modules/views/tests/src/Functional/ViewsTemplateTest.php new file mode 100644 index 0000000..7ad0b46 --- /dev/null +++ b/core/modules/views/tests/src/Functional/ViewsTemplateTest.php @@ -0,0 +1,46 @@ +enableViewsTestModule(); + ViewTestData::createTestViews(get_class($this), array('views_test_config')); + } + + /** + * Tests render functionality. + */ + public function testTemplate() { + + // Make sure that the rendering just calls the preprocess function once. + $view = Views::getView('test_view_display_template'); + $output = $view->preview(); + + // Check if we got the rendered output of our template file. + $this->assertTrue(strpos(\Drupal::service('renderer')->renderRoot($output), 'This module defines its own display template.') !== FALSE, 'Display plugin DisplayTemplateTest defines its own template.'); + + } + +} diff --git a/core/modules/views/tests/src/Functional/ViewsThemeIntegrationTest.php b/core/modules/views/tests/src/Functional/ViewsThemeIntegrationTest.php new file mode 100644 index 0000000..933e62f --- /dev/null +++ b/core/modules/views/tests/src/Functional/ViewsThemeIntegrationTest.php @@ -0,0 +1,81 @@ +enableViewsTestModule(); + } + + /** + * Tests for exceptions and successful execution of hook_views_pre_render() + * and hook_views_post_render() in theme and subtheme. + */ + public function testThemedViewPage() { + + \Drupal::service('theme_handler')->install(array('test_basetheme', 'test_subtheme')); + + // Make base theme default then test for hook invocations. + $this->config('system.theme') + ->set('default', 'test_basetheme') + ->save(); + $this->assertEqual($this->config('system.theme')->get('default'), 'test_basetheme'); + + // Make sure a views rendered page is touched. + $this->drupalGet('test_page_display_200'); + + $this->assertRaw("test_basetheme_views_pre_render", "Views title changed by test_basetheme.test_basetheme_views_pre_render"); + $this->assertRaw("test_basetheme_views_post_render", "Views title changed by test_basetheme.test_basetheme_views_post_render"); + + // Make sub theme default to test for hook invocation + // from both sub and base theme. + $this->config('system.theme') + ->set('default', 'test_subtheme') + ->save(); + $this->assertEqual($this->config('system.theme')->get('default'), 'test_subtheme'); + + // Make sure a views rendered page is touched. + $this->drupalGet('test_page_display_200'); + + $this->assertRaw("test_subtheme_views_pre_render", "Views title changed by test_subtheme.test_subtheme_views_pre_render"); + $this->assertRaw("test_subtheme_views_post_render", "Views title changed by test_subtheme.test_subtheme_views_post_render"); + + $this->assertRaw("test_basetheme_views_pre_render", "Views title changed by test_basetheme.test_basetheme_views_pre_render"); + $this->assertRaw("test_basetheme_views_post_render", "Views title changed by test_basetheme.test_basetheme_views_post_render"); + + $this->assertRaw('' . count($this->dataSet()) . ' items found.', 'Views group title added by test_subtheme.test_subtheme_views_post_render'); + } + +} diff --git a/core/modules/views_ui/tests/src/Functional/AnalyzeTest.php b/core/modules/views_ui/tests/src/Functional/AnalyzeTest.php new file mode 100644 index 0000000..866c9ac --- /dev/null +++ b/core/modules/views_ui/tests/src/Functional/AnalyzeTest.php @@ -0,0 +1,59 @@ +enableViewsTestModule(); + + // Add an admin user will full rights; + $this->admin = $this->drupalCreateUser(array('administer views')); + } + + /** + * Tests that analyze works in general. + */ + function testAnalyzeBasic() { + $this->drupalLogin($this->admin); + + $this->drupalGet('admin/structure/views/view/test_view/edit'); + $this->assertLink(t('Analyze view')); + + // This redirects the user to the analyze form. + $this->clickLink(t('Analyze view')); + $this->assertText(t('View analysis')); + + foreach (array('ok', 'warning', 'error') as $type) { + $xpath = $this->xpath('//div[contains(@class, :class)]', array(':class' => $type)); + $this->assertTrue(count($xpath), format_string('Analyse messages with @type found', array('@type' => $type))); + } + + // This redirects the user back to the main views edit page. + $this->drupalPostForm(NULL, array(), t('Ok')); + } + +} diff --git a/core/modules/views_ui/tests/src/Functional/AreaEntityUITest.php b/core/modules/views_ui/tests/src/Functional/AreaEntityUITest.php new file mode 100644 index 0000000..d1894d8 --- /dev/null +++ b/core/modules/views_ui/tests/src/Functional/AreaEntityUITest.php @@ -0,0 +1,101 @@ + 'test_id', 'plugin' => 'system_main_block']); + $block->save(); + + $entity_test = EntityTest::create(['bundle' => 'entity_test']); + $entity_test->save(); + + $default = $this->randomView([]); + $id = $default['id']; + $view = View::load($id); + + $this->drupalGet($view->urlInfo('edit-form')); + + // Add a global NULL argument to the view for testing argument placeholders. + $this->drupalPostForm("admin/structure/views/nojs/add-handler/$id/page_1/argument", ['name[views.null]' => 1], 'Add and configure contextual filters'); + $this->drupalPostForm(NULL, [], 'Apply'); + + // Configure both the entity_test area header and the block header to + // reference the given entities. + $this->drupalPostForm("admin/structure/views/nojs/add-handler/$id/page_1/header", ['name[views.entity_block]' => 1], 'Add and configure header'); + $this->drupalPostForm(NULL, ['options[target]' => $block->id()], 'Apply'); + + $this->drupalPostForm("admin/structure/views/nojs/add-handler/$id/page_1/header", ['name[views.entity_entity_test]' => 1], 'Add and configure header'); + $this->drupalPostForm(NULL, ['options[target]' => $entity_test->id()], 'Apply'); + + $this->drupalPostForm(NULL, [], 'Save'); + + // Confirm the correct target identifiers were saved for both entities. + $view = View::load($id); + $header = $view->getDisplay('default')['display_options']['header']; + $this->assertEqual(['entity_block', 'entity_entity_test'], array_keys($header)); + + $this->assertEqual($block->id(), $header['entity_block']['target']); + $this->assertEqual($entity_test->uuid(), $header['entity_entity_test']['target']); + + // Confirm that the correct serial ID (for the entity_test) and config ID + // (for the block) are displayed in the form. + $this->drupalGet("admin/structure/views/nojs/handler/$id/page_1/header/entity_block"); + $this->assertFieldByName('options[target]', $block->id()); + + $this->drupalGet("admin/structure/views/nojs/handler/$id/page_1/header/entity_entity_test"); + $this->assertFieldByName('options[target]', $entity_test->id()); + + // Replace the header target entities with argument placeholders. + $this->drupalPostForm("admin/structure/views/nojs/handler/$id/page_1/header/entity_block", ['options[target]' => '{{ raw_arguments.null }}'], 'Apply'); + $this->drupalPostForm("admin/structure/views/nojs/handler/$id/page_1/header/entity_entity_test", ['options[target]' => '{{ raw_arguments.null }}'], 'Apply'); + $this->drupalPostForm(NULL, [], 'Save'); + + // Confirm that the argument placeholders are saved. + $view = View::load($id); + $header = $view->getDisplay('default')['display_options']['header']; + $this->assertEqual(['entity_block', 'entity_entity_test'], array_keys($header)); + + $this->assertEqual('{{ raw_arguments.null }}', $header['entity_block']['target']); + $this->assertEqual('{{ raw_arguments.null }}', $header['entity_entity_test']['target']); + + // Confirm that the argument placeholders are still displayed in the form. + $this->drupalGet("admin/structure/views/nojs/handler/$id/page_1/header/entity_block"); + $this->assertFieldByName('options[target]', '{{ raw_arguments.null }}'); + + $this->drupalGet("admin/structure/views/nojs/handler/$id/page_1/header/entity_entity_test"); + $this->assertFieldByName('options[target]', '{{ raw_arguments.null }}'); + + // Change the targets for both headers back to the entities. + $this->drupalPostForm("admin/structure/views/nojs/handler/$id/page_1/header/entity_block", ['options[target]' => $block->id()], 'Apply'); + $this->drupalPostForm("admin/structure/views/nojs/handler/$id/page_1/header/entity_entity_test", ['options[target]' => $entity_test->id()], 'Apply'); + $this->drupalPostForm(NULL, [], 'Save'); + + // Confirm the targets were again saved correctly and not skipped based on + // the previous form value. + $view = View::load($id); + $header = $view->getDisplay('default')['display_options']['header']; + $this->assertEqual(['entity_block', 'entity_entity_test'], array_keys($header)); + + $this->assertEqual($block->id(), $header['entity_block']['target']); + $this->assertEqual($entity_test->uuid(), $header['entity_entity_test']['target']); + } + +} diff --git a/core/modules/views_ui/tests/src/Functional/ArgumentValidatorTest.php b/core/modules/views_ui/tests/src/Functional/ArgumentValidatorTest.php new file mode 100644 index 0000000..c5e8912 --- /dev/null +++ b/core/modules/views_ui/tests/src/Functional/ArgumentValidatorTest.php @@ -0,0 +1,57 @@ +saveArgumentHandlerWithValidationOptions(TRUE); + $view = Views::getView('test_argument'); + $handler = $view->getHandler('default', 'argument', 'id'); + $this->assertTrue($handler['specify_validation'], 'Validation for this argument has been turned on.'); + $this->assertEqual('entity:node', $handler['validate']['type'], 'Validation for the argument is based on the node.'); + + // Uncheck the 'Specify validation criteria' checkbox and expect the + // validation type to be reset back to 'none'. + $this->saveArgumentHandlerWithValidationOptions(FALSE); + $view = Views::getView('test_argument'); + $handler = $view->getHandler('default', 'argument', 'id'); + $this->assertFalse($handler['specify_validation'], 'Validation for this argument has been turned off.'); + $this->assertEqual('none', $handler['validate']['type'], 'Validation for the argument has been reverted to Basic Validation.'); + } + + /** + * Saves the test_argument view with changes made to the argument handler + * both with and without specify_validation turned on. + * + * @param bool $specify_validation + */ + protected function saveArgumentHandlerWithValidationOptions($specify_validation) { + $options = array( + 'options[validate][type]' => 'entity---node', + 'options[specify_validation]' => $specify_validation, + ); + $this->drupalPostForm('admin/structure/views/nojs/handler/test_argument/default/argument/id', $options, t('Apply')); + $this->drupalPostForm('admin/structure/views/view/test_argument', array(), t('Save')); + } + +} diff --git a/core/modules/views_ui/tests/src/Functional/CachedDataUITest.php b/core/modules/views_ui/tests/src/Functional/CachedDataUITest.php new file mode 100644 index 0000000..adca41a --- /dev/null +++ b/core/modules/views_ui/tests/src/Functional/CachedDataUITest.php @@ -0,0 +1,74 @@ +fullAdminUser->id(); + + $temp_store = $this->container->get('user.shared_tempstore')->get('views'); + // The view should not be locked. + $this->assertEqual($temp_store->getMetadata('test_view'), NULL, 'The view is not locked.'); + + $this->drupalGet('admin/structure/views/view/test_view/edit'); + // Make sure we have 'changes' to the view. + $this->drupalPostForm('admin/structure/views/nojs/display/test_view/default/title', array(), t('Apply')); + $this->assertText('You have unsaved changes.'); + $this->assertEqual($temp_store->getMetadata('test_view')->owner, $views_admin_user_uid, 'View cache has been saved.'); + + $view_cache = $temp_store->get('test_view'); + // The view should be enabled. + $this->assertTrue($view_cache->status(), 'The view is enabled.'); + // The view should now be locked. + $this->assertEqual($temp_store->getMetadata('test_view')->owner, $views_admin_user_uid, 'The view is locked.'); + + // Cancel the view edit and make sure the cache is deleted. + $this->drupalPostForm(NULL, array(), t('Cancel')); + $this->assertEqual($temp_store->getMetadata('test_view'), NULL, 'User tempstore data has been removed.'); + // Test we are redirected to the view listing page. + $this->assertUrl('admin/structure/views', array(), 'Redirected back to the view listing page.'); + + // Log in with another user and make sure the view is locked and break. + $this->drupalPostForm('admin/structure/views/nojs/display/test_view/default/title', array(), t('Apply')); + $this->drupalLogin($this->adminUser); + + $this->drupalGet('admin/structure/views/view/test_view/edit'); + // Test that save and cancel buttons are not shown. + $this->assertNoFieldById('edit-actions-submit', t('Save')); + $this->assertNoFieldById('edit-actions-cancel', t('Cancel')); + // Test we have the break lock link. + $this->assertLinkByHref('admin/structure/views/view/test_view/break-lock'); + // Break the lock. + $this->clickLink(t('break this lock')); + $this->drupalPostForm(NULL, array(), t('Break lock')); + // Test that save and cancel buttons are shown. + $this->assertFieldById('edit-actions-submit', t('Save')); + $this->assertFieldById('edit-actions-cancel', t('Cancel')); + // Test we can save the view. + $this->drupalPostForm('admin/structure/views/view/test_view/edit', array(), t('Save')); + $this->assertRaw(t('The view %view has been saved.', array('%view' => 'Test view'))); + + // Test that a deleted view has no tempstore data. + $this->drupalPostForm('admin/structure/views/nojs/display/test_view/default/title', array(), t('Apply')); + $this->drupalPostForm('admin/structure/views/view/test_view/delete', array(), t('Delete')); + // No view tempstore data should be returned for this view after deletion. + $this->assertEqual($temp_store->getMetadata('test_view'), NULL, 'View tempstore data has been removed after deletion.'); + } + +} diff --git a/core/modules/views_ui/tests/src/Functional/CustomBooleanTest.php b/core/modules/views_ui/tests/src/Functional/CustomBooleanTest.php new file mode 100644 index 0000000..b2b95d3 --- /dev/null +++ b/core/modules/views_ui/tests/src/Functional/CustomBooleanTest.php @@ -0,0 +1,181 @@ +setDisplay(); + + $view->displayHandlers->get('default')->overrideOption('fields', array( + 'age' => array( + 'id' => 'age', + 'table' => 'views_test_data', + 'field' => 'age', + 'relationship' => 'none', + 'plugin_id' => 'boolean', + ), + )); + $view->save(); + + $this->executeView($view); + + $custom_true = 'Yay'; + $custom_false = 'Nay'; + + // Set up some custom value mappings for different types. + $custom_values = array( + 'plain' => array( + 'true' => $custom_true, + 'false' => $custom_false, + 'test' => 'assertTrue', + ), + 'allowed tag' => array( + 'true' => '

' . $custom_true . '

', + 'false' => '

' . $custom_false . '

', + 'test' => 'assertTrue', + ), + 'disallowed tag' => array( + 'true' => '', + 'false' => '', + 'test' => 'assertFalse', + ), + ); + + // Run the same tests on each type. + foreach ($custom_values as $type => $values) { + $options = array( + 'options[type]' => 'custom', + 'options[type_custom_true]' => $values['true'], + 'options[type_custom_false]' => $values['false'], + ); + $this->drupalPostForm('admin/structure/views/nojs/handler/test_view/default/field/age', $options, 'Apply'); + + // Save the view. + $this->drupalPostForm('admin/structure/views/view/test_view', array(), 'Save'); + + $view = Views::getView('test_view'); + $output = $view->preview(); + $output = \Drupal::service('renderer')->renderRoot($output); + $this->{$values['test']}(strpos($output, $values['true']), SafeMarkup::format('Expected custom boolean TRUE value %value in output for %type', ['%value' => $values['true'], '%type' => $type])); + $this->{$values['test']}(strpos($output, $values['false']), SafeMarkup::format('Expected custom boolean FALSE value %value in output for %type', ['%value' => $values['false'], '%type' => $type])); + } + } + + /** + * Tests the setting and output of custom labels for boolean values. + */ + public function testCustomOptionTemplate() { + // Install theme to test with template system. + \Drupal::service('theme_handler')->install(['views_test_theme']); + + // Set the default theme for Views preview. + $this->config('system.theme') + ->set('default', 'views_test_theme') + ->save(); + $this->assertEqual($this->config('system.theme')->get('default'), 'views_test_theme'); + + // Add the boolean field handler to the test view. + $view = Views::getView('test_view'); + $view->setDisplay(); + + $view->displayHandlers->get('default')->overrideOption('fields', [ + 'age' => [ + 'id' => 'age', + 'table' => 'views_test_data', + 'field' => 'age', + 'relationship' => 'none', + 'plugin_id' => 'boolean', + ], + ]); + $view->save(); + + $this->executeView($view); + + $custom_true = 'Yay'; + $custom_false = 'Nay'; + + // Set up some custom value mappings for different types. + $custom_values = array( + 'plain' => array( + 'true' => $custom_true, + 'false' => $custom_false, + 'test' => 'assertTrue', + ), + 'allowed tag' => array( + 'true' => '

' . $custom_true . '

', + 'false' => '

' . $custom_false . '

', + 'test' => 'assertTrue', + ), + 'disallowed tag' => array( + 'true' => '', + 'false' => '', + 'test' => 'assertFalse', + ), + ); + + // Run the same tests on each type. + foreach ($custom_values as $type => $values) { + $options = array( + 'options[type]' => 'custom', + 'options[type_custom_true]' => $values['true'], + 'options[type_custom_false]' => $values['false'], + ); + $this->drupalPostForm('admin/structure/views/nojs/handler/test_view/default/field/age', $options, 'Apply'); + + // Save the view. + $this->drupalPostForm('admin/structure/views/view/test_view', array(), 'Save'); + + $view = Views::getView('test_view'); + $output = $view->preview(); + $output = \Drupal::service('renderer')->renderRoot($output); + $this->{$values['test']}(strpos($output, $values['true']), SafeMarkup::format('Expected custom boolean TRUE value %value in output for %type', ['%value' => $values['true'], '%type' => $type])); + $this->{$values['test']}(strpos($output, $values['false']), SafeMarkup::format('Expected custom boolean FALSE value %value in output for %type', ['%value' => $values['false'], '%type' => $type])); + + // Assert that we are using the correct template. + $this->setRawContent($output); + $this->assertText('llama', 'Loaded the correct views-view-field.html.twig template'); + } + } + +} diff --git a/core/modules/views_ui/tests/src/Functional/DefaultViewsTest.php b/core/modules/views_ui/tests/src/Functional/DefaultViewsTest.php new file mode 100644 index 0000000..ad3469c --- /dev/null +++ b/core/modules/views_ui/tests/src/Functional/DefaultViewsTest.php @@ -0,0 +1,247 @@ +drupalPlaceBlock('page_title_block'); + } + + /** + * Tests default views. + */ + function testDefaultViews() { + // Make sure the view starts off as disabled (does not appear on the listing + // page). + $edit_href = 'admin/structure/views/view/glossary'; + $this->drupalGet('admin/structure/views'); + // @todo Disabled default views do now appear on the front page. Test this + // behavior with templates instead. + // $this->assertNoLinkByHref($edit_href); + + // Enable the view, and make sure it is now visible on the main listing + // page. + $this->drupalGet('admin/structure/views'); + $this->clickViewsOperationLink(t('Enable'), '/glossary/'); + $this->assertUrl('admin/structure/views'); + $this->assertLinkByHref($edit_href); + + // It should not be possible to revert the view yet. + // @todo Figure out how to handle this with the new configuration system. + // $this->assertNoLink(t('Revert')); + // $revert_href = 'admin/structure/views/view/glossary/revert'; + // $this->assertNoLinkByHref($revert_href); + + // Edit the view and change the title. Make sure that the new title is + // displayed. + $new_title = $this->randomMachineName(16); + $edit = array('title' => $new_title); + $this->drupalPostForm('admin/structure/views/nojs/display/glossary/page_1/title', $edit, t('Apply')); + $this->drupalPostForm('admin/structure/views/view/glossary/edit/page_1', array(), t('Save')); + $this->drupalGet('glossary'); + $this->assertResponse(200); + $this->assertText($new_title); + + // Save another view in the UI. + $this->drupalPostForm('admin/structure/views/nojs/display/archive/page_1/title', array(), t('Apply')); + $this->drupalPostForm('admin/structure/views/view/archive/edit/page_1', array(), t('Save')); + + // Check there is an enable link. i.e. The view has not been enabled after + // editing. + $this->drupalGet('admin/structure/views'); + $this->assertLinkByHref('admin/structure/views/view/archive/enable'); + // Enable it again so it can be tested for access permissions. + $this->clickViewsOperationLink(t('Enable'), '/archive/'); + + // It should now be possible to revert the view. Do that, and make sure the + // view title we added above no longer is displayed. + // $this->drupalGet('admin/structure/views'); + // $this->assertLink(t('Revert')); + // $this->assertLinkByHref($revert_href); + // $this->drupalPostForm($revert_href, array(), t('Revert')); + // $this->drupalGet('glossary'); + // $this->assertNoText($new_title); + + // Duplicate the view and check that the normal schema of duplicated views is used. + $this->drupalGet('admin/structure/views'); + $this->clickViewsOperationLink(t('Duplicate'), '/glossary'); + $edit = array( + 'id' => 'duplicate_of_glossary', + ); + $this->assertTitle(t('Duplicate of @label | @site-name', array('@label' => 'Glossary', '@site-name' => $this->config('system.site')->get('name')))); + $this->drupalPostForm(NULL, $edit, t('Duplicate')); + $this->assertUrl('admin/structure/views/view/duplicate_of_glossary', array(), 'The normal duplicating name schema is applied.'); + + // Duplicate a view and set a custom name. + $this->drupalGet('admin/structure/views'); + $this->clickViewsOperationLink(t('Duplicate'), '/glossary'); + $random_name = strtolower($this->randomMachineName()); + $this->drupalPostForm(NULL, array('id' => $random_name), t('Duplicate')); + $this->assertUrl("admin/structure/views/view/$random_name", array(), 'The custom view name got saved.'); + + // Now disable the view, and make sure it stops appearing on the main view + // listing page but instead goes back to displaying on the disabled views + // listing page. + // @todo Test this behavior with templates instead. + $this->drupalGet('admin/structure/views'); + $this->clickViewsOperationLink(t('Disable'), '/glossary/'); + // $this->assertUrl('admin/structure/views'); + // $this->assertNoLinkByHref($edit_href); + // The easiest way to verify it appears on the disabled views listing page + // is to try to click the "enable" link from there again. + $this->drupalGet('admin/structure/views'); + $this->clickViewsOperationLink(t('Enable'), '/glossary/'); + $this->assertUrl('admin/structure/views'); + $this->assertLinkByHref($edit_href); + + // Clear permissions for anonymous users to check access for default views. + Role::load(RoleInterface::ANONYMOUS_ID)->revokePermission('access content')->save(); + + // Test the default views disclose no data by default. + $this->drupalLogout(); + $this->drupalGet('glossary'); + $this->assertResponse(403); + $this->drupalGet('archive'); + $this->assertResponse(403); + + // Test deleting a view. + $this->drupalLogin($this->fullAdminUser); + $this->drupalGet('admin/structure/views'); + $this->clickViewsOperationLink(t('Delete'), '/glossary/'); + // Submit the confirmation form. + $this->drupalPostForm(NULL, array(), t('Delete')); + // Ensure the view is no longer listed. + $this->assertUrl('admin/structure/views'); + $this->assertNoLinkByHref($edit_href); + // Ensure the view is no longer available. + $this->drupalGet($edit_href); + $this->assertResponse(404); + $this->assertText('Page not found'); + + // Delete all duplicated Glossary views. + $this->drupalGet('admin/structure/views'); + $this->clickViewsOperationLink(t('Delete'), 'duplicate_of_glossary'); + // Submit the confirmation form. + $this->drupalPostForm(NULL, array(), t('Delete')); + + $this->drupalGet('glossary'); + $this->assertResponse(200); + + $this->drupalGet('admin/structure/views'); + $this->clickViewsOperationLink(t('Delete'), $random_name); + // Submit the confirmation form. + $this->drupalPostForm(NULL, array(), t('Delete')); + $this->drupalGet('glossary'); + $this->assertResponse(404); + $this->assertText('Page not found'); + } + + /** + * Tests that enabling views moves them to the correct table. + */ + function testSplitListing() { + // Build a re-usable xpath query. + $xpath = '//div[@id="views-entity-list"]/div[@class = :status]/table//tr[@title = :title]'; + $arguments = array( + ':status' => 'views-list-section enabled', + ':title' => t('Machine name: test_view_status'), + ); + + $this->drupalGet('admin/structure/views'); + + $elements = $this->xpath($xpath, $arguments); + $this->assertIdentical(count($elements), 0, 'A disabled view is not found in the enabled views table.'); + + $arguments[':status'] = 'views-list-section disabled'; + $elements = $this->xpath($xpath, $arguments); + $this->assertIdentical(count($elements), 1, 'A disabled view is found in the disabled views table.'); + + // Enable the view. + $this->clickViewsOperationLink(t('Enable'), '/test_view_status/'); + + $elements = $this->xpath($xpath, $arguments); + $this->assertIdentical(count($elements), 0, 'After enabling a view, it is not found in the disabled views table.'); + + $arguments[':status'] = 'views-list-section enabled'; + $elements = $this->xpath($xpath, $arguments); + $this->assertIdentical(count($elements), 1, 'After enabling a view, it is found in the enabled views table.'); + + // Attempt to disable the view by path directly, with no token. + $this->drupalGet('admin/structure/views/view/test_view_status/disable'); + $this->assertResponse(403); + } + + /** + * Tests that page displays show the correct path. + */ + public function testPathDestination() { + $this->drupalGet('admin/structure/views'); + + // Check that links to views on default tabs are rendered correctly. + $this->assertLinkByHref('test_page_display_menu'); + $this->assertNoLinkByHref('test_page_display_menu/default'); + $this->assertLinkByHref('test_page_display_menu/local'); + + // Check that a dynamic path is shown as text. + $this->assertRaw('test_route_with_suffix/%/suffix'); + $this->assertNoLinkByHref(Url::fromUri('base:test_route_with_suffix/%/suffix')->toString()); + } + + /** + * Click a link to perform an operation on a view. + * + * In general, we expect lots of links titled "enable" or "disable" on the + * various views listing pages, and they might have tokens in them. So we + * need special code to find the correct one to click. + * + * @param $label + * Text between the anchor tags of the desired link. + * @param $unique_href_part + * A unique string that is expected to occur within the href of the desired + * link. For example, if the link URL is expected to look like + * "admin/structure/views/view/glossary/*", then "/glossary/" could be + * passed as the expected unique string. + * + * @return + * The page content that results from clicking on the link, or FALSE on + * failure. Failure also results in a failed assertion. + */ + function clickViewsOperationLink($label, $unique_href_part) { + $links = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label)); + foreach ($links as $link_index => $link) { + $position = strpos($link['href'], $unique_href_part); + if ($position !== FALSE) { + $index = $link_index; + break; + } + } + $this->assertTrue(isset($index), format_string('Link to "@label" containing @part found.', array('@label' => $label, '@part' => $unique_href_part))); + if (isset($index)) { + return $this->clickLink($label, $index); + } + else { + return FALSE; + } + } + +} diff --git a/core/modules/views_ui/tests/src/Functional/DisplayAttachmentTest.php b/core/modules/views_ui/tests/src/Functional/DisplayAttachmentTest.php new file mode 100644 index 0000000..37a0b19 --- /dev/null +++ b/core/modules/views_ui/tests/src/Functional/DisplayAttachmentTest.php @@ -0,0 +1,61 @@ +drupalGet('admin/structure/views/view/test_attachment_ui/edit/attachment_1'); + $this->assertText(t('Not defined'), 'The right text appears if there is no attachment selection yet.'); + + $attachment_display_url = 'admin/structure/views/nojs/display/test_attachment_ui/attachment_1/displays'; + $this->drupalGet($attachment_display_url); + // Display labels should be escaped. + $this->assertEscaped('Page'); + + foreach (array('default', 'page-1') as $display_id) { + $this->assertNoFieldChecked("edit-displays-$display_id", format_string('Make sure the @display_id can be marked as attached', array('@display_id' => $display_id))); + } + + // Save the attachments and test the value on the view. + $this->drupalPostForm($attachment_display_url, array('displays[page_1]' => 1), t('Apply')); + // Options summary should be escaped. + $this->assertEscaped('Page'); + $this->assertNoRaw('Page'); + $result = $this->xpath('//a[@id = :id]', array(':id' => 'views-attachment-1-displays')); + $this->assertEqual($result[0]->attributes()->title, t('Page')); + $this->drupalPostForm(NULL, array(), t('Save')); + + $view = Views::getView('test_attachment_ui'); + $view->initDisplay(); + $this->assertEqual(array_keys(array_filter($view->displayHandlers->get('attachment_1')->getOption('displays'))), array('page_1'), 'The attached displays got saved as expected'); + + $this->drupalPostForm($attachment_display_url, array('displays[default]' => 1, 'displays[page_1]' => 1), t('Apply')); + $result = $this->xpath('//a[@id = :id]', array(':id' => 'views-attachment-1-displays')); + $this->assertEqual($result[0]->attributes()->title, t('Multiple displays')); + $this->drupalPostForm(NULL, array(), t('Save')); + + $view = Views::getView('test_attachment_ui'); + $view->initDisplay(); + $this->assertEqual(array_keys($view->displayHandlers->get('attachment_1')->getOption('displays')), array('default', 'page_1'), 'The attached displays got saved as expected'); + } + +} diff --git a/core/modules/views_ui/tests/src/Functional/DisplayCRUDTest.php b/core/modules/views_ui/tests/src/Functional/DisplayCRUDTest.php new file mode 100644 index 0000000..95d4492 --- /dev/null +++ b/core/modules/views_ui/tests/src/Functional/DisplayCRUDTest.php @@ -0,0 +1,138 @@ +config('views.settings')->set('ui.show.master_display', TRUE)->save(); + + $settings['page[create]'] = FALSE; + $view = $this->randomView($settings); + + $path_prefix = 'admin/structure/views/view/' . $view['id'] . '/edit'; + $this->drupalGet($path_prefix); + + // Add a new display. + $this->drupalPostForm(NULL, array(), 'Add Page'); + $this->assertLinkByHref($path_prefix . '/page_1', 0, 'Make sure after adding a display the new display appears in the UI'); + + $this->assertNoLink('Master*', 'Make sure the master display is not marked as changed.'); + $this->assertLink('Page*', 0, 'Make sure the added display is marked as changed.'); + + $this->drupalPostForm("admin/structure/views/nojs/display/{$view['id']}/page_1/path", array('path' => 'test/path'), t('Apply')); + $this->drupalPostForm(NULL, array(), t('Save')); + } + + /** + * Tests removing a display. + */ + public function testRemoveDisplay() { + $view = $this->randomView(); + $path_prefix = 'admin/structure/views/view/' . $view['id'] . '/edit'; + + $this->drupalGet($path_prefix . '/default'); + $this->assertNoFieldById('edit-displays-settings-settings-content-tab-content-details-top-actions-delete', 'Delete Page', 'Make sure there is no delete button on the default display.'); + + $this->drupalGet($path_prefix . '/page_1'); + $this->assertFieldById('edit-displays-settings-settings-content-tab-content-details-top-actions-delete', 'Delete Page', 'Make sure there is a delete button on the page display.'); + + // Delete the page, so we can test the undo process. + $this->drupalPostForm($path_prefix . '/page_1', array(), 'Delete Page'); + $this->assertFieldById('edit-displays-settings-settings-content-tab-content-details-top-actions-undo-delete', 'Undo delete of Page', 'Make sure there a undo button on the page display after deleting.'); + $element = $this->xpath('//a[contains(@href, :href) and contains(@class, :class)]', array(':href' => $path_prefix . '/page_1', ':class' => 'views-display-deleted-link')); + $this->assertTrue(!empty($element), 'Make sure the display link is marked as to be deleted.'); + + $element = $this->xpath('//a[contains(@href, :href) and contains(@class, :class)]', array(':href' => $path_prefix . '/page_1', ':class' => 'views-display-deleted-link')); + $this->assertTrue(!empty($element), 'Make sure the display link is marked as to be deleted.'); + + // Undo the deleting of the display. + $this->drupalPostForm($path_prefix . '/page_1', array(), 'Undo delete of Page'); + $this->assertNoFieldById('edit-displays-settings-settings-content-tab-content-details-top-actions-undo-delete', 'Undo delete of Page', 'Make sure there is no undo button on the page display after reverting.'); + $this->assertFieldById('edit-displays-settings-settings-content-tab-content-details-top-actions-delete', 'Delete Page', 'Make sure there is a delete button on the page display after the reverting.'); + + // Now delete again and save the view. + $this->drupalPostForm($path_prefix . '/page_1', array(), 'Delete Page'); + $this->drupalPostForm(NULL, array(), t('Save')); + + $this->assertNoLinkByHref($path_prefix . '/page_1', 'Make sure there is no display tab for the deleted display.'); + } + + /** + * Tests that the correct display is loaded by default. + */ + public function testDefaultDisplay() { + $this->drupalGet('admin/structure/views/view/test_display'); + $elements = $this->xpath('//*[@id="views-page-1-display-title"]'); + $this->assertEqual(count($elements), 1, 'The page display is loaded as the default display.'); + } + + /** + * Tests the duplicating of a display. + */ + public function testDuplicateDisplay() { + $view = $this->randomView(); + $path_prefix = 'admin/structure/views/view/' . $view['id'] . '/edit'; + $path = $view['page[path]']; + + $this->drupalGet($path_prefix); + $this->drupalPostForm(NULL, array(), 'Duplicate Page'); + $this->assertLinkByHref($path_prefix . '/page_2', 0, 'Make sure after duplicating the new display appears in the UI'); + $this->assertUrl($path_prefix . '/page_2', array(), 'The user got redirected to the new display.'); + + // Set the title and override the css classes. + $random_title = $this->randomMachineName(); + $random_css = $this->randomMachineName(); + $this->drupalPostForm("admin/structure/views/nojs/display/{$view['id']}/page_2/title", array('title' => $random_title), t('Apply')); + $this->drupalPostForm("admin/structure/views/nojs/display/{$view['id']}/page_2/css_class", array('override[dropdown]' => 'page_2', 'css_class' => $random_css), t('Apply')); + + // Duplicate as a different display type. + $this->drupalPostForm(NULL, array(), 'Duplicate as Block'); + $this->assertLinkByHref($path_prefix . '/block_1', 0, 'Make sure after duplicating the new display appears in the UI'); + $this->assertUrl($path_prefix . '/block_1', array(), 'The user got redirected to the new display.'); + $this->assertText(t('Block settings')); + $this->assertNoText(t('Page settings')); + + $this->drupalPostForm(NULL, array(), t('Save')); + $view = Views::getView($view['id']); + $view->initDisplay(); + + $page_2 = $view->displayHandlers->get('page_2'); + $this->assertTrue($page_2, 'The new page display got saved.'); + $this->assertEqual($page_2->display['display_title'], 'Page'); + $this->assertEqual($page_2->display['display_options']['path'], $path); + $block_1 = $view->displayHandlers->get('block_1'); + $this->assertTrue($block_1, 'The new block display got saved.'); + $this->assertEqual($block_1->display['display_plugin'], 'block'); + $this->assertEqual($block_1->display['display_title'], 'Block', 'The new display title got generated as expected.'); + $this->assertFalse(isset($block_1->display['display_options']['path'])); + $this->assertEqual($block_1->getOption('title'), $random_title, 'The overridden title option from the display got copied into the duplicate'); + $this->assertEqual($block_1->getOption('css_class'), $random_css, 'The overridden css_class option from the display got copied into the duplicate'); + } + +} diff --git a/core/modules/views_ui/tests/src/Functional/DisplayExtenderUITest.php b/core/modules/views_ui/tests/src/Functional/DisplayExtenderUITest.php new file mode 100644 index 0000000..36137e7 --- /dev/null +++ b/core/modules/views_ui/tests/src/Functional/DisplayExtenderUITest.php @@ -0,0 +1,44 @@ +config('views.settings')->set('display_extenders', array('display_extender_test'))->save(); + + $view = Views::getView('test_view'); + $view_edit_url = "admin/structure/views/view/{$view->storage->id()}/edit"; + $display_option_url = 'admin/structure/views/nojs/display/test_view/default/test_extender_test_option'; + + $this->drupalGet($view_edit_url); + $this->assertLinkByHref($display_option_url, 0, 'Make sure the option defined by the test display extender appears in the UI.'); + + $random_text = $this->randomMachineName(); + $this->drupalPostForm($display_option_url, array('test_extender_test_option' => $random_text), t('Apply')); + $this->assertLink($random_text); + $this->drupalPostForm(NULL, array(), t('Save')); + $view = Views::getView($view->storage->id()); + $view->initDisplay(); + $display_extender_options = $view->display_handler->getOption('display_extenders'); + $this->assertEqual($display_extender_options['display_extender_test']['test_extender_test_option'], $random_text, 'Make sure that the display extender option got saved.'); + } + +} diff --git a/core/modules/views_ui/tests/src/Functional/DisplayFeedTest.php b/core/modules/views_ui/tests/src/Functional/DisplayFeedTest.php new file mode 100644 index 0000000..048a96c --- /dev/null +++ b/core/modules/views_ui/tests/src/Functional/DisplayFeedTest.php @@ -0,0 +1,84 @@ +checkFeedViewUi($view_name); + } + } + + /** + * Checks views UI for a specific feed view. + * + * @param string $view_name + * The view name to check against. + */ + protected function checkFeedViewUi($view_name) { + $this->drupalGet('admin/structure/views'); + // Verify that the page lists the $view_name view. + // Regression test: ViewListBuilder::getDisplayPaths() did not properly + // check whether a DisplayPluginCollection was returned in iterating over + // all displays. + $this->assertText($view_name); + + // Check the attach TO interface. + $this->drupalGet('admin/structure/views/nojs/display/' . $view_name . '/feed_1/displays'); + // Display labels should be escaped. + $this->assertEscaped('Page'); + + // Load all the options of the checkbox. + $result = $this->xpath('//div[@id="edit-displays"]/div'); + $options = array(); + foreach ($result as $item) { + foreach ($item->input->attributes() as $attribute => $value) { + if ($attribute == 'value') { + $options[] = (string) $value; + } + } + } + + $this->assertEqual($options, array('default', 'page'), 'Make sure all displays appears as expected.'); + + // Post and save this and check the output. + $this->drupalPostForm('admin/structure/views/nojs/display/' . $view_name . '/feed_1/displays', array('displays[page]' => 'page'), t('Apply')); + // Options summary should be escaped. + $this->assertEscaped('Page'); + $this->assertNoRaw('Page'); + + $this->drupalGet('admin/structure/views/view/' . $view_name . '/edit/feed_1'); + $this->assertFieldByXpath('//*[@id="views-feed-1-displays"]', 'Page'); + + // Add the default display, so there should now be multiple displays. + $this->drupalPostForm('admin/structure/views/nojs/display/' . $view_name . '/feed_1/displays', array('displays[default]' => 'default'), t('Apply')); + $this->drupalGet('admin/structure/views/view/' . $view_name . '/edit/feed_1'); + $this->assertFieldByXpath('//*[@id="views-feed-1-displays"]', 'Multiple displays'); + } + +} diff --git a/core/modules/views_ui/tests/src/Functional/DisplayPathTest.php b/core/modules/views_ui/tests/src/Functional/DisplayPathTest.php new file mode 100644 index 0000000..5f0555a --- /dev/null +++ b/core/modules/views_ui/tests/src/Functional/DisplayPathTest.php @@ -0,0 +1,248 @@ +drupalPlaceBlock('page_title_block'); + } + + /** + * {@inheritdoc} + */ + public static $modules = array('menu_ui'); + + /** + * Views used by this test. + * + * @var array + */ + public static $testViews = array('test_view', 'test_page_display_menu'); + + /** + * Runs the tests. + */ + public function testPathUI() { + $this->doBasicPathUITest(); + $this->doAdvancedPathsValidationTest(); + $this->doPathXssFilterTest(); + } + + /** + * Tests basic functionality in configuring a view. + */ + protected function doBasicPathUITest() { + $this->drupalGet('admin/structure/views/view/test_view'); + + // Add a new page display and check the appearing text. + $this->drupalPostForm(NULL, array(), 'Add Page'); + $this->assertText(t('No path is set'), 'The right text appears if no path was set.'); + $this->assertNoLink(t('View @display', array('@display' => 'page')), 'No view page link found on the page.'); + + // Save a path and make sure the summary appears as expected. + $random_path = $this->randomMachineName(); + // @todo Once https://www.drupal.org/node/2351379 is resolved, Views will no + // longer use Url::fromUri(), and this path will be able to contain ':'. + $random_path = str_replace(':', '', $random_path); + + $this->drupalPostForm('admin/structure/views/nojs/display/test_view/page_1/path', array('path' => $random_path), t('Apply')); + $this->assertText('/' . $random_path, 'The custom path appears in the summary.'); + $display_link_text = t('View @display', ['@display' => 'Page']); + $this->assertLink($display_link_text, 0, 'view page link found on the page.'); + $this->clickLink($display_link_text); + $this->assertUrl($random_path); + } + + /** + * Tests that View paths are properly filtered for XSS. + */ + public function doPathXssFilterTest() { + $this->drupalGet('admin/structure/views/view/test_view'); + $this->drupalPostForm(NULL, array(), 'Add Page'); + $this->drupalPostForm('admin/structure/views/nojs/display/test_view/page_2/path', array('path' => 'malformed_path'), t('Apply')); + $this->drupalPostForm(NULL, array(), 'Add Page'); + $this->drupalPostForm('admin/structure/views/nojs/display/test_view/page_3/path', array('path' => ''), t('Apply')); + $this->drupalPostForm(NULL, array(), 'Add Page'); + $this->drupalPostForm('admin/structure/views/nojs/display/test_view/page_4/path', array('path' => ''), t('Apply')); + $this->drupalPostForm('admin/structure/views/view/test_view', array(), t('Save')); + $this->drupalGet('admin/structure/views'); + // The anchor text should be escaped. + $this->assertEscaped('/malformed_path'); + $this->assertEscaped('/'); + $this->assertEscaped('/'); + // Links should be url-encoded. + $this->assertRaw('/%3Cobject%3Emalformed_path%3C/object%3E'); + $this->assertRaw('/%3Cscript%3Ealert%28%22hello%22%29%3B%3C/script%3E'); + } + + /** + * Tests a couple of invalid path patterns. + */ + protected function doAdvancedPathsValidationTest() { + $url = 'admin/structure/views/nojs/display/test_view/page_1/path'; + + $this->drupalPostForm($url, array('path' => '%/magrathea'), t('Apply')); + $this->assertUrl($url); + $this->assertText('"%" may not be used for the first segment of a path.'); + + $this->drupalPostForm($url, array('path' => 'user/%1/example'), t('Apply')); + $this->assertUrl($url); + $this->assertText("Numeric placeholders may not be used. Please use plain placeholders (%)."); + } + + /** + * Tests deleting a page display that has no path. + */ + public function testDeleteWithNoPath() { + $this->drupalGet('admin/structure/views/view/test_view'); + $this->drupalPostForm(NULL, array(), t('Add Page')); + $this->drupalPostForm(NULL, array(), t('Delete Page')); + $this->drupalPostForm(NULL, array(), t('Save')); + $this->assertRaw(t('The view %view has been saved.', array('%view' => 'Test view'))); + } + + /** + * Tests the menu and tab option form. + */ + public function testMenuOptions() { + $this->container->get('module_installer')->install(array('menu_ui')); + $this->drupalGet('admin/structure/views/view/test_view'); + + // Add a new page display. + $this->drupalPostForm(NULL, array(), 'Add Page'); + + // Add an invalid path (only fragment). + $this->drupalPostForm('admin/structure/views/nojs/display/test_view/page_1/path', array('path' => '#foo'), t('Apply')); + $this->assertText('Path is empty'); + + // Add an invalid path with a query. + $this->drupalPostForm('admin/structure/views/nojs/display/test_view/page_1/path', array('path' => 'foo?bar'), t('Apply')); + $this->assertText('No query allowed.'); + + // Add an invalid path with just a query. + $this->drupalPostForm('admin/structure/views/nojs/display/test_view/page_1/path', array('path' => '?bar'), t('Apply')); + $this->assertText('Path is empty'); + + // Provide a random, valid path string. + $random_string = $this->randomMachineName(); + + // Save a path. + $this->drupalPostForm('admin/structure/views/nojs/display/test_view/page_1/path', array('path' => $random_string), t('Apply')); + $this->drupalGet('admin/structure/views/view/test_view'); + + $this->drupalPostForm('admin/structure/views/nojs/display/test_view/page_1/menu', array('menu[type]' => 'default tab', 'menu[title]' => 'Test tab title'), t('Apply')); + $this->assertResponse(200); + $this->assertUrl('admin/structure/views/nojs/display/test_view/page_1/tab_options'); + + $this->drupalPostForm(NULL, array('tab_options[type]' => 'tab', 'tab_options[title]' => $this->randomString()), t('Apply')); + $this->assertResponse(200); + $this->assertUrl('admin/structure/views/view/test_view/edit/page_1'); + + $this->drupalGet('admin/structure/views/view/test_view'); + $this->assertLink(t('Tab: @title', array('@title' => 'Test tab title'))); + // If it's a default tab, it should also have an additional settings link. + $this->assertLinkByHref('admin/structure/views/nojs/display/test_view/page_1/tab_options'); + + // Ensure that you can select a parent in case the parent does not exist. + $this->drupalGet('admin/structure/views/nojs/display/test_page_display_menu/page_5/menu'); + $this->assertResponse(200); + $menu_parent = $this->xpath('//select[@id="edit-menu-parent"]'); + $menu_options = (array) $menu_parent[0]->option; + unset($menu_options['@attributes']); + + $this->assertEqual([ + '', + '-- My account', + '-- Log out', + '', + '