Upgraded drupal core with security updates
[yaffs-website] / web / core / tests / Drupal / FunctionalJavascriptTests / JSWebAssert.php
1 <?php
2
3 namespace Drupal\FunctionalJavascriptTests;
4
5 use Behat\Mink\Element\NodeElement;
6 use Behat\Mink\Exception\ElementHtmlException;
7 use Behat\Mink\Exception\ElementNotFoundException;
8 use Behat\Mink\Exception\UnsupportedDriverActionException;
9 use Drupal\Tests\WebAssert;
10
11 /**
12  * Defines a class with methods for asserting presence of elements during tests.
13  */
14 class JSWebAssert extends WebAssert {
15
16   /**
17    * Waits for AJAX request to be completed.
18    *
19    * @param int $timeout
20    *   (Optional) Timeout in milliseconds, defaults to 10000.
21    * @param string $message
22    *   (optional) A message for exception.
23    *
24    * @throws \RuntimeException
25    *   When the request is not completed. If left blank, a default message will
26    *   be displayed.
27    */
28   public function assertWaitOnAjaxRequest($timeout = 10000, $message = 'Unable to complete AJAX request.') {
29     $condition = <<<JS
30       (function() {
31         function isAjaxing(instance) {
32           return instance && instance.ajaxing === true;
33         }
34         return (
35           // Assert no AJAX request is running (via jQuery or Drupal) and no
36           // animation is running.
37           (typeof jQuery === 'undefined' || (jQuery.active === 0 && jQuery(':animated').length === 0)) &&
38           (typeof Drupal === 'undefined' || typeof Drupal.ajax === 'undefined' || !Drupal.ajax.instances.some(isAjaxing))
39         );
40       }());
41 JS;
42     $result = $this->session->wait($timeout, $condition);
43     if (!$result) {
44       throw new \RuntimeException($message);
45     }
46   }
47
48   /**
49    * Waits for the specified selector and returns it when available.
50    *
51    * @param string $selector
52    *   The selector engine name. See ElementInterface::findAll() for the
53    *   supported selectors.
54    * @param string|array $locator
55    *   The selector locator.
56    * @param int $timeout
57    *   (Optional) Timeout in milliseconds, defaults to 10000.
58    *
59    * @return \Behat\Mink\Element\NodeElement|null
60    *   The page element node if found, NULL if not.
61    *
62    * @see \Behat\Mink\Element\ElementInterface::findAll()
63    */
64   public function waitForElement($selector, $locator, $timeout = 10000) {
65     $page = $this->session->getPage();
66
67     $result = $page->waitFor($timeout / 1000, function() use ($page, $selector, $locator) {
68       return $page->find($selector, $locator);
69     });
70
71     return $result;
72   }
73
74   /**
75    * Waits for the specified selector and returns it when available and visible.
76    *
77    * @param string $selector
78    *   The selector engine name. See ElementInterface::findAll() for the
79    *   supported selectors.
80    * @param string|array $locator
81    *   The selector locator.
82    * @param int $timeout
83    *   (Optional) Timeout in milliseconds, defaults to 10000.
84    *
85    * @return \Behat\Mink\Element\NodeElement|null
86    *   The page element node if found and visible, NULL if not.
87    *
88    * @see \Behat\Mink\Element\ElementInterface::findAll()
89    */
90   public function waitForElementVisible($selector, $locator, $timeout = 10000) {
91     $page = $this->session->getPage();
92
93     $result = $page->waitFor($timeout / 1000, function() use ($page, $selector, $locator) {
94       $element = $page->find($selector, $locator);
95       if (!empty($element) && $element->isVisible()) {
96         return $element;
97       }
98       return NULL;
99     });
100
101     return $result;
102   }
103   /**
104    * Waits for a button (input[type=submit|image|button|reset], button) with
105    * specified locator and returns it.
106    *
107    * @param string $locator
108    *   The button ID, value or alt string.
109    * @param int $timeout
110    *   (Optional) Timeout in milliseconds, defaults to 10000.
111    *
112    * @return \Behat\Mink\Element\NodeElement|null
113    *   The page element node if found, NULL if not.
114    */
115   public function waitForButton($locator, $timeout = 10000) {
116     return $this->waitForElement('named', ['button', $locator], $timeout);
117   }
118
119   /**
120    * Waits for a link with specified locator and returns it when available.
121    *
122    * @param string $locator
123    *   The link ID, title, text or image alt.
124    * @param int $timeout
125    *   (Optional) Timeout in milliseconds, defaults to 10000.
126    *
127    * @return \Behat\Mink\Element\NodeElement|null
128    *   The page element node if found, NULL if not.
129    */
130   public function waitForLink($locator, $timeout = 10000) {
131     return $this->waitForElement('named', ['link', $locator], $timeout);
132   }
133
134   /**
135    * Waits for a field with specified locator and returns it when available.
136    *
137    * @param string $locator
138    *   The input ID, name or label for the field (input, textarea, select).
139    * @param int $timeout
140    *   (Optional) Timeout in milliseconds, defaults to 10000.
141    *
142    * @return \Behat\Mink\Element\NodeElement|null
143    *   The page element node if found, NULL if not.
144    */
145   public function waitForField($locator, $timeout = 10000) {
146     return $this->waitForElement('named', ['field', $locator], $timeout);
147   }
148
149   /**
150    * Waits for an element by its id and returns it when available.
151    *
152    * @param string $id
153    *   The element ID.
154    * @param int $timeout
155    *   (Optional) Timeout in milliseconds, defaults to 10000.
156    *
157    * @return \Behat\Mink\Element\NodeElement|null
158    *   The page element node if found, NULL if not.
159    */
160   public function waitForId($id, $timeout = 10000) {
161     return $this->waitForElement('named', ['id', $id], $timeout);
162   }
163
164   /**
165    * Waits for the jQuery autocomplete delay duration.
166    *
167    * @see https://api.jqueryui.com/autocomplete/#option-delay
168    */
169   public function waitOnAutocomplete() {
170     // Wait for the autocomplete to be visible.
171     return $this->waitForElementVisible('css', '.ui-autocomplete li');
172   }
173
174   /**
175    * Test that a node, or it's specific corner, is visible in the viewport.
176    *
177    * Note: Always set the viewport size. This can be done with a PhantomJS
178    * startup parameter or in your test with \Behat\Mink\Session->resizeWindow().
179    * Drupal CI Javascript tests by default use a viewport of 1024x768px.
180    *
181    * @param string $selector_type
182    *   The element selector type (CSS, XPath).
183    * @param string|array $selector
184    *   The element selector. Note: the first found element is used.
185    * @param bool|string $corner
186    *   (Optional) The corner to test:
187    *   topLeft, topRight, bottomRight, bottomLeft.
188    *   Or FALSE to check the complete element (default).
189    * @param string $message
190    *   (optional) A message for the exception.
191    *
192    * @throws \Behat\Mink\Exception\ElementHtmlException
193    *   When the element doesn't exist.
194    * @throws \Behat\Mink\Exception\ElementNotFoundException
195    *   When the element is not visible in the viewport.
196    */
197   public function assertVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is not visible in the viewport.') {
198     $node = $this->session->getPage()->find($selector_type, $selector);
199     if ($node === NULL) {
200       if (is_array($selector)) {
201         $selector = implode(' ', $selector);
202       }
203       throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
204     }
205
206     // Check if the node is visible on the page, which is a prerequisite of
207     // being visible in the viewport.
208     if (!$node->isVisible()) {
209       throw new ElementHtmlException($message, $this->session->getDriver(), $node);
210     }
211
212     $result = $this->checkNodeVisibilityInViewport($node, $corner);
213
214     if (!$result) {
215       throw new ElementHtmlException($message, $this->session->getDriver(), $node);
216     }
217   }
218
219   /**
220    * Test that a node, or its specific corner, is not visible in the viewport.
221    *
222    * Note: the node should exist in the page, otherwise this assertion fails.
223    *
224    * @param string $selector_type
225    *   The element selector type (CSS, XPath).
226    * @param string|array $selector
227    *   The element selector. Note: the first found element is used.
228    * @param bool|string $corner
229    *   (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
230    *   Or FALSE to check the complete element (default).
231    * @param string $message
232    *   (optional) A message for the exception.
233    *
234    * @throws \Behat\Mink\Exception\ElementHtmlException
235    *   When the element doesn't exist.
236    * @throws \Behat\Mink\Exception\ElementNotFoundException
237    *   When the element is not visible in the viewport.
238    *
239    * @see \Drupal\FunctionalJavascriptTests\JSWebAssert::assertVisibleInViewport()
240    */
241   public function assertNotVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is visible in the viewport.') {
242     $node = $this->session->getPage()->find($selector_type, $selector);
243     if ($node === NULL) {
244       if (is_array($selector)) {
245         $selector = implode(' ', $selector);
246       }
247       throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
248     }
249
250     $result = $this->checkNodeVisibilityInViewport($node, $corner);
251
252     if ($result) {
253       throw new ElementHtmlException($message, $this->session->getDriver(), $node);
254     }
255   }
256
257   /**
258    * Check the visibility of a node, or it's specific corner.
259    *
260    * @param \Behat\Mink\Element\NodeElement $node
261    *   A valid node.
262    * @param bool|string $corner
263    *   (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.
264    *   Or FALSE to check the complete element (default).
265    *
266    * @return bool
267    *   Returns TRUE if the node is visible in the viewport, FALSE otherwise.
268    *
269    * @throws \Behat\Mink\Exception\UnsupportedDriverActionException
270    *   When an invalid corner specification is given.
271    */
272   private function checkNodeVisibilityInViewport(NodeElement $node, $corner = FALSE) {
273     $xpath = $node->getXpath();
274
275     // Build the Javascript to test if the complete element or a specific corner
276     // is in the viewport.
277     switch ($corner) {
278       case 'topLeft':
279         $test_javascript_function = <<<JS
280           function t(r, lx, ly) {
281             return (
282               r.top >= 0 &&
283               r.top <= ly &&
284               r.left >= 0 &&
285               r.left <= lx
286             )
287           }
288 JS;
289         break;
290
291       case 'topRight':
292         $test_javascript_function = <<<JS
293           function t(r, lx, ly) {
294             return (
295               r.top >= 0 &&
296               r.top <= ly &&
297               r.right >= 0 &&
298               r.right <= lx
299             );
300           }
301 JS;
302         break;
303
304       case 'bottomRight':
305         $test_javascript_function = <<<JS
306           function t(r, lx, ly) {
307             return (
308               r.bottom >= 0 &&
309               r.bottom <= ly &&
310               r.right >= 0 &&
311               r.right <= lx
312             );
313           }
314 JS;
315         break;
316
317       case 'bottomLeft':
318         $test_javascript_function = <<<JS
319           function t(r, lx, ly) {
320             return (
321               r.bottom >= 0 &&
322               r.bottom <= ly &&
323               r.left >= 0 &&
324               r.left <= lx
325             );
326           }
327 JS;
328         break;
329
330       case FALSE:
331         $test_javascript_function = <<<JS
332           function t(r, lx, ly) {
333             return (
334               r.top >= 0 &&
335               r.left >= 0 &&
336               r.bottom <= ly &&
337               r.right <= lx
338             );
339           }
340 JS;
341         break;
342
343       // Throw an exception if an invalid corner parameter is given.
344       default:
345         throw new UnsupportedDriverActionException($corner, $this->session->getDriver());
346     }
347
348     // Build the full Javascript test. The shared logic gets the corner
349     // specific test logic injected.
350     $full_javascript_visibility_test = <<<JS
351       (function(t){
352         var w = window,
353         d = document,
354         e = d.documentElement,
355         n = d.evaluate("$xpath", d, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue,
356         r = n.getBoundingClientRect(),
357         lx = (w.innerWidth || e.clientWidth),
358         ly = (w.innerHeight || e.clientHeight);
359
360         return t(r, lx, ly);
361       }($test_javascript_function));
362 JS;
363
364     // Check the visibility by injecting and executing the full Javascript test
365     // script in the page.
366     return $this->session->evaluateScript($full_javascript_visibility_test);
367   }
368
369 }