first commit

This commit is contained in:
2024-07-15 12:33:27 +02:00
commit ce50ae282b
22084 changed files with 2623791 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
/**
* @file
* Just a placeholder file for the test.
*/

View File

@@ -0,0 +1,12 @@
name: 'Views UI Test'
type: module
description: 'Test module for Views UI.'
package: Testing
# version: VERSION
dependencies:
- drupal:views_ui
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,4 @@
views_ui_test.test:
css:
component:
css/views_ui_test.test.css: {}

View File

@@ -0,0 +1,17 @@
<?php
/**
* @file
* Helper module for Views UI tests.
*/
/**
* Implements hook_views_preview_info_alter().
*
* Add a row count row to the live preview area.
*/
function views_ui_test_views_preview_info_alter(&$rows, $view) {
$data = ['#markup' => t('Test row count')];
$data['#attached']['library'][] = 'views_ui_test/views_ui_test.test';
$rows['query'][] = [['data' => $data], count($view->result)];
}

View File

@@ -0,0 +1,12 @@
name: 'Views test field'
type: module
description: 'Add custom global field for testing purposes.'
package: Testing
# version: VERSION
dependencies:
- drupal:views_ui
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,18 @@
<?php
/**
* @file
* ViewsUI Test field module.
*/
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_form_FORM_ID_alter() for views_ui_add_handler_form.
*
* Changes the label for one of the tests fields to validate this label is not
* searched on.
*/
function views_ui_test_field_form_views_ui_add_handler_form_alter(&$form, FormStateInterface $form_state) {
$form['options']['name']['#options']['views.views_test_field_1']['title']['data']['#title'] .= ' FIELD_1_LABEL';
}

View File

@@ -0,0 +1,29 @@
<?php
/**
* @file
* Provide views data for testing purposes.
*/
/**
* Implements hook_views_data().
*/
function views_ui_test_field_views_data() {
$data['views']['views_test_field_1'] = [
'title' => t('Views test field 1 - FIELD_1_TITLE'),
'help' => t('Field 1 for testing purposes - FIELD_1_DESCRIPTION'),
'field' => [
'id' => 'views_test_field_1',
],
];
$data['views']['views_test_field_2'] = [
'title' => t('Views test field 2 - FIELD_2_TITLE'),
'help' => t('Field 2 for testing purposes - FIELD_2_DESCRIPTION'),
'field' => [
'id' => 'views_test_field_2',
],
];
return $data;
}

View File

@@ -0,0 +1,55 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
/**
* Tests the views analyze system.
*
* @group views_ui
*/
class AnalyzeTest extends UITestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['views_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view'];
/**
* Tests that analyze works in general.
*/
public function testAnalyzeBasic(): void {
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/structure/views/view/test_view/edit');
$this->assertSession()->linkExists('Analyze view');
// This redirects the user to the analyze form.
$this->clickLink('Analyze view');
$this->assertSession()->titleEquals('View analysis | Drupal');
foreach (['ok', 'warning', 'error'] as $type) {
// Check that analyze messages with the expected type found.
$this->assertSession()->elementExists('css', 'div.' . $type);
}
// This redirects the user back to the main views edit page.
$this->submitForm([], 'Ok');
}
}

View File

@@ -0,0 +1,115 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\block\Entity\Block;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\views\Entity\View;
/**
* Tests the entity area UI test.
*
* @see \Drupal\views\Plugin\views\area\Entity
* @group views_ui
*/
class AreaEntityUITest extends UITestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['entity_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
public function testUI(): void {
// Set up a block and an entity_test entity.
$block = Block::create(['id' => 'test_id', 'plugin' => 'system_main_block', 'theme' => 'stark']);
$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->toUrl('edit-form'));
// Add a global NULL argument to the view for testing argument placeholders.
$this->drupalGet("admin/structure/views/nojs/add-handler/{$id}/page_1/argument");
$this->submitForm(['name[views.null]' => TRUE], 'Add and configure contextual filters');
$this->submitForm([], 'Apply');
// Configure both the entity_test area header and the block header to
// reference the given entities.
$this->drupalGet("admin/structure/views/nojs/add-handler/{$id}/page_1/header");
$this->submitForm(['name[views.entity_block]' => TRUE], 'Add and configure header');
$this->submitForm(['options[target]' => $block->id()], 'Apply');
$this->drupalGet("admin/structure/views/nojs/add-handler/{$id}/page_1/header");
$this->submitForm(['name[views.entity_entity_test]' => TRUE], 'Add and configure header');
$this->submitForm(['options[target]' => $entity_test->id()], 'Apply');
$this->submitForm([], 'Save');
// Confirm the correct target identifiers were saved for both entities.
$view = View::load($id);
$header = $view->getDisplay('default')['display_options']['header'];
$this->assertEquals(['entity_block', 'entity_entity_test'], array_keys($header));
$this->assertEquals($block->id(), $header['entity_block']['target']);
$this->assertEquals($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->assertSession()->fieldValueEquals('options[target]', $block->id());
$this->drupalGet("admin/structure/views/nojs/handler/$id/page_1/header/entity_entity_test");
$this->assertSession()->fieldValueEquals('options[target]', $entity_test->id());
// Replace the header target entities with argument placeholders.
$this->drupalGet("admin/structure/views/nojs/handler/{$id}/page_1/header/entity_block");
$this->submitForm(['options[target]' => '{{ raw_arguments.null }}'], 'Apply');
$this->drupalGet("admin/structure/views/nojs/handler/{$id}/page_1/header/entity_entity_test");
$this->submitForm(['options[target]' => '{{ raw_arguments.null }}'], 'Apply');
$this->submitForm([], 'Save');
// Confirm that the argument placeholders are saved.
$view = View::load($id);
$header = $view->getDisplay('default')['display_options']['header'];
$this->assertEquals(['entity_block', 'entity_entity_test'], array_keys($header));
$this->assertEquals('{{ raw_arguments.null }}', $header['entity_block']['target']);
$this->assertEquals('{{ 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->assertSession()->fieldValueEquals('options[target]', '{{ raw_arguments.null }}');
$this->drupalGet("admin/structure/views/nojs/handler/$id/page_1/header/entity_entity_test");
$this->assertSession()->fieldValueEquals('options[target]', '{{ raw_arguments.null }}');
// Change the targets for both headers back to the entities.
$this->drupalGet("admin/structure/views/nojs/handler/{$id}/page_1/header/entity_block");
$this->submitForm(['options[target]' => $block->id()], 'Apply');
$this->drupalGet("admin/structure/views/nojs/handler/{$id}/page_1/header/entity_entity_test");
$this->submitForm(['options[target]' => $entity_test->id()], 'Apply');
$this->submitForm([], '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->assertEquals(['entity_block', 'entity_entity_test'], array_keys($header));
$this->assertEquals($block->id(), $header['entity_block']['target']);
$this->assertEquals($entity_test->uuid(), $header['entity_entity_test']['target']);
}
}

View File

@@ -0,0 +1,66 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\views\Views;
/**
* Tests the Argument validator through the UI.
*
* @group views_ui
*/
class ArgumentValidatorTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_argument'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the 'Specify validation criteria' checkbox functionality.
*/
public function testSpecifyValidation(): void {
// Specify a validation based on Node for the 'id' argument on the default
// display and assert that this works.
$this->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->assertEquals('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->assertEquals('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.
*
* @param bool $specify_validation
* The form validation.
*/
protected function saveArgumentHandlerWithValidationOptions($specify_validation) {
$options = [
'options[validate][type]' => 'entity---node',
'options[specify_validation]' => $specify_validation,
];
$this->drupalGet('admin/structure/views/nojs/handler/test_argument/default/argument/id');
$this->submitForm($options, 'Apply');
$this->drupalGet('admin/structure/views/view/test_argument');
$this->submitForm([], 'Save');
}
}

View File

@@ -0,0 +1,86 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
/**
* Tests the shared tempstore cache in the UI.
*
* @group views_ui
*/
class CachedDataUITest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the shared tempstore views data in the UI.
*/
public function testCacheData(): void {
$views_admin_user_uid = $this->fullAdminUser->id();
$temp_store = $this->container->get('tempstore.shared')->get('views');
// The view should not be locked.
$this->assertNull($temp_store->getMetadata('test_view'), 'The view is not locked.');
$this->drupalGet('admin/structure/views/view/test_view/edit');
// Make sure we have 'changes' to the view.
$this->drupalGet('admin/structure/views/nojs/display/test_view/default/title');
$this->submitForm([], 'Apply');
$this->assertSession()->pageTextContains('You have unsaved changes.');
$this->assertEquals($views_admin_user_uid, $temp_store->getMetadata('test_view')->getOwnerId(), '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->assertEquals($views_admin_user_uid, $temp_store->getMetadata('test_view')->getOwnerId(), 'The view is locked.');
// Cancel the view edit and make sure the cache is deleted.
$this->submitForm([], 'Cancel');
$this->assertNull($temp_store->getMetadata('test_view'), 'Shared tempstore data has been removed.');
// Test we are redirected to the view listing page.
$this->assertSession()->addressEquals('admin/structure/views');
// Log in with another user and make sure the view is locked and break.
$this->drupalGet('admin/structure/views/nojs/display/test_view/default/title');
$this->submitForm([], 'Apply');
$this->drupalLogin($this->adminUser);
$this->drupalGet('admin/structure/views/view/test_view/edit');
// Test that save and cancel buttons are not shown.
$this->assertSession()->buttonNotExists('Save');
$this->assertSession()->buttonNotExists('Cancel');
// Test we have the break lock link.
$this->assertSession()->linkByHrefExists('admin/structure/views/view/test_view/break-lock');
// Break the lock.
$this->clickLink('break this lock');
$this->submitForm([], 'Break lock');
// Test that save and cancel buttons are shown.
$this->assertSession()->buttonExists('Save');
$this->assertSession()->buttonExists('Cancel');
// Test we can save the view.
$this->drupalGet('admin/structure/views/view/test_view/edit');
$this->submitForm([], 'Save');
$this->assertSession()->pageTextContains("The view Test view has been saved.");
// Test that a deleted view has no tempstore data.
$this->drupalGet('admin/structure/views/nojs/display/test_view/default/title');
$this->submitForm([], 'Apply');
$this->drupalGet('admin/structure/views/view/test_view/delete');
$this->submitForm([], 'Delete');
// No view tempstore data should be returned for this view after deletion.
$this->assertNull($temp_store->getMetadata('test_view'), 'View tempstore data has been removed after deletion.');
}
}

View File

@@ -0,0 +1,190 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\views\Views;
/**
* Tests the UI and functionality for the Custom boolean field handler options.
*
* @group views_ui
* @see \Drupal\views\Plugin\views\field\Boolean
*/
class CustomBooleanTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* \Drupal\views\Tests\ViewTestBase::viewsData().
*/
public function viewsData() {
$data = parent::viewsData();
$data['views_test_data']['age']['field']['id'] = 'boolean';
return $data;
}
/**
* {@inheritdoc}
*/
public function dataSet() {
$data = parent::dataSet();
$data[0]['age'] = 0;
$data[3]['age'] = 0;
return $data;
}
/**
* Tests the setting and output of custom labels for boolean values.
*/
public function testCustomOption(): void {
// 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 = [
'plain' => [
'true' => $custom_true,
'false' => $custom_false,
'test' => 'assertStringContainsString',
],
'allowed tag' => [
'true' => '<p>' . $custom_true . '</p>',
'false' => '<p>' . $custom_false . '</p>',
'test' => 'assertStringContainsString',
],
'disallowed tag' => [
'true' => '<script>' . $custom_true . '</script>',
'false' => '<script>' . $custom_false . '</script>',
'test' => 'assertStringNotContainsString',
],
];
// Run the same tests on each type.
foreach ($custom_values as $type => $values) {
$options = [
'options[type]' => 'custom',
'options[type_custom_true]' => $values['true'],
'options[type_custom_false]' => $values['false'],
];
$this->drupalGet('admin/structure/views/nojs/handler/test_view/default/field/age');
$this->submitForm($options, 'Apply');
// Save the view.
$this->drupalGet('admin/structure/views/view/test_view');
$this->submitForm([], 'Save');
$view = Views::getView('test_view');
$output = $view->preview();
$output = \Drupal::service('renderer')->renderRoot($output);
$this->{$values['test']}($values['true'], (string) $output, "Expected custom boolean TRUE value {$values['true']} in output for $type");
$this->{$values['test']}($values['false'], (string) $output, "Expected custom boolean FALSE value {$values['false']} in output for $type");
}
}
/**
* Tests the setting and output of custom labels for boolean values.
*/
public function testCustomOptionTemplate(): void {
// Install theme to test with template system.
\Drupal::service('theme_installer')->install(['views_test_theme']);
// Set the default theme for Views preview.
$this->config('system.theme')
->set('default', 'views_test_theme')
->save();
$this->assertEquals('views_test_theme', $this->config('system.theme')->get('default'));
// 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 = [
'plain' => [
'true' => $custom_true,
'false' => $custom_false,
'test' => 'assertStringContainsString',
],
'allowed tag' => [
'true' => '<p>' . $custom_true . '</p>',
'false' => '<p>' . $custom_false . '</p>',
'test' => 'assertStringContainsString',
],
'disallowed tag' => [
'true' => '<script>' . $custom_true . '</script>',
'false' => '<script>' . $custom_false . '</script>',
'test' => 'assertStringNotContainsString',
],
];
// Run the same tests on each type.
foreach ($custom_values as $type => $values) {
$options = [
'options[type]' => 'custom',
'options[type_custom_true]' => $values['true'],
'options[type_custom_false]' => $values['false'],
];
$this->drupalGet('admin/structure/views/nojs/handler/test_view/default/field/age');
$this->submitForm($options, 'Apply');
// Save the view.
$this->drupalGet('admin/structure/views/view/test_view');
$this->submitForm([], 'Save');
$view = Views::getView('test_view');
$output = $view->preview();
$output = \Drupal::service('renderer')->renderRoot($output);
$this->{$values['test']}($values['true'], (string) $output, "Expected custom boolean TRUE value {$values['true']} in output for $type");
$this->{$values['test']}($values['false'], (string) $output, "Expected custom boolean FALSE value {$values['false']} in output for $type");
// Assert that we are using the correct template.
$this->assertStringContainsString('llama', (string) $output);
}
}
}

View File

