See Release Notes
Long Term Support Release
<?php // This file is part of Moodle - http://moodle.org/ // // Moodle is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // Moodle is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with Moodle. If not, see <http://www.gnu.org/licenses/>. /** * mod_h5pactivity grader tests * * @package mod_h5pactivity * @category test * @copyright 2020 Ferran Recio <ferran@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */ namespace mod_h5pactivity\local; use grade_item; use stdClass; /** * Grader tests class for mod_h5pactivity. * * @package mod_h5pactivity * @category test * @copyright 2020 Ferran Recio <ferran@moodle.com> * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later */< class grader_testcase extends \advanced_testcase {> class grader_test extends \advanced_testcase {/** * Setup to ensure that fixtures are loaded. */ public static function setupBeforeClass(): void { global $CFG; require_once($CFG->libdir.'/gradelib.php'); } /** * Test for grade item delete. */ public function test_grade_item_delete() { $this->resetAfterTest(); $this->setAdminUser(); $course = $this->getDataGenerator()->create_course(); $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]); $user = $this->getDataGenerator()->create_and_enrol($course, 'student'); $grader = new grader($activity); // Force a user grade. $this->generate_fake_attempt($activity, $user, 5, 10); $grader->update_grades($user->id); $gradeinfo = grade_get_grades($course->id, 'mod', 'h5pactivity', $activity->id, $user->id); $this->assertNotEquals(0, count($gradeinfo->items)); $this->assertArrayHasKey($user->id, $gradeinfo->items[0]->grades); $grader->grade_item_delete(); $gradeinfo = grade_get_grades($course->id, 'mod', 'h5pactivity', $activity->id, $user->id); $this->assertEquals(0, count($gradeinfo->items)); } /** * Test for grade item update. * * @dataProvider grade_item_update_data * @param int $newgrade new activity grade * @param bool $reset if has to reset grades * @param string $idnumber the new idnumber */ public function test_grade_item_update(int $newgrade, bool $reset, string $idnumber) { $this->resetAfterTest(); $this->setAdminUser(); $course = $this->getDataGenerator()->create_course(); $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]); $user = $this->getDataGenerator()->create_and_enrol($course, 'student'); // Force a user initial grade. $grader = new grader($activity); $this->generate_fake_attempt($activity, $user, 5, 10); $grader->update_grades($user->id); $gradeinfo = grade_get_grades($course->id, 'mod', 'h5pactivity', $activity->id, $user->id); $this->assertNotEquals(0, count($gradeinfo->items)); $item = array_shift($gradeinfo->items); $this->assertArrayHasKey($user->id, $item->grades); $this->assertEquals(50, round($item->grades[$user->id]->grade)); // Module grade value determine the way gradebook acts. That means that the expected // result depends on this value. // - Grade > 0: regular max grade value. // - Grade = 0: no grading is used (but grademax remains the same). // - Grade < 0: a scaleid is used (value = -scaleid). if ($newgrade > 0) { $grademax = $newgrade; $scaleid = null; $usergrade = ($newgrade > 50) ? 50 : $newgrade; } else if ($newgrade == 0) { $grademax = 100; $scaleid = null; $usergrade = null; // No user grades expected. } else if ($newgrade < 0) { $scale = $this->getDataGenerator()->create_scale(array("scale" => "value1, value2, value3")); $newgrade = -1 * $scale->id; $grademax = 3; $scaleid = $scale->id; $usergrade = 3; // 50 value will ve converted to "value 3" on scale. } // Update grade item. $activity->grade = $newgrade; // In case a reset is need, usergrade will be empty. if ($reset) { $param = 'reset'; $usergrade = null; } else { // Individual user gradings will be tested as a subcall of update_grades. $param = null; } $grader = new grader($activity, $idnumber); $grader->grade_item_update($param); // Check new grade item and grades. $gradeinfo = grade_get_grades($course->id, 'mod', 'h5pactivity', $activity->id, $user->id); $item = array_shift($gradeinfo->items); $this->assertEquals($scaleid, $item->scaleid); $this->assertEquals($grademax, $item->grademax); $this->assertArrayHasKey($user->id, $item->grades); if ($usergrade) { $this->assertEquals($usergrade, round($item->grades[$user->id]->grade)); } else { $this->assertEmpty($item->grades[$user->id]->grade); } if (!empty($idnumber)) { $gradeitem = grade_item::fetch(['idnumber' => $idnumber, 'courseid' => $course->id]); $this->assertInstanceOf('grade_item', $gradeitem); } } /** * Data provider for test_grade_item_update. * * @return array */ public function grade_item_update_data(): array { return [ 'Change idnumber' => [ 100, false, 'newidnumber' ], 'Increase max grade to 110' => [ 110, false, '' ], 'Decrease max grade to 80' => [ 40, false, '' ], 'Decrease max grade to 40 (less than actual grades)' => [ 40, false, '' ], 'Reset grades' => [ 100, true, '' ], 'Disable grades' => [ 0, false, '' ], 'Use scales' => [ -1, false, '' ], 'Use scales with reset' => [ -1, true, '' ], ]; } /** * Test for grade update. * * @dataProvider update_grades_data * @param int $newgrade the new activity grade * @param bool $all if has to be applied to all students or just to one * @param int $completion 1 all student have the activity completed, 0 one have incompleted * @param array $results expected results (user1 grade, user2 grade) */ public function test_update_grades(int $newgrade, bool $all, int $completion, array $results) { $this->resetAfterTest(); $this->setAdminUser(); $course = $this->getDataGenerator()->create_course(); $activity = $this->getDataGenerator()->create_module('h5pactivity', ['course' => $course]); $user1 = $this->getDataGenerator()->create_and_enrol($course, 'student'); $user2 = $this->getDataGenerator()->create_and_enrol($course, 'student'); // Force a user initial grade. $grader = new grader($activity); $this->generate_fake_attempt($activity, $user1, 5, 10); $this->generate_fake_attempt($activity, $user2, 3, 12, $completion); $grader->update_grades(); $gradeinfo = grade_get_grades($course->id, 'mod', 'h5pactivity', $activity->id, [$user1->id, $user2->id]); $this->assertNotEquals(0, count($gradeinfo->items)); $item = array_shift($gradeinfo->items); $this->assertArrayHasKey($user1->id, $item->grades); $this->assertArrayHasKey($user2->id, $item->grades); $this->assertEquals(50, $item->grades[$user1->id]->grade); // Uncompleted attempts does not generate grades. if ($completion) { $this->assertEquals(25, $item->grades[$user2->id]->grade); } else { $this->assertNull($item->grades[$user2->id]->grade); } // Module grade value determine the way gradebook acts. That means that the expected // result depends on this value. // - Grade > 0: regular max grade value. // - Grade <= 0: no grade calculation is used (scale and no grading). if ($newgrade < 0) { $scale = $this->getDataGenerator()->create_scale(array("scale" => "value1, value2, value3")); $activity->grade = -1 * $scale->id; } else { $activity->grade = $newgrade; } $userid = ($all) ? 0 : $user1->id; $grader = new grader($activity); $grader->update_grades($userid); // Check new grade item and grades. $gradeinfo = grade_get_grades($course->id, 'mod', 'h5pactivity', $activity->id, [$user1->id, $user2->id]); $item = array_shift($gradeinfo->items); $this->assertArrayHasKey($user1->id, $item->grades); $this->assertArrayHasKey($user2->id, $item->grades); $this->assertEquals($results[0], $item->grades[$user1->id]->grade); $this->assertEquals($results[1], $item->grades[$user2->id]->grade); } /** * Data provider for test_grade_item_update. * * @return array */ public function update_grades_data(): array { return [ // Quantitative grade, all attempts completed. 'Same grademax, all users, all completed' => [ 100, true, 1, [50, 25] ], 'Same grademax, one user, all completed' => [ 100, false, 1, [50, 25] ], 'Increade max, all users, all completed' => [ 200, true, 1, [100, 50] ], 'Increade max, one user, all completed' => [ 200, false, 1, [100, 25] ], 'Decrease max, all users, all completed' => [ 50, true, 1, [25, 12.5] ], 'Decrease max, one user, all completed' => [ 50, false, 1, [25, 25] ], // Quantitative grade, some attempts not completed. 'Same grademax, all users, not completed' => [ 100, true, 0, [50, null] ], 'Same grademax, one user, not completed' => [ 100, false, 0, [50, null] ], 'Increade max, all users, not completed' => [ 200, true, 0, [100, null] ], 'Increade max, one user, not completed' => [ 200, false, 0, [100, null] ], 'Decrease max, all users, not completed' => [ 50, true, 0, [25, null] ], 'Decrease max, one user, not completed' => [ 50, false, 0, [25, null] ], // No grade (no grade will be used). 'No grade, all users, all completed' => [ 0, true, 1, [null, null] ], 'No grade, one user, all completed' => [ 0, false, 1, [null, null] ], 'No grade, all users, not completed' => [ 0, true, 0, [null, null] ], 'No grade, one user, not completed' => [ 0, false, 0, [null, null] ], // Scale (grate item will updated but without regrading). 'Scale, all users, all completed' => [ -1, true, 1, [3, 3] ], 'Scale, one user, all completed' => [ -1, false, 1, [3, 3] ], 'Scale, all users, not completed' => [ -1, true, 0, [3, null] ], 'Scale, one user, not completed' => [ -1, false, 0, [3, null] ], ]; } /** * Create a fake attempt for a specific user. * * @param stdClass $activity activity instance record. * @param stdClass $user user record * @param int $rawscore score obtained * @param int $maxscore attempt max score * @param int $completion 1 for activity completed, 0 for not completed yet * @return stdClass the attempt record */ private function generate_fake_attempt(stdClass $activity, stdClass $user, int $rawscore, int $maxscore, int $completion = 1): stdClass { global $DB; $attempt = (object)[ 'h5pactivityid' => $activity->id, 'userid' => $user->id, 'timecreated' => 10, 'timemodified' => 20, 'attempt' => 1, 'rawscore' => $rawscore, 'maxscore' => $maxscore, 'duration' => 2, 'completion' => $completion, 'success' => 0, ]; $attempt->scaled = $attempt->rawscore / $attempt->maxscore; $attempt->id = $DB->insert_record('h5pactivity_attempts', $attempt); return $attempt; } }