See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 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 // NOTE: no MOODLE_INTERNAL test here, this file may be required by behat before including /config.php. 18 19 require_once (__DIR__ . '/behat_question_base.php'); 20 21 use Behat\Gherkin\Node\TableNode as TableNode; 22 use Behat\Mink\Exception\ExpectationException as ExpectationException; 23 use Behat\Mink\Exception\ElementNotFoundException as ElementNotFoundException; 24 25 /** 26 * Steps definitions related with the question bank management. 27 * 28 * @package core_question 29 * @category test 30 * @copyright 2013 David MonllaĆ³ 31 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 32 */ 33 class behat_core_question extends behat_question_base { 34 35 /** 36 * Convert page names to URLs for steps like 'When I am on the "[page name]" page'. 37 * 38 * Recognised page names are: 39 * | None so far! | | 40 * 41 * @param string $page name of the page, with the component name removed e.g. 'Admin notification'. 42 * @return moodle_url the corresponding URL. 43 * @throws Exception with a meaningful error message if the specified page cannot be found. 44 */ 45 protected function resolve_page_url(string $page): moodle_url { 46 switch (strtolower($page)) { 47 default: 48 throw new Exception('Unrecognised core_question page type "' . $page . '."'); 49 } 50 } 51 52 /** 53 * Convert page names to URLs for steps like 'When I am on the "[identifier]" "[page type]" page'. 54 * 55 * Recognised page names are: 56 * | pagetype | name meaning | description | 57 * | course question bank | Course name | The question bank for a course | 58 * | course question import | Course name | The import questions screen for a course | 59 * | course question export | Course name | The export questions screen for a course | 60 * | preview | Question name | The screen to preview a question | 61 * | edit | Question name | The screen to edit a question | 62 * 63 * @param string $type identifies which type of page this is, e.g. 'Preview'. 64 * @param string $identifier identifies the particular page, e.g. 'My question'. 65 * @return moodle_url the corresponding URL. 66 * @throws Exception with a meaningful error message if the specified page cannot be found. 67 */ 68 protected function resolve_page_instance_url(string $type, string $identifier): moodle_url { 69 switch (strtolower($type)) { 70 case 'course question bank': 71 return new moodle_url('/question/edit.php', 72 ['courseid' => $this->get_course_id($identifier)]); 73 74 case 'course question categories': 75 return new moodle_url('/question/bank/managecategories/category.php', 76 ['courseid' => $this->get_course_id($identifier)]); 77 78 case 'course question import': 79 return new moodle_url('/question/bank/importquestions/import.php', 80 ['courseid' => $this->get_course_id($identifier)]); 81 82 case 'course question export': 83 return new moodle_url('/question/bank/exportquestions/export.php', 84 ['courseid' => $this->get_course_id($identifier)]); 85 86 case 'preview': 87 [$questionid, $otheridtype, $otherid] = $this->find_question_by_name($identifier); 88 return new moodle_url('/question/bank/previewquestion/preview.php', 89 ['id' => $questionid, $otheridtype => $otherid]); 90 91 case 'edit': 92 [$questionid, $otheridtype, $otherid] = $this->find_question_by_name($identifier); 93 return new moodle_url('/question/bank/editquestion/question.php', 94 ['id' => $questionid, $otheridtype => $otherid]); 95 96 default: 97 throw new Exception('Unrecognised core_question page type "' . $type . '."'); 98 } 99 } 100 101 /** 102 * Find a question, and where it is, from the question name. 103 * 104 * This is a helper used by resolve_page_instance_url. 105 * 106 * @param string $questionname 107 * @return array with three elemnets, int question id, a string 'cmid' or 'courseid', 108 * and int either cmid or courseid as applicable. 109 */ 110 protected function find_question_by_name(string $questionname): array { 111 global $DB; 112 $questionid = $DB->get_field('question', 'id', ['name' => $questionname], MUST_EXIST); 113 $question = question_bank::load_question_data($questionid); 114 $context = context_helper::instance_by_id($question->contextid); 115 116 if ($context->contextlevel == CONTEXT_MODULE) { 117 return [$questionid, 'cmid', $context->instanceid]; 118 } else if ($context->contextlevel == CONTEXT_COURSE) { 119 return [$questionid, 'courseid', $context->instanceid]; 120 } else { 121 throw new coding_exception('Unsupported context level ' . $context->contextlevel); 122 } 123 } 124 125 /** 126 * Creates a question in the current course questions bank with the provided data. 127 * This step can only be used when creating question types composed by a single form. 128 * 129 * @Given /^I add a "(?P<question_type_name_string>(?:[^"]|\\")*)" question filling the form with:$/ 130 * @param string $questiontypename The question type name 131 * @param TableNode $questiondata The data to fill the question type form. 132 */ 133 public function i_add_a_question_filling_the_form_with($questiontypename, TableNode $questiondata) { 134 // Click on create question. 135 $this->execute('behat_forms::press_button', get_string('createnewquestion', 'question')); 136 137 // Add question. 138 $this->finish_adding_question($questiontypename, $questiondata); 139 } 140 141 /** 142 * Checks the state of the specified question. 143 * 144 * @Then /^the state of "(?P<question_description_string>(?:[^"]|\\")*)" question is shown as "(?P<state_string>(?:[^"]|\\")*)"$/ 145 * @throws ExpectationException 146 * @throws ElementNotFoundException 147 * @param string $questiondescription 148 * @param string $state 149 */ 150 public function the_state_of_question_is_shown_as($questiondescription, $state) { 151 152 // Using xpath literal to avoid quotes problems. 153 $questiondescriptionliteral = behat_context_helper::escape($questiondescription); 154 $stateliteral = behat_context_helper::escape($state); 155 156 // Split in two checkings to give more feedback in case of exception. 157 $exception = new ElementNotFoundException($this->getSession(), 'Question "' . $questiondescription . '" '); 158 $questionxpath = "//div[contains(concat(' ', normalize-space(@class), ' '), ' que ')]" . 159 "[contains(div[@class='content']/div[contains(concat(' ', normalize-space(@class), ' '), ' formulation ')]," . 160 "{$questiondescriptionliteral})]"; 161 $this->find('xpath', $questionxpath, $exception); 162 163 $exception = new ExpectationException('Question "' . $questiondescription . 164 '" state is not "' . $state . '"', $this->getSession()); 165 $xpath = $questionxpath . "/div[@class='info']/div[@class='state' and contains(., {$stateliteral})]"; 166 $this->find('xpath', $xpath, $exception); 167 } 168 169 /** 170 * Activates a particular action on a particular question in the question bank UI. 171 * 172 * @When I choose :action action for :questionname in the question bank 173 * @param string $action the label for the action you want to activate. 174 * @param string $questionname the question name. 175 */ 176 public function i_action_the_question($action, $questionname) { 177 $this->execute('behat_action_menu::i_choose_in_the_named_menu_in_container', [ 178 $action, 179 get_string('edit', 'core'), 180 $questionname, 181 'table_row', 182 ]); 183 } 184 185 /** 186 * Checks that action does exist for a question. 187 * 188 * @Then the :action action should exist for the :questionname question in the question bank 189 * @param string $action the label for the action you want to activate. 190 * @param string $questionname the question name. 191 */ 192 public function action_exists($action, $questionname) { 193 $this->execute('behat_action_menu::item_should_exist_in_the', [ 194 $action, 195 get_string('edit', 'core'), 196 $questionname, 197 'table_row', 198 ]); 199 } 200 201 /** 202 * Checks that action does not exist for a question. 203 * 204 * @Then the :action action should not exist for the :questionname question in the question bank 205 * @param string $action the label for the action you want to activate. 206 * @param string $questionname the question name. 207 */ 208 public function action_not_exists($action, $questionname) { 209 $this->execute('behat_action_menu::item_should_not_exist_in_the', [ 210 $action, 211 get_string('edit', 'core'), 212 $questionname, 213 'table_row', 214 ]); 215 } 216 217 /** 218 * A particular bulk action is visible in the question bank UI. 219 * 220 * @When I should see question bulk action :action 221 * @param string $action the value of the input for the action. 222 */ 223 public function i_should_see_question_bulk_action($action) { 224 // Check if its visible. 225 $this->execute("behat_general::should_be_visible", 226 ["#bulkactionsui-container input[name='$action']", "css_element"]); 227 } 228 229 /** 230 * A particular bulk action should not be visible in the question bank UI. 231 * 232 * @When I should not see question bulk action :action 233 * @param string $action the value of the input for the action. 234 */ 235 public function i_should_not_see_question_bulk_action($action) { 236 // Check if its visible. 237 $this->execute("behat_general::should_not_be_visible", 238 ["#bulkactionsui-container input[name='$action']", "css_element"]); 239 } 240 241 /** 242 * A click on a particular bulk action in the question bank UI. 243 * 244 * @When I click on question bulk action :action 245 * @param string $action the value of the input for the action. 246 */ 247 public function i_click_on_question_bulk_action($action) { 248 // Click the bulk action. 249 $this->execute("behat_general::i_click_on", 250 ["#bulkactionsui-container input[name='$action']", "css_element"]); 251 } 252 253 /** 254 * Change the question type of the give question to a type that does not exist. 255 * 256 * This is useful for testing robustness of the code when a question type 257 * has been uninstalled, even though there are still questions of that type 258 * or attempts at them. 259 * 260 * In order to set things up, you probably need to start by generating 261 * questions of a valid type, then using this to change the type once the 262 * data is created. 263 * 264 * @Given question :questionname is changed to simulate being of an uninstalled type 265 * @param string $questionname the question name. 266 */ 267 public function change_question_to_nonexistant_type($questionname) { 268 global $DB; 269 [$id] = $this->find_question_by_name($questionname); 270 271 // Check our assumption. 272 $nonexistanttype = 'invalidqtype'; 273 if (question_bank::is_qtype_installed($nonexistanttype)) { 274 throw new coding_exception('This code assumes that the qtype_' . $nonexistanttype . 275 ' is not a valid plugin name, but that plugin now seems to exist!'); 276 } 277 278 $DB->set_field('question', 'qtype', $nonexistanttype, ['id' => $id]); 279 question_bank::notify_question_edited($id); 280 } 281 282 /** 283 * Forcibly delete a question from the database. 284 * 285 * This is useful for testing robustness of the code when a question 286 * record is no longer in the database, even though it is referred to. 287 * Obviously, this should never happen, but it has been known to in the past 288 * and so we sometimes need to be able to test the code can handle this situation. 289 * 290 * In order to set things up, you probably need to start by generating 291 * a valid questions, then using this to remove it once the data is created. 292 * 293 * @Given question :questionname no longer exists in the database 294 * @param string $questionname the question name. 295 */ 296 public function remove_question_from_db($questionname) { 297 global $DB; 298 [$id] = $this->find_question_by_name($questionname); 299 $DB->delete_records('question', ['id' => $id]); 300 question_bank::notify_question_edited($id); 301 } 302 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body