See Release Notes
Long Term Support Release
Differences Between: [Versions 401 and 402] [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 * TinyMCE custom steps definitions. 19 * 20 * @package editor_tiny 21 * @category test 22 * @copyright 2022 Andrew Lyons <andrew@nicols.co.uk> 23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 24 */ 25 26 use Behat\Behat\Hook\Scope\BeforeScenarioScope; 27 use Behat\Mink\Exception\DriverException; 28 use Behat\Mink\Exception\ExpectationException; 29 30 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. 31 require_once (__DIR__ . '/../../../../behat/behat_base.php'); 32 require_once (__DIR__ . '/editor_tiny_helpers.php'); 33 34 /** 35 * TinyMCE custom behat step definitions. 36 * 37 * @package editor_tiny 38 * @category test 39 * @copyright 2022 Andrew Lyons <andrew@nicols.co.uk> 40 */ 41 class behat_editor_tiny extends behat_base implements \core_behat\settable_editor { 42 use editor_tiny_helpers; 43 44 /** 45 * Set Tiny as default editor before executing Tiny tests. 46 * 47 * This step is required to ensure that TinyMCE is set as the current default editor as it may 48 * not always be the default editor. 49 * 50 * Any Scenario, or Feature, which has the `editor_tiny` tag, or any `tiny_*` tag will have 51 * this step executed before the Scenario. 52 * 53 * @BeforeScenario 54 * @param BeforeScenarioScope $scope The Behat Scope 55 */ 56 public function set_default_editor_flag(BeforeScenarioScope $scope): void { 57 // This only applies to a scenario which matches the editor_tiny, or an tiny subplugin. 58 $callback = function (string $tag): bool { 59 return $tag === 'editor_tiny' || substr($tag, 0, 5) === 'tiny_'; 60 }; 61 62 if (!self::scope_tags_match($scope, $callback)) { 63 // This scope does not require TinyMCE. Exit now. 64 return; 65 } 66 67 // TinyMCE is a JavaScript editor so require JS here. 68 $this->require_javascript(); 69 70 $this->execute('behat_general::the_default_editor_is_set_to', ['tiny']); 71 } 72 73 /** 74 * Click on a button for the specified TinyMCE editor. 75 * 76 * @When /^I click on the "(?P<button_string>(?:[^"]|\\")*)" button for the "(?P<locator_string>(?:[^"]|\\")*)" TinyMCE editor$/ 77 * 78 * @param string $button The label of the button 79 * @param string $locator The locator for the editor 80 */ 81 public function i_click_on_button(string $button, string $locator): void { 82 $this->require_tiny_tags(); 83 $container = $this->get_editor_container_for_locator($locator); 84 85 $this->execute('behat_general::i_click_on_in_the', [$button, 'button', $container, 'NodeElement']); 86 } 87 88 /** 89 * Confirm that the button state of the specified button/editor combination matches the expectation. 90 * 91 * @Then /^the "(?P<button_string>(?:[^"]|\\")*)" button of the "(?P<locator_string>(?:[^"]|\\")*)" TinyMCE editor has state "(?P<state_string>(?:[^"]|\\")*)"$/ 92 * 93 * @param string $button The text name of the button 94 * @param string $locator The locator string for the editor 95 * @param string $state The state of the button 96 * @throws ExpectationException Thrown if the button state is not correct 97 */ 98 public function button_state_is(string $button, string $locator, string $state): void { 99 $this->require_tiny_tags(); 100 $container = $this->get_editor_container_for_locator($locator); 101 102 $button = $this->find_button($button, false, $container); 103 $buttonstate = $button->getAttribute('aria-pressed'); 104 105 if ($buttonstate !== $state) { 106 throw new ExpectationException("Button '{$button}' is in state '{$buttonstate}' not '{$state}'", $this->getSession()); 107 } 108 } 109 110 /** 111 * Click on a button for the specified TinyMCE editor. 112 * 113 * @When /^I click on the "(?P<menuitem_string>(?:[^"]|\\")*)" menu item for the "(?P<locator_string>(?:[^"]|\\")*)" TinyMCE editor$/ 114 * 115 * @param string $menuitem The label of the menu item 116 * @param string $locator The locator for the editor 117 */ 118 public function i_click_on_menuitem_in_menu(string $menuitem, string $locator): void { 119 $this->require_tiny_tags(); 120 $container = $this->get_editor_container_for_locator($locator); 121 122 $menubar = $container->find('css', '[role="menubar"]'); 123 124 $menus = array_map(function(string $value): string { 125 return trim($value); 126 }, explode('>', $menuitem)); 127 128 // Open the menu bar. 129 $mainmenu = array_shift($menus); 130 $this->execute('behat_general::i_click_on_in_the', [$mainmenu, 'button', $menubar, 'NodeElement']); 131 132 foreach ($menus as $menuitem) { 133 // Find the menu that was opened. 134 $openmenu = $this->find('css', '.tox-selected-menu'); 135 136 // Move the mouse to the first item in the list. 137 // This is required because WebDriver takes the shortest path to the next click location, 138 // which will mean crossing across other menu items. 139 $firstlink = $openmenu->find('css', "[role^='menuitem'] .tox-collection__item-icon"); 140 $firstlink->mouseover(); 141 142 // Now match by title where the role matches any menuitem, or menuitemcheckbox, or menuitem*. 143 $link = $openmenu->find('css', "[title='{$menuitem}'][role^='menuitem']"); 144 $this->execute('behat_general::i_click_on', [$link, 'NodeElement']); 145 } 146 } 147 148 /** 149 * Select the element type/index for the specified TinyMCE editor. 150 * 151 * @When /^I select the "(?P<textlocator_string>(?:[^"]|\\")*)" element in position "(?P<position_int>(?:[^"]|\\")*)" of the "(?P<locator_string>(?:[^"]|\\")*)" TinyMCE editor$/ 152 * @param string $textlocator The type of element to select (for example `p` or `span`) 153 * @param int $position The zero-indexed position 154 * @param string $locator The editor to select within 155 */ 156 public function select_text(string $textlocator, int $position, string $locator): void { 157 $this->require_tiny_tags(); 158 159 $editor = $this->get_textarea_for_locator($locator); 160 $editorid = $editor->getAttribute('id'); 161 162 // Ensure that a name is set on the iframe relating to the editorid. 163 $js = <<<EOF 164 const element = instance.dom.select("$textlocator}")[$position}]; 165 instance.selection.select(element); 166 EOF; 167 168 $this->execute_javascript_for_editor($editorid, $js); 169 } 170 171 /** 172 * Upload a file in the file picker using the repository_upload plugin. 173 * 174 * Note: This step assumes we are already in the file picker. 175 * Note: This step is for use by TinyMCE and will be removed once an appropriate step is added to core. 176 * See MDL-76001 for details. 177 * 178 * @Given /^I upload "(?P<filepath_string>(?:[^"]|\\")*)" to the file picker for TinyMCE$/ 179 */ 180 public function i_upload_a_file_in_the_filepicker(string $filepath): void { 181 if (!$this->has_tag('javascript')) { 182 throw new DriverException('The file picker is only available with javascript enabled'); 183 } 184 185 if (!$this->has_tag('_file_upload')) { 186 throw new DriverException('File upload tests must have the @_file_upload tag on either the scenario or feature.'); 187 } 188 189 if (!$this->has_tag('editor_tiny')) { 190 throw new DriverException('This step is intended for use in TinyMCE. Please vote for MDL-76001'); 191 } 192 193 $filepicker = $this->find('dialogue', get_string('filepicker', 'core_repository')); 194 195 $this->execute('behat_general::i_click_on_in_the', [ 196 get_string('pluginname', 'repository_upload'), 'link', 197 $filepicker, 'NodeElement', 198 ]); 199 200 $reporegion = $filepicker->find('css', '.fp-repo-items'); 201 $fileinput = $this->find('field', get_string('attachment', 'core_repository'), false, $reporegion); 202 203 $filepath = $this->normalise_fixture_filepath($filepath); 204 205 $fileinput->attachFile($filepath); 206 $this->execute('behat_general::i_click_on_in_the', [ 207 get_string('upload', 'repository'), 'button', 208 $reporegion, 'NodeElement', 209 ]); 210 } 211 212 /** 213 * Select in the editor. 214 * 215 * @param string $locator 216 * @param string $type 217 * @param string $editorlocator 218 * 219 * @Given /^I select the "(?P<locator_string>(?:[^"]|\\")*)" "(?P<type_string>(?:[^"]|\\")*)" in the "(?P<editorlocator_string>(?:[^"]|\\")*)" TinyMCE editor$/ 220 */ 221 public function select_in_editor(string $locator, string $type, string $editorlocator): void { 222 $this->require_tiny_tags(); 223 224 $editor = $this->get_textarea_for_locator($editorlocator); 225 $editorid = $editor->getAttribute('id'); 226 227 // Get the iframe name for this editor. 228 $iframename = $this->get_editor_iframe_name($editorlocator); 229 230 // Switch to it. 231 $this->execute('behat_general::switch_to_iframe', [$iframename]); 232 233 // Find the element. 234 $element = $this->find($type, $locator); 235 $xpath = $element->getXpath(); 236 237 // Switch back to the main window. 238 $this->execute('behat_general::switch_to_the_main_frame', []); 239 240 // Select the Node using the xpath. 241 $js = <<<EOF 242 const editorDocument = instance.getDoc(); 243 const element = editorDocument.evaluate( 244 "$xpath}", 245 editorDocument, 246 null, 247 XPathResult.FIRST_ORDERED_NODE_TYPE, 248 null 249 ).singleNodeValue; 250 251 instance.selection.select(element); 252 EOF; 253 $this->execute_javascript_for_editor($editorid, $js); 254 } 255 256 /** 257 * Expand all of the TinyMCE toolbars. 258 * 259 * @Given /^I expand all toolbars for the "(?P<editorlocator_string>(?:[^"]|\\")*)" TinyMCE editor$/ 260 * 261 * @param string $locator 262 */ 263 public function expand_all_toolbars(string $editorlocator): void { 264 $this->require_tiny_tags(); 265 266 $editor = $this->get_editor_container_for_locator($editorlocator); 267 try { 268 $button = $this->find('button', get_string('tiny:more...', 'editor_tiny'), false, $editor); 269 } catch (ExpectationException $e) { 270 // No more button, so no need to expand. 271 return; 272 } 273 274 if ($button->getAttribute(('aria-pressed')) === 'false') { 275 $this->execute('behat_general::i_click_on', [$button, 'NodeElement']); 276 } 277 } 278 279 /** 280 * Switch to the TinyMCE iframe using a selector. 281 * 282 * @param string $editorlocator 283 * 284 * @When /^I switch to the "(?P<editorlocator_string>(?:[^"]|\\")*)" TinyMCE editor iframe$/ 285 */ 286 public function switch_to_tiny_iframe(string $editorlocator): void { 287 $this->require_tiny_tags(); 288 289 // Get the iframe name for this editor. 290 $iframename = $this->get_editor_iframe_name($editorlocator); 291 292 // Switch to it. 293 $this->execute('behat_general::switch_to_iframe', [$iframename]); 294 } 295 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body