MDL-45002 mod_quiz: completion option, at least N attempts completed

This commit is contained in:
François Moreau 2020-06-09 14:26:50 -04:00
parent ee23a8cf25
commit 760ed019ee
10 changed files with 426 additions and 88 deletions

View File

@ -50,7 +50,7 @@ class backup_quiz_activity_structure_step extends backup_questions_activity_stru
'sumgrades', 'grade', 'timecreated',
'timemodified', 'password', 'subnet', 'browsersecurity',
'delay1', 'delay2', 'showuserpicture', 'showblocks', 'completionattemptsexhausted', 'completionpass',
'allowofflineattempts'));
'completionminattempts', 'allowofflineattempts'));
// Define elements for access rule subplugin settings.
$this->add_subplugin_structure('quizaccess', $quiz, true);

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" ?>
<XMLDB PATH="mod/quiz/db" VERSION="20180719" COMMENT="XMLDB file for Moodle mod/quiz"
<XMLDB PATH="mod/quiz/db" VERSION="20200615" COMMENT="XMLDB file for Moodle mod/quiz"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="../../../lib/xmldb/xmldb.xsd"
>
@ -46,6 +46,7 @@
<FIELD NAME="showblocks" TYPE="int" LENGTH="4" NOTNULL="true" DEFAULT="0" SEQUENCE="false" COMMENT="Whether blocks should be shown on the attempt.php and review.php pages."/>
<FIELD NAME="completionattemptsexhausted" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="completionpass" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="completionminattempts" TYPE="int" LENGTH="10" NOTNULL="true" DEFAULT="0" SEQUENCE="false"/>
<FIELD NAME="allowofflineattempts" TYPE="int" LENGTH="1" NOTNULL="false" DEFAULT="0" SEQUENCE="false" COMMENT="Whether to allow the quiz to be attempted offline in the mobile app"/>
</FIELDS>
<KEYS>

View File

