diff --git a/core/modules/aggregator/tests/src/Functional/AddFeedTest.php b/core/modules/aggregator/src/Tests/AddFeedTest.php similarity index 98% rename from core/modules/aggregator/tests/src/Functional/AddFeedTest.php rename to core/modules/aggregator/src/Tests/AddFeedTest.php index 90fff77..b7ffe5f 100644 --- a/core/modules/aggregator/tests/src/Functional/AddFeedTest.php +++ b/core/modules/aggregator/src/Tests/AddFeedTest.php @@ -1,6 +1,6 @@ getRawContent(). + */ + protected function basicAuthGet($path, $username, $password, array $options = []) { + return $this->drupalGet($path, $options, $this->getBasicAuthHeaders($username, $password)); + } + + /** + * Executes a form submission using basic authentication. + * + * @param string $path + * Location of the post form. + * @param array $edit + * Field data in an associative array. + * @param string $submit + * Value of the submit button whose click is to be emulated. + * @param string $username + * The username to use for basic authentication. + * @param string $password + * The password to use for basic authentication. + * @param array $options + * Options to be forwarded to the url generator. + * @param string $form_html_id + * (optional) HTML ID of the form to be submitted. + * @param string $extra_post + * (optional) A string of additional data to append to the POST submission. + * + * @return string + * The retrieved HTML string. + * + * @see \Drupal\simpletest\WebTestBase::drupalPostForm() + */ + protected function basicAuthPostForm($path, $edit, $submit, $username, $password, array $options = array(), $form_html_id = NULL, $extra_post = NULL) { + return $this->drupalPostForm($path, $edit, $submit, $options, $this->getBasicAuthHeaders($username, $password), $form_html_id, $extra_post); + } + + /** + * Returns HTTP headers that can be used for basic authentication in Curl. + * + * @param string $username + * The username to use for basic authentication. + * @param string $password + * The password to use for basic authentication. + * + * @return array + * An array of raw request headers as used by curl_setopt(). + */ + protected function getBasicAuthHeaders($username, $password) { + // Set up Curl to use basic authentication with the test user's credentials. + return ['Authorization: Basic ' . base64_encode("$username:$password")]; + } + +} diff --git a/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php b/core/modules/big_pipe/src/Tests/BigPipeTest.php similarity index 99% rename from core/modules/big_pipe/tests/src/Functional/BigPipeTest.php rename to core/modules/big_pipe/src/Tests/BigPipeTest.php index 1708f63..a3f773e 100644 --- a/core/modules/big_pipe/tests/src/Functional/BigPipeTest.php +++ b/core/modules/big_pipe/src/Tests/BigPipeTest.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/src/Tests/DefaultViewsTest.php b/core/modules/views/src/Tests/DefaultViewsTest.php new file mode 100644 index 0000000..5c1f601 --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/FieldApiDataTest.php b/core/modules/views/src/Tests/FieldApiDataTest.php new file mode 100644 index 0000000..086f179 --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/GlossaryTest.php b/core/modules/views/src/Tests/GlossaryTest.php new file mode 100644 index 0000000..508a4eb --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/RenderCacheWebTest.php b/core/modules/views/src/Tests/RenderCacheWebTest.php new file mode 100644 index 0000000..fcd38b7 --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/SearchIntegrationTest.php b/core/modules/views/src/Tests/SearchIntegrationTest.php new file mode 100644 index 0000000..e71e159 --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/SearchMultilingualTest.php b/core/modules/views/src/Tests/SearchMultilingualTest.php new file mode 100644 index 0000000..5f583f0 --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/TestHelperPlugin.php b/core/modules/views/src/Tests/TestHelperPlugin.php new file mode 100644 index 0000000..8656063 --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/ViewAjaxTest.php b/core/modules/views/src/Tests/ViewAjaxTest.php new file mode 100644 index 0000000..c1705a5 --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/ViewElementTest.php b/core/modules/views/src/Tests/ViewElementTest.php new file mode 100644 index 0000000..2ec2e67 --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/ViewKernelTestBase.php b/core/modules/views/src/Tests/ViewKernelTestBase.php new file mode 100644 index 0000000..3a5df34 --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/ViewRenderTest.php b/core/modules/views/src/Tests/ViewRenderTest.php new file mode 100644 index 0000000..4bde8b2 --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/ViewResultAssertionTrait.php b/core/modules/views/src/Tests/ViewResultAssertionTrait.php new file mode 100644 index 0000000..62ae67c --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/ViewTestBase.php b/core/modules/views/src/Tests/ViewTestBase.php new file mode 100644 index 0000000..6ce3494 --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/ViewTestData.php b/core/modules/views/src/Tests/ViewTestData.php new file mode 100644 index 0000000..d54902d --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/ViewsEscapingTest.php b/core/modules/views/src/Tests/ViewsEscapingTest.php new file mode 100644 index 0000000..34f5148 --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/ViewsFormMultipleTest.php b/core/modules/views/src/Tests/ViewsFormMultipleTest.php new file mode 100644 index 0000000..844a1b1 --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/ViewsTemplateTest.php b/core/modules/views/src/Tests/ViewsTemplateTest.php new file mode 100644 index 0000000..2d7b206 --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/ViewsThemeIntegrationTest.php b/core/modules/views/src/Tests/ViewsThemeIntegrationTest.php new file mode 100644 index 0000000..e9beb38 --- /dev/null +++ b/core/modules/views/src/Tests/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/src/Tests/AnalyzeTest.php b/core/modules/views_ui/src/Tests/AnalyzeTest.php new file mode 100644 index 0000000..6cc9203 --- /dev/null +++ b/core/modules/views_ui/src/Tests/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/src/Tests/AreaEntityUITest.php b/core/modules/views_ui/src/Tests/AreaEntityUITest.php new file mode 100644 index 0000000..ffe895a --- /dev/null +++ b/core/modules/views_ui/src/Tests/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/src/Tests/ArgumentValidatorTest.php b/core/modules/views_ui/src/Tests/ArgumentValidatorTest.php new file mode 100644 index 0000000..b62f876 --- /dev/null +++ b/core/modules/views_ui/src/Tests/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/src/Tests/CachedDataUITest.php b/core/modules/views_ui/src/Tests/CachedDataUITest.php new file mode 100644 index 0000000..1f69b19 --- /dev/null +++ b/core/modules/views_ui/src/Tests/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/src/Tests/CustomBooleanTest.php b/core/modules/views_ui/src/Tests/CustomBooleanTest.php new file mode 100644 index 0000000..828b5b9 --- /dev/null +++ b/core/modules/views_ui/src/Tests/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/src/Tests/DefaultViewsTest.php b/core/modules/views_ui/src/Tests/DefaultViewsTest.php new file mode 100644 index 0000000..3b3f2a7 --- /dev/null +++ b/core/modules/views_ui/src/Tests/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/src/Tests/DisplayAttachmentTest.php b/core/modules/views_ui/src/Tests/DisplayAttachmentTest.php new file mode 100644 index 0000000..fbacb14 --- /dev/null +++ b/core/modules/views_ui/src/Tests/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/src/Tests/DisplayCRUDTest.php b/core/modules/views_ui/src/Tests/DisplayCRUDTest.php new file mode 100644 index 0000000..20f57d9 --- /dev/null +++ b/core/modules/views_ui/src/Tests/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/src/Tests/DisplayExtenderUITest.php b/core/modules/views_ui/src/Tests/DisplayExtenderUITest.php new file mode 100644 index 0000000..7e26dff --- /dev/null +++ b/core/modules/views_ui/src/Tests/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/src/Tests/DisplayFeedTest.php b/core/modules/views_ui/src/Tests/DisplayFeedTest.php new file mode 100644 index 0000000..4e7bf8a --- /dev/null +++ b/core/modules/views_ui/src/Tests/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/src/Tests/DisplayPathTest.php b/core/modules/views_ui/src/Tests/DisplayPathTest.php new file mode 100644 index 0000000..8baa506 --- /dev/null +++ b/core/modules/views_ui/src/Tests/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', + '', + '