Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403] [Versions 402 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 * Unit tests for (some of) mod/quiz/locallib.php. 19 * 20 * @package mod_quiz 21 * @category test 22 * @copyright 2008 The Open University 23 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License 24 */ 25 namespace mod_quiz; 26 27 use core_external\external_api; 28 use mod_quiz\quiz_settings; 29 30 defined('MOODLE_INTERNAL') || die(); 31 32 global $CFG; 33 require_once($CFG->dirroot . '/mod/quiz/lib.php'); 34 require_once($CFG->dirroot . '/mod/quiz/tests/quiz_question_helper_test_trait.php'); 35 36 /** 37 * @copyright 2008 The Open University 38 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License 39 */ 40 class lib_test extends \advanced_testcase { 41 use \quiz_question_helper_test_trait; 42 43 public function test_quiz_has_grades() { 44 $quiz = new \stdClass(); 45 $quiz->grade = '100.0000'; 46 $quiz->sumgrades = '100.0000'; 47 $this->assertTrue(quiz_has_grades($quiz)); 48 $quiz->sumgrades = '0.0000'; 49 $this->assertFalse(quiz_has_grades($quiz)); 50 $quiz->grade = '0.0000'; 51 $this->assertFalse(quiz_has_grades($quiz)); 52 $quiz->sumgrades = '100.0000'; 53 $this->assertFalse(quiz_has_grades($quiz)); 54 } 55 56 public function test_quiz_format_grade() { 57 $quiz = new \stdClass(); 58 $quiz->decimalpoints = 2; 59 $this->assertEquals(quiz_format_grade($quiz, 0.12345678), format_float(0.12, 2)); 60 $this->assertEquals(quiz_format_grade($quiz, 0), format_float(0, 2)); 61 $this->assertEquals(quiz_format_grade($quiz, 1.000000000000), format_float(1, 2)); 62 $quiz->decimalpoints = 0; 63 $this->assertEquals(quiz_format_grade($quiz, 0.12345678), '0'); 64 } 65 66 public function test_quiz_get_grade_format() { 67 $quiz = new \stdClass(); 68 $quiz->decimalpoints = 2; 69 $this->assertEquals(quiz_get_grade_format($quiz), 2); 70 $this->assertEquals($quiz->questiondecimalpoints, -1); 71 $quiz->questiondecimalpoints = 2; 72 $this->assertEquals(quiz_get_grade_format($quiz), 2); 73 $quiz->decimalpoints = 3; 74 $quiz->questiondecimalpoints = -1; 75 $this->assertEquals(quiz_get_grade_format($quiz), 3); 76 $quiz->questiondecimalpoints = 4; 77 $this->assertEquals(quiz_get_grade_format($quiz), 4); 78 } 79 80 public function test_quiz_format_question_grade() { 81 $quiz = new \stdClass(); 82 $quiz->decimalpoints = 2; 83 $quiz->questiondecimalpoints = 2; 84 $this->assertEquals(quiz_format_question_grade($quiz, 0.12345678), format_float(0.12, 2)); 85 $this->assertEquals(quiz_format_question_grade($quiz, 0), format_float(0, 2)); 86 $this->assertEquals(quiz_format_question_grade($quiz, 1.000000000000), format_float(1, 2)); 87 $quiz->decimalpoints = 3; 88 $quiz->questiondecimalpoints = -1; 89 $this->assertEquals(quiz_format_question_grade($quiz, 0.12345678), format_float(0.123, 3)); 90 $this->assertEquals(quiz_format_question_grade($quiz, 0), format_float(0, 3)); 91 $this->assertEquals(quiz_format_question_grade($quiz, 1.000000000000), format_float(1, 3)); 92 $quiz->questiondecimalpoints = 4; 93 $this->assertEquals(quiz_format_question_grade($quiz, 0.12345678), format_float(0.1235, 4)); 94 $this->assertEquals(quiz_format_question_grade($quiz, 0), format_float(0, 4)); 95 $this->assertEquals(quiz_format_question_grade($quiz, 1.000000000000), format_float(1, 4)); 96 } 97 98 /** 99 * Test deleting a quiz instance. 100 */ 101 public function test_quiz_delete_instance() { 102 global $SITE, $DB; 103 $this->resetAfterTest(true); 104 $this->setAdminUser(); 105 106 // Setup a quiz with 1 standard and 1 random question. 107 $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz'); 108 $quiz = $quizgenerator->create_instance(['course' => $SITE->id, 'questionsperpage' => 3, 'grade' => 100.0]); 109 110 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 111 $cat = $questiongenerator->create_question_category(); 112 $standardq = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); 113 114 quiz_add_quiz_question($standardq->id, $quiz); 115 $this->add_random_questions($quiz->id, 0, $cat->id, 1); 116 117 // Get the random question. 118 $randomq = $DB->get_record('question', ['qtype' => 'random']); 119 120 quiz_delete_instance($quiz->id); 121 122 // Check that the random question was deleted. 123 if ($randomq) { 124 $count = $DB->count_records('question', ['id' => $randomq->id]); 125 $this->assertEquals(0, $count); 126 } 127 // Check that the standard question was not deleted. 128 $count = $DB->count_records('question', ['id' => $standardq->id]); 129 $this->assertEquals(1, $count); 130 131 // Check that all the slots were removed. 132 $count = $DB->count_records('quiz_slots', ['quizid' => $quiz->id]); 133 $this->assertEquals(0, $count); 134 135 // Check that the quiz was removed. 136 $count = $DB->count_records('quiz', ['id' => $quiz->id]); 137 $this->assertEquals(0, $count); 138 } 139 140 public function test_quiz_get_user_attempts() { 141 global $DB; 142 $this->resetAfterTest(); 143 144 $dg = $this->getDataGenerator(); 145 $quizgen = $dg->get_plugin_generator('mod_quiz'); 146 $course = $dg->create_course(); 147 $u1 = $dg->create_user(); 148 $u2 = $dg->create_user(); 149 $u3 = $dg->create_user(); 150 $u4 = $dg->create_user(); 151 $role = $DB->get_record('role', ['shortname' => 'student']); 152 153 $dg->enrol_user($u1->id, $course->id, $role->id); 154 $dg->enrol_user($u2->id, $course->id, $role->id); 155 $dg->enrol_user($u3->id, $course->id, $role->id); 156 $dg->enrol_user($u4->id, $course->id, $role->id); 157 158 $quiz1 = $quizgen->create_instance(['course' => $course->id, 'sumgrades' => 2]); 159 $quiz2 = $quizgen->create_instance(['course' => $course->id, 'sumgrades' => 2]); 160 161 // Questions. 162 $questgen = $dg->get_plugin_generator('core_question'); 163 $quizcat = $questgen->create_question_category(); 164 $question = $questgen->create_question('numerical', null, ['category' => $quizcat->id]); 165 quiz_add_quiz_question($question->id, $quiz1); 166 quiz_add_quiz_question($question->id, $quiz2); 167 168 $quizobj1a = quiz_settings::create($quiz1->id, $u1->id); 169 $quizobj1b = quiz_settings::create($quiz1->id, $u2->id); 170 $quizobj1c = quiz_settings::create($quiz1->id, $u3->id); 171 $quizobj1d = quiz_settings::create($quiz1->id, $u4->id); 172 $quizobj2a = quiz_settings::create($quiz2->id, $u1->id); 173 174 // Set attempts. 175 $quba1a = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1a->get_context()); 176 $quba1a->set_preferred_behaviour($quizobj1a->get_quiz()->preferredbehaviour); 177 $quba1b = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1b->get_context()); 178 $quba1b->set_preferred_behaviour($quizobj1b->get_quiz()->preferredbehaviour); 179 $quba1c = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1c->get_context()); 180 $quba1c->set_preferred_behaviour($quizobj1c->get_quiz()->preferredbehaviour); 181 $quba1d = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1d->get_context()); 182 $quba1d->set_preferred_behaviour($quizobj1d->get_quiz()->preferredbehaviour); 183 $quba2a = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context()); 184 $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour); 185 186 $timenow = time(); 187 188 // User 1 passes quiz 1. 189 $attempt = quiz_create_attempt($quizobj1a, 1, false, $timenow, false, $u1->id); 190 quiz_start_new_attempt($quizobj1a, $quba1a, $attempt, 1, $timenow); 191 quiz_attempt_save_started($quizobj1a, $quba1a, $attempt); 192 $attemptobj = quiz_attempt::create($attempt->id); 193 $attemptobj->process_submitted_actions($timenow, false, [1 => ['answer' => '3.14']]); 194 $attemptobj->process_finish($timenow, false); 195 196 // User 2 goes overdue in quiz 1. 197 $attempt = quiz_create_attempt($quizobj1b, 1, false, $timenow, false, $u2->id); 198 quiz_start_new_attempt($quizobj1b, $quba1b, $attempt, 1, $timenow); 199 quiz_attempt_save_started($quizobj1b, $quba1b, $attempt); 200 $attemptobj = quiz_attempt::create($attempt->id); 201 $attemptobj->process_going_overdue($timenow, true); 202 203 // User 3 does not finish quiz 1. 204 $attempt = quiz_create_attempt($quizobj1c, 1, false, $timenow, false, $u3->id); 205 quiz_start_new_attempt($quizobj1c, $quba1c, $attempt, 1, $timenow); 206 quiz_attempt_save_started($quizobj1c, $quba1c, $attempt); 207 208 // User 4 abandons the quiz 1. 209 $attempt = quiz_create_attempt($quizobj1d, 1, false, $timenow, false, $u4->id); 210 quiz_start_new_attempt($quizobj1d, $quba1d, $attempt, 1, $timenow); 211 quiz_attempt_save_started($quizobj1d, $quba1d, $attempt); 212 $attemptobj = quiz_attempt::create($attempt->id); 213 $attemptobj->process_abandon($timenow, true); 214 215 // User 1 attempts the quiz three times (abandon, finish, in progress). 216 $quba2a = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context()); 217 $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour); 218 219 $attempt = quiz_create_attempt($quizobj2a, 1, false, $timenow, false, $u1->id); 220 quiz_start_new_attempt($quizobj2a, $quba2a, $attempt, 1, $timenow); 221 quiz_attempt_save_started($quizobj2a, $quba2a, $attempt); 222 $attemptobj = quiz_attempt::create($attempt->id); 223 $attemptobj->process_abandon($timenow, true); 224 225 $quba2a = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context()); 226 $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour); 227 228 $attempt = quiz_create_attempt($quizobj2a, 2, false, $timenow, false, $u1->id); 229 quiz_start_new_attempt($quizobj2a, $quba2a, $attempt, 2, $timenow); 230 quiz_attempt_save_started($quizobj2a, $quba2a, $attempt); 231 $attemptobj = quiz_attempt::create($attempt->id); 232 $attemptobj->process_finish($timenow, false); 233 234 $quba2a = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context()); 235 $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour); 236 237 $attempt = quiz_create_attempt($quizobj2a, 3, false, $timenow, false, $u1->id); 238 quiz_start_new_attempt($quizobj2a, $quba2a, $attempt, 3, $timenow); 239 quiz_attempt_save_started($quizobj2a, $quba2a, $attempt); 240 241 // Check for user 1. 242 $attempts = quiz_get_user_attempts($quiz1->id, $u1->id, 'all'); 243 $this->assertCount(1, $attempts); 244 $attempt = array_shift($attempts); 245 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state); 246 $this->assertEquals($u1->id, $attempt->userid); 247 $this->assertEquals($quiz1->id, $attempt->quiz); 248 249 $attempts = quiz_get_user_attempts($quiz1->id, $u1->id, 'finished'); 250 $this->assertCount(1, $attempts); 251 $attempt = array_shift($attempts); 252 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state); 253 $this->assertEquals($u1->id, $attempt->userid); 254 $this->assertEquals($quiz1->id, $attempt->quiz); 255 256 $attempts = quiz_get_user_attempts($quiz1->id, $u1->id, 'unfinished'); 257 $this->assertCount(0, $attempts); 258 259 // Check for user 2. 260 $attempts = quiz_get_user_attempts($quiz1->id, $u2->id, 'all'); 261 $this->assertCount(1, $attempts); 262 $attempt = array_shift($attempts); 263 $this->assertEquals(quiz_attempt::OVERDUE, $attempt->state); 264 $this->assertEquals($u2->id, $attempt->userid); 265 $this->assertEquals($quiz1->id, $attempt->quiz); 266 267 $attempts = quiz_get_user_attempts($quiz1->id, $u2->id, 'finished'); 268 $this->assertCount(0, $attempts); 269 270 $attempts = quiz_get_user_attempts($quiz1->id, $u2->id, 'unfinished'); 271 $this->assertCount(1, $attempts); 272 $attempt = array_shift($attempts); 273 $this->assertEquals(quiz_attempt::OVERDUE, $attempt->state); 274 $this->assertEquals($u2->id, $attempt->userid); 275 $this->assertEquals($quiz1->id, $attempt->quiz); 276 277 // Check for user 3. 278 $attempts = quiz_get_user_attempts($quiz1->id, $u3->id, 'all'); 279 $this->assertCount(1, $attempts); 280 $attempt = array_shift($attempts); 281 $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state); 282 $this->assertEquals($u3->id, $attempt->userid); 283 $this->assertEquals($quiz1->id, $attempt->quiz); 284 285 $attempts = quiz_get_user_attempts($quiz1->id, $u3->id, 'finished'); 286 $this->assertCount(0, $attempts); 287 288 $attempts = quiz_get_user_attempts($quiz1->id, $u3->id, 'unfinished'); 289 $this->assertCount(1, $attempts); 290 $attempt = array_shift($attempts); 291 $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state); 292 $this->assertEquals($u3->id, $attempt->userid); 293 $this->assertEquals($quiz1->id, $attempt->quiz); 294 295 // Check for user 4. 296 $attempts = quiz_get_user_attempts($quiz1->id, $u4->id, 'all'); 297 $this->assertCount(1, $attempts); 298 $attempt = array_shift($attempts); 299 $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state); 300 $this->assertEquals($u4->id, $attempt->userid); 301 $this->assertEquals($quiz1->id, $attempt->quiz); 302 303 $attempts = quiz_get_user_attempts($quiz1->id, $u4->id, 'finished'); 304 $this->assertCount(1, $attempts); 305 $attempt = array_shift($attempts); 306 $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state); 307 $this->assertEquals($u4->id, $attempt->userid); 308 $this->assertEquals($quiz1->id, $attempt->quiz); 309 310 $attempts = quiz_get_user_attempts($quiz1->id, $u4->id, 'unfinished'); 311 $this->assertCount(0, $attempts); 312 313 // Multiple attempts for user 1 in quiz 2. 314 $attempts = quiz_get_user_attempts($quiz2->id, $u1->id, 'all'); 315 $this->assertCount(3, $attempts); 316 $attempt = array_shift($attempts); 317 $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state); 318 $this->assertEquals($u1->id, $attempt->userid); 319 $this->assertEquals($quiz2->id, $attempt->quiz); 320 $attempt = array_shift($attempts); 321 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state); 322 $this->assertEquals($u1->id, $attempt->userid); 323 $this->assertEquals($quiz2->id, $attempt->quiz); 324 $attempt = array_shift($attempts); 325 $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state); 326 $this->assertEquals($u1->id, $attempt->userid); 327 $this->assertEquals($quiz2->id, $attempt->quiz); 328 329 $attempts = quiz_get_user_attempts($quiz2->id, $u1->id, 'finished'); 330 $this->assertCount(2, $attempts); 331 $attempt = array_shift($attempts); 332 $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state); 333 $attempt = array_shift($attempts); 334 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state); 335 336 $attempts = quiz_get_user_attempts($quiz2->id, $u1->id, 'unfinished'); 337 $this->assertCount(1, $attempts); 338 $attempt = array_shift($attempts); 339 340 // Multiple quiz attempts fetched at once. 341 $attempts = quiz_get_user_attempts([$quiz1->id, $quiz2->id], $u1->id, 'all'); 342 $this->assertCount(4, $attempts); 343 $attempt = array_shift($attempts); 344 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state); 345 $this->assertEquals($u1->id, $attempt->userid); 346 $this->assertEquals($quiz1->id, $attempt->quiz); 347 $attempt = array_shift($attempts); 348 $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state); 349 $this->assertEquals($u1->id, $attempt->userid); 350 $this->assertEquals($quiz2->id, $attempt->quiz); 351 $attempt = array_shift($attempts); 352 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state); 353 $this->assertEquals($u1->id, $attempt->userid); 354 $this->assertEquals($quiz2->id, $attempt->quiz); 355 $attempt = array_shift($attempts); 356 $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state); 357 $this->assertEquals($u1->id, $attempt->userid); 358 $this->assertEquals($quiz2->id, $attempt->quiz); 359 } 360 361 /** 362 * Test for quiz_get_group_override_priorities(). 363 */ 364 public function test_quiz_get_group_override_priorities() { 365 global $DB; 366 $this->resetAfterTest(); 367 368 $dg = $this->getDataGenerator(); 369 $quizgen = $dg->get_plugin_generator('mod_quiz'); 370 $course = $dg->create_course(); 371 372 $quiz = $quizgen->create_instance(['course' => $course->id, 'sumgrades' => 2]); 373 374 $this->assertNull(quiz_get_group_override_priorities($quiz->id)); 375 376 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 377 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 378 379 $now = 100; 380 $override1 = (object)[ 381 'quiz' => $quiz->id, 382 'groupid' => $group1->id, 383 'timeopen' => $now, 384 'timeclose' => $now + 20 385 ]; 386 $DB->insert_record('quiz_overrides', $override1); 387 388 $override2 = (object)[ 389 'quiz' => $quiz->id, 390 'groupid' => $group2->id, 391 'timeopen' => $now - 10, 392 'timeclose' => $now + 10 393 ]; 394 $DB->insert_record('quiz_overrides', $override2); 395 396 $priorities = quiz_get_group_override_priorities($quiz->id); 397 $this->assertNotEmpty($priorities); 398 399 $openpriorities = $priorities['open']; 400 // Override 2's time open has higher priority since it is sooner than override 1's. 401 $this->assertEquals(2, $openpriorities[$override1->timeopen]); 402 $this->assertEquals(1, $openpriorities[$override2->timeopen]); 403 404 $closepriorities = $priorities['close']; 405 // Override 1's time close has higher priority since it is later than override 2's. 406 $this->assertEquals(1, $closepriorities[$override1->timeclose]); 407 $this->assertEquals(2, $closepriorities[$override2->timeclose]); 408 } 409 410 public function test_quiz_core_calendar_provide_event_action_open() { 411 $this->resetAfterTest(); 412 413 $this->setAdminUser(); 414 415 // Create a course. 416 $course = $this->getDataGenerator()->create_course(); 417 // Create a student and enrol into the course. 418 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 419 // Create a quiz. 420 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 421 'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS]); 422 423 // Create a calendar event. 424 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_OPEN); 425 // Now, log in as student. 426 $this->setUser($student); 427 // Create an action factory. 428 $factory = new \core_calendar\action_factory(); 429 430 // Decorate action event. 431 $actionevent = mod_quiz_core_calendar_provide_event_action($event, $factory); 432 433 // Confirm the event was decorated. 434 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); 435 $this->assertEquals(get_string('attemptquiznow', 'quiz'), $actionevent->get_name()); 436 $this->assertInstanceOf('moodle_url', $actionevent->get_url()); 437 $this->assertEquals(1, $actionevent->get_item_count()); 438 $this->assertTrue($actionevent->is_actionable()); 439 } 440 441 public function test_quiz_core_calendar_provide_event_action_open_for_user() { 442 $this->resetAfterTest(); 443 444 $this->setAdminUser(); 445 446 // Create a course. 447 $course = $this->getDataGenerator()->create_course(); 448 // Create a student and enrol into the course. 449 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 450 // Create a quiz. 451 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 452 'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS]); 453 454 // Create a calendar event. 455 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_OPEN); 456 457 // Create an action factory. 458 $factory = new \core_calendar\action_factory(); 459 460 // Decorate action event for the student. 461 $actionevent = mod_quiz_core_calendar_provide_event_action($event, $factory, $student->id); 462 463 // Confirm the event was decorated. 464 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); 465 $this->assertEquals(get_string('attemptquiznow', 'quiz'), $actionevent->get_name()); 466 $this->assertInstanceOf('moodle_url', $actionevent->get_url()); 467 $this->assertEquals(1, $actionevent->get_item_count()); 468 $this->assertTrue($actionevent->is_actionable()); 469 } 470 471 public function test_quiz_core_calendar_provide_event_action_closed() { 472 $this->resetAfterTest(); 473 474 $this->setAdminUser(); 475 476 // Create a course. 477 $course = $this->getDataGenerator()->create_course(); 478 479 // Create a quiz. 480 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 481 'timeclose' => time() - DAYSECS]); 482 483 // Create a calendar event. 484 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_CLOSE); 485 486 // Create an action factory. 487 $factory = new \core_calendar\action_factory(); 488 489 // Confirm the result was null. 490 $this->assertNull(mod_quiz_core_calendar_provide_event_action($event, $factory)); 491 } 492 493 public function test_quiz_core_calendar_provide_event_action_closed_for_user() { 494 $this->resetAfterTest(); 495 496 $this->setAdminUser(); 497 498 // Create a course. 499 $course = $this->getDataGenerator()->create_course(); 500 501 // Create a student. 502 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 503 504 // Create a quiz. 505 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 506 'timeclose' => time() - DAYSECS]); 507 508 // Create a calendar event. 509 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_CLOSE); 510 511 // Create an action factory. 512 $factory = new \core_calendar\action_factory(); 513 514 // Confirm the result was null. 515 $this->assertNull(mod_quiz_core_calendar_provide_event_action($event, $factory, $student->id)); 516 } 517 518 public function test_quiz_core_calendar_provide_event_action_open_in_future() { 519 $this->resetAfterTest(); 520 521 $this->setAdminUser(); 522 523 // Create a course. 524 $course = $this->getDataGenerator()->create_course(); 525 // Create a student and enrol into the course. 526 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 527 // Create a quiz. 528 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 529 'timeopen' => time() + DAYSECS]); 530 531 // Create a calendar event. 532 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_CLOSE); 533 // Now, log in as student. 534 $this->setUser($student); 535 // Create an action factory. 536 $factory = new \core_calendar\action_factory(); 537 538 // Decorate action event. 539 $actionevent = mod_quiz_core_calendar_provide_event_action($event, $factory); 540 541 // Confirm the event was decorated. 542 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); 543 $this->assertEquals(get_string('attemptquiznow', 'quiz'), $actionevent->get_name()); 544 $this->assertInstanceOf('moodle_url', $actionevent->get_url()); 545 $this->assertEquals(1, $actionevent->get_item_count()); 546 $this->assertFalse($actionevent->is_actionable()); 547 } 548 549 public function test_quiz_core_calendar_provide_event_action_open_in_future_for_user() { 550 $this->resetAfterTest(); 551 552 $this->setAdminUser(); 553 554 // Create a course. 555 $course = $this->getDataGenerator()->create_course(); 556 // Create a student and enrol into the course. 557 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 558 // Create a quiz. 559 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 560 'timeopen' => time() + DAYSECS]); 561 562 // Create a calendar event. 563 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_CLOSE); 564 565 // Create an action factory. 566 $factory = new \core_calendar\action_factory(); 567 568 // Decorate action event for the student. 569 $actionevent = mod_quiz_core_calendar_provide_event_action($event, $factory, $student->id); 570 571 // Confirm the event was decorated. 572 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); 573 $this->assertEquals(get_string('attemptquiznow', 'quiz'), $actionevent->get_name()); 574 $this->assertInstanceOf('moodle_url', $actionevent->get_url()); 575 $this->assertEquals(1, $actionevent->get_item_count()); 576 $this->assertFalse($actionevent->is_actionable()); 577 } 578 579 public function test_quiz_core_calendar_provide_event_action_no_capability() { 580 global $DB; 581 582 $this->resetAfterTest(); 583 $this->setAdminUser(); 584 585 // Create a course. 586 $course = $this->getDataGenerator()->create_course(); 587 588 // Create a student. 589 $student = $this->getDataGenerator()->create_user(); 590 $studentrole = $DB->get_record('role', ['shortname' => 'student']); 591 592 // Enrol student. 593 $this->assertTrue($this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id)); 594 595 // Create a quiz. 596 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]); 597 598 // Remove the permission to attempt or review the quiz for the student role. 599 $coursecontext = \context_course::instance($course->id); 600 assign_capability('mod/quiz:reviewmyattempts', CAP_PROHIBIT, $studentrole->id, $coursecontext); 601 assign_capability('mod/quiz:attempt', CAP_PROHIBIT, $studentrole->id, $coursecontext); 602 603 // Create a calendar event. 604 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_OPEN); 605 606 // Create an action factory. 607 $factory = new \core_calendar\action_factory(); 608 609 // Set current user to the student. 610 $this->setUser($student); 611 612 // Confirm null is returned. 613 $this->assertNull(mod_quiz_core_calendar_provide_event_action($event, $factory)); 614 } 615 616 public function test_quiz_core_calendar_provide_event_action_no_capability_for_user() { 617 global $DB; 618 619 $this->resetAfterTest(); 620 $this->setAdminUser(); 621 622 // Create a course. 623 $course = $this->getDataGenerator()->create_course(); 624 625 // Create a student. 626 $student = $this->getDataGenerator()->create_user(); 627 $studentrole = $DB->get_record('role', ['shortname' => 'student']); 628 629 // Enrol student. 630 $this->assertTrue($this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id)); 631 632 // Create a quiz. 633 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]); 634 635 // Remove the permission to attempt or review the quiz for the student role. 636 $coursecontext = \context_course::instance($course->id); 637 assign_capability('mod/quiz:reviewmyattempts', CAP_PROHIBIT, $studentrole->id, $coursecontext); 638 assign_capability('mod/quiz:attempt', CAP_PROHIBIT, $studentrole->id, $coursecontext); 639 640 // Create a calendar event. 641 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_OPEN); 642 643 // Create an action factory. 644 $factory = new \core_calendar\action_factory(); 645 646 // Confirm null is returned. 647 $this->assertNull(mod_quiz_core_calendar_provide_event_action($event, $factory, $student->id)); 648 } 649 650 public function test_quiz_core_calendar_provide_event_action_already_finished() { 651 global $DB; 652 653 $this->resetAfterTest(); 654 655 $this->setAdminUser(); 656 657 // Create a course. 658 $course = $this->getDataGenerator()->create_course(); 659 660 // Create a student. 661 $student = $this->getDataGenerator()->create_user(); 662 $studentrole = $DB->get_record('role', ['shortname' => 'student']); 663 664 // Enrol student. 665 $this->assertTrue($this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id)); 666 667 // Create a quiz. 668 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 669 'sumgrades' => 1]); 670 671 // Add a question to the quiz. 672 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 673 $cat = $questiongenerator->create_question_category(); 674 $question = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]); 675 quiz_add_quiz_question($question->id, $quiz); 676 677 // Get the quiz object. 678 $quizobj = quiz_settings::create($quiz->id, $student->id); 679 680 // Create an attempt for the student in the quiz. 681 $timenow = time(); 682 $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $student->id); 683 $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context()); 684 $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour); 685 quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow); 686 quiz_attempt_save_started($quizobj, $quba, $attempt); 687 688 // Finish the attempt. 689 $attemptobj = quiz_attempt::create($attempt->id); 690 $attemptobj->process_finish($timenow, false); 691 692 // Create a calendar event. 693 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_OPEN); 694 695 // Create an action factory. 696 $factory = new \core_calendar\action_factory(); 697 698 // Set current user to the student. 699 $this->setUser($student); 700 701 // Confirm null is returned. 702 $this->assertNull(mod_quiz_core_calendar_provide_event_action($event, $factory)); 703 } 704 705 public function test_quiz_core_calendar_provide_event_action_already_finished_for_user() { 706 global $DB; 707 708 $this->resetAfterTest(); 709 710 $this->setAdminUser(); 711 712 // Create a course. 713 $course = $this->getDataGenerator()->create_course(); 714 715 // Create a student. 716 $student = $this->getDataGenerator()->create_user(); 717 $studentrole = $DB->get_record('role', ['shortname' => 'student']); 718 719 // Enrol student. 720 $this->assertTrue($this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id)); 721 722 // Create a quiz. 723 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 724 'sumgrades' => 1]); 725 726 // Add a question to the quiz. 727 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 728 $cat = $questiongenerator->create_question_category(); 729 $question = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]); 730 quiz_add_quiz_question($question->id, $quiz); 731 732 // Get the quiz object. 733 $quizobj = quiz_settings::create($quiz->id, $student->id); 734 735 // Create an attempt for the student in the quiz. 736 $timenow = time(); 737 $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $student->id); 738 $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context()); 739 $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour); 740 quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow); 741 quiz_attempt_save_started($quizobj, $quba, $attempt); 742 743 // Finish the attempt. 744 $attemptobj = quiz_attempt::create($attempt->id); 745 $attemptobj->process_finish($timenow, false); 746 747 // Create a calendar event. 748 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_OPEN); 749 750 // Create an action factory. 751 $factory = new \core_calendar\action_factory(); 752 753 // Confirm null is returned. 754 $this->assertNull(mod_quiz_core_calendar_provide_event_action($event, $factory, $student->id)); 755 } 756 757 public function test_quiz_core_calendar_provide_event_action_already_completed() { 758 $this->resetAfterTest(); 759 set_config('enablecompletion', 1); 760 $this->setAdminUser(); 761 762 // Create the activity. 763 $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); 764 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id], 765 ['completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS]); 766 767 // Get some additional data. 768 $cm = get_coursemodule_from_instance('quiz', $quiz->id); 769 770 // Create a calendar event. 771 $event = $this->create_action_event($course->id, $quiz->id, 772 \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED); 773 774 // Mark the activity as completed. 775 $completion = new \completion_info($course); 776 $completion->set_module_viewed($cm); 777 778 // Create an action factory. 779 $factory = new \core_calendar\action_factory(); 780 781 // Decorate action event. 782 $actionevent = mod_quiz_core_calendar_provide_event_action($event, $factory); 783 784 // Ensure result was null. 785 $this->assertNull($actionevent); 786 } 787 788 public function test_quiz_core_calendar_provide_event_action_already_completed_for_user() { 789 $this->resetAfterTest(); 790 set_config('enablecompletion', 1); 791 $this->setAdminUser(); 792 793 // Create the activity. 794 $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); 795 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id], 796 ['completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS]); 797 798 // Enrol a student in the course. 799 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 800 801 // Get some additional data. 802 $cm = get_coursemodule_from_instance('quiz', $quiz->id); 803 804 // Create a calendar event. 805 $event = $this->create_action_event($course->id, $quiz->id, 806 \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED); 807 808 // Mark the activity as completed for the student. 809 $completion = new \completion_info($course); 810 $completion->set_module_viewed($cm, $student->id); 811 812 // Create an action factory. 813 $factory = new \core_calendar\action_factory(); 814 815 // Decorate action event for the student. 816 $actionevent = mod_quiz_core_calendar_provide_event_action($event, $factory, $student->id); 817 818 // Ensure result was null. 819 $this->assertNull($actionevent); 820 } 821 822 /** 823 * Creates an action event. 824 * 825 * @param int $courseid 826 * @param int $instanceid The quiz id. 827 * @param string $eventtype The event type. eg. QUIZ_EVENT_TYPE_OPEN. 828 * @return bool|calendar_event 829 */ 830 private function create_action_event($courseid, $instanceid, $eventtype) { 831 $event = new \stdClass(); 832 $event->name = 'Calendar event'; 833 $event->modulename = 'quiz'; 834 $event->courseid = $courseid; 835 $event->instance = $instanceid; 836 $event->type = CALENDAR_EVENT_TYPE_ACTION; 837 $event->eventtype = $eventtype; 838 $event->timestart = time(); 839 840 return \calendar_event::create($event); 841 } 842 843 /** 844 * Test the callback responsible for returning the completion rule descriptions. 845 * This function should work given either an instance of the module (cm_info), such as when checking the active rules, 846 * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type. 847 */ 848 public function test_mod_quiz_completion_get_active_rule_descriptions() { 849 $this->resetAfterTest(); 850 $this->setAdminUser(); 851 852 // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't. 853 $course = $this->getDataGenerator()->create_course(['enablecompletion' => 2]); 854 $quiz1 = $this->getDataGenerator()->create_module('quiz', [ 855 'course' => $course->id, 856 'completion' => 2, 857 'completionusegrade' => 1, 858 'completionpassgrade' => 1, 859 'completionattemptsexhausted' => 1, 860 ]); 861 $quiz2 = $this->getDataGenerator()->create_module('quiz', [ 862 'course' => $course->id, 863 'completion' => 2, 864 'completionusegrade' => 0 865 ]); 866 $cm1 = \cm_info::create(get_coursemodule_from_instance('quiz', $quiz1->id)); 867 $cm2 = \cm_info::create(get_coursemodule_from_instance('quiz', $quiz2->id)); 868 869 // Data for the stdClass input type. 870 // This type of input would occur when checking the default completion rules for an activity type, where we don't have 871 // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info. 872 $moddefaults = new \stdClass(); 873 $moddefaults->customdata = ['customcompletionrules' => [ 874 'completionattemptsexhausted' => 1, 875 ]]; 876 $moddefaults->completion = 2; 877 878 $activeruledescriptions = [ 879 get_string('completionpassorattemptsexhausteddesc', 'quiz'), 880 ]; 881 $this->assertEquals(mod_quiz_get_completion_active_rule_descriptions($cm1), $activeruledescriptions); 882 $this->assertEquals(mod_quiz_get_completion_active_rule_descriptions($cm2), []); 883 $this->assertEquals(mod_quiz_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions); 884 $this->assertEquals(mod_quiz_get_completion_active_rule_descriptions(new \stdClass()), []); 885 } 886 887 /** 888 * A user who does not have capabilities to add events to the calendar should be able to create a quiz. 889 */ 890 public function test_creation_with_no_calendar_capabilities() { 891 $this->resetAfterTest(); 892 $course = self::getDataGenerator()->create_course(); 893 $context = \context_course::instance($course->id); 894 $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher'); 895 $roleid = self::getDataGenerator()->create_role(); 896 self::getDataGenerator()->role_assign($roleid, $user->id, $context->id); 897 assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true); 898 $generator = self::getDataGenerator()->get_plugin_generator('mod_quiz'); 899 // Create an instance as a user without the calendar capabilities. 900 $this->setUser($user); 901 $time = time(); 902 $params = [ 903 'course' => $course->id, 904 'timeopen' => $time + 200, 905 'timeclose' => $time + 2000, 906 ]; 907 $generator->create_instance($params); 908 } 909 910 /** 911 * Data provider for summarise_response() test cases. 912 * 913 * @return array List of data sets (test cases) 914 */ 915 public function mod_quiz_inplace_editable_provider(): array { 916 return [ 917 'set to A1' => [1, 'A1'], 918 'set with HTML characters' => [2, 'A & & <-:'], 919 'set to integer' => [3, '3'], 920 'set to blank' => [4, ''], 921 'set with Unicode characters' => [1, 'L\'Aina Lluís^'], 922 'set with Unicode at the truncation point' => [1, '123456789012345碁'], 923 'set with HTML Char at the truncation point' => [1, '123456789012345>'], 924 ]; 925 } 926 927 /** 928 * Test customised and automated question numbering for a given slot number and customised value. 929 * 930 * @dataProvider mod_quiz_inplace_editable_provider 931 * @param int $slotnumber 932 * @param string $newvalue 933 * @covers ::mod_quiz_inplace_editable 934 */ 935 public function test_mod_quiz_inplace_editable(int $slotnumber, string $newvalue): void { 936 global $CFG; 937 require_once($CFG->dirroot . '/lib/external/externallib.php'); 938 $this->resetAfterTest(); 939 940 $this->setAdminUser(); 941 $course = self::getDataGenerator()->create_course(); 942 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 'sumgrades' => 1]); 943 $cm = get_coursemodule_from_id('quiz', $quiz->cmid); 944 945 // Add few questions to the quiz. 946 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 947 $cat = $questiongenerator->create_question_category(); 948 949 $question = $questiongenerator->create_question('truefalse', null, ['category' => $cat->id]); 950 quiz_add_quiz_question($question->id, $quiz); 951 952 $question = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); 953 quiz_add_quiz_question($question->id, $quiz); 954 955 $question = $questiongenerator->create_question('multichoice', null, ['category' => $cat->id]); 956 quiz_add_quiz_question($question->id, $quiz); 957 958 $question = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]); 959 quiz_add_quiz_question($question->id, $quiz); 960 961 // Create the quiz object. 962 $quizobj = new quiz_settings($quiz, $cm, $course); 963 $structure = $quizobj->get_structure(); 964 965 $slots = $structure->get_slots(); 966 $this->assertEquals(4, count($slots)); 967 968 $slotid = $structure->get_slot_id_for_slot($slotnumber); 969 $inplaceeditable = mod_quiz_inplace_editable('slotdisplaynumber', $slotid, $newvalue); 970 $result = \core_external::update_inplace_editable('mod_quiz', 'slotdisplaynumber', $slotid, $newvalue); 971 $result = external_api::clean_returnvalue(\core_external::update_inplace_editable_returns(), $result); 972 973 $this->assertEquals(count((array) $inplaceeditable), count($result)); 974 $this->assertEquals($slotid, $result['itemid']); 975 if ($newvalue === '' || is_null($newvalue)) { 976 // Check against default. 977 $this->assertEquals($slotnumber, $result['displayvalue']); 978 $this->assertEquals($slotnumber, $result['value']); 979 } else { 980 // Check against the custom number. 981 $this->assertEquals(s($newvalue), $result['displayvalue']); 982 $this->assertEquals($newvalue, $result['value']); 983 } 984 } 985 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body