Search moodle.org's
Developer Documentation

See Release Notes
Long Term Support Release

  • Bug fixes for general core bugs in 4.1.x will end 13 November 2023 (12 months).
  • Bug fixes for security issues in 4.1.x will end 10 November 2025 (36 months).
  • PHP version: minimum PHP 7.4.0 Note: minimum PHP version has increased since Moodle 4.0. PHP 8.0.x is supported too.

Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]

   1  <?php
   2  // This file is part of Moodle - http://moodle.org/
   3  //
   4  // Moodle is free software: you can redistribute it and/or modify
   5  // it under the terms of the GNU General Public License as published by
   6  // the Free Software Foundation, either version 3 of the License, or
   7  // (at your option) any later version.
   8  //
   9  // Moodle is distributed in the hope that it will be useful,
  10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  12  // GNU General Public License for more details.
  13  //
  14  // You should have received a copy of the GNU General Public License
  15  // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
  16  
  17  /**
  18   * Unit tests for grade/lib.php.
  19   *
  20   * @package   core_grades
  21   * @category  test
  22   * @copyright 2016 Jun Pataleta
  23   * @license   http://www.gnu.org/copyleft/gpl.html GNU Public License
  24   */
  25  namespace core_grades;
  26  
  27  use assign;
  28  use cm_info;
  29  use grade_item;
  30  use grade_plugin_return;
  31  use grade_report_summary;
  32  
  33  defined('MOODLE_INTERNAL') || die();
  34  
  35  global $CFG;
  36  require_once($CFG->dirroot . '/grade/lib.php');
  37  
  38  /**
  39   * Unit tests for grade/lib.php.
  40   *
  41   * @package   core_grades
  42   * @category  test
  43   * @copyright 2016 Jun Pataleta <jun@moodle.com>
  44   * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  45   */
  46  class lib_test extends \advanced_testcase {
  47  
  48      /**
  49       * Test can_output_item.
  50       */
  51      public function test_can_output_item() {
  52          $this->resetAfterTest();
  53  
  54          $generator = $this->getDataGenerator();
  55  
  56          // Course level grade category.
  57          $course = $generator->create_course();
  58          // Grade tree looks something like:
  59          // - Test course    (Rendered).
  60          $gradetree = \grade_category::fetch_course_tree($course->id);
  61          $this->assertTrue(\grade_tree::can_output_item($gradetree));
  62  
  63          // Add a grade category with default settings.
  64          $generator->create_grade_category(array('courseid' => $course->id));
  65          // Grade tree now looks something like:
  66          // - Test course n        (Rendered).
  67          // -- Grade category n    (Rendered).
  68          $gradetree = \grade_category::fetch_course_tree($course->id);
  69          $this->assertNotEmpty($gradetree['children']);
  70          foreach ($gradetree['children'] as $child) {
  71              $this->assertTrue(\grade_tree::can_output_item($child));
  72          }
  73  
  74          // Add a grade category with grade type = None.
  75          $nototalcategory = 'No total category';
  76          $nototalparams = [
  77              'courseid' => $course->id,
  78              'fullname' => $nototalcategory,
  79              'aggregation' => GRADE_AGGREGATE_WEIGHTED_MEAN
  80          ];
  81          $nototal = $generator->create_grade_category($nototalparams);
  82          $catnototal = \grade_category::fetch(array('id' => $nototal->id));
  83          // Set the grade type of the grade item associated to the grade category.
  84          $catitemnototal = $catnototal->load_grade_item();
  85          $catitemnototal->gradetype = GRADE_TYPE_NONE;
  86          $catitemnototal->update();
  87  
  88          // Grade tree looks something like:
  89          // - Test course n        (Rendered).
  90          // -- Grade category n    (Rendered).
  91          // -- No total category   (Not rendered).
  92          $gradetree = \grade_category::fetch_course_tree($course->id);
  93          foreach ($gradetree['children'] as $child) {
  94              if ($child['object']->fullname == $nototalcategory) {
  95                  $this->assertFalse(\grade_tree::can_output_item($child));
  96              } else {
  97                  $this->assertTrue(\grade_tree::can_output_item($child));
  98              }
  99          }
 100  
 101          // Add another grade category with default settings under 'No total category'.
 102          $normalinnototalparams = [
 103              'courseid' => $course->id,
 104              'fullname' => 'Normal category in no total category',
 105              'parent' => $nototal->id
 106          ];
 107          $generator->create_grade_category($normalinnototalparams);
 108  
 109          // Grade tree looks something like:
 110          // - Test course n                           (Rendered).
 111          // -- Grade category n                       (Rendered).
 112          // -- No total category                      (Rendered).
 113          // --- Normal category in no total category  (Rendered).
 114          $gradetree = \grade_category::fetch_course_tree($course->id);
 115          foreach ($gradetree['children'] as $child) {
 116              // All children are now visible.
 117              $this->assertTrue(\grade_tree::can_output_item($child));
 118              if (!empty($child['children'])) {
 119                  foreach ($child['children'] as $grandchild) {
 120                      $this->assertTrue(\grade_tree::can_output_item($grandchild));
 121                  }
 122              }
 123          }
 124  
 125          // Add a grade category with grade type = None.
 126          $nototalcategory2 = 'No total category 2';
 127          $nototal2params = [
 128              'courseid' => $course->id,
 129              'fullname' => $nototalcategory2,
 130              'aggregation' => GRADE_AGGREGATE_WEIGHTED_MEAN
 131          ];
 132          $nototal2 = $generator->create_grade_category($nototal2params);
 133          $catnototal2 = \grade_category::fetch(array('id' => $nototal2->id));
 134          // Set the grade type of the grade item associated to the grade category.
 135          $catitemnototal2 = $catnototal2->load_grade_item();
 136          $catitemnototal2->gradetype = GRADE_TYPE_NONE;
 137          $catitemnototal2->update();
 138  
 139          // Add a category with no total under 'No total category'.
 140          $nototalinnototalcategory = 'Category with no total in no total category';
 141          $nototalinnototalparams = [
 142              'courseid' => $course->id,
 143              'fullname' => $nototalinnototalcategory,
 144              'aggregation' => GRADE_AGGREGATE_WEIGHTED_MEAN,
 145              'parent' => $nototal2->id
 146          ];
 147          $nototalinnototal = $generator->create_grade_category($nototalinnototalparams);
 148          $catnototalinnototal = \grade_category::fetch(array('id' => $nototalinnototal->id));
 149          // Set the grade type of the grade item associated to the grade category.
 150          $catitemnototalinnototal = $catnototalinnototal->load_grade_item();
 151          $catitemnototalinnototal->gradetype = GRADE_TYPE_NONE;
 152          $catitemnototalinnototal->update();
 153  
 154          // Grade tree looks something like:
 155          // - Test course n                                    (Rendered).
 156          // -- Grade category n                                (Rendered).
 157          // -- No total category                               (Rendered).
 158          // --- Normal category in no total category           (Rendered).
 159          // -- No total category 2                             (Not rendered).
 160          // --- Category with no total in no total category    (Not rendered).
 161          $gradetree = \grade_category::fetch_course_tree($course->id);
 162          foreach ($gradetree['children'] as $child) {
 163              if ($child['object']->fullname == $nototalcategory2) {
 164                  $this->assertFalse(\grade_tree::can_output_item($child));
 165              } else {
 166                  $this->assertTrue(\grade_tree::can_output_item($child));
 167              }
 168              if (!empty($child['children'])) {
 169                  foreach ($child['children'] as $grandchild) {
 170                      if ($grandchild['object']->fullname == $nototalinnototalcategory) {
 171                          $this->assertFalse(\grade_tree::can_output_item($grandchild));
 172                      } else {
 173                          $this->assertTrue(\grade_tree::can_output_item($grandchild));
 174                      }
 175                  }
 176              }
 177          }
 178      }
 179  
 180      /**
 181       * Tests that ungraded_counts calculates count and sum of grades correctly when there are graded users.
 182       *
 183       * @covers \grade_report::ungraded_counts
 184       */
 185      public function test_ungraded_counts_count_sumgrades() {
 186          global $DB;
 187  
 188          $this->resetAfterTest(true);
 189  
 190          $course1 = $this->getDataGenerator()->create_course();
 191          $course2 = $this->getDataGenerator()->create_course();
 192  
 193          $studentrole = $DB->get_record('role', ['shortname' => 'student'], '*', MUST_EXIST);
 194          $teacherrole = $DB->get_record('role', ['shortname' => 'editingteacher'], '*', MUST_EXIST);
 195  
 196          // Custom roles (gradable and non gradable).
 197          $gradeblerole = create_role('New student role', 'gradable',
 198              'Gradable role', 'student');
 199          $nongradeblerole = create_role('New student role', 'nongradable',
 200              'Non gradable role', 'student');
 201  
 202          // Set up gradable roles.
 203          set_config('gradebookroles', $studentrole->id . ',' . $gradeblerole);
 204  
 205          // Create users.
 206  
 207          // These will be gradable users.
 208          $student1 = $this->getDataGenerator()->create_user(['username' => 'student1']);
 209          $student2 = $this->getDataGenerator()->create_user(['username' => 'student2']);
 210          $student3 = $this->getDataGenerator()->create_user(['username' => 'student3']);
 211          $student5 = $this->getDataGenerator()->create_user(['username' => 'student5']);
 212  
 213          // These will be non-gradable users.
 214          $student4 = $this->getDataGenerator()->create_user(['username' => 'student4']);
 215          $student6 = $this->getDataGenerator()->create_user(['username' => 'student6']);
 216          $teacher = $this->getDataGenerator()->create_user(['username' => 'teacher']);
 217  
 218          // Enrol students.
 219          $this->getDataGenerator()->enrol_user($student1->id, $course1->id, $studentrole->id);
 220          $this->getDataGenerator()->enrol_user($student2->id, $course1->id, $studentrole->id);
 221          $this->getDataGenerator()->enrol_user($student3->id, $course1->id, $gradeblerole);
 222  
 223          $this->getDataGenerator()->enrol_user($student5->id, $course1->id, $nongradeblerole);
 224          $this->getDataGenerator()->enrol_user($student6->id, $course1->id, $studentrole->id);
 225          $this->getDataGenerator()->enrol_user($teacher->id, $course1->id, $teacherrole->id);
 226  
 227          // User that is enrolled in a different course.
 228          $this->getDataGenerator()->enrol_user($student4->id, $course2->id, $studentrole->id);
 229  
 230          // Mark user as deleted.
 231          $student6->deleted = 1;
 232          $DB->update_record('user', $student6);
 233  
 234          // Create grade items in course 1.
 235          $assign1 = $this->getDataGenerator()->create_module('assign', ['course' => $course1->id]);
 236          $assign2 = $this->getDataGenerator()->create_module('assign', ['course' => $course1->id]);
 237          $quiz1 = $this->getDataGenerator()->create_module('quiz', ['course' => $course1->id]);
 238  
 239          $manuaitem = new \grade_item($this->getDataGenerator()->create_grade_item([
 240              'itemname'        => 'Grade item1',
 241              'idnumber'        => 'git1',
 242              'courseid'        => $course1->id,
 243          ]));
 244  
 245          // Create grade items in course 2.
 246          $assign3 = $this->getDataGenerator()->create_module('assign', ['course' => $course2->id]);
 247  
 248          // Grade users in first course.
 249          $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign1->id));
 250          $assigninstance = new assign($cm->context, $cm, $course1);
 251          $grade = $assigninstance->get_user_grade($student1->id, true);
 252          $grade->grade = 40;
 253          $assigninstance->update_grade($grade);
 254  
 255          $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign2->id));
 256          $assigninstance = new assign($cm->context, $cm, $course1);
 257          $grade = $assigninstance->get_user_grade($student3->id, true);
 258          $grade->grade = 50;
 259          $assigninstance->update_grade($grade);
 260  
 261          // Override grade for assignment in gradebook.
 262          $gi = \grade_item::fetch([
 263              'itemtype' => 'mod',
 264              'itemmodule' => 'assign',
 265              'iteminstance' => $cm->instance,
 266              'courseid' => $course1->id
 267          ]);
 268          $gi->update_final_grade($student3->id, 55);
 269  
 270          // Grade user in second course.
 271          $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign3->id));
 272          $assigninstance = new assign($cm->context, $cm, $course2);
 273          $grade = $assigninstance->get_user_grade($student4->id, true);
 274          $grade->grade = 40;
 275          $assigninstance->update_grade($grade);
 276  
 277          $manuaitem->update_final_grade($student1->id, 1);
 278          $manuaitem->update_final_grade($student3->id, 2);
 279  
 280          // Trigger a regrade.
 281          grade_force_full_regrading($course1->id);
 282          grade_force_full_regrading($course2->id);
 283          grade_regrade_final_grades($course1->id);
 284          grade_regrade_final_grades($course2->id);
 285  
 286          // Initialise reports.
 287          $context1 = \context_course::instance($course1->id);
 288          $context2 = \context_course::instance($course2->id);
 289  
 290          $gpr1 = new grade_plugin_return(
 291              [
 292                  'type' => 'report',
 293                  'plugin' => 'summary',
 294                  'course' => $course1,
 295              ]
 296          );
 297  
 298          $gpr2 = new grade_plugin_return(
 299              [
 300                  'type' => 'report',
 301                  'plugin' => 'summary',
 302                  'course' => $course2,
 303              ]
 304          );
 305  
 306          $report1 = new grade_report_summary($course1->id, $gpr1, $context1);
 307          $report2 = new grade_report_summary($course2->id, $gpr2, $context2);
 308  
 309          $ungradedcounts = [];
 310          $ungradedcounts[$course1->id] = $report1->ungraded_counts();
 311          $ungradedcounts[$course2->id] = $report2->ungraded_counts();
 312  
 313          foreach ($ungradedcounts as $key => $ungradedcount) {
 314              $gradeitems = grade_item::fetch_all(['courseid' => $key]);
 315              if ($key == $course1->id) {
 316                  $gradeitemkeys = array_keys($gradeitems);
 317                  $ungradedcountskeys = array_keys($ungradedcount['ungradedcounts']);
 318  
 319                  // For each grade item there is some student that is not graded yet in course 1.
 320                  $this->assertEmpty(array_diff_key($gradeitemkeys, $ungradedcountskeys));
 321  
 322                  // Only quiz does not have any grades, the remaning 4 grade items should have some.
 323                  // We can do more and match by gradeitem id numbers. But feels like overengeneering.
 324                  $this->assertEquals(4, count($ungradedcount['sumarray']));
 325              } else {
 326  
 327                  // In course 2 there is one student, and he is graded.
 328                  $this->assertEmpty($ungradedcount['ungradedcounts']);
 329  
 330                  // There are 2 grade items and they both have some grades.
 331                  $this->assertEquals(2, count($ungradedcount['sumarray']));
 332              }
 333  
 334              foreach ($gradeitems as $gradeitem) {
 335                  $sumgrades = null;
 336                  if (array_key_exists($gradeitem->id, $ungradedcount['ungradedcounts'])) {
 337                      $ungradeditem = $ungradedcount['ungradedcounts'][$gradeitem->id];
 338                      if ($gradeitem->itemtype === 'course') {
 339                          $this->assertEquals(1, $ungradeditem->count);
 340                      } else if ($gradeitem->itemmodule === 'assign') {
 341                          $this->assertEquals(2, $ungradeditem->count);
 342                      } else if ($gradeitem->itemmodule === 'quiz') {
 343                          $this->assertEquals(3, $ungradeditem->count);
 344                      } else if ($gradeitem->itemtype === 'manual') {
 345                          $this->assertEquals(1, $ungradeditem->count);
 346                      }
 347                  }
 348  
 349                  if (array_key_exists($gradeitem->id, $ungradedcount['sumarray'])) {
 350                      $sumgrades = $ungradedcount['sumarray'][$gradeitem->id];
 351                      if ($gradeitem->itemtype === 'course') {
 352                          if ($key == $course1->id) {
 353                              $this->assertEquals('98.00000', $sumgrades); // 40 + 55 + 1 + 2
 354                          } else {
 355                              $this->assertEquals('40.00000', $sumgrades);
 356                          }
 357                      } else if ($gradeitem->itemmodule === 'assign') {
 358                          if (($gradeitem->itemname === $assign1->name) || ($gradeitem->itemname === $assign3->name)) {
 359                              $this->assertEquals('40.00000', $sumgrades);
 360                          } else {
 361                              $this->assertEquals('55.00000', $sumgrades);
 362                          }
 363                      } else if ($gradeitem->itemtype === 'manual') {
 364                          $this->assertEquals('3.00000', $sumgrades);
 365                      }
 366                  }
 367              }
 368          }
 369      }
 370  
 371      /**
 372       * Tests that ungraded_counts calculates count and sum of grades correctly for groups when there are graded users.
 373       *
 374       * @covers \grade_report::ungraded_counts
 375       */
 376      public function test_ungraded_count_sumgrades_groups() {
 377          global $DB;
 378  
 379          $this->resetAfterTest(true);
 380  
 381          $course = $this->getDataGenerator()->create_course();
 382  
 383          $studentrole = $DB->get_record('role', ['shortname' => 'student'], '*', MUST_EXIST);
 384  
 385          // Create users.
 386  
 387          $student1 = $this->getDataGenerator()->create_user(['username' => 'student1']);
 388          $student2 = $this->getDataGenerator()->create_user(['username' => 'student2']);
 389          $student3 = $this->getDataGenerator()->create_user(['username' => 'student3']);
 390  
 391          // Enrol students.
 392          $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
 393          $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
 394          $this->getDataGenerator()->enrol_user($student3->id, $course->id, $studentrole->id);
 395  
 396          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
 397          $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
 398          $this->getDataGenerator()->create_group_member(['userid' => $student1->id, 'groupid' => $group1->id]);
 399          $this->getDataGenerator()->create_group_member(['userid' => $student2->id, 'groupid' => $group2->id]);
 400          $this->getDataGenerator()->create_group_member(['userid' => $student3->id, 'groupid' => $group2->id]);
 401          $DB->set_field('course', 'groupmode', SEPARATEGROUPS, ['id' => $course->id]);
 402  
 403          // Create grade items in course 1.
 404          $assign1 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
 405          $assign2 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
 406          $quiz1 = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
 407  
 408          $manuaitem = new \grade_item($this->getDataGenerator()->create_grade_item([
 409              'itemname'        => 'Grade item1',
 410              'idnumber'        => 'git1',
 411              'courseid'        => $course->id,
 412          ]));
 413  
 414          // Grade users in first course.
 415          $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign1->id));
 416          $assigninstance = new assign($cm->context, $cm, $course);
 417          $grade = $assigninstance->get_user_grade($student1->id, true);
 418          $grade->grade = 40;
 419          $assigninstance->update_grade($grade);
 420  
 421          $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign2->id));
 422          $assigninstance = new assign($cm->context, $cm, $course);
 423          $grade = $assigninstance->get_user_grade($student3->id, true);
 424          $grade->grade = 50;
 425          $assigninstance->update_grade($grade);
 426  
 427          $manuaitem->update_final_grade($student1->id, 1);
 428          $manuaitem->update_final_grade($student3->id, 2);
 429  
 430          // Trigger a regrade.
 431          grade_force_full_regrading($course->id);
 432          grade_regrade_final_grades($course->id);
 433  
 434          // Initialise report.
 435          $context = \context_course::instance($course->id);
 436  
 437          $gpr1 = new grade_plugin_return(
 438              [
 439                  'type' => 'report',
 440                  'plugin' => 'summary',
 441                  'course' => $course,
 442                  'groupid' => $group1->id,
 443              ]
 444          );
 445  
 446          $gpr2 = new grade_plugin_return(
 447              [
 448                  'type' => 'report',
 449                  'plugin' => 'summary',
 450                  'course' => $course,
 451                  'groupid' => $group2->id,
 452              ]
 453          );
 454  
 455          $report1 = new grade_report_summary($course->id, $gpr1, $context);
 456          $report2 = new grade_report_summary($course->id, $gpr2, $context);
 457  
 458          $ungradedcounts = [];
 459          $ungradedcounts[$group1->id] = $report1->ungraded_counts();
 460          $ungradedcounts[$group2->id] = $report2->ungraded_counts();
 461  
 462          $gradeitems = grade_item::fetch_all(['courseid' => $course->id]);
 463  
 464          // In group1 there is 1 student and assign1 and quiz1 are not graded for him.
 465          $this->assertEquals(2, count($ungradedcounts[$group1->id]['ungradedcounts']));
 466  
 467          // In group1 manual grade item, assign1 and course total have some grades.
 468          $this->assertEquals(3, count($ungradedcounts[$group1->id]['sumarray']));
 469  
 470          // In group2 student2 has no grades at all so all 5 grade items should present.
 471          $this->assertEquals(5, count($ungradedcounts[$group2->id]['ungradedcounts']));
 472  
 473          // In group2 manual grade item, assign2 and course total have some grades.
 474          $this->assertEquals(3, count($ungradedcounts[$group2->id]['sumarray']));
 475  
 476          foreach ($gradeitems as $gradeitem) {
 477              $sumgrades = null;
 478  
 479              foreach ($ungradedcounts as $key => $ungradedcount) {
 480                  if (array_key_exists($gradeitem->id, $ungradedcount['ungradedcounts'])) {
 481                      $ungradeditem = $ungradedcount['ungradedcounts'][$gradeitem->id];
 482                      if ($key == $group1->id) {
 483                          // Both assign2 and quiz1 are not graded for student1.
 484                          $this->assertEquals(1, $ungradeditem->count);
 485                      } else {
 486                          if ($gradeitem->itemtype === 'course') {
 487                              $this->assertEquals(1, $ungradeditem->count);
 488                          } else if ($gradeitem->itemmodule === 'assign') {
 489                              if ($gradeitem->itemname === $assign1->name) {
 490                                  // In group2 assign1 is not graded for anyone.
 491                                  $this->assertEquals(2, $ungradeditem->count);
 492                              } else {
 493                                  // In group2 assign2 is graded for student3.
 494                                  $this->assertEquals(1, $ungradeditem->count);
 495                              }
 496                          } else if ($gradeitem->itemmodule === 'quiz') {
 497                              $this->assertEquals(2, $ungradeditem->count);
 498                          } else if ($gradeitem->itemtype === 'manual') {
 499                              $this->assertEquals(1, $ungradeditem->count);
 500                          }
 501                      }
 502                  }
 503  
 504                  if (array_key_exists($gradeitem->id, $ungradedcount['sumarray'])) {
 505                      $sumgrades = $ungradedcount['sumarray'][$gradeitem->id];
 506                      if ($key == $group1->id) {
 507                          if ($gradeitem->itemtype === 'course') {
 508                              $this->assertEquals('41.00000', $sumgrades);
 509                          } else if ($gradeitem->itemmodule === 'assign') {
 510                              $this->assertEquals('40.00000', $sumgrades);
 511                          } else if ($gradeitem->itemtype === 'manual') {
 512                              $this->assertEquals('1.00000', $sumgrades);
 513                          }
 514                      } else {
 515                          if ($gradeitem->itemtype === 'course') {
 516                              $this->assertEquals('52.00000', $sumgrades);
 517                          } else if ($gradeitem->itemmodule === 'assign') {
 518                              $this->assertEquals('50.00000', $sumgrades);
 519                          } else if ($gradeitem->itemtype === 'manual') {
 520                              $this->assertEquals('2.00000', $sumgrades);
 521                          }
 522                      }
 523                  }
 524              }
 525          }
 526      }
 527  
 528      /**
 529       * Tests for calculate_average.
 530       * @dataProvider calculate_average_data()
 531       * @param int $meanselection Whether to inlcude all grades or non-empty grades in aggregation.
 532       * @param array $expectedmeancount expected meancount value
 533       * @param array $expectedaverage expceted average value
 534       *
 535       * @covers \grade_report::calculate_average
 536       */
 537      public function test_calculate_average(int $meanselection, array $expectedmeancount, array $expectedaverage) {
 538          global $DB;
 539  
 540          $this->resetAfterTest(true);
 541  
 542          $course = $this->getDataGenerator()->create_course();
 543  
 544          $student1 = $this->getDataGenerator()->create_user(['username' => 'student1']);
 545          $student2 = $this->getDataGenerator()->create_user(['username' => 'student2']);
 546          $student3 = $this->getDataGenerator()->create_user(['username' => 'student3']);
 547  
 548          $studentrole = $DB->get_record('role', ['shortname' => 'student'], '*', MUST_EXIST);
 549  
 550          // Enrol students.
 551          $this->getDataGenerator()->enrol_user($student1->id, $course->id, $studentrole->id);
 552          $this->getDataGenerator()->enrol_user($student2->id, $course->id, $studentrole->id);
 553          $this->getDataGenerator()->enrol_user($student3->id, $course->id, $studentrole->id);
 554  
 555          // Create activities.
 556          $assign1 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
 557          $assign2 = $this->getDataGenerator()->create_module('assign', ['course' => $course->id]);
 558          $quiz1 = $this->getDataGenerator()->create_module('quiz', ['course' => $course->id]);
 559  
 560          // Grade users.
 561          $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign1->id));
 562          $assigninstance = new assign($cm->context, $cm, $course);
 563          $grade = $assigninstance->get_user_grade($student1->id, true);
 564          $grade->grade = 40;
 565          $assigninstance->update_grade($grade);
 566  
 567          $grade = $assigninstance->get_user_grade($student2->id, true);
 568          $grade->grade = 30;
 569          $assigninstance->update_grade($grade);
 570  
 571          $cm = cm_info::create(get_coursemodule_from_instance('assign', $assign2->id));
 572          $assigninstance = new assign($cm->context, $cm, $course);
 573          $grade = $assigninstance->get_user_grade($student3->id, true);
 574          $grade->grade = 50;
 575          $assigninstance->update_grade($grade);
 576  
 577          $grade = $assigninstance->get_user_grade($student1->id, true);
 578          $grade->grade = 100;
 579          $assigninstance->update_grade($grade);
 580  
 581          // Make a manual grade items.
 582          $manuaitem = new \grade_item($this->getDataGenerator()->create_grade_item([
 583              'itemname'        => 'Grade item1',
 584              'idnumber'        => 'git1',
 585              'courseid'        => $course->id,
 586          ]));
 587          $manuaitem->update_final_grade($student1->id, 1);
 588          $manuaitem->update_final_grade($student3->id, 2);
 589  
 590          // Initialise report.
 591          $context = \context_course::instance($course->id);
 592  
 593          $gpr = new grade_plugin_return(
 594              [
 595                  'type' => 'report',
 596                  'plugin' => 'summary',
 597                  'course' => $course,
 598              ]
 599          );
 600  
 601          $report = new grade_report_summary($course->id, $gpr, $context);
 602  
 603          $ungradedcounts = $report->ungraded_counts();
 604          $ungradedcounts['report']['meanselection'] = $meanselection;
 605  
 606          $gradeitems = grade_item::fetch_all(['courseid' => $course->id]);
 607  
 608          foreach ($gradeitems as $gradeitem) {
 609              $name = $gradeitem->itemname . ' ' . $gradeitem->itemtype;
 610              $aggr = $report->calculate_average($gradeitem, $ungradedcounts);
 611  
 612              $this->assertEquals($expectedmeancount[$name], $aggr['meancount']);
 613              $this->assertEquals($expectedaverage[$name], $aggr['average']);
 614          }
 615      }
 616  
 617      /**
 618       * Data provider for test_calculate_average
 619       *
 620       * @return array of testing scenarios
 621       */
 622      public function calculate_average_data() : array {
 623          return [
 624              'Non-empty grades' => [
 625                  'meanselection' => 1,
 626                  'expectedmeancount' => [' course' => 3, 'Assignment 1 mod' => 2, 'Assignment 2 mod' => 2,
 627                      'Quiz 1 mod' => 0, 'Grade item1 manual' => 2],
 628                  'expectedaverage' => [' course' => 73.33333333333333, 'Assignment 1 mod' => 35.0,
 629                      'Assignment 2 mod' => 75.0, 'Quiz 1 mod' => null, 'Grade item1 manual' => 1.5],
 630              ],
 631              'All grades' => [
 632                  'meanselection' => 0,
 633                  'expectedmeancount' => [' course' => 3, 'Assignment 1 mod' => 3, 'Assignment 2 mod' => 3,
 634                      'Quiz 1 mod' => 3, 'Grade item1 manual' => 3],
 635                  'expectedaverage' => [' course' => 73.33333333333333, 'Assignment 1 mod' => 23.333333333333332,
 636                      'Assignment 2 mod' => 50.0, 'Quiz 1 mod' => null, 'Grade item1 manual' => 1.0],
 637              ],
 638          ];
 639      }
 640  
 641      /**
 642       * Tests for item types.
 643       *
 644       * @covers \grade_report::item_types
 645       */
 646      public function test_item_types() {
 647          $this->resetAfterTest(true);
 648  
 649          $course1 = $this->getDataGenerator()->create_course();
 650          $course2 = $this->getDataGenerator()->create_course();
 651  
 652          // Create activities.
 653          $this->getDataGenerator()->create_module('assign', ['course' => $course1->id]);
 654          $this->getDataGenerator()->create_module('assign', ['course' => $course1->id]);
 655          $this->getDataGenerator()->create_module('quiz', ['course' => $course1->id]);
 656  
 657          $this->getDataGenerator()->create_module('assign', ['course' => $course2->id]);
 658  
 659          // Create manual grade items.
 660          new \grade_item($this->getDataGenerator()->create_grade_item([
 661              'itemname'        => 'Grade item1',
 662              'idnumber'        => 'git1',
 663              'courseid'        => $course1->id,
 664          ]));
 665  
 666          new \grade_item($this->getDataGenerator()->create_grade_item([
 667              'itemname'        => 'Grade item2',
 668              'idnumber'        => 'git2',
 669              'courseid'        => $course2->id,
 670          ]));
 671  
 672          // Create a grade category (it should not be fetched by item_types).
 673          new \grade_category($this->getDataGenerator()
 674              ->create_grade_category(['courseid' => $course1->id]), false);
 675  
 676          // Initialise reports.
 677          $context = \context_course::instance($course1->id);
 678  
 679          $gpr = new grade_plugin_return(
 680              [
 681                  'type' => 'report',
 682                  'plugin' => 'summary',
 683                  'course' => $course1,
 684              ]
 685          );
 686  
 687          $report1 = new grade_report_summary($course1->id, $gpr, $context);
 688  
 689          $context = \context_course::instance($course2->id);
 690  
 691          $gpr = new grade_plugin_return(
 692              [
 693                  'type' => 'report',
 694                  'plugin' => 'summary',
 695                  'course' => $course2,
 696              ]
 697          );
 698  
 699          $report2 = new grade_report_summary($course2->id, $gpr, $context);
 700  
 701          $gradeitems1 = $report1->item_types();
 702          $gradeitems2 = $report2->item_types();
 703  
 704          $this->assertEquals(3, count($gradeitems1));
 705          $this->assertEquals(2, count($gradeitems2));
 706  
 707          $this->assertArrayHasKey('assign', $gradeitems1);
 708          $this->assertArrayHasKey('quiz', $gradeitems1);
 709          $this->assertArrayHasKey('manual', $gradeitems1);
 710  
 711          $this->assertArrayHasKey('assign', $gradeitems2);
 712          $this->assertArrayHasKey('manual', $gradeitems2);
 713      }
 714  
 715      /**
 716       * Test get_gradable_users() function.
 717       *
 718       * @covers ::get_gradable_users
 719       */
 720      public function test_get_gradable_users() {
 721          global $DB;
 722  
 723          $this->setAdminUser();
 724          $this->resetAfterTest(true);
 725  
 726          $roleteacher = $DB->get_record('role', ['shortname' => 'teacher'], '*', MUST_EXIST);
 727  
 728          // Create a course.
 729          $course = $this->getDataGenerator()->create_course();
 730          $coursecontext = \context_course::instance($course->id);
 731          // Create groups.
 732          $group1 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
 733          $group2 = $this->getDataGenerator()->create_group(['courseid' => $course->id]);
 734          // Create and enrol a teacher and some students into the course.
 735          $teacher = $this->getDataGenerator()->create_and_enrol($course, 'teacher');
 736          $student1 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 737          $student2 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 738          $student3 = $this->getDataGenerator()->create_and_enrol($course, 'student');
 739          // Add student1 and student2 to group1.
 740          $this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $student1->id]);
 741          $this->getDataGenerator()->create_group_member(['groupid' => $group1->id, 'userid' => $student2->id]);
 742          // Add student3 to group2.
 743          $this->getDataGenerator()->create_group_member(['groupid' => $group2->id, 'userid' => $student3->id]);
 744  
 745          // Perform a regrade before creating the report.
 746          grade_regrade_final_grades($course->id);
 747          // Should return all gradable users (only students).
 748          $gradableusers = get_gradable_users($course->id);
 749          $this->assertEqualsCanonicalizing([$student1->id, $student2->id, $student3->id], array_keys($gradableusers));
 750  
 751          // Now, let's suspend the enrolment of student2.
 752          $this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student', 'manual', 0, 0, ENROL_USER_SUSPENDED);
 753          // Should return only the active gradable users (student1 and student3).
 754          $gradableusers = \grade_report::get_gradable_users($course->id);
 755          $this->assertEqualsCanonicalizing([$student1->id, $student3->id], array_keys($gradableusers));
 756  
 757          // Give teacher 'viewsuspendedusers' capability and set a preference to display suspended users.
 758          assign_capability('moodle/course:viewsuspendedusers', CAP_ALLOW, $roleteacher->id, $coursecontext, true);
 759          set_user_preference('grade_report_showonlyactiveenrol', false, $teacher);
 760          accesslib_clear_all_caches_for_unit_testing();
 761  
 762          $this->setUser($teacher);
 763          // Should return all gradable users (including suspended enrolments).
 764          $gradableusers = \grade_report::get_gradable_users($course->id);
 765          $this->assertEqualsCanonicalizing([$student1->id, $student2->id, $student3->id], array_keys($gradableusers));
 766  
 767          // Reactivate the course enrolment of student2.
 768          $this->getDataGenerator()->enrol_user($student2->id, $course->id, 'student', 'manual', 0, 0, ENROL_USER_ACTIVE);
 769          $this->setAdminUser();
 770          // Should return all gradable users from group1 (student1 and student2).
 771          $gradableusers = \grade_report::get_gradable_users($course->id, $group1->id);
 772          $this->assertEqualsCanonicalizing([$student1->id, $student2->id], array_keys($gradableusers));
 773          // Should return all gradable users from group2 (student3).
 774          $gradableusers = \grade_report::get_gradable_users($course->id, $group2->id);
 775          $this->assertEqualsCanonicalizing([$student3->id], array_keys($gradableusers));
 776      }
 777  }