Differences Between: [Versions 310 and 403] [Versions 311 and 403] [Versions 39 and 403] [Versions 400 and 403] [Versions 401 and 403]
1 <?php 2 // This file is part of Moodle - http://moodle.org/ 3 // 4 // Moodle is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // Moodle is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU General Public License for more details. 13 // 14 // You should have received a copy of the GNU General Public License 15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>. 16 17 namespace core; 18 19 defined('MOODLE_INTERNAL') || die(); 20 21 require_once (__DIR__.'/fixtures/lib.php'); 22 23 24 /** 25 * Test grade categories 26 * 27 * @package core 28 * @category test 29 * @copyright nicolas@moodle.com 30 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later 31 */ 32 class grade_category_test extends \grade_base_testcase { 33 34 public function test_grade_category() { 35 $this->sub_test_grade_category_construct(); 36 $this->sub_test_grade_category_build_path(); 37 $this->sub_test_grade_category_fetch(); 38 $this->sub_test_grade_category_fetch_all(); 39 $this->sub_test_grade_category_update(); 40 $this->sub_test_grade_category_delete(); 41 $this->sub_test_grade_category_insert(); 42 $this->sub_test_grade_category_qualifies_for_regrading(); 43 $this->sub_test_grade_category_force_regrading(); 44 $this->sub_test_grade_category_aggregate_grades(); 45 $this->sub_test_grade_category_apply_limit_rules(); 46 $this->sub_test_grade_category_is_aggregationcoef_used(); 47 $this->sub_test_grade_category_aggregation_uses_aggregationcoef(); 48 $this->sub_test_grade_category_fetch_course_tree(); 49 $this->sub_test_grade_category_get_children(); 50 $this->sub_test_grade_category_load_grade_item(); 51 $this->sub_test_grade_category_get_grade_item(); 52 $this->sub_test_grade_category_load_parent_category(); 53 $this->sub_test_grade_category_get_parent_category(); 54 $this->sub_test_grade_category_get_name_escaped(); 55 $this->sub_test_grade_category_get_name_unescaped(); 56 $this->sub_test_grade_category_generate_grades_aggregationweight(); 57 $this->sub_test_grade_category_set_parent(); 58 $this->sub_test_grade_category_get_final(); 59 $this->sub_test_grade_category_get_sortorder(); 60 $this->sub_test_grade_category_set_sortorder(); 61 $this->sub_test_grade_category_is_editable(); 62 $this->sub_test_grade_category_move_after_sortorder(); 63 $this->sub_test_grade_category_is_course_category(); 64 $this->sub_test_grade_category_fetch_course_category(); 65 $this->sub_test_grade_category_is_locked(); 66 $this->sub_test_grade_category_set_locked(); 67 $this->sub_test_grade_category_is_hidden(); 68 $this->sub_test_grade_category_set_hidden(); 69 $this->sub_test_grade_category_can_control_visibility(); 70 $this->sub_test_grade_category_total_visibility(); 71 72 // This won't work until MDL-11837 is complete. 73 // $this->sub_test_grade_category_generate_grades(); 74 75 // Do this last as adding a second course category messes up the data. 76 $this->sub_test_grade_category_insert_course_category(); 77 $this->sub_test_grade_category_is_extracredit_used(); 78 $this->sub_test_grade_category_aggregation_uses_extracredit(); 79 } 80 81 // Adds 3 new grade categories at various depths. 82 protected function sub_test_grade_category_construct() { 83 $course_category = \grade_category::fetch_course_category($this->courseid); 84 85 $params = new \stdClass(); 86 87 $params->courseid = $this->courseid; 88 $params->fullname = 'unittestcategory4'; 89 90 $grade_category = new \grade_category($params, false); 91 $grade_category->insert(); 92 $this->grade_categories[] = $grade_category; 93 94 $this->assertEquals($params->courseid, $grade_category->courseid); 95 $this->assertEquals($params->fullname, $grade_category->fullname); 96 $this->assertEquals(2, $grade_category->depth); 97 $this->assertEquals("/$course_category->id/$grade_category->id/", $grade_category->path); 98 $parentpath = $grade_category->path; 99 100 // Test a child category. 101 $params->parent = $grade_category->id; 102 $params->fullname = 'unittestcategory5'; 103 $grade_category = new \grade_category($params, false); 104 $grade_category->insert(); 105 $this->grade_categories[] = $grade_category; 106 107 $this->assertEquals(3, $grade_category->depth); 108 $this->assertEquals($parentpath.$grade_category->id."/", $grade_category->path); 109 $parentpath = $grade_category->path; 110 111 // Test a third depth category. 112 $params->parent = $grade_category->id; 113 $params->fullname = 'unittestcategory6'; 114 $grade_category = new \grade_category($params, false); 115 $grade_category->insert(); 116 $this->grade_categories[50] = $grade_category;// Going to delete this one later hence the special index. 117 118 $this->assertEquals(4, $grade_category->depth); 119 $this->assertEquals($parentpath.$grade_category->id."/", $grade_category->path); 120 } 121 122 protected function sub_test_grade_category_build_path() { 123 $grade_category = new \grade_category($this->grade_categories[1]); 124 $this->assertTrue(method_exists($grade_category, 'build_path')); 125 $path = \grade_category::build_path($grade_category); 126 $this->assertEquals($grade_category->path, $path); 127 } 128 129 protected function sub_test_grade_category_fetch() { 130 $grade_category = new \grade_category(); 131 $this->assertTrue(method_exists($grade_category, 'fetch')); 132 133 $grade_category = \grade_category::fetch(array('id'=>$this->grade_categories[0]->id)); 134 $this->assertEquals($this->grade_categories[0]->id, $grade_category->id); 135 $this->assertEquals($this->grade_categories[0]->fullname, $grade_category->fullname); 136 } 137 138 protected function sub_test_grade_category_fetch_all() { 139 $grade_category = new \grade_category(); 140 $this->assertTrue(method_exists($grade_category, 'fetch_all')); 141 142 $grade_categories = \grade_category::fetch_all(array('courseid'=>$this->courseid)); 143 $this->assertEquals(count($this->grade_categories), count($grade_categories)-1); 144 } 145 146 protected function sub_test_grade_category_update() { 147 global $DB; 148 $grade_category = new \grade_category($this->grade_categories[0]); 149 $this->assertTrue(method_exists($grade_category, 'update')); 150 151 $grade_category->fullname = 'Updated info for this unittest grade_category'; 152 $grade_category->path = null; // Path must be recalculated if missing. 153 $grade_category->depth = null; 154 $grade_category->aggregation = GRADE_AGGREGATE_MAX; // Should force regrading. 155 156 $grade_item = $grade_category->get_grade_item(); 157 $this->assertEquals(0, $grade_item->needsupdate); 158 159 $this->assertTrue($grade_category->update()); 160 161 $fullname = $DB->get_field('grade_categories', 'fullname', array('id' => $this->grade_categories[0]->id)); 162 $this->assertEquals($grade_category->fullname, $fullname); 163 164 $path = $DB->get_field('grade_categories', 'path', array('id' => $this->grade_categories[0]->id)); 165 $this->assertEquals($grade_category->path, $path); 166 167 $depth = $DB->get_field('grade_categories', 'depth', array('id' => $this->grade_categories[0]->id)); 168 $this->assertEquals($grade_category->depth, $depth); 169 170 $grade_item = $grade_category->get_grade_item(); 171 $this->assertEquals(1, $grade_item->needsupdate); 172 } 173 174 protected function sub_test_grade_category_delete() { 175 global $DB; 176 177 $grade_category = new \grade_category($this->grade_categories[50]); 178 $this->assertTrue(method_exists($grade_category, 'delete')); 179 180 $this->assertTrue($grade_category->delete()); 181 $this->assertFalse($DB->get_record('grade_categories', array('id' => $grade_category->id))); 182 } 183 184 protected function sub_test_grade_category_insert() { 185 $course_category = \grade_category::fetch_course_category($this->courseid); 186 187 $grade_category = new \grade_category(); 188 $this->assertTrue(method_exists($grade_category, 'insert')); 189 190 $grade_category->fullname = 'unittestcategory4'; 191 $grade_category->courseid = $this->courseid; 192 $grade_category->aggregation = GRADE_AGGREGATE_MEAN; 193 $grade_category->aggregateonlygraded = 1; 194 $grade_category->keephigh = 100; 195 $grade_category->droplow = 10; 196 $grade_category->hidden = 0; 197 $grade_category->parent = $this->grade_categories[1]->id; // sub_test_grade_category_delete() removed the category at 0. 198 199 $grade_category->insert(); 200 201 $this->assertEquals('/'.$course_category->id.'/'.$this->grade_categories[1]->parent.'/'.$this->grade_categories[1]->id.'/'.$grade_category->id.'/', $grade_category->path); 202 $this->assertEquals(4, $grade_category->depth); 203 204 $last_grade_category = end($this->grade_categories); 205 206 $this->assertFalse(empty($grade_category->grade_item)); 207 $this->assertEquals($grade_category->id, $grade_category->grade_item->iteminstance); 208 $this->assertEquals('category', $grade_category->grade_item->itemtype); 209 210 $this->assertEquals($grade_category->id, $last_grade_category->id + 1); 211 $this->assertFalse(empty($grade_category->timecreated)); 212 $this->assertFalse(empty($grade_category->timemodified)); 213 } 214 215 protected function sub_test_grade_category_qualifies_for_regrading() { 216 $grade_category = new \grade_category($this->grade_categories[1]); 217 $this->assertTrue(method_exists($grade_category, 'qualifies_for_regrading')); 218 $this->assertFalse($grade_category->qualifies_for_regrading()); 219 220 $grade_category->aggregation = GRADE_AGGREGATE_MAX; 221 $this->assertTrue($grade_category->qualifies_for_regrading()); 222 223 $grade_category = new \grade_category($this->grade_categories[1]); 224 $grade_category->droplow = 99; 225 $this->assertTrue($grade_category->qualifies_for_regrading()); 226 227 $grade_category = new \grade_category($this->grade_categories[1]); 228 $grade_category->keephigh = 99; 229 $this->assertTrue($grade_category->qualifies_for_regrading()); 230 } 231 232 protected function sub_test_grade_category_force_regrading() { 233 $grade_category = new \grade_category($this->grade_categories[1]); 234 $this->assertTrue(method_exists($grade_category, 'force_regrading')); 235 236 $grade_category->load_grade_item(); 237 $this->assertEquals(0, $grade_category->grade_item->needsupdate); 238 239 $grade_category->force_regrading(); 240 241 $grade_category->grade_item = null; 242 $grade_category->load_grade_item(); 243 244 $this->assertEquals(1, $grade_category->grade_item->needsupdate); 245 } 246 247 /** 248 * Tests the setting of the grade_grades aggregationweight column. 249 * Currently, this is only a regression test for MDL-51715. 250 * This must be run before sub_test_grade_category_set_parent(), which alters 251 * the fixture. 252 */ 253 protected function sub_test_grade_category_generate_grades_aggregationweight() { 254 global $DB; 255 256 // Start of regression test for MDL-51715. 257 // grade_categories [1] and [2] are child categories of [0] 258 // Ensure that grades have been generated with fixture data. 259 $childcat1 = new \grade_category($this->grade_categories[1]); 260 $childcat1itemid = $childcat1->load_grade_item()->id; 261 $childcat1->generate_grades(); 262 $childcat2 = new \grade_category($this->grade_categories[2]); 263 $childcat2itemid = $childcat2->load_grade_item()->id; 264 $childcat2->generate_grades(); 265 $parentcat = new \grade_category($this->grade_categories[0]); 266 $parentcat->generate_grades(); 267 268 // Drop low and and re-generate to produce 'dropped' aggregation status. 269 $parentcat->droplow = 1; 270 $parentcat->generate_grades(); 271 272 $this->assertTrue($DB->record_exists_select( 273 'grade_grades', 274 "aggregationstatus='dropped' and itemid in (?,?)", 275 array($childcat1itemid, $childcat2itemid))); 276 $this->assertFalse($DB->record_exists_select( 277 'grade_grades', 278 "aggregationstatus='dropped' and aggregationweight > 0.00"), 279 "aggregationweight should be 0.00 if aggregationstatus=='dropped'"); 280 281 // Reset grade data to be consistent with fixture data. 282 $parentcat->droplow = 0; 283 $parentcat->generate_grades(); 284 285 // Blank out the final grade for one of the child categories and re-generate 286 // to produce 'novalue' aggregationstatus. Direct DB update is testing shortcut. 287 $DB->set_field('grade_grades', 'finalgrade', null, array('itemid'=>$childcat1itemid)); 288 $parentcat->generate_grades(); 289 290 $this->assertFalse($DB->record_exists_select( 291 'grade_grades', 292 "aggregationstatus='dropped' and itemid in (?,?)", 293 array($childcat1itemid, $childcat2itemid))); 294 $this->assertTrue($DB->record_exists_select( 295 'grade_grades', 296 "aggregationstatus='novalue' and itemid = ?", 297 array($childcat1itemid))); 298 $this->assertFalse($DB->record_exists_select( 299 'grade_grades', 300 "aggregationstatus='novalue' and aggregationweight > 0.00"), 301 "aggregationweight should be 0.00 if aggregationstatus=='novalue'"); 302 303 // Re-generate to be consistent with fixture data. 304 $childcat1->generate_grades(); 305 $parentcat->generate_grades(); 306 // End of regression test for MDL-51715. 307 } 308 309 /** 310 * Tests the calculation of grades using the various aggregation methods with and without hidden grades 311 * This will not work entirely until MDL-11837 is done 312 */ 313 protected function sub_test_grade_category_generate_grades() { 314 global $DB; 315 316 // Inserting some special grade items to make testing the final grade calculation easier. 317 $params = new \stdClass(); 318 $params->courseid = $this->courseid; 319 $params->fullname = 'unittestgradecalccategory'; 320 $params->aggregation = GRADE_AGGREGATE_MEAN; 321 $params->aggregateonlygraded = 0; 322 $grade_category = new \grade_category($params, false); 323 $grade_category->insert(); 324 325 $this->assertTrue(method_exists($grade_category, 'generate_grades')); 326 327 $grade_category->load_grade_item(); 328 $cgi = $grade_category->get_grade_item(); 329 $cgi->grademin = 0; 330 $cgi->grademax = 20; // 3 grade items out of 10 but category is out of 20 to force scaling to occur. 331 $cgi->update(); 332 333 // 3 grade items each with a maximum grade of 10. 334 $grade_items = array(); 335 for ($i=0; $i<3; $i++) { 336 $grade_items[$i] = new \grade_item(); 337 $grade_items[$i]->courseid = $this->courseid; 338 $grade_items[$i]->categoryid = $grade_category->id; 339 $grade_items[$i]->itemname = 'manual grade_item '.$i; 340 $grade_items[$i]->itemtype = 'manual'; 341 $grade_items[$i]->itemnumber = 0; 342 $grade_items[$i]->needsupdate = false; 343 $grade_items[$i]->gradetype = GRADE_TYPE_VALUE; 344 $grade_items[$i]->grademin = 0; 345 $grade_items[$i]->grademax = 10; 346 $grade_items[$i]->iteminfo = 'Manual grade item used for unit testing'; 347 $grade_items[$i]->timecreated = time(); 348 $grade_items[$i]->timemodified = time(); 349 350 // Used as the weight by weighted mean and as extra credit by mean with extra credit. 351 // Will be 0, 1 and 2. 352 $grade_items[$i]->aggregationcoef = $i; 353 354 $grade_items[$i]->insert(); 355 } 356 357 // A grade for each grade item. 358 $grade_grades = array(); 359 for ($i=0; $i<3; $i++) { 360 $grade_grades[$i] = new \grade_grade(); 361 $grade_grades[$i]->itemid = $grade_items[$i]->id; 362 $grade_grades[$i]->userid = $this->userid; 363 $grade_grades[$i]->rawgrade = ($i+1)*2; // Produce grade grades of 2, 4 and 6. 364 $grade_grades[$i]->finalgrade = ($i+1)*2; 365 $grade_grades[$i]->timecreated = time(); 366 $grade_grades[$i]->timemodified = time(); 367 $grade_grades[$i]->information = '1 of 2 grade_grades'; 368 $grade_grades[$i]->informationformat = FORMAT_PLAIN; 369 $grade_grades[$i]->feedback = 'Good, but not good enough..'; 370 $grade_grades[$i]->feedbackformat = FORMAT_PLAIN; 371 372 $grade_grades[$i]->insert(); 373 } 374 375 // 3 grade items with 1 grade_grade each. 376 // grade grades have the values 2, 4 and 6. 377 378 // First correct answer is the aggregate with all 3 grades. 379 // Second correct answer is with the first grade (value 2) hidden. 380 381 $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MEDIAN, 'GRADE_AGGREGATE_MEDIAN', 8, 8); 382 $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MAX, 'GRADE_AGGREGATE_MAX', 12, 12); 383 $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MODE, 'GRADE_AGGREGATE_MODE', 12, 12); 384 385 // Weighted mean. note grade totals are rounded to an int to prevent rounding discrepancies. correct final grade isnt actually exactly 10 386 // 3 items with grades 2, 4 and 6 with weights 0, 1 and 2 and all out of 10. then doubled to be out of 20. 387 $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_WEIGHTED_MEAN, 'GRADE_AGGREGATE_WEIGHTED_MEAN', 10, 10); 388 389 // Simple weighted mean. 390 // 3 items with grades 2, 4 and 6 equally weighted and all out of 10. then doubled to be out of 20. 391 $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_WEIGHTED_MEAN2, 'GRADE_AGGREGATE_WEIGHTED_MEAN2', 8, 10); 392 393 // Mean of grades with extra credit. 394 // 3 items with grades 2, 4 and 6 with extra credit 0, 1 and 2 equally weighted and all out of 10. then doubled to be out of 20. 395 $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_EXTRACREDIT_MEAN, 'GRADE_AGGREGATE_EXTRACREDIT_MEAN', 10, 13); 396 397 // Aggregation tests the are affected by a hidden grade currently dont work as we dont store the altered grade in the database 398 // instead an in memory recalculation is done. This should be remedied by MDL-11837. 399 400 // Fails with 1 grade hidden. still reports 8 as being correct. 401 $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MEAN, 'GRADE_AGGREGATE_MEAN', 8, 10); 402 403 // Fails with 1 grade hidden. still reports 4 as being correct. 404 $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_MIN, 'GRADE_AGGREGATE_MIN', 4, 8); 405 406 // Fails with 1 grade hidden. still reports 12 as being correct. 407 $this->helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, GRADE_AGGREGATE_SUM, 'GRADE_AGGREGATE_SUM', 12, 10); 408 } 409 410 /** 411 * Test grade category aggregation using the supplied grade objects and aggregation method 412 * @param \grade_category $grade_category the category to be tested 413 * @param array $grade_items array of instance of grade_item 414 * @param array $grade_grades array of instances of grade_grade 415 * @param int $aggmethod the aggregation method to apply ie GRADE_AGGREGATE_MEAN 416 * @param string $aggmethodname the name of the aggregation method to apply. Used to display any test failure messages 417 * @param int $correct1 the correct final grade for the category with NO items hidden 418 * @param int $correct2 the correct final grade for the category with the grade at $grade_grades[0] hidden 419 * @return void 420 */ 421 protected function helper_test_grade_agg_method($grade_category, $grade_items, $grade_grades, $aggmethod, $aggmethodname, $correct1, $correct2) { 422 $grade_category->aggregation = $aggmethod; 423 $grade_category->update(); 424 425 // Check grade_item isnt hidden from a previous test. 426 $grade_items[0]->set_hidden(0, true); 427 $this->helper_test_grade_aggregation_result($grade_category, $correct1, 'Testing aggregation method('.$aggmethodname.') with no items hidden %s'); 428 429 // Hide the grade item with grade of 2. 430 $grade_items[0]->set_hidden(1, true); 431 $this->helper_test_grade_aggregation_result($grade_category, $correct2, 'Testing aggregation method('.$aggmethodname.') with 1 item hidden %s'); 432 } 433 434 /** 435 * Verify the value of the category grade item for $this->userid 436 * @param \grade_category $grade_category the category to be tested 437 * @param int $correctgrade the expected grade 438 * @param string $msg The message that should be displayed if the correct grade is not found 439 * @return void 440 */ 441 protected function helper_test_grade_aggregation_result($grade_category, $correctgrade, $msg) { 442 global $DB; 443 444 $category_grade_item = $grade_category->get_grade_item(); 445 446 // This creates all the grade_grades we need. 447 grade_regrade_final_grades($this->courseid); 448 449 $grade = $DB->get_record('grade_grades', array('itemid'=>$category_grade_item->id, 'userid'=>$this->userid)); 450 $this->assertWithinMargin($grade->rawgrade, $grade->rawgrademin, $grade->rawgrademax); 451 $this->assertEquals(intval($correctgrade), intval($grade->finalgrade), $msg); 452 453 /* 454 * TODO this doesnt work as the grade_grades created by $grade_category->generate_grades(); dont 455 * observe the category's max grade 456 // delete the grade_grades for the category itself and check they get recreated correctly. 457 $DB->delete_records('grade_grades', array('itemid'=>$category_grade_item->id)); 458 $grade_category->generate_grades(); 459 460 $grade = $DB->get_record('grade_grades', array('itemid'=>$category_grade_item->id, 'userid'=>$this->userid)); 461 $this->assertWithinMargin($grade->rawgrade, $grade->rawgrademin, $grade->rawgrademax); 462 $this->assertEquals(intval($correctgrade), intval($grade->finalgrade), $msg); 463 * 464 */ 465 } 466 467 protected function sub_test_grade_category_aggregate_grades() { 468 $category = new \grade_category($this->grade_categories[0]); 469 $this->assertTrue(method_exists($category, 'aggregate_grades')); 470 // Tested more fully via test_grade_category_generate_grades(). 471 } 472 473 protected function sub_test_grade_category_apply_limit_rules() { 474 $items[$this->grade_items[0]->id] = new \grade_item($this->grade_items[0], false); 475 $items[$this->grade_items[1]->id] = new \grade_item($this->grade_items[1], false); 476 $items[$this->grade_items[2]->id] = new \grade_item($this->grade_items[2], false); 477 $items[$this->grade_items[4]->id] = new \grade_item($this->grade_items[4], false); 478 479 // Test excluding the lowest 2 out of 4 grades from aggregation with no 0 grades. 480 $category = new \grade_category(); 481 $category->droplow = 2; 482 $grades = array($this->grade_items[0]->id=>5.374, 483 $this->grade_items[1]->id=>9.4743, 484 $this->grade_items[2]->id=>2.5474, 485 $this->grade_items[4]->id=>7.3754); 486 $category->apply_limit_rules($grades, $items); 487 $this->assertEquals(count($grades), 2); 488 $this->assertEquals($grades[$this->grade_items[1]->id], 9.4743); 489 $this->assertEquals($grades[$this->grade_items[4]->id], 7.3754); 490 491 // Test aggregating only the highest 1 out of 4 grades. 492 $category = new \grade_category(); 493 $category->keephigh = 1; 494 $category->droplow = 0; 495 $grades = array($this->grade_items[0]->id=>5.374, 496 $this->grade_items[1]->id=>9.4743, 497 $this->grade_items[2]->id=>2.5474, 498 $this->grade_items[4]->id=>7.3754); 499 $category->apply_limit_rules($grades, $items); 500 $this->assertEquals(count($grades), 1); 501 $grade = reset($grades); 502 $this->assertEquals(9.4743, $grade); 503 504 // Test excluding the lowest 2 out of 4 grades from aggregation with no 0 grades. 505 // An extra credit grade item should be kept even if droplow means it would otherwise be excluded. 506 $category = new \grade_category(); 507 $category->droplow = 2; 508 $category->aggregation = GRADE_AGGREGATE_SUM; 509 $items[$this->grade_items[2]->id]->aggregationcoef = 1; // Mark grade item 2 as "extra credit". 510 $grades = array($this->grade_items[0]->id=>5.374, 511 $this->grade_items[1]->id=>9.4743, 512 $this->grade_items[2]->id=>2.5474, 513 $this->grade_items[4]->id=>7.3754); 514 $category->apply_limit_rules($grades, $items); 515 $this->assertEquals(count($grades), 2); 516 $this->assertEquals($grades[$this->grade_items[1]->id], 9.4743); 517 $this->assertEquals($grades[$this->grade_items[2]->id], 2.5474); 518 519 // Test only aggregating the highest 1 out of 4 grades. 520 // An extra credit grade item is retained in addition to the highest grade. 521 $category = new \grade_category(); 522 $category->keephigh = 1; 523 $category->droplow = 0; 524 $category->aggregation = GRADE_AGGREGATE_SUM; 525 $items[$this->grade_items[2]->id]->aggregationcoef = 1; // Mark grade item 2 as "extra credit". 526 $grades = array($this->grade_items[0]->id=>5.374, 527 $this->grade_items[1]->id=>9.4743, 528 $this->grade_items[2]->id=>2.5474, 529 $this->grade_items[4]->id=>7.3754); 530 $category->apply_limit_rules($grades, $items); 531 $this->assertEquals(count($grades), 2); 532 $this->assertEquals($grades[$this->grade_items[1]->id], 9.4743); 533 $this->assertEquals($grades[$this->grade_items[2]->id], 2.5474); 534 535 // Test excluding the lowest 1 out of 4 grades from aggregation with two 0 grades. 536 $items[$this->grade_items[2]->id]->aggregationcoef = 0; // Undo marking grade item 2 as "extra credit". 537 $category = new \grade_category(); 538 $category->droplow = 1; 539 $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // Simple weighted mean. 540 $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation. 541 $this->grade_items[1]->id=>5, // 5 out of 100. 542 $this->grade_items[2]->id=>2, // 0 out of 6. 543 $this->grade_items[4]->id=>0); // 0 out of 100. 544 $category->apply_limit_rules($grades, $items); 545 $this->assertEquals(count($grades), 3); 546 $this->assertEquals($grades[$this->grade_items[1]->id], 5); 547 $this->assertEquals($grades[$this->grade_items[2]->id], 2); 548 $this->assertEquals($grades[$this->grade_items[4]->id], 0); 549 550 // Test excluding the lowest 2 out of 4 grades from aggregation with three 0 grades. 551 $category = new \grade_category(); 552 $category->droplow = 2; 553 $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // Simple weighted mean. 554 $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation. 555 $this->grade_items[1]->id=>5, // 5 out of 100. 556 $this->grade_items[2]->id=>0, // 0 out of 6. 557 $this->grade_items[4]->id=>0); // 0 out of 100. Should be excluded from aggregation. 558 $category->apply_limit_rules($grades, $items); 559 $this->assertEquals(count($grades), 2); 560 $this->assertEquals($grades[$this->grade_items[1]->id], 5); 561 $this->assertEquals($grades[$this->grade_items[2]->id], 0); 562 563 // Test excluding the lowest 5 out of 4 grades from aggregation. 564 // Just to check we handle this sensibly. 565 $category = new \grade_category(); 566 $category->droplow = 5; 567 $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // Simple weighted mean. 568 $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation. 569 $this->grade_items[1]->id=>5, // 5 out of 100. 570 $this->grade_items[2]->id=>6, // 6 out of 6. 571 $this->grade_items[4]->id=>1);// 1 out of 100. Should be excluded from aggregation. 572 $category->apply_limit_rules($grades, $items); 573 $this->assertEquals(count($grades), 0); 574 575 // Test excluding the lowest 4 out of 4 grades from aggregation with one marked as extra credit. 576 $category = new \grade_category(); 577 $category->droplow = 4; 578 $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // Simple weighted mean. 579 $items[$this->grade_items[2]->id]->aggregationcoef = 1; // Mark grade item 2 as "extra credit". 580 $grades = array($this->grade_items[0]->id=>0, // 0 out of 110. Should be excluded from aggregation. 581 $this->grade_items[1]->id=>5, // 5 out of 100. Should be excluded from aggregation. 582 $this->grade_items[2]->id=>6, // 6 out of 6. Extra credit. Should be retained. 583 $this->grade_items[4]->id=>1);// 1 out of 100. Should be excluded from aggregation. 584 $category->apply_limit_rules($grades, $items); 585 $this->assertEquals(count($grades), 1); 586 $this->assertEquals($grades[$this->grade_items[2]->id], 6); 587 588 // MDL-35667 - There was an infinite loop if several items had the same grade and at least one was extra credit. 589 $category = new \grade_category(); 590 $category->droplow = 1; 591 $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; // Simple weighted mean. 592 $items[$this->grade_items[1]->id]->aggregationcoef = 1; // Mark grade item 1 as "extra credit". 593 $grades = array($this->grade_items[0]->id=>1, // 1 out of 110. Should be excluded from aggregation. 594 $this->grade_items[1]->id=>1, // 1 out of 100. Extra credit. Should be retained. 595 $this->grade_items[2]->id=>1, // 1 out of 6. Should be retained. 596 $this->grade_items[4]->id=>1);// 1 out of 100. Should be retained. 597 $category->apply_limit_rules($grades, $items); 598 $this->assertEquals(count($grades), 3); 599 $this->assertEquals($grades[$this->grade_items[1]->id], 1); 600 $this->assertEquals($grades[$this->grade_items[2]->id], 1); 601 $this->assertEquals($grades[$this->grade_items[4]->id], 1); 602 603 } 604 605 protected function sub_test_grade_category_is_aggregationcoef_used() { 606 $category = new \grade_category(); 607 // Following use aggregationcoef. 608 $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN; 609 $this->assertTrue($category->is_aggregationcoef_used()); 610 $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; 611 $this->assertTrue($category->is_aggregationcoef_used()); 612 $category->aggregation = GRADE_AGGREGATE_EXTRACREDIT_MEAN; 613 $this->assertTrue($category->is_aggregationcoef_used()); 614 $category->aggregation = GRADE_AGGREGATE_SUM; 615 $this->assertTrue($category->is_aggregationcoef_used()); 616 617 // Following don't use aggregationcoef. 618 $category->aggregation = GRADE_AGGREGATE_MAX; 619 $this->assertFalse($category->is_aggregationcoef_used()); 620 $category->aggregation = GRADE_AGGREGATE_MEAN; 621 $this->assertFalse($category->is_aggregationcoef_used()); 622 $category->aggregation = GRADE_AGGREGATE_MEDIAN; 623 $this->assertFalse($category->is_aggregationcoef_used()); 624 $category->aggregation = GRADE_AGGREGATE_MIN; 625 $this->assertFalse($category->is_aggregationcoef_used()); 626 $category->aggregation = GRADE_AGGREGATE_MODE; 627 $this->assertFalse($category->is_aggregationcoef_used()); 628 } 629 630 protected function sub_test_grade_category_aggregation_uses_aggregationcoef() { 631 632 $this->assertTrue(\grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_WEIGHTED_MEAN)); 633 $this->assertTrue(\grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_WEIGHTED_MEAN2)); 634 $this->assertTrue(\grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_EXTRACREDIT_MEAN)); 635 $this->assertTrue(\grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_SUM)); 636 637 $this->assertFalse(\grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MAX)); 638 $this->assertFalse(\grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MEAN)); 639 $this->assertFalse(\grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MEDIAN)); 640 $this->assertFalse(\grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MIN)); 641 $this->assertFalse(\grade_category::aggregation_uses_aggregationcoef(GRADE_AGGREGATE_MODE)); 642 } 643 644 protected function sub_test_grade_category_fetch_course_tree() { 645 $category = new \grade_category(); 646 $this->assertTrue(method_exists($category, 'fetch_course_tree')); 647 // TODO: add some tests. 648 } 649 650 protected function sub_test_grade_category_get_children() { 651 $course_category = \grade_category::fetch_course_category($this->courseid); 652 653 $category = new \grade_category($this->grade_categories[0]); 654 $this->assertTrue(method_exists($category, 'get_children')); 655 656 $children_array = $category->get_children(0); 657 658 $this->assertTrue(is_array($children_array)); 659 $this->assertFalse(empty($children_array[2])); 660 $this->assertFalse(empty($children_array[2]['object'])); 661 $this->assertFalse(empty($children_array[2]['children'])); 662 $this->assertEquals($this->grade_categories[1]->id, $children_array[2]['object']->id); 663 $this->assertEquals($this->grade_categories[2]->id, $children_array[5]['object']->id); 664 $this->assertEquals($this->grade_items[0]->id, $children_array[2]['children'][3]['object']->id); 665 $this->assertEquals($this->grade_items[1]->id, $children_array[2]['children'][4]['object']->id); 666 $this->assertEquals($this->grade_items[2]->id, $children_array[5]['children'][6]['object']->id); 667 } 668 669 protected function sub_test_grade_category_load_grade_item() { 670 $category = new \grade_category($this->grade_categories[0]); 671 $this->assertTrue(method_exists($category, 'load_grade_item')); 672 $this->assertEquals(null, $category->grade_item); 673 $category->load_grade_item(); 674 $this->assertEquals($this->grade_items[3]->id, $category->grade_item->id); 675 } 676 677 protected function sub_test_grade_category_get_grade_item() { 678 $category = new \grade_category($this->grade_categories[0]); 679 $this->assertTrue(method_exists($category, 'get_grade_item')); 680 $grade_item = $category->get_grade_item(); 681 $this->assertEquals($this->grade_items[3]->id, $grade_item->id); 682 } 683 684 protected function sub_test_grade_category_load_parent_category() { 685 $category = new \grade_category($this->grade_categories[1]); 686 $this->assertTrue(method_exists($category, 'load_parent_category')); 687 $this->assertEquals(null, $category->parent_category); 688 $category->load_parent_category(); 689 $this->assertEquals($this->grade_categories[0]->id, $category->parent_category->id); 690 } 691 692 protected function sub_test_grade_category_get_parent_category() { 693 $category = new \grade_category($this->grade_categories[1]); 694 $this->assertTrue(method_exists($category, 'get_parent_category')); 695 $parent_category = $category->get_parent_category(); 696 $this->assertEquals($this->grade_categories[0]->id, $parent_category->id); 697 } 698 699 /** 700 * Tests the getter of the category fullname with escaped HTML. 701 */ 702 protected function sub_test_grade_category_get_name_escaped() { 703 $category = new \grade_category($this->grade_categories[0]); 704 $this->assertTrue(method_exists($category, 'get_name')); 705 $this->assertEquals(format_string($this->grade_categories[0]->fullname, true, ['escape' => true]), 706 $category->get_name(true)); 707 } 708 709 /** 710 * Tests the getter of the category fullname with unescaped HTML. 711 */ 712 protected function sub_test_grade_category_get_name_unescaped() { 713 $category = new \grade_category($this->grade_categories[0]); 714 $this->assertTrue(method_exists($category, 'get_name')); 715 $this->assertEquals(format_string($this->grade_categories[0]->fullname, true, ['escape' => false]), 716 $category->get_name(false)); 717 } 718 719 protected function sub_test_grade_category_set_parent() { 720 $category = new \grade_category($this->grade_categories[1]); 721 $this->assertTrue(method_exists($category, 'set_parent')); 722 // TODO: implement detailed tests. 723 724 $course_category = \grade_category::fetch_course_category($this->courseid); 725 $this->assertTrue($category->set_parent($course_category->id)); 726 $this->assertEquals($course_category->id, $category->parent); 727 } 728 729 protected function sub_test_grade_category_get_final() { 730 $category = new \grade_category($this->grade_categories[0]); 731 $this->assertTrue(method_exists($category, 'get_final')); 732 $category->load_grade_item(); 733 $this->assertEquals($category->get_final(), $category->grade_item->get_final()); 734 } 735 736 protected function sub_test_grade_category_get_sortorder() { 737 $category = new \grade_category($this->grade_categories[0]); 738 $this->assertTrue(method_exists($category, 'get_sortorder')); 739 $category->load_grade_item(); 740 $this->assertEquals($category->get_sortorder(), $category->grade_item->get_sortorder()); 741 } 742 743 protected function sub_test_grade_category_set_sortorder() { 744 $category = new \grade_category($this->grade_categories[0]); 745 $this->assertTrue(method_exists($category, 'set_sortorder')); 746 $category->load_grade_item(); 747 $this->assertEquals($category->set_sortorder(10), $category->grade_item->set_sortorder(10)); 748 } 749 750 protected function sub_test_grade_category_move_after_sortorder() { 751 $category = new \grade_category($this->grade_categories[0]); 752 $this->assertTrue(method_exists($category, 'move_after_sortorder')); 753 $category->load_grade_item(); 754 $this->assertEquals($category->move_after_sortorder(10), $category->grade_item->move_after_sortorder(10)); 755 } 756 757 protected function sub_test_grade_category_is_course_category() { 758 $category = \grade_category::fetch_course_category($this->courseid); 759 $this->assertTrue(method_exists($category, 'is_course_category')); 760 $this->assertTrue($category->is_course_category()); 761 } 762 763 protected function sub_test_grade_category_fetch_course_category() { 764 $category = new \grade_category(); 765 $this->assertTrue(method_exists($category, 'fetch_course_category')); 766 $category = \grade_category::fetch_course_category($this->courseid); 767 $this->assertTrue(empty($category->parent)); 768 } 769 /** 770 * TODO implement 771 */ 772 protected function sub_test_grade_category_is_editable() { 773 774 } 775 776 protected function sub_test_grade_category_is_locked() { 777 $category = new \grade_category($this->grade_categories[0]); 778 $this->assertTrue(method_exists($category, 'is_locked')); 779 $category->load_grade_item(); 780 $this->assertEquals($category->is_locked(), $category->grade_item->is_locked()); 781 } 782 783 protected function sub_test_grade_category_set_locked() { 784 $category = new \grade_category($this->grade_categories[0]); 785 $this->assertTrue(method_exists($category, 'set_locked')); 786 787 // Even though a grade that needs updating cannot be locked, set_locked will return true because it will successfully 788 // schedule the locking for as soon as final grades are recalculated. 789 $this->assertTrue($category->set_locked(1)); 790 // The category should not be locked yet as we are waiting for a recalculation. 791 $this->assertFalse($category->is_locked()); 792 grade_regrade_final_grades($this->courseid); 793 794 // Get the category from the db again. 795 $category = new \grade_category($this->grade_categories[0]); 796 // The category is locked now. 797 $this->assertTrue($category->is_locked()); 798 } 799 800 protected function sub_test_grade_category_is_hidden() { 801 $category = new \grade_category($this->grade_categories[0]); 802 $this->assertTrue(method_exists($category, 'is_hidden')); 803 $category->load_grade_item(); 804 $this->assertEquals($category->is_hidden(), $category->grade_item->is_hidden()); 805 } 806 807 protected function sub_test_grade_category_set_hidden() { 808 $category = new \grade_category($this->grade_categories[0]); 809 $this->assertTrue(method_exists($category, 'set_hidden')); 810 $category->set_hidden(1, true); 811 $category->load_grade_item(); 812 $this->assertEquals(true, $category->grade_item->is_hidden()); 813 } 814 815 protected function sub_test_grade_category_can_control_visibility() { 816 $category = new \grade_category($this->grade_categories[0]); 817 $this->assertTrue($category->can_control_visibility()); 818 } 819 820 protected function sub_test_grade_category_insert_course_category() { 821 // Beware: adding a duplicate course category messes up the data in a way that's hard to recover from. 822 $grade_category = new \grade_category(); 823 $this->assertTrue(method_exists($grade_category, 'insert_course_category')); 824 825 $id = $grade_category->insert_course_category($this->courseid); 826 $this->assertNotNull($id); 827 $this->assertEquals('?', $grade_category->fullname); 828 $this->assertEquals(GRADE_AGGREGATE_WEIGHTED_MEAN2, $grade_category->aggregation); 829 $this->assertEquals("/$id/", $grade_category->path); 830 $this->assertEquals(1, $grade_category->depth); 831 $this->assertNull($grade_category->parent); 832 } 833 834 protected function generate_random_raw_grade($item, $userid) { 835 $grade = new \grade_grade(); 836 $grade->itemid = $item->id; 837 $grade->userid = $userid; 838 $grade->grademin = 0; 839 $grade->grademax = 1; 840 $valuetype = "grade$item->gradetype"; 841 $grade->rawgrade = rand(0, 1000) / 1000; 842 $grade->insert(); 843 return $grade->rawgrade; 844 } 845 846 protected function sub_test_grade_category_is_extracredit_used() { 847 $category = new \grade_category(); 848 // Following use aggregationcoef. 849 $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN2; 850 $this->assertTrue($category->is_extracredit_used()); 851 $category->aggregation = GRADE_AGGREGATE_EXTRACREDIT_MEAN; 852 $this->assertTrue($category->is_extracredit_used()); 853 $category->aggregation = GRADE_AGGREGATE_SUM; 854 $this->assertTrue($category->is_extracredit_used()); 855 856 // Following don't use aggregationcoef. 857 $category->aggregation = GRADE_AGGREGATE_WEIGHTED_MEAN; 858 $this->assertFalse($category->is_extracredit_used()); 859 $category->aggregation = GRADE_AGGREGATE_MAX; 860 $this->assertFalse($category->is_extracredit_used()); 861 $category->aggregation = GRADE_AGGREGATE_MEAN; 862 $this->assertFalse($category->is_extracredit_used()); 863 $category->aggregation = GRADE_AGGREGATE_MEDIAN; 864 $this->assertFalse($category->is_extracredit_used()); 865 $category->aggregation = GRADE_AGGREGATE_MIN; 866 $this->assertFalse($category->is_extracredit_used()); 867 $category->aggregation = GRADE_AGGREGATE_MODE; 868 $this->assertFalse($category->is_extracredit_used()); 869 } 870 871 protected function sub_test_grade_category_aggregation_uses_extracredit() { 872 873 $this->assertTrue(\grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_WEIGHTED_MEAN2)); 874 $this->assertTrue(\grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_EXTRACREDIT_MEAN)); 875 $this->assertTrue(\grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_SUM)); 876 877 $this->assertFalse(\grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_WEIGHTED_MEAN)); 878 $this->assertFalse(\grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MAX)); 879 $this->assertFalse(\grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MEAN)); 880 $this->assertFalse(\grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MEDIAN)); 881 $this->assertFalse(\grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MIN)); 882 $this->assertFalse(\grade_category::aggregation_uses_extracredit(GRADE_AGGREGATE_MODE)); 883 } 884 885 /** 886 * Test for category total visibility. 887 */ 888 protected function sub_test_grade_category_total_visibility() { 889 // 15 is a manual grade item in grade_categories[5]. 890 $category = new \grade_category($this->grade_categories[5], true); 891 $gradeitem = new \grade_item($this->grade_items[15], true); 892 893 // Hide grade category. 894 $category->set_hidden(true, true); 895 $this->assertTrue($category->is_hidden()); 896 // Category total is hidden. 897 $categorytotal = $category->get_grade_item(); 898 $this->assertTrue($categorytotal->is_hidden()); 899 // Manual grade is hidden. 900 $gradeitem->update_from_db(); 901 $this->assertTrue($gradeitem->is_hidden()); 902 903 // Unhide manual grade item. 904 $gradeitem->set_hidden(false); 905 $this->assertFalse($gradeitem->is_hidden()); 906 // Category is unhidden. 907 $category->update_from_db(); 908 $this->assertFalse($category->is_hidden()); 909 // Category total remain hidden. 910 $categorytotal = $category->get_grade_item(); 911 $this->assertTrue($categorytotal->is_hidden()); 912 913 // Edit manual grade item. 914 $this->assertFalse($gradeitem->is_locked()); 915 $gradeitem->set_locked(true); 916 $gradeitem->update_from_db(); 917 $this->assertTrue($gradeitem->is_locked()); 918 // Category total should still be hidden. 919 $category->update_from_db(); 920 $categorytotal = $category->get_grade_item(); 921 $this->assertTrue($categorytotal->is_hidden()); 922 } 923 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body