diff --git a/mod/quiz/locallib.php b/mod/quiz/locallib.php index 320866827de..aa39169bc6e 100644 --- a/mod/quiz/locallib.php +++ b/mod/quiz/locallib.php @@ -372,6 +372,8 @@ function quiz_attempt_save_started( $originalattempt = null; if (isset($attempt->id) && $attempt->state === quiz_attempt::NOT_STARTED) { $originalattempt = clone $attempt; + // In case questions have been edited since attempts were pre-created, update questions now. + quiz_attempt::create($attempt->id)->update_questions_to_new_version_if_changed(); // Update the attempt's state. $attempt->state = quiz_attempt::IN_PROGRESS; $DB->update_record('quiz_attempts', $attempt); diff --git a/mod/quiz/tests/attempt_test.php b/mod/quiz/tests/attempt_test.php index 8a3695b30c4..f6bdc290b24 100644 --- a/mod/quiz/tests/attempt_test.php +++ b/mod/quiz/tests/attempt_test.php @@ -571,4 +571,21 @@ final class attempt_test extends \advanced_testcase { $this->assertEquals(0, $grades[$readinggrade->id]->grade); $this->assertEquals(1, $grades[$readinggrade->id]->maxgrade); } + + /** + * When creating a new quiz attempt, question attempts should be created with the first step's timecreated set to null. + * + * When the question attempt is rendered, it should be set to the current time. + * + * @return void + * @throws \coding_exception + * @covers ::quiz_start_new_attempt + */ + public function test_step_timecreated_unset_when_starting_quiz_attempt(): void { + $attempt = $this->create_quiz_and_attempt_with_layout('1'); + $questionattempt = $attempt->get_question_attempt(1); + $this->assertEquals(\question_attempt_step::TIMECREATED_ON_FIRST_RENDER, $questionattempt->get_step(0)->get_timecreated()); + $questionattempt->render(new \question_display_options(), 1); + $this->assertEqualsWithDelta(time(), $questionattempt->get_step(0)->get_timecreated(), 1); + } } diff --git a/mod/quiz/tests/attempt_walkthrough_test.php b/mod/quiz/tests/attempt_walkthrough_test.php index be92c959098..126f873ad4d 100644 --- a/mod/quiz/tests/attempt_walkthrough_test.php +++ b/mod/quiz/tests/attempt_walkthrough_test.php @@ -694,4 +694,83 @@ final class attempt_walkthrough_test extends \advanced_testcase { $this->assertEquals($attemptobj->get_context(), $gradedevent->get_context()); $this->assertEquals($attempturl, $gradedevent->get_url()); } + + /** + * Create a quiz with questions, pre-create an attempt, edit a question, then begin the attempt. + */ + public function test_quiz_attempt_walkthrough_update_question_after_precreate(): void { + global $SITE; + + $this->resetAfterTest(true); + + // Make a quiz. + $quizgenerator = $this->getDataGenerator()->get_plugin_generator('mod_quiz'); + + $quiz = $quizgenerator->create_instance( + [ + 'course' => $SITE->id, + 'questionsperpage' => 0, + 'grade' => 100.0, + 'sumgrades' => 3, + ], + ); + + // Create a couple of questions. + $questiongenerator = $this->getDataGenerator()->get_plugin_generator('core_question'); + + $cat = $questiongenerator->create_question_category(); + $saq = $questiongenerator->create_question('shortanswer', null, ['category' => $cat->id]); + $numq = $questiongenerator->create_question('numerical', null, ['category' => $cat->id]); + $matchq = $questiongenerator->create_question('match', null, ['category' => $cat->id]); + $description = $questiongenerator->create_question('description', null, ['category' => $cat->id]); + + // Add them to the quiz. + quiz_add_quiz_question($saq->id, $quiz); + quiz_add_quiz_question($numq->id, $quiz); + quiz_add_quiz_question($matchq->id, $quiz); + quiz_add_quiz_question($description->id, $quiz); + + // Make a user to do the quiz. + $user1 = $this->getDataGenerator()->create_user(); + + $quizobj = quiz_settings::create($quiz->id, $user1->id); + + // Start the 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, $user1->id); + + quiz_start_new_attempt($quizobj, $quba, $attempt, 1, $timenow); + $this->assertEquals('1,2,3,4,0', $attempt->layout); + + quiz_attempt_save_not_started($quba, $attempt); + + $attemptobj = quiz_attempt::create($attempt->id); + + // Update question in quiz. + $newsa = $questiongenerator->update_question($saq, null, + ['name' => 'This is the second version of shortanswer']); + $newnumbq = $questiongenerator->update_question($numq, null, + ['name' => 'This is the second version of numerical']); + $newmatch = $questiongenerator->update_question($matchq, null, + ['name' => 'This is the second version of match']); + $newdescription = $questiongenerator->update_question($description, null, + ['name' => 'This is the second version of description']); + + $this->assertEquals($saq->id, $attemptobj->get_question_attempt(1)->get_question_id()); + $this->assertEquals($numq->id, $attemptobj->get_question_attempt(2)->get_question_id()); + $this->assertEquals($matchq->id, $attemptobj->get_question_attempt(3)->get_question_id()); + $this->assertEquals($description->id, $attemptobj->get_question_attempt(4)->get_question_id()); + + quiz_attempt_save_started($quizobj, $quba, $attempt); + + // Verify that the started attempt contains the new questions. + $attemptobj = quiz_attempt::create($attempt->id); + $this->assertEquals($newsa->id, $attemptobj->get_question_attempt(1)->get_question_id()); + $this->assertEquals($newnumbq->id, $attemptobj->get_question_attempt(2)->get_question_id()); + $this->assertEquals($newmatch->id, $attemptobj->get_question_attempt(3)->get_question_id()); + $this->assertEquals($newdescription->id, $attemptobj->get_question_attempt(4)->get_question_id()); + } }