@@ -0,0 +1,226 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\Core\Url;
use Drupal\user\Entity\Role;
use Drupal\user\RoleInterface;
/**
* Tests enabling, disabling, and reverting default views via the listing page.
*
* @group views_ui
*/
class DefaultViewsTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view_status', 'test_page_display_menu', 'test_page_display_arguments'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = ['views_test_config']): void {
parent::setUp($import_test_views, $modules);
$this->placeBlock('page_title_block');
}
/**
* Tests default views.
*/
public function testDefaultViews(): void {
// 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->assertSession()->linkByHrefNotExists($edit_href);
// Enable the view, and make sure it is now visible on the main listing
// page.
$this->drupalGet('admin/structure/views');
$this->clickViewsOperationLink('Enable', '/glossary/');
$this->assertSession()->addressEquals('admin/structure/views');
$this->assertSession()->linkByHrefExists($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->assertSession()->linkNotExists('Revert');
// $revert_href = 'admin/structure/views/view/glossary/revert';
// $this->assertSession()->linkByHrefNotExists($revert_href);
// Edit the view and change the title. Make sure that the new title is
// displayed.
$new_title = $this->randomMachineName(16);
$edit = ['title' => $new_title];
$this->drupalGet('admin/structure/views/nojs/display/glossary/page_1/title');
$this->submitForm($edit, 'Apply');
$this->drupalGet('admin/structure/views/view/glossary/edit/page_1');
$this->submitForm([], 'Save');
$this->drupalGet('glossary');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains($new_title);
// Save another view in the UI.
$this->drupalGet('admin/structure/views/nojs/display/archive/page_1/title');
$this->submitForm([], 'Apply');
$this->drupalGet('admin/structure/views/view/archive/edit/page_1');
$this->submitForm([], 'Save');
// Check there is an enable link. i.e. The view has not been enabled after
// editing.
$this->drupalGet('admin/structure/views');
$this->assertSession()->linkByHrefExists('admin/structure/views/view/archive/enable');
// Enable it again so it can be tested for access permissions.
$this->clickViewsOperationLink('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->assertSession()->linkExists('Revert');
// $this->assertSession()->linkByHrefExists($revert_href);
// $this->drupalGet($revert_href);
// $this->submitForm(array(), 'Revert');
// $this->drupalGet('glossary');
// $this->assertSession()->pageTextNotContains($new_title);
// Duplicate the view and check that the normal schema of duplicated views is used.
$this->drupalGet('admin/structure/views');
$this->clickViewsOperationLink('Duplicate', '/glossary');
$edit = [
'id' => 'duplicate_of_glossary',
];
$this->assertSession()->titleEquals('Duplicate of Glossary | Drupal');
$this->submitForm($edit, 'Duplicate');
$this->assertSession()->addressEquals('admin/structure/views/view/duplicate_of_glossary');
// Duplicate a view and set a custom name.
$this->drupalGet('admin/structure/views');
$this->clickViewsOperationLink('Duplicate', '/glossary');
$random_name = $this->randomMachineName();
$this->submitForm(['id' => $random_name], 'Duplicate');
$this->assertSession()->addressEquals("admin/structure/views/view/$random_name");
// 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('Disable', '/glossary/');
// $this->assertSession()->addressEquals('admin/structure/views');
// $this->assertSession()->linkByHrefNotExists($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('Enable', '/glossary/');
$this->assertSession()->addressEquals('admin/structure/views');
$this->assertSession()->linkByHrefExists($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->assertSession()->statusCodeEquals(403);
$this->drupalGet('archive');
$this->assertSession()->statusCodeEquals(403);
// Test deleting a view.
$this->drupalLogin($this->fullAdminUser);
$this->drupalGet('admin/structure/views');
$this->clickViewsOperationLink('Delete', '/glossary/');
// Submit the confirmation form.
$this->submitForm([], 'Delete');
// Ensure the view is no longer listed.
$this->assertSession()->addressEquals('admin/structure/views');
$this->assertSession()->linkByHrefNotExists($edit_href);
// Ensure the view is no longer available.
$this->drupalGet($edit_href);
$this->assertSession()->statusCodeEquals(404);
$this->assertSession()->pageTextContains('Page not found');
// Delete all duplicated Glossary views.
$this->drupalGet('admin/structure/views');
$this->clickViewsOperationLink('Delete', 'duplicate_of_glossary');
// Submit the confirmation form.
$this->submitForm([], 'Delete');
$this->drupalGet('glossary');
$this->assertSession()->statusCodeEquals(200);
$this->drupalGet('admin/structure/views');
$this->clickViewsOperationLink('Delete', $random_name);
// Submit the confirmation form.
$this->submitForm([], 'Delete');
$this->drupalGet('glossary');
$this->assertSession()->statusCodeEquals(404);
$this->assertSession()->pageTextContains('Page not found');
}
/**
* Tests that enabling views moves them to the correct table.
*/
public function testSplitListing(): void {
$this->drupalGet('admin/structure/views');
$this->assertSession()->elementNotExists('xpath', '//div[@id="views-entity-list"]/div[@class = "views-list-section enabled"]/table//td/text()[contains(., "test_view_status")]');
$this->assertSession()->elementsCount('xpath', '//div[@id="views-entity-list"]/div[@class = "views-list-section disabled"]/table//td/text()[contains(., "test_view_status")]', 1);
// Enable the view.
$this->clickViewsOperationLink('Enable', '/test_view_status/');
$this->assertSession()->elementNotExists('xpath', '//div[@id="views-entity-list"]/div[@class = "views-list-section disabled"]/table//td/text()[contains(., "test_view_status")]');
$this->assertSession()->elementsCount('xpath', '//div[@id="views-entity-list"]/div[@class = "views-list-section enabled"]/table//td/text()[contains(., "test_view_status")]', 1);
// Attempt to disable the view by path directly, with no token.
$this->drupalGet('admin/structure/views/view/test_view_status/disable');
$this->assertSession()->statusCodeEquals(403);
}
/**
* Tests that page displays show the correct path.
*/
public function testPathDestination(): void {
$this->drupalGet('admin/structure/views');
// Check that links to views on default tabs are rendered correctly.
$this->assertSession()->linkByHrefExists('test_page_display_menu');
$this->assertSession()->linkByHrefNotExists('test_page_display_menu/default');
$this->assertSession()->linkByHrefExists('test_page_display_menu/local');
// Check that a dynamic path is shown as text.
$this->assertSession()->responseContains('test_route_with_suffix/%/suffix');
$this->assertSession()->linkByHrefNotExists(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.
*/
public function clickViewsOperationLink($label, $unique_href_part) {
$this->assertSession()->elementExists('xpath', "//a[normalize-space(text())='$label' and contains(@href, '$unique_href_part')]")->click();
}
}

View File

@@ -0,0 +1,139 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\views\Views;
/**
* Tests the UI for the attachment display plugin.
*
* @group views_ui
* @see \Drupal\views\Plugin\views\display\Attachment
*/
class DisplayAttachmentTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
* .
*/
public static $testViews = ['test_attachment_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the attachment UI.
*/
public function testAttachmentUI(): void {
$this->drupalGet('admin/structure/views/view/test_attachment_ui/edit/attachment_1');
$this->assertSession()->pageTextContains('Not defined');
$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->assertSession()->assertEscaped('<em>Page</em>');
$this->assertSession()->checkboxNotChecked("edit-displays-default");
$this->assertSession()->checkboxNotChecked("edit-displays-page-1");
// Save the attachments and test the value on the view.
$this->drupalGet($attachment_display_url);
$this->submitForm(['displays[page_1]' => 1], 'Apply');
// Options summary should be escaped.
$this->assertSession()->assertEscaped('<em>Page</em>');
$this->assertSession()->responseNotContains('<em>Page</em>');
$this->assertSession()->elementAttributeContains('xpath', '//a[@id = "views-attachment-1-displays"]', 'title', 'Page');
$this->submitForm([], 'Save');
$view = Views::getView('test_attachment_ui');
$view->initDisplay();
$this->assertEquals(['page_1'], array_keys(array_filter($view->displayHandlers->get('attachment_1')->getOption('displays'))), 'The attached displays got saved as expected');
$this->drupalGet($attachment_display_url);
$this->submitForm([
'displays[default]' => 1,
'displays[page_1]' => 1,
], 'Apply');
$this->assertSession()->elementAttributeContains('xpath', '//a[@id = "views-attachment-1-displays"]', 'title', 'Multiple displays');
$this->submitForm([], 'Save');
$view = Views::getView('test_attachment_ui');
$view->initDisplay();
$this->assertEquals(['default', 'page_1'], array_keys($view->displayHandlers->get('attachment_1')->getOption('displays')), 'The attached displays got saved as expected');
}
/**
* Tests the attachment working after the attached page was deleted.
*/
public function testRemoveAttachedDisplay(): void {
// Create a view.
$view = $this->randomView();
$path_prefix = 'admin/structure/views/view/' . $view['id'] . '/edit';
$attachment_display_url = 'admin/structure/views/nojs/display/' . $view['id'] . '/attachment_1/displays';
// Open the Page display and create the attachment display.
$this->drupalGet($path_prefix . '/page_1');
$this->submitForm([], 'Add Attachment');
$this->assertSession()->pageTextContains('Not defined');
// Attach the Attachment to the Page display.
$this->drupalGet($attachment_display_url);
$this->submitForm(['displays[page_1]' => 1], 'Apply');
$this->submitForm([], 'Save');
// Open the Page display and mark it as deleted.
$this->drupalGet($path_prefix . '/page_1');
$this->assertSession()->buttonExists('edit-displays-settings-settings-content-tab-content-details-top-actions-delete');
$this->drupalGet($path_prefix . '/page_1');
$this->submitForm([], 'Delete Page');
// Open the attachment display and save it.
$this->drupalGet($path_prefix . '/attachment_1');
$this->submitForm([], 'Save');
// Check that there is no warning for the removed page display.
$this->assertSession()->pageTextNotContains("Plugin ID 'page_1' was not found.");
// Check that the attachment is no longer linked to the removed display.
$this->assertSession()->pageTextContains('Not defined');
}
/**
* Tests the attachment after changing machine name.
*/
public function testAttachmentOnAttachedMachineNameChange(): void {
$view = $this->randomView();
$path_prefix = 'admin/structure/views/view/' . $view['id'] . '/edit';
$attachment_display_url = 'admin/structure/views/nojs/display/' . $view['id'] . '/attachment_1/displays';
// Open the Page display and create the attachment display.
$this->drupalGet($path_prefix . '/page_1');
$this->submitForm([], 'Add Attachment');
$this->assertSession()->pageTextContains('Not defined');
// Attach the Attachment to the Default and Page display.
$this->drupalGet($attachment_display_url);
$this->submitForm(['displays[default]' => 1, 'displays[page_1]' => 1], 'Apply');
$this->submitForm([], 'Save');
// Change the machine name of the page.
$this->drupalGet('admin/structure/views/nojs/display/' . $view['id'] . '/page_1/display_id');
$this->submitForm(['display_id' => 'page_new_id'], 'Apply');
$this->submitForm([], 'Save');
// Check that the attachment is still attached to the page.
$this->drupalGet($attachment_display_url);
$this->assertSession()->checkboxChecked("edit-displays-default");
$this->assertSession()->checkboxChecked("edit-displays-page-new-id");
}
}

View File

@@ -0,0 +1,174 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\views\Views;
/**
* Tests creation, retrieval, updating, and deletion of displays in the Web UI.
*
* @group views_ui
*/
class DisplayCRUDTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_display'];
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['contextual'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests adding a display.
*/
public function testAddDisplay(): void {
// Show the default display.
$this->config('views.settings')->set('ui.show.default_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->submitForm([], 'Add Page');
$this->assertSession()->linkByHrefExists($path_prefix . '/page_1', 0, 'Make sure after adding a display the new display appears in the UI');
$this->assertSession()->linkNotExists('Default*', 'Make sure the default display is not marked as changed.');
$this->assertSession()->linkExists('Page*', 0, 'Make sure the added display is marked as changed.');
$this->drupalGet("admin/structure/views/nojs/display/{$view['id']}/page_1/path");
$this->submitForm(['path' => 'test/path'], 'Apply');
$this->submitForm([], 'Save');
}
/**
* Tests removing a display.
*/
public function testRemoveDisplay(): void {
$view = $this->randomView();
$path_prefix = 'admin/structure/views/view/' . $view['id'] . '/edit';
// Make sure there is no delete button on the default display.
$this->drupalGet($path_prefix . '/default');
$this->assertSession()->buttonNotExists('edit-displays-settings-settings-content-tab-content-details-top-actions-delete');
$this->drupalGet($path_prefix . '/page_1');
$this->assertSession()->buttonExists('edit-displays-settings-settings-content-tab-content-details-top-actions-delete');
// Delete the page, so we can test the undo process.
$this->drupalGet($path_prefix . '/page_1');
$this->submitForm([], 'Delete Page');
$this->assertSession()->buttonExists('edit-displays-settings-settings-content-tab-content-details-top-actions-undo-delete');
// Test that the display link is marked as to be deleted.
$this->assertSession()->elementExists('xpath', "//a[contains(@href, '{$path_prefix}/page_1') and contains(@class, 'views-display-deleted-link')]");
// Undo the deleting of the display.
$this->drupalGet($path_prefix . '/page_1');
$this->submitForm([], 'Undo delete of Page');
$this->assertSession()->buttonNotExists('edit-displays-settings-settings-content-tab-content-details-top-actions-undo-delete');
$this->assertSession()->buttonExists('edit-displays-settings-settings-content-tab-content-details-top-actions-delete');
// Now delete again and save the view.
$this->drupalGet($path_prefix . '/page_1');
$this->submitForm([], 'Delete Page');
$this->submitForm([], 'Save');
$this->assertSession()->linkByHrefNotExists($path_prefix . '/page_1', 'Make sure there is no display tab for the deleted display.');
// Test deleting a display that has a modified machine name.
$view = $this->randomView();
$machine_name = 'new_machine_name';
$path_prefix = 'admin/structure/views/view/' . $view['id'] . '/edit';
$this->drupalGet("admin/structure/views/nojs/display/{$view['id']}/page_1/display_id");
$this->submitForm(['display_id' => $machine_name], 'Apply');
$this->submitForm([], 'Delete Page');
$this->submitForm([], 'Save');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->linkByHrefNotExists($path_prefix . '/new_machine_name', 'Make sure there is no display tab for the deleted display.');
}
/**
* Tests that the correct display is loaded by default.
*/
public function testDefaultDisplay(): void {
$this->drupalGet('admin/structure/views/view/test_display');
$this->assertSession()->elementsCount('xpath', '//*[@id="views-page-1-display-title"]', 1);
}
/**
* Tests the duplicating of a display.
*/
public function testDuplicateDisplay(): void {
$view = $this->randomView();
$path_prefix = 'admin/structure/views/view/' . $view['id'] . '/edit';
$path = $view['page[path]'];
$this->drupalGet($path_prefix);
$this->submitForm([], 'Duplicate Page');
// Verify that the user got redirected to the new display.
$this->assertSession()->linkByHrefExists($path_prefix . '/page_2', 0, 'Make sure after duplicating the new display appears in the UI');
$this->assertSession()->addressEquals($path_prefix . '/page_2');
// Set the title and override the css classes.
$random_title = $this->randomMachineName();
$random_css = $this->randomMachineName();
$this->drupalGet("admin/structure/views/nojs/display/{$view['id']}/page_2/title");
$this->submitForm(['title' => $random_title], 'Apply');
$this->drupalGet("admin/structure/views/nojs/display/{$view['id']}/page_2/css_class");
$this->submitForm([
'override[dropdown]' => 'page_2',
'css_class' => $random_css,
], 'Apply');
// Duplicate as a different display type.
$this->submitForm([], 'Duplicate as Block');
$this->assertSession()->linkByHrefExists($path_prefix . '/block_1', 0, 'Make sure after duplicating the new display appears in the UI');
$this->assertSession()->addressEquals($path_prefix . '/block_1');
$this->assertSession()->pageTextContains('Block settings');
$this->assertSession()->pageTextNotContains('Page settings');
$this->submitForm([], 'Save');
$view = Views::getView($view['id']);
$view->initDisplay();
$page_2 = $view->displayHandlers->get('page_2');
$this->assertNotEmpty($page_2, 'The new page display got saved.');
$this->assertEquals('Page', $page_2->display['display_title']);
$this->assertEquals($path, $page_2->display['display_options']['path']);
$block_1 = $view->displayHandlers->get('block_1');
$this->assertNotEmpty($block_1, 'The new block display got saved.');
$this->assertEquals('block', $block_1->display['display_plugin']);
$this->assertEquals('Block', $block_1->display['display_title'], 'The new display title got generated as expected.');
$this->assertFalse(isset($block_1->display['display_options']['path']));
$this->assertEquals($random_title, $block_1->getOption('title'), 'The overridden title option from the display got copied into the duplicate');
$this->assertEquals($random_css, $block_1->getOption('css_class'), 'The overridden css_class option from the display got copied into the duplicate');
// Test duplicating a display after changing the machine name.
$view_id = $view->id();
$this->drupalGet("admin/structure/views/nojs/display/{$view_id}/page_2/display_id");
$this->submitForm(['display_id' => 'page_new'], 'Apply');
$this->submitForm([], 'Duplicate as Block');
$this->submitForm([], 'Save');
$view = Views::getView($view_id);
$view->initDisplay();
$this->assertNotNull($view->displayHandlers->get('page_new'), 'The original display is saved with a changed id');
$this->assertNotNull($view->displayHandlers->get('block_2'), 'The duplicate display is saved with new id');
}
}

View File

@@ -0,0 +1,52 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\views\Views;
/**
* Tests the display extender UI.
*
* @group views_ui
*/
class DisplayExtenderUITest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the display extender UI.
*/
public function testDisplayExtenderUI(): void {
$this->config('views.settings')->set('display_extenders', ['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->assertSession()->linkByHrefExists($display_option_url, 0, 'Make sure the option defined by the test display extender appears in the UI.');
$random_text = $this->randomMachineName();
$this->drupalGet($display_option_url);
$this->submitForm(['test_extender_test_option' => $random_text], 'Apply');
$this->assertSession()->linkExists($random_text);
$this->submitForm([], 'Save');
$view = Views::getView($view->storage->id());
$view->initDisplay();
$display_extender_options = $view->display_handler->getOption('display_extenders');
$this->assertEquals($random_text, $display_extender_options['display_extender_test']['test_extender_test_option'], 'Make sure that the display extender option got saved.');
}
}

View File

@@ -0,0 +1,92 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
/**
* Tests the UI for feed display plugin.
*
* @group views_ui
* @see \Drupal\views\Plugin\views\display\Feed
*/
class DisplayFeedTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_display_feed'];
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['views_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests feed display admin UI.
*/
public function testFeedUI(): void {
// Test the RSS feed.
foreach (self::$testViews as $view_name) {
$this->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->assertSession()->pageTextContains($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->assertSession()->assertEscaped('<em>Page</em>');
// Load all the options of the checkbox.
$result = $this->xpath('//div[@id="edit-displays"]/div');
$options = [];
foreach ($result as $item) {
$input_node = $item->find('css', 'input');
if ($input_node->hasAttribute('value')) {
$options[] = $input_node->getAttribute('value');
}
}
$this->assertEquals(['default', 'page'], $options, 'Make sure all displays appears as expected.');
// Post and save this and check the output.
$this->drupalGet('admin/structure/views/nojs/display/' . $view_name . '/feed_1/displays');
$this->submitForm(['displays[page]' => 'page'], 'Apply');
// Options summary should be escaped.
$this->assertSession()->assertEscaped('<em>Page</em>');
$this->assertSession()->responseNotContains('<em>Page</em>');
$this->drupalGet('admin/structure/views/view/' . $view_name . '/edit/feed_1');
$this->assertSession()->elementTextContains('xpath', '//*[@id="views-feed-1-displays"]', 'Page');
// Add the default display, so there should now be multiple displays.
$this->drupalGet('admin/structure/views/nojs/display/' . $view_name . '/feed_1/displays');
$this->submitForm(['displays[default]' => 'default'], 'Apply');
$this->drupalGet('admin/structure/views/view/' . $view_name . '/edit/feed_1');
$this->assertSession()->elementTextContains('xpath', '//*[@id="views-feed-1-displays"]', 'Multiple displays');
}
}

View File

@@ -0,0 +1,347 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\Core\Menu\MenuTreeParameters;
use Drupal\menu_link_content\Entity\MenuLinkContent;
use Drupal\Tests\SchemaCheckTestTrait;
use Drupal\Tests\system\Functional\Cache\AssertPageCacheContextsAndTagsTrait;
/**
* Tests the UI of generic display path plugin.
*
* @group views_ui
* @group #slow
* @see \Drupal\views\Plugin\views\display\PathPluginBase
*/
class DisplayPathTest extends UITestBase {
use AssertPageCacheContextsAndTagsTrait;
use SchemaCheckTestTrait;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = ['views_test_config']): void {
parent::setUp($import_test_views, $modules);
$this->placeBlock('page_title_block');
}
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view', 'test_page_display_menu'];
/**
* Runs the tests.
*/
public function testPathUI(): void {
$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->submitForm([], 'Add Page');
$this->assertSession()->pageTextContains('No path is set');
$this->assertSession()->linkNotExists('View 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->drupalGet('admin/structure/views/nojs/display/test_view/page_1/path');
$this->submitForm(['path' => $random_path], 'Apply');
$this->assertSession()->pageTextContains('/' . $random_path);
$this->clickLink('View Page');
$this->assertSession()->addressEquals($random_path);
}
/**
* Tests that View paths are properly filtered for XSS.
*/
public function doPathXssFilterTest() {
$this->drupalGet('admin/structure/views/view/test_view');
$this->submitForm([], 'Add Page');
$this->drupalGet('admin/structure/views/nojs/display/test_view/page_2/path');
$this->submitForm(['path' => '<object>malformed_path</object>'], 'Apply');
$this->submitForm([], 'Add Page');
$this->drupalGet('admin/structure/views/nojs/display/test_view/page_3/path');
$this->submitForm(['path' => '<script>alert("hello");</script>'], 'Apply');
$this->submitForm([], 'Add Page');
$this->drupalGet('admin/structure/views/nojs/display/test_view/page_4/path');
$this->submitForm(['path' => '<script>alert("hello I have placeholders %");</script>'], 'Apply');
$this->drupalGet('admin/structure/views/view/test_view');
$this->submitForm([], 'Save');
$this->drupalGet('admin/structure/views');
// The anchor text should be escaped.
$this->assertSession()->assertEscaped('/<object>malformed_path</object>');
$this->assertSession()->assertEscaped('/<script>alert("hello");</script>');
$this->assertSession()->assertEscaped('/<script>alert("hello I have placeholders %");</script>');
// Links should be URL-encoded.
$this->assertSession()->responseContains('/%3Cobject%3Emalformed_path%3C/object%3E');
$this->assertSession()->responseContains('/%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->drupalGet($url);
$this->submitForm(['path' => '%/foo'], 'Apply');
$this->assertSession()->addressEquals($url);
$this->assertSession()->pageTextContains('"%" may not be used for the first segment of a path.');
$this->drupalGet($url);
$this->submitForm(['path' => 'user/%1/example'], 'Apply');
$this->assertSession()->addressEquals($url);
$this->assertSession()->pageTextContains("Numeric placeholders may not be used. Use plain placeholders (%).");
}
/**
* Tests deleting a page display that has no path.
*/
public function testDeleteWithNoPath(): void {
$this->drupalGet('admin/structure/views/view/test_view');
$this->submitForm([], 'Add Page');
$this->submitForm([], 'Delete Page');
$this->submitForm([], 'Save');
$this->assertSession()->pageTextContains("The view Test view has been saved.");
}
/**
* Tests the menu and tab option form.
*/
public function testMenuOptions(): void {
$this->drupalGet('admin/structure/views/view/test_view');
// Add a new page display.
$this->submitForm([], 'Add Page');
// Add an invalid path (only fragment).
$this->drupalGet('admin/structure/views/nojs/display/test_view/page_1/path');
$this->submitForm(['path' => '#foo'], 'Apply');
$this->assertSession()->pageTextContains('Path is empty');
// Add an invalid path with a query.
$this->drupalGet('admin/structure/views/nojs/display/test_view/page_1/path');
$this->submitForm(['path' => 'foo?bar'], 'Apply');
$this->assertSession()->pageTextContains('No query allowed.');
// Add an invalid path with just a query.
$this->drupalGet('admin/structure/views/nojs/display/test_view/page_1/path');
$this->submitForm(['path' => '?bar'], 'Apply');
$this->assertSession()->pageTextContains('Path is empty');
// Provide a random, valid path string.
$random_string = $this->randomMachineName();
// Save a path.
$this->drupalGet('admin/structure/views/nojs/display/test_view/page_1/path');
$this->submitForm(['path' => $random_string], 'Apply');
$this->drupalGet('admin/structure/views/view/test_view');
$this->drupalGet('admin/structure/views/nojs/display/test_view/page_1/menu');
$this->submitForm([
'menu[type]' => 'default tab',
'menu[title]' => 'Test tab title',
], 'Apply');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->addressEquals('admin/structure/views/nojs/display/test_view/page_1/tab_options');
$this->submitForm(['tab_options[type]' => 'tab', 'tab_options[title]' => $this->randomString()], 'Apply');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->addressEquals('admin/structure/views/view/test_view/edit/page_1');
$this->drupalGet('admin/structure/views/view/test_view');
$this->assertSession()->linkExists('Tab: Test tab title');
// If it's a default tab, it should also have an additional settings link.
$this->assertSession()->linkByHrefExists('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->assertSession()->statusCodeEquals(200);
$menu_options = $this->assertSession()->selectExists('edit-menu-parent')->findAll('css', 'option');
$menu_options = array_map(function ($element) {
return $element->getText();
}, $menu_options);
$this->assertEquals([
'<User account menu>',
'-- My account',
'-- Log out',
'<Administration>',
'<Footer>',
'<Main navigation>',
'<Tools>',
'-- Compose tips (disabled)',
'-- Test menu link',
], $menu_options);
// The cache contexts associated with the (in)accessible menu links are
// bubbled.
$this->assertCacheContext('user.permissions');
}
/**
* Tests the regression in https://www.drupal.org/node/2532490.
*/
public function testDefaultMenuTabRegression(): void {
$this->container->get('module_installer')->install(['menu_link_content', 'toolbar', 'system']);
$this->resetAll();
$admin_user = $this->drupalCreateUser([
'administer views',
'administer blocks',
'bypass node access',
'access user profiles',
'view all revisions',
'administer permissions',
'administer menu',
'link to any page',
'access toolbar',
'access administration pages',
]);
$this->drupalLogin($admin_user);
$edit = [
'title[0][value]' => 'Menu title',
'link[0][uri]' => '/admin/foo',
'menu_parent' => 'admin:system.admin',
];
$this->drupalGet('admin/structure/menu/manage/admin/add');
$this->submitForm($edit, 'Save');
$menu_items = \Drupal::entityTypeManager()->getStorage('menu_link_content')->getQuery()
->accessCheck(FALSE)
->sort('id', 'DESC')
->pager(1)
->execute();
$menu_item = end($menu_items);
/** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link_content */
$menu_link_content = MenuLinkContent::load($menu_item);
$edit = [];
$edit['label'] = $this->randomMachineName(16);
$view_id = $edit['id'] = $this->randomMachineName(16);
$edit['description'] = $this->randomMachineName(16);
$edit['page[create]'] = TRUE;
$edit['page[path]'] = 'admin/foo';
$this->drupalGet('admin/structure/views/add');
$this->submitForm($edit, 'Save and edit');
$parameters = new MenuTreeParameters();
$parameters->addCondition('id', $menu_link_content->getPluginId());
$result = \Drupal::menuTree()->load('admin', $parameters);
$plugin_definition = end($result)->link->getPluginDefinition();
$this->assertEquals('view.' . $view_id . '.page_1', $plugin_definition['route_name']);
$this->clickLink('No menu');
$this->submitForm([
'menu[type]' => 'default tab',
'menu[title]' => 'Menu title',
], 'Apply');
$this->assertSession()->pageTextContains('Default tab options');
$this->submitForm([
'tab_options[type]' => 'normal',
'tab_options[title]' => 'Parent title',
], 'Apply');
// Open the menu options again.
$this->clickLink('Tab: Menu title');
// Assert a menu can be selected as a parent.
$this->assertSession()->optionExists('menu[parent]', 'admin:');
// Assert a parent menu item can be selected from within a menu.
$this->assertSession()->optionExists('menu[parent]', 'admin:system.admin');
// Check that parent menu item can now be
// added without the menu_ui module being enabled.
$this->submitForm([
'menu[type]' => 'normal',
'menu[parent]' => 'admin:system.admin',
'menu[title]' => 'Menu title',
], 'Apply');
$this->submitForm([], 'Save');
// Assert that saving the view will not cause an exception.
$this->assertSession()->statusCodeEquals(200);
}
/**
* Tests the "Use the administration theme" configuration.
*
* @see \Drupal\Tests\views\Functional\Plugin\DisplayPageWebTest::testAdminTheme
*/
public function testUseAdminTheme(): void {
$this->drupalGet('admin/structure/views/view/test_view');
// Add a new page display.
$this->submitForm([], 'Add Page');
$this->assertSession()->pageTextContains('No path is set');
$this->assertSession()->pageTextContains('Administration theme: No');
// Test with a path starting with "/admin".
$admin_path = 'admin/test_admin_path';
$this->drupalGet('admin/structure/views/nojs/display/test_view/page_1/path');
$this->submitForm(['path' => $admin_path], 'Apply');
$this->assertSession()->pageTextContains('/' . $admin_path);
$this->assertSession()->pageTextContains('Administration theme: Yes (admin path)');
$this->submitForm([], 'Save');
$this->assertConfigSchemaByName('views.view.test_view');
$display_options = $this->config('views.view.test_view')->get('display.page_1.display_options');
$this->assertArrayNotHasKey('use_admin_theme', $display_options);
$this->drupalGet('admin/structure/views/nojs/display/test_view/page_1/use_admin_theme');
$this->assertSession()->elementExists('css', 'input[name="use_admin_theme"][disabled="disabled"][checked="checked"]');
// Test with a non-administration path.
$non_admin_path = 'kittens';
$this->drupalGet('admin/structure/views/nojs/display/test_view/page_1/path');
$this->submitForm(['path' => $non_admin_path], 'Apply');
$this->assertSession()->pageTextContains('/' . $non_admin_path);
$this->assertSession()->pageTextContains('Administration theme: No');
$this->submitForm([], 'Save');
$this->assertConfigSchemaByName('views.view.test_view');
$display_options = $this->config('views.view.test_view')->get('display.page_1.display_options');
$this->assertArrayNotHasKey('use_admin_theme', $display_options);
// Enable administration theme.
$this->drupalGet('admin/structure/views/nojs/display/test_view/page_1/use_admin_theme');
$this->submitForm(['use_admin_theme' => TRUE], 'Apply');
$this->assertSession()->pageTextContains('Administration theme: Yes');
$this->submitForm([], 'Save');
$this->assertConfigSchemaByName('views.view.test_view');
$display_options = $this->config('views.view.test_view')->get('display.page_1.display_options');
$this->assertArrayHasKey('use_admin_theme', $display_options);
$this->assertTrue($display_options['use_admin_theme']);
}
}

View File

@@ -0,0 +1,308 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\Component\Utility\Unicode;
use Drupal\views\Entity\View;
use Drupal\views\Views;
/**
* Tests the display UI.
*
* @group views_ui
* @group #slow
*/
class DisplayTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_display'];
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['contextual'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests adding a display.
*/
public function testAddDisplay(): void {
$view = $this->randomView();
$this->assertSession()->elementNotExists('xpath', '//li[@data-drupal-selector="edit-displays-top-tabs-block-1"]');
$this->assertSession()->elementNotExists('xpath', '//li[@data-drupal-selector="edit-displays-top-tabs-block-2"]');
$this->assertSession()->pageTextMatchesCount(0, '/Block name:/');
$this->submitForm([], 'Add Block');
$this->assertSession()->elementTextContains('xpath', '//li[@data-drupal-selector="edit-displays-top-tabs-block-1"]', 'Block*');
$this->assertSession()->elementNotExists('xpath', '//li[@data-drupal-selector="edit-displays-top-tabs-block-2"]');
$this->assertSession()->pageTextMatchesCount(1, '/Block name:/');
}
/**
* Tests reordering of displays.
*/
public function testReorderDisplay(): void {
$view = [
'block[create]' => TRUE,
];
$view = $this->randomView($view);
$this->clickLink('Reorder displays');
$this->assertSession()->elementExists('xpath', '//tr[@id="display-row-default"]');
$this->assertSession()->elementExists('xpath', '//tr[@id="display-row-page_1"]');
$this->assertSession()->elementExists('xpath', '//tr[@id="display-row-block_1"]');
// Ensure the view displays are in the expected order in configuration.
$expected_display_order = ['default', 'block_1', 'page_1'];
$this->assertEquals($expected_display_order, array_keys(Views::getView($view['id'])->storage->get('display')), 'The correct display names are present.');
// Put the block display in front of the page display.
$edit = [
'displays[page_1][weight]' => 2,
'displays[block_1][weight]' => 1,
];
$this->submitForm($edit, 'Apply');
$this->submitForm([], 'Save');
$view = Views::getView($view['id']);
$displays = $view->storage->get('display');
$this->assertEquals(0, $displays['default']['position'], 'Make sure the default display comes first.');
$this->assertEquals(1, $displays['block_1']['position'], 'Make sure the block display comes before the page display.');
$this->assertEquals(2, $displays['page_1']['position'], 'Make sure the page display comes after the block display.');
// Ensure the view displays are in the expected order in configuration.
$this->assertEquals($expected_display_order, array_keys($view->storage->get('display')), 'The correct display names are present.');
}
/**
* Tests disabling of a display.
*/
public function testDisableDisplay(): void {
$view = $this->randomView();
$path_prefix = 'admin/structure/views/view/' . $view['id'] . '/edit';
// Verify that the disabled display css class does not appear after initial
// adding of a view.
$this->drupalGet($path_prefix);
$this->assertSession()->elementNotExists('xpath', "//div[contains(@class, 'views-display-disabled')]");
$this->assertSession()->buttonExists('edit-displays-settings-settings-content-tab-content-details-top-actions-disable');
$this->assertSession()->buttonNotExists('edit-displays-settings-settings-content-tab-content-details-top-actions-enable');
// Verify that the disabled display css class appears once the display is
// marked as such.
$this->submitForm([], 'Disable Page');
$this->assertSession()->elementExists('xpath', "//div[contains(@class, 'views-display-disabled')]");
$this->assertSession()->buttonNotExists('edit-displays-settings-settings-content-tab-content-details-top-actions-disable');
$this->assertSession()->buttonExists('edit-displays-settings-settings-content-tab-content-details-top-actions-enable');
// Verify that the disabled display css class does not appears once the
// display is enabled again.
$this->submitForm([], 'Enable Page');
$this->assertSession()->elementNotExists('xpath', "//div[contains(@class, 'views-display-disabled')]");
}
/**
* Tests views_ui_views_plugins_display_alter is altering plugin definitions.
*/
public function testDisplayPluginsAlter(): void {
$definitions = Views::pluginManager('display')->getDefinitions();
$expected = [
'route_name' => 'entity.view.edit_form',
'route_parameters_names' => ['view' => 'id'],
];
// Test the expected views_ui array exists on each definition.
foreach ($definitions as $definition) {
$this->assertSame($expected, $definition['contextual links']['entity.view.edit_form'], 'Expected views_ui array found in plugin definition.');
}
}
/**
* Tests display areas.
*/
public function testDisplayAreas(): void {
// Show the advanced column.
$this->config('views.settings')->set('ui.show.advanced_column', TRUE)->save();
// Add a new data display to the view.
$view = Views::getView('test_display');
$view->storage->addDisplay('display_no_area_test');
$view->save();
$this->drupalGet('admin/structure/views/view/test_display/edit/display_no_area_test_1');
$areas = [
'header',
'footer',
'empty',
];
// Assert that the expected text is found in each area category.
foreach ($areas as $type) {
$this->assertSession()->elementTextEquals('xpath', "//div[contains(@class, '$type')]/div", "The selected display type does not use $type plugins");
}
}
/**
* Tests the link-display setting.
*/
public function testLinkDisplay(): void {
// Test setting the link display in the UI form.
$path = 'admin/structure/views/view/test_display/edit/block_1';
$link_display_path = 'admin/structure/views/nojs/display/test_display/block_1/link_display';
// Test the link text displays 'None' and not 'Block 1'
$this->drupalGet($path);
$this->assertSession()->elementTextEquals('xpath', "//a[contains(@href, '{$link_display_path}')]", 'None');
$this->drupalGet($link_display_path);
$this->assertSession()->checkboxChecked('edit-link-display-0');
// Test the default radio option on the link display form.
$this->drupalGet($link_display_path);
$this->submitForm(['link_display' => 'page_1'], 'Apply');
// The form redirects to the default display.
$this->drupalGet($path);
// Test that the link option summary shows the right linked display.
$this->assertSession()->elementTextEquals('xpath', "//a[contains(@href, '{$link_display_path}')]", 'Page');
$this->drupalGet($link_display_path);
$this->submitForm([
'link_display' => 'custom_url',
'link_url' => 'a-custom-url',
], 'Apply');
// The form redirects to the default display.
$this->drupalGet($path);
$this->assertSession()->linkExists('Custom URL', 0, 'The link option has custom URL as summary.');
// Test the default link_url value for new display
$this->submitForm([], 'Add Block');
$this->assertSession()->addressEquals('admin/structure/views/view/test_display/edit/block_2');
$this->clickLink('Custom URL');
$this->assertSession()->fieldValueEquals('link_url', 'a-custom-url');
}
/**
* Tests that the view status is correctly reflected on the edit form.
*/
public function testViewStatus(): void {
$view = $this->randomView();
$id = $view['id'];
// The view should initially have the enabled class on its form wrapper.
$this->drupalGet('admin/structure/views/view/' . $id);
$this->assertSession()->elementExists('xpath', "//div[contains(@class, 'views-edit-view') and contains(@class, 'enabled')]");
$view = Views::getView($id);
$view->storage->disable()->save();
// The view should now have the disabled class on its form wrapper.
$this->drupalGet('admin/structure/views/view/' . $id);
$this->assertSession()->elementExists('xpath', "//div[contains(@class, 'views-edit-view') and contains(@class, 'disabled')]");
}
/**
* Ensures that no XSS is possible for buttons.
*/
public function testDisplayTitleInButtonsXss(): void {
$xss_markup = '"><script>alert(123)</script>';
$view = $this->randomView();
$view = View::load($view['id']);
\Drupal::configFactory()->getEditable('views.settings')->set('ui.show.default_display', TRUE)->save();
foreach ([$xss_markup, '&quot;><script>alert(123)</script>'] as $input) {
$display =& $view->getDisplay('page_1');
$display['display_title'] = $input;
$view->save();
$this->drupalGet("admin/structure/views/view/{$view->id()}");
$escaped = Unicode::truncate($input, 25, FALSE, TRUE);
$this->assertSession()->assertEscaped($escaped);
$this->assertSession()->responseNotContains($xss_markup);
$this->drupalGet("admin/structure/views/view/{$view->id()}/edit/page_1");
$this->assertSession()->assertEscaped("View $escaped");
$this->assertSession()->responseNotContains("View $xss_markup");
$this->assertSession()->assertEscaped("Duplicate $escaped");
$this->assertSession()->responseNotContains("Duplicate $xss_markup");
$this->assertSession()->assertEscaped("Delete $escaped");
$this->assertSession()->responseNotContains("Delete $xss_markup");
}
}
/**
* Tests the action links on the edit display UI.
*/
public function testActionLinks(): void {
// Change the display title of a display so it contains characters that will
// be escaped when rendered.
$display_title = "'<test>'";
$this->drupalGet('admin/structure/views/view/test_display');
$display_title_path = 'admin/structure/views/nojs/display/test_display/block_1/display_title';
$this->drupalGet($display_title_path);
$this->submitForm(['display_title' => $display_title], 'Apply');
// Ensure that the title is escaped as expected.
$this->assertSession()->assertEscaped($display_title);
$this->assertSession()->responseNotContains($display_title);
// Ensure that the dropdown buttons are displayed correctly.
$this->assertSession()->buttonExists('Duplicate ' . $display_title);
$this->assertSession()->buttonExists('Delete ' . $display_title);
$this->assertSession()->buttonExists('Disable ' . $display_title);
$this->assertSession()->buttonNotExists('Enable ' . $display_title);
// Disable the display so we can test the rendering of the "Enable" button.
$this->submitForm([], 'Disable ' . $display_title);
$this->assertSession()->buttonExists('Enable ' . $display_title);
$this->assertSession()->buttonNotExists('Disable ' . $display_title);
// Ensure that the title is escaped as expected.
$this->assertSession()->assertEscaped($display_title);
$this->assertSession()->responseNotContains($display_title);
}
/**
* Tests that the override option is hidden when it's not needed.
*/
public function testHideDisplayOverride(): void {
// Test that the override option appears with two displays.
$this->drupalGet('admin/structure/views/nojs/handler/test_display/page_1/field/title');
$this->assertSession()->pageTextContains('All displays');
// Remove a display and test if the override option is hidden.
$this->drupalGet('admin/structure/views/view/test_display/edit/block_1');
$this->submitForm([], 'Delete Block');
$this->submitForm([], 'Save');
$this->drupalGet('admin/structure/views/nojs/handler/test_display/page_1/field/title');
$this->assertSession()->pageTextNotContains('All displays');
// Test that the override option is shown when default display is on.
\Drupal::configFactory()->getEditable('views.settings')->set('ui.show.default_display', TRUE)->save();
$this->drupalGet('admin/structure/views/nojs/handler/test_display/page_1/field/title');
$this->assertSession()->pageTextContains('All displays');
// Test that the override option is shown if the current display is
// overridden so that the option to revert is available.
$this->submitForm(['override[dropdown]' => 'page_1'], 'Apply');
\Drupal::configFactory()->getEditable('views.settings')->set('ui.show.default_display', FALSE)->save();
$this->drupalGet('admin/structure/views/nojs/handler/test_display/page_1/field/title');
$this->assertSession()->pageTextContains('Revert to default');
}
}

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Tests the UI for view duplicate tool.
*
* @group views_ui
*/
class DuplicateTest extends UITestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['config_translation', 'locale', 'language'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = ['views_test_config']): void {
parent::setUp($import_test_views, $modules);
$this->placeBlock('page_title_block');
}
/**
* Checks if duplicated view exists and has correct label.
*/
public function testDuplicateView(): void {
$language_manager = $this->container->get('language_manager');
ConfigurableLanguage::createFromLangcode('nl')->save();
// Create random view.
$random_view = $this->randomView();
// Add a translation to the View.
$translation = $language_manager->getLanguageConfigOverride('nl', 'views.view.' . $random_view['id']);
$translation->setData(['label' => 'NL label']);
$translation->save();
// Initialize array for duplicated view.
$view = [];
// Generate random label and id for new view.
$view['label'] = $this->randomMachineName(255);
$view['id'] = $this->randomMachineName(128);
// Duplicate view.
$this->drupalGet('admin/structure/views/view/' . $random_view['id'] . '/duplicate');
$this->submitForm($view, 'Duplicate');
// Assert that the page URL is correct.
$this->assertSession()->addressEquals('admin/structure/views/view/' . $view['id']);
// Assert that the page title is correctly displayed.
$this->assertSession()->pageTextContains($view['label']);
$copy_translation = $language_manager->getLanguageConfigOverride('nl', 'views.view.' . $view['id']);
$this->assertEquals(['label' => 'NL label'], $copy_translation->get());
}
}

View File

@@ -0,0 +1,404 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\views\Entity\View;
/**
* Tests exposed forms UI functionality.
*
* @group views_ui
* @group #slow
*/
class ExposedFormUITest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_exposed_admin_ui'];
/**
* {@inheritdoc}
*/
protected static $modules = [
'node',
'views_ui',
'block',
'taxonomy',
'field_ui',
'datetime',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Array of error message strings raised by the grouped form.
*
* @var array
*
* @see FilterPluginBase::buildGroupValidate
*/
protected $groupFormUiErrors = [];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = ['views_test_config']): void {
parent::setUp($import_test_views, $modules);
$this->drupalCreateContentType(['type' => 'article']);
$this->drupalCreateContentType(['type' => 'page']);
// Create some random nodes.
for ($i = 0; $i < 5; $i++) {
$this->drupalCreateNode();
}
// Error strings used in the grouped filter form validation.
$this->groupFormUiErrors['missing_value'] = 'A value is required if the label for this item is defined.';
$this->groupFormUiErrors['missing_title'] = 'A label is required if the value for this item is defined.';
$this->groupFormUiErrors['missing_title_empty_operator'] = 'A label is required for the specified operator.';
}
/**
* Tests the admin interface of exposed filter and sort items.
*/
public function testExposedAdminUi(): void {
$edit = [];
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
// Be sure that the button is called exposed.
$this->helperButtonHasLabel('edit-options-expose-button-button', 'Expose filter');
// The first time the filter UI is displayed, the operator and the
// value forms should be shown.
$this->assertSession()->fieldValueEquals('edit-options-operator-in', 'in');
$this->assertSession()->fieldValueEquals('edit-options-operator-not-in', 'in');
$this->assertSession()->checkboxNotChecked('edit-options-value-page');
$this->assertSession()->checkboxNotChecked('edit-options-value-article');
// Click the Expose filter button.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
$this->submitForm($edit, 'Expose filter');
// Check the label of the expose button.
$this->helperButtonHasLabel('edit-options-expose-button-button', 'Hide filter');
// After exposing the filter, Operator and Value should be still here.
$this->assertSession()->fieldValueEquals('edit-options-operator-in', 'in');
$this->assertSession()->fieldValueEquals('edit-options-operator-not-in', 'in');
$this->assertSession()->checkboxNotChecked('edit-options-value-page');
$this->assertSession()->checkboxNotChecked('edit-options-value-article');
// Check the validations of the filter handler.
$edit = [];
$edit['options[expose][identifier]'] = '';
$this->submitForm($edit, 'Apply');
$this->assertSession()->pageTextContains('The identifier is required if the filter is exposed.');
$edit = [];
$edit['options[expose][identifier]'] = 'value';
$this->submitForm($edit, 'Apply');
$this->assertSession()->pageTextContains('This identifier is not allowed.');
// Now check the sort criteria.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/sort/created');
$this->helperButtonHasLabel('edit-options-expose-button-button', 'Expose sort');
$this->assertSession()->fieldNotExists('edit-options-expose-label');
$this->assertSession()->fieldNotExists('Sort field identifier');
// Un-expose the filter.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
$this->submitForm([], 'Hide filter');
// After Un-exposing the filter, Operator and Value should be shown again.
$this->assertSession()->fieldValueEquals('edit-options-operator-in', 'in');
$this->assertSession()->fieldValueEquals('edit-options-operator-not-in', 'in');
$this->assertSession()->checkboxNotChecked('edit-options-value-page');
$this->assertSession()->checkboxNotChecked('edit-options-value-article');
// Click the Expose sort button.
$edit = [];
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/sort/created');
$this->submitForm($edit, 'Expose sort');
// Check the label of the expose button.
$this->helperButtonHasLabel('edit-options-expose-button-button', 'Hide sort');
$this->assertSession()->fieldValueEquals('edit-options-expose-label', 'Authored on');
$this->assertSession()->fieldValueEquals('Sort field identifier', 'created');
// Test adding a new exposed sort criteria.
$view_id = $this->randomView()['id'];
$this->drupalGet("admin/structure/views/nojs/add-handler/$view_id/default/sort");
$this->submitForm(['name[node_field_data.created]' => 1], 'Add and configure sort criteria');
$this->assertSession()->fieldValueEquals('options[order]', 'ASC');
// Change the order and expose the sort.
$this->submitForm(['options[order]' => 'DESC'], 'Apply');
$this->drupalGet("admin/structure/views/nojs/handler/{$view_id}/default/sort/created");
$this->submitForm([], 'Expose sort');
$this->assertSession()->fieldValueEquals('options[order]', 'DESC');
$this->assertSession()->fieldValueEquals('options[expose][label]', 'Authored on');
$this->assertSession()->fieldValueEquals('Sort field identifier', 'created');
// Change the label and try with an empty identifier.
$edit = [
'options[expose][label]' => $this->randomString(),
'options[expose][field_identifier]' => '',
];
$this->submitForm($edit, 'Apply');
$this->assertSession()->pageTextContains('Sort field identifier field is required.');
// Try with an invalid identifiers.
$edit['options[expose][field_identifier]'] = 'abc&! ###08.';
$this->submitForm($edit, 'Apply');
$this->assertSession()->pageTextContains('This identifier has illegal characters.');
$edit['options[expose][field_identifier]'] = '^abcde';
$this->submitForm($edit, 'Apply');
$this->assertSession()->pageTextContains('This identifier has illegal characters.');
// Use a valid identifier.
$edit['options[expose][field_identifier]'] = $this->randomMachineName() . '_-~.';
$this->submitForm($edit, 'Apply');
$this->submitForm([], 'Save');
// Check that the values were saved.
$display = View::load($view_id)->getDisplay('default');
$this->assertTrue($display['display_options']['sorts']['created']['exposed']);
$this->assertSame([
'label' => $edit['options[expose][label]'],
'field_identifier' => $edit['options[expose][field_identifier]'],
], $display['display_options']['sorts']['created']['expose']);
$this->assertSame('DESC', $display['display_options']['sorts']['created']['order']);
// Test the identifier uniqueness.
$this->drupalGet("admin/structure/views/nojs/handler/{$view_id}/default/sort/created_1");
$this->submitForm([], 'Expose sort');
$this->submitForm([
'options[expose][field_identifier]' => $edit['options[expose][field_identifier]'],
], 'Apply');
$this->assertSession()->pageTextContains('This identifier is already used by Content: Authored on sort handler.');
}
/**
* Tests the admin interface of exposed grouped filters.
*/
public function testGroupedFilterAdminUi(): void {
$edit = [];
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
// Click the Expose filter button.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
$this->submitForm($edit, 'Expose filter');
// Check the label of the grouped filters button.
$this->helperButtonHasLabel('edit-options-group-button-button', 'Grouped filters');
// Click the Grouped Filters button.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
$this->submitForm([], 'Grouped filters');
// After click on 'Grouped Filters', the standard operator and value should
// not be displayed.
$this->assertSession()->fieldNotExists('edit-options-operator-in');
$this->assertSession()->fieldNotExists('edit-options-operator-not-in');
$this->assertSession()->fieldNotExists('edit-options-value-page');
$this->assertSession()->fieldNotExists('edit-options-value-article');
// Check that after click on 'Grouped Filters', a new button is shown to
// add more items to the list.
$this->helperButtonHasLabel('edit-options-group-info-add-group', 'Add another item');
// Validate a single entry for a grouped filter.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
$edit = [];
$edit["options[group_info][group_items][1][title]"] = 'Is Article';
$edit["options[group_info][group_items][1][value][article]"] = 'article';
$this->submitForm($edit, 'Apply');
$this->assertSession()->addressEquals('admin/structure/views/view/test_exposed_admin_ui/edit/default');
$this->assertNoGroupedFilterErrors();
// Validate multiple entries for grouped filters.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
$edit = [];
$edit["options[group_info][group_items][1][title]"] = 'Is Article';
$edit["options[group_info][group_items][1][value][article]"] = 'article';
$edit["options[group_info][group_items][2][title]"] = 'Is Page';
$edit["options[group_info][group_items][2][value][page]"] = 'page';
$edit["options[group_info][group_items][3][title]"] = 'Is Page and Article';
$edit["options[group_info][group_items][3][value][article]"] = 'article';
$edit["options[group_info][group_items][3][value][page]"] = 'page';
$this->submitForm($edit, 'Apply');
$this->assertSession()->addressEquals('admin/structure/views/view/test_exposed_admin_ui/edit/default');
$this->assertNoGroupedFilterErrors();
// Validate an "is empty" filter -- title without value is valid.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/body_value');
$edit = [];
$edit["options[group_info][group_items][1][title]"] = 'No body';
$edit["options[group_info][group_items][1][operator]"] = 'empty';
$this->submitForm($edit, 'Apply');
$this->assertSession()->addressEquals('admin/structure/views/view/test_exposed_admin_ui/edit/default');
$this->assertNoGroupedFilterErrors();
// Ensure the string "0" can be used as a value for numeric filters.
$this->drupalGet('admin/structure/views/nojs/add-handler/test_exposed_admin_ui/default/filter');
$this->submitForm(['name[node_field_data.nid]' => TRUE], 'Add and configure filter criteria');
$this->submitForm([], 'Expose filter');
$this->submitForm([], 'Grouped filters');
$edit = [];
$edit['options[group_info][group_items][1][title]'] = 'Testing zero';
$edit['options[group_info][group_items][1][operator]'] = '>';
$edit['options[group_info][group_items][1][value][value]'] = '0';
$this->submitForm($edit, 'Apply');
$this->assertSession()->addressEquals('admin/structure/views/view/test_exposed_admin_ui/edit/default');
$this->assertNoGroupedFilterErrors();
// Ensure "between" filters validate correctly.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/nid');
$edit['options[group_info][group_items][1][title]'] = 'ID between test';
$edit['options[group_info][group_items][1][operator]'] = 'between';
$edit['options[group_info][group_items][1][value][min]'] = '0';
$edit['options[group_info][group_items][1][value][max]'] = '10';
$this->submitForm($edit, 'Apply');
$this->assertSession()->addressEquals('admin/structure/views/view/test_exposed_admin_ui/edit/default');
$this->assertNoGroupedFilterErrors();
}
public function testGroupedFilterAdminUiErrors(): void {
// Select the empty operator without a title specified.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/body_value');
$edit = [];
$edit["options[group_info][group_items][1][title]"] = '';
$edit["options[group_info][group_items][1][operator]"] = 'empty';
$this->submitForm($edit, 'Apply');
$this->assertSession()->pageTextContains($this->groupFormUiErrors['missing_title_empty_operator']);
// Specify a title without a value.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
$this->submitForm([], 'Expose filter');
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
$this->submitForm([], 'Grouped filters');
$edit = [];
$edit["options[group_info][group_items][1][title]"] = 'Is Article';
$this->submitForm($edit, 'Apply');
$this->assertSession()->pageTextContains($this->groupFormUiErrors['missing_value']);
// Specify a value without a title.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
$edit = [];
$edit["options[group_info][group_items][1][title]"] = '';
$edit["options[group_info][group_items][1][value][article]"] = 'article';
$this->submitForm($edit, 'Apply');
$this->assertSession()->pageTextContains($this->groupFormUiErrors['missing_title']);
}
/**
* Asserts that there are no Grouped Filters errors.
*
* @param string $message
* The assert message.
*
* @internal
*/
protected function assertNoGroupedFilterErrors(string $message = ''): void {
foreach ($this->groupFormUiErrors as $error) {
if (empty($message)) {
$this->assertSession()->responseNotContains($error);
}
}
}
/**
* Tests the configuration of grouped exposed filters.
*/
public function testExposedGroupedFilter(): void {
// Click the Expose filter button.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
$this->submitForm([], 'Expose filter');
// Select 'Grouped filters' radio button.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
$this->submitForm([], 'Grouped filters');
// Add 3 groupings.
$edit = [
'options[group_button][radios][radios]' => 1,
'options[group_info][group_items][1][title]' => '1st',
'options[group_info][group_items][1][value][all]' => 'all',
'options[group_info][group_items][2][title]' => '2nd',
'options[group_info][group_items][2][value][article]' => 'article',
'options[group_info][group_items][3][title]' => '3rd',
'options[group_info][group_items][3][value][page]' => 'page',
'options[group_info][default_group]' => '3',
];
// Apply the filter settings.
$this->submitForm($edit, 'Apply');
// Check that the view is saved without errors.
$this->submitForm([], 'Save');
$this->assertSession()->statusCodeEquals(200);
// Check the default filter value.
$this->drupalGet('test_exposed_admin_ui');
$this->assertSession()->fieldValueEquals('type', '3');
// Enable "Allow multiple selections" option and set a default group.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/type');
$edit['options[group_info][multiple]'] = 1;
$edit['options[group_info][default_group_multiple][1]'] = 1;
$this->submitForm($edit, 'Apply');
$this->submitForm([], 'Save');
// Check the default filter values again.
$this->drupalGet('test_exposed_admin_ui');
$this->assertSession()->checkboxChecked('type[1]');
$this->assertSession()->checkboxNotChecked('type[2]');
$this->assertSession()->checkboxNotChecked('type[3]');
// Click the Expose filter button.
$this->drupalGet('admin/structure/views/nojs/add-handler/test_exposed_admin_ui/default/filter');
$this->submitForm(['name[node_field_data.status]' => 1], 'Add and configure filter criteria');
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/status');
$this->submitForm([], 'Expose filter');
// Select 'Grouped filters' radio button.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/status');
$this->submitForm([], 'Grouped filters');
// Add 3 groupings.
$edit = [
'options[group_button][radios][radios]' => 1,
'options[group_info][group_items][1][title]' => 'Any',
'options[group_info][group_items][1][value]' => 'All',
'options[group_info][group_items][2][title]' => 'Published',
'options[group_info][group_items][2][value]' => 1,
'options[group_info][group_items][3][title]' => 'Unpublished',
'options[group_info][group_items][3][value]' => 0,
'options[group_info][default_group]' => 2,
];
// Apply the filter settings.
$this->submitForm($edit, 'Apply');
// Check that the view is saved without errors.
$this->submitForm([], 'Save');
$this->assertSession()->statusCodeEquals(200);
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/status');
// Assert the same settings defined before still are there.
$this->assertSession()->checkboxChecked('edit-options-group-info-group-items-1-value-all');
$this->assertSession()->checkboxChecked('edit-options-group-info-group-items-2-value-1');
$this->assertSession()->checkboxChecked('edit-options-group-info-group-items-3-value-0');
// Check the default filter value.
$this->drupalGet('test_exposed_admin_ui');
$this->assertSession()->fieldValueEquals('status', '2');
// Enable "Allow multiple selections" option and set a default group.
$this->drupalGet('admin/structure/views/nojs/handler/test_exposed_admin_ui/default/filter/status');
$edit['options[group_info][multiple]'] = 1;
$edit['options[group_info][default_group_multiple][3]'] = 1;
$this->submitForm($edit, 'Apply');
$this->submitForm([], 'Save');
// Check the default filter value again.
$this->drupalGet('test_exposed_admin_ui');
$this->assertSession()->checkboxNotChecked('status[1]');
$this->assertSession()->checkboxNotChecked('status[2]');
$this->assertSession()->checkboxChecked('status[3]');
}
}

View File

@@ -0,0 +1,107 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\Component\Serialization\Json;
use Drupal\views\Views;
/**
* Tests the UI of field handlers.
*
* @group views_ui
* @see \Drupal\views\Plugin\views\field\FieldPluginBase
*/
class FieldUITest extends UITestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view'];
/**
* Tests the UI of field handlers.
*/
public function testFieldUI(): void {
// Ensure the field is not marked as hidden on the first run.
$this->drupalGet('admin/structure/views/view/test_view/edit');
$this->assertSession()->pageTextContains('Views test: Name');
$this->assertSession()->pageTextNotContains('Views test: Name [hidden]');
// Hides the field and check whether the hidden label is appended.
$edit_handler_url = 'admin/structure/views/nojs/handler/test_view/default/field/name';
$this->drupalGet($edit_handler_url);
$this->submitForm(['options[exclude]' => TRUE], 'Apply');
$this->assertSession()->pageTextContains('Views test: Name [hidden]');
// Ensure that the expected tokens appear in the UI.
$edit_handler_url = 'admin/structure/views/nojs/handler/test_view/default/field/age';
$this->drupalGet($edit_handler_url);
$xpath = '//details[@id="edit-options-alter-help"]/ul/li';
$this->assertSession()->elementTextEquals('xpath', $xpath, '{{ age }} == Age');
$edit_handler_url = 'admin/structure/views/nojs/handler/test_view/default/field/id';
$this->drupalGet($edit_handler_url);
$this->assertSession()->elementTextEquals('xpath', "{$xpath}[1]", '{{ age }} == Age');
$this->assertSession()->elementTextEquals('xpath', "{$xpath}[2]", '{{ id }} == ID');
$edit_handler_url = 'admin/structure/views/nojs/handler/test_view/default/field/name';
$this->drupalGet($edit_handler_url);
$this->assertSession()->elementTextEquals('xpath', "{$xpath}[1]", '{{ age }} == Age');
$this->assertSession()->elementTextEquals('xpath', "{$xpath}[2]", '{{ id }} == ID');
$this->assertSession()->elementTextEquals('xpath', "{$xpath}[3]", '{{ name }} == Name');
$this->assertSession()->elementNotExists('xpath', '//details[@id="edit-options-more"]');
// Ensure that dialog titles are not escaped.
$edit_groupby_url = 'admin/structure/views/nojs/handler/test_view/default/field/name';
$this->assertSession()->linkByHrefNotExists($edit_groupby_url, 0, 'No aggregation link found.');
// Enable aggregation on the view.
$edit = [
'group_by' => TRUE,
];
$this->drupalGet('/admin/structure/views/nojs/display/test_view/default/group_by');
$this->submitForm($edit, 'Apply');
$this->assertSession()->linkByHrefExists($edit_groupby_url, 0, 'Aggregation link found.');
$edit_handler_url = '/admin/structure/views/ajax/handler-group/test_view/default/field/name';
$this->drupalGet($edit_handler_url);
$data = Json::decode($this->getSession()->getPage()->getContent());
$this->assertEquals('Configure aggregation settings for field Views test: Name', $data[3]['dialogOptions']['title']);
}
/**
* Tests the field labels.
*/
public function testFieldLabel(): void {
// Create a view with unformatted style and make sure the fields have no
// labels by default.
$view = [];
$view['label'] = $this->randomMachineName(16);
$view['id'] = $this->randomMachineName(16);
$view['description'] = $this->randomMachineName(16);
$view['show[wizard_key]'] = 'node';
$view['page[create]'] = TRUE;
$view['page[style][style_plugin]'] = 'default';
$view['page[title]'] = $this->randomMachineName(16);
$view['page[path]'] = $view['id'];
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
$view = Views::getView($view['id']);
$view->initHandlers();
$this->assertEquals('', $view->field['title']->options['label'], 'The field label for normal styles are empty.');
}
}

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
/**
* Tests the boolean filter UI.
*
* @group views_ui
* @see \Drupal\views\Plugin\views\filter\BooleanOperator
*/
class FilterBooleanWebTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the filter boolean UI.
*/
public function testFilterBooleanUI(): void {
$this->drupalGet('admin/structure/views/nojs/add-handler/test_view/default/filter');
$this->submitForm(['name[views_test_data.status]' => TRUE], 'Add and configure filter criteria');
// Check the field widget label. 'title' should be used as a fallback.
$result = $this->cssSelect('#edit-options-value--wrapper legend span');
$this->assertEquals('Status', $result[0]->getHtml());
// Ensure that the operator and the filter value are displayed using correct
// layout.
$this->assertSession()->elementExists('css', '.views-left-30 .form-item-options-operator');
$this->assertSession()->elementExists('css', '.views-right-70 .form-item-options-value');
$this->submitForm([], 'Expose filter');
$this->submitForm([], 'Grouped filters');
$edit = [];
$edit['options[group_info][group_items][1][title]'] = 'Published';
$edit['options[group_info][group_items][1][operator]'] = '=';
$edit['options[group_info][group_items][1][value]'] = 1;
$edit['options[group_info][group_items][2][title]'] = 'Not published';
$edit['options[group_info][group_items][2][operator]'] = '=';
$edit['options[group_info][group_items][2][value]'] = 0;
$edit['options[group_info][group_items][3][title]'] = 'Not published2';
$edit['options[group_info][group_items][3][operator]'] = '!=';
$edit['options[group_info][group_items][3][value]'] = 1;
$this->submitForm($edit, 'Apply');
$this->drupalGet('admin/structure/views/nojs/handler/test_view/default/filter/status');
$result = $this->xpath('//input[@name="options[group_info][group_items][1][value]"]');
$this->assertEquals('checked', $result[1]->getAttribute('checked'));
$result = $this->xpath('//input[@name="options[group_info][group_items][2][value]"]');
$this->assertEquals('checked', $result[2]->getAttribute('checked'));
$result = $this->xpath('//input[@name="options[group_info][group_items][3][value]"]');
$this->assertEquals('checked', $result[1]->getAttribute('checked'));
// Test that there is a remove link for each group.
$this->assertCount(3, $this->cssSelect('a.views-remove-link'));
// Test selecting a default and removing an item.
$edit = [];
$edit['options[group_info][default_group]'] = 2;
$edit['options[group_info][group_items][3][remove]'] = 1;
$this->submitForm($edit, 'Apply');
$this->drupalGet('admin/structure/views/nojs/handler/test_view/default/filter/status');
$this->assertSession()->fieldValueEquals('options[group_info][default_group]', 2);
$this->assertSession()->fieldNotExists('options[group_info][group_items][3][remove]');
}
}

View File

@@ -0,0 +1,152 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\Tests\SchemaCheckTestTrait;
/**
* Tests the numeric filter UI.
*
* @group views_ui
* @see \Drupal\views\Plugin\views\filter\NumericFilter
*/
class FilterNumericWebTest extends UITestBase {
use SchemaCheckTestTrait;
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the filter numeric UI.
*/
public function testFilterNumericUI(): void {
// Add a page display to the test_view to be able to test the filtering.
$path = 'test_view-path';
$this->drupalGet('admin/structure/views/view/test_view/edit');
$this->submitForm([], 'Add Page');
$this->drupalGet('admin/structure/views/nojs/display/test_view/page_1/path');
$this->submitForm(['path' => $path], 'Apply');
$this->submitForm([], 'Save');
$this->drupalGet('admin/structure/views/nojs/add-handler/test_view/default/filter');
$this->submitForm(['name[views_test_data.age]' => TRUE], 'Add and configure filter criteria');
$this->submitForm([], 'Expose filter');
$this->submitForm([], 'Grouped filters');
$edit = [];
$edit['options[group_info][group_items][1][title]'] = 'Old';
$edit['options[group_info][group_items][1][operator]'] = '>';
$edit['options[group_info][group_items][1][value][value]'] = 27;
$edit['options[group_info][group_items][2][title]'] = 'Young';
$edit['options[group_info][group_items][2][operator]'] = '<=';
$edit['options[group_info][group_items][2][value][value]'] = 27;
$edit['options[group_info][group_items][3][title]'] = 'From 26 to 28';
$edit['options[group_info][group_items][3][operator]'] = 'between';
$edit['options[group_info][group_items][3][value][min]'] = 26;
$edit['options[group_info][group_items][3][value][max]'] = 28;
$this->submitForm($edit, 'Apply');
$this->drupalGet('admin/structure/views/nojs/handler/test_view/default/filter/age');
foreach ($edit as $name => $value) {
$this->assertSession()->fieldValueEquals($name, $value);
}
$this->drupalGet('admin/structure/views/view/test_view');
$this->submitForm([], 'Save');
$this->assertConfigSchemaByName('views.view.test_view');
// Test that the exposed filter works as expected.
$this->drupalGet('test_view-path');
$this->assertSession()->pageTextContains('John');
$this->assertSession()->pageTextContains('Paul');
$this->assertSession()->pageTextContains('Ringo');
$this->assertSession()->pageTextContains('George');
$this->assertSession()->pageTextContains('Meredith');
$this->submitForm(['age' => '2'], 'Apply');
$this->assertSession()->pageTextContains('John');
$this->assertSession()->pageTextContains('Paul');
$this->assertSession()->pageTextNotContains('Ringo');
$this->assertSession()->pageTextContains('George');
$this->assertSession()->pageTextNotContains('Meredith');
// Change the filter to a single filter to test the schema when the operator
// is not exposed.
$this->drupalGet('admin/structure/views/nojs/handler/test_view/default/filter/age');
$this->submitForm([], 'Single filter');
$edit = [];
$edit['options[value][value]'] = 25;
$this->submitForm($edit, 'Apply');
$this->drupalGet('admin/structure/views/view/test_view');
$this->submitForm([], 'Save');
$this->assertConfigSchemaByName('views.view.test_view');
// Test that the filter works as expected.
$this->drupalGet('test_view-path');
$this->assertSession()->pageTextContains('John');
$this->assertSession()->pageTextNotContains('Paul');
$this->assertSession()->pageTextNotContains('Ringo');
$this->assertSession()->pageTextNotContains('George');
$this->assertSession()->pageTextNotContains('Meredith');
$this->submitForm(['age' => '26'], 'Apply');
$this->assertSession()->pageTextNotContains('John');
$this->assertSession()->pageTextContains('Paul');
$this->assertSession()->pageTextNotContains('Ringo');
$this->assertSession()->pageTextNotContains('George');
$this->assertSession()->pageTextNotContains('Meredith');
// Change the filter to a 'between' filter to test if the label and
// description are set for the 'minimum' filter element.
$this->drupalGet('admin/structure/views/nojs/handler/test_view/default/filter/age');
$edit = [];
$edit['options[expose][label]'] = 'Age between';
$edit['options[expose][description]'] = 'Description of the exposed filter';
$edit['options[operator]'] = 'between';
$edit['options[value][min]'] = 26;
$edit['options[value][max]'] = 28;
$this->submitForm($edit, 'Apply');
$this->drupalGet('admin/structure/views/view/test_view');
$this->submitForm([], 'Save');
$this->assertConfigSchemaByName('views.view.test_view');
$this->submitForm([], 'Update preview');
// Check the field (wrapper) label.
$this->assertSession()->elementTextContains('css', 'fieldset#edit-age-wrapper legend', 'Age between');
// Check the min/max labels.
$this->assertSession()->elementsCount('xpath', '//fieldset[contains(@id, "edit-age-wrapper")]//label[contains(@for, "edit-age-min") and contains(text(), "Min")]', 1);
$this->assertSession()->elementsCount('xpath', '//fieldset[contains(@id, "edit-age-wrapper")]//label[contains(@for, "edit-age-max") and contains(text(), "Max")]', 1);
// Check that the description is shown in the right place.
$this->assertEquals('Description of the exposed filter', trim($this->cssSelect('#edit-age-wrapper--description')[0]->getText()));
// Change to an operation that only requires one form element ('>').
$this->drupalGet('admin/structure/views/nojs/handler/test_view/default/filter/age');
$edit = [];
$edit['options[expose][label]'] = 'Age greater than';
$edit['options[expose][description]'] = 'Description of the exposed filter';
$edit['options[operator]'] = '>';
$edit['options[value][value]'] = 1000;
$this->submitForm($edit, 'Apply');
$this->drupalGet('admin/structure/views/view/test_view');
$this->submitForm([], 'Save');
$this->assertConfigSchemaByName('views.view.test_view');
$this->submitForm([], 'Update preview');
// Make sure the label is visible and that there's no fieldset wrapper.
$this->assertSession()->elementsCount('xpath', '//label[contains(@for, "edit-age") and contains(text(), "Age greater than")]', 1);
$this->assertSession()->elementNotExists('xpath', '//fieldset[contains(@id, "edit-age-wrapper")]');
}
}

View File

@@ -0,0 +1,142 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
/**
* Tests for the filters from the UI.
*
* @group views_ui
*/
class FilterUITest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_filter_in_operator_ui', 'test_filter_groups'];
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['views_ui', 'node'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = ['views_test_config']): void {
parent::setUp($import_test_views, $modules);
$this->drupalCreateContentType(['type' => 'page']);
}
/**
* Tests that an option for a filter is saved as expected from the UI.
*/
public function testFilterInOperatorUi(): void {
$admin_user = $this->drupalCreateUser([
'administer views',
'administer site configuration',
]);
$this->drupalLogin($admin_user);
$path = 'admin/structure/views/nojs/handler/test_filter_in_operator_ui/default/filter/type';
$this->drupalGet($path);
// Verifies that "Limit list to selected items" option is not selected.
$this->assertSession()->fieldValueEquals('options[expose][reduce]', FALSE);
// Select "Limit list to selected items" option and apply.
$edit = [
'options[expose][reduce]' => TRUE,
];
$this->drupalGet($path);
$this->submitForm($edit, 'Apply');
// Verifies that the option was saved as expected.
$this->drupalGet($path);
$this->assertSession()->fieldValueEquals('options[expose][reduce]', TRUE);
}
/**
* Tests the filters from the UI.
*/
public function testFiltersUI(): void {
$admin_user = $this->drupalCreateUser([
'administer views',
'administer site configuration',
]);
$this->drupalLogin($admin_user);
$this->drupalGet('admin/structure/views/view/test_filter_groups');
$this->assertSession()->linkExists('Content: ID (= 1)', 0, 'Content: ID (= 1) link appears correctly.');
// Tests that we can create a new filter group from UI.
$this->drupalGet('admin/structure/views/nojs/rearrange-filter/test_filter_groups/page');
$this->assertSession()->elementNotExists('xpath', '//span[text()="Group 3"]');
// Create 2 new groups.
$this->submitForm([], 'Create new filter group');
$this->submitForm([], 'Create new filter group');
// Remove the new group 3.
$this->submitForm([], 'Remove group 3');
// Verify that the group 4 is now named as 3.
$this->assertSession()->responseContains('<span>Group 3</span>');
// Remove the group 3 again.
$this->submitForm([], 'Remove group 3');
// Group 3 now does not exist.
$this->assertSession()->elementNotExists('xpath', '//span[text()="Group 3"]');
}
/**
* Tests the identifier settings and restrictions.
*/
public function testFilterIdentifier(): void {
$admin_user = $this->drupalCreateUser([
'administer views',
'administer site configuration',
]);
$this->drupalLogin($admin_user);
$path = 'admin/structure/views/nojs/handler/test_filter_in_operator_ui/default/filter/type';
// Set an empty identifier.
$edit = [
'options[expose][identifier]' => '',
];
$this->drupalGet($path);
$this->submitForm($edit, 'Apply');
$this->assertSession()->pageTextContains('The identifier is required if the filter is exposed.');
// Set the identifier to 'value'.
$edit = [
'options[expose][identifier]' => 'value',
];
$this->drupalGet($path);
$this->submitForm($edit, 'Apply');
$this->assertSession()->pageTextContains('This identifier is not allowed.');
// Try a few restricted values for the identifier.
foreach (['value value', 'value^value'] as $identifier) {
$edit = [
'options[expose][identifier]' => $identifier,
];
$this->drupalGet($path);
$this->submitForm($edit, 'Apply');
$this->assertSession()->pageTextContains('This identifier has illegal characters.');
}
}
}

View File

@@ -0,0 +1,14 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\Tests\system\Functional\Module\GenericModuleTestBase;
/**
* Generic module test for views_ui.
*
* @group views_ui
*/
class GenericTest extends GenericModuleTestBase {}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
/**
* Tests UI of aggregate functionality..
*
* @group views_ui
*/
class GroupByTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_views_groupby_save'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests whether basic saving works.
*
* @todo This should check the change of the settings as well.
*/
public function testGroupBySave(): void {
$this->drupalGet('admin/structure/views/view/test_views_groupby_save/edit');
$edit_groupby_url = 'admin/structure/views/nojs/handler-group/test_views_groupby_save/default/field/id';
$this->assertSession()->linkByHrefNotExists($edit_groupby_url, 0, 'No aggregation link found.');
// Enable aggregation on the view.
$edit = [
'group_by' => TRUE,
];
$this->drupalGet('admin/structure/views/nojs/display/test_views_groupby_save/default/group_by');
$this->submitForm($edit, 'Apply');
$this->assertSession()->linkByHrefExists($edit_groupby_url, 0, 'Aggregation link found.');
// Change the groupby type in the UI.
$this->drupalGet($edit_groupby_url);
$this->submitForm(['options[group_type]' => 'count'], 'Apply');
$this->assertSession()->linkExists('COUNT(Views test: ID)', 0, 'The count setting is displayed in the UI');
$this->submitForm([], 'Save');
$view = $this->container->get('entity_type.manager')->getStorage('view')->load('test_views_groupby_save');
$display = $view->getDisplay('default');
$this->assertTrue($display['display_options']['group_by'], 'The groupby setting was saved on the view.');
$this->assertEquals('count', $display['display_options']['fields']['id']['group_type'], 'Count groupby_type was saved on the view.');
}
}

View File

@@ -0,0 +1,307 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\views\Tests\ViewTestData;
use Drupal\views\ViewExecutable;
/**
* Tests handler UI for views.
*
* @group views_ui
* @group #slow
* @see \Drupal\views\Plugin\views\HandlerBase
*/
class HandlerTest extends UITestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['node_test_views'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view_empty', 'test_view_broken', 'node', 'test_node_view'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = ['views_test_config']): void {
parent::setUp($import_test_views, $modules);
$this->placeBlock('page_title_block');
ViewTestData::createTestViews(static::class, ['node_test_views']);
}
/**
* Overrides \Drupal\views\Tests\ViewTestBase::schemaDefinition().
*
* Adds a uid column to test the relationships.
*
* @internal
*/
protected function schemaDefinition() {
$schema = parent::schemaDefinition();
$schema['views_test_data']['fields']['uid'] = [
'description' => "The {users}.uid of the author of the beatle entry.",
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'default' => 0,
];
return $schema;
}
/**
* Overrides \Drupal\views\Tests\ViewTestBase::viewsData().
*
* Adds:
* - a relationship for the uid column.
* - a dummy field with no help text.
*/
protected function viewsData() {
$data = parent::viewsData();
$data['views_test_data']['uid'] = [
'title' => 'UID',
'help' => 'The test data UID',
'relationship' => [
'id' => 'standard',
'base' => 'users_field_data',
'base field' => 'uid',
],
];
// Create a dummy field with no help text.
$data['views_test_data']['no_help'] = $data['views_test_data']['name'];
$data['views_test_data']['no_help']['field']['title'] = 'No help';
$data['views_test_data']['no_help']['field']['real field'] = 'name';
unset($data['views_test_data']['no_help']['help']);
return $data;
}
/**
* Tests UI CRUD.
*/
public function testUiCrud(): void {
$handler_types = ViewExecutable::getHandlerTypes();
foreach ($handler_types as $type => $type_info) {
// Test adding handlers.
$add_handler_url = "admin/structure/views/nojs/add-handler/test_view_empty/default/$type";
// Area handler types need to use a different handler.
if (in_array($type, ['header', 'footer', 'empty'])) {
$this->drupalGet($add_handler_url);
$this->submitForm([
'name[views.area]' => TRUE,
], 'Add and configure ' . $type_info['ltitle']);
$id = 'area';
$edit_handler_url = "admin/structure/views/nojs/handler/test_view_empty/default/$type/$id";
}
elseif ($type == 'relationship') {
$this->drupalGet($add_handler_url);
$this->submitForm([
'name[views_test_data.uid]' => TRUE,
], 'Add and configure ' . $type_info['ltitle']);
$id = 'uid';
$edit_handler_url = "admin/structure/views/nojs/handler/test_view_empty/default/$type/$id";
}
else {
$this->drupalGet($add_handler_url);
$this->submitForm([
'name[views_test_data.job]' => TRUE,
], 'Add and configure ' . $type_info['ltitle']);
$id = 'job';
$edit_handler_url = "admin/structure/views/nojs/handler/test_view_empty/default/$type/$id";
}
// Verify that the user got redirected to the handler edit form.
$this->assertSession()->addressEquals($edit_handler_url);
$random_label = $this->randomMachineName();
$this->submitForm(['options[admin_label]' => $random_label], 'Apply');
// Verify that the user got redirected to the views edit form.
$this->assertSession()->addressEquals('admin/structure/views/view/test_view_empty/edit/default');
$this->assertSession()->linkByHrefExists($edit_handler_url, 0, 'The handler edit link appears in the UI.');
// Test that the handler edit link has the right label.
$this->assertSession()->elementExists('xpath', "//a[starts-with(normalize-space(text()), '{$random_label}')]");
// Save the view and have a look whether the handler was added as expected.
$this->submitForm([], 'Save');
$view = $this->container->get('entity_type.manager')->getStorage('view')->load('test_view_empty');
$display = $view->getDisplay('default');
$this->assertTrue(isset($display['display_options'][$type_info['plural']][$id]), 'Ensure the field was added to the view itself.');
// Remove the item and check that it's removed
$this->drupalGet($edit_handler_url);
$this->submitForm([], 'Remove');
$this->assertSession()->linkByHrefNotExists($edit_handler_url, 0, 'The handler edit link does not appears in the UI after removing.');
$this->submitForm([], 'Save');
$view = $this->container->get('entity_type.manager')->getStorage('view')->load('test_view_empty');
$display = $view->getDisplay('default');
$this->assertFalse(isset($display['display_options'][$type_info['plural']][$id]), 'Ensure the field was removed from the view itself.');
}
// Test adding a field of the user table using the uid relationship.
$type_info = $handler_types['relationship'];
$add_handler_url = "admin/structure/views/nojs/add-handler/test_view_empty/default/relationship";
$this->drupalGet($add_handler_url);
$this->submitForm([
'name[views_test_data.uid]' => TRUE,
], 'Add and configure ' . $type_info['ltitle']);
$add_handler_url = "admin/structure/views/nojs/add-handler/test_view_empty/default/field";
$type_info = $handler_types['field'];
$this->drupalGet($add_handler_url);
$this->submitForm([
'name[users_field_data.name]' => TRUE,
], 'Add and configure ' . $type_info['ltitle']);
$id = 'name';
$edit_handler_url = "admin/structure/views/nojs/handler/test_view_empty/default/field/$id";
// Verify that the user got redirected to the handler edit form.
$this->assertSession()->addressEquals($edit_handler_url);
$this->assertSession()->fieldValueEquals('options[relationship]', 'uid');
$this->submitForm([], 'Apply');
$this->submitForm([], 'Save');
$view = $this->container->get('entity_type.manager')->getStorage('view')->load('test_view_empty');
$display = $view->getDisplay('default');
$this->assertTrue(isset($display['display_options'][$type_info['plural']][$id]), 'Ensure the field was added to the view itself.');
}
/**
* Tests escaping of field labels in help text.
*/
public function testHandlerHelpEscaping(): void {
// Setup a field with two instances using a different label.
// Ensure that the label is escaped properly.
$this->drupalCreateContentType(['type' => 'article']);
$this->drupalCreateContentType(['type' => 'page']);
FieldStorageConfig::create([
'field_name' => 'field_test',
'entity_type' => 'node',
'type' => 'string',
])->save();
FieldConfig::create([
'field_name' => 'field_test',
'entity_type' => 'node',
'bundle' => 'page',
'label' => 'The giraffe" label',
])->save();
FieldConfig::create([
'field_name' => 'field_test',
'entity_type' => 'node',
'bundle' => 'article',
'label' => 'The <em>giraffe"</em> label <script>alert("the return of the xss")</script>',
])->save();
$this->drupalGet('admin/structure/views/nojs/add-handler/content/default/field');
$this->assertSession()->assertEscaped('The <em>giraffe"</em> label <script>alert("the return of the xss")</script>');
$this->assertSession()->assertEscaped('Appears in: page, article. Also known as: Content: The giraffe" label');
}
/**
* Tests broken handlers.
*/
public function testBrokenHandlers(): void {
$handler_types = ViewExecutable::getHandlerTypes();
foreach ($handler_types as $type => $type_info) {
$this->drupalGet('admin/structure/views/view/test_view_broken/edit');
$href = "admin/structure/views/nojs/handler/test_view_broken/default/$type/id_broken";
$text = 'Broken/missing handler';
// Test that the handler edit link is present.
$this->assertSession()->elementsCount('xpath', "//a[contains(@href, '{$href}')]", 1);
$result = $this->assertSession()->elementTextEquals('xpath', "//a[contains(@href, '{$href}')]", $text);
$this->drupalGet($href);
$this->assertSession()->elementTextContains('xpath', '//h1', $text);
$original_configuration = [
'field' => 'id_broken',
'id' => 'id_broken',
'relationship' => 'none',
'table' => 'views_test_data',
'plugin_id' => 'numeric',
];
foreach ($original_configuration as $key => $value) {
$this->assertSession()->pageTextContains($key . ': ' . $value);
}
}
}
/**
* Ensures that neither node type or node ID appears multiple times.
*
* @see \Drupal\views\EntityViewsData
*/
public function testNoDuplicateFields(): void {
$handler_types = ['field', 'filter', 'sort', 'argument'];
foreach ($handler_types as $handler_type) {
$add_handler_url = 'admin/structure/views/nojs/add-handler/test_node_view/default/' . $handler_type;
$this->drupalGet($add_handler_url);
$this->assertNoDuplicateField('ID', 'Content');
$this->assertNoDuplicateField('ID', 'Content revision');
$this->assertNoDuplicateField('Content type', 'Content');
$this->assertNoDuplicateField('UUID', 'Content');
$this->assertNoDuplicateField('Revision ID', 'Content');
$this->assertNoDuplicateField('Revision ID', 'Content revision');
}
}
/**
* Ensures that no missing help text is shown.
*
* @see \Drupal\views\EntityViewsData
*/
public function testErrorMissingHelp(): void {
// Test that the error message is not shown for entity fields but an empty
// description field is shown instead.
$this->drupalGet('admin/structure/views/nojs/add-handler/test_node_view/default/field');
$this->assertSession()->pageTextNotContains('Error: missing help');
$this->assertSession()->responseContains('<td class="description"></td>');
// Test that no error message is shown for other fields.
$this->drupalGet('admin/structure/views/nojs/add-handler/test_view_empty/default/field');
$this->assertSession()->pageTextNotContains('Error: missing help');
}
/**
* Asserts that fields only appear once.
*
* @param string $field_name
* The field name.
* @param string $entity_type
* The entity type to which the field belongs.
*
* @internal
*/
public function assertNoDuplicateField(string $field_name, string $entity_type): void {
$this->assertSession()->elementsCount('xpath', '//td[.="' . $entity_type . '"]/preceding-sibling::td[@class="title" and .="' . $field_name . '"]', 1);
}
}

View File

@@ -0,0 +1,61 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
/**
* Tests configuration schema against new views.
*
* @group views_ui
*/
class NewViewConfigSchemaTest extends UITestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'views_ui',
'node',
'comment',
'file',
'taxonomy',
'dblog',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests creating brand new views.
*/
public function testNewViews(): void {
$this->drupalLogin($this->drupalCreateUser(['administer views']));
// Create views with all core Views wizards.
$wizards = [
// Wizard with their own classes.
'node',
'node_revision',
'users',
'comment',
'file_managed',
'taxonomy_term',
'watchdog',
];
foreach ($wizards as $wizard_key) {
$edit = [];
$edit['label'] = $this->randomString();
$edit['id'] = $this->randomMachineName();
$edit['show[wizard_key]'] = $wizard_key;
$edit['description'] = $this->randomString();
$this->drupalGet('admin/structure/views/add');
$this->submitForm($edit, 'Save and edit');
}
}
}

View File

@@ -0,0 +1,225 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
/**
* Tests that displays can be correctly overridden via the user interface.
*
* @group views_ui
* @group #slow
*/
class OverrideDisplaysTest extends UITestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = ['views_test_config']): void {
parent::setUp($import_test_views, $modules);
$this->drupalPlaceBlock('page_title_block');
}
/**
* Tests that displays can be overridden via the UI.
*/
public function testOverrideDisplays(): void {
// Create a basic view that shows all content, with a page and a block
// display.
$view['label'] = $this->randomMachineName(16);
$view['id'] = $this->randomMachineName(16);
$view['page[create]'] = 1;
$view['page[path]'] = $this->randomMachineName(16);
$view['block[create]'] = 1;
$view_path = $view['page[path]'];
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
// Configure its title. Since the page and block both started off with the
// same (empty) title in the views wizard, we expect the wizard to have set
// things up so that they both inherit from the default display, and we
// therefore only need to change that to have it take effect for both.
$edit = [];
$edit['title'] = $original_title = $this->randomMachineName(16);
$edit['override[dropdown]'] = 'default';
$this->drupalGet("admin/structure/views/nojs/display/{$view['id']}/page_1/title");
$this->submitForm($edit, 'Apply');
$this->drupalGet("admin/structure/views/view/{$view['id']}/edit/page_1");
$this->submitForm([], 'Save');
// Add a node that will appear in the view, so that the block will actually
// be displayed.
$this->drupalCreateContentType(['type' => 'page']);
$this->drupalCreateNode();
// Make sure the title appears in the page.
$this->drupalGet($view_path);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains($original_title);
// Confirm that the view block is available in the block administration UI.
$this->drupalGet('admin/structure/block/list/' . $this->config('system.theme')->get('default'));
$this->clickLink('Place block');
$this->assertSession()->pageTextContains($view['label']);
// Place the block.
$this->container->get('plugin.manager.block')->clearCachedDefinitions();
$this->drupalPlaceBlock("views_block:{$view['id']}-block_1");
// Make sure the title appears in the block.
$this->drupalGet('');
$this->assertSession()->pageTextContains($original_title);
// Change the title for the page display only, and make sure that the
// original title still appears on the page.
$edit = [];
$edit['title'] = $new_title = $this->randomMachineName(16);
$edit['override[dropdown]'] = 'page_1';
$this->drupalGet("admin/structure/views/nojs/display/{$view['id']}/page_1/title");
$this->submitForm($edit, 'Apply');
$this->drupalGet("admin/structure/views/view/{$view['id']}/edit/page_1");
$this->submitForm([], 'Save');
$this->drupalGet($view_path);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains($new_title);
$this->assertSession()->pageTextContains($original_title);
}
/**
* Tests that the wizard correctly sets up default and overridden displays.
*/
public function testWizardMixedDefaultOverriddenDisplays(): void {
// Create a basic view with a page, block, and feed. Give the page and feed
// identical titles, but give the block a different one, so we expect the
// page and feed to inherit their titles from the default display, but the
// block to override it.
$view['label'] = $this->randomMachineName(16);
$view['id'] = $this->randomMachineName(16);
$view['page[create]'] = 1;
$view['page[title]'] = $this->randomMachineName(16);
$view['page[path]'] = $this->randomMachineName(16);
$view['page[feed]'] = 1;
$view['page[feed_properties][path]'] = $this->randomMachineName(16);
$view['block[create]'] = 1;
$view['block[title]'] = $this->randomMachineName(16);
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
// Add a node that will appear in the view, so that the block will actually
// be displayed.
$this->drupalCreateContentType(['type' => 'page']);
$this->drupalCreateNode();
// Make sure that the feed, page and block all start off with the correct
// titles.
$this->drupalGet($view['page[path]']);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains($view['page[title]']);
$this->assertSession()->pageTextNotContains($view['block[title]']);
$this->drupalGet($view['page[feed_properties][path]']);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->responseContains($view['page[title]']);
$this->assertSession()->responseNotContains($view['block[title]']);
// Confirm that the block is available in the block administration UI.
$this->drupalGet('admin/structure/block/list/' . $this->config('system.theme')->get('default'));
$this->clickLink('Place block');
$this->assertSession()->pageTextContains($view['label']);
// Put the block into the first sidebar region, and make sure it will not
// display on the view's page display (since we will be searching for the
// presence/absence of the view's title in both the page and the block).
$this->container->get('plugin.manager.block')->clearCachedDefinitions();
$this->drupalPlaceBlock("views_block:{$view['id']}-block_1", [
'visibility' => [
'request_path' => [
'pages' => '/' . $view['page[path]'],
'negate' => TRUE,
],
],
]);
$this->drupalGet('');
$this->assertSession()->pageTextContains($view['block[title]']);
$this->assertSession()->pageTextNotContains($view['page[title]']);
// Edit the page and change the title. This should automatically change
// the feed's title also, but not the block.
$edit = [];
$edit['title'] = $new_default_title = $this->randomMachineName(16);
$this->drupalGet("admin/structure/views/nojs/display/{$view['id']}/page_1/title");
$this->submitForm($edit, 'Apply');
$this->drupalGet("admin/structure/views/view/{$view['id']}/edit/page_1");
$this->submitForm([], 'Save');
$this->drupalGet($view['page[path]']);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains($new_default_title);
$this->assertSession()->pageTextNotContains($view['page[title]']);
$this->assertSession()->pageTextNotContains($view['block[title]']);
$this->drupalGet($view['page[feed_properties][path]']);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->responseContains($new_default_title);
$this->assertSession()->responseNotContains($view['page[title]']);
$this->assertSession()->responseNotContains($view['block[title]']);
$this->drupalGet('');
$this->assertSession()->pageTextNotContains($new_default_title);
$this->assertSession()->pageTextNotContains($view['page[title]']);
$this->assertSession()->pageTextContains($view['block[title]']);
// Edit the block and change the title. This should automatically change
// the block title only, and leave the defaults alone.
$edit = [];
$edit['title'] = $new_block_title = $this->randomMachineName(16);
$this->drupalGet("admin/structure/views/nojs/display/{$view['id']}/block_1/title");
$this->submitForm($edit, 'Apply');
$this->drupalGet("admin/structure/views/view/{$view['id']}/edit/block_1");
$this->submitForm([], 'Save');
$this->drupalGet($view['page[path]']);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains($new_default_title);
$this->drupalGet($view['page[feed_properties][path]']);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->responseContains($new_default_title);
$this->assertSession()->responseNotContains($new_block_title);
$this->drupalGet('');
$this->assertSession()->pageTextContains($new_block_title);
$this->assertSession()->pageTextNotContains($view['block[title]']);
}
/**
* Tests that the revert to all displays select-option works as expected.
*/
public function testRevertAllDisplays(): void {
// Create a basic view with a page, block.
// Because there is both a title on page and block we expect the title on
// the block be overridden.
$view['label'] = $this->randomMachineName(16);
$view['id'] = $this->randomMachineName(16);
$view['page[create]'] = 1;
$view['page[title]'] = $this->randomMachineName(16);
$view['page[path]'] = $this->randomMachineName(16);
$view['block[create]'] = 1;
$view['block[title]'] = $this->randomMachineName(16);
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
// Revert the title of the block to the default ones, but submit some new
// values to be sure that the new value is not stored.
$edit = [];
$edit['title'] = $this->randomMachineName();
$edit['override[dropdown]'] = 'default_revert';
$this->drupalGet("admin/structure/views/nojs/display/{$view['id']}/block_1/title");
$this->submitForm($edit, 'Apply');
$this->drupalGet("admin/structure/views/view/{$view['id']}/edit/block_1");
$this->submitForm([], 'Save');
$this->assertSession()->pageTextContains($view['page[title]']);
}
}

