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 namespace qbank_managecategories; 18 19 defined('MOODLE_INTERNAL') || die(); 20 21 global $CFG; 22 require_once($CFG->dirroot . '/question/editlib.php'); 23 24 use context; 25 use context_course; 26 use context_module; 27 use moodle_url; 28 use core_question\local\bank\question_edit_contexts; 29 use stdClass; 30 31 /** 32 * Unit tests for qbank_managecategories\question_category_object. 33 * 34 * @package qbank_managecategories 35 * @copyright 2019 the Open University 36 * @author 2021, Guillermo Gomez Arias <guillermogomez@catalyst-au.net> 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 * @coversDefaultClass \qbank_managecategories\question_category_object 39 */ 40 class question_category_object_test extends \advanced_testcase { 41 42 /** 43 * @var question_category_object used in the tests. 44 */ 45 protected $qcobject; 46 47 /** 48 * @var context a context to use. 49 */ 50 protected $context; 51 52 /** 53 * @var stdClass top category in context. 54 */ 55 protected $topcat; 56 57 /** 58 * @var stdClass course object. 59 */ 60 protected $course; 61 62 /** 63 * @var stdClass quiz object. 64 */ 65 protected $quiz; 66 67 /** 68 * @var question_edit_contexts 69 */ 70 private $qcontexts; 71 72 /** 73 * @var false|object|stdClass|null 74 */ 75 private $defaultcategoryobj; 76 77 /** 78 * @var string 79 */ 80 private $defaultcategory; 81 82 /** 83 * @var question_category_object 84 */ 85 private $qcobjectquiz; 86 87 protected function setUp(): void { 88 parent::setUp(); 89 self::setAdminUser(); 90 $this->resetAfterTest(); 91 $this->context = context_course::instance(SITEID); 92 $contexts = new question_edit_contexts($this->context); 93 $this->topcat = question_get_top_category($this->context->id, true); 94 $this->qcobject = new question_category_object(null, 95 new moodle_url('/question/bank/managecategories/category.php', ['courseid' => SITEID]), 96 $contexts->having_one_edit_tab_cap('categories'), 0, null, 0, 97 $contexts->having_cap('moodle/question:add')); 98 99 // Set up tests in a quiz context. 100 $this->course = $this->getDataGenerator()->create_course(); 101 $this->quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $this->course->id]); 102 $this->qcontexts = new question_edit_contexts(context_module::instance($this->quiz->cmid)); 103 104 $this->defaultcategoryobj = question_make_default_categories([$this->qcontexts->lowest()]); 105 $this->defaultcategory = $this->defaultcategoryobj->id . ',' . $this->defaultcategoryobj->contextid; 106 107 $this->qcobjectquiz = new question_category_object( 108 1, 109 new moodle_url('/mod/quiz/edit.php', ['cmid' => $this->quiz->cmid]), 110 $this->qcontexts->having_one_edit_tab_cap('categories'), 111 $this->defaultcategoryobj->id, 112 $this->defaultcategory, 113 null, 114 $this->qcontexts->having_cap('moodle/question:add')); 115 116 } 117 118 /** 119 * Test creating a category. 120 * 121 * @covers ::add_category 122 */ 123 public function test_add_category_no_idnumber() { 124 global $DB; 125 126 $id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 127 'New category', '', true, FORMAT_HTML, ''); // No idnumber passed as '' to match form data. 128 129 $newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST); 130 $this->assertSame('New category', $newcat->name); 131 $this->assertNull($newcat->idnumber); 132 } 133 134 /** 135 * Test creating a category with a tricky idnumber. 136 * 137 * @covers ::add_category 138 */ 139 public function test_add_category_set_idnumber_0() { 140 global $DB; 141 142 $id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 143 'New category', '', true, FORMAT_HTML, '0'); 144 145 $newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST); 146 $this->assertSame('New category', $newcat->name); 147 $this->assertSame('0', $newcat->idnumber); 148 } 149 150 /** 151 * Trying to add a category with duplicate idnumber blanks it. 152 * (In reality, this would probably get caught by form validation.) 153 * 154 * @covers ::add_category 155 */ 156 public function test_add_category_try_to_set_duplicate_idnumber() { 157 global $DB; 158 159 $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 160 'Existing category', '', true, FORMAT_HTML, 'frog'); 161 162 $id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 163 'New category', '', true, FORMAT_HTML, 'frog'); 164 165 $newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST); 166 $this->assertSame('New category', $newcat->name); 167 $this->assertNull($newcat->idnumber); 168 } 169 170 /** 171 * Test updating a category. 172 * 173 * @covers ::update_category 174 */ 175 public function test_update_category() { 176 global $DB; 177 178 $id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 179 'Old name', 'Description', true, FORMAT_HTML, 'frog'); 180 181 $this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid, 182 'New name', 'New description', FORMAT_HTML, '0', false); 183 184 $newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST); 185 $this->assertSame('New name', $newcat->name); 186 $this->assertSame('0', $newcat->idnumber); 187 } 188 189 /** 190 * Test updating a category to remove the idnumber. 191 * 192 * @covers ::update_category 193 */ 194 public function test_update_category_removing_idnumber() { 195 global $DB; 196 197 $id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 198 'Old name', 'Description', true, FORMAT_HTML, 'frog'); 199 200 $this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid, 201 'New name', 'New description', FORMAT_HTML, '', false); 202 203 $newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST); 204 $this->assertSame('New name', $newcat->name); 205 $this->assertNull($newcat->idnumber); 206 } 207 208 /** 209 * Test updating a category without changing the idnumber. 210 * 211 * @covers ::update_category 212 */ 213 public function test_update_category_dont_change_idnumber() { 214 global $DB; 215 216 $id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 217 'Old name', 'Description', true, FORMAT_HTML, 'frog'); 218 219 $this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid, 220 'New name', 'New description', FORMAT_HTML, 'frog', false); 221 222 $newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST); 223 $this->assertSame('New name', $newcat->name); 224 $this->assertSame('frog', $newcat->idnumber); 225 } 226 227 /** 228 * Trying to update a category so its idnumber duplicates idnumber blanks it. 229 * (In reality, this would probably get caught by form validation.) 230 * 231 * @covers ::update_category 232 */ 233 public function test_update_category_try_to_set_duplicate_idnumber() { 234 global $DB; 235 236 $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 237 'Existing category', '', true, FORMAT_HTML, 'toad'); 238 $id = $this->qcobject->add_category($this->topcat->id . ',' . $this->topcat->contextid, 239 'old name', '', true, FORMAT_HTML, 'frog'); 240 241 $this->qcobject->update_category($id, $this->topcat->id . ',' . $this->topcat->contextid, 242 'New name', '', FORMAT_HTML, 'toad', false); 243 244 $newcat = $DB->get_record('question_categories', ['id' => $id], '*', MUST_EXIST); 245 $this->assertSame('New name', $newcat->name); 246 $this->assertNull($newcat->idnumber); 247 } 248 249 /** 250 * Test the question category created event. 251 * 252 * @covers ::add_category 253 */ 254 public function test_question_category_created() { 255 // Trigger and capture the event. 256 $sink = $this->redirectEvents(); 257 $categoryid = $this->qcobjectquiz->add_category($this->defaultcategory, 'newcategory', '', true); 258 $events = $sink->get_events(); 259 $event = reset($events); 260 261 // Check that the event data is valid. 262 $this->assertInstanceOf('\core\event\question_category_created', $event); 263 $this->assertEquals(context_module::instance($this->quiz->cmid), $event->get_context()); 264 $expected = [$this->course->id, 'quiz', 'addcategory', 'view.php?id=' . $this->quiz->cmid , $categoryid, $this->quiz->cmid]; 265 $this->assertEventLegacyLogData($expected, $event); 266 $this->assertEventContextNotUsed($event); 267 } 268 269 /** 270 * Test the question category deleted event. 271 * 272 * @covers ::delete_category 273 */ 274 public function test_question_category_deleted() { 275 // Create the category. 276 $categoryid = $this->qcobjectquiz->add_category($this->defaultcategory, 'newcategory', '', true); 277 278 // Trigger and capture the event. 279 $sink = $this->redirectEvents(); 280 $this->qcobjectquiz->delete_category($categoryid); 281 $events = $sink->get_events(); 282 $event = reset($events); 283 284 // Check that the event data is valid. 285 $this->assertInstanceOf('\core\event\question_category_deleted', $event); 286 $this->assertEquals(context_module::instance($this->quiz->cmid), $event->get_context()); 287 $this->assertEquals($categoryid, $event->objectid); 288 $this->assertDebuggingNotCalled(); 289 } 290 291 /** 292 * Test the question category updated event. 293 * 294 * @covers ::update_category 295 */ 296 public function test_question_category_updated() { 297 // Create the category. 298 $categoryid = $this->qcobjectquiz->add_category($this->defaultcategory, 'newcategory', '', true); 299 300 // Trigger and capture the event. 301 $sink = $this->redirectEvents(); 302 $this->qcobjectquiz->update_category($categoryid, $this->defaultcategory, 'updatedcategory', '', FORMAT_HTML, '', false); 303 $events = $sink->get_events(); 304 $event = reset($events); 305 306 // Check that the event data is valid. 307 $this->assertInstanceOf('\core\event\question_category_updated', $event); 308 $this->assertEquals(context_module::instance($this->quiz->cmid), $event->get_context()); 309 $this->assertEquals($categoryid, $event->objectid); 310 $this->assertDebuggingNotCalled(); 311 } 312 313 /** 314 * Test the question category viewed event. 315 * There is no external API for viewing the category, so the unit test will simply 316 * create and trigger the event and ensure data is returned as expected. 317 * 318 * @covers ::add_category 319 */ 320 public function test_question_category_viewed() { 321 // Create the category. 322 $categoryid = $this->qcobjectquiz->add_category($this->defaultcategory, 'newcategory', '', true); 323 324 // Log the view of this category. 325 $category = new stdClass(); 326 $category->id = $categoryid; 327 $context = context_module::instance($this->quiz->cmid); 328 $event = \core\event\question_category_viewed::create_from_question_category_instance($category, $context); 329 330 // Trigger and capture the event. 331 $sink = $this->redirectEvents(); 332 $event->trigger(); 333 $events = $sink->get_events(); 334 $event = reset($events); 335 336 // Check that the event data is valid. 337 $this->assertInstanceOf('\core\event\question_category_viewed', $event); 338 $this->assertEquals(context_module::instance($this->quiz->cmid), $event->get_context()); 339 $this->assertEquals($categoryid, $event->objectid); 340 $this->assertDebuggingNotCalled(); 341 342 } 343 344 /** 345 * Test that get_real_question_ids_in_category() returns question id 346 * of a shortanswer question in a category. 347 * 348 * @covers ::get_real_question_ids_in_category 349 */ 350 public function test_get_real_question_ids_in_category_shortanswer() { 351 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 352 $categoryid = $this->defaultcategoryobj->id; 353 354 // Short answer question is made of one question. 355 $shortanswer = $generator->create_question('shortanswer', null, ['category' => $categoryid]); 356 $questionids = $this->qcobject->get_real_question_ids_in_category($categoryid); 357 $this->assertCount(1, $questionids); 358 $this->assertContains($shortanswer->id, $questionids); 359 } 360 361 /** 362 * Test that get_real_question_ids_in_category() returns question id 363 * of a multianswer question in a category. 364 * 365 * @covers ::get_real_question_ids_in_category 366 */ 367 public function test_get_real_question_ids_in_category_multianswer() { 368 global $DB; 369 $countq = $DB->count_records('question'); 370 $countqbe = $DB->count_records('question_bank_entries'); 371 372 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 373 $categoryid = $this->defaultcategoryobj->id; 374 375 // Multi answer question is made of one parent and two child questions. 376 $multianswer = $generator->create_question('multianswer', null, ['category' => $categoryid]); 377 $questionids = $this->qcobject->get_real_question_ids_in_category($categoryid); 378 $this->assertCount(1, $questionids); 379 $this->assertContains($multianswer->id, $questionids); 380 $this->assertEquals(3, $DB->count_records('question') - $countq); 381 $this->assertEquals(3, $DB->count_records('question_bank_entries') - $countqbe); 382 } 383 384 /** 385 * Test that get_real_question_ids_in_category() returns question ids 386 * of two versions of a multianswer question in a category. 387 * 388 * @covers ::get_real_question_ids_in_category 389 */ 390 public function test_get_real_question_ids_in_category_multianswer_two_versions() { 391 global $DB; 392 $countq = $DB->count_records('question'); 393 $countqv = $DB->count_records('question_versions'); 394 $countqbe = $DB->count_records('question_bank_entries'); 395 396 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 397 $categoryid = $this->defaultcategoryobj->id; 398 399 // Create two versions of a multianswer question which will lead to 400 // 2 parents and 4 child questions in the question bank. 401 $multianswer = $generator->create_question('multianswer', null, ['category' => $categoryid]); 402 $multianswernew = $generator->update_question($multianswer, null, ['name' => 'This is a new version']); 403 $questionids = $this->qcobject->get_real_question_ids_in_category($categoryid); 404 $this->assertCount(2, $questionids); 405 $this->assertContains($multianswer->id, $questionids); 406 $this->assertContains($multianswernew->id, $questionids); 407 $this->assertEquals(6, $DB->count_records('question') - $countq); 408 $this->assertEquals(6, $DB->count_records('question_versions') - $countqv); 409 $this->assertEquals(3, $DB->count_records('question_bank_entries') - $countqbe); 410 } 411 412 /** 413 * Test that get_real_question_ids_in_category() returns question id 414 * of a multianswer question in a category even if their child questions are 415 * linked to a category that doesn't exist. 416 * 417 * @covers ::get_real_question_ids_in_category 418 */ 419 public function test_get_real_question_ids_in_category_multianswer_bad_data() { 420 global $DB; 421 $countqbe = $DB->count_records('question_bank_entries'); 422 423 $generator = $this->getDataGenerator()->get_plugin_generator('core_question'); 424 $categoryid = $this->defaultcategoryobj->id; 425 426 // Multi answer question is made of one parent and two child questions. 427 $multianswer = $generator->create_question('multianswer', null, ['category' => $categoryid]); 428 $qversion = $DB->get_record('question_versions', ['questionid' => $multianswer->id]); 429 430 // Update category id for child questions to a category that doesn't exist. 431 $DB->set_field_select('question_bank_entries', 'questioncategoryid', 432 123456, 'id <> :id', ['id' => $qversion->questionbankentryid]); 433 434 $questionids = $this->qcobject->get_real_question_ids_in_category($categoryid); 435 $this->assertCount(1, $questionids); 436 $this->assertContains($multianswer->id, $questionids); 437 $this->assertEquals(3, $DB->count_records('question_bank_entries') - $countqbe); 438 } 439 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body