[ Index ] |
PHP Cross Reference of moodle-2.8 |
[Summary view] [Print] [Text view]
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 * Single select form field class. 19 * 20 * @package core_form 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__ . '/behat_form_field.php'); 29 30 /** 31 * Single select form field. 32 * 33 * @package core_form 34 * @category test 35 * @copyright 2012 David Monllaó 36 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 37 */ 38 class behat_form_select extends behat_form_field { 39 40 /** 41 * Sets the value(s) of a select element. 42 * 43 * Seems an easy select, but there are lots of combinations 44 * of browsers and operative systems and each one manages the 45 * autosubmits and the multiple option selects in a different way. 46 * 47 * @param string $value plain value or comma separated values if multiple. Commas in values escaped with backslash. 48 * @return void 49 */ 50 public function set_value($value) { 51 52 // In some browsers we select an option and it triggers all the 53 // autosubmits and works as expected but not in all of them, so we 54 // try to catch all the possibilities to make this function work as 55 // expected. 56 57 // Get the internal id of the element we are going to click. 58 // This kind of internal IDs are only available in the selenium wire 59 // protocol, so only available using selenium drivers, phantomjs and family. 60 if ($this->running_javascript()) { 61 $currentelementid = $this->get_internal_field_id(); 62 } 63 64 // Is the select multiple? 65 $multiple = $this->field->hasAttribute('multiple'); 66 67 // By default, assume the passed value is a non-multiple option. 68 $options = array(trim($value)); 69 70 // Here we select the option(s). 71 if ($multiple) { 72 // Split and decode values. Comma separated list of values allowed. With valuable commas escaped with backslash. 73 $options = preg_replace('/\\\,/', ',', preg_split('/(?<!\\\),/', $value)); 74 // This is a multiple select, let's pass the multiple flag after first option. 75 $afterfirstoption = false; 76 foreach ($options as $option) { 77 $this->field->selectOption(trim($option), $afterfirstoption); 78 $afterfirstoption = true; 79 } 80 } else { 81 // This is a single select, let's pass the last one specified. 82 $this->field->selectOption(end($options)); 83 } 84 85 // With JS disabled this is enough and we finish here. 86 if (!$this->running_javascript()) { 87 return; 88 } 89 90 // With JS enabled we add more clicks as some selenium 91 // drivers requires it to fire JS events. 92 93 // In some browsers the selectOption actions can perform a form submit or reload page 94 // so we need to ensure the element is still available to continue interacting 95 // with it. We don't wait here. 96 // getXpath() does not send a query to selenium, so we don't need to wrap it in a try & catch. 97 $selectxpath = $this->field->getXpath(); 98 if (!$this->session->getDriver()->find($selectxpath)) { 99 return; 100 } 101 102 // We also check the selenium internal element id, if it have changed 103 // we are dealing with an autosubmit that was already executed, and we don't to 104 // execute anything else as the action we wanted was already performed. 105 if ($currentelementid != $this->get_internal_field_id()) { 106 return; 107 } 108 109 // Wait for all the possible AJAX requests that have been 110 // already triggered by selectOption() to be finished. 111 $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS); 112 113 // Wrapped in try & catch as the element may disappear if an AJAX request was submitted. 114 try { 115 $multiple = $this->field->hasAttribute('multiple'); 116 } catch (Exception $e) { 117 // We do not specify any specific Exception type as there are 118 // different exceptions that can be thrown by the driver and 119 // we can not control them all, also depending on the selenium 120 // version the exception type can change. 121 return; 122 } 123 124 // Single select sometimes needs an extra click in the option. 125 if (!$multiple) { 126 127 // Var $options only contains 1 option. 128 $optionxpath = $this->get_option_xpath(end($options), $selectxpath); 129 130 // Using the driver direcly because Element methods are messy when dealing 131 // with elements inside containers. 132 if ($optionnodes = $this->session->getDriver()->find($optionxpath)) { 133 134 // Wrapped in a try & catch as we can fall into race conditions 135 // and the element may not be there. 136 try { 137 current($optionnodes)->click(); 138 } catch (Exception $e) { 139 // We continue and return as this means that the element is not there or it is not the same. 140 return; 141 } 142 } 143 144 } else { 145 146 // Wrapped in a try & catch as we can fall into race conditions 147 // and the element may not be there. 148 try { 149 // Multiple ones needs the click in the select. 150 $this->field->click(); 151 } catch (Exception $e) { 152 // We continue and return as this means that the element is not there or it is not the same. 153 return; 154 } 155 156 // We also check that the option(s) are still there. We neither wait. 157 foreach ($options as $option) { 158 $optionxpath = $this->get_option_xpath($option, $selectxpath); 159 if (!$this->session->getDriver()->find($optionxpath)) { 160 return; 161 } 162 } 163 164 // Wait for all the possible AJAX requests that have been 165 // already triggered by clicking on the field to be finished. 166 $this->session->wait(behat_base::TIMEOUT * 1000, behat_base::PAGE_READY_JS); 167 168 // Wrapped in a try & catch as we can fall into race conditions 169 // and the element may not be there. 170 try { 171 172 // Repeating the select(s) as some drivers (chrome that I know) are moving 173 // to another option after the general select field click above. 174 $afterfirstoption = false; 175 foreach ($options as $option) { 176 $this->field->selectOption(trim($option), $afterfirstoption); 177 $afterfirstoption = true; 178 } 179 } catch (Exception $e) { 180 // We continue and return as this means that the element is not there or it is not the same. 181 return; 182 } 183 } 184 } 185 186 /** 187 * Returns the text of the currently selected options. 188 * 189 * @return string Comma separated if multiple options are selected. Commas in option texts escaped with backslash. 190 */ 191 public function get_value() { 192 return $this->get_selected_options(); 193 } 194 195 /** 196 * Returns whether the provided argument matches the current value. 197 * 198 * @param mixed $expectedvalue 199 * @return bool 200 */ 201 public function matches($expectedvalue) { 202 203 $multiple = $this->field->hasAttribute('multiple'); 204 205 // Same implementation as the parent if it is a single select. 206 if (!$multiple) { 207 $cleanexpectedvalue = trim($expectedvalue); 208 $selectedtext = trim($this->get_selected_options()); 209 $selectedvalue = trim($this->get_selected_options(false)); 210 if ($cleanexpectedvalue != $selectedvalue && $cleanexpectedvalue != $selectedtext) { 211 return false; 212 } 213 return true; 214 } 215 216 // We are dealing with a multi-select. 217 218 // Can pass multiple comma separated, with valuable commas escaped with backslash. 219 $expectedarr = array(); // Array of passed text options to test. 220 221 // Unescape + trim all options and flip it to have the expected values as keys. 222 $expectedoptions = $this->get_unescaped_options($expectedvalue); 223 224 // Get currently selected option's texts. 225 $texts = $this->get_selected_options(true); 226 $selectedoptiontexts = $this->get_unescaped_options($texts); 227 228 // Get currently selected option's values. 229 $values = $this->get_selected_options(false); 230 $selectedoptionvalues = $this->get_unescaped_options($values); 231 232 // Precheck to speed things up. 233 if (count($expectedoptions) !== count($selectedoptiontexts) || 234 count($expectedoptions) !== count($selectedoptionvalues)) { 235 return false; 236 } 237 238 // We check against string-ordered lists of options. 239 if ($expectedoptions != $selectedoptiontexts && 240 $expectedoptions != $selectedoptionvalues) { 241 return false; 242 } 243 244 return true; 245 } 246 247 /** 248 * Cleans the list of options and returns it as a string separating options with |||. 249 * 250 * @param string $value The string containing the escaped options. 251 * @return string The options 252 */ 253 protected function get_unescaped_options($value) { 254 255 // Can be multiple comma separated, with valuable commas escaped with backslash. 256 $optionsarray = array_map( 257 'trim', 258 preg_replace('/\\\,/', ',', 259 preg_split('/(?<!\\\),/', $value) 260 ) 261 ); 262 263 // Sort by value (keeping the keys is irrelevant). 264 core_collator::asort($optionsarray, SORT_STRING); 265 266 // Returning it as a string which is easier to match against other values. 267 return implode('|||', $optionsarray); 268 } 269 270 /** 271 * Returns the field selected values. 272 * 273 * Externalized from the common behat_form_field API method get_value() as 274 * matches() needs to check against both values and texts. 275 * 276 * @param bool $returntexts Returns the options texts or the options values. 277 * @return string 278 */ 279 protected function get_selected_options($returntexts = true) { 280 281 $method = 'getHtml'; 282 if ($returntexts === false) { 283 $method = 'getValue'; 284 } 285 286 // Is the select multiple? 287 $multiple = $this->field->hasAttribute('multiple'); 288 289 $selectedoptions = array(); // To accumulate found selected options. 290 291 // Selenium getValue() implementation breaks - separates - values having 292 // commas within them, so we'll be looking for options with the 'selected' attribute instead. 293 if ($this->running_javascript()) { 294 // Get all the options in the select and extract their value/text pairs. 295 $alloptions = $this->field->findAll('xpath', '//option'); 296 foreach ($alloptions as $option) { 297 // Is it selected? 298 if ($option->hasAttribute('selected')) { 299 if ($multiple) { 300 // If the select is multiple, text commas must be encoded. 301 $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}())); 302 } else { 303 $selectedoptions[] = trim($option->{$method}()); 304 } 305 } 306 } 307 308 } else { 309 // Goutte does not keep the 'selected' attribute updated, but its getValue() returns 310 // the selected elements correctly, also those having commas within them. 311 312 // Goutte returns the values as an array or as a string depending 313 // on whether multiple options are selected or not. 314 $values = $this->field->getValue(); 315 if (!is_array($values)) { 316 $values = array($values); 317 } 318 319 // Get all the options in the select and extract their value/text pairs. 320 $alloptions = $this->field->findAll('xpath', '//option'); 321 foreach ($alloptions as $option) { 322 // Is it selected? 323 if (in_array($option->getValue(), $values)) { 324 if ($multiple) { 325 // If the select is multiple, text commas must be encoded. 326 $selectedoptions[] = trim(str_replace(',', '\,', $option->{$method}())); 327 } else { 328 $selectedoptions[] = trim($option->{$method}()); 329 } 330 } 331 } 332 } 333 334 return implode(', ', $selectedoptions); 335 } 336 337 /** 338 * Returns the opton XPath based on it's select xpath. 339 * 340 * @param string $option 341 * @param string $selectxpath 342 * @return string xpath 343 */ 344 protected function get_option_xpath($option, $selectxpath) { 345 $valueliteral = $this->session->getSelectorsHandler()->xpathLiteral(trim($option)); 346 return $selectxpath . "/descendant::option[(./@value=$valueliteral or normalize-space(.)=$valueliteral)]"; 347 } 348 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 20:29:05 2014 | Cross-referenced by PHPXref 0.7.1 |