View File

@@ -0,0 +1,207 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
/**
* Tests the UI preview functionality.
*
* @group views_ui
* @group #slow
*/
class PreviewTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = [
'test_preview',
'test_preview_error',
'test_pager_full',
'test_mini_pager',
'test_click_sort',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests contextual links in the preview form.
*/
public function testPreviewContextual(): void {
\Drupal::service('module_installer')->install(['contextual']);
$this->resetAll();
$this->drupalGet('admin/structure/views/view/test_preview/edit');
$this->assertSession()->statusCodeEquals(200);
$this->submitForm($edit = [], 'Update preview');
// Verify that the contextual link to add a new field is shown.
$selector = $this->assertSession()->buildXPathQuery('//div[@id="views-live-preview"]//ul[contains(@class, :ul-class)]/li/a[contains(@href, :href)]', [
':ul-class' => 'contextual-links',
':href' => '/admin/structure/views/nojs/add-handler/test_preview/default/filter',
]);
$this->assertSession()->elementsCount('xpath', $selector, 1);
$this->submitForm(['view_args' => '100'], 'Update preview');
// Test that area text and exposed filters are present and rendered.
$this->assertSession()->fieldExists('id');
$this->assertSession()->pageTextContains('Test header text');
$this->assertSession()->pageTextContains('Test footer text');
$this->assertSession()->pageTextContains('Test empty text');
$this->submitForm(['view_args' => '0'], 'Update preview');
// Test that area text and exposed filters are present and rendered.
$this->assertSession()->fieldExists('id');
$this->assertSession()->pageTextContains('Test header text');
$this->assertSession()->pageTextContains('Test footer text');
$this->assertSession()->pageTextContains('Test empty text');
}
/**
* Tests arguments in the preview form.
*/
public function testPreviewUI(): void {
$this->drupalGet('admin/structure/views/view/test_preview/edit');
$this->assertSession()->statusCodeEquals(200);
$this->submitForm($edit = [], 'Update preview');
$selector = '//div[@class = "views-row"]';
$this->assertSession()->elementsCount('xpath', $selector, 5);
// Filter just the first result.
$this->submitForm($edit = ['view_args' => '1'], 'Update preview');
$this->assertSession()->elementsCount('xpath', $selector, 1);
// Filter for no results.
$this->submitForm($edit = ['view_args' => '100'], 'Update preview');
$this->assertSession()->elementNotExists('xpath', $selector);
// Test that area text and exposed filters are present and rendered.
$this->assertSession()->fieldExists('id');
$this->assertSession()->pageTextContains('Test header text');
$this->assertSession()->pageTextContains('Test footer text');
$this->assertSession()->pageTextContains('Test empty text');
// Test feed preview.
$view = [];
$view['label'] = $this->randomMachineName(16);
$view['id'] = $this->randomMachineName(16);
$view['page[create]'] = 1;
$view['page[title]'] = $this->randomMachineName(16);
$view['page[path]'] = $this->randomMachineName(16);
$view['page[feed]'] = 1;
$view['page[feed_properties][path]'] = $this->randomMachineName(16);
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
$this->clickLink('Feed');
$this->submitForm([], 'Update preview');
$this->assertSession()->elementTextContains('xpath', '//div[@id="views-live-preview"]/pre', '<title>' . $view['page[title]'] . '</title>');
// Test the non-default UI display options.
// Statistics only, no query.
$settings = \Drupal::configFactory()->getEditable('views.settings');
$settings->set('ui.show.performance_statistics', TRUE)->save();
$this->drupalGet('admin/structure/views/view/test_preview/edit');
$this->submitForm($edit = ['view_args' => '100'], 'Update preview');
$this->assertSession()->pageTextContains('Query build time');
$this->assertSession()->pageTextContains('Query execute time');
$this->assertSession()->pageTextContains('View render time');
$this->assertSession()->responseNotContains('<strong>Query</strong>');
// Statistics and query.
$settings->set('ui.show.sql_query.enabled', TRUE)->save();
$this->submitForm($edit = ['view_args' => '100'], 'Update preview');
$this->assertSession()->pageTextContains('Query build time');
$this->assertSession()->pageTextContains('Query execute time');
$this->assertSession()->pageTextContains('View render time');
$this->assertSession()->responseContains('<strong>Query</strong>');
$query_string = <<<SQL
SELECT "views_test_data"."name" AS "views_test_data_name"
FROM
{views_test_data} "views_test_data"
WHERE (views_test_data.id = '100')
SQL;
$this->assertSession()->assertEscaped($query_string);
// Test that the statistics and query are rendered above the preview.
$this->assertLessThan(strpos($this->getSession()->getPage()->getContent(), 'js-view-dom-id'), strpos($this->getSession()->getPage()->getContent(), 'views-query-info'));
// Test that statistics and query rendered below the preview.
$settings->set('ui.show.sql_query.where', 'below')->save();
$this->submitForm($edit = ['view_args' => '100'], 'Update preview');
$this->assertLessThan(strpos($this->getSession()->getPage()->getContent(), 'views-query-info'), strpos($this->getSession()->getPage()->getContent(), 'js-view-dom-id'), 'Statistics shown below the preview.');
// Test that the preview title isn't double escaped.
$this->drupalGet("admin/structure/views/nojs/display/test_preview/default/title");
$this->submitForm($edit = ['title' => 'Double & escaped'], 'Apply');
$this->submitForm([], 'Update preview');
$this->assertSession()->elementsCount('xpath', '//div[@id="views-live-preview"]/div[contains(@class, views-query-info)]//td[text()="Double & escaped"]', 1);
}
/**
* Tests the additional information query info area.
*/
public function testPreviewAdditionalInfo(): void {
\Drupal::service('module_installer')->install(['views_ui_test']);
$this->resetAll();
$this->drupalGet('admin/structure/views/view/test_preview/edit');
$this->assertSession()->statusCodeEquals(200);
$this->submitForm($edit = [], 'Update preview');
// Check for implementation of hook_views_preview_info_alter().
// @see views_ui_test.module
// Verify that Views Query Preview Info area was altered.
$this->assertSession()->elementsCount('xpath', '//div[@id="views-live-preview"]/div[contains(@class, views-query-info)]//td[text()="Test row count"]', 1);
// Check that additional assets are attached.
$this->assertStringContainsString('views_ui_test/views_ui_test.test', $this->getDrupalSettings()['ajaxPageState']['libraries'], 'Attached library found.');
$this->assertSession()->responseContains('css/views_ui_test.test.css');
}
/**
* Tests view validation error messages in the preview.
*/
public function testPreviewError(): void {
$this->drupalGet('admin/structure/views/view/test_preview_error/edit');
$this->assertSession()->statusCodeEquals(200);
$this->submitForm($edit = [], 'Update preview');
$this->assertSession()->pageTextContains('Unable to preview due to validation errors.');
}
/**
* Tests HTML is filtered from the view title when previewing.
*/
public function testPreviewTitle(): void {
// Update the view and change title with html tags.
\Drupal::configFactory()->getEditable('views.view.test_preview')
->set('display.default.display_options.title', '<strong>Test preview title</strong>')
->save();
$this->drupalGet('admin/structure/views/view/test_preview/edit');
$this->assertSession()->statusCodeEquals(200);
$this->submitForm([], 'Update preview');
$this->assertSession()->pageTextContains('Test preview title');
// Ensure allowed HTML tags are still displayed.
$this->assertCount(2, $this->xpath('//div[@id="views-live-preview"]//strong[text()=:text]', [':text' => 'Test preview title']));
// Ensure other tags are filtered.
\Drupal::configFactory()->getEditable('views.view.test_preview')
->set('display.default.display_options.title', '<b>Test preview title</b>')
->save();
$this->submitForm([], 'Update preview');
$this->assertCount(0, $this->xpath('//div[@id="views-live-preview"]//b[text()=:text]', [':text' => 'Test preview title']));
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\views\Views;
use Drupal\views\Entity\View;
/**
* Tests query plugins.
*
* @group views_ui
*/
class QueryTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function viewsData() {
$data = parent::viewsData();
$data['views_test_data']['table']['base']['query_id'] = 'query_test';
return $data;
}
/**
* Tests query plugins settings.
*/
public function testQueryUI(): void {
$view = View::load('test_view');
$display = &$view->getDisplay('default');
$display['display_options']['query'] = ['type' => 'query_test'];
$view->save();
// Save some query settings.
$query_settings_path = "admin/structure/views/nojs/display/test_view/default/query";
$random_value = $this->randomMachineName();
$this->drupalGet($query_settings_path);
$this->submitForm(['query[options][test_setting]' => $random_value], 'Apply');
$this->submitForm([], 'Save');
// Check that the settings are saved into the view itself.
$view = Views::getView('test_view');
$view->initDisplay();
$view->initQuery();
$this->assertEquals($random_value, $view->query->options['test_setting'], 'Query settings got saved');
}
}

View File

@@ -0,0 +1,90 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\views\Views;
/**
* Tests the reordering of fields via AJAX.
*
* @group views_ui
* @see \Drupal\views_ui\Form\Ajax\Rearrange
*/
class RearrangeFieldsTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Gets the fields from the View.
*/
protected function getViewFields($view_name = 'test_view', $display_id = 'default') {
$view = Views::getView($view_name);
$view->setDisplay($display_id);
$fields = [];
foreach ($view->displayHandlers->get('default')->getHandlers('field') as $field => $handler) {
$fields[] = $field;
}
return $fields;
}
/**
* Check if the fields are in the correct order.
*
* @param string $view_name
* The name of the view.
* @param array $fields
* Array of field names.
*
* @internal
*/
protected function assertFieldOrder(string $view_name, array $fields): void {
$this->drupalGet('admin/structure/views/nojs/rearrange/' . $view_name . '/default/field');
foreach ($fields as $idx => $field) {
$this->assertSession()->fieldValueEquals('edit-fields-' . $field . '-weight', $idx + 1);
}
}
/**
* Tests field sorting.
*/
public function testRearrangeFields(): void {
$view_name = 'test_view';
// Checks that the order on the rearrange form matches the creation order.
$this->assertFieldOrder($view_name, $this->getViewFields($view_name));
// Checks that a field is not deleted if a value is not passed back.
$fields = [];
$this->drupalGet('admin/structure/views/nojs/rearrange/' . $view_name . '/default/field');
$this->submitForm($fields, 'Apply');
$this->assertFieldOrder($view_name, $this->getViewFields($view_name));
// Checks that revers the new field order is respected.
$reversedFields = array_reverse($this->getViewFields($view_name));
$fields = [];
foreach ($reversedFields as $delta => $field) {
$fields['fields[' . $field . '][weight]'] = $delta;
}
$fields_count = count($fields);
$this->drupalGet('admin/structure/views/nojs/rearrange/' . $view_name . '/default/field');
$this->submitForm($fields, 'Apply');
$this->assertFieldOrder($view_name, $reversedFields);
// Checks that there is a remove link for each field.
$this->assertCount($fields_count, $this->cssSelect('a.views-remove-link'));
}
}

