Differences Between: [Versions 310 and 311] [Versions 310 and 400] [Versions 310 and 401] [Versions 310 and 402] [Versions 310 and 403] [Versions 39 and 310]
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 the completion condition. 19 * 20 * @package availability_completion 21 * @copyright 2014 The Open University 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 */ 24 25 defined('MOODLE_INTERNAL') || die(); 26 27 use availability_completion\condition; 28 29 global $CFG; 30 require_once($CFG->libdir . '/completionlib.php'); 31 32 /** 33 * Unit tests for the completion condition. 34 * 35 * @package availability_completion 36 * @copyright 2014 The Open University 37 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 38 */ 39 class availability_completion_condition_testcase extends advanced_testcase { 40 41 /** 42 * Setup to ensure that fixtures are loaded. 43 */ 44 public static function setupBeforeClass(): void { 45 global $CFG; 46 // Load the mock info class so that it can be used. 47 require_once($CFG->dirroot . '/availability/tests/fixtures/mock_info.php'); 48 require_once($CFG->dirroot . '/availability/tests/fixtures/mock_info_module.php'); 49 require_once($CFG->dirroot . '/availability/tests/fixtures/mock_info_section.php'); 50 } 51 52 /** 53 * Load required classes. 54 */ 55 public function setUp(): void { 56 availability_completion\condition::wipe_static_cache(); 57 } 58 59 /** 60 * Tests constructing and using condition as part of tree. 61 */ 62 public function test_in_tree() { 63 global $USER, $CFG; 64 $this->resetAfterTest(); 65 66 $this->setAdminUser(); 67 68 // Create course with completion turned on and a Page. 69 $CFG->enablecompletion = true; 70 $CFG->enableavailability = true; 71 $generator = $this->getDataGenerator(); 72 $course = $generator->create_course(['enablecompletion' => 1]); 73 $page = $generator->get_plugin_generator('mod_page')->create_instance( 74 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 75 $selfpage = $generator->get_plugin_generator('mod_page')->create_instance( 76 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 77 78 $modinfo = get_fast_modinfo($course); 79 $cm = $modinfo->get_cm($page->cmid); 80 $info = new \core_availability\mock_info($course, $USER->id); 81 82 $structure = (object)[ 83 'op' => '|', 84 'show' => true, 85 'c' => [ 86 (object)[ 87 'type' => 'completion', 88 'cm' => (int)$cm->id, 89 'e' => COMPLETION_COMPLETE 90 ] 91 ] 92 ]; 93 $tree = new \core_availability\tree($structure); 94 95 // Initial check (user has not completed activity). 96 $result = $tree->check_available(false, $info, true, $USER->id); 97 $this->assertFalse($result->is_available()); 98 99 // Mark activity complete. 100 $completion = new completion_info($course); 101 $completion->update_state($cm, COMPLETION_COMPLETE); 102 103 // Now it's true! 104 $result = $tree->check_available(false, $info, true, $USER->id); 105 $this->assertTrue($result->is_available()); 106 } 107 108 /** 109 * Tests the constructor including error conditions. Also tests the 110 * string conversion feature (intended for debugging only). 111 */ 112 public function test_constructor() { 113 // No parameters. 114 $structure = new stdClass(); 115 try { 116 $cond = new condition($structure); 117 $this->fail(); 118 } catch (coding_exception $e) { 119 $this->assertStringContainsString('Missing or invalid ->cm', $e->getMessage()); 120 } 121 122 // Invalid $cm. 123 $structure->cm = 'hello'; 124 try { 125 $cond = new condition($structure); 126 $this->fail(); 127 } catch (coding_exception $e) { 128 $this->assertStringContainsString('Missing or invalid ->cm', $e->getMessage()); 129 } 130 131 // Missing $e. 132 $structure->cm = 42; 133 try { 134 $cond = new condition($structure); 135 $this->fail(); 136 } catch (coding_exception $e) { 137 $this->assertStringContainsString('Missing or invalid ->e', $e->getMessage()); 138 } 139 140 // Invalid $e. 141 $structure->e = 99; 142 try { 143 $cond = new condition($structure); 144 $this->fail(); 145 } catch (coding_exception $e) { 146 $this->assertStringContainsString('Missing or invalid ->e', $e->getMessage()); 147 } 148 149 // Successful construct & display with all different expected values. 150 $structure->e = COMPLETION_COMPLETE; 151 $cond = new condition($structure); 152 $this->assertEquals('{completion:cm42 COMPLETE}', (string)$cond); 153 154 $structure->e = COMPLETION_COMPLETE_PASS; 155 $cond = new condition($structure); 156 $this->assertEquals('{completion:cm42 COMPLETE_PASS}', (string)$cond); 157 158 $structure->e = COMPLETION_COMPLETE_FAIL; 159 $cond = new condition($structure); 160 $this->assertEquals('{completion:cm42 COMPLETE_FAIL}', (string)$cond); 161 162 $structure->e = COMPLETION_INCOMPLETE; 163 $cond = new condition($structure); 164 $this->assertEquals('{completion:cm42 INCOMPLETE}', (string)$cond); 165 166 // Successful contruct with previous activity. 167 $structure->cm = condition::OPTION_PREVIOUS; 168 $cond = new condition($structure); 169 $this->assertEquals('{completion:cmopprevious INCOMPLETE}', (string)$cond); 170 171 } 172 173 /** 174 * Tests the save() function. 175 */ 176 public function test_save() { 177 $structure = (object)['cm' => 42, 'e' => COMPLETION_COMPLETE]; 178 $cond = new condition($structure); 179 $structure->type = 'completion'; 180 $this->assertEquals($structure, $cond->save()); 181 } 182 183 /** 184 * Tests the is_available and get_description functions. 185 */ 186 public function test_usage() { 187 global $CFG, $DB; 188 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 189 $this->resetAfterTest(); 190 191 // Create course with completion turned on. 192 $CFG->enablecompletion = true; 193 $CFG->enableavailability = true; 194 $generator = $this->getDataGenerator(); 195 $course = $generator->create_course(['enablecompletion' => 1]); 196 $user = $generator->create_user(); 197 $generator->enrol_user($user->id, $course->id); 198 $this->setUser($user); 199 200 // Create a Page with manual completion for basic checks. 201 $page = $generator->get_plugin_generator('mod_page')->create_instance( 202 ['course' => $course->id, 'name' => 'Page!', 203 'completion' => COMPLETION_TRACKING_MANUAL]); 204 205 // Create an assignment - we need to have something that can be graded 206 // so as to test the PASS/FAIL states. Set it up to be completed based 207 // on its grade item. 208 $assignrow = $this->getDataGenerator()->create_module('assign', [ 209 'course' => $course->id, 'name' => 'Assign!', 210 'completion' => COMPLETION_TRACKING_AUTOMATIC]); 211 $DB->set_field('course_modules', 'completiongradeitemnumber', 0, 212 ['id' => $assignrow->cmid]); 213 $assign = new assign(context_module::instance($assignrow->cmid), false, false); 214 215 // Get basic details. 216 $modinfo = get_fast_modinfo($course); 217 $pagecm = $modinfo->get_cm($page->cmid); 218 $assigncm = $assign->get_course_module(); 219 $info = new \core_availability\mock_info($course, $user->id); 220 221 // COMPLETE state (false), positive and NOT. 222 $cond = new condition((object)[ 223 'cm' => (int)$pagecm->id, 'e' => COMPLETION_COMPLETE 224 ]); 225 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 226 $information = $cond->get_description(false, false, $info); 227 $information = \core_availability\info::format_info($information, $course); 228 $this->assertRegExp('~Page!.*is marked complete~', $information); 229 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 230 231 // INCOMPLETE state (true). 232 $cond = new condition((object)[ 233 'cm' => (int)$pagecm->id, 'e' => COMPLETION_INCOMPLETE 234 ]); 235 $this->assertTrue($cond->is_available(false, $info, true, $user->id)); 236 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 237 $information = $cond->get_description(false, true, $info); 238 $information = \core_availability\info::format_info($information, $course); 239 $this->assertRegExp('~Page!.*is marked complete~', $information); 240 241 // Mark page complete. 242 $completion = new completion_info($course); 243 $completion->update_state($pagecm, COMPLETION_COMPLETE); 244 245 // COMPLETE state (true). 246 $cond = new condition((object)[ 247 'cm' => (int)$pagecm->id, 'e' => COMPLETION_COMPLETE 248 ]); 249 $this->assertTrue($cond->is_available(false, $info, true, $user->id)); 250 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 251 $information = $cond->get_description(false, true, $info); 252 $information = \core_availability\info::format_info($information, $course); 253 $this->assertRegExp('~Page!.*is incomplete~', $information); 254 255 // INCOMPLETE state (false). 256 $cond = new condition((object)[ 257 'cm' => (int)$pagecm->id, 'e' => COMPLETION_INCOMPLETE 258 ]); 259 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 260 $information = $cond->get_description(false, false, $info); 261 $information = \core_availability\info::format_info($information, $course); 262 $this->assertRegExp('~Page!.*is incomplete~', $information); 263 $this->assertTrue($cond->is_available(true, $info, 264 true, $user->id)); 265 266 // We are going to need the grade item so that we can get pass/fails. 267 $gradeitem = $assign->get_grade_item(); 268 grade_object::set_properties($gradeitem, ['gradepass' => 50.0]); 269 $gradeitem->update(); 270 271 // With no grade, it should return true for INCOMPLETE and false for 272 // the other three. 273 $cond = new condition((object)[ 274 'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE 275 ]); 276 $this->assertTrue($cond->is_available(false, $info, true, $user->id)); 277 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 278 279 $cond = new condition((object)[ 280 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE 281 ]); 282 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 283 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 284 285 // Check $information for COMPLETE_PASS and _FAIL as we haven't yet. 286 $cond = new condition((object)[ 287 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS 288 ]); 289 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 290 $information = $cond->get_description(false, false, $info); 291 $information = \core_availability\info::format_info($information, $course); 292 $this->assertRegExp('~Assign!.*is complete and passed~', $information); 293 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 294 295 $cond = new condition((object)[ 296 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL 297 ]); 298 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 299 $information = $cond->get_description(false, false, $info); 300 $information = \core_availability\info::format_info($information, $course); 301 $this->assertRegExp('~Assign!.*is complete and failed~', $information); 302 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 303 304 // Change the grade to be complete and failed. 305 self::set_grade($assignrow, $user->id, 40); 306 307 $cond = new condition((object)[ 308 'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE 309 ]); 310 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 311 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 312 313 $cond = new condition((object)[ 314 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE 315 ]); 316 $this->assertTrue($cond->is_available(false, $info, true, $user->id)); 317 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 318 319 $cond = new condition((object)[ 320 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS 321 ]); 322 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 323 $information = $cond->get_description(false, false, $info); 324 $information = \core_availability\info::format_info($information, $course); 325 $this->assertRegExp('~Assign!.*is complete and passed~', $information); 326 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 327 328 $cond = new condition((object)[ 329 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL 330 ]); 331 $this->assertTrue($cond->is_available(false, $info, true, $user->id)); 332 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 333 $information = $cond->get_description(false, true, $info); 334 $information = \core_availability\info::format_info($information, $course); 335 $this->assertRegExp('~Assign!.*is not complete and failed~', $information); 336 337 // Now change it to pass. 338 self::set_grade($assignrow, $user->id, 60); 339 340 $cond = new condition((object)[ 341 'cm' => (int)$assigncm->id, 'e' => COMPLETION_INCOMPLETE 342 ]); 343 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 344 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 345 346 $cond = new condition((object)[ 347 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE 348 ]); 349 $this->assertTrue($cond->is_available(false, $info, true, $user->id)); 350 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 351 352 $cond = new condition((object)[ 353 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_PASS 354 ]); 355 $this->assertTrue($cond->is_available(false, $info, true, $user->id)); 356 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 357 $information = $cond->get_description(false, true, $info); 358 $information = \core_availability\info::format_info($information, $course); 359 $this->assertRegExp('~Assign!.*is not complete and passed~', $information); 360 361 $cond = new condition((object)[ 362 'cm' => (int)$assigncm->id, 'e' => COMPLETION_COMPLETE_FAIL 363 ]); 364 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 365 $information = $cond->get_description(false, false, $info); 366 $information = \core_availability\info::format_info($information, $course); 367 $this->assertRegExp('~Assign!.*is complete and failed~', $information); 368 $this->assertTrue($cond->is_available(true, $info, true, $user->id)); 369 370 // Simulate deletion of an activity by using an invalid cmid. These 371 // conditions always fail, regardless of NOT flag or INCOMPLETE. 372 $cond = new condition((object)[ 373 'cm' => ($assigncm->id + 100), 'e' => COMPLETION_COMPLETE 374 ]); 375 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 376 $information = $cond->get_description(false, false, $info); 377 $information = \core_availability\info::format_info($information, $course); 378 $this->assertRegExp('~(Missing activity).*is marked complete~', $information); 379 $this->assertFalse($cond->is_available(true, $info, true, $user->id)); 380 $cond = new condition((object)[ 381 'cm' => ($assigncm->id + 100), 'e' => COMPLETION_INCOMPLETE 382 ]); 383 $this->assertFalse($cond->is_available(false, $info, true, $user->id)); 384 } 385 386 /** 387 * Tests the is_available and get_description functions for previous activity option. 388 * 389 * @dataProvider test_previous_activity_data 390 * @param int $grade the current assign grade (0 for none) 391 * @param int $condition true for complete, false for incomplete 392 * @param string $mark activity to mark as complete 393 * @param string $activity activity name to test 394 * @param bool $result if it must be available or not 395 * @param bool $resultnot if it must be available when the condition is inverted 396 * @param string $description the availabiklity text to check 397 */ 398 public function test_previous_activity(int $grade, int $condition, string $mark, string $activity, 399 bool $result, bool $resultnot, string $description): void { 400 global $CFG, $DB; 401 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 402 $this->resetAfterTest(); 403 404 // Create course with completion turned on. 405 $CFG->enablecompletion = true; 406 $CFG->enableavailability = true; 407 $generator = $this->getDataGenerator(); 408 $course = $generator->create_course(['enablecompletion' => 1]); 409 $user = $generator->create_user(); 410 $generator->enrol_user($user->id, $course->id); 411 $this->setUser($user); 412 413 // Page 1 (manual completion). 414 $page1 = $generator->get_plugin_generator('mod_page')->create_instance( 415 ['course' => $course->id, 'name' => 'Page1!', 416 'completion' => COMPLETION_TRACKING_MANUAL]); 417 418 // Page 2 (manual completion). 419 $page2 = $generator->get_plugin_generator('mod_page')->create_instance( 420 ['course' => $course->id, 'name' => 'Page2!', 421 'completion' => COMPLETION_TRACKING_MANUAL]); 422 423 // Page ignored (no completion). 424 $pagenocompletion = $generator->get_plugin_generator('mod_page')->create_instance( 425 ['course' => $course->id, 'name' => 'Page ignored!']); 426 427 // Create an assignment - we need to have something that can be graded 428 // so as to test the PASS/FAIL states. Set it up to be completed based 429 // on its grade item. 430 $assignrow = $this->getDataGenerator()->create_module('assign', [ 431 'course' => $course->id, 'name' => 'Assign!', 432 'completion' => COMPLETION_TRACKING_AUTOMATIC 433 ]); 434 $DB->set_field('course_modules', 'completiongradeitemnumber', 0, 435 ['id' => $assignrow->cmid]); 436 $assign = new assign(context_module::instance($assignrow->cmid), false, false); 437 438 // Page 3 (manual completion). 439 $page3 = $generator->get_plugin_generator('mod_page')->create_instance( 440 ['course' => $course->id, 'name' => 'Page3!', 441 'completion' => COMPLETION_TRACKING_MANUAL]); 442 443 // Get basic details. 444 $activities = []; 445 $modinfo = get_fast_modinfo($course); 446 $activities['page1'] = $modinfo->get_cm($page1->cmid); 447 $activities['page2'] = $modinfo->get_cm($page2->cmid); 448 $activities['assign'] = $assign->get_course_module(); 449 $activities['page3'] = $modinfo->get_cm($page3->cmid); 450 $prevvalue = condition::OPTION_PREVIOUS; 451 452 // Setup gradings and completion. 453 if ($grade) { 454 $gradeitem = $assign->get_grade_item(); 455 grade_object::set_properties($gradeitem, ['gradepass' => 50.0]); 456 $gradeitem->update(); 457 self::set_grade($assignrow, $user->id, $grade); 458 } 459 if ($mark) { 460 $completion = new completion_info($course); 461 $completion->update_state($activities[$mark], COMPLETION_COMPLETE); 462 } 463 464 // Set opprevious WITH non existent previous activity. 465 $info = new \core_availability\mock_info_module($user->id, $activities[$activity]); 466 $cond = new condition((object)[ 467 'cm' => (int)$prevvalue, 'e' => $condition 468 ]); 469 470 // Do the checks. 471 $this->assertEquals($result, $cond->is_available(false, $info, true, $user->id)); 472 $this->assertEquals($resultnot, $cond->is_available(true, $info, true, $user->id)); 473 $information = $cond->get_description(false, false, $info); 474 $information = \core_availability\info::format_info($information, $course); 475 $this->assertRegExp($description, $information); 476 } 477 478 public function test_previous_activity_data(): array { 479 // Assign grade, condition, activity to complete, activity to test, result, resultnot, description. 480 return [ 481 'Missing previous activity complete' => [ 482 0, COMPLETION_COMPLETE, '', 'page1', false, false, '~Missing activity.*is marked complete~' 483 ], 484 'Missing previous activity incomplete' => [ 485 0, COMPLETION_INCOMPLETE, '', 'page1', false, false, '~Missing activity.*is incomplete~' 486 ], 487 'Previous complete condition with previous activity incompleted' => [ 488 0, COMPLETION_COMPLETE, '', 'page2', false, true, '~Page1!.*is marked complete~' 489 ], 490 'Previous incomplete condition with previous activity incompleted' => [ 491 0, COMPLETION_INCOMPLETE, '', 'page2', true, false, '~Page1!.*is incomplete~' 492 ], 493 'Previous complete condition with previous activity completed' => [ 494 0, COMPLETION_COMPLETE, 'page1', 'page2', true, false, '~Page1!.*is marked complete~' 495 ], 496 'Previous incomplete condition with previous activity completed' => [ 497 0, COMPLETION_INCOMPLETE, 'page1', 'page2', false, true, '~Page1!.*is incomplete~' 498 ], 499 // Depenging on page pass fail (pages are not gradable). 500 'Previous complete pass condition with previous no gradable activity incompleted' => [ 501 0, COMPLETION_COMPLETE_PASS, '', 'page2', false, true, '~Page1!.*is complete and passed~' 502 ], 503 'Previous complete fail condition with previous no gradable activity incompleted' => [ 504 0, COMPLETION_COMPLETE_FAIL, '', 'page2', false, true, '~Page1!.*is complete and failed~' 505 ], 506 'Previous complete pass condition with previous no gradable activity completed' => [ 507 0, COMPLETION_COMPLETE_PASS, 'page1', 'page2', false, true, '~Page1!.*is complete and passed~' 508 ], 509 'Previous complete fail condition with previous no gradable activity completed' => [ 510 0, COMPLETION_COMPLETE_FAIL, 'page1', 'page2', false, true, '~Page1!.*is complete and failed~' 511 ], 512 // There's an page without completion between page2 ans assign. 513 'Previous complete condition with sibling activity incompleted' => [ 514 0, COMPLETION_COMPLETE, '', 'assign', false, true, '~Page2!.*is marked complete~' 515 ], 516 'Previous incomplete condition with sibling activity incompleted' => [ 517 0, COMPLETION_INCOMPLETE, '', 'assign', true, false, '~Page2!.*is incomplete~' 518 ], 519 'Previous complete condition with sibling activity completed' => [ 520 0, COMPLETION_COMPLETE, 'page2', 'assign', true, false, '~Page2!.*is marked complete~' 521 ], 522 'Previous incomplete condition with sibling activity completed' => [ 523 0, COMPLETION_INCOMPLETE, 'page2', 'assign', false, true, '~Page2!.*is incomplete~' 524 ], 525 // Depending on assign without grade. 526 'Previous complete condition with previous without grade' => [ 527 0, COMPLETION_COMPLETE, '', 'page3', false, true, '~Assign!.*is marked complete~' 528 ], 529 'Previous incomplete condition with previous without grade' => [ 530 0, COMPLETION_INCOMPLETE, '', 'page3', true, false, '~Assign!.*is incomplete~' 531 ], 532 'Previous complete pass condition with previous without grade' => [ 533 0, COMPLETION_COMPLETE_PASS, '', 'page3', false, true, '~Assign!.*is complete and passed~' 534 ], 535 'Previous complete fail condition with previous without grade' => [ 536 0, COMPLETION_COMPLETE_FAIL, '', 'page3', false, true, '~Assign!.*is complete and failed~' 537 ], 538 // Depending on assign with grade. 539 'Previous complete condition with previous fail grade' => [ 540 40, COMPLETION_COMPLETE, '', 'page3', true, false, '~Assign!.*is marked complete~' 541 ], 542 'Previous incomplete condition with previous fail grade' => [ 543 40, COMPLETION_INCOMPLETE, '', 'page3', false, true, '~Assign!.*is incomplete~' 544 ], 545 'Previous complete pass condition with previous fail grade' => [ 546 40, COMPLETION_COMPLETE_PASS, '', 'page3', false, true, '~Assign!.*is complete and passed~' 547 ], 548 'Previous complete fail condition with previous fail grade' => [ 549 40, COMPLETION_COMPLETE_FAIL, '', 'page3', true, false, '~Assign!.*is complete and failed~' 550 ], 551 'Previous complete condition with previous pass grade' => [ 552 60, COMPLETION_COMPLETE, '', 'page3', true, false, '~Assign!.*is marked complete~' 553 ], 554 'Previous incomplete condition with previous pass grade' => [ 555 60, COMPLETION_INCOMPLETE, '', 'page3', false, true, '~Assign!.*is incomplete~' 556 ], 557 'Previous complete pass condition with previous pass grade' => [ 558 60, COMPLETION_COMPLETE_PASS, '', 'page3', true, false, '~Assign!.*is complete and passed~' 559 ], 560 'Previous complete fail condition with previous pass grade' => [ 561 60, COMPLETION_COMPLETE_FAIL, '', 'page3', false, true, '~Assign!.*is complete and failed~' 562 ], 563 ]; 564 } 565 566 /** 567 * Tests the is_available and get_description functions for 568 * previous activity option in course sections. 569 * 570 * @dataProvider test_section_previous_activity_data 571 * @param int $condition condition value 572 * @param bool $mark if Page 1 must be mark as completed 573 * @param string $section section to add the availability 574 * @param bool $result expected result 575 * @param bool $resultnot expected negated result 576 * @param string $description description to match 577 */ 578 public function test_section_previous_activity(int $condition, bool $mark, string $section, 579 bool $result, bool $resultnot, string $description): void { 580 global $CFG, $DB; 581 require_once($CFG->dirroot . '/mod/assign/locallib.php'); 582 $this->resetAfterTest(); 583 584 // Create course with completion turned on. 585 $CFG->enablecompletion = true; 586 $CFG->enableavailability = true; 587 $generator = $this->getDataGenerator(); 588 $course = $generator->create_course( 589 ['numsections' => 4, 'enablecompletion' => 1], 590 ['createsections' => true]); 591 $user = $generator->create_user(); 592 $generator->enrol_user($user->id, $course->id); 593 $this->setUser($user); 594 595 // Section 1 - page1 (manual completion). 596 $page1 = $generator->get_plugin_generator('mod_page')->create_instance( 597 ['course' => $course->id, 'name' => 'Page1!', 'section' => 1, 598 'completion' => COMPLETION_TRACKING_MANUAL]); 599 600 // Section 1 - page ignored 1 (no completion). 601 $pagenocompletion1 = $generator->get_plugin_generator('mod_page')->create_instance( 602 ['course' => $course, 'name' => 'Page ignored!', 'section' => 1]); 603 604 // Section 2 - page ignored 2 (no completion). 605 $pagenocompletion2 = $generator->get_plugin_generator('mod_page')->create_instance( 606 ['course' => $course, 'name' => 'Page ignored!', 'section' => 2]); 607 608 // Section 3 - page2 (manual completion). 609 $page2 = $generator->get_plugin_generator('mod_page')->create_instance( 610 ['course' => $course->id, 'name' => 'Page2!', 'section' => 3, 611 'completion' => COMPLETION_TRACKING_MANUAL]); 612 613 // Section 4 is empty. 614 615 // Get basic details. 616 get_fast_modinfo(0, 0, true); 617 $modinfo = get_fast_modinfo($course); 618 $sections['section1'] = $modinfo->get_section_info(1); 619 $sections['section2'] = $modinfo->get_section_info(2); 620 $sections['section3'] = $modinfo->get_section_info(3); 621 $sections['section4'] = $modinfo->get_section_info(4); 622 $page1cm = $modinfo->get_cm($page1->cmid); 623 $prevvalue = condition::OPTION_PREVIOUS; 624 625 if ($mark) { 626 // Mark page1 complete. 627 $completion = new completion_info($course); 628 $completion->update_state($page1cm, COMPLETION_COMPLETE); 629 } 630 631 $info = new \core_availability\mock_info_section($user->id, $sections[$section]); 632 $cond = new condition((object)[ 633 'cm' => (int)$prevvalue, 'e' => $condition 634 ]); 635 $this->assertEquals($result, $cond->is_available(false, $info, true, $user->id)); 636 $this->assertEquals($resultnot, $cond->is_available(true, $info, true, $user->id)); 637 $information = $cond->get_description(false, false, $info); 638 $information = \core_availability\info::format_info($information, $course); 639 $this->assertRegExp($description, $information); 640 641 } 642 643 public function test_section_previous_activity_data(): array { 644 return [ 645 // Condition, Activity completion, section to test, result, resultnot, description. 646 'Completion complete Section with no previous activity' => [ 647 COMPLETION_COMPLETE, false, 'section1', false, false, '~Missing activity.*is marked complete~' 648 ], 649 'Completion incomplete Section with no previous activity' => [ 650 COMPLETION_INCOMPLETE, false, 'section1', false, false, '~Missing activity.*is incomplete~' 651 ], 652 // Section 2 depending on section 1 -> Page 1 (no grading). 653 'Completion complete Section with previous activity incompleted' => [ 654 COMPLETION_COMPLETE, false, 'section2', false, true, '~Page1!.*is marked complete~' 655 ], 656 'Completion incomplete Section with previous activity incompleted' => [ 657 COMPLETION_INCOMPLETE, false, 'section2', true, false, '~Page1!.*is incomplete~' 658 ], 659 'Completion complete Section with previous activity completed' => [ 660 COMPLETION_COMPLETE, true, 'section2', true, false, '~Page1!.*is marked complete~' 661 ], 662 'Completion incomplete Section with previous activity completed' => [ 663 COMPLETION_INCOMPLETE, true, 'section2', false, true, '~Page1!.*is incomplete~' 664 ], 665 // Section 3 depending on section 1 -> Page 1 (no grading). 666 'Completion complete Section ignoring empty sections and activity incompleted' => [ 667 COMPLETION_COMPLETE, false, 'section3', false, true, '~Page1!.*is marked complete~' 668 ], 669 'Completion incomplete Section ignoring empty sections and activity incompleted' => [ 670 COMPLETION_INCOMPLETE, false, 'section3', true, false, '~Page1!.*is incomplete~' 671 ], 672 'Completion complete Section ignoring empty sections and activity completed' => [ 673 COMPLETION_COMPLETE, true, 'section3', true, false, '~Page1!.*is marked complete~' 674 ], 675 'Completion incomplete Section ignoring empty sections and activity completed' => [ 676 COMPLETION_INCOMPLETE, true, 'section3', false, true, '~Page1!.*is incomplete~' 677 ], 678 // Section 4 depending on section 3 -> Page 2 (no grading). 679 'Completion complete Last section with previous activity incompleted' => [ 680 COMPLETION_COMPLETE, false, 'section4', false, true, '~Page2!.*is marked complete~' 681 ], 682 'Completion incomplete Last section with previous activity incompleted' => [ 683 COMPLETION_INCOMPLETE, false, 'section4', true, false, '~Page2!.*is incomplete~' 684 ], 685 'Completion complete Last section with previous activity completed' => [ 686 COMPLETION_COMPLETE, true, 'section4', false, true, '~Page2!.*is marked complete~' 687 ], 688 'Completion incomplete Last section with previous activity completed' => [ 689 COMPLETION_INCOMPLETE, true, 'section4', true, false, '~Page2!.*is incomplete~' 690 ], 691 ]; 692 } 693 694 /** 695 * Tests completion_value_used static function. 696 */ 697 public function test_completion_value_used() { 698 global $CFG, $DB; 699 $this->resetAfterTest(); 700 $prevvalue = condition::OPTION_PREVIOUS; 701 702 // Create course with completion turned on and some sections. 703 $CFG->enablecompletion = true; 704 $CFG->enableavailability = true; 705 $generator = $this->getDataGenerator(); 706 $course = $generator->create_course( 707 ['numsections' => 1, 'enablecompletion' => 1], 708 ['createsections' => true]); 709 710 // Create six pages with manual completion. 711 $page1 = $generator->get_plugin_generator('mod_page')->create_instance( 712 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 713 $page2 = $generator->get_plugin_generator('mod_page')->create_instance( 714 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 715 $page3 = $generator->get_plugin_generator('mod_page')->create_instance( 716 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 717 $page4 = $generator->get_plugin_generator('mod_page')->create_instance( 718 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 719 $page5 = $generator->get_plugin_generator('mod_page')->create_instance( 720 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 721 $page6 = $generator->get_plugin_generator('mod_page')->create_instance( 722 ['course' => $course->id, 'completion' => COMPLETION_TRACKING_MANUAL]); 723 724 // Set up page3 to depend on page1, and section1 to depend on page2. 725 $DB->set_field('course_modules', 'availability', 726 '{"op":"|","show":true,"c":[' . 727 '{"type":"completion","e":1,"cm":' . $page1->cmid . '}]}', 728 ['id' => $page3->cmid]); 729 $DB->set_field('course_sections', 'availability', 730 '{"op":"|","show":true,"c":[' . 731 '{"type":"completion","e":1,"cm":' . $page2->cmid . '}]}', 732 ['course' => $course->id, 'section' => 1]); 733 // Set up page5 and page6 to depend on previous activity. 734 $DB->set_field('course_modules', 'availability', 735 '{"op":"|","show":true,"c":[' . 736 '{"type":"completion","e":1,"cm":' . $prevvalue . '}]}', 737 ['id' => $page5->cmid]); 738 $DB->set_field('course_modules', 'availability', 739 '{"op":"|","show":true,"c":[' . 740 '{"type":"completion","e":1,"cm":' . $prevvalue . '}]}', 741 ['id' => $page6->cmid]); 742 743 // Check 1: nothing depends on page3 and page6 but something does on the others. 744 $this->assertTrue(availability_completion\condition::completion_value_used( 745 $course, $page1->cmid)); 746 $this->assertTrue(availability_completion\condition::completion_value_used( 747 $course, $page2->cmid)); 748 $this->assertFalse(availability_completion\condition::completion_value_used( 749 $course, $page3->cmid)); 750 $this->assertTrue(availability_completion\condition::completion_value_used( 751 $course, $page4->cmid)); 752 $this->assertTrue(availability_completion\condition::completion_value_used( 753 $course, $page5->cmid)); 754 $this->assertFalse(availability_completion\condition::completion_value_used( 755 $course, $page6->cmid)); 756 } 757 758 /** 759 * Updates the grade of a user in the given assign module instance. 760 * 761 * @param stdClass $assignrow Assignment row from database 762 * @param int $userid User id 763 * @param float $grade Grade 764 */ 765 protected static function set_grade($assignrow, $userid, $grade) { 766 $grades = []; 767 $grades[$userid] = (object)[ 768 'rawgrade' => $grade, 'userid' => $userid 769 ]; 770 $assignrow->cmidnumber = null; 771 assign_grade_item_update($assignrow, $grades); 772 } 773 774 /** 775 * Tests the update_dependency_id() function. 776 */ 777 public function test_update_dependency_id() { 778 $cond = new condition((object)[ 779 'cm' => 42, 'e' => COMPLETION_COMPLETE, 'selfid' => 43 780 ]); 781 $this->assertFalse($cond->update_dependency_id('frogs', 42, 540)); 782 $this->assertFalse($cond->update_dependency_id('course_modules', 12, 34)); 783 $this->assertTrue($cond->update_dependency_id('course_modules', 42, 456)); 784 $after = $cond->save(); 785 $this->assertEquals(456, $after->cm); 786 787 // Test selfid updating. 788 $cond = new condition((object)[ 789 'cm' => 42, 'e' => COMPLETION_COMPLETE 790 ]); 791 $this->assertFalse($cond->update_dependency_id('frogs', 43, 540)); 792 $this->assertFalse($cond->update_dependency_id('course_modules', 12, 34)); 793 $after = $cond->save(); 794 $this->assertEquals(42, $after->cm); 795 796 // Test on previous activity. 797 $cond = new condition((object)[ 798 'cm' => condition::OPTION_PREVIOUS, 799 'e' => COMPLETION_COMPLETE 800 ]); 801 $this->assertFalse($cond->update_dependency_id('frogs', 43, 80)); 802 $this->assertFalse($cond->update_dependency_id('course_modules', 12, 34)); 803 $after = $cond->save(); 804 $this->assertEquals(condition::OPTION_PREVIOUS, $after->cm); 805 } 806 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body