1<?php 2 3namespace Drupal\Tests\views_ui\FunctionalJavascript; 4 5use Behat\Mink\Element\NodeElement; 6use Drupal\Core\Database\Database; 7use Drupal\FunctionalJavascriptTests\WebDriverTestBase; 8use Drupal\views\Tests\ViewTestData; 9 10/** 11 * Tests the UI preview functionality. 12 * 13 * @group views_ui 14 */ 15class PreviewTest extends WebDriverTestBase { 16 17 /** 18 * Views used by this test. 19 * 20 * @var array 21 */ 22 public static $testViews = ['test_preview', 'test_pager_full_ajax', 'test_mini_pager_ajax', 'test_click_sort_ajax']; 23 24 /** 25 * {@inheritdoc} 26 */ 27 public static $modules = [ 28 'node', 29 'views', 30 'views_ui', 31 'views_test_config', 32 ]; 33 34 /** 35 * {@inheritdoc} 36 */ 37 protected $defaultTheme = 'classy'; 38 39 /** 40 * {@inheritdoc} 41 */ 42 public function setUp() { 43 parent::setUp(); 44 45 ViewTestData::createTestViews(self::class, ['views_test_config']); 46 47 $this->enableViewsTestModule(); 48 49 $admin_user = $this->drupalCreateUser([ 50 'administer site configuration', 51 'administer views', 52 'administer nodes', 53 'access content overview', 54 ]); 55 56 // Disable automatic live preview to make the sequence of calls clearer. 57 \Drupal::configFactory()->getEditable('views.settings')->set('ui.always_live_preview', FALSE)->save(); 58 $this->drupalLogin($admin_user); 59 } 60 61 /** 62 * Sets up the views_test_data.module. 63 * 64 * Because the schema of views_test_data.module is dependent on the test 65 * using it, it cannot be enabled normally. 66 */ 67 protected function enableViewsTestModule() { 68 // Define the schema and views data variable before enabling the test module. 69 \Drupal::state()->set('views_test_data_schema', $this->schemaDefinition()); 70 \Drupal::state()->set('views_test_data_views_data', $this->viewsData()); 71 72 \Drupal::service('module_installer')->install(['views_test_data']); 73 $this->resetAll(); 74 $this->rebuildContainer(); 75 $this->container->get('module_handler')->reload(); 76 77 // Load the test dataset. 78 $data_set = $this->dataSet(); 79 $query = Database::getConnection()->insert('views_test_data') 80 ->fields(array_keys($data_set[0])); 81 foreach ($data_set as $record) { 82 $query->values($record); 83 } 84 $query->execute(); 85 } 86 87 /** 88 * Returns the schema definition. 89 * 90 * @internal 91 */ 92 protected function schemaDefinition() { 93 return ViewTestData::schemaDefinition(); 94 } 95 96 /** 97 * Returns the views data definition. 98 */ 99 protected function viewsData() { 100 return ViewTestData::viewsData(); 101 } 102 103 /** 104 * Returns a very simple test dataset. 105 */ 106 protected function dataSet() { 107 return ViewTestData::dataSet(); 108 } 109 110 /** 111 * Tests the taxonomy term preview AJAX. 112 * 113 * This tests a specific regression in the taxonomy term view preview. 114 * 115 * @see https://www.drupal.org/node/2452659 116 */ 117 public function testTaxonomyAJAX() { 118 \Drupal::service('module_installer')->install(['taxonomy']); 119 $this->getPreviewAJAX('taxonomy_term', 'page_1', 0); 120 } 121 122 /** 123 * Tests pagers in the preview form. 124 */ 125 public function testPreviewWithPagersUI() { 126 // Create 11 nodes and make sure that everyone is returned. 127 $this->drupalCreateContentType(['type' => 'page']); 128 for ($i = 0; $i < 11; $i++) { 129 $this->drupalCreateNode(); 130 } 131 132 // Test Full Pager. 133 $this->getPreviewAJAX('test_pager_full_ajax', 'default', 5); 134 135 // Test that the pager is present and rendered. 136 $elements = $this->xpath('//ul[contains(@class, :class)]/li', [':class' => 'pager__items']); 137 $this->assertTrue(!empty($elements), 'Full pager found.'); 138 139 // Verify elements and links to pages. 140 // We expect to find 5 elements: current page == 1, links to pages 2 and 141 // and 3, links to 'next >' and 'last >>' pages. 142 $this->assertClass($elements[0], 'is-active', 'Element for current page has .is-active class.'); 143 $this->assertNotEmpty($elements[0]->find('css', 'a'), 'Element for current page has link.'); 144 145 $this->assertClass($elements[1], 'pager__item', 'Element for page 2 has .pager__item class.'); 146 $this->assertNotEmpty($elements[1]->find('css', 'a'), 'Link to page 2 found.'); 147 148 $this->assertClass($elements[2], 'pager__item', 'Element for page 3 has .pager__item class.'); 149 $this->assertNotEmpty($elements[2]->find('css', 'a'), 'Link to page 3 found.'); 150 151 $this->assertClass($elements[3], 'pager__item--next', 'Element for next page has .pager__item--next class.'); 152 $this->assertNotEmpty($elements[3]->find('css', 'a'), 'Link to next page found.'); 153 154 $this->assertClass($elements[4], 'pager__item--last', 'Element for last page has .pager__item--last class.'); 155 $this->assertNotEmpty($elements[4]->find('css', 'a'), 'Link to last page found.'); 156 157 // Navigate to next page. 158 $elements = $this->xpath('//li[contains(@class, :class)]/a', [':class' => 'pager__item--next']); 159 $this->clickPreviewLinkAJAX($elements[0], 5); 160 161 // Test that the pager is present and rendered. 162 $elements = $this->xpath('//ul[contains(@class, :class)]/li', [':class' => 'pager__items']); 163 $this->assertTrue(!empty($elements), 'Full pager found.'); 164 165 // Verify elements and links to pages. 166 // We expect to find 7 elements: links to '<< first' and '< previous' 167 // pages, link to page 1, current page == 2, link to page 3 and links 168 // to 'next >' and 'last >>' pages. 169 $this->assertClass($elements[0], 'pager__item--first', 'Element for first page has .pager__item--first class.'); 170 $this->assertNotEmpty($elements[0]->find('css', 'a'), 'Link to first page found.'); 171 172 $this->assertClass($elements[1], 'pager__item--previous', 'Element for previous page has .pager__item--previous class.'); 173 $this->assertNotEmpty($elements[1]->find('css', 'a'), 'Link to previous page found.'); 174 175 $this->assertClass($elements[2], 'pager__item', 'Element for page 1 has .pager__item class.'); 176 $this->assertNotEmpty($elements[2]->find('css', 'a'), 'Link to page 1 found.'); 177 178 $this->assertClass($elements[3], 'is-active', 'Element for current page has .is-active class.'); 179 $this->assertNotEmpty($elements[3]->find('css', 'a'), 'Element for current page has link.'); 180 181 $this->assertClass($elements[4], 'pager__item', 'Element for page 3 has .pager__item class.'); 182 $this->assertNotEmpty($elements[4]->find('css', 'a'), 'Link to page 3 found.'); 183 184 $this->assertClass($elements[5], 'pager__item--next', 'Element for next page has .pager__item--next class.'); 185 $this->assertNotEmpty($elements[5]->find('css', 'a'), 'Link to next page found.'); 186 187 $this->assertClass($elements[6], 'pager__item--last', 'Element for last page has .pager__item--last class.'); 188 $this->assertNotEmpty($elements[6]->find('css', 'a'), 'Link to last page found.'); 189 190 // Test Mini Pager. 191 $this->getPreviewAJAX('test_mini_pager_ajax', 'default', 3); 192 193 // Test that the pager is present and rendered. 194 $elements = $this->xpath('//ul[contains(@class, :class)]/li', [':class' => 'pager__items']); 195 $this->assertTrue(!empty($elements), 'Mini pager found.'); 196 197 // Verify elements and links to pages. 198 // We expect to find current pages element with no link, next page element 199 // with a link, and not to find previous page element. 200 $this->assertClass($elements[0], 'is-active', 'Element for current page has .is-active class.'); 201 202 $this->assertClass($elements[1], 'pager__item--next', 'Element for next page has .pager__item--next class.'); 203 $this->assertNotEmpty($elements[1]->find('css', 'a'), 'Link to next page found.'); 204 205 // Navigate to next page. 206 $elements = $this->xpath('//li[contains(@class, :class)]/a', [':class' => 'pager__item--next']); 207 $this->clickPreviewLinkAJAX($elements[0], 3); 208 209 // Test that the pager is present and rendered. 210 $elements = $this->xpath('//ul[contains(@class, :class)]/li', [':class' => 'pager__items']); 211 $this->assertTrue(!empty($elements), 'Mini pager found.'); 212 213 // Verify elements and links to pages. 214 // We expect to find 3 elements: previous page with a link, current 215 // page with no link, and next page with a link. 216 $this->assertClass($elements[0], 'pager__item--previous', 'Element for previous page has .pager__item--previous class.'); 217 $this->assertNotEmpty($elements[0]->find('css', 'a'), 'Link to previous page found.'); 218 219 $this->assertClass($elements[1], 'is-active', 'Element for current page has .is-active class.'); 220 $this->assertEmpty($elements[1]->find('css', 'a'), 'Element for current page has no link.'); 221 222 $this->assertClass($elements[2], 'pager__item--next', 'Element for next page has .pager__item--next class.'); 223 $this->assertNotEmpty($elements[2]->find('css', 'a'), 'Link to next page found.'); 224 } 225 226 /** 227 * Tests the link to sort in the preview form. 228 */ 229 public function testPreviewSortLink() { 230 // Get the preview. 231 $this->getPreviewAJAX('test_click_sort_ajax', 'page_1', 0); 232 233 // Test that the header label is present. 234 $elements = $this->xpath('//th[contains(@class, :class)]/a', [':class' => 'views-field views-field-name']); 235 $this->assertTrue(!empty($elements), 'The header label is present.'); 236 237 // Verify link. 238 $this->assertLinkByHref('preview/page_1?_wrapper_format=drupal_ajax&order=name&sort=desc', 0, 'The output URL is as expected.'); 239 240 // Click link to sort. 241 $elements[0]->click(); 242 $sort_link = $this->assertSession()->waitForElement('xpath', '//th[contains(@class, \'views-field views-field-name is-active\')]/a'); 243 244 $this->assertNotEmpty($sort_link); 245 246 // Verify link. 247 $this->assertLinkByHref('preview/page_1?_wrapper_format=drupal_ajax&order=name&sort=asc', 0, 'The output URL is as expected.'); 248 } 249 250 /** 251 * Get the preview form and force an AJAX preview update. 252 * 253 * @param string $view_name 254 * The view to test. 255 * @param string $panel_id 256 * The view panel to test. 257 * @param int $row_count 258 * The expected number of rows in the preview. 259 */ 260 protected function getPreviewAJAX($view_name, $panel_id, $row_count) { 261 $this->drupalGet('admin/structure/views/view/' . $view_name . '/edit/' . $panel_id); 262 $this->getSession()->getPage()->pressButton('Update preview'); 263 $this->assertSession()->assertWaitOnAjaxRequest(); 264 $this->assertPreviewAJAX($row_count); 265 } 266 267 /** 268 * Click on a preview link. 269 * 270 * @param \Behat\Mink\Element\NodeElement $element 271 * The element to click. 272 * @param int $row_count 273 * The expected number of rows in the preview. 274 */ 275 protected function clickPreviewLinkAJAX(NodeElement $element, $row_count) { 276 $element->click(); 277 $this->assertSession()->assertWaitOnAjaxRequest(); 278 $this->assertPreviewAJAX($row_count); 279 } 280 281 /** 282 * Assert that the preview contains expected data. 283 * 284 * @param int $row_count 285 * The expected number of rows in the preview. 286 */ 287 protected function assertPreviewAJAX($row_count) { 288 $elements = $this->getSession()->getPage()->findAll('css', '.view-content .views-row'); 289 $this->assertCount($row_count, $elements, 'Expected items found on page.'); 290 } 291 292 /** 293 * Asserts that an element has a given class. 294 * 295 * @param \Behat\Mink\Element\NodeElement $element 296 * The element to test. 297 * @param string $class 298 * The class to assert. 299 * @param string $message 300 * (optional) A verbose message to output. 301 */ 302 protected function assertClass(NodeElement $element, $class, $message = NULL) { 303 if (!isset($message)) { 304 $message = "Class .$class found."; 305 } 306 $this->assertStringContainsString($class, $element->getAttribute('class'), $message); 307 } 308 309} 310