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