MDL-74533 core_completion: Update using new grade pass for completion

This commit is contained in:
hieuvu
2022-04-21 15:45:40 +07:00
parent 941a29925e
commit 5cfaa18cad
3 changed files with 253 additions and 10 deletions
+1 -2
View File
@@ -678,6 +678,7 @@ function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
if (core_tag_tag::is_enabled('core', 'course_modules') && isset($moduleinfo->tags)) {
core_tag_tag::set_item_tags('core', 'course_modules', $moduleinfo->coursemodule, $modcontext, $moduleinfo->tags);
}
$moduleinfo = edit_module_post_actions($moduleinfo, $course);
// Now that module is fully updated, also update completion data if required.
// (this will wipe all user completion data and recalculate it)
@@ -691,8 +692,6 @@ function update_moduleinfo($cm, $moduleinfo, $course, $mform = null) {
$cm->name = $moduleinfo->name;
\core\event\course_module_updated::create_from_cm($cm, $modcontext)->trigger();
$moduleinfo = edit_module_post_actions($moduleinfo, $course);
return array($cm, $moduleinfo);
}
@@ -0,0 +1,67 @@
@mod @mod_quiz @core_completion
Feature: Activity completion in the quiz activity with unlocked and re-grading.
In order to have visibility of quiz completion requirements
As a student
I need to be able to view my quiz completion progress even teacher have re-grading the grade pass.
Background:
Given the following "users" exist:
| username | firstname | lastname | email |
| student1 | Student | 1 | student1@example.com |
| teacher1 | Teacher | 1 | teacher1@example.com |
And the following "courses" exist:
| fullname | shortname | category | enablecompletion |
| Course 1 | C1 | 0 | 1 |
And the following "course enrolments" exist:
| user | course | role |
| teacher1 | C1 | editingteacher |
| student1 | C1 | student |
And the following config values are set as admin:
| grade_item_advanced | hiddenuntil |
And the following "question categories" exist:
| contextlevel | reference | name |
| Course | C1 | Test questions |
And the following "questions" exist:
| questioncategory | qtype | name | questiontext | defaultmark |
| Test questions | truefalse | First question | Answer the first question | 8 |
| Test questions | truefalse | Second question | Answer the second question | 2 |
And the following "activity" exists:
| activity | quiz |
| course | C1 |
| idnumber | quiz1 |
| name | Test quiz name |
| section | 1 |
| gradepass | 10.00 |
| grade | 10 |
| grademethod | 1 |
| completion | 2 |
| completionusegrade | 1 |
| completionpassgrade | 1 |
And quiz "Test quiz name" contains the following questions:
| question | page |
| First question | 1 |
| Second question | 2 |
@javascript
Scenario: Student will receive correct completion even when teacher unlocked completion and re-grading.
Given I am on the "Test quiz name" "quiz activity" page logged in as student1
And the "Receive a grade" completion condition of "Test quiz name" is displayed as "todo"
And the "Receive a passing grade" completion condition of "Test quiz name" is displayed as "todo"
And user "student1" has attempted "Test quiz name" with responses:
| slot | response |
| 1 | True |
| 2 | False |
And I am on "Course 1" course homepage
And I follow "Test quiz name"
And the "Receive a grade" completion condition of "Test quiz name" is displayed as "done"
And the "Receive a passing grade" completion condition of "Test quiz name" is displayed as "failed"
And I log out
When I am on the "Test quiz name" "quiz activity" page logged in as teacher1
And I navigate to "Settings" in current page administration
And I expand all fieldsets
And I press "Unlock completion options"
And I set the following fields to these values:
| gradepass | 8 |
And I press "Save and return to course"
And I navigate to "Reports > Activity completion" in current page administration
Then "Completed (achieved pass grade)" "icon" should exist in the "Student 1" "table_row"
+185 -8
View File
@@ -46,7 +46,7 @@ class custom_completion_test extends advanced_testcase {
* Setup function for all tests.
*
* @param array $completionoptions ['nbstudents'] => int, ['qtype'] => string, ['quizoptions'] => array
* @return array [$students, $quiz, $cm]
* @return array [$students, $quiz, $cm, $litecm]
*/
private function setup_quiz_for_testing_completion(array $completionoptions): array {
global $CFG, $DB;
@@ -60,6 +60,8 @@ class custom_completion_test extends advanced_testcase {
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$course = $this->getDataGenerator()->create_course(['enablecompletion' => true]);
$students = [];
$sumgrades = $completionoptions['sumgrades'] ?? 1;
$nbquestions = $completionoptions['nbquestions'] ?? 1;
for ($i = 0; $i < $completionoptions['nbstudents']; $i++) {
$students[$i] = $this->getDataGenerator()->create_user();
$this->assertTrue($this->getDataGenerator()->enrol_user($students[$i]->id, $course->id, $studentrole->id));
@@ -71,30 +73,36 @@ class custom_completion_test extends advanced_testcase {
'course' => $course->id,
'grade' => 100.0,
'questionsperpage' => 0,
'sumgrades' => 1,
'sumgrades' => $sumgrades,
'completion' => COMPLETION_TRACKING_AUTOMATIC
], $completionoptions['quizoptions']);
$quiz = $quizgenerator->create_instance($data);
$cm = get_coursemodule_from_id('quiz', $quiz->cmid);
$cm = cm_info::create($cm);
$litecm = get_coursemodule_from_id('quiz', $quiz->cmid);
$cm = cm_info::create($litecm);
// Create a question.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$question = $questiongenerator->create_question($completionoptions['qtype'], null, ['category' => $cat->id]);
quiz_add_quiz_question($question->id, $quiz);
for ($i = 0; $i < $nbquestions; $i++) {
$overrideparams = ['category' => $cat->id];
if (isset($completionoptions['questiondefaultmarks'][$i])) {
$overrideparams['defaultmark'] = $completionoptions['questiondefaultmarks'][$i];
}
$question = $questiongenerator->create_question($completionoptions['qtype'], null, $overrideparams);
quiz_add_quiz_question($question->id, $quiz);
}
// Set grade to pass.
$item = grade_item::fetch(['courseid' => $course->id, 'itemtype' => 'mod', 'itemmodule' => 'quiz',
'iteminstance' => $quiz->id, 'outcomeid' => null]);
$item->gradepass = 80;
$item->update();
return [
$students,
$quiz,
$cm
$cm,
$litecm
];
}
@@ -324,4 +332,173 @@ class custom_completion_test extends advanced_testcase {
$rules
);
}
/**
* Test update moduleinfo.
*
* @covers \update_moduleinfo
*/
public function test_update_moduleinfo() {
$this->setAdminUser();
// We need lite cm object not a full cm because update_moduleinfo is not allow some properties to be updated.
list($students, $quiz, $cm, $litecm) = $this->setup_quiz_for_testing_completion([
'nbstudents' => 1,
'qtype' => 'numerical',
'nbquestions' => 2,
'sumgrades' => 100,
'questiondefaultmarks' => [20, 80],
'quizoptions' => [
'completionusegrade' => 1,
'completionpassgrade' => 1,
'completionview' => 0,
]
]);
$course = $cm->get_course();
list($student) = $students;
// Do a first attempt with a pass marks = 20.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $student,
'attemptnumber' => 1,
'tosubmit' => [1 => ['answer' => '3.14']]
]);
$completioninfo = new \completion_info($course);
$cminfo = \cm_info::create($cm);
$completiondetails = new cm_completion_details($completioninfo, $cminfo, (int) $student->id);
// Check the results. Completion is fail because gradepass = 80.
$this->assertEquals(COMPLETION_COMPLETE_FAIL, $completiondetails->get_details()['completionpassgrade']->status);
$this->assertEquals(
'Receive a passing grade',
$completiondetails->get_details()['completionpassgrade']->description
);
// Update quiz with passgrade = 20 and use highest grade to calculate.
$moduleinfo = $this->prepare_module_info($cm, $quiz, $course, 20, QUIZ_GRADEHIGHEST);
update_moduleinfo($litecm, $moduleinfo, $course, null);
$completiondetails = new cm_completion_details($completioninfo, $cminfo, (int) $student->id);
// Check the results. Completion is pass.
$this->assertEquals(COMPLETION_COMPLETE_PASS, $completiondetails->get_details()['completionpassgrade']->status);
$this->assertEquals(
'Receive a passing grade',
$completiondetails->get_details()['completionpassgrade']->description
);
// Do a second attempt with pass marks = 80.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $student,
'attemptnumber' => 2,
'tosubmit' => [2 => ['answer' => '3.14']]
]);
// Update quiz with gradepass = 80 and use highest grade to calculate completion.
$moduleinfo = $this->prepare_module_info($cm, $quiz, $course, 80, QUIZ_GRADEHIGHEST);
update_moduleinfo($litecm, $moduleinfo, $course, null);
$completiondetails = new cm_completion_details($completioninfo, $cminfo, (int) $student->id);
// Check the results. Completion is pass.
$this->assertEquals(COMPLETION_COMPLETE_PASS, $completiondetails->get_details()['completionpassgrade']->status);
$this->assertEquals(
'Receive a passing grade',
$completiondetails->get_details()['completionpassgrade']->description
);
// Update quiz with gradepass = 80 and use average grade to calculate completion.
$moduleinfo = $this->prepare_module_info($cm, $quiz, $course, 80, QUIZ_GRADEAVERAGE);
update_moduleinfo($litecm, $moduleinfo, $course, null);
$completiondetails = new cm_completion_details($completioninfo, $cminfo, (int) $student->id);
// Check the results. Completion is fail because student grade = 50.
$this->assertEquals(COMPLETION_COMPLETE_FAIL, $completiondetails->get_details()['completionpassgrade']->status);
$this->assertEquals(
'Receive a passing grade',
$completiondetails->get_details()['completionpassgrade']->description
);
// Update quiz with gradepass = 50 and use average grade to calculate completion.
$moduleinfo = $this->prepare_module_info($cm, $quiz, $course, 50, QUIZ_GRADEAVERAGE);
update_moduleinfo($litecm, $moduleinfo, $course, null);
$completiondetails = new cm_completion_details($completioninfo, $cminfo, (int) $student->id);
// Check the results. Completion is pass.
$this->assertEquals(COMPLETION_COMPLETE_PASS, $completiondetails->get_details()['completionpassgrade']->status);
$this->assertEquals(
'Receive a passing grade',
$completiondetails->get_details()['completionpassgrade']->description
);
// Update quiz with gradepass = 50 and use first attempt grade to calculate completion.
$moduleinfo = $this->prepare_module_info($cm, $quiz, $course, 50, QUIZ_ATTEMPTFIRST);
update_moduleinfo($litecm, $moduleinfo, $course, null);
$completiondetails = new cm_completion_details($completioninfo, $cminfo, (int) $student->id);
// Check the results. Completion is fail.
$this->assertEquals(COMPLETION_COMPLETE_FAIL, $completiondetails->get_details()['completionpassgrade']->status);
$this->assertEquals(
'Receive a passing grade',
$completiondetails->get_details()['completionpassgrade']->description
);
// Update quiz with gradepass = 50 and use last attempt grade to calculate completion.
$moduleinfo = $this->prepare_module_info($cm, $quiz, $course, 50, QUIZ_ATTEMPTLAST);
update_moduleinfo($litecm, $moduleinfo, $course, null);
$completiondetails = new cm_completion_details($completioninfo, $cminfo, (int) $student->id);
// Check the results. Completion is fail.
$this->assertEquals(COMPLETION_COMPLETE_PASS, $completiondetails->get_details()['completionpassgrade']->status);
$this->assertEquals(
'Receive a passing grade',
$completiondetails->get_details()['completionpassgrade']->description
);
}
/**
* Set up moduleinfo object sample data for quiz instance.
*
* @param object $cm course-module instance
* @param object $quiz quiz instance data.
* @param object $course Course related data.
* @param int $gradepass Grade to pass and completed completion.
* @param string $grademethod grade attempt method.
* @return \stdClass
*/
private function prepare_module_info(object $cm, object $quiz, object $course, int $gradepass, string $grademethod): \stdClass {
$grouping = $this->getDataGenerator()->create_grouping(['courseid' => $course->id]);
// Module test values.
$moduleinfo = new \stdClass();
$moduleinfo->coursemodule = $cm->id;
$moduleinfo->section = 1;
$moduleinfo->course = $course->id;
$moduleinfo->groupingid = $grouping->id;
$draftideditor = 0;
file_prepare_draft_area($draftideditor, null, null, null, null);
$moduleinfo->introeditor = ['text' => 'This is a module', 'format' => FORMAT_HTML, 'itemid' => $draftideditor];
$moduleinfo->modulename = 'quiz';
$moduleinfo->quizpassword = '';
$moduleinfo->cmidnumber = '';
$moduleinfo->marksopen = 1;
$moduleinfo->visible = 1;
$moduleinfo->visibleoncoursepage = 1;
$moduleinfo->completion = COMPLETION_TRACKING_AUTOMATIC;
$moduleinfo->completionview = COMPLETION_VIEW_NOT_REQUIRED;
$moduleinfo->name = $quiz->name;
$moduleinfo->timeopen = $quiz->timeopen;
$moduleinfo->timeclose = $quiz->timeclose;
$moduleinfo->timelimit = $quiz->timelimit;
$moduleinfo->graceperiod = $quiz->graceperiod;
$moduleinfo->decimalpoints = $quiz->decimalpoints;
$moduleinfo->questiondecimalpoints = $quiz->questiondecimalpoints;
$moduleinfo->gradepass = $gradepass;
$moduleinfo->grademethod = $grademethod;
return $moduleinfo;
}
}