View File

@@ -0,0 +1,59 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
/**
* Tests the redirecting after saving a views.
*
* @group views_ui
*/
class RedirectTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view', 'test_redirect_view'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the redirecting.
*/
public function testRedirect(): void {
$view_name = 'test_view';
$random_destination = $this->randomMachineName();
$edit_path = "admin/structure/views/view/$view_name/edit";
// Verify that the user gets redirected to the expected page defined in the
// destination.
$this->drupalGet($edit_path, ['query' => ['destination' => $random_destination]]);
$this->submitForm([], 'Save');
$this->assertSession()->addressEquals($random_destination);
// Setup a view with a certain page display path. If you change the path
// but have the old URL in the destination the user should be redirected to
// the new path.
$view_name = 'test_redirect_view';
$new_path = $this->randomMachineName();
$edit_path = "admin/structure/views/view/$view_name/edit";
$path_edit_path = "admin/structure/views/nojs/display/$view_name/page_1/path";
$this->drupalGet($path_edit_path);
$this->submitForm(['path' => $new_path], 'Apply');
$this->drupalGet($edit_path, ['query' => ['destination' => 'test-redirect-view']]);
$this->submitForm([], 'Save');
// Verify that the user gets redirected to the expected page after changing
// the URL of a page display.
$this->assertSession()->addressEquals($new_path);
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests the Views fields report page.
*
* @group views_ui
*/
class ReportFieldsTest extends UITestBase {
/**
* {@inheritdoc}
*/
public static $testViews = ['test_field_field_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected static $modules = ['entity_test'];
/**
* Tests the Views fields report page.
*/
public function testReportFields(): void {
$this->drupalGet('admin/reports/fields/views-fields');
$this->assertSession()->pageTextContains('Used in views');
$this->assertSession()->pageTextContains('No fields have been used in views yet.');
// Set up the field_test field.
$field_storage = FieldStorageConfig::create([
'field_name' => 'field_test',
'type' => 'integer',
'entity_type' => 'entity_test',
]);
$field_storage->save();
$field = FieldConfig::create([
'field_name' => 'field_test',
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
]);
$field->save();
// Assert that the newly created field appears in the overview.
$this->drupalGet('admin/reports/fields/views-fields');
$this->assertSession()->responseContains('<td>field_test</td>');
$this->assertSession()->responseContains('>test_field_field_test</a>');
$this->assertSession()->pageTextContains('Used in views');
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
/**
* Tests existence of the views plugin report.
*
* @group views_ui
*/
class ReportTest extends UITestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['views', 'views_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Stores an admin user used by the different tests.
*
* @var \Drupal\user\Entity\User
*/
protected $adminUser;
/**
* Tests the existence of the views plugin report.
*/
public function testReport(): void {
$this->drupalLogin($this->adminUser);
// Test the report page.
$this->drupalGet('admin/reports/views-plugins');
$this->assertSession()->statusCodeEquals(200);
}
}

View File

@@ -0,0 +1,94 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\Core\Entity\Entity\EntityViewMode;
use Drupal\views\Views;
/**
* Tests the UI of row plugins.
*
* @group views_ui
* @see \Drupal\views_test_data\Plugin\views\row\RowTest.
*/
class RowUITest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests changing the row plugin and changing some options of a row.
*/
public function testRowUI(): void {
$view_name = 'test_view';
$view_edit_url = "admin/structure/views/view/$view_name/edit";
$row_plugin_url = "admin/structure/views/nojs/display/$view_name/default/row";
$row_options_url = "admin/structure/views/nojs/display/$view_name/default/row_options";
$this->drupalGet($row_plugin_url);
$this->assertSession()->fieldValueEquals('row[type]', 'fields');
$edit = [
'row[type]' => 'test_row',
];
$this->submitForm($edit, 'Apply');
// Make sure the custom settings form from the test plugin appears.
$this->assertSession()->fieldExists('row_options[test_option]');
$random_name = $this->randomMachineName();
$edit = [
'row_options[test_option]' => $random_name,
];
$this->submitForm($edit, 'Apply');
$this->drupalGet($row_options_url);
// Make sure the custom settings form field has the expected value stored.
$this->assertSession()->fieldValueEquals('row_options[test_option]', $random_name);
$this->drupalGet($view_edit_url);
$this->submitForm([], 'Save');
$this->assertSession()->linkExists('Test row plugin', 0, 'Make sure the test row plugin is shown in the UI');
$view = Views::getView($view_name);
$view->initDisplay();
$row = $view->display_handler->getOption('row');
$this->assertEquals('test_row', $row['type'], 'Make sure that the test_row got saved as used row plugin.');
$this->assertEquals($random_name, $row['options']['test_option'], 'Make sure that the custom settings field got saved as expected.');
$this->drupalGet($row_plugin_url);
$this->submitForm(['row[type]' => 'fields'], 'Apply');
$this->drupalGet($row_plugin_url);
$this->assertSession()->statusCodeEquals(200);
// Make sure that 'fields' was saved as the row plugin.
$this->assertSession()->fieldValueEquals('row[type]', 'fields');
// Ensure that entity row plugins appear.
$view_name = 'content';
$row_plugin_url = "admin/structure/views/nojs/display/$view_name/default/row";
$row_options_url = "admin/structure/views/nojs/display/$view_name/default/row_options";
$this->drupalGet($row_plugin_url);
$this->submitForm(['row[type]' => 'entity:node'], 'Apply');
$this->assertSession()->addressEquals($row_options_url);
// Make sure the custom settings form from the entity row plugin appears.
$this->assertSession()->fieldValueEquals('row_options[view_mode]', 'teaser');
// Change the teaser label to have markup so we can test escaping.
$teaser = EntityViewMode::load('node.teaser');
$teaser->set('label', 'Teaser <em>markup</em>');
$teaser->save();
$this->drupalGet('admin/structure/views/view/frontpage/edit/default');
$this->assertSession()->assertEscaped('Teaser <em>markup</em>');
}
}

View File

@@ -0,0 +1,169 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\Core\Database\Database;
/**
* Tests all ui related settings under admin/structure/views/settings.
*
* @group views_ui
*/
class SettingsTest extends UITestBase {
/**
* Stores an admin user used by the different tests.
*
* @var \Drupal\user\Entity\User
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = ['views_test_config']): void {
parent::setUp($import_test_views, $modules);
$this->drupalPlaceBlock('local_tasks_block');
}
/**
* Tests the settings for the edit ui.
*/
public function testEditUI(): void {
$this->drupalLogin($this->adminUser);
// Test the settings tab exists.
$this->drupalGet('admin/structure/views');
$this->assertSession()->linkNotExists('admin/structure/views/settings');
// Test the confirmation message.
$this->drupalGet('admin/structure/views/settings');
$this->submitForm([], 'Save configuration');
$this->assertSession()->pageTextContains('The configuration options have been saved.');
// Configure to always show the default display.
$edit = [
'ui_show_default_display' => TRUE,
];
$this->drupalGet('admin/structure/views/settings');
$this->submitForm($edit, 'Save configuration');
$view = [];
$view['label'] = $this->randomMachineName(16);
$view['id'] = $this->randomMachineName(16);
$view['description'] = $this->randomMachineName(16);
$view['page[create]'] = TRUE;
$view['page[title]'] = $this->randomMachineName(16);
$view['page[path]'] = $this->randomMachineName(16);
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
// Configure to not always show the default display.
// If you have a view without a page or block the default display should be
// still shown.
$edit = [
'ui_show_default_display' => FALSE,
];
$this->drupalGet('admin/structure/views/settings');
$this->submitForm($edit, 'Save configuration');
$view['page[create]'] = FALSE;
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
// Create a view with an additional display, so default should be hidden.
$view['page[create]'] = TRUE;
$view['id'] = $this->randomMachineName();
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
$this->assertSession()->linkNotExists('Default');
// Configure to always show the advanced settings.
// @todo It doesn't seem to be a way to test this as this works just on js.
// Configure to show the embeddable display.
$edit = [
'ui_show_display_embed' => TRUE,
];
$this->drupalGet('admin/structure/views/settings');
$this->submitForm($edit, 'Save configuration');
$view['id'] = $this->randomMachineName();
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
$this->assertSession()->buttonExists('edit-displays-top-add-display-embed');
$edit = [
'ui_show_display_embed' => FALSE,
];
$this->drupalGet('admin/structure/views/settings');
$this->submitForm($edit, 'Save configuration');
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
$this->assertSession()->buttonNotExists('edit-displays-top-add-display-embed');
// Configure to hide/show the sql at the preview.
$edit = [
'ui_show_sql_query_enabled' => FALSE,
];
$this->drupalGet('admin/structure/views/settings');
$this->submitForm($edit, 'Save configuration');
$view['id'] = $this->randomMachineName();
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
// Verify that the views sql is hidden.
$this->submitForm([], 'Update preview');
$this->assertSession()->elementNotExists('xpath', '//div[@class="views-query-info"]/pre');
$edit = [
'ui_show_sql_query_enabled' => TRUE,
];
$this->drupalGet('admin/structure/views/settings');
$this->submitForm($edit, 'Save configuration');
$view['id'] = $this->randomMachineName();
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
// Verify that the views sql is shown.
$this->submitForm([], 'Update preview');
$this->assertSession()->elementExists('xpath', '//div[@class="views-query-info"]//pre');
// Verify that no placeholders are shown in the views sql.
$this->assertSession()->elementTextNotContains('xpath', '//div[@class="views-query-info"]//pre', 'db_condition_placeholder');
// Verify that the placeholders in the views sql are replaced by the actual
// values.
$this->assertSession()->elementTextContains('xpath', '//div[@class="views-query-info"]//pre', Database::getConnection()->escapeField("node_field_data.status") . " = '1'");
// Test the advanced settings form.
// Test the confirmation message.
$this->drupalGet('admin/structure/views/settings/advanced');
$this->submitForm([], 'Save configuration');
$this->assertSession()->pageTextContains('The configuration options have been saved.');
$edit = [
'sql_signature' => TRUE,
];
$this->drupalGet('admin/structure/views/settings/advanced');
$this->submitForm($edit, 'Save configuration');
$this->assertSession()->checkboxChecked('edit-sql-signature');
// Test the "Clear Views' cache" button.
$this->drupalGet('admin/structure/views/settings/advanced');
$this->submitForm([], "Clear Views' cache");
$this->assertSession()->pageTextContains('The cache has been cleared.');
}
}

View File

@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\views\Views;
/**
* Tests the UI of storage properties of views.
*
* @group views_ui
*/
class StorageTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['views_ui', 'language'];
/**
* Tests changing label, description and tag.
*
* @see views_ui_edit_details_form
*/
public function testDetails(): void {
$view_name = 'test_view';
ConfigurableLanguage::createFromLangcode('fr')->save();
$edit = [
'label' => $this->randomMachineName(),
'tag' => $this->randomMachineName(),
'description' => $this->randomMachineName(30),
'langcode' => 'fr',
];
$this->drupalGet("admin/structure/views/nojs/edit-details/{$view_name}/default");
$this->submitForm($edit, 'Apply');
$this->submitForm([], 'Save');
$view = Views::getView($view_name);
foreach (['label', 'tag', 'description', 'langcode'] as $property) {
$this->assertEquals($edit[$property], $view->storage->get($property), "Make sure the property $property got probably saved.");
}
}
}

View File

@@ -0,0 +1,43 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\views\Views;
/**
* Tests the UI of views when using the table style.
*
* @group views_ui
* @see \Drupal\views\Plugin\views\style\Table.
*/
class StyleTableTest extends UITestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests created a table style view.
*/
public function testWizard(): void {
// Create a new view and check that the first field has a label.
$view = [];
$view['label'] = $this->randomMachineName(16);
$view['id'] = $this->randomMachineName(16);
$view['show[wizard_key]'] = 'node';
$view['page[create]'] = TRUE;
$view['page[style][style_plugin]'] = 'table';
$view['page[title]'] = $this->randomMachineName(16);
$view['page[path]'] = $view['id'];
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
$view = Views::getView($view['id']);
$view->initHandlers();
$this->assertEquals('Title', $view->field['title']->options['label'], 'The field label for table styles is not empty.');
}
}

View File

@@ -0,0 +1,75 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\views\Views;
/**
* Tests the UI of style plugins.
*
* @group views_ui
* @see \Drupal\views_test_data\Plugin\views\style\StyleTest.
*/
class StyleUITest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests changing the style plugin and changing some options of a style.
*/
public function testStyleUI(): void {
$view_name = 'test_view';
$view_edit_url = "admin/structure/views/view/$view_name/edit";
$style_plugin_url = "admin/structure/views/nojs/display/$view_name/default/style";
$style_options_url = "admin/structure/views/nojs/display/$view_name/default/style_options";
$this->drupalGet($style_plugin_url);
$this->assertSession()->fieldValueEquals('style[type]', 'default');
$edit = [
'style[type]' => 'test_style',
];
$this->submitForm($edit, 'Apply');
$this->assertSession()->fieldExists('style_options[test_option]');
$random_name = $this->randomMachineName();
$edit = [
'style_options[test_option]' => $random_name,
];
$this->submitForm($edit, 'Apply');
$this->drupalGet($style_options_url);
$this->assertSession()->fieldValueEquals('style_options[test_option]', $random_name);
$this->drupalGet($view_edit_url);
$this->submitForm([], 'Save');
$this->assertSession()->linkExists('Test style plugin', 0, 'Make sure the test style plugin is shown in the UI');
$view = Views::getView($view_name);
$view->initDisplay();
$style = $view->display_handler->getOption('style');
$this->assertEquals('test_style', $style['type'], 'Make sure that the test_style got saved as used style plugin.');
$this->assertEquals($random_name, $style['options']['test_option'], 'Make sure that the custom settings field got saved as expected.');
// Test that fields are working correctly in the UI for style plugins when
// a field row plugin is selected.
$this->drupalGet("admin/structure/views/view/{$view_name}/edit");
$this->submitForm([], 'Add Page');
$this->drupalGet("admin/structure/views/nojs/display/{$view_name}/page_1/row");
$this->submitForm(['row[type]' => 'fields'], 'Apply');
// If fields are being used this text will not be shown.
$this->assertSession()->pageTextNotContains('The selected style or row format does not use fields.');
}
}

