Differences Between: [Versions 310 and 402] [Versions 311 and 402] [Versions 39 and 402] [Versions 400 and 402] [Versions 401 and 402] [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 35 /** 36 * @copyright 2008 The Open University 37 * @license http://www.gnu.org/copyleft/gpl.html GNU Public License 38 */ 39 class lib_test extends \advanced_testcase { 40 public function test_quiz_has_grades() { 41 $quiz = new \stdClass(); 42 $quiz->grade = '100.0000'; 43 $quiz->sumgrades = '100.0000'; 44 $this->assertTrue(quiz_has_grades($quiz)); 45 $quiz->sumgrades = '0.0000'; 46 $this->assertFalse(quiz_has_grades($quiz)); 47 $quiz->grade = '0.0000'; 48 $this->assertFalse(quiz_has_grades($quiz)); 49 $quiz->sumgrades = '100.0000'; 50 $this->assertFalse(quiz_has_grades($quiz)); 51 } 52 53 public function test_quiz_format_grade() { 54 $quiz = new \stdClass(); 55 $quiz->decimalpoints = 2; 56 $this->assertEquals(quiz_format_grade($quiz, 0.12345678), format_float(0.12, 2)); 57 $this->assertEquals(quiz_format_grade($quiz, 0), format_float(0, 2)); 58 $this->assertEquals(quiz_format_grade($quiz, 1.000000000000), format_float(1, 2)); 59 $quiz->decimalpoints = 0; 60 $this->assertEquals(quiz_format_grade($quiz, 0.12345678), '0'); 61 } 62 63 public function test_quiz_get_grade_format() { 64 $quiz = new \stdClass(); 65 $quiz->decimalpoints = 2; 66 $this->assertEquals(quiz_get_grade_format($quiz), 2); 67 $this->assertEquals($quiz->questiondecimalpoints, -1); 68 $quiz->questiondecimalpoints = 2; 69 $this->assertEquals(quiz_get_grade_format($quiz), 2); 70 $quiz->decimalpoints = 3; 71 $quiz->questiondecimalpoints = -1; 72 $this->assertEquals(quiz_get_grade_format($quiz), 3); 73 $quiz->questiondecimalpoints = 4; 74 $this->assertEquals(quiz_get_grade_format($quiz), 4); 75 } 76 77 public function test_quiz_format_question_grade() { 78 $quiz = new \stdClass(); 79 $quiz->decimalpoints = 2; 80 $quiz->questiondecimalpoints = 2; 81 $this->assertEquals(quiz_format_question_grade($quiz, 0.12345678), format_float(0.12, 2)); 82 $this->assertEquals(quiz_format_question_grade($quiz, 0), format_float(0, 2)); 83 $this->assertEquals(quiz_format_question_grade($quiz, 1.000000000000), format_float(1, 2)); 84 $quiz->decimalpoints = 3; 85 $quiz->questiondecimalpoints = -1; 86 $this->assertEquals(quiz_format_question_grade($quiz, 0.12345678), format_float(0.123, 3)); 87 $this->assertEquals(quiz_format_question_grade($quiz, 0), format_float(0, 3)); 88 $this->assertEquals(quiz_format_question_grade($quiz, 1.000000000000), format_float(1, 3)); 89 $quiz->questiondecimalpoints = 4; 90 $this->assertEquals(quiz_format_question_grade($quiz, 0.12345678), format_float(0.1235, 4)); 91 $this->assertEquals(quiz_format_question_grade($quiz, 0), format_float(0, 4)); 92 $this->assertEquals(quiz_format_question_grade($quiz, 1.000000000000), format_float(1, 4)); 93 } 94 95 /** 96 * Test deleting a quiz instance. 97 */ 98 public function test_quiz_delete_instance() { 99 global $SITE, $DB; 100 $this->resetAfterTest(true); 101 $this->setAdminUser(); 102 103 // Setup a quiz with 1 standard and 1 random question. 104 $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz'); 105 $quiz = $quizgenerator->create_instance(['course' => $SITE->id, 'questionsperpage' => 3, 'grade' => 100.0]); 106 107 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 108 $cat = $questiongenerator->create_question_category(); 109 $standardq = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); 110 111 quiz_add_quiz_question($standardq->id, $quiz); 112 quiz_add_random_questions($quiz, 0, $cat->id, 1, false); 113 114 // Get the random question. 115 $randomq = $DB->get_record('question', ['qtype' => 'random']); 116 117 quiz_delete_instance($quiz->id); 118 119 // Check that the random question was deleted. 120 if ($randomq) { 121 $count = $DB->count_records('question', ['id' => $randomq->id]); 122 $this->assertEquals(0, $count); 123 } 124 // Check that the standard question was not deleted. 125 $count = $DB->count_records('question', ['id' => $standardq->id]); 126 $this->assertEquals(1, $count); 127 128 // Check that all the slots were removed. 129 $count = $DB->count_records('quiz_slots', ['quizid' => $quiz->id]); 130 $this->assertEquals(0, $count); 131 132 // Check that the quiz was removed. 133 $count = $DB->count_records('quiz', ['id' => $quiz->id]); 134 $this->assertEquals(0, $count); 135 } 136 137 /** 138 * Setup function for all test_quiz_get_completion_state_* tests. 139 * 140 * @param array $completionoptions ['nbstudents'] => int, ['qtype'] => string, ['quizoptions'] => array 141 * @throws dml_exception 142 * @return array [$course, $students, $quiz, $cm] 143 */ 144 private function setup_quiz_for_testing_completion(array $completionoptions) { 145 global $CFG, $DB; 146 147 $this->resetAfterTest(true); 148 149 // Enable completion before creating modules, otherwise the completion data is not written in DB. 150 $CFG->enablecompletion = true; 151 152 // Create a course and students. 153 $studentrole = $DB->get_record('role', ['shortname' => 'student']); 154 $course = $this->getDataGenerator()->create_course(['enablecompletion' => true]); 155 $students = []; 156 for ($i = 0; $i < $completionoptions['nbstudents']; $i++) { 157 $students[$i] = $this->getDataGenerator()->create_user(); 158 $this->assertTrue($this->getDataGenerator()->enrol_user($students[$i]->id, $course->id, $studentrole->id)); 159 } 160 161 // Make a quiz. 162 $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz'); 163 $data = array_merge([ 164 'course' => $course->id, 165 'grade' => 100.0, 166 'questionsperpage' => 0, 167 'sumgrades' => 1, 168 'completion' => COMPLETION_TRACKING_AUTOMATIC 169 ], $completionoptions['quizoptions']); 170 $quiz = $quizgenerator->create_instance($data); 171 $cm = get_coursemodule_from_id('quiz', $quiz->cmid); 172 173 // Create a question. 174 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 175 176 $cat = $questiongenerator->create_question_category(); 177 $question = $questiongenerator->create_question($completionoptions['qtype'], null, ['category' => $cat->id]); 178 quiz_add_quiz_question($question->id, $quiz); 179 180 // Set grade to pass. 181 $item = \grade_item::fetch(['courseid' => $course->id, 'itemtype' => 'mod', 'itemmodule' => 'quiz', 182 'iteminstance' => $quiz->id, 'outcomeid' => null]); 183 $item->gradepass = 80; 184 $item->update(); 185 186 return [ 187 $course, 188 $students, 189 $quiz, 190 $cm 191 ]; 192 } 193 194 /** 195 * Helper function for all test_quiz_get_completion_state_* tests. 196 * Starts an attempt, processes responses and finishes the attempt. 197 * 198 * @param $attemptoptions ['quiz'] => object, ['student'] => object, ['tosubmit'] => array, ['attemptnumber'] => int 199 */ 200 private function do_attempt_quiz($attemptoptions) { 201 $quizobj = quiz_settings::create($attemptoptions['quiz']->id); 202 203 // Start the passing attempt. 204 $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context()); 205 $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour); 206 207 $timenow = time(); 208 $attempt = quiz_create_attempt($quizobj, $attemptoptions['attemptnumber'], false, $timenow, false, 209 $attemptoptions['student']->id); 210 quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptoptions['attemptnumber'], $timenow); 211 quiz_attempt_save_started($quizobj, $quba, $attempt); 212 213 // Process responses from the student. 214 $attemptobj = quiz_attempt::create($attempt->id); 215 $attemptobj->process_submitted_actions($timenow, false, $attemptoptions['tosubmit']); 216 217 // Finish the attempt. 218 $attemptobj = quiz_attempt::create($attempt->id); 219 $this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question()); 220 $attemptobj->process_finish($timenow, false); 221 } 222 223 /** 224 * Test checking the completion state of a quiz. 225 * The quiz requires a passing grade to be completed. 226 */ 227 public function test_quiz_get_completion_state_completionpass() { 228 229 list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([ 230 'nbstudents' => 2, 231 'qtype' => 'numerical', 232 'quizoptions' => [ 233 'completionusegrade' => 1, 234 'completionpassgrade' => 1 235 ] 236 ]); 237 238 list($passstudent, $failstudent) = $students; 239 240 // Do a passing attempt. 241 $this->do_attempt_quiz([ 242 'quiz' => $quiz, 243 'student' => $passstudent, 244 'attemptnumber' => 1, 245 'tosubmit' => [1 => ['answer' => '3.14']] 246 ]); 247 248 // Check the results. 249 $this->assertTrue(quiz_get_completion_state($course, $cm, $passstudent->id, 'return')); 250 251 // Do a failing attempt. 252 $this->do_attempt_quiz([ 253 'quiz' => $quiz, 254 'student' => $failstudent, 255 'attemptnumber' => 1, 256 'tosubmit' => [1 => ['answer' => '0']] 257 ]); 258 259 // Check the results. 260 $this->assertFalse(quiz_get_completion_state($course, $cm, $failstudent->id, 'return')); 261 262 $this->assertDebuggingCalledCount(3, [ 263 'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.', 264 'quiz_completion_check_min_attempts has been deprecated.', 265 'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.', 266 ]); 267 } 268 269 /** 270 * Test checking the completion state of a quiz. 271 * To be completed, this quiz requires either a passing grade or for all attempts to be used up. 272 */ 273 public function test_quiz_get_completion_state_completionexhausted() { 274 275 list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([ 276 'nbstudents' => 2, 277 'qtype' => 'numerical', 278 'quizoptions' => [ 279 'attempts' => 2, 280 'completionusegrade' => 1, 281 'completionpassgrade' => 1, 282 'completionattemptsexhausted' => 1 283 ] 284 ]); 285 286 list($passstudent, $exhauststudent) = $students; 287 288 // Start a passing attempt. 289 $this->do_attempt_quiz([ 290 'quiz' => $quiz, 291 'student' => $passstudent, 292 'attemptnumber' => 1, 293 'tosubmit' => [1 => ['answer' => '3.14']] 294 ]); 295 296 // Check the results. Quiz is completed by $passstudent because of passing grade. 297 $this->assertTrue(quiz_get_completion_state($course, $cm, $passstudent->id, 'return')); 298 299 // Do a failing attempt. 300 $this->do_attempt_quiz([ 301 'quiz' => $quiz, 302 'student' => $exhauststudent, 303 'attemptnumber' => 1, 304 'tosubmit' => [1 => ['answer' => '0']] 305 ]); 306 307 // Check the results. Quiz is not completed by $exhauststudent yet because of failing grade and of remaining attempts. 308 $this->assertFalse(quiz_get_completion_state($course, $cm, $exhauststudent->id, 'return')); 309 310 // Do a second failing attempt. 311 $this->do_attempt_quiz([ 312 'quiz' => $quiz, 313 'student' => $exhauststudent, 314 'attemptnumber' => 2, 315 'tosubmit' => [1 => ['answer' => '0']] 316 ]); 317 318 // Check the results. Quiz is completed by $exhauststudent because there are no remaining attempts. 319 $this->assertTrue(quiz_get_completion_state($course, $cm, $exhauststudent->id, 'return')); 320 321 $this->assertDebuggingCalledCount(5, [ 322 'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.', 323 'quiz_completion_check_min_attempts has been deprecated.', 324 'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.', 325 'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.', 326 'quiz_completion_check_min_attempts has been deprecated.', 327 ]); 328 } 329 330 /** 331 * Test checking the completion state of a quiz. 332 * To be completed, this quiz requires a minimum number of attempts. 333 */ 334 public function test_quiz_get_completion_state_completionminattempts() { 335 336 list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([ 337 'nbstudents' => 1, 338 'qtype' => 'essay', 339 'quizoptions' => [ 340 'completionminattemptsenabled' => 1, 341 'completionminattempts' => 2 342 ] 343 ]); 344 345 list($student) = $students; 346 347 // Do a first attempt. 348 $this->do_attempt_quiz([ 349 'quiz' => $quiz, 350 'student' => $student, 351 'attemptnumber' => 1, 352 'tosubmit' => [1 => ['answer' => 'Lorem ipsum.', 'answerformat' => '1']] 353 ]); 354 355 // Check the results. Quiz is not completed yet because only one attempt was done. 356 $this->assertFalse(quiz_get_completion_state($course, $cm, $student->id, 'return')); 357 358 // Do a second attempt. 359 $this->do_attempt_quiz([ 360 'quiz' => $quiz, 361 'student' => $student, 362 'attemptnumber' => 2, 363 'tosubmit' => [1 => ['answer' => 'Lorem ipsum.', 'answerformat' => '1']] 364 ]); 365 366 // Check the results. Quiz is completed by $student because two attempts were done. 367 $this->assertTrue(quiz_get_completion_state($course, $cm, $student->id, 'return')); 368 369 $this->assertDebuggingCalledCount(4, [ 370 'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.', 371 'quiz_completion_check_min_attempts has been deprecated.', 372 'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.', 373 'quiz_completion_check_min_attempts has been deprecated.', 374 ]); 375 } 376 377 /** 378 * Test checking the completion state of a quiz. 379 * To be completed, this quiz requires a minimum number of attempts AND a passing grade. 380 * This is somewhat of an edge case as it is hard to imagine a scenario in which these precise settings are useful. 381 * Nevertheless, this test makes sure these settings interact as intended. 382 */ 383 public function test_quiz_get_completion_state_completionminattempts_pass() { 384 385 list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([ 386 'nbstudents' => 1, 387 'qtype' => 'numerical', 388 'quizoptions' => [ 389 'attempts' => 2, 390 'completionusegrade' => 1, 391 'completionpassgrade' => 1, 392 'completionminattemptsenabled' => 1, 393 'completionminattempts' => 2 394 ] 395 ]); 396 397 list($student) = $students; 398 399 // Start a first attempt. 400 $this->do_attempt_quiz([ 401 'quiz' => $quiz, 402 'student' => $student, 403 'attemptnumber' => 1, 404 'tosubmit' => [1 => ['answer' => '3.14']] 405 ]); 406 407 // Check the results. Even though one requirement is met (passing grade) quiz is not completed yet because only 408 // one attempt was done. 409 $this->assertFalse(quiz_get_completion_state($course, $cm, $student->id, 'return')); 410 411 // Start a second attempt. 412 $this->do_attempt_quiz([ 413 'quiz' => $quiz, 414 'student' => $student, 415 'attemptnumber' => 2, 416 'tosubmit' => [1 => ['answer' => '42']] 417 ]); 418 419 // Check the results. Quiz is completed by $student because two attempts were done AND a passing grade was obtained. 420 $this->assertTrue(quiz_get_completion_state($course, $cm, $student->id, 'return')); 421 422 $this->assertDebuggingCalledCount(4, [ 423 'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.', 424 'quiz_completion_check_min_attempts has been deprecated.', 425 'quiz_completion_check_passing_grade_or_all_attempts has been deprecated.', 426 'quiz_completion_check_min_attempts has been deprecated.', 427 ]); 428 } 429 430 public function test_quiz_get_user_attempts() { 431 global $DB; 432 $this->resetAfterTest(); 433 434 $dg = $this->getDataGenerator(); 435 $quizgen = $dg->get_plugin_generator('mod_quiz'); 436 $course = $dg->create_course(); 437 $u1 = $dg->create_user(); 438 $u2 = $dg->create_user(); 439 $u3 = $dg->create_user(); 440 $u4 = $dg->create_user(); 441 $role = $DB->get_record('role', ['shortname' => 'student']); 442 443 $dg->enrol_user($u1->id, $course->id, $role->id); 444 $dg->enrol_user($u2->id, $course->id, $role->id); 445 $dg->enrol_user($u3->id, $course->id, $role->id); 446 $dg->enrol_user($u4->id, $course->id, $role->id); 447 448 $quiz1 = $quizgen->create_instance(['course' => $course->id, 'sumgrades' => 2]); 449 $quiz2 = $quizgen->create_instance(['course' => $course->id, 'sumgrades' => 2]); 450 451 // Questions. 452 $questgen = $dg->get_plugin_generator('core_question'); 453 $quizcat = $questgen->create_question_category(); 454 $question = $questgen->create_question('numerical', null, ['category' => $quizcat->id]); 455 quiz_add_quiz_question($question->id, $quiz1); 456 quiz_add_quiz_question($question->id, $quiz2); 457 458 $quizobj1a = quiz_settings::create($quiz1->id, $u1->id); 459 $quizobj1b = quiz_settings::create($quiz1->id, $u2->id); 460 $quizobj1c = quiz_settings::create($quiz1->id, $u3->id); 461 $quizobj1d = quiz_settings::create($quiz1->id, $u4->id); 462 $quizobj2a = quiz_settings::create($quiz2->id, $u1->id); 463 464 // Set attempts. 465 $quba1a = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1a->get_context()); 466 $quba1a->set_preferred_behaviour($quizobj1a->get_quiz()->preferredbehaviour); 467 $quba1b = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1b->get_context()); 468 $quba1b->set_preferred_behaviour($quizobj1b->get_quiz()->preferredbehaviour); 469 $quba1c = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1c->get_context()); 470 $quba1c->set_preferred_behaviour($quizobj1c->get_quiz()->preferredbehaviour); 471 $quba1d = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj1d->get_context()); 472 $quba1d->set_preferred_behaviour($quizobj1d->get_quiz()->preferredbehaviour); 473 $quba2a = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context()); 474 $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour); 475 476 $timenow = time(); 477 478 // User 1 passes quiz 1. 479 $attempt = quiz_create_attempt($quizobj1a, 1, false, $timenow, false, $u1->id); 480 quiz_start_new_attempt($quizobj1a, $quba1a, $attempt, 1, $timenow); 481 quiz_attempt_save_started($quizobj1a, $quba1a, $attempt); 482 $attemptobj = quiz_attempt::create($attempt->id); 483 $attemptobj->process_submitted_actions($timenow, false, [1 => ['answer' => '3.14']]); 484 $attemptobj->process_finish($timenow, false); 485 486 // User 2 goes overdue in quiz 1. 487 $attempt = quiz_create_attempt($quizobj1b, 1, false, $timenow, false, $u2->id); 488 quiz_start_new_attempt($quizobj1b, $quba1b, $attempt, 1, $timenow); 489 quiz_attempt_save_started($quizobj1b, $quba1b, $attempt); 490 $attemptobj = quiz_attempt::create($attempt->id); 491 $attemptobj->process_going_overdue($timenow, true); 492 493 // User 3 does not finish quiz 1. 494 $attempt = quiz_create_attempt($quizobj1c, 1, false, $timenow, false, $u3->id); 495 quiz_start_new_attempt($quizobj1c, $quba1c, $attempt, 1, $timenow); 496 quiz_attempt_save_started($quizobj1c, $quba1c, $attempt); 497 498 // User 4 abandons the quiz 1. 499 $attempt = quiz_create_attempt($quizobj1d, 1, false, $timenow, false, $u4->id); 500 quiz_start_new_attempt($quizobj1d, $quba1d, $attempt, 1, $timenow); 501 quiz_attempt_save_started($quizobj1d, $quba1d, $attempt); 502 $attemptobj = quiz_attempt::create($attempt->id); 503 $attemptobj->process_abandon($timenow, true); 504 505 // User 1 attempts the quiz three times (abandon, finish, in progress). 506 $quba2a = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context()); 507 $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour); 508 509 $attempt = quiz_create_attempt($quizobj2a, 1, false, $timenow, false, $u1->id); 510 quiz_start_new_attempt($quizobj2a, $quba2a, $attempt, 1, $timenow); 511 quiz_attempt_save_started($quizobj2a, $quba2a, $attempt); 512 $attemptobj = quiz_attempt::create($attempt->id); 513 $attemptobj->process_abandon($timenow, true); 514 515 $quba2a = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context()); 516 $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour); 517 518 $attempt = quiz_create_attempt($quizobj2a, 2, false, $timenow, false, $u1->id); 519 quiz_start_new_attempt($quizobj2a, $quba2a, $attempt, 2, $timenow); 520 quiz_attempt_save_started($quizobj2a, $quba2a, $attempt); 521 $attemptobj = quiz_attempt::create($attempt->id); 522 $attemptobj->process_finish($timenow, false); 523 524 $quba2a = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj2a->get_context()); 525 $quba2a->set_preferred_behaviour($quizobj2a->get_quiz()->preferredbehaviour); 526 527 $attempt = quiz_create_attempt($quizobj2a, 3, false, $timenow, false, $u1->id); 528 quiz_start_new_attempt($quizobj2a, $quba2a, $attempt, 3, $timenow); 529 quiz_attempt_save_started($quizobj2a, $quba2a, $attempt); 530 531 // Check for user 1. 532 $attempts = quiz_get_user_attempts($quiz1->id, $u1->id, 'all'); 533 $this->assertCount(1, $attempts); 534 $attempt = array_shift($attempts); 535 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state); 536 $this->assertEquals($u1->id, $attempt->userid); 537 $this->assertEquals($quiz1->id, $attempt->quiz); 538 539 $attempts = quiz_get_user_attempts($quiz1->id, $u1->id, 'finished'); 540 $this->assertCount(1, $attempts); 541 $attempt = array_shift($attempts); 542 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state); 543 $this->assertEquals($u1->id, $attempt->userid); 544 $this->assertEquals($quiz1->id, $attempt->quiz); 545 546 $attempts = quiz_get_user_attempts($quiz1->id, $u1->id, 'unfinished'); 547 $this->assertCount(0, $attempts); 548 549 // Check for user 2. 550 $attempts = quiz_get_user_attempts($quiz1->id, $u2->id, 'all'); 551 $this->assertCount(1, $attempts); 552 $attempt = array_shift($attempts); 553 $this->assertEquals(quiz_attempt::OVERDUE, $attempt->state); 554 $this->assertEquals($u2->id, $attempt->userid); 555 $this->assertEquals($quiz1->id, $attempt->quiz); 556 557 $attempts = quiz_get_user_attempts($quiz1->id, $u2->id, 'finished'); 558 $this->assertCount(0, $attempts); 559 560 $attempts = quiz_get_user_attempts($quiz1->id, $u2->id, 'unfinished'); 561 $this->assertCount(1, $attempts); 562 $attempt = array_shift($attempts); 563 $this->assertEquals(quiz_attempt::OVERDUE, $attempt->state); 564 $this->assertEquals($u2->id, $attempt->userid); 565 $this->assertEquals($quiz1->id, $attempt->quiz); 566 567 // Check for user 3. 568 $attempts = quiz_get_user_attempts($quiz1->id, $u3->id, 'all'); 569 $this->assertCount(1, $attempts); 570 $attempt = array_shift($attempts); 571 $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state); 572 $this->assertEquals($u3->id, $attempt->userid); 573 $this->assertEquals($quiz1->id, $attempt->quiz); 574 575 $attempts = quiz_get_user_attempts($quiz1->id, $u3->id, 'finished'); 576 $this->assertCount(0, $attempts); 577 578 $attempts = quiz_get_user_attempts($quiz1->id, $u3->id, 'unfinished'); 579 $this->assertCount(1, $attempts); 580 $attempt = array_shift($attempts); 581 $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state); 582 $this->assertEquals($u3->id, $attempt->userid); 583 $this->assertEquals($quiz1->id, $attempt->quiz); 584 585 // Check for user 4. 586 $attempts = quiz_get_user_attempts($quiz1->id, $u4->id, 'all'); 587 $this->assertCount(1, $attempts); 588 $attempt = array_shift($attempts); 589 $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state); 590 $this->assertEquals($u4->id, $attempt->userid); 591 $this->assertEquals($quiz1->id, $attempt->quiz); 592 593 $attempts = quiz_get_user_attempts($quiz1->id, $u4->id, 'finished'); 594 $this->assertCount(1, $attempts); 595 $attempt = array_shift($attempts); 596 $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state); 597 $this->assertEquals($u4->id, $attempt->userid); 598 $this->assertEquals($quiz1->id, $attempt->quiz); 599 600 $attempts = quiz_get_user_attempts($quiz1->id, $u4->id, 'unfinished'); 601 $this->assertCount(0, $attempts); 602 603 // Multiple attempts for user 1 in quiz 2. 604 $attempts = quiz_get_user_attempts($quiz2->id, $u1->id, 'all'); 605 $this->assertCount(3, $attempts); 606 $attempt = array_shift($attempts); 607 $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state); 608 $this->assertEquals($u1->id, $attempt->userid); 609 $this->assertEquals($quiz2->id, $attempt->quiz); 610 $attempt = array_shift($attempts); 611 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state); 612 $this->assertEquals($u1->id, $attempt->userid); 613 $this->assertEquals($quiz2->id, $attempt->quiz); 614 $attempt = array_shift($attempts); 615 $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state); 616 $this->assertEquals($u1->id, $attempt->userid); 617 $this->assertEquals($quiz2->id, $attempt->quiz); 618 619 $attempts = quiz_get_user_attempts($quiz2->id, $u1->id, 'finished'); 620 $this->assertCount(2, $attempts); 621 $attempt = array_shift($attempts); 622 $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state); 623 $attempt = array_shift($attempts); 624 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state); 625 626 $attempts = quiz_get_user_attempts($quiz2->id, $u1->id, 'unfinished'); 627 $this->assertCount(1, $attempts); 628 $attempt = array_shift($attempts); 629 630 // Multiple quiz attempts fetched at once. 631 $attempts = quiz_get_user_attempts([$quiz1->id, $quiz2->id], $u1->id, 'all'); 632 $this->assertCount(4, $attempts); 633 $attempt = array_shift($attempts); 634 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state); 635 $this->assertEquals($u1->id, $attempt->userid); 636 $this->assertEquals($quiz1->id, $attempt->quiz); 637 $attempt = array_shift($attempts); 638 $this->assertEquals(quiz_attempt::ABANDONED, $attempt->state); 639 $this->assertEquals($u1->id, $attempt->userid); 640 $this->assertEquals($quiz2->id, $attempt->quiz); 641 $attempt = array_shift($attempts); 642 $this->assertEquals(quiz_attempt::FINISHED, $attempt->state); 643 $this->assertEquals($u1->id, $attempt->userid); 644 $this->assertEquals($quiz2->id, $attempt->quiz); 645 $attempt = array_shift($attempts); 646 $this->assertEquals(quiz_attempt::IN_PROGRESS, $attempt->state); 647 $this->assertEquals($u1->id, $attempt->userid); 648 $this->assertEquals($quiz2->id, $attempt->quiz); 649 } 650 651 /** 652 * Test for quiz_get_group_override_priorities(). 653 */ 654 public function test_quiz_get_group_override_priorities() { 655 global $DB; 656 $this->resetAfterTest(); 657 658 $dg = $this->getDataGenerator(); 659 $quizgen = $dg->get_plugin_generator('mod_quiz'); 660 $course = $dg->create_course(); 661 662 $quiz = $quizgen->create_instance(['course' => $course->id, 'sumgrades' => 2]); 663 664 $this->assertNull(quiz_get_group_override_priorities($quiz->id)); 665 666 $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 667 $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]); 668 669 $now = 100; 670 $override1 = (object)[ 671 'quiz' => $quiz->id, 672 'groupid' => $group1->id, 673 'timeopen' => $now, 674 'timeclose' => $now + 20 675 ]; 676 $DB->insert_record('quiz_overrides', $override1); 677 678 $override2 = (object)[ 679 'quiz' => $quiz->id, 680 'groupid' => $group2->id, 681 'timeopen' => $now - 10, 682 'timeclose' => $now + 10 683 ]; 684 $DB->insert_record('quiz_overrides', $override2); 685 686 $priorities = quiz_get_group_override_priorities($quiz->id); 687 $this->assertNotEmpty($priorities); 688 689 $openpriorities = $priorities['open']; 690 // Override 2's time open has higher priority since it is sooner than override 1's. 691 $this->assertEquals(2, $openpriorities[$override1->timeopen]); 692 $this->assertEquals(1, $openpriorities[$override2->timeopen]); 693 694 $closepriorities = $priorities['close']; 695 // Override 1's time close has higher priority since it is later than override 2's. 696 $this->assertEquals(1, $closepriorities[$override1->timeclose]); 697 $this->assertEquals(2, $closepriorities[$override2->timeclose]); 698 } 699 700 public function test_quiz_core_calendar_provide_event_action_open() { 701 $this->resetAfterTest(); 702 703 $this->setAdminUser(); 704 705 // Create a course. 706 $course = $this->getDataGenerator()->create_course(); 707 // Create a student and enrol into the course. 708 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 709 // Create a quiz. 710 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 711 'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS]); 712 713 // Create a calendar event. 714 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_OPEN); 715 // Now, log in as student. 716 $this->setUser($student); 717 // Create an action factory. 718 $factory = new \core_calendar\action_factory(); 719 720 // Decorate action event. 721 $actionevent = mod_quiz_core_calendar_provide_event_action($event, $factory); 722 723 // Confirm the event was decorated. 724 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); 725 $this->assertEquals(get_string('attemptquiznow', 'quiz'), $actionevent->get_name()); 726 $this->assertInstanceOf('moodle_url', $actionevent->get_url()); 727 $this->assertEquals(1, $actionevent->get_item_count()); 728 $this->assertTrue($actionevent->is_actionable()); 729 } 730 731 public function test_quiz_core_calendar_provide_event_action_open_for_user() { 732 $this->resetAfterTest(); 733 734 $this->setAdminUser(); 735 736 // Create a course. 737 $course = $this->getDataGenerator()->create_course(); 738 // Create a student and enrol into the course. 739 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 740 // Create a quiz. 741 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 742 'timeopen' => time() - DAYSECS, 'timeclose' => time() + DAYSECS]); 743 744 // Create a calendar event. 745 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_OPEN); 746 747 // Create an action factory. 748 $factory = new \core_calendar\action_factory(); 749 750 // Decorate action event for the student. 751 $actionevent = mod_quiz_core_calendar_provide_event_action($event, $factory, $student->id); 752 753 // Confirm the event was decorated. 754 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); 755 $this->assertEquals(get_string('attemptquiznow', 'quiz'), $actionevent->get_name()); 756 $this->assertInstanceOf('moodle_url', $actionevent->get_url()); 757 $this->assertEquals(1, $actionevent->get_item_count()); 758 $this->assertTrue($actionevent->is_actionable()); 759 } 760 761 public function test_quiz_core_calendar_provide_event_action_closed() { 762 $this->resetAfterTest(); 763 764 $this->setAdminUser(); 765 766 // Create a course. 767 $course = $this->getDataGenerator()->create_course(); 768 769 // Create a quiz. 770 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 771 'timeclose' => time() - DAYSECS]); 772 773 // Create a calendar event. 774 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_CLOSE); 775 776 // Create an action factory. 777 $factory = new \core_calendar\action_factory(); 778 779 // Confirm the result was null. 780 $this->assertNull(mod_quiz_core_calendar_provide_event_action($event, $factory)); 781 } 782 783 public function test_quiz_core_calendar_provide_event_action_closed_for_user() { 784 $this->resetAfterTest(); 785 786 $this->setAdminUser(); 787 788 // Create a course. 789 $course = $this->getDataGenerator()->create_course(); 790 791 // Create a student. 792 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 793 794 // Create a quiz. 795 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 796 'timeclose' => time() - DAYSECS]); 797 798 // Create a calendar event. 799 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_CLOSE); 800 801 // Create an action factory. 802 $factory = new \core_calendar\action_factory(); 803 804 // Confirm the result was null. 805 $this->assertNull(mod_quiz_core_calendar_provide_event_action($event, $factory, $student->id)); 806 } 807 808 public function test_quiz_core_calendar_provide_event_action_open_in_future() { 809 $this->resetAfterTest(); 810 811 $this->setAdminUser(); 812 813 // Create a course. 814 $course = $this->getDataGenerator()->create_course(); 815 // Create a student and enrol into the course. 816 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 817 // Create a quiz. 818 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 819 'timeopen' => time() + DAYSECS]); 820 821 // Create a calendar event. 822 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_CLOSE); 823 // Now, log in as student. 824 $this->setUser($student); 825 // Create an action factory. 826 $factory = new \core_calendar\action_factory(); 827 828 // Decorate action event. 829 $actionevent = mod_quiz_core_calendar_provide_event_action($event, $factory); 830 831 // Confirm the event was decorated. 832 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); 833 $this->assertEquals(get_string('attemptquiznow', 'quiz'), $actionevent->get_name()); 834 $this->assertInstanceOf('moodle_url', $actionevent->get_url()); 835 $this->assertEquals(1, $actionevent->get_item_count()); 836 $this->assertFalse($actionevent->is_actionable()); 837 } 838 839 public function test_quiz_core_calendar_provide_event_action_open_in_future_for_user() { 840 $this->resetAfterTest(); 841 842 $this->setAdminUser(); 843 844 // Create a course. 845 $course = $this->getDataGenerator()->create_course(); 846 // Create a student and enrol into the course. 847 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 848 // Create a quiz. 849 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 850 'timeopen' => time() + DAYSECS]); 851 852 // Create a calendar event. 853 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_CLOSE); 854 855 // Create an action factory. 856 $factory = new \core_calendar\action_factory(); 857 858 // Decorate action event for the student. 859 $actionevent = mod_quiz_core_calendar_provide_event_action($event, $factory, $student->id); 860 861 // Confirm the event was decorated. 862 $this->assertInstanceOf('\core_calendar\local\event\value_objects\action', $actionevent); 863 $this->assertEquals(get_string('attemptquiznow', 'quiz'), $actionevent->get_name()); 864 $this->assertInstanceOf('moodle_url', $actionevent->get_url()); 865 $this->assertEquals(1, $actionevent->get_item_count()); 866 $this->assertFalse($actionevent->is_actionable()); 867 } 868 869 public function test_quiz_core_calendar_provide_event_action_no_capability() { 870 global $DB; 871 872 $this->resetAfterTest(); 873 $this->setAdminUser(); 874 875 // Create a course. 876 $course = $this->getDataGenerator()->create_course(); 877 878 // Create a student. 879 $student = $this->getDataGenerator()->create_user(); 880 $studentrole = $DB->get_record('role', ['shortname' => 'student']); 881 882 // Enrol student. 883 $this->assertTrue($this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id)); 884 885 // Create a quiz. 886 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]); 887 888 // Remove the permission to attempt or review the quiz for the student role. 889 $coursecontext = \context_course::instance($course->id); 890 assign_capability('mod/quiz:reviewmyattempts', CAP_PROHIBIT, $studentrole->id, $coursecontext); 891 assign_capability('mod/quiz:attempt', CAP_PROHIBIT, $studentrole->id, $coursecontext); 892 893 // Create a calendar event. 894 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_OPEN); 895 896 // Create an action factory. 897 $factory = new \core_calendar\action_factory(); 898 899 // Set current user to the student. 900 $this->setUser($student); 901 902 // Confirm null is returned. 903 $this->assertNull(mod_quiz_core_calendar_provide_event_action($event, $factory)); 904 } 905 906 public function test_quiz_core_calendar_provide_event_action_no_capability_for_user() { 907 global $DB; 908 909 $this->resetAfterTest(); 910 $this->setAdminUser(); 911 912 // Create a course. 913 $course = $this->getDataGenerator()->create_course(); 914 915 // Create a student. 916 $student = $this->getDataGenerator()->create_user(); 917 $studentrole = $DB->get_record('role', ['shortname' => 'student']); 918 919 // Enrol student. 920 $this->assertTrue($this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id)); 921 922 // Create a quiz. 923 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]); 924 925 // Remove the permission to attempt or review the quiz for the student role. 926 $coursecontext = \context_course::instance($course->id); 927 assign_capability('mod/quiz:reviewmyattempts', CAP_PROHIBIT, $studentrole->id, $coursecontext); 928 assign_capability('mod/quiz:attempt', CAP_PROHIBIT, $studentrole->id, $coursecontext); 929 930 // Create a calendar event. 931 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_OPEN); 932 933 // Create an action factory. 934 $factory = new \core_calendar\action_factory(); 935 936 // Confirm null is returned. 937 $this->assertNull(mod_quiz_core_calendar_provide_event_action($event, $factory, $student->id)); 938 } 939 940 public function test_quiz_core_calendar_provide_event_action_already_finished() { 941 global $DB; 942 943 $this->resetAfterTest(); 944 945 $this->setAdminUser(); 946 947 // Create a course. 948 $course = $this->getDataGenerator()->create_course(); 949 950 // Create a student. 951 $student = $this->getDataGenerator()->create_user(); 952 $studentrole = $DB->get_record('role', ['shortname' => 'student']); 953 954 // Enrol student. 955 $this->assertTrue($this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id)); 956 957 // Create a quiz. 958 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 959 'sumgrades' => 1]); 960 961 // Add a question to the quiz. 962 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 963 $cat = $questiongenerator->create_question_category(); 964 $question = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]); 965 quiz_add_quiz_question($question->id, $quiz); 966 967 // Get the quiz object. 968 $quizobj = quiz_settings::create($quiz->id, $student->id); 969 970 // Create an attempt for the student in the quiz. 971 $timenow = time(); 972 $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $student->id); 973 $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context()); 974 $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour); 975 quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow); 976 quiz_attempt_save_started($quizobj, $quba, $attempt); 977 978 // Finish the attempt. 979 $attemptobj = quiz_attempt::create($attempt->id); 980 $attemptobj->process_finish($timenow, false); 981 982 // Create a calendar event. 983 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_OPEN); 984 985 // Create an action factory. 986 $factory = new \core_calendar\action_factory(); 987 988 // Set current user to the student. 989 $this->setUser($student); 990 991 // Confirm null is returned. 992 $this->assertNull(mod_quiz_core_calendar_provide_event_action($event, $factory)); 993 } 994 995 public function test_quiz_core_calendar_provide_event_action_already_finished_for_user() { 996 global $DB; 997 998 $this->resetAfterTest(); 999 1000 $this->setAdminUser(); 1001 1002 // Create a course. 1003 $course = $this->getDataGenerator()->create_course(); 1004 1005 // Create a student. 1006 $student = $this->getDataGenerator()->create_user(); 1007 $studentrole = $DB->get_record('role', ['shortname' => 'student']); 1008 1009 // Enrol student. 1010 $this->assertTrue($this->getDataGenerator()->enrol_user($student->id, $course->id, $studentrole->id)); 1011 1012 // Create a quiz. 1013 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 1014 'sumgrades' => 1]); 1015 1016 // Add a question to the quiz. 1017 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 1018 $cat = $questiongenerator->create_question_category(); 1019 $question = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]); 1020 quiz_add_quiz_question($question->id, $quiz); 1021 1022 // Get the quiz object. 1023 $quizobj = quiz_settings::create($quiz->id, $student->id); 1024 1025 // Create an attempt for the student in the quiz. 1026 $timenow = time(); 1027 $attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $student->id); 1028 $quba = \question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context()); 1029 $quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour); 1030 quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow); 1031 quiz_attempt_save_started($quizobj, $quba, $attempt); 1032 1033 // Finish the attempt. 1034 $attemptobj = quiz_attempt::create($attempt->id); 1035 $attemptobj->process_finish($timenow, false); 1036 1037 // Create a calendar event. 1038 $event = $this->create_action_event($course->id, $quiz->id, QUIZ_EVENT_TYPE_OPEN); 1039 1040 // Create an action factory. 1041 $factory = new \core_calendar\action_factory(); 1042 1043 // Confirm null is returned. 1044 $this->assertNull(mod_quiz_core_calendar_provide_event_action($event, $factory, $student->id)); 1045 } 1046 1047 public function test_quiz_core_calendar_provide_event_action_already_completed() { 1048 $this->resetAfterTest(); 1049 set_config('enablecompletion', 1); 1050 $this->setAdminUser(); 1051 1052 // Create the activity. 1053 $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); 1054 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id], 1055 ['completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS]); 1056 1057 // Get some additional data. 1058 $cm = get_coursemodule_from_instance('quiz', $quiz->id); 1059 1060 // Create a calendar event. 1061 $event = $this->create_action_event($course->id, $quiz->id, 1062 \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED); 1063 1064 // Mark the activity as completed. 1065 $completion = new \completion_info($course); 1066 $completion->set_module_viewed($cm); 1067 1068 // Create an action factory. 1069 $factory = new \core_calendar\action_factory(); 1070 1071 // Decorate action event. 1072 $actionevent = mod_quiz_core_calendar_provide_event_action($event, $factory); 1073 1074 // Ensure result was null. 1075 $this->assertNull($actionevent); 1076 } 1077 1078 public function test_quiz_core_calendar_provide_event_action_already_completed_for_user() { 1079 $this->resetAfterTest(); 1080 set_config('enablecompletion', 1); 1081 $this->setAdminUser(); 1082 1083 // Create the activity. 1084 $course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); 1085 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id], 1086 ['completion' => 2, 'completionview' => 1, 'completionexpected' => time() + DAYSECS]); 1087 1088 // Enrol a student in the course. 1089 $student = $this->getDataGenerator()->create_and_enrol($course, 'student'); 1090 1091 // Get some additional data. 1092 $cm = get_coursemodule_from_instance('quiz', $quiz->id); 1093 1094 // Create a calendar event. 1095 $event = $this->create_action_event($course->id, $quiz->id, 1096 \core_completion\api::COMPLETION_EVENT_TYPE_DATE_COMPLETION_EXPECTED); 1097 1098 // Mark the activity as completed for the student. 1099 $completion = new \completion_info($course); 1100 $completion->set_module_viewed($cm, $student->id); 1101 1102 // Create an action factory. 1103 $factory = new \core_calendar\action_factory(); 1104 1105 // Decorate action event for the student. 1106 $actionevent = mod_quiz_core_calendar_provide_event_action($event, $factory, $student->id); 1107 1108 // Ensure result was null. 1109 $this->assertNull($actionevent); 1110 } 1111 1112 /** 1113 * Creates an action event. 1114 * 1115 * @param int $courseid 1116 * @param int $instanceid The quiz id. 1117 * @param string $eventtype The event type. eg. QUIZ_EVENT_TYPE_OPEN. 1118 * @return bool|calendar_event 1119 */ 1120 private function create_action_event($courseid, $instanceid, $eventtype) { 1121 $event = new \stdClass(); 1122 $event->name = 'Calendar event'; 1123 $event->modulename = 'quiz'; 1124 $event->courseid = $courseid; 1125 $event->instance = $instanceid; 1126 $event->type = CALENDAR_EVENT_TYPE_ACTION; 1127 $event->eventtype = $eventtype; 1128 $event->timestart = time(); 1129 1130 return \calendar_event::create($event); 1131 } 1132 1133 /** 1134 * Test the callback responsible for returning the completion rule descriptions. 1135 * This function should work given either an instance of the module (cm_info), such as when checking the active rules, 1136 * or if passed a stdClass of similar structure, such as when checking the the default completion settings for a mod type. 1137 */ 1138 public function test_mod_quiz_completion_get_active_rule_descriptions() { 1139 $this->resetAfterTest(); 1140 $this->setAdminUser(); 1141 1142 // Two activities, both with automatic completion. One has the 'completionsubmit' rule, one doesn't. 1143 $course = $this->getDataGenerator()->create_course(['enablecompletion' => 2]); 1144 $quiz1 = $this->getDataGenerator()->create_module('quiz', [ 1145 'course' => $course->id, 1146 'completion' => 2, 1147 'completionusegrade' => 1, 1148 'completionpassgrade' => 1, 1149 'completionattemptsexhausted' => 1, 1150 ]); 1151 $quiz2 = $this->getDataGenerator()->create_module('quiz', [ 1152 'course' => $course->id, 1153 'completion' => 2, 1154 'completionusegrade' => 0 1155 ]); 1156 $cm1 = \cm_info::create(get_coursemodule_from_instance('quiz', $quiz1->id)); 1157 $cm2 = \cm_info::create(get_coursemodule_from_instance('quiz', $quiz2->id)); 1158 1159 // Data for the stdClass input type. 1160 // This type of input would occur when checking the default completion rules for an activity type, where we don't have 1161 // any access to cm_info, rather the input is a stdClass containing completion and customdata attributes, just like cm_info. 1162 $moddefaults = new \stdClass(); 1163 $moddefaults->customdata = ['customcompletionrules' => [ 1164 'completionattemptsexhausted' => 1, 1165 ]]; 1166 $moddefaults->completion = 2; 1167 1168 $activeruledescriptions = [ 1169 get_string('completionpassorattemptsexhausteddesc', 'quiz'), 1170 ]; 1171 $this->assertEquals(mod_quiz_get_completion_active_rule_descriptions($cm1), $activeruledescriptions); 1172 $this->assertEquals(mod_quiz_get_completion_active_rule_descriptions($cm2), []); 1173 $this->assertEquals(mod_quiz_get_completion_active_rule_descriptions($moddefaults), $activeruledescriptions); 1174 $this->assertEquals(mod_quiz_get_completion_active_rule_descriptions(new \stdClass()), []); 1175 } 1176 1177 /** 1178 * A user who does not have capabilities to add events to the calendar should be able to create a quiz. 1179 */ 1180 public function test_creation_with_no_calendar_capabilities() { 1181 $this->resetAfterTest(); 1182 $course = self::getDataGenerator()->create_course(); 1183 $context = \context_course::instance($course->id); 1184 $user = self::getDataGenerator()->create_and_enrol($course, 'editingteacher'); 1185 $roleid = self::getDataGenerator()->create_role(); 1186 self::getDataGenerator()->role_assign($roleid, $user->id, $context->id); 1187 assign_capability('moodle/calendar:manageentries', CAP_PROHIBIT, $roleid, $context, true); 1188 $generator = self::getDataGenerator()->get_plugin_generator('mod_quiz'); 1189 // Create an instance as a user without the calendar capabilities. 1190 $this->setUser($user); 1191 $time = time(); 1192 $params = [ 1193 'course' => $course->id, 1194 'timeopen' => $time + 200, 1195 'timeclose' => $time + 2000, 1196 ]; 1197 $generator->create_instance($params); 1198 } 1199 1200 /** 1201 * Data provider for summarise_response() test cases. 1202 * 1203 * @return array List of data sets (test cases) 1204 */ 1205 public function mod_quiz_inplace_editable_provider(): array { 1206 return [ 1207 'set to A1' => [1, 'A1'], 1208 'set with HTML characters' => [2, 'A & & <-:'], 1209 'set to integer' => [3, '3'], 1210 'set to blank' => [4, ''], 1211 'set with Unicode characters' => [1, 'L\'Aina Lluís^'], 1212 'set with Unicode at the truncation point' => [1, '123456789012345碁'], 1213 'set with HTML Char at the truncation point' => [1, '123456789012345>'], 1214 ]; 1215 } 1216 1217 /** 1218 * Test customised and automated question numbering for a given slot number and customised value. 1219 * 1220 * @dataProvider mod_quiz_inplace_editable_provider 1221 * @param int $slotnumber 1222 * @param string $newvalue 1223 * @covers ::mod_quiz_inplace_editable 1224 */ 1225 public function test_mod_quiz_inplace_editable(int $slotnumber, string $newvalue): void { 1226 global $CFG; 1227 require_once($CFG->dirroot . '/lib/external/externallib.php'); 1228 $this->resetAfterTest(); 1229 1230 $this->setAdminUser(); 1231 $course = self::getDataGenerator()->create_course(); 1232 $quiz = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id, 'sumgrades' => 1]); 1233 $cm = get_coursemodule_from_id('quiz', $quiz->cmid); 1234 1235 // Add few questions to the quiz. 1236 $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); 1237 $cat = $questiongenerator->create_question_category(); 1238 1239 $question = $questiongenerator->create_question('truefalse', null, ['category' => $cat->id]); 1240 quiz_add_quiz_question($question->id, $quiz); 1241 1242 $question = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); 1243 quiz_add_quiz_question($question->id, $quiz); 1244 1245 $question = $questiongenerator->create_question('multichoice', null, ['category' => $cat->id]); 1246 quiz_add_quiz_question($question->id, $quiz); 1247 1248 $question = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]); 1249 quiz_add_quiz_question($question->id, $quiz); 1250 1251 // Create the quiz object. 1252 $quizobj = new quiz_settings($quiz, $cm, $course); 1253 $structure = $quizobj->get_structure(); 1254 1255 $slots = $structure->get_slots(); 1256 $this->assertEquals(4, count($slots)); 1257 1258 $slotid = $structure->get_slot_id_for_slot($slotnumber); 1259 $inplaceeditable = mod_quiz_inplace_editable('slotdisplaynumber', $slotid, $newvalue); 1260 $result = \core_external::update_inplace_editable('mod_quiz', 'slotdisplaynumber', $slotid, $newvalue); 1261 $result = external_api::clean_returnvalue(\core_external::update_inplace_editable_returns(), $result); 1262 1263 $this->assertEquals(count((array) $inplaceeditable), count($result)); 1264 $this->assertEquals($slotid, $result['itemid']); 1265 if ($newvalue === '' || is_null($newvalue)) { 1266 // Check against default. 1267 $this->assertEquals($slotnumber, $result['displayvalue']); 1268 $this->assertEquals($slotnumber, $result['value']); 1269 } else { 1270 // Check against the custom number. 1271 $this->assertEquals(s($newvalue), $result['displayvalue']); 1272 $this->assertEquals($newvalue, $result['value']); 1273 } 1274 } 1275 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body