diff --git a/classes/activitymanager.php b/classes/activitymanager.php index 2bee8a5..faff30b 100644 --- a/classes/activitymanager.php +++ b/classes/activitymanager.php @@ -78,14 +78,20 @@ public function __construct(stdClass $course, stdClass $user, int $group = 0) { /** * Checks whether a given course module is completed (either by the user or at least one * of the users of the group, if group is set). - * + * Please be aware: If an activity has a passing grade set, the passing grade is only used + * to check completion when it is set as an completion requirement. Otherwise * @param \cm_info $cm course module to check */ public function is_completed(cm_info $cm): bool { foreach ($this->members as $member) { + $completionstate = $this->completion->get_data($cm, true, $member->id)->completionstate; if ( - $this->completion->get_data($cm, true, $member->id)->completionstate == COMPLETION_COMPLETE || - $this->completion->get_data($cm, true, $member->id)->completionstate == COMPLETION_COMPLETE_PASS + $completionstate == COMPLETION_COMPLETE || + $completionstate == COMPLETION_COMPLETE_PASS || + ( + intval($cm->completionpassgrade) == 0 && + $completionstate == COMPLETION_COMPLETE_FAIL + ) ) { return true; } @@ -114,7 +120,11 @@ public function get_completion_order(array $cms): array { foreach ($this->members as $member) { if ( $this->completion->get_data($cm, true, $member->id)->completionstate == COMPLETION_COMPLETE || - $this->completion->get_data($cm, true, $member->id)->completionstate == COMPLETION_COMPLETE_PASS + $this->completion->get_data($cm, true, $member->id)->completionstate == COMPLETION_COMPLETE_PASS || + ( + intval($cm->completionpassgrade) == 0 && + $this->completion->get_data($cm, true, $member->id)->completionstate == COMPLETION_COMPLETE_FAIL + ) ) { $completed = $this->completion->get_data($cm, true, $member->id)->timemodified; if (!isset($completiontime[$cm->id]) || $completed < $completiontime[$cm->id]) { diff --git a/tests/mod_learningmap_activitymanager_test.php b/tests/mod_learningmap_activitymanager_test.php new file mode 100644 index 0000000..c255abd --- /dev/null +++ b/tests/mod_learningmap_activitymanager_test.php @@ -0,0 +1,104 @@ +. + +namespace mod_learningmap; + +use course_modinfo; + +defined('MOODLE_INTERNAL') || die(); + +global $CFG; +require_once($CFG->dirroot . '/mod/learningmap/tests/mod_learningmap_testcase.php'); + +/** + * Unit test for mod_learningmap + * + * @package mod_learningmap + * @copyright 2021-2023, ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + * @group mod_learningmap + * @group mebis + * @covers \mod_learningmap\activitymanager + */ +class mod_learningmap_activitymanager_test extends mod_learningmap_testcase { + /** + * Activitymanager instance for testing. + * @var activitymanager $activitymanager + */ + protected $activitymanager; + + /** + * Test completion checking + * @return void + */ + public function test_is_completed(): void { + $this->resetAfterTest(); + $this->setAdminUser(); + $this->prepare(completion\custom_completion::COMPLETION_WITH_ONE_TARGET, true, true); + $this->activitymanager = new activitymanager($this->course, $this->users[0]); + $cm = $this->modinfo->get_cm($this->activities[0]->cmid); + $this->completion->set_module_viewed($cm, $this->users[0]->id); + // The module requires only viewing it. Should be complete now. + $this->assertEquals($this->activitymanager->is_completed($cm), true); + + $assign = $this->activities[7]; + $grades = []; + $grades[$this->users[0]->id] = (object)[ + 'rawgrade' => 90, 'userid' => $this->users[0]->id, + ]; + $assign->cmidnumber = null; + assign_grade_item_update($assign, $grades); + $cm = $this->modinfo->get_cm($this->activities[7]->cmid); + // The module requires only viewing it. Should be incomplete after grading, as it is not viewed yet. + $this->assertEquals($this->activitymanager->is_completed($cm), false); + $this->completion->set_module_viewed($cm, $this->users[0]->id); + // Should be complete after viewing it. + $this->assertEquals($this->activitymanager->is_completed($cm), true); + + $assign = $this->activities[8]; + $grades = []; + $grades[$this->users[0]->id] = (object)[ + 'rawgrade' => 1, 'userid' => $this->users[0]->id, + ]; + $assign->cmidnumber = $assign->cmid; + assign_grade_item_update($assign, $grades); + $cm = $this->modinfo->get_cm($this->activities[8]->cmid); + // The module requires viewing it and a passing grade. Should be incomplete after grading, as it is not viewed yet + // and it the grade is below the passing grade. + $this->assertEquals($this->activitymanager->is_completed($cm), false); + $this->completion->set_module_viewed($cm, $this->users[0]->id); + // Should still be incomplete, as passing grade is not reached. + $this->assertEquals($this->activitymanager->is_completed($cm), false); + $grades[$this->users[0]->id] = (object)[ + 'rawgrade' => 90, 'userid' => $this->users[0]->id, + ]; + $result = assign_grade_item_update($assign, $grades); + $this->assertEquals(0, $result); + // Should be complete as passing grade is reached now. + $this->assertEquals($this->activitymanager->is_completed($cm), true); + + $this->activitymanager = new activitymanager($this->course, $this->users[1], $this->group->id); + $cm = $this->modinfo->get_cm($this->activities[0]->cmid); + $this->assertEquals($this->activitymanager->is_completed($cm), true); + $cm = $this->modinfo->get_cm($this->activities[1]->cmid); + $this->assertEquals($this->activitymanager->is_completed($cm), false); + $cm = $this->modinfo->get_cm($this->activities[7]->cmid); + $this->assertEquals($this->activitymanager->is_completed($cm), true); + $cm = $this->modinfo->get_cm($this->activities[8]->cmid); + $this->assertEquals($this->activitymanager->is_completed($cm), true); + } +} diff --git a/tests/mod_learningmap_completion_test.php b/tests/mod_learningmap_completion_test.php index 286137c..5391853 100644 --- a/tests/mod_learningmap_completion_test.php +++ b/tests/mod_learningmap_completion_test.php @@ -18,7 +18,11 @@ use mod_learningmap\completion\custom_completion; defined('MOODLE_INTERNAL') || die(); -require_once(__DIR__ . '/../lib.php'); + +global $CFG; + +require_once($CFG->dirroot . '/mod/learningmap/lib.php'); +require_once($CFG->dirroot . '/mod/learningmap/tests/mod_learningmap_testcase.php'); /** * Unit test for mod_learningmap @@ -31,128 +35,7 @@ * @group mebis * @covers \mod_learningmap\completion\custom_completion */ -class mod_learningmap_completion_test extends \advanced_testcase { - /** - * The course used for testing - * - * @var \stdClass - */ - protected $course; - /** - * The learning map used for testing - * - * @var \stdClass - */ - protected $learningmap; - /** - * The activities linked in the learningmap - * - * @var array - */ - protected $activities; - /** - * The users used for testing - * - * @var array - */ - protected $users; - /** - * The group used for testing - * - * @var \stdClass - */ - protected $group; - /** - * Whether group mode is active - * - * @var boolean - */ - protected $groupmode; - /** - * The modinfo of the course - * - * @var \course_modinfo|null - */ - protected $modinfo; - /** - * The completion info of the course - * - * @var \completion_info - */ - protected $completion; - /** - * The cm_info object belonging to the learning map (differs from the learningmap record) - * - * @var \cm_info - */ - protected $cm; - /** - * Prepare testing environment - */ - /** - * Prepare testing environment - * @param int $completiontype Type for automatic completion - * @param bool $groupmode Whether to use group mode (defaults to false) - */ - public function prepare($completiontype, $groupmode = false): void { - global $DB; - $this->groupmode = $groupmode; - $this->course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); - $this->learningmap = $this->getDataGenerator()->create_module( - 'learningmap', - ['course' => $this->course, 'completion' => 2, 'completiontype' => $completiontype, - 'groupmode' => ($groupmode ? SEPARATEGROUPS : NOGROUPS), ] - ); - - $this->activities = []; - for ($i = 0; $i < 9; $i++) { - $this->activities[] = $this->getDataGenerator()->create_module( - 'page', - ['name' => 'A', 'content' => 'B', 'course' => $this->course, 'completion' => 2, 'completionview' => 1] - ); - $this->learningmap->placestore = str_replace(99990 + $i, $this->activities[$i]->cmid, $this->learningmap->placestore); - } - $DB->set_field('learningmap', 'placestore', $this->learningmap->placestore, ['id' => $this->learningmap->id]); - - $this->users[0] = $this->getDataGenerator()->create_user( - [ - 'email' => 'user1@example.com', - 'username' => 'user1', - ] - ); - $studentrole = $DB->get_record('role', ['shortname' => 'student']); - $this->getDataGenerator()->enrol_user($this->users[0]->id, $this->course->id, $studentrole->id); - if ($this->groupmode) { - $this->group = $this->getDataGenerator()->create_group(['courseid' => $this->course->id]); - $this->users[1] = $this->getDataGenerator()->create_user( - [ - 'email' => 'user2@example.com', - 'username' => 'user2', - ] - ); - $this->users[2] = $this->getDataGenerator()->create_user( - [ - 'email' => 'user3@example.com', - 'username' => 'user3', - ] - ); - $this->getDataGenerator()->enrol_user($this->users[1]->id, $this->course->id, $studentrole->id); - $this->getDataGenerator()->enrol_user($this->users[2]->id, $this->course->id, $studentrole->id); - $this->getDataGenerator()->create_group_member([ - 'userid' => $this->users[0]->id, - 'groupid' => $this->group->id, - ]); - $this->getDataGenerator()->create_group_member([ - 'userid' => $this->users[1]->id, - 'groupid' => $this->group->id, - ]); - } - - $this->modinfo = get_fast_modinfo($this->course, $this->users[0]->id); - $this->completion = new \completion_info($this->modinfo->get_course()); - $this->cm = $this->modinfo->get_cm($this->learningmap->cmid); - } - +class mod_learningmap_completion_test extends mod_learningmap_testcase { /** * Tests completiontype 1 in individual mode * diff --git a/tests/mod_learningmap_testcase.php b/tests/mod_learningmap_testcase.php new file mode 100644 index 0000000..b8824f3 --- /dev/null +++ b/tests/mod_learningmap_testcase.php @@ -0,0 +1,192 @@ +. + +namespace mod_learningmap; + +defined('MOODLE_INTERNAL') || die(); +require_once(__DIR__ . '/../lib.php'); + +/** + * Test class with common test methods for mod_learningmap + * + * @package mod_learningmap + * @copyright 2021-2022, ISB Bayern + * @author Stefan Hanauska + * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later + */ +abstract class mod_learningmap_testcase extends \advanced_testcase { + /** + * The course used for testing + * @var \stdClass + */ + protected $course; + + /** + * The learning map used for testing + * @var \stdClass + */ + protected $learningmap; + + /** + * The activities linked in the learningmap + * @var array + */ + protected $activities; + + /** + * The users used for testing + * @var array + */ + protected $users; + + /** + * The group used for testing + * @var \stdClass + */ + protected $group; + + /** + * Whether group mode is active + * @var boolean + */ + protected $groupmode; + + /** + * The modinfo of the course + * @var \course_modinfo|null + */ + protected $modinfo; + + /** + * The completion info of the course + * @var \completion_info + */ + protected $completion; + + /** + * The cm_info object belonging to the learning map (differs from the learningmap record) + * @var \cm_info + */ + protected $cm; + + /** + * Prepare testing environment + * @param int $completiontype Type for automatic completion + * @param bool $groupmode Whether to use group mode (defaults to false) + * @param bool $passinggrade Whether to use activities with passing grade + */ + public function prepare($completiontype, $groupmode = false, $passinggrade = false): void { + global $DB; + $this->groupmode = $groupmode; + $this->course = $this->getDataGenerator()->create_course(['enablecompletion' => 1]); + // Set up the learning map for this test. + $this->learningmap = $this->getDataGenerator()->create_module( + 'learningmap', + ['course' => $this->course, 'completion' => 2, 'completiontype' => $completiontype, + 'groupmode' => ($groupmode ? SEPARATEGROUPS : NOGROUPS), ] + ); + + $this->activities = []; + // Create activities for this test. If we test with passing grade, the last two activities + // will have a passing grade set. + for ($i = 0; $i < ($passinggrade ? 7 : 9); $i++) { + $this->activities[] = $this->getDataGenerator()->create_module( + 'page', + [ + 'name' => 'A', + 'content' => 'B', + 'course' => $this->course, + 'completion' => COMPLETION_TRACKING_AUTOMATIC, + 'completionview' => COMPLETION_VIEW_REQUIRED, + ] + ); + $this->learningmap->placestore = str_replace(99990 + $i, $this->activities[$i]->cmid, $this->learningmap->placestore); + } + if ($passinggrade) { + $assignrow = $this->getDataGenerator()->create_module('assign', [ + 'course' => $this->course->id, + 'name' => 'Assign without passinggrade completion', + 'completion' => COMPLETION_TRACKING_AUTOMATIC, + 'completionview' => COMPLETION_VIEW_REQUIRED, + 'gradefeedbackenabled' => true, + ]); + $assign = new \assign(\context_module::instance($assignrow->cmid), false, false); + $gradeitem = $assign->get_grade_item(); + \grade_object::set_properties($gradeitem, ['gradepass' => 50.0]); + $gradeitem->update(); + $this->activities[] = $assignrow; + $this->learningmap->placestore = str_replace(99997, $assignrow->cmid, $this->learningmap->placestore); + $assignrow = $this->getDataGenerator()->create_module('assign', [ + 'course' => $this->course->id, + 'name' => 'Assign with passinggrade completion', + 'completion' => COMPLETION_TRACKING_AUTOMATIC, + 'completionpassgrade' => true, + 'completionusegrade' => true, + 'completionview' => COMPLETION_VIEW_NOT_REQUIRED, + 'gradefeedbackenabled' => true, + ]); + $DB->set_field('course_modules', 'completiongradeitemnumber', 0, ['id' => $assignrow->cmid]); + rebuild_course_cache($this->course->id, true); + $assign = new \assign(\context_module::instance($assignrow->cmid), false, false); + $gradeitem = $assign->get_grade_item(); + \grade_object::set_properties($gradeitem, ['gradepass' => 50.0]); + $gradeitem->update(); + $this->activities[] = $assignrow; + $this->learningmap->placestore = str_replace(99998, $assignrow->cmid, $this->learningmap->placestore); + } + $DB->set_field('learningmap', 'placestore', $this->learningmap->placestore, ['id' => $this->learningmap->id]); + + $studentrole = $DB->get_record('role', ['shortname' => 'student']); + + $this->users[0] = $this->getDataGenerator()->create_user( + [ + 'email' => 'user1@example.com', + 'username' => 'user1', + ] + ); + $this->getDataGenerator()->enrol_user($this->users[0]->id, $this->course->id, $studentrole->id); + + if ($this->groupmode) { + $this->group = $this->getDataGenerator()->create_group(['courseid' => $this->course->id]); + $this->users[1] = $this->getDataGenerator()->create_user( + [ + 'email' => 'user2@example.com', + 'username' => 'user2', + ] + ); + $this->users[2] = $this->getDataGenerator()->create_user( + [ + 'email' => 'user3@example.com', + 'username' => 'user3', + ] + ); + $this->getDataGenerator()->enrol_user($this->users[1]->id, $this->course->id, $studentrole->id); + $this->getDataGenerator()->enrol_user($this->users[2]->id, $this->course->id, $studentrole->id); + $this->getDataGenerator()->create_group_member([ + 'userid' => $this->users[0]->id, + 'groupid' => $this->group->id, + ]); + $this->getDataGenerator()->create_group_member([ + 'userid' => $this->users[1]->id, + 'groupid' => $this->group->id, + ]); + } + + $this->modinfo = get_fast_modinfo($this->course, $this->users[0]->id); + $this->completion = new \completion_info($this->modinfo->get_course()); + $this->cm = $this->modinfo->get_cm($this->learningmap->cmid); + } +}