PageRenderTime 266ms CodeModel.GetById 83ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/grade/tests/grade_category_test.php

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