View File

@@ -0,0 +1,54 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\views\Entity\View;
/**
* Tests the token display for the TokenizeAreaPluginBase UI.
*
* @see \Drupal\views\Plugin\views\area\Entity
* @group views_ui
*/
class TokenizeAreaUITest extends UITestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['entity_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests that the right tokens are shown as available for replacement.
*/
public function testTokenUI(): void {
$entity_test = EntityTest::create(['bundle' => 'entity_test']);
$entity_test->save();
$default = $this->randomView([]);
$id = $default['id'];
$view = View::load($id);
$this->drupalGet($view->toUrl('edit-form'));
// Add a global NULL argument to the view for testing argument tokens.
$this->drupalGet("admin/structure/views/nojs/add-handler/{$id}/page_1/argument");
$this->submitForm(['name[views.null]' => 1], 'Add and configure contextual filters');
$this->submitForm([], 'Apply');
$this->drupalGet("admin/structure/views/nojs/add-handler/{$id}/page_1/header");
$this->submitForm(['name[views.area]' => 'views.area'], 'Add and configure header');
// Test that field tokens are shown.
$this->assertSession()->pageTextContains('{{ title }} == Content: Title');
// Test that argument tokens are shown.
$this->assertSession()->pageTextContains('{{ arguments.null }} == Global: Null title');
}
}