@ -29,7 +29,8 @@ defined('MOODLE_INTERNAL') || die();
* @param string $oldversion the version we are upgrading from.
*/
function xmldb_quiz_upgrade($oldversion) {
global $CFG;
global $CFG, $DB;
$dbman = $DB->get_manager();
// Automatically generated Moodle v3.5.0 release upgrade line.
// Put any upgrade step following this.
@ -46,5 +47,21 @@ function xmldb_quiz_upgrade($oldversion) {
// Automatically generated Moodle v3.9.0 release upgrade line.
// Put any upgrade step following this.
if ($oldversion < 2020061501) {
// Define field completionminattempts to be added to quiz.
$table = new xmldb_table('quiz');
$field = new xmldb_field('completionminattempts', XMLDB_TYPE_INTEGER, '10', null, XMLDB_NOTNULL, null, '0',
'completionpass');
// Conditionally launch add field completionminattempts.
if (!$dbman->field_exists($table, $field)) {
$dbman->add_field($table, $field);
}
// Quiz savepoint reached.
upgrade_mod_savepoint(true, 2020061501, 'quiz');
}
return true;
}

View File

@ -179,6 +179,9 @@ $string['comment'] = 'Comment';
$string['commentorgrade'] = 'Make comment or override grade';
$string['comments'] = 'Comments';
$string['completedon'] = 'Completed on';
$string['completionminattempts'] = 'Students must send attempts:';
$string['completionminattemptsgroup'] = 'Require attempts';
$string['completionminattemptserror'] = 'Minimum number of attempts must be lower or equal to attempts allowed.';
$string['completionpass'] = 'Require passing grade';
$string['completionpassdesc'] = 'Student must achieve a passing grade to complete this activity';
$string['completionpass_help'] = 'If enabled, this activity is considered complete when the student receives a pass grade (as specified in the Grade section of the quiz settings) or higher.';

View File

@ -1128,6 +1128,9 @@ function quiz_process_options($quiz) {
if (empty($quiz->completionpass)) {
$quiz->completionattemptsexhausted = 0;
}
if (empty($quiz->completionminattemptsenabled)) {
$quiz->completionminattempts = 0;
}
}
/**
@ -1894,6 +1897,74 @@ function quiz_get_navigation_options() {
);
}
/**
* Internal function used in quiz_get_completion_state. Check passing grade (or no attempts left) requirement for completion.
*
* @param object $course
* @param object $cm
* @param int $userid
* @param object $quiz
* @return bool True if the passing grade (or no attempts left) requirement is disabled or met.
* @throws coding_exception
*/
function quiz_completion_check_passing_grade_or_all_attempts($course, $cm, $userid, $quiz) {
global $CFG;
if (!$quiz->completionpass) {
return true;
}
// Check for passing grade.
require_once($CFG->libdir . '/gradelib.php');
$item = grade_item::fetch(array('courseid' => $course->id, 'itemtype' => 'mod',
'itemmodule' => 'quiz', 'iteminstance' => $cm->instance, 'outcomeid' => null));
if ($item) {
$grades = grade_grade::fetch_users_grades($item, array($userid), false);
if (!empty($grades[$userid]) && $grades[$userid]->is_passed($item)) {
return true;
}
}
// If a passing grade is required and exhausting all available attempts is not accepted for completion,
// then this quiz is not complete.
if (!$quiz->completionattemptsexhausted) {
return false;
}
// Check if all attempts are used up.
$attempts = quiz_get_user_attempts($quiz->id, $userid, 'finished', true);
if (!$attempts) {
return false;
}
$lastfinishedattempt = end($attempts);
$context = context_module::instance($cm->id);
$quizobj = quiz::create($quiz->id, $userid);
$accessmanager = new quiz_access_manager($quizobj, time(),
has_capability('mod/quiz:ignoretimelimits', $context, $userid, false));
return $accessmanager->is_finished(count($attempts), $lastfinishedattempt);
}
/**
* Internal function used in quiz_get_completion_state. Check minimum attempts requirement for completion.
*
* @param int $userid
* @param object $quiz
* @return bool True if minimum attempts requirement is disabled or met.
* @throws coding_exception
*/
function quiz_completion_check_min_attempts($userid, $quiz) {
global $DB;
if (empty($quiz->completionminattempts)) {
return true;
}
// Check if the user has done enough attempts.
$attempts = quiz_get_user_attempts($quiz->id, $userid, 'finished', true);
return $quiz->completionminattempts <= count($attempts);
}
/**
* Obtains the automatic completion state for this quiz on any conditions
* in quiz settings, such as if all attempts are used or a certain grade is achieved.
@ -1907,41 +1978,21 @@ function quiz_get_navigation_options() {
*/
function quiz_get_completion_state($course, $cm, $userid, $type) {
global $DB;
global $CFG;
$quiz = $DB->get_record('quiz', array('id' => $cm->instance), '*', MUST_EXIST);
if (!$quiz->completionattemptsexhausted && !$quiz->completionpass) {
if (!$quiz->completionattemptsexhausted && !$quiz->completionpass && !$quiz->completionminattempts) {
return $type;
}
// Check if the user has used up all attempts.
if ($quiz->completionattemptsexhausted) {
$attempts = quiz_get_user_attempts($quiz->id, $userid, 'finished', true);
if ($attempts) {
$lastfinishedattempt = end($attempts);
$context = context_module::instance($cm->id);
$quizobj = quiz::create($quiz->id, $userid);
$accessmanager = new quiz_access_manager($quizobj, time(),
has_capability('mod/quiz:ignoretimelimits', $context, $userid, false));
if ($accessmanager->is_finished(count($attempts), $lastfinishedattempt)) {
return true;
}
}
if (!quiz_completion_check_passing_grade_or_all_attempts($course, $cm, $userid, $quiz)) {
return false;
}
// Check for passing grade.
if ($quiz->completionpass) {
require_once($CFG->libdir . '/gradelib.php');
$item = grade_item::fetch(array('courseid' => $course->id, 'itemtype' => 'mod',
'itemmodule' => 'quiz', 'iteminstance' => $cm->instance, 'outcomeid' => null));
if ($item) {
$grades = grade_grade::fetch_users_grades($item, array($userid), false);
if (!empty($grades[$userid])) {
return $grades[$userid]->is_passed($item);
}
}
if (!quiz_completion_check_min_attempts($userid, $quiz)) {
return false;
}
return false;
return true;
}
/**

View File

@ -1799,7 +1799,8 @@ function quiz_attempt_submitted_handler($event) {
// Update completion state.
$completion = new completion_info($course);
if ($completion->is_enabled($cm) && ($quiz->completionattemptsexhausted || $quiz->completionpass)) {
if ($completion->is_enabled($cm) &&
($quiz->completionattemptsexhausted || $quiz->completionpass || $quiz->completionminattempts)) {
$completion->update_state($cm, COMPLETION_COMPLETE, $event->userid);
}
return quiz_send_notification_messages($course, $quiz, $attempt,

View File

@ -481,6 +481,31 @@ class mod_quiz_mod_form extends moodleform_mod {
$toform[$name] = $value;
}
}
if (empty($toform['completionminattempts'])) {
$toform['completionminattempts'] = 1;
} else {
$toform['completionminattemptsenabled'] = $toform['completionminattempts'] > 0;
}
}
/**
* Allows module to modify the data returned by form get_data().
* This method is also called in the bulk activity completion form.
*
* Only available on moodleform_mod.
*
* @param stdClass $data the form data to be modified.
*/
public function data_postprocessing($data) {
parent::data_postprocessing($data);
if (!empty($data->completionunlocked)) {
// Turn off completion settings if the checkboxes aren't ticked.
$autocompletion = !empty($data->completion) && $data->completion == COMPLETION_TRACKING_AUTOMATIC;
if (empty($data->completionminattemptsenabled) || !$autocompletion) {
$data->completionminattempts = 0;
}
}
}
public function validation($data, $files) {
@ -513,6 +538,12 @@ class mod_quiz_mod_form extends moodleform_mod {
}
}
if (!empty($data['completionminattempts'])) {
if ($data['attempts'] > 0 && $data['completionminattempts'] > $data['attempts']) {
$errors['completionminattemptsgroup'] = get_string('completionminattemptserror', 'quiz');
}
}
// Check the boundary value is a number or a percentage, and in range.
$i = 0;
while (!empty($data['feedbackboundaries'][$i] )) {
@ -593,6 +624,17 @@ class mod_quiz_mod_form extends moodleform_mod {
$mform->addGroup($group, 'completionpassgroup', get_string('completionpass', 'quiz'), ' &nbsp; ', false);
$mform->addHelpButton('completionpassgroup', 'completionpass', 'quiz');
$items[] = 'completionpassgroup';
$group = array();
$group[] = $mform->createElement('checkbox', 'completionminattemptsenabled', '',
get_string('completionminattempts', 'quiz'));
$group[] = $mform->createElement('text', 'completionminattempts', '', array('size' => 3));
$mform->setType('completionminattempts', PARAM_INT);
$mform->addGroup($group, 'completionminattemptsgroup', get_string('completionminattemptsgroup', 'quiz'), array(' '), false);
$mform->disabledIf('completionminattempts', 'completionminattemptsenabled', 'notchecked');
$items[] = 'completionminattemptsgroup';
return $items;
}
@ -603,7 +645,9 @@ class mod_quiz_mod_form extends moodleform_mod {
* @return bool True if one or more rules is enabled, false if none are.
*/
public function completion_rule_enabled($data) {
return !empty($data['completionattemptsexhausted']) || !empty($data['completionpass']);
return !empty($data['completionattemptsexhausted']) ||
!empty($data['completionpass']) ||
!empty($data['completionminattemptsenabled']);
}
/**

View File

@ -0,0 +1,56 @@
@mod @mod_quiz
Feature: Set a quiz to be marked complete when the student completes a minimum amount of attempts
In order to ensure a student has completed the quiz before being marked complete
As a teacher
I need to set a quiz to complete when the student completes a certain amount of attempts
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 |
| Test questions | truefalse | First question | Answer the first question |
And the following "activities" exist:
| activity | name | course | idnumber | completion | completionminattemptsenabled | completionminattempts |
| quiz | Test quiz name | C1 | quiz1 | 2 | 1 | 2 |
And quiz "Test quiz name" contains the following questions:
| question | page |
| First question | 1 |
And user "student1" has attempted "Test quiz name" with responses:
| slot | response |
| 1 | False |
Scenario: student1 uses up both attempts without passing
When I log in as "teacher1"
And I am on "Course 1" course homepage
And "Completed: Test quiz name" "icon" should not exist in the "Test quiz name" "list_item"
And I log out
And I log in as "student1"
And I am on "Course 1" course homepage
And the "Test quiz name" "quiz" activity with "auto" completion should be marked as not complete
And I follow "Test quiz name"
And I press "Re-attempt quiz"
And I set the field "False" to "1"
And I press "Finish attempt ..."
And I press "Submit all and finish"
And I am on "Course 1" course homepage
Then "Completed: Test quiz name" "icon" should exist in the "Test quiz name" "list_item"
And I log out
And I log in as "teacher1"
And I am on "Course 1" course homepage
And I navigate to "Reports > Activity completion" in current page administration
And "Completed" "icon" should exist in the "Student 1" "table_row"

View File

@ -130,105 +130,270 @@ class mod_quiz_lib_testcase extends advanced_testcase {
}
/**
* Test checking the completion state of a quiz.
* Setup function for all test_quiz_get_completion_state_* tests.
*
* @param array $completionoptions ['nbstudents'] => int, ['qtype'] => string, ['quizoptions'] => array
* @throws dml_exception
* @return array [$course, $students, $quiz, $cm]
*/
public function test_quiz_get_completion_state() {
private function setup_quiz_for_testing_completion(array $completionoptions) {
global $CFG, $DB;
$this->resetAfterTest(true);
// Enable completion before creating modules, otherwise the completion data is not written in DB.
$CFG->enablecompletion = true;
// Create a course and student.
$course = $this->getDataGenerator()->create_course(array('enablecompletion' => true));
$passstudent = $this->getDataGenerator()->create_user();
$failstudent = $this->getDataGenerator()->create_user();
$studentrole = $DB->get_record('role', array('shortname' => 'student'));
$this->assertNotEmpty($studentrole);
// Create a course and students.
$studentrole = $DB->get_record('role', ['shortname' => 'student']);
$course = $this->getDataGenerator()->create_course(['enablecompletion' => true]);
$students = [];
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));
}
// Enrol students.
$this->assertTrue($this->getDataGenerator()->enrol_user($passstudent->id, $course->id, $studentrole->id));
$this->assertTrue($this->getDataGenerator()->enrol_user($failstudent->id, $course->id, $studentrole->id));
// Make a scale and an outcome.
$scale = $this->getDataGenerator()->create_scale();
$data = array('courseid' => $course->id,
'fullname' => 'Team work',
'shortname' => 'Team work',
'scaleid' => $scale->id);
$outcome = $this->getDataGenerator()->create_grade_outcome($data);
// Make a quiz with the outcome on.
// Make a quiz.
$quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz');
$data = array('course' => $course->id,
'outcome_'.$outcome->id => 1,
'grade' => 100.0,
'questionsperpage' => 0,
'sumgrades' => 1,
'completion' => COMPLETION_TRACKING_AUTOMATIC,
'completionusegrade' => 1,
'completionpass' => 1);
$data = array_merge([
'course' => $course->id,
'grade' => 100.0,
'questionsperpage' => 0,
'sumgrades' => 1,
'completion' => COMPLETION_TRACKING_AUTOMATIC
], $completionoptions['quizoptions']);
$quiz = $quizgenerator->create_instance($data);
$cm = get_coursemodule_from_id('quiz', $quiz->cmid);
// Create a couple of questions.
// Create a question.
$questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question');
$cat = $questiongenerator->create_question_category();
$question = $questiongenerator->create_question('numerical', null, array('category' => $cat->id));
$question = $questiongenerator->create_question($completionoptions['qtype'], null, ['category' => $cat->id]);
quiz_add_quiz_question($question->id, $quiz);
$quizobj = quiz::create($quiz->id, $passstudent->id);
// Set grade to pass.
$item = grade_item::fetch(array('courseid' => $course->id, 'itemtype' => 'mod',
'itemmodule' => 'quiz', 'iteminstance' => $quiz->id, 'outcomeid' => null));
$item = grade_item::fetch(['courseid' => $course->id, 'itemtype' => 'mod', 'itemmodule' => 'quiz',
'iteminstance' => $quiz->id, 'outcomeid' => null]);
$item->gradepass = 80;
$item->update();
return [
$course,
$students,
$quiz,
$cm
];
}
/**
* Helper function for all test_quiz_get_completion_state_* tests.
* Starts an attempt, processes responses and finishes the attempt.
*
* @param $attemptoptions ['quiz'] => object, ['student'] => object, ['tosubmit'] => array, ['attemptnumber'] => int
*/
private function do_attempt_quiz($attemptoptions) {
$quizobj = quiz::create($attemptoptions['quiz']->id);
// Start the passing attempt.
$quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
$timenow = time();
$attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $passstudent->id);
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
$attempt = quiz_create_attempt($quizobj, $attemptoptions['attemptnumber'], false, $timenow, false,
$attemptoptions['student']->id);
quiz_start_new_attempt($quizobj, $quba, $attempt, $attemptoptions['attemptnumber'], $timenow);
quiz_attempt_save_started($quizobj, $quba, $attempt);
// Process some responses from the student.
// Process responses from the student.
$attemptobj = quiz_attempt::create($attempt->id);
$tosubmit = array(1 => array('answer' => '3.14'));
$attemptobj->process_submitted_actions($timenow, false, $tosubmit);
$attemptobj->process_submitted_actions($timenow, false, $attemptoptions['tosubmit']);
// Finish the attempt.
$attemptobj = quiz_attempt::create($attempt->id);
$this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question());
$attemptobj->process_finish($timenow, false);
}
// Start the failing attempt.
$quba = question_engine::make_questions_usage_by_activity('mod_quiz', $quizobj->get_context());
$quba->set_preferred_behaviour($quizobj->get_quiz()->preferredbehaviour);
/**
* Test checking the completion state of a quiz.
* The quiz requires a passing grade to be completed.
*/
public function test_quiz_get_completion_state_completionpass() {
$timenow = time();
$attempt = quiz_create_attempt($quizobj, 1, false, $timenow, false, $failstudent->id);
quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow);
quiz_attempt_save_started($quizobj, $quba, $attempt);
list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([
'nbstudents' => 2,
'qtype' => 'numerical',
'quizoptions' => [
'completionusegrade' => 1,
'completionpass' => 1
]
]);
// Process some responses from the student.
$attemptobj = quiz_attempt::create($attempt->id);
$tosubmit = array(1 => array('answer' => '0'));
$attemptobj->process_submitted_actions($timenow, false, $tosubmit);
list($passstudent, $failstudent) = $students;
// Finish the attempt.
$attemptobj = quiz_attempt::create($attempt->id);
$this->assertTrue($attemptobj->has_response_to_at_least_one_graded_question());
$attemptobj->process_finish($timenow, false);
// Do a passing attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $passstudent,
'attemptnumber' => 1,
'tosubmit' => [1 => ['answer' => '3.14']]
]);
// Check the results.
$this->assertTrue(quiz_get_completion_state($course, $cm, $passstudent->id, 'return'));
// Do a failing attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $failstudent,
'attemptnumber' => 1,
'tosubmit' => [1 => ['answer' => '0']]
]);
// Check the results.
$this->assertFalse(quiz_get_completion_state($course, $cm, $failstudent->id, 'return'));
}
/**
* Test checking the completion state of a quiz.
* To be completed, this quiz requires either a passing grade or for all attempts to be used up.
*/
public function test_quiz_get_completion_state_completionexhausted() {
list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([
'nbstudents' => 2,
'qtype' => 'numerical',
'quizoptions' => [
'attempts' => 2,
'completionusegrade' => 1,
'completionpass' => 1,
'completionattemptsexhausted' => 1
]
]);
list($passstudent, $exhauststudent) = $students;
// Start a passing attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $passstudent,
'attemptnumber' => 1,
'tosubmit' => [1 => ['answer' => '3.14']]
]);
// Check the results. Quiz is completed by $passstudent because of passing grade.
$this->assertTrue(quiz_get_completion_state($course, $cm, $passstudent->id, 'return'));
// Do a failing attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $exhauststudent,
'attemptnumber' => 1,
'tosubmit' => [1 => ['answer' => '0']]
]);
// Check the results. Quiz is not completed by $exhauststudent yet because of failing grade and of remaining attempts.
$this->assertFalse(quiz_get_completion_state($course, $cm, $exhauststudent->id, 'return'));
// Do a second failing attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $exhauststudent,
'attemptnumber' => 2,
'tosubmit' => [1 => ['answer' => '0']]
]);
// Check the results. Quiz is completed by $exhauststudent because there are no remaining attempts.
$this->assertTrue(quiz_get_completion_state($course, $cm, $exhauststudent->id, 'return'));
}
/**
* Test checking the completion state of a quiz.
* To be completed, this quiz requires a minimum number of attempts.
*/
public function test_quiz_get_completion_state_completionminattempts() {
list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([
'nbstudents' => 1,
'qtype' => 'essay',
'quizoptions' => [
'completionminattemptsenabled' => 1,
'completionminattempts' => 2
]
]);
list($student) = $students;
// Do a first attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $student,
'attemptnumber' => 1,
'tosubmit' => [1 => ['answer' => 'Lorem ipsum.', 'answerformat' => '1']]
]);
// Check the results. Quiz is not completed yet because only one attempt was done.
$this->assertFalse(quiz_get_completion_state($course, $cm, $student->id, 'return'));
// Do a second attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $student,
'attemptnumber' => 2,
'tosubmit' => [1 => ['answer' => 'Lorem ipsum.', 'answerformat' => '1']]
]);
// Check the results. Quiz is completed by $student because two attempts were done.
$this->assertTrue(quiz_get_completion_state($course, $cm, $student->id, 'return'));
}
/**
* Test checking the completion state of a quiz.
* To be completed, this quiz requires a minimum number of attempts AND a passing grade.
* This is somewhat of an edge case as it is hard to imagine a scenario in which these precise settings are useful.
* Nevertheless, this test makes sure these settings interact as intended.
*/
public function test_quiz_get_completion_state_completionminattempts_pass() {
list($course, $students, $quiz, $cm) = $this->setup_quiz_for_testing_completion([
'nbstudents' => 1,
'qtype' => 'numerical',
'quizoptions' => [
'attempts' => 2,
'completionusegrade' => 1,
'completionpass' => 1,
'completionminattemptsenabled' => 1,
'completionminattempts' => 2
]
]);
list($student) = $students;
// Start a first attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $student,
'attemptnumber' => 1,
'tosubmit' => [1 => ['answer' => '3.14']]
]);
// Check the results. Even though one requirement is met (passing grade) quiz is not completed yet because only
// one attempt was done.
$this->assertFalse(quiz_get_completion_state($course, $cm, $student->id, 'return'));
// Start a second attempt.
$this->do_attempt_quiz([
'quiz' => $quiz,
'student' => $student,
'attemptnumber' => 2,
'tosubmit' => [1 => ['answer' => '42']]
]);
// Check the results. Quiz is completed by $student because two attempts were done AND a passing grade was obtained.
$this->assertTrue(quiz_get_completion_state($course, $cm, $student->id, 'return'));
}
public function test_quiz_get_user_attempts() {
global $DB;
$this->resetAfterTest();

View File

@ -24,6 +24,6 @@
defined('MOODLE_INTERNAL') || die();
$plugin->version = 2020061500;
$plugin->version = 2020061501;
$plugin->requires = 2020060900;
$plugin->component = 'mod_quiz';