Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401] [Versions 401 and 403]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Steps definitions related with forms.
  19   *
  20   * @package    core
  21   * @category   test
  22   * @copyright  2012 David MonllaĆ³
  23   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  24   */
  25  
  26  // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php.
  27  
  28  require_once (__DIR__ . '/../../../lib/behat/behat_base.php');
  29  require_once (__DIR__ . '/../../../lib/behat/behat_field_manager.php');
  30  
  31  use Behat\Gherkin\Node\{TableNode, PyStringNode};
  32  use Behat\Mink\Element\NodeElement;
  33  use Behat\Mink\Exception\{ElementNotFoundException, ExpectationException};
  34  
  35  /**
  36   * Forms-related steps definitions.
  37   *
  38   * Note, Behat tests to verify that the steps defined here work as advertised
  39   * are kept in admin/tool/behat/tests/behat.
  40   *
  41   * @package    core
  42   * @category   test
  43   * @copyright  2012 David MonllaĆ³
  44   * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  45   */
  46  class behat_forms extends behat_base {
  47  
  48      /**
  49       * Presses button with specified id|name|title|alt|value.
  50       *
  51       * @When /^I press "(?P<button_string>(?:[^"]|\\")*)"$/
  52       * @throws ElementNotFoundException Thrown by behat_base::find
  53       * @param string $button
  54       */
  55      public function press_button($button) {
  56          $this->execute('behat_general::i_click_on', [$button, 'button']);
  57      }
  58  
  59      /**
  60       * Press button with specified id|name|title|alt|value and switch to main window.
  61       *
  62       * @When /^I press "(?P<button_string>(?:[^"]|\\")*)" and switch to main window$/
  63       * @throws ElementNotFoundException Thrown by behat_base::find
  64       * @param string $button
  65       */
  66      public function press_button_and_switch_to_main_window($button) {
  67          // Ensures the button is present, before pressing.
  68          $buttonnode = $this->find_button($button);
  69          $buttonnode->press();
  70          $this->wait_for_pending_js();
  71          $this->look_for_exceptions();
  72  
  73          // Switch to main window.
  74          $this->execute('behat_general::switch_to_the_main_window');
  75      }
  76  
  77      /**
  78       * Fills a form with field/value data.
  79       *
  80       * @Given /^I set the following fields to these values:$/
  81       * @throws ElementNotFoundException Thrown by behat_base::find
  82       * @param TableNode $data
  83       */
  84      public function i_set_the_following_fields_to_these_values(TableNode $data) {
  85  
  86          // Expand all fields in case we have.
  87          $this->expand_all_fields();
  88  
  89          $datahash = $data->getRowsHash();
  90  
  91          // The action depends on the field type.
  92          foreach ($datahash as $locator => $value) {
  93              $this->set_field_value($locator, $value);
  94          }
  95      }
  96  
  97      /**
  98       * Fills a form with field/value data.
  99       *
 100       * @Given /^I set the following fields in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" to these values:$/
 101       * @throws ElementNotFoundException Thrown by behat_base::find
 102       * @param string $containerelement Element we look in
 103       * @param string $containerselectortype The type of selector where we look in
 104       * @param TableNode $data
 105       */
 106      public function i_set_the_following_fields_in_container_to_these_values(
 107              $containerelement, $containerselectortype, TableNode $data) {
 108  
 109          // Expand all fields in case we have.
 110          $this->expand_all_fields();
 111  
 112          $datahash = $data->getRowsHash();
 113  
 114          // The action depends on the field type.
 115          foreach ($datahash as $locator => $value) {
 116              $this->set_field_value_in_container($locator, $value, $containerselectortype, $containerelement);
 117          }
 118      }
 119  
 120      /**
 121       * Expands all moodleform's fields, including collapsed fieldsets and advanced fields if they are present.
 122       * @Given /^I expand all fieldsets$/
 123       */
 124      public function i_expand_all_fieldsets() {
 125          $this->expand_all_fields();
 126      }
 127  
 128      /**
 129       * Expands all moodle form fieldsets if they exists.
 130       *
 131       * Externalized from i_expand_all_fields to call it from
 132       * other form-related steps without having to use steps-group calls.
 133       *
 134       * @throws ElementNotFoundException Thrown by behat_base::find_all
 135       * @return void
 136       */
 137      protected function expand_all_fields() {
 138          // Expand only if JS mode, else not needed.
 139          if (!$this->running_javascript()) {
 140              return;
 141          }
 142  
 143          // We already know that we waited for the DOM and the JS to be loaded, even the editor
 144          // so, we will use the reduced timeout as it is a common task and we should save time.
 145          try {
 146              $this->wait_for_pending_js();
 147              // Expand all fieldsets link - which will only be there if there is more than one collapsible section.
 148              $expandallxpath = "//div[@class='collapsible-actions']" .
 149                  "//a[contains(concat(' ', @class, ' '), ' collapsed ')]" .
 150                  "//span[contains(concat(' ', @class, ' '), ' expandall ')]";
 151              // Else, look for the first expand fieldset link (old theme structure).
 152              $expandsectionold = "//legend[@class='ftoggler']" .
 153                      "//a[contains(concat(' ', @class, ' '), ' icons-collapse-expand ') and @aria-expanded = 'false']";
 154              // Else, look for the first expand fieldset link (current theme structure).
 155              $expandsectioncurrent = "//fieldset//div[contains(concat(' ', @class, ' '), ' ftoggler ')]" .
 156                      "//a[contains(concat(' ', @class, ' '), ' icons-collapse-expand ') and @aria-expanded = 'false']";
 157  
 158              $collapseexpandlink = $this->find('xpath', $expandallxpath . '|' . $expandsectionold . '|' . $expandsectioncurrent,
 159                      false, false, behat_base::get_reduced_timeout());
 160              $collapseexpandlink->click();
 161              $this->wait_for_pending_js();
 162  
 163          } catch (ElementNotFoundException $e) {
 164              // The behat_base::find() method throws an exception if there are no elements,
 165              // we should not fail a test because of this. We continue if there are not expandable fields.
 166          }
 167  
 168          // Different try & catch as we can have expanded fieldsets with advanced fields on them.
 169          try {
 170  
 171              // Expand all fields xpath.
 172              $showmorexpath = "//a[normalize-space(.)='" . get_string('showmore', 'form') . "']" .
 173                  "[contains(concat(' ', normalize-space(@class), ' '), ' moreless-toggler')]";
 174  
 175              // We don't wait here as we already waited when getting the expand fieldsets links.
 176              if (!$showmores = $this->getSession()->getPage()->findAll('xpath', $showmorexpath)) {
 177                  return;
 178              }
 179  
 180              if ($this->getSession()->getDriver() instanceof \DMore\ChromeDriver\ChromeDriver) {
 181                  // Chrome Driver produces unique xpaths for each element.
 182                  foreach ($showmores as $showmore) {
 183                      $showmore->click();
 184                  }
 185              } else {
 186                  // Funny thing about this, with findAll() we specify a pattern and each element matching the pattern
 187                  // is added to the array with of xpaths with a [0], [1]... sufix, but when we click on an element it
 188                  // does not matches the specified xpath anymore (now is a "Show less..." link) so [1] becomes [0],
 189                  // that's why we always click on the first XPath match, will be always the next one.
 190                  $iterations = count($showmores);
 191                  for ($i = 0; $i < $iterations; $i++) {
 192                      $showmores[0]->click();
 193                  }
 194              }
 195  
 196          } catch (ElementNotFoundException $e) {
 197              // We continue with the test.
 198          }
 199  
 200      }
 201  
 202      /**
 203       * Sets the field to wwwroot plus the given path. Include the first slash.
 204       *
 205       * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to local url "(?P<field_path_string>(?:[^"]|\\")*)"$/
 206       * @throws ElementNotFoundException Thrown by behat_base::find
 207       * @param string $field
 208       * @param string $path
 209       * @return void
 210       */
 211      public function i_set_the_field_to_local_url($field, $path) {
 212          global $CFG;
 213          $this->set_field_value($field, $CFG->wwwroot . $path);
 214      }
 215  
 216      /**
 217       * Sets the specified value to the field.
 218       *
 219       * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/
 220       * @throws ElementNotFoundException Thrown by behat_base::find
 221       * @param string $field
 222       * @param string $value
 223       * @return void
 224       */
 225      public function i_set_the_field_to($field, $value) {
 226          $this->set_field_value($field, $value);
 227      }
 228  
 229      /**
 230       * Sets the specified value to the field.
 231       *
 232       * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/
 233       * @throws ElementNotFoundException Thrown by behat_base::find
 234       * @param string $field
 235       * @param string $containerelement Element we look in
 236       * @param string $containerselectortype The type of selector where we look in
 237       * @param string $value
 238       */
 239      public function i_set_the_field_in_container_to($field, $containerelement, $containerselectortype, $value) {
 240          $this->set_field_value_in_container($field, $value, $containerselectortype, $containerelement);
 241      }
 242  
 243      /**
 244       * Press the key in the field to trigger the javascript keypress event
 245       *
 246       * Note that the character key will not actually be typed in the input field
 247       *
 248       * @Given /^I press key "(?P<key_string>(?:[^"]|\\")*)" in the field "(?P<field_string>(?:[^"]|\\")*)"$/
 249       * @throws ElementNotFoundException Thrown by behat_base::find
 250       * @param string $key either char-code or character itself,
 251       *          may optionally be prefixed with ctrl-, alt-, shift- or meta-
 252       * @param string $field
 253       * @return void
 254       */
 255      public function i_press_key_in_the_field($key, $field) {
 256          if (!$this->running_javascript()) {
 257              throw new DriverException('Key press step is not available with Javascript disabled');
 258          }
 259          $fld = behat_field_manager::get_form_field_from_label($field, $this);
 260          $modifier = null;
 261          $char = $key;
 262          if (preg_match('/-/', $key)) {
 263              list($modifier, $char) = preg_split('/-/', $key, 2);
 264          }
 265          if (is_numeric($char)) {
 266              $char = (int)$char;
 267          }
 268          $fld->key_press($char, $modifier);
 269      }
 270  
 271      /**
 272       * Sets the specified value to the field.
 273       *
 274       * @Given /^I set the field "(?P<field_string>(?:[^"]|\\")*)" to multiline:$/
 275       * @throws ElementNotFoundException Thrown by behat_base::find
 276       * @param string $field
 277       * @param PyStringNode $value
 278       * @return void
 279       */
 280      public function i_set_the_field_to_multiline($field, PyStringNode $value) {
 281          $this->set_field_value($field, (string)$value);
 282      }
 283  
 284      /**
 285       * Sets the specified value to the field with xpath.
 286       *
 287       * @Given /^I set the field with xpath "(?P<fieldxpath_string>(?:[^"]|\\")*)" to "(?P<field_value_string>(?:[^"]|\\")*)"$/
 288       * @throws ElementNotFoundException Thrown by behat_base::find
 289       * @param string $field
 290       * @param string $value
 291       * @return void
 292       */
 293      public function i_set_the_field_with_xpath_to($fieldxpath, $value) {
 294          $this->set_field_node_value($this->find('xpath', $fieldxpath), $value);
 295      }
 296  
 297      /**
 298       * Checks, the field matches the value.
 299       *
 300       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 301       * @throws ElementNotFoundException Thrown by behat_base::find
 302       * @param string $field
 303       * @param string $value
 304       * @return void
 305       */
 306      public function the_field_matches_value($field, $value) {
 307  
 308          // Get the field.
 309          $formfield = behat_field_manager::get_form_field_from_label($field, $this);
 310  
 311          // Checks if the provided value matches the current field value.
 312          if (!$formfield->matches($value)) {
 313              $fieldvalue = $formfield->get_value();
 314              throw new ExpectationException(
 315                  'The \'' . $field . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' ,
 316                  $this->getSession()
 317              );
 318          }
 319      }
 320  
 321      /**
 322       * Checks, the field matches the value.
 323       *
 324       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" matches multiline:$/
 325       * @throws ElementNotFoundException Thrown by behat_base::find
 326       * @param string $field
 327       * @param PyStringNode $value
 328       * @return void
 329       */
 330      public function the_field_matches_multiline($field, PyStringNode $value) {
 331          $this->the_field_matches_value($field, (string)$value);
 332      }
 333  
 334      /**
 335       * Checks, the field does not match the value.
 336       *
 337       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" does not match value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 338       * @throws ExpectationException
 339       * @throws ElementNotFoundException Thrown by behat_base::find
 340       * @param string $field
 341       * @param string $value
 342       */
 343      public function the_field_does_not_match_value($field, $value) {
 344  
 345          // Get the field.
 346          $formfield = behat_field_manager::get_form_field_from_label($field, $this);
 347  
 348          // Checks if the provided value matches the current field value.
 349          if ($formfield->matches($value)) {
 350              throw new ExpectationException(
 351                  'The \'' . $field . '\' value matches \'' . $value . '\' and it should not match it' ,
 352                  $this->getSession()
 353              );
 354          }
 355      }
 356  
 357      /**
 358       * Checks, the field matches the value.
 359       *
 360       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 361       * @throws ElementNotFoundException Thrown by behat_base::find
 362       * @param string $field
 363       * @param string $containerelement Element we look in
 364       * @param string $containerselectortype The type of selector where we look in
 365       * @param string $value
 366       */
 367      public function the_field_in_container_matches_value($field, $containerelement, $containerselectortype, $value) {
 368  
 369          // Get the field.
 370          $node = $this->get_node_in_container('field', $field, $containerselectortype, $containerelement);
 371          $formfield = behat_field_manager::get_form_field($node, $this->getSession());
 372  
 373          // Checks if the provided value matches the current field value.
 374          if (!$formfield->matches($value)) {
 375              $fieldvalue = $formfield->get_value();
 376              throw new ExpectationException(
 377                      'The \'' . $field . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' ,
 378                      $this->getSession()
 379              );
 380          }
 381      }
 382  
 383      /**
 384       * Checks, the field does not match the value.
 385       *
 386       * @Then /^the field "(?P<field_string>(?:[^"]|\\")*)" in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" does not match value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 387       * @throws ExpectationException
 388       * @throws ElementNotFoundException Thrown by behat_base::find
 389       * @param string $field
 390       * @param string $containerelement Element we look in
 391       * @param string $containerselectortype The type of selector where we look in
 392       * @param string $value
 393       */
 394      public function the_field_in_container_does_not_match_value($field, $containerelement, $containerselectortype, $value) {
 395  
 396          // Get the field.
 397          $node = $this->get_node_in_container('field', $field, $containerselectortype, $containerelement);
 398          $formfield = behat_field_manager::get_form_field($node, $this->getSession());
 399  
 400          // Checks if the provided value matches the current field value.
 401          if ($formfield->matches($value)) {
 402              throw new ExpectationException(
 403                      'The \'' . $field . '\' value matches \'' . $value . '\' and it should not match it' ,
 404                      $this->getSession()
 405              );
 406          }
 407      }
 408  
 409      /**
 410       * Checks, the field matches the value.
 411       *
 412       * @Then /^the field with xpath "(?P<xpath_string>(?:[^"]|\\")*)" matches value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 413       * @throws ExpectationException
 414       * @throws ElementNotFoundException Thrown by behat_base::find
 415       * @param string $fieldxpath
 416       * @param string $value
 417       * @return void
 418       */
 419      public function the_field_with_xpath_matches_value($fieldxpath, $value) {
 420  
 421          // Get the field.
 422          $fieldnode = $this->find('xpath', $fieldxpath);
 423          $formfield = behat_field_manager::get_form_field($fieldnode, $this->getSession());
 424  
 425          // Checks if the provided value matches the current field value.
 426          if (!$formfield->matches($value)) {
 427              $fieldvalue = $formfield->get_value();
 428              throw new ExpectationException(
 429                  'The \'' . $fieldxpath . '\' value is \'' . $fieldvalue . '\', \'' . $value . '\' expected' ,
 430                  $this->getSession()
 431              );
 432          }
 433      }
 434  
 435      /**
 436       * Checks, the field does not match the value.
 437       *
 438       * @Then /^the field with xpath "(?P<xpath_string>(?:[^"]|\\")*)" does not match value "(?P<field_value_string>(?:[^"]|\\")*)"$/
 439       * @throws ExpectationException
 440       * @throws ElementNotFoundException Thrown by behat_base::find
 441       * @param string $fieldxpath
 442       * @param string $value
 443       * @return void
 444       */
 445      public function the_field_with_xpath_does_not_match_value($fieldxpath, $value) {
 446  
 447          // Get the field.
 448          $fieldnode = $this->find('xpath', $fieldxpath);
 449          $formfield = behat_field_manager::get_form_field($fieldnode, $this->getSession());
 450  
 451          // Checks if the provided value matches the current field value.
 452          if ($formfield->matches($value)) {
 453              throw new ExpectationException(
 454                  'The \'' . $fieldxpath . '\' value matches \'' . $value . '\' and it should not match it' ,
 455                  $this->getSession()
 456              );
 457          }
 458      }
 459  
 460      /**
 461       * Checks, the provided field/value matches.
 462       *
 463       * @Then /^the following fields match these values:$/
 464       * @throws ExpectationException
 465       * @param TableNode $data Pairs of | field | value |
 466       */
 467      public function the_following_fields_match_these_values(TableNode $data) {
 468  
 469          // Expand all fields in case we have.
 470          $this->expand_all_fields();
 471  
 472          $datahash = $data->getRowsHash();
 473  
 474          // The action depends on the field type.
 475          foreach ($datahash as $locator => $value) {
 476              $this->the_field_matches_value($locator, $value);
 477          }
 478      }
 479  
 480      /**
 481       * Checks that the provided field/value pairs don't match.
 482       *
 483       * @Then /^the following fields do not match these values:$/
 484       * @throws ExpectationException
 485       * @param TableNode $data Pairs of | field | value |
 486       */
 487      public function the_following_fields_do_not_match_these_values(TableNode $data) {
 488  
 489          // Expand all fields in case we have.
 490          $this->expand_all_fields();
 491  
 492          $datahash = $data->getRowsHash();
 493  
 494          // The action depends on the field type.
 495          foreach ($datahash as $locator => $value) {
 496              $this->the_field_does_not_match_value($locator, $value);
 497          }
 498      }
 499  
 500      /**
 501       * Checks, the provided field/value matches.
 502       *
 503       * @Then /^the following fields in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" match these values:$/
 504       * @throws ExpectationException
 505       * @param string $containerelement Element we look in
 506       * @param string $containerselectortype The type of selector where we look in
 507       * @param TableNode $data Pairs of | field | value |
 508       */
 509      public function the_following_fields_in_container_match_these_values(
 510              $containerelement, $containerselectortype, TableNode $data) {
 511  
 512          // Expand all fields in case we have.
 513          $this->expand_all_fields();
 514  
 515          $datahash = $data->getRowsHash();
 516  
 517          // The action depends on the field type.
 518          foreach ($datahash as $locator => $value) {
 519              $this->the_field_in_container_matches_value($locator, $containerelement, $containerselectortype, $value);
 520          }
 521      }
 522  
 523      /**
 524       * Checks that the provided field/value pairs don't match.
 525       *
 526       * @Then /^the following fields in the "(?P<element_container_string>(?:[^"]|\\")*)" "(?P<text_selector_string>[^"]*)" do not match these values:$/
 527       * @throws ExpectationException
 528       * @param string $containerelement Element we look in
 529       * @param string $containerselectortype The type of selector where we look in
 530       * @param TableNode $data Pairs of | field | value |
 531       */
 532      public function the_following_fields_in_container_do_not_match_these_values(
 533              $containerelement, $containerselectortype, TableNode $data) {
 534  
 535          // Expand all fields in case we have.
 536          $this->expand_all_fields();
 537  
 538          $datahash = $data->getRowsHash();
 539  
 540          // The action depends on the field type.
 541          foreach ($datahash as $locator => $value) {
 542              $this->the_field_in_container_does_not_match_value($locator, $containerelement, $containerselectortype, $value);
 543          }
 544      }
 545  
 546      /**
 547       * Checks, that given select box contains the specified option.
 548       *
 549       * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should contain "(?P<option_string>(?:[^"]|\\")*)"$/
 550       * @throws ExpectationException
 551       * @throws ElementNotFoundException Thrown by behat_base::find
 552       * @param string $select The select element name
 553       * @param string $option The option text/value. Plain value or comma separated
 554       *                       values if multiple. Commas in multiple values escaped with backslash.
 555       */
 556      public function the_select_box_should_contain($select, $option) {
 557  
 558          $selectnode = $this->find_field($select);
 559          $multiple = $selectnode->hasAttribute('multiple');
 560          $optionsarr = array(); // Array of passed value/text options to test.
 561  
 562          if ($multiple) {
 563              // Can pass multiple comma separated, with valuable commas escaped with backslash.
 564              foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $option)) as $opt) {
 565                  $optionsarr[] = trim($opt);
 566              }
 567          } else {
 568              // Only one option has been passed.
 569              $optionsarr[] = trim($option);
 570          }
 571  
 572          // Now get all the values and texts in the select.
 573          $options = $selectnode->findAll('xpath', '//option');
 574          $values = array();
 575          foreach ($options as $opt) {
 576              $values[trim($opt->getValue())] = trim($opt->getText());
 577          }
 578  
 579          foreach ($optionsarr as $opt) {
 580              // Verify every option is a valid text or value.
 581              if (!in_array($opt, $values) && !array_key_exists($opt, $values)) {
 582                  throw new ExpectationException(
 583                      'The select box "' . $select . '" does not contain the option "' . $opt . '"',
 584                      $this->getSession()
 585                  );
 586              }
 587          }
 588      }
 589  
 590      /**
 591       * Checks, that given select box does not contain the specified option.
 592       *
 593       * @Then /^the "(?P<select_string>(?:[^"]|\\")*)" select box should not contain "(?P<option_string>(?:[^"]|\\")*)"$/
 594       * @throws ExpectationException
 595       * @throws ElementNotFoundException Thrown by behat_base::find
 596       * @param string $select The select element name
 597       * @param string $option The option text/value. Plain value or comma separated
 598       *                       values if multiple. Commas in multiple values escaped with backslash.
 599       */
 600      public function the_select_box_should_not_contain($select, $option) {
 601  
 602          $selectnode = $this->find_field($select);
 603          $multiple = $selectnode->hasAttribute('multiple');
 604          $optionsarr = array(); // Array of passed value/text options to test.
 605  
 606          if ($multiple) {
 607              // Can pass multiple comma separated, with valuable commas escaped with backslash.
 608              foreach (preg_replace('/\\\,/', ',',  preg_split('/(?<!\\\),/', $option)) as $opt) {
 609                  $optionsarr[] = trim($opt);
 610              }
 611          } else {
 612              // Only one option has been passed.
 613              $optionsarr[] = trim($option);
 614          }
 615  
 616          // Now get all the values and texts in the select.
 617          $options = $selectnode->findAll('xpath', '//option');
 618          $values = array();
 619          foreach ($options as $opt) {
 620              $values[trim($opt->getValue())] = trim($opt->getText());
 621          }
 622  
 623          foreach ($optionsarr as $opt) {
 624              // Verify every option is not a valid text or value.
 625              if (in_array($opt, $values) || array_key_exists($opt, $values)) {
 626                  throw new ExpectationException(
 627                      'The select box "' . $select . '" contains the option "' . $opt . '"',
 628                      $this->getSession()
 629                  );
 630              }
 631          }
 632      }
 633  
 634      /**
 635       * Generic field setter.
 636       *
 637       * Internal API method, a generic *I set "VALUE" to "FIELD" field*
 638       * could be created based on it.
 639       *
 640       * @param string $fieldlocator The pointer to the field, it will depend on the field type.
 641       * @param string $value
 642       * @return void
 643       */
 644      protected function set_field_value($fieldlocator, $value) {
 645          // We delegate to behat_form_field class, it will
 646          // guess the type properly as it is a select tag.
 647          $field = behat_field_manager::get_form_field_from_label($fieldlocator, $this);
 648          $field->set_value($value);
 649      }
 650  
 651      /**
 652       * Generic field setter to be used by chainable steps.
 653       *
 654       * @param NodeElement $fieldnode
 655       * @param string $value
 656       */
 657      public function set_field_node_value(NodeElement $fieldnode, string $value): void {
 658          $field = behat_field_manager::get_form_field($fieldnode, $this->getSession());
 659          $field->set_value($value);
 660      }
 661  
 662      /**
 663       * Generic field setter.
 664       *
 665       * Internal API method, a generic *I set "VALUE" to "FIELD" field*
 666       * could be created based on it.
 667       *
 668       * @param string $fieldlocator The pointer to the field, it will depend on the field type.
 669       * @param string $value the value to set
 670       * @param string $containerselectortype The type of selector where we look in
 671       * @param string $containerelement Element we look in
 672       */
 673      protected function set_field_value_in_container($fieldlocator, $value, $containerselectortype, $containerelement) {
 674          $node = $this->get_node_in_container('field', $fieldlocator, $containerselectortype, $containerelement);
 675          $this->set_field_node_value($node, $value);
 676      }
 677  
 678      /**
 679       * Select a value from single select and redirect.
 680       *
 681       * @Given /^I select "(?P<singleselect_option_string>(?:[^"]|\\")*)" from the "(?P<singleselect_name_string>(?:[^"]|\\")*)" singleselect$/
 682       */
 683      public function i_select_from_the_singleselect($option, $singleselect) {
 684  
 685          $this->execute('behat_forms::i_set_the_field_to', array($this->escape($singleselect), $this->escape($option)));
 686  
 687          if (!$this->running_javascript()) {
 688              // Press button in the specified select container.
 689              $containerxpath = "//div[" .
 690                  "(contains(concat(' ', normalize-space(@class), ' '), ' singleselect ') " .
 691                      "or contains(concat(' ', normalize-space(@class), ' '), ' urlselect ')".
 692                  ") and (
 693                  .//label[contains(normalize-space(string(.)), '" . $singleselect . "')] " .
 694                      "or .//select[(./@name='" . $singleselect . "' or ./@id='". $singleselect . "')]" .
 695                  ")]";
 696  
 697              $this->execute('behat_general::i_click_on_in_the',
 698                  array(get_string('go'), "button", $containerxpath, "xpath_element")
 699              );
 700          }
 701      }
 702  
 703      /**
 704       * Select item from autocomplete list.
 705       *
 706       * @Given /^I click on "([^"]*)" item in the autocomplete list$/
 707       *
 708       * @param string $item
 709       */
 710      public function i_click_on_item_in_the_autocomplete_list($item) {
 711          $xpathtarget = "//ul[@class='form-autocomplete-suggestions']//*[contains(concat('|', string(.), '|'),'|" . $item . "|')]";
 712  
 713          $this->execute('behat_general::i_click_on', [$xpathtarget, 'xpath_element']);
 714      }
 715  
 716      /**
 717       * Open the auto-complete suggestions list (Assuming there is only one on the page.).
 718       *
 719       * @Given I open the autocomplete suggestions list
 720       * @Given I open the autocomplete suggestions list in the :container :containertype
 721       */
 722      public function i_open_the_autocomplete_suggestions_list($container = null, $containertype = null) {
 723          $csstarget = ".form-autocomplete-downarrow";
 724          if ($container && $containertype) {
 725              $this->execute('behat_general::i_click_on_in_the', [$csstarget, 'css_element', $container, $containertype]);
 726          } else {
 727              $this->execute('behat_general::i_click_on', [$csstarget, 'css_element']);
 728          }
 729      }
 730  
 731      /**
 732       * Expand the given autocomplete list
 733       *
 734       * @Given /^I expand the "(?P<field_string>(?:[^"]|\\")*)" autocomplete$/
 735       *
 736       * @param string $field Field name
 737       */
 738      public function i_expand_the_autocomplete($field) {
 739          $csstarget = '.form-autocomplete-downarrow';
 740          $node = $this->get_node_in_container('css_element', $csstarget, 'form_row', $field);
 741          $this->ensure_node_is_visible($node);
 742          $node->click();
 743      }
 744  
 745      /**
 746       * Assert the given option exist in the given autocomplete list
 747       *
 748       * @Given /^I should see "(?P<option_string>(?:[^"]|\\")*)" in the list of options for the "(?P<field_string>(?:[^"]|\\")*)" autocomplete$$/
 749       *
 750       * @param string $option Name of option
 751       * @param string $field Field name
 752       */
 753      public function i_should_see_in_the_list_of_option_for_the_autocomplete($option, $field) {
 754          $xpathtarget = "//div[contains(@class, 'form-autocomplete-selection') and contains(.//div, '" . $option . "')]";
 755          $node = $this->get_node_in_container('xpath_element', $xpathtarget, 'form_row', $field);
 756          $this->ensure_node_is_visible($node);
 757      }
 758  
 759      /**
 760       * Checks whether the select menu contains an option with specified text or not.
 761       *
 762       * @Then the :name select menu should contain :option
 763       * @Then the :name select menu should :not contain :option
 764       *
 765       * @throws ExpectationException When the expectation is not satisfied
 766       * @param string $label The label of the select menu element
 767       * @param string $option The string that is used to identify an option within the select menu. If the string
 768       *                       has two items separated by '>' (ex. "Group > Option"), the first item ("Group") will be
 769       *                       used to identify a particular group within the select menu, while the second ("Option")
 770       *                       will be used to identify an option within that group. Otherwise, a string with a single
 771       *                       item (ex. "Option") will be used to identify an option within the select menu regardless
 772       *                       of any existing groups.
 773       * @param string|null $not If set, the select menu should not contain the specified option. If null, the option
 774       *                         should be present.
 775       */
 776      public function the_select_menu_should_contain(string $label, string $option, ?string $not = null) {
 777  
 778          $field = behat_field_manager::get_form_field_from_label($label, $this);
 779  
 780          if (!method_exists($field, 'has_option')) {
 781              throw new coding_exception('Field does not support the has_option function.');
 782          }
 783  
 784          // If the select menu contains the specified option but it should not.
 785          if ($field->has_option($option) && $not) {
 786              throw new ExpectationException(
 787                  "The select menu should not contain \"{$option}\" but it does.",
 788                  $this->getSession()
 789              );
 790          }
 791          // If the select menu does not contain the specified option but it should.
 792          if (!$field->has_option($option) && !$not) {
 793              throw new ExpectationException(
 794                  "The select menu should contain \"{$option}\" but it does not.",
 795                  $this->getSession()
 796              );
 797          }
 798      }
 799  }