View File

@@ -0,0 +1,131 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\language\Entity\ConfigurableLanguage;
// cspell:ignore fichiers
/**
* Tests that translated strings in views UI don't override original strings.
*
* @group views_ui
*/
class TranslatedViewTest extends UITestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'config_translation',
'views_ui',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Languages to enable.
*
* @var array
*/
protected $langcodes = [
'fr',
];
/**
* Administrator user for tests.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = []): void {
parent::setUp($import_test_views, $modules);
$permissions = [
'administer site configuration',
'administer views',
'translate configuration',
'translate interface',
];
$this->drupalPlaceBlock('local_tasks_block', ['id' => 'test_role_admin_test_local_tasks_block']);
// Create and log in user.
$this->adminUser = $this->drupalCreateUser($permissions);
$this->drupalLogin($this->adminUser);
// Add languages.
foreach ($this->langcodes as $langcode) {
ConfigurableLanguage::createFromLangcode($langcode)->save();
}
$this->resetAll();
$this->rebuildContainer();
}
public function testTranslatedStrings(): void {
$translation_url = 'admin/structure/views/view/files/translate/fr/add';
$edit_url = 'admin/structure/views/view/files';
// Check the original string.
$this->drupalGet($edit_url);
$this->assertSession()->titleEquals('Files (File) | Drupal');
// Translate the label of the view.
$this->drupalGet($translation_url);
$edit = [
'translation[config_names][views.view.files][label]' => 'Fichiers',
];
$this->submitForm($edit, 'Save translation');
// Check if the label is translated.
$this->drupalGet($edit_url, ['language' => \Drupal::languageManager()->getLanguage('fr')]);
$this->assertSession()->titleEquals('Files (File) | Drupal');
$this->assertSession()->pageTextNotContains('Fichiers');
// Ensure that "Link URL" and "Link Path" fields are translatable.
// First, Add the block display and change pager's 'link display' to
// custom URL.
// Second, change filename to use plain text and rewrite output with link.
$this->drupalGet($edit_url);
$this->submitForm([], 'Add Block');
$this->drupalGet('admin/structure/views/nojs/display/files/block_1/link_display');
$edit = [
'link_display' => 'custom_url',
'link_url' => '/node',
];
$this->submitForm($edit, 'Apply');
$this->submitForm([], 'Save');
$this->drupalGet('admin/structure/views/nojs/handler/files/block_1/field/filename');
$edit = [
'override[dropdown]' => 'block_1',
'options[type]' => 'string',
'options[alter][path]' => '/node',
'options[alter][make_link]' => 1,
];
$this->submitForm($edit, 'Apply');
$this->submitForm([], 'Save');
// Visit the translation page and ensure that field exists.
$this->drupalGet($translation_url);
$this->assertSession()->fieldExists('translation[config_names][views.view.files][display][block_1][display_options][fields][filename][alter][path]');
$this->assertSession()->fieldExists('translation[config_names][views.view.files][display][default][display_options][link_url]');
// Assert that the View translation link is shown when viewing a display.
$this->drupalGet($edit_url);
$this->assertSession()->linkExists('Translate view');
$this->drupalGet('/admin/structure/views/view/files/edit/block_1');
$this->assertSession()->linkExists('Translate view');
}
}

View File

@@ -0,0 +1,93 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\Tests\views\Functional\ViewTestBase;
/**
* Provides a base class for testing the Views UI.
*/
abstract class UITestBase extends ViewTestBase {
/**
* An admin user with the 'administer views' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* An admin user with administrative permissions for views, blocks, and nodes.
*
* @var \Drupal\user\UserInterface
*/
protected $fullAdminUser;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['node', 'views_ui', 'block', 'taxonomy'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = ['views_test_config']): void {
parent::setUp($import_test_views, $modules);
$this->enableViewsTestModule();
$this->adminUser = $this->drupalCreateUser(['administer views']);
$this->fullAdminUser = $this->drupalCreateUser(['administer views',
'administer blocks',
'bypass node access',
'access user profiles',
'view all revisions',
'administer permissions',
]);
$this->drupalLogin($this->fullAdminUser);
}
/**
* A helper method which creates a random view.
*/
public function randomView(array $view = []) {
// Create a new view in the UI.
$default = [];
$default['label'] = $this->randomMachineName(16);
$default['id'] = $this->randomMachineName(16);
$default['description'] = $this->randomMachineName(16);
$default['page[create]'] = TRUE;
$default['page[path]'] = $default['id'];
$view += $default;
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
return $default;
}
/**
* {@inheritdoc}
*/
protected function drupalGet($path, array $options = [], array $headers = []) {
$url = $this->buildUrl($path, $options);
// Ensure that each nojs page is accessible via ajax as well.
if (str_contains($url, '/nojs/')) {
$url = preg_replace('|/nojs/|', '/ajax/', $url, 1);
$result = $this->drupalGet($url, $options);
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->responseHeaderEquals('Content-Type', 'application/json');
$this->assertNotEmpty(json_decode($result), 'Ensure that the AJAX request returned valid content.');
}
return parent::drupalGet($path, $options, $headers);
}
}

View File

@@ -0,0 +1,84 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
/**
* Tests covering Preview of unsaved Views.
*
* @group views_ui
*/
class UnsavedPreviewTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['content'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* An admin user with the 'administer views' permission.
*
* @var \Drupal\user\UserInterface
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected static $modules = ['node', 'views_ui'];
/**
* Sets up a Drupal site for running functional and integration tests.
*/
protected function setUp($import_test_views = TRUE, $modules = ['views_test_config']): void {
parent::setUp(FALSE, $modules);
$this->adminUser = $this->drupalCreateUser(['administer views']);
$this->drupalLogin($this->adminUser);
}
/**
* Tests previews of unsaved new page displays.
*/
public function testUnsavedPageDisplayPreview(): void {
$this->drupalCreateContentType(['type' => 'page']);
for ($i = 0; $i < 5; $i++) {
$this->drupalCreateNode();
}
$this->drupalGet('admin/structure/views/view/content');
$this->assertSession()->statusCodeEquals(200);
$this->submitForm([], 'Add Page');
$this->assertSession()->statusCodeEquals(200);
$this->drupalGet('admin/structure/views/nojs/display/content/page_2/path');
$this->assertSession()->statusCodeEquals(200);
$this->submitForm(['path' => 'foobar'], 'Apply');
$this->assertSession()->statusCodeEquals(200);
$this->submitForm([], 'Update preview');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->pageTextContains('This display has no path');
$this->drupalGet('admin/structure/views/view/content/edit/page_2');
$this->assertSession()->statusCodeEquals(200);
$this->submitForm([], 'Save');
$this->assertSession()->statusCodeEquals(200);
$this->submitForm([], 'Update preview');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->linkByHrefExists('foobar');
}
}

View File

@@ -0,0 +1,276 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\Core\Database\Database;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\views\Entity\View;
/**
* Tests some general functionality of editing views, like deleting a view.
*
* @group views_ui
* @group #slow
*/
class ViewEditTest extends UITestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view', 'test_display', 'test_groupwise_term_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests the delete link on a views UI.
*/
public function testDeleteLink(): void {
$this->drupalGet('admin/structure/views/view/test_view');
$this->assertSession()->linkExists('Delete view', 0, 'Ensure that the view delete link appears');
$view = $this->container->get('entity_type.manager')->getStorage('view')->load('test_view');
$this->assertInstanceOf(View::class, $view);
$this->clickLink('Delete view');
$this->assertSession()->addressEquals('admin/structure/views/view/test_view/delete');
$this->submitForm([], 'Delete');
$this->assertSession()->pageTextContains("The view {$view->label()} has been deleted.");
$this->assertSession()->addressEquals('admin/structure/views');
$view = $this->container->get('entity_type.manager')->getStorage('view')->load('test_view');
$this->assertNotInstanceOf(View::class, $view);
}
/**
* Tests the machine name and administrative comment forms.
*/
public function testOtherOptions(): void {
\Drupal::service('module_installer')->install(['dblog']);
$this->drupalGet('admin/structure/views/view/test_view');
// Add a new attachment display.
$this->submitForm([], 'Add Attachment');
// Test that a long administrative comment is truncated.
$edit = ['display_comment' => 'one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen'];
$this->drupalGet('admin/structure/views/nojs/display/test_view/attachment_1/display_comment');
$this->submitForm($edit, 'Apply');
$this->assertSession()->pageTextContains('one two three four five six seven eight nine ten eleven twelve thirteen…');
// Change the machine name for the display from page_1 to test_1.
$edit = ['display_id' => 'test_1'];
$this->drupalGet('admin/structure/views/nojs/display/test_view/attachment_1/display_id');
$this->submitForm($edit, 'Apply');
$this->assertSession()->linkExists('test_1');
// Save the view, and test the new ID has been saved.
$this->submitForm([], 'Save');
$view = \Drupal::entityTypeManager()->getStorage('view')->load('test_view');
$displays = $view->get('display');
$this->assertNotEmpty($displays['test_1'], 'Display data found for new display ID key.');
$this->assertSame('test_1', $displays['test_1']['id'], 'New display ID matches the display ID key.');
$this->assertArrayNotHasKey('attachment_1', $displays);
// Set to the same machine name and save the View.
$edit = ['display_id' => 'test_1'];
$this->drupalGet('admin/structure/views/nojs/display/test_view/test_1/display_id');
$this->submitForm($edit, 'Apply');
$this->submitForm([], 'Save');
$this->assertSession()->linkExists('test_1');
// Test the form validation with invalid IDs.
$machine_name_edit_url = 'admin/structure/views/nojs/display/test_view/test_1/display_id';
$error_text = 'Display machine name must contain only lowercase letters, numbers, or underscores.';
// Test that potential invalid display ID requests are detected
$this->drupalGet('admin/structure/views/ajax/handler/test_view/fake_display_name/filter/title');
$arguments = [
'@display_id' => 'fake_display_name',
];
$logged = Database::getConnection()->select('watchdog')
->fields('watchdog', ['variables'])
->condition('type', 'views')
->condition('message', 'setDisplay() called with invalid display ID "@display_id".')
->execute()
->fetchField();
$this->assertEquals(serialize($arguments), $logged);
$edit = ['display_id' => 'test 1'];
$this->drupalGet($machine_name_edit_url);
$this->submitForm($edit, 'Apply');
$this->assertSession()->pageTextContains($error_text);
$edit = ['display_id' => 'test_1#'];
$this->drupalGet($machine_name_edit_url);
$this->submitForm($edit, 'Apply');
$this->assertSession()->pageTextContains($error_text);
// Test using an existing display ID.
$edit = ['display_id' => 'default'];
$this->drupalGet($machine_name_edit_url);
$this->submitForm($edit, 'Apply');
$this->assertSession()->pageTextContains('Display id should be unique.');
// Test that the display ID has not been changed.
$this->drupalGet('admin/structure/views/view/test_view/edit/test_1');
$this->assertSession()->linkExists('test_1');
// Test that validation does not run on cancel.
$this->drupalGet('admin/structure/views/view/test_view');
// Delete the field to cause an error on save.
$fields = [];
$fields['fields[age][removed]'] = 1;
$fields['fields[id][removed]'] = 1;
$fields['fields[name][removed]'] = 1;
$this->drupalGet('admin/structure/views/nojs/rearrange/test_view/default/field');
$this->submitForm($fields, 'Apply');
$this->submitForm([], 'Save');
$this->submitForm([], 'Cancel');
// Verify that no error message is displayed.
$this->assertSession()->elementNotExists('xpath', '//div[contains(@class, "error")]');
// Verify page was redirected to the view listing.
$this->assertSession()->addressEquals('admin/structure/views');
}
/**
* Tests the language options on the views edit form.
*/
public function testEditFormLanguageOptions(): void {
$assert_session = $this->assertSession();
// Language options should not exist without language module.
$test_views = [
'test_view' => 'default',
'test_display' => 'page_1',
];
foreach ($test_views as $view_name => $display) {
$this->drupalGet('admin/structure/views/view/' . $view_name);
$this->assertSession()->statusCodeEquals(200);
$langcode_url = 'admin/structure/views/nojs/display/' . $view_name . '/' . $display . '/rendering_language';
$this->assertSession()->linkByHrefNotExists($langcode_url);
$assert_session->linkNotExistsExact('Content language selected for page');
$this->assertSession()->linkNotExists('Content language of view row');
}
// Make the site multilingual and test the options again.
$this->container->get('module_installer')->install(['language', 'content_translation']);
ConfigurableLanguage::createFromLangcode('hu')->save();
$this->resetAll();
$this->rebuildContainer();
// Language options should now exist with entity language the default.
foreach ($test_views as $view_name => $display) {
$this->drupalGet('admin/structure/views/view/' . $view_name);
$this->assertSession()->statusCodeEquals(200);
$langcode_url = 'admin/structure/views/nojs/display/' . $view_name . '/' . $display . '/rendering_language';
if ($view_name == 'test_view') {
$this->assertSession()->linkByHrefNotExists($langcode_url);
$assert_session->linkNotExistsExact('Content language selected for page');
$this->assertSession()->linkNotExists('Content language of view row');
}
else {
$this->assertSession()->linkByHrefExists($langcode_url);
$assert_session->linkNotExistsExact('Content language selected for page');
$this->assertSession()->linkExists('Content language of view row');
}
$this->drupalGet($langcode_url);
$this->assertSession()->statusCodeEquals(200);
if ($view_name == 'test_view') {
$this->assertSession()->pageTextContains('The view is not based on a translatable entity type or the site is not multilingual.');
}
else {
$this->assertSession()->fieldValueEquals('rendering_language', '***LANGUAGE_entity_translation***');
// Test that the order of the language list is similar to other language
// lists, such as in the content translation settings.
$expected_elements = [
'***LANGUAGE_entity_translation***',
'***LANGUAGE_entity_default***',
'***LANGUAGE_site_default***',
'***LANGUAGE_language_interface***',
'en',
'hu',
];
$elements = $this->assertSession()->selectExists('edit-rendering-language')->findAll('css', 'option');
$elements = array_map(function ($element) {
return $element->getValue();
}, $elements);
$this->assertSame($expected_elements, $elements);
// Check that the selected values are respected even we they are not
// supposed to be listed.
// Give permission to edit languages to authenticated users.
$edit = [
'authenticated[administer languages]' => TRUE,
];
$this->drupalGet('/admin/people/permissions');
$this->submitForm($edit, 'Save permissions');
// Enable Content language negotiation so we have one more item
// to select.
$edit = [
'language_content[configurable]' => TRUE,
];
$this->drupalGet('admin/config/regional/language/detection');
$this->submitForm($edit, 'Save settings');
// Choose the new negotiation as the rendering language.
$edit = [
'rendering_language' => '***LANGUAGE_language_content***',
];
$this->drupalGet('/admin/structure/views/nojs/display/' . $view_name . '/' . $display . '/rendering_language');
$this->submitForm($edit, 'Apply');
// Disable language content negotiation.
$edit = [
'language_content[configurable]' => FALSE,
];
$this->drupalGet('admin/config/regional/language/detection');
$this->submitForm($edit, 'Save settings');
// Check that the previous selection is listed and selected.
$this->drupalGet($langcode_url);
$this->assertTrue($this->assertSession()->optionExists('edit-rendering-language', '***LANGUAGE_language_content***')->isSelected());
// Check the order for the langcode filter.
$langcode_url = 'admin/structure/views/nojs/handler/' . $view_name . '/' . $display . '/filter/langcode';
$this->drupalGet($langcode_url);
$this->assertSession()->statusCodeEquals(200);
$expected_elements = [
'all',
'***LANGUAGE_site_default***',
'***LANGUAGE_language_interface***',
'***LANGUAGE_language_content***',
'en',
'hu',
'und',
'zxx',
];
$elements = $this->xpath('//div[@id="edit-options-value"]//input');
// Compare values inside the option elements with expected values.
for ($i = 0; $i < count($elements); $i++) {
$this->assertEquals($expected_elements[$i], $elements[$i]->getAttribute('value'));
}
}
}
}
/**
* Tests Representative Node for a Taxonomy Term.
*/
public function testRelationRepresentativeNode(): void {
// Populate and submit the form.
$edit["name[taxonomy_term_field_data.tid_representative]"] = TRUE;
$this->drupalGet('admin/structure/views/nojs/add-handler/test_groupwise_term_ui/default/relationship');
$this->submitForm($edit, 'Add and configure relationships');
// Apply changes.
$edit = [];
$this->drupalGet('admin/structure/views/nojs/handler/test_groupwise_term_ui/default/relationship/tid_representative');
$this->submitForm($edit, 'Apply');
}
}

View File

@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\views\Entity\View;
use Drupal\views\Views;
/**
* Tests the views list.
*
* @group views_ui
*/
class ViewsListTest extends UITestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['block', 'views_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* A user with permission to administer views.
*
* @var \Drupal\user\Entity\User
*/
protected $adminUser;
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE, $modules = []): void {
parent::setUp($import_test_views, $modules);
$this->drupalPlaceBlock('local_tasks_block');
$this->drupalPlaceBlock('local_actions_block');
$this->adminUser = $this->drupalCreateUser(['administer views']);
$this->drupalLogin($this->adminUser);
}
/**
* Tests that the views list does not use a pager.
*/
public function testViewsListLimit(): void {
// Check if we can access the main views admin page.
$this->drupalGet('admin/structure/views');
$this->assertSession()->statusCodeEquals(200);
$this->assertSession()->linkExists('Add view');
// Check that there is a link to the content view without a destination
// parameter.
$this->drupalGet('admin/structure/views');
$links = $this->getSession()->getPage()->findAll('xpath', "//a[contains(@href, 'admin/structure/views/view/content')]");
$this->assertStringEndsWith('admin/structure/views/view/content', $links[0]->getAttribute('href'));
$this->assertSession()->linkByHrefExists('admin/structure/views/view/content/delete?destination');
// Count default views to be subtracted from the limit.
$views = count(Views::getEnabledViews());
// Create multiples views.
$limit = 51;
$values = $this->config('views.view.test_view_storage')->get();
for ($i = 1; $i <= $limit - $views; $i++) {
$values['id'] = $values['label'] = 'test_view_storage_new' . $i;
unset($values['uuid']);
$created = View::create($values);
$created->save();
}
$this->drupalGet('admin/structure/views');
// Check that all the rows are listed.
$this->assertSession()->elementsCount('xpath', '//tbody/tr[contains(@class,"views-ui-list-enabled")]', $limit);
}
}

View File

@@ -0,0 +1,74 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
use Drupal\Component\Utility\Unicode;
use Drupal\Tests\views\Functional\Wizard\WizardTestBase;
/**
* Tests the wizard.
*
* @group views_ui
* @see \Drupal\views\Plugin\views\display\DisplayPluginBase
* @see \Drupal\views\Plugin\views\display\PathPluginBase
* @see \Drupal\views\Plugin\views\wizard\WizardPluginBase
*/
class WizardTest extends WizardTestBase {
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Tests filling in the wizard with really long strings.
*/
public function testWizardFieldLength(): void {
$view = [];
$view['label'] = $this->randomMachineName(256);
$view['id'] = $this->randomMachineName(129);
$view['page[create]'] = TRUE;
$view['page[path]'] = $this->randomMachineName(255);
$view['page[title]'] = $this->randomMachineName(256);
$view['page[feed]'] = TRUE;
$view['page[feed_properties][path]'] = $this->randomMachineName(255);
$view['block[create]'] = TRUE;
$view['block[title]'] = $this->randomMachineName(256);
$view['rest_export[create]'] = TRUE;
$view['rest_export[path]'] = $this->randomMachineName(255);
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
$this->assertSession()->pageTextContains('Machine-readable name cannot be longer than 128 characters but is currently 129 characters long.');
$this->assertSession()->pageTextContains('Path cannot be longer than 254 characters but is currently 255 characters long.');
$this->assertSession()->pageTextContains('Page title cannot be longer than 255 characters but is currently 256 characters long.');
$this->assertSession()->pageTextContains('View name cannot be longer than 255 characters but is currently 256 characters long.');
$this->assertSession()->pageTextContains('Feed path cannot be longer than 254 characters but is currently 255 characters long.');
$this->assertSession()->pageTextContains('Block title cannot be longer than 255 characters but is currently 256 characters long.');
$this->assertSession()->pageTextContains('REST export path cannot be longer than 254 characters but is currently 255 characters long.');
$view['label'] = $this->randomMachineName(255);
$view['id'] = $this->randomMachineName(128);
$view['page[create]'] = TRUE;
$view['page[path]'] = $this->randomMachineName(254);
$view['page[title]'] = $this->randomMachineName(255);
$view['page[feed]'] = TRUE;
$view['page[feed_properties][path]'] = $this->randomMachineName(254);
$view['block[create]'] = TRUE;
$view['block[title]'] = $this->randomMachineName(255);
$view['rest_export[create]'] = TRUE;
$view['rest_export[path]'] = $this->randomMachineName(254);
// Make sure the view saving was successful and the browser got redirected
// to the edit page.
$this->drupalGet('admin/structure/views/add');
$this->submitForm($view, 'Save and edit');
$this->assertSession()->addressEquals('admin/structure/views/view/' . $view['id']);
// Assert that the page title is correctly truncated.
$this->assertSession()->pageTextContains(Unicode::truncate($view['page[title]'], 32, FALSE, TRUE));
}
}

View File

@@ -0,0 +1,51 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Functional;
/**
* Tests the Xss vulnerability.
*
* @group views_ui
*/
class XssTest extends UITestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['node', 'user', 'views_ui', 'views_ui_test'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
public function testViewsUi(): void {
$this->drupalGet('admin/structure/views/view/sa_contrib_2013_035');
// Verify that the field admin label is properly escaped.
$this->assertSession()->assertEscaped('<marquee>test</marquee>');
$this->drupalGet('admin/structure/views/nojs/handler/sa_contrib_2013_035/page_1/header/area');
// Verify that the token label is properly escaped.
$this->assertSession()->assertEscaped('{{ title }} == <marquee>test</marquee>');
$this->assertSession()->assertEscaped('{{ title_1 }} == <script>alert("XSS")</script>');
}
/**
* Checks the admin UI for double escaping.
*/
public function testNoDoubleEscaping(): void {
$this->drupalGet('admin/structure/views');
$this->assertSession()->assertNoEscaped('&lt;');
$this->drupalGet('admin/structure/views/view/sa_contrib_2013_035');
$this->assertSession()->assertNoEscaped('&lt;');
$this->drupalGet('admin/structure/views/nojs/handler/sa_contrib_2013_035/page_1/header/area');
$this->assertSession()->assertNoEscaped('&lt;');
}
}

View File

@@ -0,0 +1,85 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Tests the admin UI AJAX interactions.
*
* @group views_ui
*/
class AdminAjaxTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'views_ui',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'views_ui_test_theme';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$this->drupalLogin($this->createUser([
'administer views',
]));
}
/**
* Confirms that form_alter is triggered after AJAX rebuilds.
*/
public function testAjaxRebuild(): void {
\Drupal::service('theme_installer')->install(['views_ui_test_theme']);
$this->config('system.theme')
->set('default', 'views_ui_test_theme')
->save();
$page = $this->getSession()->getPage();
$assert_session = $this->assertSession();
$this->drupalGet('admin/structure/views/view/user_admin_people');
$assert_session->pageTextContains('This is text added to the display tabs at the top');
$assert_session->pageTextContains('This is text added to the display edit form');
$page->clickLink('User: Name (Username)');
$assert_session->waitForElementVisible('css', '.views-ui-dialog');
$page->fillField('Label', 'New Title');
$page->find('css', '.ui-dialog-buttonset button:contains("Apply")')->press();
$assert_session->waitForElementRemoved('css', '.views-ui-dialog');
$assert_session->pageTextContains('This is text added to the display tabs at the top');
$assert_session->pageTextContains('This is text added to the display edit form');
}
/**
* Tests body scroll.
*/
public function testBodyScroll(): void {
$this->drupalGet('admin/structure/views/view/user_admin_people');
$page = $this->getSession()->getPage();
foreach (['name[views.nothing]', 'name[views.dropbutton]'] as $field) {
$page->find('css', '#views-add-field')->click();
$this->assertSession()->assertWaitOnAjaxRequest();
$page->checkField($field);
$page->find('css', '.ui-dialog-buttonset')->pressButton('Add and configure fields');
$this->assertSession()->assertWaitOnAjaxRequest();
$this->assertJsCondition('document.documentElement.style.overflow === "hidden"');
$page->find('css', '.ui-dialog-buttonset')->pressButton('Apply');
$this->assertSession()->assertWaitOnAjaxRequest();
// Check overflow.
$this->assertJsCondition('document.documentElement.style.overflow !== "hidden"');
}
}
}

