From 032f39aeefccb2ab89b9f71e37d17026b7ebd9f2 Mon Sep 17 00:00:00 2001 From: Sara Arjona Date: Wed, 29 Nov 2023 08:16:18 +0100 Subject: [PATCH] MDL-79726 completion: Overall should consider pass/fail states too In MDL-78744, when the overallcompletion state was added to be displayed in the activity card, only COMPLETE/INCOMPLETE status were considered. However, depending on the activity completion settings, there are a couple of states more that should also be included (COMPLETE_FAIL and COMPLETE_PASS). A new method has been added to the cm_completion_details class to calculate whether a module should be considered or not completed, based on their completion options and the current value for the overall complete state. --- completion/classes/cm_completion_details.php | 26 ++++ .../tests/cm_completion_details_test.php | 135 ++++++++++++++++++ completion/upgrade.txt | 4 + course/classes/output/activity_completion.php | 5 +- .../tests/behat/coursepage_completion.feature | 72 +++++++++- 5 files changed, 239 insertions(+), 3 deletions(-) diff --git a/completion/classes/cm_completion_details.php b/completion/classes/cm_completion_details.php index b879e35a882..72aed1ff239 100644 --- a/completion/classes/cm_completion_details.php +++ b/completion/classes/cm_completion_details.php @@ -193,6 +193,32 @@ class cm_completion_details { return (int)$this->completiondata->completionstate; } + /** + * Returns whether the overall completion state of this course module should be marked as complete or not. + * This is based on the completion settings of the course module, so when the course module requires a passing grade, + * it will only be marked as complete when the user has passed the course module. Otherwise, it will be marked as complete + * even when the user has failed the course module. + * + * @return bool True when the module can be marked as completed. + */ + public function is_overall_complete(): bool { + $completionstates = []; + if ($this->is_manual()) { + $completionstates = [COMPLETION_COMPLETE]; + } else if ($this->is_automatic()) { + // Successfull completion states depend on the completion settings. + if (isset($this->completiondata->passgrade)) { + // Passing grade is required. Don't mark it as complete when state is COMPLETION_COMPLETE_FAIL. + $completionstates = [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS]; + } else { + // Any grade is required. Mark it as complete even when state is COMPLETION_COMPLETE_FAIL. + $completionstates = [COMPLETION_COMPLETE, COMPLETION_COMPLETE_PASS, COMPLETION_COMPLETE_FAIL]; + } + } + + return in_array($this->get_overall_completion(), $completionstates); + } + /** * Whether this activity module has completion enabled. * diff --git a/completion/tests/cm_completion_details_test.php b/completion/tests/cm_completion_details_test.php index b4379877a85..5fca54e8ee8 100644 --- a/completion/tests/cm_completion_details_test.php +++ b/completion/tests/cm_completion_details_test.php @@ -223,6 +223,141 @@ class cm_completion_details_test extends advanced_testcase { $this->assertEquals($state, $cmcompletion->get_overall_completion()); } + /** + * Data provider for test_is_overall_complete(). + * @return array[] + */ + public static function is_overall_complete_provider(): array { + return [ + 'Automatic, require view, not viewed' => [ + 'expected' => false, + 'completion' => COMPLETION_TRACKING_AUTOMATIC, + 'completionstate' => COMPLETION_INCOMPLETE, + 'completionview' => COMPLETION_INCOMPLETE, + 'completiongrade' => null, + 'completionpassgrade' => null, + ], + 'Automatic, require view, viewed' => [ + 'expected' => true, + 'completion' => COMPLETION_TRACKING_AUTOMATIC, + 'completionstate' => COMPLETION_COMPLETE, + 'completionview' => COMPLETION_COMPLETE, + 'completiongrade' => null, + 'completionpassgrade' => null, + ], + 'Automatic, require grade, not graded' => [ + 'expected' => false, + 'completion' => COMPLETION_TRACKING_AUTOMATIC, + 'completionstate' => COMPLETION_INCOMPLETE, + 'completionview' => null, + 'completiongrade' => COMPLETION_INCOMPLETE, + 'completionpassgrade' => null, + ], + 'Automatic, require grade, graded with fail' => [ + 'expected' => true, + 'completion' => COMPLETION_TRACKING_AUTOMATIC, + 'completionstate' => COMPLETION_COMPLETE_FAIL, + 'completionview' => null, + 'completiongrade' => COMPLETION_COMPLETE_FAIL, + 'completionpassgrade' => null, + ], + 'Automatic, require grade, graded with passing' => [ + 'expected' => true, + 'completion' => COMPLETION_TRACKING_AUTOMATIC, + 'completionstate' => COMPLETION_COMPLETE_PASS, + 'completionview' => null, + 'completiongrade' => COMPLETION_COMPLETE_PASS, + 'completionpassgrade' => null, + ], + 'Automatic, require passgrade, not graded' => [ + 'expected' => false, + 'completion' => COMPLETION_TRACKING_AUTOMATIC, + 'completionstate' => COMPLETION_INCOMPLETE, + 'completionview' => null, + 'completiongrade' => null, + 'completionpassgrade' => COMPLETION_INCOMPLETE, + ], + 'Automatic, require passgrade, graded with fail' => [ + 'expected' => false, + 'completion' => COMPLETION_TRACKING_AUTOMATIC, + 'completionstate' => COMPLETION_COMPLETE_FAIL, + 'completionview' => null, + 'completiongrade' => null, + 'completionpassgrade' => COMPLETION_COMPLETE_FAIL, + ], + 'Automatic, require passgrade, graded with passing' => [ + 'expected' => true, + 'completion' => COMPLETION_TRACKING_AUTOMATIC, + 'completionstate' => COMPLETION_COMPLETE_PASS, + 'completionview' => null, + 'completiongrade' => null, + 'completionpassgrade' => COMPLETION_COMPLETE_PASS, + ], + 'Manual, incomplete' => [ + 'expected' => false, + 'completion' => COMPLETION_TRACKING_MANUAL, + 'completionstate' => COMPLETION_INCOMPLETE, + ], + 'Manual, complete' => [ + 'expected' => true, + 'completion' => COMPLETION_TRACKING_MANUAL, + 'completionstate' => COMPLETION_COMPLETE, + ], + 'None, incomplete' => [ + 'expected' => false, + 'completion' => COMPLETION_TRACKING_NONE, + 'completionstate' => COMPLETION_INCOMPLETE, + ], + 'None, complete' => [ + 'expected' => false, + 'completion' => COMPLETION_TRACKING_NONE, + 'completionstate' => COMPLETION_COMPLETE, + ], + ]; + } + + /** + * Test for is_overall_complete(). + * + * @covers ::is_overall_complete + * @dataProvider is_overall_complete_provider + * @param bool $expected Expected result returned by is_overall_complete(). + * @param int $completion The completion tracking mode. + * @param int $completionstate The overall completion state. + * @param int|null $completionview Completion status of the "view" completion condition. + * @param int|null $completiongrade Completion status of the "must receive grade" completion condition. + * @param int|null $completionpassgrade Completion status of the "must receive passing grade" completion condition. + */ + public function test_is_overall_complete( + bool $expected, + int $completion, + int $completionstate, + ?int $completionview = null, + ?int $completiongrade = null, + ?int $completionpassgrade = null, + ): void { + $options = []; + $getdatareturn = (object)[ + 'completionstate' => $completionstate, + 'viewed' => $completionview, + 'completiongrade' => $completiongrade, + 'passgrade' => $completionpassgrade, + ]; + + if (!is_null($completionview)) { + $options['completionview'] = true; + } + if (!is_null($completiongrade)) { + $options['completionusegrade'] = true; + } + if (!is_null($completionpassgrade)) { + $options['completionpassgrade'] = true; + } + + $cmcompletion = $this->setup_data($completion, $options, $getdatareturn); + $this->assertEquals($expected, $cmcompletion->is_overall_complete()); + } + /** * Data provider for test_get_details(). * @return array[] diff --git a/completion/upgrade.txt b/completion/upgrade.txt index b19fd6dac56..7b40b722976 100644 --- a/completion/upgrade.txt +++ b/completion/upgrade.txt @@ -1,6 +1,10 @@ This file describes API changes in /completion/* - completion, information provided here is intended especially for developers. +=== 4.4 === +* A new method, cm_completion_details::is_overall_complete() has been added to calculate whether a module should be considered or + not completed, based on their completion options and the current value for the overall complete state. + === 4.3 === * A trait class, core_completion/form/form_trait has been added to reuse code for adding and validation completion settings to any form. diff --git a/course/classes/output/activity_completion.php b/course/classes/output/activity_completion.php index ffa74ce6175..b2475902edc 100644 --- a/course/classes/output/activity_completion.php +++ b/course/classes/output/activity_completion.php @@ -57,6 +57,7 @@ class activity_completion implements renderable, templatable { global $CFG; $overallcompletion = $this->cmcompletion->get_overall_completion(); + $isoverallcomplete = $this->cmcompletion->is_overall_complete(); $overrideby = $this->get_overrideby(); $course = $this->cminfo->get_course(); @@ -77,8 +78,8 @@ class activity_completion implements renderable, templatable { 'ismanual' => $this->cmcompletion->is_manual(), 'showmanualcompletion' => $this->cmcompletion->show_manual_completion(), 'istrackeduser' => $this->cmcompletion->is_tracked_user(), - 'overallcomplete' => $overallcompletion == COMPLETION_COMPLETE, - 'overallincomplete' => $overallcompletion == COMPLETION_INCOMPLETE, + 'overallcomplete' => $isoverallcomplete, + 'overallincomplete' => !$isoverallcomplete, 'withavailability' => $withavailability ?? false, 'overrideby' => $overrideby, 'completiondetails' => $this->get_completion_details($overrideby), diff --git a/course/format/tests/behat/coursepage_completion.feature b/course/format/tests/behat/coursepage_completion.feature index 597f72465e2..263048c4095 100644 --- a/course/format/tests/behat/coursepage_completion.feature +++ b/course/format/tests/behat/coursepage_completion.feature @@ -56,7 +56,7 @@ Feature: Course page activities completion And I am on the "Course 1" course page And "Completion" "button" should exist in the "Activity sample" "activity" - Scenario: Student should see the automatic completion criterias statuses of activities + Scenario: Student should see the automatic completion criterias statuses of activities with completion view Given the following "activity" exists: | activity | assign | | name | Activity sample | @@ -71,6 +71,76 @@ Feature: Course page activities completion And "To do" "button" should not exist in the "Activity sample" "activity" And the "View" item should exist in the "Done" dropdown of the "Activity sample" "activity" + Scenario: Student should see the automatic completion criterias statuses of activities with completion grade + Given the following "activities" exist: + | activity | name | course | idnumber | gradepass | completion | completionusegrade | + | quiz | Activity sample 1 | C1 | quiz1 | 5.00 | 2 | 1 | + | quiz | Activity sample 2 | C1 | quiz2 | 5.00 | 2 | 1 | + And the following "question categories" exist: + | contextlevel | reference | name | + | Course | C1 | Test questions | + And the following "questions" exist: + | questioncategory | qtype | name | questiontext | + | Test questions | truefalse | First question | Answer the first question | + And quiz "Activity sample 1" contains the following questions: + | question | page | + | First question | 1 | + And quiz "Activity sample 2" contains the following questions: + | question | page | + | First question | 1 | + When I am on the "C1" "Course" page logged in as "student1" + Then the "Receive a grade" item should exist in the "To do" dropdown of the "Activity sample 1" "activity" + And the "Receive a grade" item should exist in the "To do" dropdown of the "Activity sample 2" "activity" + # Pass grade. + And user "student1" has attempted "Activity sample 1" with responses: + | slot | response | + | 1 | True | + # Fail grade. + And user "student1" has attempted "Activity sample 2" with responses: + | slot | response | + | 1 | False | + # After receiving a grade, the completion criteria dropdown should display "Done" instead of "To do", regardless of pass/fail. + And I am on the "Course 1" course page + And "To do" "button" should not exist in the "Activity sample 1" "activity" + And the "Receive a grade" item should exist in the "Done" dropdown of the "Activity sample 1" "activity" + And "To do" "button" should not exist in the "Activity sample 2" "activity" + And the "Receive a grade" item should exist in the "Done" dropdown of the "Activity sample 2" "activity" + + Scenario: Student should see the automatic completion criterias statuses of activities with completion passgrade + Given the following "activities" exist: + | activity | name | course | idnumber | gradepass | completion | completionusegrade | completionpassgrade | + | quiz | Activity sample 1 | C1 | quiz1 | 5.00 | 2 | 1 | 1 | + | quiz | Activity sample 2 | C1 | quiz2 | 5.00 | 2 | 1 | 1 | + And the following "question categories" exist: + | contextlevel | reference | name | + | Course | C1 | Test questions | + And the following "questions" exist: + | questioncategory | qtype | name | questiontext | + | Test questions | truefalse | First question | Answer the first question | + And quiz "Activity sample 1" contains the following questions: + | question | page | + | First question | 1 | + And quiz "Activity sample 2" contains the following questions: + | question | page | + | First question | 1 | + When I am on the "C1" "Course" page logged in as "student1" + Then the "Receive a grade" item should exist in the "To do" dropdown of the "Activity sample 1" "activity" + And the "Receive a grade" item should exist in the "To do" dropdown of the "Activity sample 2" "activity" + # Pass grade. + And user "student1" has attempted "Activity sample 1" with responses: + | slot | response | + | 1 | True | + # Fail grade. + And user "student1" has attempted "Activity sample 2" with responses: + | slot | response | + | 1 | False | + # After receiving a grade, the completion criteria dropdown should display "Done" only for the passing grade. + And I am on the "Course 1" course page + And "To do" "button" should not exist in the "Activity sample 1" "activity" + And the "Receive a grade" item should exist in the "Done" dropdown of the "Activity sample 1" "activity" + But "To do" "button" should exist in the "Activity sample 2" "activity" + And the "Receive a grade" item should exist in the "To do" dropdown of the "Activity sample 2" "activity" + Scenario: Teacher can edit activity completion using completion dialog link Given the following "activity" exists: | activity | assign |