| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369 | <?phpnamespace Drupal\FunctionalJavascriptTests;use Behat\Mink\Element\NodeElement;use Behat\Mink\Exception\ElementHtmlException;use Behat\Mink\Exception\ElementNotFoundException;use Behat\Mink\Exception\UnsupportedDriverActionException;use Drupal\Tests\WebAssert;/** * Defines a class with methods for asserting presence of elements during tests. */class JSWebAssert extends WebAssert {  /**   * Waits for AJAX request to be completed.   *   * @param int $timeout   *   (Optional) Timeout in milliseconds, defaults to 10000.   * @param string $message   *   (optional) A message for exception.   *   * @throws \RuntimeException   *   When the request is not completed. If left blank, a default message will   *   be displayed.   */  public function assertWaitOnAjaxRequest($timeout = 10000, $message = 'Unable to complete AJAX request.') {    $condition = <<<JS      (function() {        function isAjaxing(instance) {          return instance && instance.ajaxing === true;        }        return (          // Assert no AJAX request is running (via jQuery or Drupal) and no          // animation is running.          (typeof jQuery === 'undefined' || (jQuery.active === 0 && jQuery(':animated').length === 0)) &&          (typeof Drupal === 'undefined' || typeof Drupal.ajax === 'undefined' || !Drupal.ajax.instances.some(isAjaxing))        );      }());JS;    $result = $this->session->wait($timeout, $condition);    if (!$result) {      throw new \RuntimeException($message);    }  }  /**   * Waits for the specified selector and returns it when available.   *   * @param string $selector   *   The selector engine name. See ElementInterface::findAll() for the   *   supported selectors.   * @param string|array $locator   *   The selector locator.   * @param int $timeout   *   (Optional) Timeout in milliseconds, defaults to 10000.   *   * @return \Behat\Mink\Element\NodeElement|null   *   The page element node if found, NULL if not.   *   * @see \Behat\Mink\Element\ElementInterface::findAll()   */  public function waitForElement($selector, $locator, $timeout = 10000) {    $page = $this->session->getPage();    $result = $page->waitFor($timeout / 1000, function () use ($page, $selector, $locator) {      return $page->find($selector, $locator);    });    return $result;  }  /**   * Waits for the specified selector and returns it when available and visible.   *   * @param string $selector   *   The selector engine name. See ElementInterface::findAll() for the   *   supported selectors.   * @param string|array $locator   *   The selector locator.   * @param int $timeout   *   (Optional) Timeout in milliseconds, defaults to 10000.   *   * @return \Behat\Mink\Element\NodeElement|null   *   The page element node if found and visible, NULL if not.   *   * @see \Behat\Mink\Element\ElementInterface::findAll()   */  public function waitForElementVisible($selector, $locator, $timeout = 10000) {    $page = $this->session->getPage();    $result = $page->waitFor($timeout / 1000, function () use ($page, $selector, $locator) {      $element = $page->find($selector, $locator);      if (!empty($element) && $element->isVisible()) {        return $element;      }      return NULL;    });    return $result;  }  /**   * Waits for a button (input[type=submit|image|button|reset], button) with   * specified locator and returns it.   *   * @param string $locator   *   The button ID, value or alt string.   * @param int $timeout   *   (Optional) Timeout in milliseconds, defaults to 10000.   *   * @return \Behat\Mink\Element\NodeElement|null   *   The page element node if found, NULL if not.   */  public function waitForButton($locator, $timeout = 10000) {    return $this->waitForElement('named', ['button', $locator], $timeout);  }  /**   * Waits for a link with specified locator and returns it when available.   *   * @param string $locator   *   The link ID, title, text or image alt.   * @param int $timeout   *   (Optional) Timeout in milliseconds, defaults to 10000.   *   * @return \Behat\Mink\Element\NodeElement|null   *   The page element node if found, NULL if not.   */  public function waitForLink($locator, $timeout = 10000) {    return $this->waitForElement('named', ['link', $locator], $timeout);  }  /**   * Waits for a field with specified locator and returns it when available.   *   * @param string $locator   *   The input ID, name or label for the field (input, textarea, select).   * @param int $timeout   *   (Optional) Timeout in milliseconds, defaults to 10000.   *   * @return \Behat\Mink\Element\NodeElement|null   *   The page element node if found, NULL if not.   */  public function waitForField($locator, $timeout = 10000) {    return $this->waitForElement('named', ['field', $locator], $timeout);  }  /**   * Waits for an element by its id and returns it when available.   *   * @param string $id   *   The element ID.   * @param int $timeout   *   (Optional) Timeout in milliseconds, defaults to 10000.   *   * @return \Behat\Mink\Element\NodeElement|null   *   The page element node if found, NULL if not.   */  public function waitForId($id, $timeout = 10000) {    return $this->waitForElement('named', ['id', $id], $timeout);  }  /**   * Waits for the jQuery autocomplete delay duration.   *   * @see https://api.jqueryui.com/autocomplete/#option-delay   */  public function waitOnAutocomplete() {    // Wait for the autocomplete to be visible.    return $this->waitForElementVisible('css', '.ui-autocomplete li');  }  /**   * Test that a node, or its specific corner, is visible in the viewport.   *   * Note: Always set the viewport size. This can be done with a PhantomJS   * startup parameter or in your test with \Behat\Mink\Session->resizeWindow().   * Drupal CI Javascript tests by default use a viewport of 1024x768px.   *   * @param string $selector_type   *   The element selector type (CSS, XPath).   * @param string|array $selector   *   The element selector. Note: the first found element is used.   * @param bool|string $corner   *   (Optional) The corner to test:   *   topLeft, topRight, bottomRight, bottomLeft.   *   Or FALSE to check the complete element (default).   * @param string $message   *   (optional) A message for the exception.   *   * @throws \Behat\Mink\Exception\ElementHtmlException   *   When the element doesn't exist.   * @throws \Behat\Mink\Exception\ElementNotFoundException   *   When the element is not visible in the viewport.   */  public function assertVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is not visible in the viewport.') {    $node = $this->session->getPage()->find($selector_type, $selector);    if ($node === NULL) {      if (is_array($selector)) {        $selector = implode(' ', $selector);      }      throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);    }    // Check if the node is visible on the page, which is a prerequisite of    // being visible in the viewport.    if (!$node->isVisible()) {      throw new ElementHtmlException($message, $this->session->getDriver(), $node);    }    $result = $this->checkNodeVisibilityInViewport($node, $corner);    if (!$result) {      throw new ElementHtmlException($message, $this->session->getDriver(), $node);    }  }  /**   * Test that a node, or its specific corner, is not visible in the viewport.   *   * Note: the node should exist in the page, otherwise this assertion fails.   *   * @param string $selector_type   *   The element selector type (CSS, XPath).   * @param string|array $selector   *   The element selector. Note: the first found element is used.   * @param bool|string $corner   *   (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.   *   Or FALSE to check the complete element (default).   * @param string $message   *   (optional) A message for the exception.   *   * @throws \Behat\Mink\Exception\ElementHtmlException   *   When the element doesn't exist.   * @throws \Behat\Mink\Exception\ElementNotFoundException   *   When the element is not visible in the viewport.   *   * @see \Drupal\FunctionalJavascriptTests\JSWebAssert::assertVisibleInViewport()   */  public function assertNotVisibleInViewport($selector_type, $selector, $corner = FALSE, $message = 'Element is visible in the viewport.') {    $node = $this->session->getPage()->find($selector_type, $selector);    if ($node === NULL) {      if (is_array($selector)) {        $selector = implode(' ', $selector);      }      throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);    }    $result = $this->checkNodeVisibilityInViewport($node, $corner);    if ($result) {      throw new ElementHtmlException($message, $this->session->getDriver(), $node);    }  }  /**   * Check the visibility of a node, or its specific corner.   *   * @param \Behat\Mink\Element\NodeElement $node   *   A valid node.   * @param bool|string $corner   *   (Optional) Corner to test: topLeft, topRight, bottomRight, bottomLeft.   *   Or FALSE to check the complete element (default).   *   * @return bool   *   Returns TRUE if the node is visible in the viewport, FALSE otherwise.   *   * @throws \Behat\Mink\Exception\UnsupportedDriverActionException   *   When an invalid corner specification is given.   */  private function checkNodeVisibilityInViewport(NodeElement $node, $corner = FALSE) {    $xpath = $node->getXpath();    // Build the Javascript to test if the complete element or a specific corner    // is in the viewport.    switch ($corner) {      case 'topLeft':        $test_javascript_function = <<<JS          function t(r, lx, ly) {            return (              r.top >= 0 &&              r.top <= ly &&              r.left >= 0 &&              r.left <= lx            )          }JS;        break;      case 'topRight':        $test_javascript_function = <<<JS          function t(r, lx, ly) {            return (              r.top >= 0 &&              r.top <= ly &&              r.right >= 0 &&              r.right <= lx            );          }JS;        break;      case 'bottomRight':        $test_javascript_function = <<<JS          function t(r, lx, ly) {            return (              r.bottom >= 0 &&              r.bottom <= ly &&              r.right >= 0 &&              r.right <= lx            );          }JS;        break;      case 'bottomLeft':        $test_javascript_function = <<<JS          function t(r, lx, ly) {            return (              r.bottom >= 0 &&              r.bottom <= ly &&              r.left >= 0 &&              r.left <= lx            );          }JS;        break;      case FALSE:        $test_javascript_function = <<<JS          function t(r, lx, ly) {            return (              r.top >= 0 &&              r.left >= 0 &&              r.bottom <= ly &&              r.right <= lx            );          }JS;        break;      // Throw an exception if an invalid corner parameter is given.      default:        throw new UnsupportedDriverActionException($corner, $this->session->getDriver());    }    // Build the full Javascript test. The shared logic gets the corner    // specific test logic injected.    $full_javascript_visibility_test = <<<JS      (function(t){        var w = window,        d = document,        e = d.documentElement,        n = d.evaluate("$xpath", d, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue,        r = n.getBoundingClientRect(),        lx = (w.innerWidth || e.clientWidth),        ly = (w.innerHeight || e.clientHeight);        return t(r, lx, ly);      }($test_javascript_function));JS;    // Check the visibility by injecting and executing the full Javascript test    // script in the page.    return $this->session->evaluateScript($full_javascript_visibility_test);  }}
 |