View File

@@ -0,0 +1,210 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\locale\SourceString;
use Drupal\views\Entity\View;
use Drupal\views\Tests\ViewTestData;
use Drupal\Tests\node\Traits\NodeCreationTrait;
// cSpell:ignore Blokk hozzáadása
/**
* Tests the display UI.
*
* @group views_ui
*/
class DisplayTest extends WebDriverTestBase {
use NodeCreationTrait;
/**
* {@inheritdoc}
*/
protected static $modules = [
'block',
'contextual',
'node',
'language',
'locale',
'views',
'views_ui',
'views_test_config',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
public static $testViews = ['test_content_ajax', 'test_display'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
ViewTestData::createTestViews(self::class, ['views_test_config']);
$admin_user = $this->drupalCreateUser([
'administer site configuration',
'administer views',
'administer nodes',
'access content overview',
'access contextual links',
]);
// Disable automatic live preview to make the sequence of calls clearer.
\Drupal::configFactory()->getEditable('views.settings')->set('ui.always_live_preview', FALSE)->save();
$this->drupalLogin($admin_user);
}
/**
* Tests adding a display.
*/
public function testAddDisplay(): void {
$this->drupalGet('admin/structure/views/view/test_content_ajax');
$page = $this->getSession()->getPage();
$page->find('css', '#views-display-menu-tabs .add')->click();
// Wait for the animation to complete.
$this->getSession()->wait(1000, "jQuery(':animated').length === 0;");
// Add the display.
$page->find('css', '#edit-displays-top-add-display-block')->click();
$element = $page->findById('views-display-menu-tabs')->findLink('Block');
$this->assertNotEmpty($element);
}
/**
* Tests setting the administrative title.
*/
public function testRenameDisplayAdminName(): void {
$titles = ['New admin title', '</title><script>alert("alert!")</script>'];
foreach ($titles as $new_title) {
$this->drupalGet('admin/structure/views/view/test_content_ajax');
$page = $this->getSession()->getPage();
$page->findLink('Edit view name/description')->click();
$this->getSession()->executeScript("document.title = 'Initial title | " . \Drupal::config('system.site')->get('name') . "'");
$admin_name_field = $this->assertSession()
->waitForField('Administrative name');
$dialog_buttons = $page->find('css', '.ui-dialog-buttonset');
$admin_name_field->setValue($new_title);
$dialog_buttons->pressButton('Apply');
$this->assertJsCondition("document.title === '" . $new_title . " (Content) | " . \Drupal::config('system.site')->get('name') . "'");
}
}
/**
* Tests contextual links on Views page displays.
*/
public function testPageContextualLinks(): void {
$view = View::load('test_display');
$view->enable()->save();
$this->container->get('router.builder')->rebuildIfNeeded();
// Create node so the view has content and the contextual area is higher
// than 0 pixels.
$this->drupalCreateContentType(['type' => 'page']);
$this->createNode();
// When no "main content" block is placed, we find a contextual link
// placeholder for editing just the view.
$this->drupalGet('test-display');
$page = $this->getSession()->getPage();
$this->assertSession()->assertWaitOnAjaxRequest();
$selector = '.views-element-container';
$this->toggleContextualTriggerVisibility($selector);
$element = $this->getSession()->getPage()->find('css', $selector);
$element->find('css', '.contextual button')->press();
$contextual_container_id = 'entity.view.edit_form:view=test_display:location=page&name=test_display&display_id=page_1&langcode=en';
$contextual_container = $page->find('css', '[data-contextual-id="' . $contextual_container_id . '"]');
$this->assertNotEmpty($contextual_container);
$edit_link = $contextual_container->findLink('Edit view');
$this->assertNotEmpty($edit_link);
// When a "main content" is placed, we still find a contextual link
// placeholder for editing just the view (not the main content block).
// @see system_block_view_system_main_block_alter()
$this->drupalPlaceBlock('system_main_block', ['id' => 'main_content']);
$contextual_container = $page->find('css', '[data-contextual-id="' . $contextual_container_id . '"]');
$this->assertNotEmpty($contextual_container);
}
/**
* Toggles the visibility of a contextual trigger.
*
* @param string $selector
* The selector for the element that contains the contextual Rink.
*/
protected function toggleContextualTriggerVisibility($selector) {
// Hovering over the element itself with should be enough, but does not
// work. Manually remove the visually-hidden class.
$this->getSession()->executeScript("jQuery('{$selector} .contextual .trigger').toggleClass('visually-hidden');");
}
/**
* Test if 'add' translations are filtered from multilingual display options.
*/
public function testAddDisplayBlockTranslation(): void {
// Set up an additional language (Hungarian).
$langcode = 'hu';
ConfigurableLanguage::createFromLangcode($langcode)->save();
$config = $this->config('language.negotiation');
$config->set('url.prefixes', [$langcode => $langcode])->save();
\Drupal::service('kernel')->rebuildContainer();
\Drupal::languageManager()->reset();
// Add Hungarian translations.
$this->addTranslation($langcode, 'Block', 'Blokk');
$this->addTranslation($langcode, 'Add @display', '@display hozzáadása');
$this->drupalGet('hu/admin/structure/views/view/test_display');
$page = $this->getSession()->getPage();
$page->find('css', '#views-display-menu-tabs .add')->click();
// Wait for the animation to complete.
$this->getSession()->wait(1000, "jQuery(':animated').length === 0;");
// Look for the input element, always in second spot.
$elements = $page->findAll('css', '.add ul input');
$this->assertEquals('Blokk', $elements[1]->getAttribute('value'));
}
/**
* Helper function for adding interface text translations.
*/
private function addTranslation($langcode, $source_string, $translation_string) {
$storage = \Drupal::service('locale.storage');
$string = $storage->findString(['source' => $source_string]);
if (is_null($string)) {
$string = new SourceString();
$string
->setString($source_string)
->setStorage($storage)
->save();
}
$storage->createTranslation([
'lid' => $string->getId(),
'language' => $langcode,
'translation' => $translation_string,
])->save();
}
}

View File

@@ -0,0 +1,91 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\views\Tests\ViewTestData;
/**
* Tests the fields dialogs.
*
* @group views_ui
*/
class FieldDialogsTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'node',
'views',
'views_ui',
'views_test_config',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* Views used by this test.
*
* @var string[]
*/
public static $testViews = ['test_content_ajax'];
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
ViewTestData::createTestViews(self::class, ['views_test_config']);
$admin_user = $this->drupalCreateUser([
'administer site configuration',
'administer views',
'access content overview',
]);
// Disable automatic live preview to make the sequence of calls clearer.
\Drupal::configFactory()->getEditable('views.settings')->set('ui.always_live_preview', FALSE)->save();
$this->drupalLogin($admin_user);
}
/**
* Tests removing a field through the rearrange dialog.
*/
public function testRemoveFieldHandler(): void {
$this->drupalGet('admin/structure/views/view/test_content_ajax');
$page = $this->getSession()->getPage();
$this->openFieldDialog();
$remove_link = $page->findAll('css', '.views-remove-link')[1];
$parent = $remove_link->getParent();
$this->assertTrue($remove_link->isVisible());
$remove_checkbox = $this->assertSession()->fieldExists('fields[title][removed]', $parent);
$this->assertFalse($remove_checkbox->isVisible());
$this->assertFalse($remove_checkbox->isChecked());
$remove_link->click();
$this->assertFalse($remove_link->isVisible());
$this->assertTrue($remove_checkbox->isChecked());
}
/**
* Uses the 'And/Or Rearrange' link for fields to open a dialog.
*/
protected function openFieldDialog() {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$dropbutton = $page->find('css', '.views-ui-display-tab-bucket.field .dropbutton-toggle button');
$dropbutton->click();
$add_link = $page->findById('views-rearrange-field');
$this->assertTrue($add_link->isVisible(), 'And/Or Rearrange button found.');
$add_link->click();
$assert_session->assertWaitOnAjaxRequest();
}
}

View File

@@ -0,0 +1,192 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Tests the View UI filter criteria group dialog.
*
* @group views_ui
*/
class FilterCriteriaTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['node', 'views', 'views_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$admin_user = $this->drupalCreateUser([
'administer site configuration',
'administer views',
'administer nodes',
'access content overview',
]);
// Disable automatic live preview to make the sequence of calls clearer.
\Drupal::configFactory()->getEditable('views.settings')->set('ui.always_live_preview', FALSE)->save();
$this->drupalLogin($admin_user);
}
/**
* Tests dialog for filter criteria.
*/
public function testFilterCriteriaDialog(): void {
// Checks that the admin summary is not double escaped.
$this->drupalGet('admin/structure/views/view/who_s_online');
$page = $this->getSession()->getPage();
$this->assertNotNull($page->findLink('User: Last access (>= -15 minutes)'));
$this->drupalGet('admin/structure/views/view/content_recent');
$assert_session = $this->assertSession();
$session = $this->getSession();
$page = $session->getPage();
$this->openFilterDialog();
// Add a new filter group.
$create_new_filter_group = $page->findById('views-add-group-link');
$this->assertTrue($create_new_filter_group->isVisible(), 'Add group link found.');
$create_new_filter_group->click();
$assert_session->assertWaitOnAjaxRequest();
// Assert the existence of the new filter group by checking the remove group
// link.
$remove_link = $page->findLink('Remove group');
$this->assertTrue($remove_link->isVisible(), 'New group found.');
// Remove the group again and assert the group is not present anymore.
$remove_link->click();
$assert_session->assertWaitOnAjaxRequest();
$remove_link = $page->findLink('Remove group');
$this->assertEmpty($remove_link, 'Remove button not available');
// Add group again to test drag-n-drop.
$create_new_filter_group = $page->findById('views-add-group-link');
$this->assertTrue($create_new_filter_group->isVisible(), 'Add group link found.');
$create_new_filter_group->click();
$assert_session->assertWaitOnAjaxRequest();
// Validate dragging behaviors.
// First get relevant elements and the current values.
$status_extra_row = $page->findById("views-row-status_extra");
$langcode_row = $page->findById("views-row-langcode");
$status_extra_group_field = $status_extra_row->findField('filters[status_extra][group]');
$langcode_group_field = $langcode_row->findField('filters[langcode][group]');
$status_extra_original_value = $status_extra_group_field->getValue();
$langcode_original_value = $langcode_group_field->getValue();
// Validate dragging the first filter works correctly but checking the
// remove group link is not visible anymore.
$drag_handle = $status_extra_row->find('css', '.tabledrag-handle');
$target = $page->find('css', '.filter-group-operator-row');
$drag_handle->dragTo($target);
// Assert dragging a filter works.
$remove_link = $page->findLink('Remove group');
$this->assertFalse($remove_link->isVisible(), 'Remove group should be invisible after drag.');
// Drag another filter to the end of the last filter group to verify the
// group is set correctly.
$drag_handle = $langcode_row->find('css', '.tabledrag-handle');
$drag_handle->dragTo($status_extra_row);
// Both rows must be in the same group.
$this->assertNotEquals($status_extra_original_value, $status_extra_group_field->getValue(), 'Status extra group should be changed');
$this->assertNotEquals($langcode_original_value, $langcode_group_field->getValue(), 'Langcode group should be changed');
$this->assertSession()->waitForLink('Create new filter group', 20000);
$create_new_filter_group = $page->findLink('Create new filter group');
$this->assertTrue($create_new_filter_group->isVisible(), 'Add group link found.');
$create_new_filter_group->click();
$assert_session->assertWaitOnAjaxRequest();
// Validate dragging works correctly and the new group will contain the new
// filter.
$dragged = $page->find('css', ".tabledrag-handle");
$target = $page->find('css', '.filter-group-operator-row');
$dragged->dragTo($target);
$remove_link = $page->findLink('Remove group');
$this->assertFalse($remove_link->isVisible(), 'Remove group should be invisible after drag.');
}
/**
* Tests operator labels.
*/
public function testOperatorLabels(): void {
// Open the "Frontpage" view.
$this->drupalGet('admin/structure/views/view/frontpage');
$session = $this->getSession();
$page = $session->getPage();
// Open the "Rearrange filter criteria" dialog.
$this->openFilterDialog();
// Get the last filter on the list.
$row = $page->findAll('css', '.draggable');
$row_count = count($row);
$last_row = array_pop($row);
$penultimate_row = array_pop($row);
// Drag the last row before the penultimate row.
$drag_handle = $last_row->find('css', '.tabledrag-handle');
$drag_handle->dragTo($penultimate_row);
// Assert there are valid number of visible operator labels.
$operator_label = $page->findAll('css', '.views-operator-label');
$this->assertEquals($row_count - 1, count($operator_label), 'There are valid number of operator labels after drag.');
// Get the last filter on the rearranged list.
$row = $page->findAll('css', '.draggable');
$last_row = array_pop($row);
$penultimate_row = array_pop($row);
// Assert the operator label in the penultimate row is shown.
$operator_label = $penultimate_row->find('css', '.views-operator-label');
$this->assertTrue($operator_label->isVisible(), 'Operator label in the penultimate row is not visible after drag.');
// Assert the operator label in the last row is not shown.
$operator_label = $last_row->find('css', '.views-operator-label');
$this->assertNull($operator_label, 'Operator label in the last row is not visible after drag.');
// Remove the last filter.
$remove_link = $last_row->find('css', '.views-remove-link');
$remove_link->click();
// The current last filter shouldn't have the operator label.
$operator_label = $penultimate_row->find('css', '.views-operator-label');
$this->assertNull($operator_label, 'The penultimate filter has no operator label after the last filter is removed.');
}
/**
* Uses the 'And/Or Rearrange' link for filters to open a dialog.
*/
protected function openFilterDialog() {
$assert_session = $this->assertSession();
$page = $this->getSession()->getPage();
$dropbutton = $page->find('css', '.views-ui-display-tab-bucket.filter .dropbutton-toggle button');
$dropbutton->click();
$add_link = $page->findById('views-rearrange-filter');
$this->assertTrue($add_link->isVisible(), 'And/Or Rearrange button found.');
$add_link->click();
$assert_session->assertWaitOnAjaxRequest();
}
}

View File

@@ -0,0 +1,88 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Tests the JavaScript filtering of options in add handler form.
*
* @group views_ui
*/
class FilterOptionsTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = [
'node',
'views',
'views_ui',
'views_ui_test_field',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$admin_user = $this->drupalCreateUser([
'administer views',
]);
$this->drupalLogin($admin_user);
}
/**
* Tests filtering options in the 'Add fields' dialog.
*/
public function testFilterOptionsAddFields(): void {
$this->drupalGet('admin/structure/views/view/content');
$session = $this->getSession();
$web_assert = $this->assertSession();
$page = $session->getPage();
// Open the dialog.
$page->clickLink('views-add-field');
// Wait for the popup to open and the search field to be available.
$options_search = $web_assert->waitForField('override[controls][options_search]');
// Test that the both special fields are visible.
$this->assertTrue($page->findField('name[views.views_test_field_1]')->isVisible());
$this->assertTrue($page->findField('name[views.views_test_field_2]')->isVisible());
// Test the ".title" field in search.
$options_search->setValue('FIELD_1_TITLE');
$page->waitFor(10, function () use ($page) {
return !$page->findField('name[views.views_test_field_2]')->isVisible();
});
$this->assertTrue($page->findField('name[views.views_test_field_1]')->isVisible());
$this->assertFalse($page->findField('name[views.views_test_field_2]')->isVisible());
// Test the ".description" field in search.
$options_search->setValue('FIELD_2_DESCRIPTION');
$page->waitFor(10, function () use ($page) {
return !$page->findField('name[views.views_test_field_1]')->isVisible();
});
$this->assertTrue($page->findField('name[views.views_test_field_2]')->isVisible());
$this->assertFalse($page->findField('name[views.views_test_field_1]')->isVisible());
// Test the "label" field not in search.
$options_search->setValue('FIELD_1_LABEL');
$page->waitFor(10, function () use ($page) {
return !$page->findField('name[views.views_test_field_2]')->isVisible();
});
$this->assertFalse($page->findField('name[views.views_test_field_2]')->isVisible());
$this->assertFalse($page->findField('name[views.views_test_field_1]')->isVisible());
}
}

View File

@@ -0,0 +1,315 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\FunctionalJavascript;
use Behat\Mink\Element\NodeElement;
use Drupal\Core\Database\Database;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
use Drupal\views\Tests\ViewTestData;
/**
* Tests the UI preview functionality.
*
* @group views_ui
*/
class PreviewTest extends WebDriverTestBase {
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_preview', 'test_pager_full_ajax', 'test_mini_pager_ajax', 'test_click_sort_ajax'];
/**
* {@inheritdoc}
*/
protected static $modules = [
'node',
'views',
'views_ui',
'views_test_config',
];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
ViewTestData::createTestViews(self::class, ['views_test_config']);
$this->enableViewsTestModule();
$admin_user = $this->drupalCreateUser([
'administer site configuration',
'administer views',
'administer nodes',
'access content overview',
]);
// Disable automatic live preview to make the sequence of calls clearer.
\Drupal::configFactory()->getEditable('views.settings')->set('ui.always_live_preview', FALSE)->save();
$this->drupalLogin($admin_user);
}
/**
* Sets up the views_test_data.module.
*
* Because the schema of views_test_data.module is dependent on the test
* using it, it cannot be enabled normally.
*/
protected function enableViewsTestModule() {
// 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());
\Drupal::service('module_installer')->install(['views_test_data']);
$this->resetAll();
$this->rebuildContainer();
$this->container->get('module_handler')->reload();
// Load the test dataset.
$data_set = $this->dataSet();
$query = Database::getConnection()->insert('views_test_data')
->fields(array_keys($data_set[0]));
foreach ($data_set as $record) {
$query->values($record);
}
$query->execute();
}
/**
* Returns the schema definition.
*
* @internal
*/
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();
}
/**
* Tests the taxonomy term preview AJAX.
*
* This tests a specific regression in the taxonomy term view preview.
*
* @see https://www.drupal.org/node/2452659
*/
public function testTaxonomyAJAX(): void {
\Drupal::service('module_installer')->install(['taxonomy']);
$this->getPreviewAJAX('taxonomy_term', 'page_1', 0);
}
/**
* Tests pagers in the preview form.
*/
public function testPreviewWithPagersUI(): void {
// Create 11 nodes and make sure that everyone is returned.
$this->drupalCreateContentType(['type' => 'page']);
for ($i = 0; $i < 11; $i++) {
$this->drupalCreateNode();
}
// Test Full Pager.
$this->getPreviewAJAX('test_pager_full_ajax', 'default', 5);
// Test that the pager is present and rendered.
$elements = $this->xpath('//ul[contains(@class, "pager__items")]/li');
$this->assertNotEmpty($elements);
// Verify elements and links to pages.
// We expect to find 5 elements: current page == 1, links to pages 2 and
// and 3, links to 'next >' and 'last >>' pages.
$this->assertClass($elements[0], 'is-active', 'Element for current page has .is-active class.');
$this->assertNotEmpty($elements[0]->find('css', 'a'), 'Element for current page has link.');
$this->assertClass($elements[1], 'pager__item', 'Element for page 2 has .pager__item class.');
$this->assertNotEmpty($elements[1]->find('css', 'a'), 'Link to page 2 found.');
$this->assertClass($elements[2], 'pager__item', 'Element for page 3 has .pager__item class.');
$this->assertNotEmpty($elements[2]->find('css', 'a'), 'Link to page 3 found.');
$this->assertClass($elements[3], 'pager__item--next', 'Element for next page has .pager__item--next class.');
$this->assertNotEmpty($elements[3]->find('css', 'a'), 'Link to next page found.');
$this->assertClass($elements[4], 'pager__item--last', 'Element for last page has .pager__item--last class.');
$this->assertNotEmpty($elements[4]->find('css', 'a'), 'Link to last page found.');
// Navigate to next page.
$element = $this->assertSession()->elementExists('xpath', '//li[contains(@class, "pager__item--next")]/a');
$this->clickPreviewLinkAJAX($element, 5);
// Test that the pager is present and rendered.
$elements = $this->xpath('//ul[contains(@class, "pager__items")]/li');
$this->assertNotEmpty($elements);
// Verify elements and links to pages.
// We expect to find 7 elements: links to '<< first' and '< previous'
// pages, link to page 1, current page == 2, link to page 3 and links
// to 'next >' and 'last >>' pages.
$this->assertClass($elements[0], 'pager__item--first', 'Element for first page has .pager__item--first class.');
$this->assertNotEmpty($elements[0]->find('css', 'a'), 'Link to first page found.');
$this->assertClass($elements[1], 'pager__item--previous', 'Element for previous page has .pager__item--previous class.');
$this->assertNotEmpty($elements[1]->find('css', 'a'), 'Link to previous page found.');
$this->assertClass($elements[2], 'pager__item', 'Element for page 1 has .pager__item class.');
$this->assertNotEmpty($elements[2]->find('css', 'a'), 'Link to page 1 found.');
$this->assertClass($elements[3], 'is-active', 'Element for current page has .is-active class.');
$this->assertNotEmpty($elements[3]->find('css', 'a'), 'Element for current page has link.');
$this->assertClass($elements[4], 'pager__item', 'Element for page 3 has .pager__item class.');
$this->assertNotEmpty($elements[4]->find('css', 'a'), 'Link to page 3 found.');
$this->assertClass($elements[5], 'pager__item--next', 'Element for next page has .pager__item--next class.');
$this->assertNotEmpty($elements[5]->find('css', 'a'), 'Link to next page found.');
$this->assertClass($elements[6], 'pager__item--last', 'Element for last page has .pager__item--last class.');
$this->assertNotEmpty($elements[6]->find('css', 'a'), 'Link to last page found.');
// Test Mini Pager.
$this->getPreviewAJAX('test_mini_pager_ajax', 'default', 3);
// Test that the pager is present and rendered.
$elements = $this->xpath('//ul[contains(@class, "pager__items")]/li');
$this->assertNotEmpty($elements);
// Verify elements and links to pages.
// We expect to find current pages element with no link, next page element
// with a link, and not to find previous page element.
$this->assertEquals('Page 1', trim($elements[0]->getHtml()), 'Element for current page is not a link.');
$next_page_link = $elements[1]->find('css', 'a');
$this->assertNotEmpty($next_page_link, 'Link to next page found.');
$this->assertEquals('Go to next page', $next_page_link->getAttribute('title'));
// Navigate to next page.
$this->clickPreviewLinkAJAX($next_page_link, 3);
// Test that the pager is present and rendered.
$elements = $this->xpath('//ul[contains(@class, "pager__items")]/li');
$this->assertNotEmpty($elements);
// Verify elements and links to pages.
// We expect to find 3 elements: previous page with a link, current
// page with no link, and next page with a link.
$previous_page_link = $elements[0]->find('css', 'a');
$this->assertNotEmpty($previous_page_link, 'Link to previous page found.');
$this->assertEquals('Go to previous page', $previous_page_link->getAttribute('title'));
$this->assertEquals('Page 2', trim($elements[1]->getHtml()), 'Element for current page is not a link.');
$next_page_link = $elements[2]->find('css', 'a');
$this->assertNotEmpty($next_page_link, 'Link to next page found.');
$this->assertEquals('Go to next page', $next_page_link->getAttribute('title'));
}
/**
* Tests the link to sort in the preview form.
*/
public function testPreviewSortLink(): void {
// Get the preview.
$this->getPreviewAJAX('test_click_sort_ajax', 'page_1', 0);
// Test that the header label is present.
$element = $this->assertSession()->elementExists('xpath', '//th[contains(@class, "views-field views-field-name")]/a');
// Verify link.
$this->assertSession()->linkByHrefExists('preview/page_1?_wrapper_format=drupal_ajax&order=name&sort=desc', 0, 'The output URL is as expected.');
// Click link to sort.
$element->click();
$sort_link = $this->assertSession()->waitForElement('xpath', '//th[contains(@class, \'views-field views-field-name is-active\')]/a');
$this->assertNotEmpty($sort_link);
// Verify link.
$this->assertSession()->linkByHrefExists('preview/page_1?_wrapper_format=drupal_ajax&order=name&sort=asc', 0, 'The output URL is as expected.');
}
/**
* Get the preview form and force an AJAX preview update.
*
* @param string $view_name
* The view to test.
* @param string $panel_id
* The view panel to test.
* @param int $row_count
* The expected number of rows in the preview.
*/
protected function getPreviewAJAX($view_name, $panel_id, $row_count) {
$this->drupalGet('admin/structure/views/view/' . $view_name . '/edit/' . $panel_id);
$this->getSession()->getPage()->pressButton('Update preview');
$this->assertSession()->assertWaitOnAjaxRequest();
$this->assertPreviewAJAX($row_count);
}
/**
* Click on a preview link.
*
* @param \Behat\Mink\Element\NodeElement $element
* The element to click.
* @param int $row_count
* The expected number of rows in the preview.
*/
protected function clickPreviewLinkAJAX(NodeElement $element, $row_count) {
$element->click();
$this->assertSession()->assertWaitOnAjaxRequest();
$this->assertPreviewAJAX($row_count);
}
/**
* Assert that the preview contains expected data.
*
* @param int $row_count
* The expected number of rows in the preview.
*
* @internal
*/
protected function assertPreviewAJAX(int $row_count): void {
$elements = $this->getSession()->getPage()->findAll('css', '#views-live-preview .views-row');
$this->assertCount($row_count, $elements, 'Expected items found on page.');
}
/**
* Asserts that an element has a given class.
*
* @param \Behat\Mink\Element\NodeElement $element
* The element to test.
* @param string $class
* The class to assert.
* @param string $message
* (optional) A verbose message to output.
*
* @internal
*/
protected function assertClass(NodeElement $element, string $class, string $message = ''): void {
if (!isset($message)) {
$message = "Class .$class found.";
}
$this->assertStringContainsString($class, $element->getAttribute('class'), $message);
}
}

View File

