See Release Notes
Long Term Support Release
Differences Between: [Versions 400 and 401] [Versions 401 and 402] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 /** 18 * Course related unit tests 19 * 20 * @package core_course 21 * @copyright 2014 Marina Glancy 22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 23 * @coversDefaultClass \core_courseformat\base 24 */ 25 class base_test extends advanced_testcase { 26 27 /** 28 * Setup to ensure that fixtures are loaded. 29 */ 30 public static function setupBeforeClass(): void { 31 global $CFG; 32 require_once($CFG->dirroot . '/course/lib.php'); 33 require_once($CFG->dirroot . '/course/format/tests/fixtures/format_theunittest.php'); 34 require_once($CFG->dirroot . '/course/format/tests/fixtures/format_theunittest_output_course_format_state.php'); 35 require_once($CFG->dirroot . '/course/format/tests/fixtures/format_theunittest_output_course_format_invalidoutput.php'); 36 } 37 38 /** 39 * Tests the save and load functionality. 40 * 41 * @author Jason den Dulk 42 * @covers \core_courseformat 43 */ 44 public function test_courseformat_saveandload() { 45 $this->resetAfterTest(); 46 47 $courseformatoptiondata = (object) [ 48 "hideoddsections" => 1, 49 'summary_editor' => [ 50 'text' => '<p>Somewhere over the rainbow</p><p>The <b>quick</b> brown fox jumpos over the lazy dog.</p>', 51 'format' => 1 52 ] 53 ]; 54 $generator = $this->getDataGenerator(); 55 $course1 = $generator->create_course(array('format' => 'theunittest')); 56 $this->assertEquals('theunittest', $course1->format); 57 course_create_sections_if_missing($course1, array(0, 1)); 58 59 $courseformat = course_get_format($course1); 60 $courseformat->update_course_format_options($courseformatoptiondata); 61 62 $savedcourseformatoptiondata = $courseformat->get_format_options(); 63 64 $this->assertEqualsCanonicalizing($courseformatoptiondata, (object) $savedcourseformatoptiondata); 65 } 66 67 public function test_available_hook() { 68 global $DB; 69 $this->resetAfterTest(); 70 71 // Generate a course with two sections (0 and 1) and two modules. Course format is set to 'theunittest'. 72 $generator = $this->getDataGenerator(); 73 $course1 = $generator->create_course(array('format' => 'theunittest')); 74 $this->assertEquals('theunittest', $course1->format); 75 course_create_sections_if_missing($course1, array(0, 1)); 76 $assign0 = $generator->create_module('assign', array('course' => $course1, 'section' => 0)); 77 $assign1 = $generator->create_module('assign', array('course' => $course1, 'section' => 1)); 78 $assign2 = $generator->create_module('assign', array('course' => $course1, 'section' => 0, 'visible' => 0)); 79 80 // Create a courseoverview role based on the student role. 81 $roleattr = array('name' => 'courseoverview', 'shortname' => 'courseoverview', 'archetype' => 'student'); 82 $generator->create_role($roleattr); 83 84 // Create user student, editingteacher, teacher and courseoverview. 85 $student = $generator->create_user(); 86 $teacher = $generator->create_user(); 87 $editingteacher = $generator->create_user(); 88 $courseoverviewuser = $generator->create_user(); 89 90 // Enrol users into their roles. 91 $roleids = $DB->get_records_menu('role', null, '', 'shortname, id'); 92 $generator->enrol_user($student->id, $course1->id, $roleids['student']); 93 $generator->enrol_user($teacher->id, $course1->id, $roleids['teacher']); 94 $generator->enrol_user($editingteacher->id, $course1->id, $roleids['editingteacher']); 95 $generator->enrol_user($courseoverviewuser->id, $course1->id, $roleids['courseoverview']); 96 97 // Remove the ignoreavailabilityrestrictions from the teacher role. 98 role_change_permission($roleids['teacher'], context_system::instance(0), 99 'moodle/course:ignoreavailabilityrestrictions', CAP_PREVENT); 100 101 // Allow the courseoverview role to ingore available restriction. 102 role_change_permission($roleids['courseoverview'], context_system::instance(0), 103 'moodle/course:ignoreavailabilityrestrictions', CAP_ALLOW); 104 105 // Make sure that initially both sections and both modules are available and visible for a student. 106 $modinfostudent = get_fast_modinfo($course1, $student->id); 107 $this->assertTrue($modinfostudent->get_section_info(1)->available); 108 $this->assertTrue($modinfostudent->get_cm($assign0->cmid)->available); 109 $this->assertTrue($modinfostudent->get_cm($assign0->cmid)->uservisible); 110 $this->assertTrue($modinfostudent->get_cm($assign1->cmid)->available); 111 $this->assertTrue($modinfostudent->get_cm($assign1->cmid)->uservisible); 112 $this->assertFalse($modinfostudent->get_cm($assign2->cmid)->uservisible); 113 114 // Set 'hideoddsections' for the course to 1. 115 // Section1 and assign1 will be unavailable, uservisible will be false for student and true for teacher. 116 $data = (object)array('id' => $course1->id, 'hideoddsections' => 1); 117 course_get_format($course1)->update_course_format_options($data); 118 $modinfostudent = get_fast_modinfo($course1, $student->id); 119 $this->assertFalse($modinfostudent->get_section_info(1)->available); 120 $this->assertEmpty($modinfostudent->get_section_info(1)->availableinfo); 121 $this->assertFalse($modinfostudent->get_section_info(1)->uservisible); 122 $this->assertTrue($modinfostudent->get_cm($assign0->cmid)->available); 123 $this->assertTrue($modinfostudent->get_cm($assign0->cmid)->uservisible); 124 $this->assertFalse($modinfostudent->get_cm($assign1->cmid)->available); 125 $this->assertFalse($modinfostudent->get_cm($assign1->cmid)->uservisible); 126 $this->assertFalse($modinfostudent->get_cm($assign2->cmid)->uservisible); 127 128 $modinfoteacher = get_fast_modinfo($course1, $teacher->id); 129 $this->assertFalse($modinfoteacher->get_section_info(1)->available); 130 $this->assertEmpty($modinfoteacher->get_section_info(1)->availableinfo); 131 $this->assertFalse($modinfoteacher->get_section_info(1)->uservisible); 132 $this->assertTrue($modinfoteacher->get_cm($assign0->cmid)->available); 133 $this->assertTrue($modinfoteacher->get_cm($assign0->cmid)->uservisible); 134 $this->assertFalse($modinfoteacher->get_cm($assign1->cmid)->available); 135 $this->assertFalse($modinfoteacher->get_cm($assign1->cmid)->uservisible); 136 $this->assertTrue($modinfoteacher->get_cm($assign2->cmid)->available); 137 $this->assertTrue($modinfoteacher->get_cm($assign2->cmid)->uservisible); 138 139 $modinfoteacher = get_fast_modinfo($course1, $editingteacher->id); 140 $this->assertFalse($modinfoteacher->get_section_info(1)->available); 141 $this->assertEmpty($modinfoteacher->get_section_info(1)->availableinfo); 142 $this->assertTrue($modinfoteacher->get_section_info(1)->uservisible); 143 $this->assertTrue($modinfoteacher->get_cm($assign0->cmid)->available); 144 $this->assertTrue($modinfoteacher->get_cm($assign0->cmid)->uservisible); 145 $this->assertFalse($modinfoteacher->get_cm($assign1->cmid)->available); 146 $this->assertTrue($modinfoteacher->get_cm($assign1->cmid)->uservisible); 147 $this->assertTrue($modinfoteacher->get_cm($assign2->cmid)->uservisible); 148 149 $modinfocourseoverview = get_fast_modinfo($course1, $courseoverviewuser->id); 150 $this->assertFalse($modinfocourseoverview->get_section_info(1)->available); 151 $this->assertEmpty($modinfocourseoverview->get_section_info(1)->availableinfo); 152 $this->assertTrue($modinfocourseoverview->get_section_info(1)->uservisible); 153 $this->assertTrue($modinfocourseoverview->get_cm($assign0->cmid)->available); 154 $this->assertTrue($modinfocourseoverview->get_cm($assign0->cmid)->uservisible); 155 $this->assertFalse($modinfocourseoverview->get_cm($assign1->cmid)->available); 156 $this->assertTrue($modinfocourseoverview->get_cm($assign1->cmid)->uservisible); 157 $this->assertFalse($modinfocourseoverview->get_cm($assign2->cmid)->uservisible); 158 159 // Set 'hideoddsections' for the course to 2. 160 // Section1 and assign1 will be unavailable, uservisible will be false for student and true for teacher. 161 // Property availableinfo will be not empty. 162 $data = (object)array('id' => $course1->id, 'hideoddsections' => 2); 163 course_get_format($course1)->update_course_format_options($data); 164 $modinfostudent = get_fast_modinfo($course1, $student->id); 165 $this->assertFalse($modinfostudent->get_section_info(1)->available); 166 $this->assertNotEmpty($modinfostudent->get_section_info(1)->availableinfo); 167 $this->assertFalse($modinfostudent->get_section_info(1)->uservisible); 168 $this->assertTrue($modinfostudent->get_cm($assign0->cmid)->available); 169 $this->assertTrue($modinfostudent->get_cm($assign0->cmid)->uservisible); 170 $this->assertFalse($modinfostudent->get_cm($assign1->cmid)->available); 171 $this->assertFalse($modinfostudent->get_cm($assign1->cmid)->uservisible); 172 173 $modinfoteacher = get_fast_modinfo($course1, $editingteacher->id); 174 $this->assertFalse($modinfoteacher->get_section_info(1)->available); 175 $this->assertNotEmpty($modinfoteacher->get_section_info(1)->availableinfo); 176 $this->assertTrue($modinfoteacher->get_section_info(1)->uservisible); 177 $this->assertTrue($modinfoteacher->get_cm($assign0->cmid)->available); 178 $this->assertTrue($modinfoteacher->get_cm($assign0->cmid)->uservisible); 179 $this->assertFalse($modinfoteacher->get_cm($assign1->cmid)->available); 180 $this->assertTrue($modinfoteacher->get_cm($assign1->cmid)->uservisible); 181 } 182 183 /** 184 * Test for supports_news() with a course format plugin that doesn't define 'news_items' in default blocks. 185 */ 186 public function test_supports_news() { 187 $this->resetAfterTest(); 188 $format = course_get_format((object)['format' => 'testformat']); 189 $this->assertFalse($format->supports_news()); 190 } 191 192 /** 193 * Test for supports_news() for old course format plugins that defines 'news_items' in default blocks. 194 */ 195 public function test_supports_news_legacy() { 196 $this->resetAfterTest(); 197 $format = course_get_format((object)['format' => 'testlegacy']); 198 $this->assertTrue($format->supports_news()); 199 } 200 201 /** 202 * Test for get_view_url() to ensure that the url is only given for the correct cases 203 */ 204 public function test_get_view_url() { 205 global $CFG; 206 $this->resetAfterTest(); 207 208 $linkcoursesections = $CFG->linkcoursesections; 209 210 // Generate a course with two sections (0 and 1) and two modules. Course format is set to 'testformat'. 211 // This will allow us to test the default implementation of get_view_url. 212 $generator = $this->getDataGenerator(); 213 $course1 = $generator->create_course(array('format' => 'testformat')); 214 course_create_sections_if_missing($course1, array(0, 1)); 215 216 $data = (object)['id' => $course1->id]; 217 $format = course_get_format($course1); 218 $format->update_course_format_options($data); 219 220 // In page. 221 $CFG->linkcoursesections = 0; 222 $this->assertNotEmpty($format->get_view_url(null)); 223 $this->assertNotEmpty($format->get_view_url(0)); 224 $this->assertNotEmpty($format->get_view_url(1)); 225 $CFG->linkcoursesections = 1; 226 $this->assertNotEmpty($format->get_view_url(null)); 227 $this->assertNotEmpty($format->get_view_url(0)); 228 $this->assertNotEmpty($format->get_view_url(1)); 229 230 // Navigation. 231 $CFG->linkcoursesections = 0; 232 $this->assertNull($format->get_view_url(1, ['navigation' => 1])); 233 $this->assertNull($format->get_view_url(0, ['navigation' => 1])); 234 $CFG->linkcoursesections = 1; 235 $this->assertNotEmpty($format->get_view_url(1, ['navigation' => 1])); 236 $this->assertNotEmpty($format->get_view_url(0, ['navigation' => 1])); 237 } 238 239 /** 240 * Test for get_output_classname method. 241 * 242 * @dataProvider get_output_classname_provider 243 * @param string $find the class to find 244 * @param string $result the expected result classname 245 * @param bool $exception if the method will raise an exception 246 */ 247 public function test_get_output_classname($find, $result, $exception) { 248 $this->resetAfterTest(); 249 250 $course = $this->getDataGenerator()->create_course(['format' => 'theunittest']); 251 $courseformat = course_get_format($course); 252 253 if ($exception) { 254 $this->expectException(coding_exception::class); 255 } 256 257 $courseclass = $courseformat->get_output_classname($find); 258 $this->assertEquals($result, $courseclass); 259 } 260 261 /** 262 * Data provider for test_get_output_classname. 263 * 264 * @return array the testing scenarios 265 */ 266 public function get_output_classname_provider(): array { 267 return [ 268 'overridden class' => [ 269 'find' => 'state\\course', 270 'result' => 'format_theunittest\\output\\courseformat\\state\\course', 271 'exception' => false, 272 ], 273 'original class' => [ 274 'find' => 'state\\section', 275 'result' => 'core_courseformat\\output\\local\\state\\section', 276 'exception' => false, 277 ], 278 'invalid overridden class' => [ 279 'find' => 'state\\invalidoutput', 280 'result' => '', 281 'exception' => true, 282 ], 283 ]; 284 } 285 286 /** 287 * Test for the default delete format data behaviour. 288 * 289 * @covers ::get_sections_preferences 290 */ 291 public function test_get_sections_preferences() { 292 $this->resetAfterTest(); 293 $generator = $this->getDataGenerator(); 294 $course = $generator->create_course(); 295 $user = $generator->create_and_enrol($course, 'student'); 296 297 // Create fake preferences generated by the frontend js module. 298 $data = (object)[ 299 'pref1' => [1,2], 300 'pref2' => [1], 301 ]; 302 set_user_preference('coursesectionspreferences_' . $course->id, json_encode($data), $user->id); 303 304 $format = course_get_format($course); 305 306 // Load data from user 1. 307 $this->setUser($user); 308 $preferences = $format->get_sections_preferences(); 309 310 $this->assertEquals( 311 (object)['pref1' => true, 'pref2' => true], 312 $preferences[1] 313 ); 314 $this->assertEquals( 315 (object)['pref1' => true], 316 $preferences[2] 317 ); 318 } 319 320 /** 321 * Test for the default delete format data behaviour. 322 * 323 * @covers ::set_sections_preference 324 */ 325 public function test_set_sections_preference() { 326 $this->resetAfterTest(); 327 $generator = $this->getDataGenerator(); 328 $course = $generator->create_course(); 329 $user = $generator->create_and_enrol($course, 'student'); 330 331 $format = course_get_format($course); 332 $this->setUser($user); 333 334 // Load data from user 1. 335 $format->set_sections_preference('pref1', [1, 2]); 336 $format->set_sections_preference('pref2', [1]); 337 $format->set_sections_preference('pref3', []); 338 339 $preferences = $format->get_sections_preferences(); 340 $this->assertEquals( 341 (object)['pref1' => true, 'pref2' => true], 342 $preferences[1] 343 ); 344 $this->assertEquals( 345 (object)['pref1' => true], 346 $preferences[2] 347 ); 348 } 349 350 /** 351 * Test that retrieving last section number for a course 352 * 353 * @covers ::get_last_section_number 354 */ 355 public function test_get_last_section_number(): void { 356 global $DB; 357 358 $this->resetAfterTest(); 359 360 // Course with two additional sections. 361 $courseone = $this->getDataGenerator()->create_course(['numsections' => 2]); 362 $this->assertEquals(2, course_get_format($courseone)->get_last_section_number()); 363 364 // Course without additional sections, section zero is the "default" section that always exists. 365 $coursetwo = $this->getDataGenerator()->create_course(['numsections' => 0]); 366 $this->assertEquals(0, course_get_format($coursetwo)->get_last_section_number()); 367 368 // Course without additional sections, manually remove section zero, as "course_delete_section" prevents that. This 369 // simulates course data integrity issues that previously triggered errors. 370 $coursethree = $this->getDataGenerator()->create_course(['numsections' => 0]); 371 $DB->delete_records('course_sections', ['course' => $coursethree->id, 'section' => 0]); 372 373 $this->assertEquals(-1, course_get_format($coursethree)->get_last_section_number()); 374 } 375 376 /** 377 * Test for the default delete format data behaviour. 378 * 379 * @covers ::delete_format_data 380 * @dataProvider delete_format_data_provider 381 * @param bool $usehook if it should use course_delete to trigger $format->delete_format_data as a hook 382 */ 383 public function test_delete_format_data(bool $usehook) { 384 global $DB; 385 386 $this->resetAfterTest(); 387 388 $generator = $this->getDataGenerator(); 389 $course = $generator->create_course(); 390 course_create_sections_if_missing($course, [0, 1]); 391 $user = $generator->create_and_enrol($course, 'student'); 392 393 // Create a coursesectionspreferences_XX preference. 394 $key = 'coursesectionspreferences_' . $course->id; 395 $fakevalue = 'No dark sarcasm in the classroom'; 396 set_user_preference($key, $fakevalue, $user->id); 397 $this->assertEquals( 398 $fakevalue, 399 $DB->get_field('user_preferences', 'value', ['name' => $key, 'userid' => $user->id]) 400 ); 401 402 // Create another random user preference. 403 $key2 = 'somepreference'; 404 $fakevalue2 = "All in all it's just another brick in the wall"; 405 set_user_preference($key2, $fakevalue2, $user->id); 406 $this->assertEquals( 407 $fakevalue2, 408 $DB->get_field('user_preferences', 'value', ['name' => $key2, 'userid' => $user->id]) 409 ); 410 411 if ($usehook) { 412 delete_course($course, false); 413 } else { 414 $format = course_get_format($course); 415 $format->delete_format_data(); 416 } 417 418 // Check which the preferences exists. 419 $this->assertFalse( 420 $DB->record_exists('user_preferences', ['name' => $key, 'userid' => $user->id]) 421 ); 422 set_user_preference($key2, $fakevalue2, $user->id); 423 $this->assertEquals( 424 $fakevalue2, 425 $DB->get_field('user_preferences', 'value', ['name' => $key2, 'userid' => $user->id]) 426 ); 427 } 428 429 /** 430 * Data provider for test_delete_format_data. 431 * 432 * @return array the testing scenarios 433 */ 434 public function delete_format_data_provider(): array { 435 return [ 436 'direct call' => [ 437 'usehook' => false 438 ], 439 'use hook' => [ 440 'usehook' => true, 441 ] 442 ]; 443 } 444 } 445 446 /** 447 * Class format_testformat. 448 * 449 * A test class that simulates a course format that doesn't define 'news_items' in default blocks. 450 * 451 * @copyright 2016 Jun Pataleta <jun@moodle.com> 452 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 453 */ 454 class format_testformat extends core_courseformat\base { 455 /** 456 * Returns the list of blocks to be automatically added for the newly created course. 457 * 458 * @return array 459 */ 460 public function get_default_blocks() { 461 return [ 462 BLOCK_POS_RIGHT => [], 463 BLOCK_POS_LEFT => [] 464 ]; 465 } 466 } 467 468 /** 469 * Class format_testlegacy. 470 * 471 * A test class that simulates old course formats that define 'news_items' in default blocks. 472 * 473 * @copyright 2016 Jun Pataleta <jun@moodle.com> 474 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 475 */ 476 class format_testlegacy extends core_courseformat\base { 477 /** 478 * Returns the list of blocks to be automatically added for the newly created course. 479 * 480 * @return array 481 */ 482 public function get_default_blocks() { 483 return [ 484 BLOCK_POS_RIGHT => ['news_items'], 485 BLOCK_POS_LEFT => [] 486 ]; 487 } 488 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body