';
+ }
+ $this->verbose($verbose_message);
+ }
+
+ /**
+ * Returns the schema definition.
+ */
+ protected function schemaDefinition() {
+ return ViewTestData::schemaDefinition();
+ }
+
+ /**
+ * Returns the views data definition.
+ */
+ protected function viewsData() {
+ return ViewTestData::viewsData();
+ }
+
+ /**
+ * Returns a very simple test dataset.
+ */
+ protected function dataSet() {
+ return ViewTestData::dataSet();
+ }
+
+}
diff --git a/core/modules/views/tests/src/Functional/ViewRenderTest.php b/core/modules/views/tests/src/Functional/ViewRenderTest.php
new file mode 100644
index 0000000..a9b922d
--- /dev/null
+++ b/core/modules/views/tests/src/Functional/ViewRenderTest.php
@@ -0,0 +1,42 @@
+enableViewsTestModule();
+ }
+
+
+ /**
+ * Tests render functionality.
+ */
+ public function testRender() {
+ \Drupal::state()->set('views_render.test', 0);
+
+ // Make sure that the rendering just calls the preprocess function once.
+ $view = Views::getView('test_view_render');
+ $output = $view->preview();
+ $this->container->get('renderer')->renderRoot($output);
+
+ $this->assertEqual(\Drupal::state()->get('views_render.test'), 1);
+ }
+
+}
diff --git a/core/modules/views/tests/src/Functional/ViewResultAssertionTrait.php b/core/modules/views/tests/src/Functional/ViewResultAssertionTrait.php
new file mode 100644
index 0000000..853ab05
--- /dev/null
+++ b/core/modules/views/tests/src/Functional/ViewResultAssertionTrait.php
@@ -0,0 +1,147 @@
+assertIdenticalResultsetHelper($view, $expected_result, $column_map, 'assertIdentical', $message);
+ }
+
+ /**
+ * Verifies that a result set returned by a View differs from certain values.
+ *
+ * Inverse of ViewsTestCase::assertIdenticalResultset().
+ *
+ * @param \Drupal\views\ViewExecutable $view
+ * An executed View.
+ * @param array $expected_result
+ * An expected result set.
+ * @param array $column_map
+ * (optional) An associative array mapping the columns of the result set
+ * from the view (as keys) and the expected result set (as values).
+ * @param string $message
+ * (optional) A custom message to display with the assertion. Defaults to
+ * 'Non-identical result set.'
+ *
+ * @return bool
+ * TRUE if the assertion succeeded, or FALSE otherwise.
+ */
+ protected function assertNotIdenticalResultset($view, $expected_result, $column_map = array(), $message = NULL) {
+ return $this->assertIdenticalResultsetHelper($view, $expected_result, $column_map, 'assertNotIdentical', $message);
+ }
+
+ /**
+ * Performs View result assertions.
+ *
+ * This is a helper method for ViewTestBase::assertIdenticalResultset() and
+ * ViewTestBase::assertNotIdenticalResultset().
+ *
+ * @param \Drupal\views\ViewExecutable $view
+ * An executed View.
+ * @param array $expected_result
+ * An expected result set.
+ * @param array $column_map
+ * An associative array mapping the columns of the result set
+ * from the view (as keys) and the expected result set (as values).
+ * @param string $assert_method
+ * The TestBase assertion method to use (either 'assertIdentical' or
+ * 'assertNotIdentical').
+ * @param string $message
+ * (optional) The message to display with the assertion.
+ *
+ * @return bool
+ * TRUE if the assertion succeeded, or FALSE otherwise.
+ */
+ protected function assertIdenticalResultsetHelper($view, $expected_result, $column_map, $assert_method, $message = NULL) {
+ // Convert $view->result to an array of arrays.
+ $result = array();
+ foreach ($view->result as $key => $value) {
+ $row = array();
+ foreach ($column_map as $view_column => $expected_column) {
+ if (property_exists($value, $view_column)) {
+ $row[$expected_column] = (string) $value->$view_column;
+ }
+ // The comparison will be done on the string representation of the value.
+ // For entity fields we don't have the raw value. Let's try to fetch it
+ // using the entity itself.
+ elseif (empty($value->$view_column) && isset($view->field[$expected_column]) && ($field = $view->field[$expected_column]) && $field instanceof Field) {
+ $column = NULL;
+ if (count(explode(':', $view_column)) == 2) {
+ $column = explode(':', $view_column)[1];
+ }
+ $row[$expected_column] = $field->getValue($value, $column);
+ }
+ }
+ $result[$key] = $row;
+ }
+
+ // Remove the columns we don't need from the expected result.
+ foreach ($expected_result as $key => $value) {
+ $row = array();
+ foreach ($column_map as $expected_column) {
+ // The comparison will be done on the string representation of the value.
+ if (is_object($value)) {
+ $row[$expected_column] = (string) $value->$expected_column;
+ }
+ // This case is about fields with multiple values.
+ elseif (is_array($value[$expected_column])) {
+ foreach (array_keys($value[$expected_column]) as $delta) {
+ $row[$expected_column][$delta] = (string) $value[$expected_column][$delta];
+ }
+ }
+ else {
+ $row[$expected_column] = (string) $value[$expected_column];
+ }
+ }
+ $expected_result[$key] = $row;
+ }
+
+ $this->verbose('
'
+ . "\n\nQuery:\n" . $view->build_info['query']
+ . "\n\nQuery arguments:\n" . var_export($view->build_info['query']->getArguments(), TRUE)
+ . "\n\nActual result:\n" . var_export($result, TRUE)
+ . "\n\nExpected result:\n" . var_export($expected_result, TRUE));
+
+ // Reset the numbering of the arrays.
+ $result = array_values($result);
+ $expected_result = array_values($expected_result);
+
+ // Do the actual comparison.
+ if (!isset($message)) {
+ $not = (strpos($assert_method, 'Not') ? 'not' : '');
+ $message = format_string("Actual result
\n@actual\n
is $not identical to expected
\n@expected\n
", array(
+ '@actual' => var_export($result, TRUE),
+ '@expected' => var_export($expected_result, TRUE),
+ ));
+ }
+ return $this->$assert_method($result, $expected_result, $message);
+ }
+
+}
diff --git a/core/modules/views/tests/src/Functional/ViewTestBase.php b/core/modules/views/tests/src/Functional/ViewTestBase.php
new file mode 100644
index 0000000..e998bae
--- /dev/null
+++ b/core/modules/views/tests/src/Functional/ViewTestBase.php
@@ -0,0 +1,149 @@
+set('views_test_data_schema', $this->schemaDefinition());
+ \Drupal::state()->set('views_test_data_views_data', $this->viewsData());
+
+ \Drupal::service('module_installer')->install(array('views_test_data'));
+ $this->resetAll();
+ $this->rebuildContainer();
+ $this->container->get('module_handler')->reload();
+
+ // Load the test dataset.
+ $data_set = $this->dataSet();
+ $query = db_insert('views_test_data')
+ ->fields(array_keys($data_set[0]));
+ foreach ($data_set as $record) {
+ $query->values($record);
+ }
+ $query->execute();
+ }
+
+ /**
+ * Orders a nested array containing a result set based on a given column.
+ *
+ * @param array $result_set
+ * An array of rows from a result set, with each row as an associative
+ * array keyed by column name.
+ * @param string $column
+ * The column name by which to sort the result set.
+ * @param bool $reverse
+ * (optional) Boolean indicating whether to sort the result set in reverse
+ * order. Defaults to FALSE.
+ *
+ * @return array
+ * The sorted result set.
+ */
+ protected function orderResultSet($result_set, $column, $reverse = FALSE) {
+ $order = $reverse ? -1 : 1;
+ usort($result_set, function ($a, $b) use ($column, $order) {
+ if ($a[$column] == $b[$column]) {
+ return 0;
+ }
+ return $order * (($a[$column] < $b[$column]) ? -1 : 1);
+ });
+ return $result_set;
+ }
+
+ /**
+ * Asserts the existence of a button with a certain ID and label.
+ *
+ * @param string $id
+ * The HTML ID of the button
+ * @param string $label.
+ * The expected label for the button.
+ * @param string $message
+ * (optional) A custom message to display with the assertion. If no custom
+ * message is provided, the message will indicate the button label.
+ *
+ * @return bool
+ * TRUE if the assertion was successful, or FALSE on failure.
+ */
+ protected function helperButtonHasLabel($id, $expected_label, $message = 'Label has the expected value: %label.') {
+ return $this->assertFieldById($id, $expected_label, t($message, array('%label' => $expected_label)));
+ }
+
+ /**
+ * Executes a view with debugging.
+ *
+ * @param \Drupal\views\ViewExecutable $view
+ * The view object.
+ * @param array $args
+ * (optional) An array of the view arguments to use for the view.
+ */
+ protected function executeView(ViewExecutable $view, $args = array()) {
+ // A view does not really work outside of a request scope, due to many
+ // dependencies like the current user.
+ $view->setDisplay();
+ $view->preExecute($args);
+ $view->execute();
+ $verbose_message = '
';
+ }
+ $this->verbose($verbose_message);
+ }
+
+ /**
+ * Returns the schema definition.
+ */
+ protected function schemaDefinition() {
+ return ViewTestData::schemaDefinition();
+ }
+
+ /**
+ * Returns the views data definition.
+ */
+ protected function viewsData() {
+ return ViewTestData::viewsData();
+ }
+
+ /**
+ * Returns a very simple test dataset.
+ */
+ protected function dataSet() {
+ return ViewTestData::dataSet();
+ }
+
+}
diff --git a/core/modules/views/tests/src/Functional/ViewTestData.php b/core/modules/views/tests/src/Functional/ViewTestData.php
new file mode 100644
index 0000000..b1c0f4c
--- /dev/null
+++ b/core/modules/views/tests/src/Functional/ViewTestData.php
@@ -0,0 +1,271 @@
+getStorage('view');
+ $module_handler = \Drupal::moduleHandler();
+ foreach ($modules as $module) {
+ $config_dir = drupal_get_path('module', $module) . '/test_views';
+ if (!is_dir($config_dir) || !$module_handler->moduleExists($module)) {
+ continue;
+ }
+
+ $file_storage = new FileStorage($config_dir);
+ $available_views = $file_storage->listAll('views.view.');
+ foreach ($views as $id) {
+ $config_name = 'views.view.' . $id;
+ if (in_array($config_name, $available_views)) {
+ $storage
+ ->create($file_storage->read($config_name))
+ ->save();
+ }
+ }
+ }
+ }
+
+ // Rebuild the router once.
+ \Drupal::service('router.builder')->rebuild();
+ }
+
+ /**
+ * Returns the schema definition.
+ */
+ public static function schemaDefinition() {
+ $schema['views_test_data'] = array(
+ 'description' => 'Basic test table for Views tests.',
+ 'fields' => array(
+ 'id' => array(
+ 'type' => 'serial',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ ),
+ 'name' => array(
+ 'description' => "A person's name",
+ 'type' => 'varchar_ascii',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => '',
+ ),
+ 'age' => array(
+ 'description' => "The person's age",
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0),
+ 'job' => array(
+ 'description' => "The person's job",
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => TRUE,
+ 'default' => 'Undefined',
+ ),
+ 'created' => array(
+ 'description' => "The creation date of this record",
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ 'status' => array(
+ 'description' => "The status of this record",
+ 'type' => 'int',
+ 'unsigned' => TRUE,
+ 'not null' => TRUE,
+ 'default' => 0,
+ ),
+ ),
+ 'primary key' => array('id'),
+ 'unique keys' => array(
+ 'name' => array('name')
+ ),
+ 'indexes' => array(
+ 'ages' => array('age'),
+ ),
+ );
+ return $schema;
+ }
+
+ /**
+ * Returns the views data definition.
+ */
+ public static function viewsData() {
+ // Declaration of the base table.
+ $data['views_test_data']['table'] = array(
+ 'group' => 'Views test',
+ 'base' => array(
+ 'field' => 'id',
+ 'title' => 'Views test data',
+ 'help' => 'Users who have created accounts on your site.',
+ ),
+ );
+
+ // Declaration of fields.
+ $data['views_test_data']['id'] = array(
+ 'title' => 'ID',
+ 'help' => 'The test data ID',
+ 'field' => array(
+ 'id' => 'numeric',
+ ),
+ 'argument' => array(
+ 'id' => 'numeric',
+ ),
+ 'filter' => array(
+ 'id' => 'numeric',
+ ),
+ 'sort' => array(
+ 'id' => 'standard',
+ ),
+ );
+ $data['views_test_data']['name'] = array(
+ 'title' => 'Name',
+ 'help' => 'The name of the person',
+ 'field' => array(
+ 'id' => 'standard',
+ ),
+ 'argument' => array(
+ 'id' => 'string',
+ ),
+ 'filter' => array(
+ 'id' => 'string',
+ ),
+ 'sort' => array(
+ 'id' => 'standard',
+ ),
+ );
+ $data['views_test_data']['age'] = array(
+ 'title' => 'Age',
+ 'help' => 'The age of the person',
+ 'field' => array(
+ 'id' => 'numeric',
+ ),
+ 'argument' => array(
+ 'id' => 'numeric',
+ ),
+ 'filter' => array(
+ 'id' => 'numeric',
+ ),
+ 'sort' => array(
+ 'id' => 'standard',
+ ),
+ );
+ $data['views_test_data']['job'] = array(
+ 'title' => 'Job',
+ 'help' => 'The job of the person',
+ 'field' => array(
+ 'id' => 'standard',
+ ),
+ 'argument' => array(
+ 'id' => 'string',
+ ),
+ 'filter' => array(
+ 'id' => 'string',
+ ),
+ 'sort' => array(
+ 'id' => 'standard',
+ ),
+ );
+ $data['views_test_data']['created'] = array(
+ 'title' => 'Created',
+ 'help' => 'The creation date of this record',
+ 'field' => array(
+ 'id' => 'date',
+ ),
+ 'argument' => array(
+ 'id' => 'date',
+ ),
+ 'filter' => array(
+ 'id' => 'date',
+ ),
+ 'sort' => array(
+ 'id' => 'date',
+ ),
+ );
+ $data['views_test_data']['status'] = array(
+ 'title' => 'Status',
+ 'help' => 'The status of this record',
+ 'field' => array(
+ 'id' => 'boolean',
+ ),
+ 'filter' => array(
+ 'id' => 'boolean',
+ ),
+ 'sort' => array(
+ 'id' => 'standard',
+ ),
+ );
+ return $data;
+ }
+
+ /**
+ * Returns a very simple test dataset.
+ */
+ public static function dataSet() {
+ return array(
+ array(
+ 'name' => 'John',
+ 'age' => 25,
+ 'job' => 'Singer',
+ 'created' => gmmktime(0, 0, 0, 1, 1, 2000),
+ 'status' => 1,
+ ),
+ array(
+ 'name' => 'George',
+ 'age' => 27,
+ 'job' => 'Singer',
+ 'created' => gmmktime(0, 0, 0, 1, 2, 2000),
+ 'status' => 0,
+ ),
+ array(
+ 'name' => 'Ringo',
+ 'age' => 28,
+ 'job' => 'Drummer',
+ 'created' => gmmktime(6, 30, 30, 1, 1, 2000),
+ 'status' => 1,
+ ),
+ array(
+ 'name' => 'Paul',
+ 'age' => 26,
+ 'job' => 'Songwriter',
+ 'created' => gmmktime(6, 0, 0, 1, 1, 2000),
+ 'status' => 0,
+ ),
+ array(
+ 'name' => 'Meredith',
+ 'age' => 30,
+ 'job' => 'Speaker',
+ 'created' => gmmktime(6, 30, 10, 1, 1, 2000),
+ 'status' => 1,
+ ),
+ );
+ }
+
+}
diff --git a/core/modules/views/tests/src/Functional/ViewsEscapingTest.php b/core/modules/views/tests/src/Functional/ViewsEscapingTest.php
new file mode 100644
index 0000000..886cd48
--- /dev/null
+++ b/core/modules/views/tests/src/Functional/ViewsEscapingTest.php
@@ -0,0 +1,84 @@
+enableViewsTestModule();
+ }
+
+ /**
+ * Tests for incorrectly escaped markup in the views-view-fields.html.twig.
+ */
+ public function testViewsViewFieldsEscaping() {
+ // Test with system theme using theme function.
+ $this->drupalGet('test_page_display_200');
+
+ // Assert that there are no escaped '<'s characters.
+ $this->assertNoEscaped('<');
+
+ // Install theme to test with template system.
+ \Drupal::service('theme_handler')->install(array('views_test_theme'));
+
+ // Make base theme default then test for hook invocations.
+ $this->config('system.theme')
+ ->set('default', 'views_test_theme')
+ ->save();
+ $this->assertEqual($this->config('system.theme')->get('default'), 'views_test_theme');
+
+ $this->drupalGet('test_page_display_200');
+
+ // Assert that we are using the correct template.
+ $this->assertText('force', 'The force is strong with this one');
+
+ // Assert that there are no escaped '<'s characters.
+ $this->assertNoEscaped('<');
+ }
+
+ /**
+ * Tests for incorrectly escaped markup in a header label on a display table.
+ */
+ public function testViewsFieldHeaderEscaping() {
+ // Test with a field header label having an html element wrapper.
+ $this->drupalGet('test_field_header');
+
+ // Assert that there are no escaped '<'s characters.
+ $this->assertNoEscaped('<');
+
+ // Test with a field header label having a XSS test as a wrapper.
+ $this->drupalGet('test_field_header_xss');
+
+ // Assert that XSS test is escaped.
+ $this->assertNoRaw('', 'Harmful tags are escaped in header label.');
+ }
+
+}
diff --git a/core/modules/views/tests/src/Functional/ViewsFormMultipleTest.php b/core/modules/views/tests/src/Functional/ViewsFormMultipleTest.php
new file mode 100644
index 0000000..cc11ca4
--- /dev/null
+++ b/core/modules/views/tests/src/Functional/ViewsFormMultipleTest.php
@@ -0,0 +1,60 @@
+enableViewsTestModule();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function viewsData() {
+ $data = parent::viewsData();
+ $data['views_test_data']['field_form_button_test']['field'] = [
+ 'title' => t('Button test'),
+ 'help' => t('Adds a test form button.'),
+ 'id' => 'field_form_button_test',
+ ];
+ return $data;
+ }
+
+
+ /**
+ * Tests the a page with multiple View forms in it.
+ */
+ public function testViewsFormMultiple() {
+ // Get the test page.
+ $this->drupalGet('views_test_form_multiple');
+
+ $this->assertText('Test base form ID with Views forms and arguments.');
+
+ // Submit the forms, validate argument returned in message set by handler.
+ // @note There is not a way to specify a specific index for a submit button. So
+ // the row index returned is always the last occurrence.
+ $this->drupalPostForm(NULL, [], t('Test Button'), [], [], 'views-form-test-form-multiple-default-arg2');
+ $this->assertText('The test button at row 4 for test_form_multiple (default) View with args: arg2 was submitted.');
+ $this->drupalPostForm(NULL, [], t('Test Button'), [], [], 'views-form-test-form-multiple-default-arg1');
+ $this->assertText('The test button at row 4 for test_form_multiple (default) View with args: arg1 was submitted.');
+ }
+
+}
diff --git a/core/modules/views/tests/src/Functional/ViewsTemplateTest.php b/core/modules/views/tests/src/Functional/ViewsTemplateTest.php
new file mode 100644
index 0000000..7ad0b46
--- /dev/null
+++ b/core/modules/views/tests/src/Functional/ViewsTemplateTest.php
@@ -0,0 +1,46 @@
+enableViewsTestModule();
+ ViewTestData::createTestViews(get_class($this), array('views_test_config'));
+ }
+
+ /**
+ * Tests render functionality.
+ */
+ public function testTemplate() {
+
+ // Make sure that the rendering just calls the preprocess function once.
+ $view = Views::getView('test_view_display_template');
+ $output = $view->preview();
+
+ // Check if we got the rendered output of our template file.
+ $this->assertTrue(strpos(\Drupal::service('renderer')->renderRoot($output), 'This module defines its own display template.') !== FALSE, 'Display plugin DisplayTemplateTest defines its own template.');
+
+ }
+
+}
diff --git a/core/modules/views/tests/src/Functional/ViewsThemeIntegrationTest.php b/core/modules/views/tests/src/Functional/ViewsThemeIntegrationTest.php
new file mode 100644
index 0000000..933e62f
--- /dev/null
+++ b/core/modules/views/tests/src/Functional/ViewsThemeIntegrationTest.php
@@ -0,0 +1,81 @@
+enableViewsTestModule();
+ }
+
+ /**
+ * Tests for exceptions and successful execution of hook_views_pre_render()
+ * and hook_views_post_render() in theme and subtheme.
+ */
+ public function testThemedViewPage() {
+
+ \Drupal::service('theme_handler')->install(array('test_basetheme', 'test_subtheme'));
+
+ // Make base theme default then test for hook invocations.
+ $this->config('system.theme')
+ ->set('default', 'test_basetheme')
+ ->save();
+ $this->assertEqual($this->config('system.theme')->get('default'), 'test_basetheme');
+
+ // Make sure a views rendered page is touched.
+ $this->drupalGet('test_page_display_200');
+
+ $this->assertRaw("test_basetheme_views_pre_render", "Views title changed by test_basetheme.test_basetheme_views_pre_render");
+ $this->assertRaw("test_basetheme_views_post_render", "Views title changed by test_basetheme.test_basetheme_views_post_render");
+
+ // Make sub theme default to test for hook invocation
+ // from both sub and base theme.
+ $this->config('system.theme')
+ ->set('default', 'test_subtheme')
+ ->save();
+ $this->assertEqual($this->config('system.theme')->get('default'), 'test_subtheme');
+
+ // Make sure a views rendered page is touched.
+ $this->drupalGet('test_page_display_200');
+
+ $this->assertRaw("test_subtheme_views_pre_render", "Views title changed by test_subtheme.test_subtheme_views_pre_render");
+ $this->assertRaw("test_subtheme_views_post_render", "Views title changed by test_subtheme.test_subtheme_views_post_render");
+
+ $this->assertRaw("test_basetheme_views_pre_render", "Views title changed by test_basetheme.test_basetheme_views_pre_render");
+ $this->assertRaw("test_basetheme_views_post_render", "Views title changed by test_basetheme.test_basetheme_views_post_render");
+
+ $this->assertRaw('' . count($this->dataSet()) . ' items found.', 'Views group title added by test_subtheme.test_subtheme_views_post_render');
+ }
+
+}
diff --git a/core/modules/views_ui/tests/src/Functional/AnalyzeTest.php b/core/modules/views_ui/tests/src/Functional/AnalyzeTest.php
new file mode 100644
index 0000000..866c9ac
--- /dev/null
+++ b/core/modules/views_ui/tests/src/Functional/AnalyzeTest.php
@@ -0,0 +1,59 @@
+enableViewsTestModule();
+
+ // Add an admin user will full rights;
+ $this->admin = $this->drupalCreateUser(array('administer views'));
+ }
+
+ /**
+ * Tests that analyze works in general.
+ */
+ function testAnalyzeBasic() {
+ $this->drupalLogin($this->admin);
+
+ $this->drupalGet('admin/structure/views/view/test_view/edit');
+ $this->assertLink(t('Analyze view'));
+
+ // This redirects the user to the analyze form.
+ $this->clickLink(t('Analyze view'));
+ $this->assertText(t('View analysis'));
+
+ foreach (array('ok', 'warning', 'error') as $type) {
+ $xpath = $this->xpath('//div[contains(@class, :class)]', array(':class' => $type));
+ $this->assertTrue(count($xpath), format_string('Analyse messages with @type found', array('@type' => $type)));
+ }
+
+ // This redirects the user back to the main views edit page.
+ $this->drupalPostForm(NULL, array(), t('Ok'));
+ }
+
+}
diff --git a/core/modules/views_ui/tests/src/Functional/AreaEntityUITest.php b/core/modules/views_ui/tests/src/Functional/AreaEntityUITest.php
new file mode 100644
index 0000000..d1894d8
--- /dev/null
+++ b/core/modules/views_ui/tests/src/Functional/AreaEntityUITest.php
@@ -0,0 +1,101 @@
+ 'test_id', 'plugin' => 'system_main_block']);
+ $block->save();
+
+ $entity_test = EntityTest::create(['bundle' => 'entity_test']);
+ $entity_test->save();
+
+ $default = $this->randomView([]);
+ $id = $default['id'];
+ $view = View::load($id);
+
+ $this->drupalGet($view->urlInfo('edit-form'));
+
+ // Add a global NULL argument to the view for testing argument placeholders.
+ $this->drupalPostForm("admin/structure/views/nojs/add-handler/$id/page_1/argument", ['name[views.null]' => 1], 'Add and configure contextual filters');
+ $this->drupalPostForm(NULL, [], 'Apply');
+
+ // Configure both the entity_test area header and the block header to
+ // reference the given entities.
+ $this->drupalPostForm("admin/structure/views/nojs/add-handler/$id/page_1/header", ['name[views.entity_block]' => 1], 'Add and configure header');
+ $this->drupalPostForm(NULL, ['options[target]' => $block->id()], 'Apply');
+
+ $this->drupalPostForm("admin/structure/views/nojs/add-handler/$id/page_1/header", ['name[views.entity_entity_test]' => 1], 'Add and configure header');
+ $this->drupalPostForm(NULL, ['options[target]' => $entity_test->id()], 'Apply');
+
+ $this->drupalPostForm(NULL, [], 'Save');
+
+ // Confirm the correct target identifiers were saved for both entities.
+ $view = View::load($id);
+ $header = $view->getDisplay('default')['display_options']['header'];
+ $this->assertEqual(['entity_block', 'entity_entity_test'], array_keys($header));
+
+ $this->assertEqual($block->id(), $header['entity_block']['target']);
+ $this->assertEqual($entity_test->uuid(), $header['entity_entity_test']['target']);
+
+ // Confirm that the correct serial ID (for the entity_test) and config ID
+ // (for the block) are displayed in the form.
+ $this->drupalGet("admin/structure/views/nojs/handler/$id/page_1/header/entity_block");
+ $this->assertFieldByName('options[target]', $block->id());
+
+ $this->drupalGet("admin/structure/views/nojs/handler/$id/page_1/header/entity_entity_test");
+ $this->assertFieldByName('options[target]', $entity_test->id());
+
+ // Replace the header target entities with argument placeholders.
+ $this->drupalPostForm("admin/structure/views/nojs/handler/$id/page_1/header/entity_block", ['options[target]' => '{{ raw_arguments.null }}'], 'Apply');
+ $this->drupalPostForm("admin/structure/views/nojs/handler/$id/page_1/header/entity_entity_test", ['options[target]' => '{{ raw_arguments.null }}'], 'Apply');
+ $this->drupalPostForm(NULL, [], 'Save');
+
+ // Confirm that the argument placeholders are saved.
+ $view = View::load($id);
+ $header = $view->getDisplay('default')['display_options']['header'];
+ $this->assertEqual(['entity_block', 'entity_entity_test'], array_keys($header));
+
+ $this->assertEqual('{{ raw_arguments.null }}', $header['entity_block']['target']);
+ $this->assertEqual('{{ raw_arguments.null }}', $header['entity_entity_test']['target']);
+
+ // Confirm that the argument placeholders are still displayed in the form.
+ $this->drupalGet("admin/structure/views/nojs/handler/$id/page_1/header/entity_block");
+ $this->assertFieldByName('options[target]', '{{ raw_arguments.null }}');
+
+ $this->drupalGet("admin/structure/views/nojs/handler/$id/page_1/header/entity_entity_test");
+ $this->assertFieldByName('options[target]', '{{ raw_arguments.null }}');
+
+ // Change the targets for both headers back to the entities.
+ $this->drupalPostForm("admin/structure/views/nojs/handler/$id/page_1/header/entity_block", ['options[target]' => $block->id()], 'Apply');
+ $this->drupalPostForm("admin/structure/views/nojs/handler/$id/page_1/header/entity_entity_test", ['options[target]' => $entity_test->id()], 'Apply');
+ $this->drupalPostForm(NULL, [], 'Save');
+
+ // Confirm the targets were again saved correctly and not skipped based on
+ // the previous form value.
+ $view = View::load($id);
+ $header = $view->getDisplay('default')['display_options']['header'];
+ $this->assertEqual(['entity_block', 'entity_entity_test'], array_keys($header));
+
+ $this->assertEqual($block->id(), $header['entity_block']['target']);
+ $this->assertEqual($entity_test->uuid(), $header['entity_entity_test']['target']);
+ }
+
+}
diff --git a/core/modules/views_ui/tests/src/Functional/ArgumentValidatorTest.php b/core/modules/views_ui/tests/src/Functional/ArgumentValidatorTest.php
new file mode 100644
index 0000000..c5e8912
--- /dev/null
+++ b/core/modules/views_ui/tests/src/Functional/ArgumentValidatorTest.php
@@ -0,0 +1,57 @@
+saveArgumentHandlerWithValidationOptions(TRUE);
+ $view = Views::getView('test_argument');
+ $handler = $view->getHandler('default', 'argument', 'id');
+ $this->assertTrue($handler['specify_validation'], 'Validation for this argument has been turned on.');
+ $this->assertEqual('entity:node', $handler['validate']['type'], 'Validation for the argument is based on the node.');
+
+ // Uncheck the 'Specify validation criteria' checkbox and expect the
+ // validation type to be reset back to 'none'.
+ $this->saveArgumentHandlerWithValidationOptions(FALSE);
+ $view = Views::getView('test_argument');
+ $handler = $view->getHandler('default', 'argument', 'id');
+ $this->assertFalse($handler['specify_validation'], 'Validation for this argument has been turned off.');
+ $this->assertEqual('none', $handler['validate']['type'], 'Validation for the argument has been reverted to Basic Validation.');
+ }
+
+ /**
+ * Saves the test_argument view with changes made to the argument handler
+ * both with and without specify_validation turned on.
+ *
+ * @param bool $specify_validation
+ */
+ protected function saveArgumentHandlerWithValidationOptions($specify_validation) {
+ $options = array(
+ 'options[validate][type]' => 'entity---node',
+ 'options[specify_validation]' => $specify_validation,
+ );
+ $this->drupalPostForm('admin/structure/views/nojs/handler/test_argument/default/argument/id', $options, t('Apply'));
+ $this->drupalPostForm('admin/structure/views/view/test_argument', array(), t('Save'));
+ }
+
+}
diff --git a/core/modules/views_ui/tests/src/Functional/CachedDataUITest.php b/core/modules/views_ui/tests/src/Functional/CachedDataUITest.php
new file mode 100644
index 0000000..adca41a
--- /dev/null
+++ b/core/modules/views_ui/tests/src/Functional/CachedDataUITest.php
@@ -0,0 +1,74 @@
+fullAdminUser->id();
+
+ $temp_store = $this->container->get('user.shared_tempstore')->get('views');
+ // The view should not be locked.
+ $this->assertEqual($temp_store->getMetadata('test_view'), NULL, 'The view is not locked.');
+
+ $this->drupalGet('admin/structure/views/view/test_view/edit');
+ // Make sure we have 'changes' to the view.
+ $this->drupalPostForm('admin/structure/views/nojs/display/test_view/default/title', array(), t('Apply'));
+ $this->assertText('You have unsaved changes.');
+ $this->assertEqual($temp_store->getMetadata('test_view')->owner, $views_admin_user_uid, 'View cache has been saved.');
+
+ $view_cache = $temp_store->get('test_view');
+ // The view should be enabled.
+ $this->assertTrue($view_cache->status(), 'The view is enabled.');
+ // The view should now be locked.
+ $this->assertEqual($temp_store->getMetadata('test_view')->owner, $views_admin_user_uid, 'The view is locked.');
+
+ // Cancel the view edit and make sure the cache is deleted.
+ $this->drupalPostForm(NULL, array(), t('Cancel'));
+ $this->assertEqual($temp_store->getMetadata('test_view'), NULL, 'User tempstore data has been removed.');
+ // Test we are redirected to the view listing page.
+ $this->assertUrl('admin/structure/views', array(), 'Redirected back to the view listing page.');
+
+ // Log in with another user and make sure the view is locked and break.
+ $this->drupalPostForm('admin/structure/views/nojs/display/test_view/default/title', array(), t('Apply'));
+ $this->drupalLogin($this->adminUser);
+
+ $this->drupalGet('admin/structure/views/view/test_view/edit');
+ // Test that save and cancel buttons are not shown.
+ $this->assertNoFieldById('edit-actions-submit', t('Save'));
+ $this->assertNoFieldById('edit-actions-cancel', t('Cancel'));
+ // Test we have the break lock link.
+ $this->assertLinkByHref('admin/structure/views/view/test_view/break-lock');
+ // Break the lock.
+ $this->clickLink(t('break this lock'));
+ $this->drupalPostForm(NULL, array(), t('Break lock'));
+ // Test that save and cancel buttons are shown.
+ $this->assertFieldById('edit-actions-submit', t('Save'));
+ $this->assertFieldById('edit-actions-cancel', t('Cancel'));
+ // Test we can save the view.
+ $this->drupalPostForm('admin/structure/views/view/test_view/edit', array(), t('Save'));
+ $this->assertRaw(t('The view %view has been saved.', array('%view' => 'Test view')));
+
+ // Test that a deleted view has no tempstore data.
+ $this->drupalPostForm('admin/structure/views/nojs/display/test_view/default/title', array(), t('Apply'));
+ $this->drupalPostForm('admin/structure/views/view/test_view/delete', array(), t('Delete'));
+ // No view tempstore data should be returned for this view after deletion.
+ $this->assertEqual($temp_store->getMetadata('test_view'), NULL, 'View tempstore data has been removed after deletion.');
+ }
+
+}
diff --git a/core/modules/views_ui/tests/src/Functional/CustomBooleanTest.php b/core/modules/views_ui/tests/src/Functional/CustomBooleanTest.php
new file mode 100644
index 0000000..b2b95d3
--- /dev/null
+++ b/core/modules/views_ui/tests/src/Functional/CustomBooleanTest.php
@@ -0,0 +1,181 @@
+setDisplay();
+
+ $view->displayHandlers->get('default')->overrideOption('fields', array(
+ 'age' => array(
+ 'id' => 'age',
+ 'table' => 'views_test_data',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ 'plugin_id' => 'boolean',
+ ),
+ ));
+ $view->save();
+
+ $this->executeView($view);
+
+ $custom_true = 'Yay';
+ $custom_false = 'Nay';
+
+ // Set up some custom value mappings for different types.
+ $custom_values = array(
+ 'plain' => array(
+ 'true' => $custom_true,
+ 'false' => $custom_false,
+ 'test' => 'assertTrue',
+ ),
+ 'allowed tag' => array(
+ 'true' => '
' . $custom_true . '
',
+ 'false' => '
' . $custom_false . '
',
+ 'test' => 'assertTrue',
+ ),
+ 'disallowed tag' => array(
+ 'true' => '',
+ 'false' => '',
+ 'test' => 'assertFalse',
+ ),
+ );
+
+ // Run the same tests on each type.
+ foreach ($custom_values as $type => $values) {
+ $options = array(
+ 'options[type]' => 'custom',
+ 'options[type_custom_true]' => $values['true'],
+ 'options[type_custom_false]' => $values['false'],
+ );
+ $this->drupalPostForm('admin/structure/views/nojs/handler/test_view/default/field/age', $options, 'Apply');
+
+ // Save the view.
+ $this->drupalPostForm('admin/structure/views/view/test_view', array(), 'Save');
+
+ $view = Views::getView('test_view');
+ $output = $view->preview();
+ $output = \Drupal::service('renderer')->renderRoot($output);
+ $this->{$values['test']}(strpos($output, $values['true']), SafeMarkup::format('Expected custom boolean TRUE value %value in output for %type', ['%value' => $values['true'], '%type' => $type]));
+ $this->{$values['test']}(strpos($output, $values['false']), SafeMarkup::format('Expected custom boolean FALSE value %value in output for %type', ['%value' => $values['false'], '%type' => $type]));
+ }
+ }
+
+ /**
+ * Tests the setting and output of custom labels for boolean values.
+ */
+ public function testCustomOptionTemplate() {
+ // Install theme to test with template system.
+ \Drupal::service('theme_handler')->install(['views_test_theme']);
+
+ // Set the default theme for Views preview.
+ $this->config('system.theme')
+ ->set('default', 'views_test_theme')
+ ->save();
+ $this->assertEqual($this->config('system.theme')->get('default'), 'views_test_theme');
+
+ // Add the boolean field handler to the test view.
+ $view = Views::getView('test_view');
+ $view->setDisplay();
+
+ $view->displayHandlers->get('default')->overrideOption('fields', [
+ 'age' => [
+ 'id' => 'age',
+ 'table' => 'views_test_data',
+ 'field' => 'age',
+ 'relationship' => 'none',
+ 'plugin_id' => 'boolean',
+ ],
+ ]);
+ $view->save();
+
+ $this->executeView($view);
+
+ $custom_true = 'Yay';
+ $custom_false = 'Nay';
+
+ // Set up some custom value mappings for different types.
+ $custom_values = array(
+ 'plain' => array(
+ 'true' => $custom_true,
+ 'false' => $custom_false,
+ 'test' => 'assertTrue',
+ ),
+ 'allowed tag' => array(
+ 'true' => '
' . $custom_true . '
',
+ 'false' => '
' . $custom_false . '
',
+ 'test' => 'assertTrue',
+ ),
+ 'disallowed tag' => array(
+ 'true' => '',
+ 'false' => '',
+ 'test' => 'assertFalse',
+ ),
+ );
+
+ // Run the same tests on each type.
+ foreach ($custom_values as $type => $values) {
+ $options = array(
+ 'options[type]' => 'custom',
+ 'options[type_custom_true]' => $values['true'],
+ 'options[type_custom_false]' => $values['false'],
+ );
+ $this->drupalPostForm('admin/structure/views/nojs/handler/test_view/default/field/age', $options, 'Apply');
+
+ // Save the view.
+ $this->drupalPostForm('admin/structure/views/view/test_view', array(), 'Save');
+
+ $view = Views::getView('test_view');
+ $output = $view->preview();
+ $output = \Drupal::service('renderer')->renderRoot($output);
+ $this->{$values['test']}(strpos($output, $values['true']), SafeMarkup::format('Expected custom boolean TRUE value %value in output for %type', ['%value' => $values['true'], '%type' => $type]));
+ $this->{$values['test']}(strpos($output, $values['false']), SafeMarkup::format('Expected custom boolean FALSE value %value in output for %type', ['%value' => $values['false'], '%type' => $type]));
+
+ // Assert that we are using the correct template.
+ $this->setRawContent($output);
+ $this->assertText('llama', 'Loaded the correct views-view-field.html.twig template');
+ }
+ }
+
+}
diff --git a/core/modules/views_ui/tests/src/Functional/DefaultViewsTest.php b/core/modules/views_ui/tests/src/Functional/DefaultViewsTest.php
new file mode 100644
index 0000000..ad3469c
--- /dev/null
+++ b/core/modules/views_ui/tests/src/Functional/DefaultViewsTest.php
@@ -0,0 +1,247 @@
+drupalPlaceBlock('page_title_block');
+ }
+
+ /**
+ * Tests default views.
+ */
+ function testDefaultViews() {
+ // Make sure the view starts off as disabled (does not appear on the listing
+ // page).
+ $edit_href = 'admin/structure/views/view/glossary';
+ $this->drupalGet('admin/structure/views');
+ // @todo Disabled default views do now appear on the front page. Test this
+ // behavior with templates instead.
+ // $this->assertNoLinkByHref($edit_href);
+
+ // Enable the view, and make sure it is now visible on the main listing
+ // page.
+ $this->drupalGet('admin/structure/views');
+ $this->clickViewsOperationLink(t('Enable'), '/glossary/');
+ $this->assertUrl('admin/structure/views');
+ $this->assertLinkByHref($edit_href);
+
+ // It should not be possible to revert the view yet.
+ // @todo Figure out how to handle this with the new configuration system.
+ // $this->assertNoLink(t('Revert'));
+ // $revert_href = 'admin/structure/views/view/glossary/revert';
+ // $this->assertNoLinkByHref($revert_href);
+
+ // Edit the view and change the title. Make sure that the new title is
+ // displayed.
+ $new_title = $this->randomMachineName(16);
+ $edit = array('title' => $new_title);
+ $this->drupalPostForm('admin/structure/views/nojs/display/glossary/page_1/title', $edit, t('Apply'));
+ $this->drupalPostForm('admin/structure/views/view/glossary/edit/page_1', array(), t('Save'));
+ $this->drupalGet('glossary');
+ $this->assertResponse(200);
+ $this->assertText($new_title);
+
+ // Save another view in the UI.
+ $this->drupalPostForm('admin/structure/views/nojs/display/archive/page_1/title', array(), t('Apply'));
+ $this->drupalPostForm('admin/structure/views/view/archive/edit/page_1', array(), t('Save'));
+
+ // Check there is an enable link. i.e. The view has not been enabled after
+ // editing.
+ $this->drupalGet('admin/structure/views');
+ $this->assertLinkByHref('admin/structure/views/view/archive/enable');
+ // Enable it again so it can be tested for access permissions.
+ $this->clickViewsOperationLink(t('Enable'), '/archive/');
+
+ // It should now be possible to revert the view. Do that, and make sure the
+ // view title we added above no longer is displayed.
+ // $this->drupalGet('admin/structure/views');
+ // $this->assertLink(t('Revert'));
+ // $this->assertLinkByHref($revert_href);
+ // $this->drupalPostForm($revert_href, array(), t('Revert'));
+ // $this->drupalGet('glossary');
+ // $this->assertNoText($new_title);
+
+ // Duplicate the view and check that the normal schema of duplicated views is used.
+ $this->drupalGet('admin/structure/views');
+ $this->clickViewsOperationLink(t('Duplicate'), '/glossary');
+ $edit = array(
+ 'id' => 'duplicate_of_glossary',
+ );
+ $this->assertTitle(t('Duplicate of @label | @site-name', array('@label' => 'Glossary', '@site-name' => $this->config('system.site')->get('name'))));
+ $this->drupalPostForm(NULL, $edit, t('Duplicate'));
+ $this->assertUrl('admin/structure/views/view/duplicate_of_glossary', array(), 'The normal duplicating name schema is applied.');
+
+ // Duplicate a view and set a custom name.
+ $this->drupalGet('admin/structure/views');
+ $this->clickViewsOperationLink(t('Duplicate'), '/glossary');
+ $random_name = strtolower($this->randomMachineName());
+ $this->drupalPostForm(NULL, array('id' => $random_name), t('Duplicate'));
+ $this->assertUrl("admin/structure/views/view/$random_name", array(), 'The custom view name got saved.');
+
+ // Now disable the view, and make sure it stops appearing on the main view
+ // listing page but instead goes back to displaying on the disabled views
+ // listing page.
+ // @todo Test this behavior with templates instead.
+ $this->drupalGet('admin/structure/views');
+ $this->clickViewsOperationLink(t('Disable'), '/glossary/');
+ // $this->assertUrl('admin/structure/views');
+ // $this->assertNoLinkByHref($edit_href);
+ // The easiest way to verify it appears on the disabled views listing page
+ // is to try to click the "enable" link from there again.
+ $this->drupalGet('admin/structure/views');
+ $this->clickViewsOperationLink(t('Enable'), '/glossary/');
+ $this->assertUrl('admin/structure/views');
+ $this->assertLinkByHref($edit_href);
+
+ // Clear permissions for anonymous users to check access for default views.
+ Role::load(RoleInterface::ANONYMOUS_ID)->revokePermission('access content')->save();
+
+ // Test the default views disclose no data by default.
+ $this->drupalLogout();
+ $this->drupalGet('glossary');
+ $this->assertResponse(403);
+ $this->drupalGet('archive');
+ $this->assertResponse(403);
+
+ // Test deleting a view.
+ $this->drupalLogin($this->fullAdminUser);
+ $this->drupalGet('admin/structure/views');
+ $this->clickViewsOperationLink(t('Delete'), '/glossary/');
+ // Submit the confirmation form.
+ $this->drupalPostForm(NULL, array(), t('Delete'));
+ // Ensure the view is no longer listed.
+ $this->assertUrl('admin/structure/views');
+ $this->assertNoLinkByHref($edit_href);
+ // Ensure the view is no longer available.
+ $this->drupalGet($edit_href);
+ $this->assertResponse(404);
+ $this->assertText('Page not found');
+
+ // Delete all duplicated Glossary views.
+ $this->drupalGet('admin/structure/views');
+ $this->clickViewsOperationLink(t('Delete'), 'duplicate_of_glossary');
+ // Submit the confirmation form.
+ $this->drupalPostForm(NULL, array(), t('Delete'));
+
+ $this->drupalGet('glossary');
+ $this->assertResponse(200);
+
+ $this->drupalGet('admin/structure/views');
+ $this->clickViewsOperationLink(t('Delete'), $random_name);
+ // Submit the confirmation form.
+ $this->drupalPostForm(NULL, array(), t('Delete'));
+ $this->drupalGet('glossary');
+ $this->assertResponse(404);
+ $this->assertText('Page not found');
+ }
+
+ /**
+ * Tests that enabling views moves them to the correct table.
+ */
+ function testSplitListing() {
+ // Build a re-usable xpath query.
+ $xpath = '//div[@id="views-entity-list"]/div[@class = :status]/table//tr[@title = :title]';
+ $arguments = array(
+ ':status' => 'views-list-section enabled',
+ ':title' => t('Machine name: test_view_status'),
+ );
+
+ $this->drupalGet('admin/structure/views');
+
+ $elements = $this->xpath($xpath, $arguments);
+ $this->assertIdentical(count($elements), 0, 'A disabled view is not found in the enabled views table.');
+
+ $arguments[':status'] = 'views-list-section disabled';
+ $elements = $this->xpath($xpath, $arguments);
+ $this->assertIdentical(count($elements), 1, 'A disabled view is found in the disabled views table.');
+
+ // Enable the view.
+ $this->clickViewsOperationLink(t('Enable'), '/test_view_status/');
+
+ $elements = $this->xpath($xpath, $arguments);
+ $this->assertIdentical(count($elements), 0, 'After enabling a view, it is not found in the disabled views table.');
+
+ $arguments[':status'] = 'views-list-section enabled';
+ $elements = $this->xpath($xpath, $arguments);
+ $this->assertIdentical(count($elements), 1, 'After enabling a view, it is found in the enabled views table.');
+
+ // Attempt to disable the view by path directly, with no token.
+ $this->drupalGet('admin/structure/views/view/test_view_status/disable');
+ $this->assertResponse(403);
+ }
+
+ /**
+ * Tests that page displays show the correct path.
+ */
+ public function testPathDestination() {
+ $this->drupalGet('admin/structure/views');
+
+ // Check that links to views on default tabs are rendered correctly.
+ $this->assertLinkByHref('test_page_display_menu');
+ $this->assertNoLinkByHref('test_page_display_menu/default');
+ $this->assertLinkByHref('test_page_display_menu/local');
+
+ // Check that a dynamic path is shown as text.
+ $this->assertRaw('test_route_with_suffix/%/suffix');
+ $this->assertNoLinkByHref(Url::fromUri('base:test_route_with_suffix/%/suffix')->toString());
+ }
+
+ /**
+ * Click a link to perform an operation on a view.
+ *
+ * In general, we expect lots of links titled "enable" or "disable" on the
+ * various views listing pages, and they might have tokens in them. So we
+ * need special code to find the correct one to click.
+ *
+ * @param $label
+ * Text between the anchor tags of the desired link.
+ * @param $unique_href_part
+ * A unique string that is expected to occur within the href of the desired
+ * link. For example, if the link URL is expected to look like
+ * "admin/structure/views/view/glossary/*", then "/glossary/" could be
+ * passed as the expected unique string.
+ *
+ * @return
+ * The page content that results from clicking on the link, or FALSE on
+ * failure. Failure also results in a failed assertion.
+ */
+ function clickViewsOperationLink($label, $unique_href_part) {
+ $links = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $label));
+ foreach ($links as $link_index => $link) {
+ $position = strpos($link['href'], $unique_href_part);
+ if ($position !== FALSE) {
+ $index = $link_index;
+ break;
+ }
+ }
+ $this->assertTrue(isset($index), format_string('Link to "@label" containing @part found.', array('@label' => $label, '@part' => $unique_href_part)));
+ if (isset($index)) {
+ return $this->clickLink($label, $index);
+ }
+ else {
+ return FALSE;
+ }
+ }
+
+}
diff --git a/core/modules/views_ui/tests/src/Functional/DisplayAttachmentTest.php b/core/modules/views_ui/tests/src/Functional/DisplayAttachmentTest.php
new file mode 100644
index 0000000..37a0b19
--- /dev/null
+++ b/core/modules/views_ui/tests/src/Functional/DisplayAttachmentTest.php
@@ -0,0 +1,61 @@
+drupalGet('admin/structure/views/view/test_attachment_ui/edit/attachment_1');
+ $this->assertText(t('Not defined'), 'The right text appears if there is no attachment selection yet.');
+
+ $attachment_display_url = 'admin/structure/views/nojs/display/test_attachment_ui/attachment_1/displays';
+ $this->drupalGet($attachment_display_url);
+ // Display labels should be escaped.
+ $this->assertEscaped('Page');
+
+ foreach (array('default', 'page-1') as $display_id) {
+ $this->assertNoFieldChecked("edit-displays-$display_id", format_string('Make sure the @display_id can be marked as attached', array('@display_id' => $display_id)));
+ }
+
+ // Save the attachments and test the value on the view.
+ $this->drupalPostForm($attachment_display_url, array('displays[page_1]' => 1), t('Apply'));
+ // Options summary should be escaped.
+ $this->assertEscaped('Page');
+ $this->assertNoRaw('Page');
+ $result = $this->xpath('//a[@id = :id]', array(':id' => 'views-attachment-1-displays'));
+ $this->assertEqual($result[0]->attributes()->title, t('Page'));
+ $this->drupalPostForm(NULL, array(), t('Save'));
+
+ $view = Views::getView('test_attachment_ui');
+ $view->initDisplay();
+ $this->assertEqual(array_keys(array_filter($view->displayHandlers->get('attachment_1')->getOption('displays'))), array('page_1'), 'The attached displays got saved as expected');
+
+ $this->drupalPostForm($attachment_display_url, array('displays[default]' => 1, 'displays[page_1]' => 1), t('Apply'));
+ $result = $this->xpath('//a[@id = :id]', array(':id' => 'views-attachment-1-displays'));
+ $this->assertEqual($result[0]->attributes()->title, t('Multiple displays'));
+ $this->drupalPostForm(NULL, array(), t('Save'));
+
+ $view = Views::getView('test_attachment_ui');
+ $view->initDisplay();
+ $this->assertEqual(array_keys($view->displayHandlers->get('attachment_1')->getOption('displays')), array('default', 'page_1'), 'The attached displays got saved as expected');
+ }
+
+}
diff --git a/core/modules/views_ui/tests/src/Functional/DisplayCRUDTest.php b/core/modules/views_ui/tests/src/Functional/DisplayCRUDTest.php
new file mode 100644
index 0000000..95d4492
--- /dev/null
+++ b/core/modules/views_ui/tests/src/Functional/DisplayCRUDTest.php
@@ -0,0 +1,138 @@
+config('views.settings')->set('ui.show.master_display', TRUE)->save();
+
+ $settings['page[create]'] = FALSE;
+ $view = $this->randomView($settings);
+
+ $path_prefix = 'admin/structure/views/view/' . $view['id'] . '/edit';
+ $this->drupalGet($path_prefix);
+
+ // Add a new display.
+ $this->drupalPostForm(NULL, array(), 'Add Page');
+ $this->assertLinkByHref($path_prefix . '/page_1', 0, 'Make sure after adding a display the new display appears in the UI');
+
+ $this->assertNoLink('Master*', 'Make sure the master display is not marked as changed.');
+ $this->assertLink('Page*', 0, 'Make sure the added display is marked as changed.');
+
+ $this->drupalPostForm("admin/structure/views/nojs/display/{$view['id']}/page_1/path", array('path' => 'test/path'), t('Apply'));
+ $this->drupalPostForm(NULL, array(), t('Save'));
+ }
+
+ /**
+ * Tests removing a display.
+ */
+ public function testRemoveDisplay() {
+ $view = $this->randomView();
+ $path_prefix = 'admin/structure/views/view/' . $view['id'] . '/edit';
+
+ $this->drupalGet($path_prefix . '/default');
+ $this->assertNoFieldById('edit-displays-settings-settings-content-tab-content-details-top-actions-delete', 'Delete Page', 'Make sure there is no delete button on the default display.');
+
+ $this->drupalGet($path_prefix . '/page_1');
+ $this->assertFieldById('edit-displays-settings-settings-content-tab-content-details-top-actions-delete', 'Delete Page', 'Make sure there is a delete button on the page display.');
+
+ // Delete the page, so we can test the undo process.
+ $this->drupalPostForm($path_prefix . '/page_1', array(), 'Delete Page');
+ $this->assertFieldById('edit-displays-settings-settings-content-tab-content-details-top-actions-undo-delete', 'Undo delete of Page', 'Make sure there a undo button on the page display after deleting.');
+ $element = $this->xpath('//a[contains(@href, :href) and contains(@class, :class)]', array(':href' => $path_prefix . '/page_1', ':class' => 'views-display-deleted-link'));
+ $this->assertTrue(!empty($element), 'Make sure the display link is marked as to be deleted.');
+
+ $element = $this->xpath('//a[contains(@href, :href) and contains(@class, :class)]', array(':href' => $path_prefix . '/page_1', ':class' => 'views-display-deleted-link'));
+ $this->assertTrue(!empty($element), 'Make sure the display link is marked as to be deleted.');
+
+ // Undo the deleting of the display.
+ $this->drupalPostForm($path_prefix . '/page_1', array(), 'Undo delete of Page');
+ $this->assertNoFieldById('edit-displays-settings-settings-content-tab-content-details-top-actions-undo-delete', 'Undo delete of Page', 'Make sure there is no undo button on the page display after reverting.');
+ $this->assertFieldById('edit-displays-settings-settings-content-tab-content-details-top-actions-delete', 'Delete Page', 'Make sure there is a delete button on the page display after the reverting.');
+
+ // Now delete again and save the view.
+ $this->drupalPostForm($path_prefix . '/page_1', array(), 'Delete Page');
+ $this->drupalPostForm(NULL, array(), t('Save'));
+
+ $this->assertNoLinkByHref($path_prefix . '/page_1', 'Make sure there is no display tab for the deleted display.');
+ }
+
+ /**
+ * Tests that the correct display is loaded by default.
+ */
+ public function testDefaultDisplay() {
+ $this->drupalGet('admin/structure/views/view/test_display');
+ $elements = $this->xpath('//*[@id="views-page-1-display-title"]');
+ $this->assertEqual(count($elements), 1, 'The page display is loaded as the default display.');
+ }
+
+ /**
+ * Tests the duplicating of a display.
+ */
+ public function testDuplicateDisplay() {
+ $view = $this->randomView();
+ $path_prefix = 'admin/structure/views/view/' . $view['id'] . '/edit';
+ $path = $view['page[path]'];
+
+ $this->drupalGet($path_prefix);
+ $this->drupalPostForm(NULL, array(), 'Duplicate Page');
+ $this->assertLinkByHref($path_prefix . '/page_2', 0, 'Make sure after duplicating the new display appears in the UI');
+ $this->assertUrl($path_prefix . '/page_2', array(), 'The user got redirected to the new display.');
+
+ // Set the title and override the css classes.
+ $random_title = $this->randomMachineName();
+ $random_css = $this->randomMachineName();
+ $this->drupalPostForm("admin/structure/views/nojs/display/{$view['id']}/page_2/title", array('title' => $random_title), t('Apply'));
+ $this->drupalPostForm("admin/structure/views/nojs/display/{$view['id']}/page_2/css_class", array('override[dropdown]' => 'page_2', 'css_class' => $random_css), t('Apply'));
+
+ // Duplicate as a different display type.
+ $this->drupalPostForm(NULL, array(), 'Duplicate as Block');
+ $this->assertLinkByHref($path_prefix . '/block_1', 0, 'Make sure after duplicating the new display appears in the UI');
+ $this->assertUrl($path_prefix . '/block_1', array(), 'The user got redirected to the new display.');
+ $this->assertText(t('Block settings'));
+ $this->assertNoText(t('Page settings'));
+
+ $this->drupalPostForm(NULL, array(), t('Save'));
+ $view = Views::getView($view['id']);
+ $view->initDisplay();
+
+ $page_2 = $view->displayHandlers->get('page_2');
+ $this->assertTrue($page_2, 'The new page display got saved.');
+ $this->assertEqual($page_2->display['display_title'], 'Page');
+ $this->assertEqual($page_2->display['display_options']['path'], $path);
+ $block_1 = $view->displayHandlers->get('block_1');
+ $this->assertTrue($block_1, 'The new block display got saved.');
+ $this->assertEqual($block_1->display['display_plugin'], 'block');
+ $this->assertEqual($block_1->display['display_title'], 'Block', 'The new display title got generated as expected.');
+ $this->assertFalse(isset($block_1->display['display_options']['path']));
+ $this->assertEqual($block_1->getOption('title'), $random_title, 'The overridden title option from the display got copied into the duplicate');
+ $this->assertEqual($block_1->getOption('css_class'), $random_css, 'The overridden css_class option from the display got copied into the duplicate');
+ }
+
+}
diff --git a/core/modules/views_ui/tests/src/Functional/DisplayExtenderUITest.php b/core/modules/views_ui/tests/src/Functional/DisplayExtenderUITest.php
new file mode 100644
index 0000000..36137e7
--- /dev/null
+++ b/core/modules/views_ui/tests/src/Functional/DisplayExtenderUITest.php
@@ -0,0 +1,44 @@
+config('views.settings')->set('display_extenders', array('display_extender_test'))->save();
+
+ $view = Views::getView('test_view');
+ $view_edit_url = "admin/structure/views/view/{$view->storage->id()}/edit";
+ $display_option_url = 'admin/structure/views/nojs/display/test_view/default/test_extender_test_option';
+
+ $this->drupalGet($view_edit_url);
+ $this->assertLinkByHref($display_option_url, 0, 'Make sure the option defined by the test display extender appears in the UI.');
+
+ $random_text = $this->randomMachineName();
+ $this->drupalPostForm($display_option_url, array('test_extender_test_option' => $random_text), t('Apply'));
+ $this->assertLink($random_text);
+ $this->drupalPostForm(NULL, array(), t('Save'));
+ $view = Views::getView($view->storage->id());
+ $view->initDisplay();
+ $display_extender_options = $view->display_handler->getOption('display_extenders');
+ $this->assertEqual($display_extender_options['display_extender_test']['test_extender_test_option'], $random_text, 'Make sure that the display extender option got saved.');
+ }
+
+}
diff --git a/core/modules/views_ui/tests/src/Functional/DisplayFeedTest.php b/core/modules/views_ui/tests/src/Functional/DisplayFeedTest.php
new file mode 100644
index 0000000..048a96c
--- /dev/null
+++ b/core/modules/views_ui/tests/src/Functional/DisplayFeedTest.php
@@ -0,0 +1,84 @@
+checkFeedViewUi($view_name);
+ }
+ }
+
+ /**
+ * Checks views UI for a specific feed view.
+ *
+ * @param string $view_name
+ * The view name to check against.
+ */
+ protected function checkFeedViewUi($view_name) {
+ $this->drupalGet('admin/structure/views');
+ // Verify that the page lists the $view_name view.
+ // Regression test: ViewListBuilder::getDisplayPaths() did not properly
+ // check whether a DisplayPluginCollection was returned in iterating over
+ // all displays.
+ $this->assertText($view_name);
+
+ // Check the attach TO interface.
+ $this->drupalGet('admin/structure/views/nojs/display/' . $view_name . '/feed_1/displays');
+ // Display labels should be escaped.
+ $this->assertEscaped('Page');
+
+ // Load all the options of the checkbox.
+ $result = $this->xpath('//div[@id="edit-displays"]/div');
+ $options = array();
+ foreach ($result as $item) {
+ foreach ($item->input->attributes() as $attribute => $value) {
+ if ($attribute == 'value') {
+ $options[] = (string) $value;
+ }
+ }
+ }
+
+ $this->assertEqual($options, array('default', 'page'), 'Make sure all displays appears as expected.');
+
+ // Post and save this and check the output.
+ $this->drupalPostForm('admin/structure/views/nojs/display/' . $view_name . '/feed_1/displays', array('displays[page]' => 'page'), t('Apply'));
+ // Options summary should be escaped.
+ $this->assertEscaped('Page');
+ $this->assertNoRaw('Page');
+
+ $this->drupalGet('admin/structure/views/view/' . $view_name . '/edit/feed_1');
+ $this->assertFieldByXpath('//*[@id="views-feed-1-displays"]', 'Page');
+
+ // Add the default display, so there should now be multiple displays.
+ $this->drupalPostForm('admin/structure/views/nojs/display/' . $view_name . '/feed_1/displays', array('displays[default]' => 'default'), t('Apply'));
+ $this->drupalGet('admin/structure/views/view/' . $view_name . '/edit/feed_1');
+ $this->assertFieldByXpath('//*[@id="views-feed-1-displays"]', 'Multiple displays');
+ }
+
+}
diff --git a/core/modules/views_ui/tests/src/Functional/DisplayPathTest.php b/core/modules/views_ui/tests/src/Functional/DisplayPathTest.php
new file mode 100644
index 0000000..5f0555a
--- /dev/null
+++ b/core/modules/views_ui/tests/src/Functional/DisplayPathTest.php
@@ -0,0 +1,248 @@
+drupalPlaceBlock('page_title_block');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static $modules = array('menu_ui');
+
+ /**
+ * Views used by this test.
+ *
+ * @var array
+ */
+ public static $testViews = array('test_view', 'test_page_display_menu');
+
+ /**
+ * Runs the tests.
+ */
+ public function testPathUI() {
+ $this->doBasicPathUITest();
+ $this->doAdvancedPathsValidationTest();
+ $this->doPathXssFilterTest();
+ }
+
+ /**
+ * Tests basic functionality in configuring a view.
+ */
+ protected function doBasicPathUITest() {
+ $this->drupalGet('admin/structure/views/view/test_view');
+
+ // Add a new page display and check the appearing text.
+ $this->drupalPostForm(NULL, array(), 'Add Page');
+ $this->assertText(t('No path is set'), 'The right text appears if no path was set.');
+ $this->assertNoLink(t('View @display', array('@display' => 'page')), 'No view page link found on the page.');
+
+ // Save a path and make sure the summary appears as expected.
+ $random_path = $this->randomMachineName();
+ // @todo Once https://www.drupal.org/node/2351379 is resolved, Views will no
+ // longer use Url::fromUri(), and this path will be able to contain ':'.
+ $random_path = str_replace(':', '', $random_path);
+
+ $this->drupalPostForm('admin/structure/views/nojs/display/test_view/page_1/path', array('path' => $random_path), t('Apply'));
+ $this->assertText('/' . $random_path, 'The custom path appears in the summary.');
+ $display_link_text = t('View @display', ['@display' => 'Page']);
+ $this->assertLink($display_link_text, 0, 'view page link found on the page.');
+ $this->clickLink($display_link_text);
+ $this->assertUrl($random_path);
+ }
+
+ /**
+ * Tests that View paths are properly filtered for XSS.
+ */
+ public function doPathXssFilterTest() {
+ $this->drupalGet('admin/structure/views/view/test_view');
+ $this->drupalPostForm(NULL, array(), 'Add Page');
+ $this->drupalPostForm('admin/structure/views/nojs/display/test_view/page_2/path', array('path' => ''), 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('/');
+ $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',
+ '',
+ '