@@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Tests the JavaScript filtering on the Views listing page.
*
* @see core/modules/views_ui/js/views_ui.listing.js
* @group views_ui
*/
class ViewsListingTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['node', 'views', 'views_ui'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$admin_user = $this->drupalCreateUser([
'administer site configuration',
'administer views',
]);
$this->drupalLogin($admin_user);
}
/**
* Tests the filtering on the Views listing page.
*/
public function testFilterViewsListing(): void {
$enabled_views_count = 6;
$disabled_views_count = 2;
$content_views_count = 3;
$this->drupalGet('admin/structure/views');
$session = $this->assertSession();
$page = $this->getSession()->getPage();
// Test that we search in both the enabled and disabled rows.
$enabled_rows = $page->findAll('css', 'tr.views-ui-list-enabled');
$enabled_rows = $this->filterVisibleElements($enabled_rows);
$disabled_rows = $page->findAll('css', 'tr.views-ui-list-disabled');
$disabled_rows = $this->filterVisibleElements($disabled_rows);
// Test that we see some rows of views in both tables.
$this->assertCount($enabled_views_count, $enabled_rows);
$this->assertCount($disabled_views_count, $disabled_rows);
// Filter on the string 'people'. This should only show the people view.
$search_input = $page->find('css', '.views-filter-text.form-search');
$search_input->setValue('people');
$enabled_rows = $page->findAll('css', 'tr.views-ui-list-enabled');
$enabled_rows = $this->filterVisibleElements($enabled_rows);
$disabled_rows = $page->findAll('css', 'tr.views-ui-list-disabled');
$disabled_rows = $this->filterVisibleElements($disabled_rows);
$this->assertCount(1, $enabled_rows);
$this->assertCount(0, $disabled_rows);
// Filter on a string that also appears in the description.
$search_input->setValue('content');
$enabled_rows = $page->findAll('css', 'tr.views-ui-list-enabled');
$enabled_rows = $this->filterVisibleElements($enabled_rows);
$disabled_rows = $page->findAll('css', 'tr.views-ui-list-disabled');
$disabled_rows = $this->filterVisibleElements($disabled_rows);
$this->assertCount($content_views_count, $enabled_rows);
$this->assertCount($disabled_views_count, $disabled_rows);
// Reset the search string and check that we are back to the initial stage.
$search_input->setValue('');
// Add a backspace to trigger the keyUp event.
$search_input->keyUp(8);
$enabled_rows = $page->findAll('css', 'tr.views-ui-list-enabled');
$enabled_rows = $this->filterVisibleElements($enabled_rows);
$disabled_rows = $page->findAll('css', 'tr.views-ui-list-disabled');
$disabled_rows = $this->filterVisibleElements($disabled_rows);
$this->assertCount($enabled_views_count, $enabled_rows);
$this->assertCount($disabled_views_count, $disabled_rows);
// Disable a View and see if it moves to the disabled listing.
$enabled_view = $page->find('css', 'tr.views-ui-list-enabled');
$view_description = $enabled_view->find('css', '.views-ui-view-name strong')->getText();
// Open the dropdown with additional actions.
$enabled_view->find('css', 'li.dropbutton-toggle button')->click();
$disable_button = $enabled_view->findLink('Disable');
// Check that the disable button is visible now.
$this->assertTrue($disable_button->isVisible());
$disable_button->click();
$session->assertWaitOnAjaxRequest();
$enabled_rows = $page->findAll('css', 'tr.views-ui-list-enabled');
$enabled_rows = $this->filterVisibleElements($enabled_rows);
$disabled_rows = $page->findAll('css', 'tr.views-ui-list-disabled');
$disabled_rows = $this->filterVisibleElements($disabled_rows);
// Test that one enabled View has been moved to the disabled list.
$this->assertCount($enabled_views_count - 1, $enabled_rows);
$this->assertCount($disabled_views_count + 1, $disabled_rows);
// Test that the keyboard focus is on the dropdown button of the View we
// just disabled.
$this->assertTrue($this->getSession()->evaluateScript("jQuery(document.activeElement).text() === 'Enable'"));
$this->assertEquals($view_description, $this->getSession()->evaluateScript("jQuery(document.activeElement).parents('tr').find('.views-ui-view-name strong').text()"));
// Enable the view again and ensure we have the focus on the edit button.
$this->getSession()->evaluateScript('jQuery(document.activeElement).click()');
$session->assertWaitOnAjaxRequest();
$this->assertTrue($this->getSession()->evaluateScript("jQuery(document.activeElement).text() === 'Edit'"));
$this->assertEquals($view_description, $this->getSession()->evaluateScript("jQuery(document.activeElement).parents('tr').find('.views-ui-view-name strong').text()"));
}
/**
* Removes any non-visible elements from the passed array.
*
* @param array $elements
* The elements.
*
* @return array
*/
protected function filterVisibleElements($elements) {
$elements = array_filter($elements, function ($element) {
return $element->isVisible();
});
return $elements;
}
}

View File

@@ -0,0 +1,97 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\FunctionalJavascript;
use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
/**
* Tests views creation wizard.
*
* @see core/modules/views_ui/js/views-admin.js
* @group views_ui
*/
class ViewsWizardTest extends WebDriverTestBase {
/**
* {@inheritdoc}
*/
protected static $modules = ['node', 'views', 'views_ui', 'block', 'user'];
/**
* {@inheritdoc}
*/
protected $defaultTheme = 'stark';
/**
* {@inheritdoc}
*/
protected function setUp(): void {
parent::setUp();
$admin_user = $this->drupalCreateUser([
'access administration pages',
'administer views',
]);
$this->drupalLogin($admin_user);
}
/**
* Tests creating a View using the wizard.
*/
public function testCreateViewWizard(): void {
$this->drupalGet('admin/structure/views/add');
$page = $this->getSession()->getPage();
// Set a view name, this should be used to prepopulate a number of other
// fields when creating displays.
$label_value = 'test view';
$search_input = $page->findField('label');
$search_input->setValue($label_value);
$page->findField('page[create]')->click();
// Test if the title and path have been populated.
$this->assertEquals($label_value, $page->findField('page[title]')->getValue());
$this->assertEquals(str_replace(' ', '-', $label_value), $page->findField('page[path]')->getValue());
// Create a menu item.
$page->findField('page[link]')->click();
$this->assertEquals($label_value, $page->findField('page[link_properties][title]')->getValue());
// Wait for conditional field to show.
$this->assertSession()->waitForElementVisible('named', ['select', 'page[link_properties][parent]']);
// Assert a menu can be selected as a parent.
$this->assertSession()->optionExists('page[link_properties][parent]', 'admin:');
// Assert a parent menu item can be selected from within a menu.
$this->assertSession()->optionExists('page[link_properties][parent]', 'admin:entity.view.collection');
// Add a block display.
$page->findField('block[create]')->click();
$this->assertEquals($label_value, $page->findField('block[title]')->getValue());
// Select the entity type to display and test that the type selector is
// shown when expected.
$this->assertSame('node', $page->findField('show[wizard_key]')->getValue());
$this->assertNull($page->findField('show[type]'), 'The "of type" filter is not added for nodes when there are no node types.');
$this->assertEquals('teasers', $page->findField('page[style][row_plugin]')->getValue(), 'The page display format shows the expected default value.');
$this->assertEquals('titles_linked', $page->findField('block[style][row_plugin]')->getValue(), 'The block display format shows the expected default value.');
$page->selectFieldOption('show[wizard_key]', 'users');
$this->assertSession()->assertWaitOnAjaxRequest();
$this->assertNull($page->findField('show[type]'), 'The "of type" filter is not added for users.');
$this->assertEquals('fields', $page->findField('page[style][row_plugin]')->getValue(), 'The page display format was updated to a valid value.');
$this->assertEquals('fields', $page->findField('block[style][row_plugin]')->getValue(), 'The block display format was updated to a valid value.');
$this->drupalCreateContentType(['type' => 'page']);
$page->selectFieldOption('show[wizard_key]', 'node');
$this->assertSession()->assertWaitOnAjaxRequest();
$this->assertNotNull($page->findField('show[type]'), 'The "of type" filter is added for nodes when there is at least one node type.');
$this->assertEquals('fields', $page->findField('page[style][row_plugin]')->getValue(), 'The page display format was not changed from a valid value.');
$this->assertEquals('fields', $page->findField('block[style][row_plugin]')->getValue(), 'The block display format was not changed from a valid value.');
}
}

View File

@@ -0,0 +1,119 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Kernel;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\views_ui\Controller\ViewsUIController;
use Drupal\Component\Utility\Html;
use Drupal\views\Entity\View;
/**
* Tests the views ui tagging functionality.
*
* @group views_ui
* @group #slow
*/
class TagTest extends ViewsKernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['views', 'views_ui', 'user'];
/**
* Tests the ViewsUIController::autocompleteTag() function.
*/
public function testViewsUiAutocompleteTag(): void {
\Drupal::moduleHandler()->loadInclude('views_ui', 'inc', 'admin');
// Save 15 views with a tag.
$tags = [];
for ($i = 0; $i < 16; $i++) {
$suffix = $i % 2 ? 'odd' : 'even';
$tag = 'autocomplete_tag_test_' . $suffix . $this->randomMachineName();
$tags[] = $tag;
View::create([
'tag' => $tag,
'id' => $this->randomMachineName(),
'label' => 'Test',
])->save();
}
// Make sure just ten results are returned.
$controller = ViewsUIController::create($this->container);
$request = $this->container->get('request_stack')->getCurrentRequest();
$request->query->set('q', 'autocomplete_tag_test');
$result = $controller->autocompleteTag($request);
$matches = (array) json_decode($result->getContent(), TRUE);
$this->assertCount(10, $matches, 'Make sure the maximum amount of tag results is 10.');
// Make sure the returned array has the proper format.
$suggestions = array_map(function ($tag) {
return ['value' => $tag, 'label' => Html::escape($tag)];
}, $tags);
foreach ($matches as $match) {
$this->assertContains($match, $suggestions, 'Make sure the returned array has the proper format.');
}
// Make sure that matching by a certain prefix works.
$request->query->set('q', 'autocomplete_tag_test_even');
$result = $controller->autocompleteTag($request);
$matches = (array) json_decode($result->getContent(), TRUE);
$this->assertCount(8, $matches, 'Make sure that only a subset is returned.');
foreach ($matches as $tag) {
$this->assertContains($tag['value'], $tags);
}
// Make sure an invalid result doesn't return anything.
$request->query->set('q', $this->randomMachineName());
$result = $controller->autocompleteTag($request);
$matches = (array) json_decode($result->getContent());
$this->assertCount(0, $matches, "Make sure an invalid tag doesn't return anything.");
}
/**
* Tests that comma delimited tags are treated as individual tags.
*
* @dataProvider providerViewsUiAutocompleteIndividualTags
*/
public function testViewsUiAutocompleteIndividualTags($expected_tag, $search_string): void {
$controller = ViewsUIController::create($this->container);
$request = $this->container->get('request_stack')->getCurrentRequest();
$tag = 'comma, 你好, Foo bar';
View::create([
'tag' => $tag,
'id' => $this->randomMachineName(),
'label' => 'Test',
])->save();
$request->query->set('q', $search_string);
$result = $controller->autocompleteTag($request);
$matches = (array) json_decode($result->getContent());
$this->assertCount(1, $matches);
$this->assertSame($expected_tag, $matches[0]->value);
}
/**
* Data provider for testViewsUiAutocompleteIndividualTags().
*
* @return array[]
* The data set.
*/
public static function providerViewsUiAutocompleteIndividualTags() {
return [
'tag' => ['comma', 'comma'],
'case insensitive tag' => ['comma', 'COMMA'],
'Hello in Chinese (partial 1)' => ['你好', '你'],
'Hello in Chinese (partial 2)' => ['你好', '好'],
'Hello in Chinese' => ['你好', '你好'],
'Starts with partial and case-sensitive' => ['Foo bar', 'Foo'],
'Starts with partial and case-insensitive' => ['Foo bar', 'fOO'],
'Ends with partial and case-sensitive' => ['Foo bar', 'bar'],
'Ends with partial and case-insensitive' => ['Foo bar', 'BAR'],
];
}
}

View File

@@ -0,0 +1,34 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Kernel;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
/**
* Tests the deprecation of views_ui_truncate() function.
*
* @group views_ui
*/
class TruncateDeprecateTest extends ViewsKernelTestBase {
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = ['views', 'views_ui'];
/**
* Tests the deprecation of views_ui_truncate() replaced by Unicode::truncate.
*
* @group legacy
*/
public function testDeprecateViewsUiTruncate(): void {
$string = 'one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen';
$short_string = views_ui_truncate($string, 80);
$this->expectDeprecation('views_ui_truncate() is deprecated in drupal:10.3.0 and is removed from drupal:12.0.0. Use \Drupal\Component\Utility\Unicode::truncate(). See https://www.drupal.org/node/3408283');
}
}

View File

@@ -0,0 +1,82 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Kernel;
use Drupal\Core\Url;
use Drupal\block\Entity\Block;
use Drupal\Tests\user\Traits\UserCreationTrait;
use Drupal\Tests\views\Kernel\ViewsKernelTestBase;
use Drupal\views\Tests\ViewTestData;
/**
* Tests ViewsBlock.
*
* @group views_ui
*/
class ViewsBlockTest extends ViewsKernelTestBase {
use UserCreationTrait;
/**
* Modules to enable.
*
* @var array
*/
protected static $modules = [
'system',
'block',
'block_test_views',
'views_ui',
'user',
];
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = ['test_view_block'];
/**
* {@inheritdoc}
*/
protected function setUp($import_test_views = TRUE): void {
parent::setUp();
ViewTestData::createTestViews(static::class, ['block_test_views']);
}
/**
* Tests the editing links for ViewsBlockBase.
*/
public function testOperationLinks(): void {
$this->setUpCurrentUser(['uid' => 0]);
$block = Block::create([
'plugin' => 'views_block:test_view_block-block_1',
'region' => 'content',
'id' => 'machine_name',
'theme' => 'stark',
]);
// The anonymous user doesn't have the "administer block" permission.
$this->assertEmpty(views_ui_entity_operation($block));
$this->setUpCurrentUser(['uid' => 1], ['administer views']);
// The admin user does have the "administer block" permission.
$this->assertEquals([
'view-edit' => [
'title' => 'Edit view',
'url' => Url::fromRoute('entity.view.edit_display_form', [
'view' => 'test_view_block',
'display_id' => 'block_1',
]),
'weight' => 50,
],
], views_ui_entity_operation($block));
}
}

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Unit\Form\Ajax;
use Drupal\Tests\UnitTestCase;
use Drupal\views_ui\Form\Ajax\RearrangeFilter;
/**
* Unit tests for Views UI module functions.
*
* @group views_ui
*/
class RearrangeFilterTest extends UnitTestCase {
/**
* Tests static methods.
*/
public function testStaticMethods(): void {
// Test the RearrangeFilter::arrayKeyPlus method.
$original = [0 => 'one', 1 => 'two', 2 => 'three'];
$expected = [1 => 'one', 2 => 'two', 3 => 'three'];
$this->assertSame(RearrangeFilter::arrayKeyPlus($original), $expected);
}
}

View File

@@ -0,0 +1,198 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Unit;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Menu\MenuParentFormSelector;
use Drupal\Tests\UnitTestCase;
use Drupal\views\Entity\View;
use Drupal\views\ViewExecutableFactory;
use Drupal\views_ui\ViewListBuilder;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
/**
* @coversDefaultClass \Drupal\views_ui\ViewListBuilder
* @group views_ui
*/
class ViewListBuilderTest extends UnitTestCase {
/**
* Tests the listing of displays on a views list builder.
*
* @see \Drupal\views_ui\ViewListBuilder::getDisplaysList()
* @covers ::buildRow
*/
public function testBuildRowEntityList(): void {
$storage = $this->getMockBuilder('Drupal\Core\Config\Entity\ConfigEntityStorage')
->disableOriginalConstructor()
->getMock();
$display_manager = $this->getMockBuilder('\Drupal\views\Plugin\ViewsPluginManager')
->disableOriginalConstructor()
->getMock();
$display_manager->expects($this->any())
->method('getDefinition')
->willReturnMap([
[
'default',
TRUE,
[
'id' => 'default',
'title' => 'Default',
'theme' => 'views_view',
'no_ui' => TRUE,
'admin' => '',
],
],
[
'page',
TRUE,
[
'id' => 'page',
'title' => 'Page',
'uses_menu_links' => TRUE,
'uses_route' => TRUE,
'contextual_links_locations' => ['page'],
'theme' => 'views_view',
'admin' => 'Page admin label',
],
],
[
'embed',
TRUE,
[
'id' => 'embed',
'title' => 'embed',
'theme' => 'views_view',
'admin' => 'Embed admin label',
],
],
]);
$default_display = $this->getMockBuilder('Drupal\views\Plugin\views\display\DefaultDisplay')
->onlyMethods(['initDisplay'])
->setConstructorArgs([[], 'default', $display_manager->getDefinition('default')])
->getMock();
$route_provider = $this->createMock('Drupal\Core\Routing\RouteProviderInterface');
$state = $this->createMock('\Drupal\Core\State\StateInterface');
$menu_storage = $this->createMock('\Drupal\Core\Entity\EntityStorageInterface');
$parent_form_selector = $this->createMock(MenuParentFormSelector::class);
$page_display = $this->getMockBuilder('Drupal\views\Plugin\views\display\Page')
->onlyMethods(['initDisplay', 'getPath'])
->setConstructorArgs([[], 'default', $display_manager->getDefinition('page'), $route_provider, $state, $menu_storage, $parent_form_selector])
->getMock();
$page_display->expects($this->any())
->method('getPath')
->willReturnOnConsecutiveCalls(
'test_page',
'<object>malformed_path</object>',
'<script>alert("placeholder_page/%")</script>',
);
$embed_display = $this->getMockBuilder('Drupal\views\Plugin\views\display\Embed')
->onlyMethods(['initDisplay'])
->setConstructorArgs([[], 'default', $display_manager->getDefinition('embed')])
->getMock();
$values = [];
$values['label'] = 'Test';
$values['status'] = FALSE;
$values['display']['default']['id'] = 'default';
$values['display']['default']['display_title'] = 'Display';
$values['display']['default']['display_plugin'] = 'default';
$values['display']['page_1']['id'] = 'page_1';
$values['display']['page_1']['display_title'] = 'Page 1';
$values['display']['page_1']['display_plugin'] = 'page';
$values['display']['page_1']['display_options']['path'] = 'test_page';
$values['display']['page_2']['id'] = 'page_2';
$values['display']['page_2']['display_title'] = 'Page 2';
$values['display']['page_2']['display_plugin'] = 'page';
$values['display']['page_2']['display_options']['path'] = '<object>malformed_path</object>';
$values['display']['page_3']['id'] = 'page_3';
$values['display']['page_3']['display_title'] = 'Page 3';
$values['display']['page_3']['display_plugin'] = 'page';
$values['display']['page_3']['display_options']['path'] = '<script>alert("placeholder_page/%")</script>';
$values['display']['embed']['id'] = 'embed';
$values['display']['embed']['display_title'] = 'Embedded';
$values['display']['embed']['display_plugin'] = 'embed';
$display_manager->expects($this->any())
->method('createInstance')
->willReturnMap([
['default', $values['display']['default'], $default_display],
['page', $values['display']['page_1'], $page_display],
['page', $values['display']['page_2'], $page_display],
['page', $values['display']['page_3'], $page_display],
['embed', $values['display']['embed'], $embed_display],
]);
$container = new ContainerBuilder();
$user = $this->createMock('Drupal\Core\Session\AccountInterface');
$request_stack = new RequestStack();
$request_stack->push(new Request());
$views_data = $this->getMockBuilder('Drupal\views\ViewsData')
->disableOriginalConstructor()
->getMock();
$route_provider = $this->createMock('Drupal\Core\Routing\RouteProviderInterface');
$executable_factory = new ViewExecutableFactory($user, $request_stack, $views_data, $route_provider, $display_manager);
$entity_type_manager = $this->prophesize(EntityTypeManagerInterface::class);
$container->set('views.executable', $executable_factory);
$container->set('plugin.manager.views.display', $display_manager);
$container->set('entity_type.manager', $entity_type_manager->reveal());
\Drupal::setContainer($container);
// Setup a view list builder with a mocked buildOperations method,
// because t() is called on there.
$entity_type = $this->createMock('Drupal\Core\Entity\EntityTypeInterface');
$entity_type_manager->getDefinition('view')->willReturn($entity_type);
$view_list_builder = new TestViewListBuilder($entity_type, $storage, $display_manager);
$view_list_builder->setStringTranslation($this->getStringTranslationStub());
// Create new view with test values.
$view = new View($values, 'view');
// Get the row object created by ViewListBuilder for this test view.
$row = $view_list_builder->buildRow($view);
// Expected output array for view's displays.
$expected_displays = [
'0' => [
'display' => 'Embed admin label',
'path' => FALSE,
],
'1' => [
'display' => 'Page admin label',
'path' => '/<object>malformed_path</object>',
],
'2' => [
'display' => 'Page admin label',
'path' => '/<script>alert("placeholder_page/%")</script>',
],
'3' => [
'display' => 'Page admin label',
'path' => '/test_page',
],
];
// Compare the expected and generated output.
$this->assertEquals($expected_displays, $row['data']['displays']['data']['#displays']);
}
}
class TestViewListBuilder extends ViewListBuilder {
public function buildOperations(EntityInterface $entity) {
return [];
}
}

View File

@@ -0,0 +1,140 @@
<?php
declare(strict_types=1);
namespace Drupal\Tests\views_ui\Unit;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\TempStore\Lock;
use Drupal\Tests\UnitTestCase;
use Drupal\views\Entity\View;
use Drupal\views_ui\ViewUI;
use Symfony\Component\DependencyInjection\ContainerBuilder;
/**
* @coversDefaultClass \Drupal\views_ui\ViewUI
* @group views_ui
*/
class ViewUIObjectTest extends UnitTestCase {
/**
* Tests entity method decoration.
*/
public function testEntityDecoration(): void {
$method_args = [];
$method_args['setOriginalId'] = [12];
$method_args['setStatus'] = [TRUE];
$method_args['enforceIsNew'] = [FALSE];
$method_args['label'] = [LanguageInterface::LANGCODE_NOT_SPECIFIED];
$reflection = new \ReflectionClass('Drupal\Core\Config\Entity\ConfigEntityInterface');
$interface_methods = [];
foreach ($reflection->getMethods() as $reflection_method) {
$interface_methods[] = $reflection_method->getName();
// EntityInterface::isNew() is missing from the list of methods, because it
// calls id(), which breaks the ->expect($this->once()) call. Call it later.
// EntityInterface::isSyncing() is only called during syncing process.
// EntityInterface::isUninstalling() is only called during uninstallation
// process. EntityInterface::getConfigDependencyName() and
// ConfigEntityInterface::calculateDependencies() are only used for
// dependency management.
if (!in_array($reflection_method->getName(), ['isNew', 'isSyncing', 'isUninstalling', 'getConfigDependencyKey', 'getConfigDependencyName', 'calculateDependencies'])) {
if (count($reflection_method->getParameters()) == 0) {
$method_args[$reflection_method->getName()] = [];
}
}
}
$storage = $this->getMockBuilder('Drupal\views\Entity\View')
->onlyMethods($interface_methods)
->setConstructorArgs([[], 'view'])
->getMock();
$executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
->disableOriginalConstructor()
->setConstructorArgs([$storage])
->getMock();
$storage->set('executable', $executable);
$view_ui = new ViewUI($storage);
foreach ($method_args as $method => $args) {
$method_mock = $storage->expects($this->once())
->method($method);
foreach ($args as $arg) {
$method_mock->with($this->equalTo($arg));
}
call_user_func_array([$view_ui, $method], $args);
}
$storage->expects($this->once())
->method('isNew');
$view_ui->isNew();
}
/**
* Tests the isLocked method.
*/
public function testIsLocked(): void {
$storage = $this->getMockBuilder('Drupal\views\Entity\View')
->setConstructorArgs([[], 'view'])
->getMock();
$executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
->disableOriginalConstructor()
->setConstructorArgs([$storage])
->getMock();
$storage->set('executable', $executable);
$account = $this->createMock('Drupal\Core\Session\AccountInterface');
$account->expects($this->exactly(2))
->method('id')
->willReturn(1);
$container = new ContainerBuilder();
$container->set('current_user', $account);
\Drupal::setContainer($container);
$view_ui = new ViewUI($storage);
// A view_ui without a lock object is not locked.
$this->assertFalse($view_ui->isLocked());
// Set the lock object with a different owner than the mocked account above.
$lock = new Lock(2, (int) $_SERVER['REQUEST_TIME']);
$view_ui->setLock($lock);
$this->assertTrue($view_ui->isLocked());
// Set a different lock object with the same object as the mocked account.
$lock = new Lock(1, (int) $_SERVER['REQUEST_TIME']);
$view_ui->setLock($lock);
$this->assertFalse($view_ui->isLocked());
$view_ui->unsetLock(NULL);
$this->assertFalse($view_ui->isLocked());
}
/**
* Tests serialization of the ViewUI object.
*/
public function testSerialization(): void {
$storage = new View([], 'view');
$executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
->disableOriginalConstructor()
->setConstructorArgs([$storage])
->getMock();
$storage->set('executable', $executable);
$view_ui = new ViewUI($storage);
// Make sure the executable is returned before serializing.
$this->assertInstanceOf('Drupal\views\ViewExecutable', $view_ui->getExecutable());
$serialized = serialize($view_ui);
// Make sure the ViewExecutable class is not found in the serialized string.
$this->assertStringNotContainsString('"Drupal\views\ViewExecutable"', $serialized);
$unserialized = unserialize($serialized);
$this->assertInstanceOf('Drupal\views_ui\ViewUI', $unserialized);
}
}

View File

@@ -0,0 +1,11 @@
name: 'Views UI Test Theme'
type: theme
description: 'Test theme for testing Views UI.'
# version: VERSION
base theme: stark
package: Testing
# Information added by Drupal.org packaging script on 2024-07-04
version: '10.3.1'
project: 'drupal'
datestamp: 1720094222

View File

@@ -0,0 +1,22 @@
<?php
/**
* @file
* Implements Views UI alter hooks in a theme to provide test coverage.
*/
use Drupal\views_ui\ViewUI;
/**
* Implements hook_views_ui_display_tab_alter().
*/
function views_ui_test_theme_views_ui_display_tab_alter(&$build, ViewUI $view, $display_id) {
$build['details']['top']['display_title']['#description'] = 'This is text added to the display edit form';
}
/**
* Implements hook_views_ui_display_top_alter().
*/
function views_ui_test_theme_views_ui_display_top_alter(&$build, ViewUI $view, $display_id) {
$build['tabs']['#suffix'] .= 'This is text added to the display tabs at the top';
}