diff --git a/README.md b/README.md index 4cbe21a..4779e23 100644 --- a/README.md +++ b/README.md @@ -294,14 +294,17 @@ public function correct_options(): Collection Please refer unit and features tests for more understanding. ### Validate A Quiz Question -Instead of getting total score for the quiz attempt, you can use `QuizAttempt` model's `validate()` method. This method will return an array with a QuizQuestion model's + +Instead of getting total score for the quiz attempt, you can use `QuizAttempt` model's `validate()` method. This method will return an array with a QuizQuestion model's `id` as the key for the assoc array that will be returned. **Example:** + ```injectablephp $quizAttempt->validate($quizQuestion->id); //For a particular question $quizAttempt->validate(); //For all the questions in the quiz attempt $quizAttempt->validate($quizQuestion->id,$data); //$data can any type ``` + ```php [ 1 => [ @@ -318,7 +321,9 @@ $quizAttempt->validate($quizQuestion->id,$data); //$data can any type ] ] ``` + To be able to render the user answer and correct answer for different types of question types other than the 3 types supported by the package, a new config option has been added. + ```php 'render_answers_responses' => [ 1 => '\Harishdurga\LaravelQuiz\Models\QuizAttempt::renderQuestionType1Answers', @@ -326,17 +331,19 @@ To be able to render the user answer and correct answer for different types of q 3 => '\Harishdurga\LaravelQuiz\Models\QuizAttempt::renderQuestionType3Answers', ] ``` -By keeping the question type id as the key, you can put the path to your custom function to handle the question type. This custom method will be called from inside the + +By keeping the question type id as the key, you can put the path to your custom function to handle the question type. This custom method will be called from inside the `validate()` method by passing the `QuizQuestion` object as the argument for your custom method as defined in the config. **Example:** + ```php -public static function renderQuestionType1Answers(QuizQuestion $quizQuestion, mixed $data=null) +public static function renderQuestionType1Answers(QuizQuestion $quizQuestion,QuizAttempt $quizAttempt,mixed $data=null) { /** * @var Question $actualQuestion */ $actualQuestion = $quizQuestion->question; - $answers = $quizQuestion->answers; + $answers = $quizQuestion->answers->where('quiz_attempt_id', $quizAttempt->id); $questionOptions = $actualQuestion->options; $correctAnswer = $actualQuestion->correct_options()->first()?->option; $givenAnswer = $answers->first()?->question_option_id; @@ -349,6 +356,7 @@ public static function renderQuestionType1Answers(QuizQuestion $quizQuestion, mi return [$correctAnswer, $givenAnswer]; } ``` + As shown in the example you customer method should return an array with two elements the first one being the correct answer and the second element being the user's answer for the question. And whatever the `$data` you send to the `validate()` will be sent to these custom methods so that you can send additional data for rendering the answers. diff --git a/src/Models/QuizAttempt.php b/src/Models/QuizAttempt.php index d22d8ef..893ad79 100644 --- a/src/Models/QuizAttempt.php +++ b/src/Models/QuizAttempt.php @@ -69,7 +69,7 @@ public function calculate_score($data = null): float /** * @param QuizAttemptAnswer[] $quizQuestionAnswers All the answers of the quiz question */ - public static function get_score_for_type_1_question(QuizAttempt $quizAttempt, QuizQuestion $quizQuestion, array|Collection $quizQuestionAnswers, $data = null): float + public static function get_score_for_type_1_question(QuizAttempt $quizAttempt, QuizQuestion $quizQuestion, array | Collection $quizQuestionAnswers, $data = null): float { $quiz = $quizAttempt->quiz; $question = $quizQuestion->question; @@ -81,13 +81,13 @@ public static function get_score_for_type_1_question(QuizAttempt $quizAttempt, Q } return $quizQuestion->is_optional ? 0 : -$negative_marks; // If the question is optional, then the negative marks will be 0 } - return count($quizQuestionAnswers) ? (float)$quizQuestion->marks : 0; // Incase of no correct answer, if there is any answer then give full marks + return count($quizQuestionAnswers) ? (float) $quizQuestion->marks : 0; // Incase of no correct answer, if there is any answer then give full marks } /** * @param QuizAttemptAnswer[] $quizQuestionAnswers All the answers of the quiz question */ - public static function get_score_for_type_2_question(QuizAttempt $quizAttempt, QuizQuestion $quizQuestion, array|Collection $quizQuestionAnswers, $data = null): float + public static function get_score_for_type_2_question(QuizAttempt $quizAttempt, QuizQuestion $quizQuestion, array | Collection $quizQuestionAnswers, $data = null): float { $quiz = $quizAttempt->quiz; $question = $quizQuestion->question; @@ -103,25 +103,29 @@ public static function get_score_for_type_2_question(QuizAttempt $quizAttempt, Q } return $quizQuestion->is_optional ? 0 : -$negative_marks; // If the question is optional, then the negative marks will be 0 } - return count($quizQuestionAnswers) ? (float)$quizQuestion->marks : 0; // Incase of no correct answer, if there is any answer then give full marks + return count($quizQuestionAnswers) ? (float) $quizQuestion->marks : 0; // Incase of no correct answer, if there is any answer then give full marks } /** * @param QuizAttemptAnswer[] $quizQuestionAnswers All the answers of the quiz question */ - public static function get_score_for_type_3_question(QuizAttempt $quizAttempt, QuizQuestion $quizQuestion, array|Collection $quizQuestionAnswers, $data = null): float + public static function get_score_for_type_3_question(QuizAttempt $quizAttempt, QuizQuestion $quizQuestion, array | Collection $quizQuestionAnswers, $data = null): float { $quiz = $quizAttempt->quiz; $question = $quizQuestion->question; $correct_answer = ($question->correct_options())->first()->option; $negative_marks = self::get_negative_marks_for_question($quiz, $quizQuestion); if (!empty($correct_answer)) { + if (count($quizQuestionAnswers)) { - return $quizQuestionAnswers[0]->answer == $correct_answer ? $quizQuestion->marks : -($negative_marks); // Return marks in case of correct answer else negative marks + if (is_array($quizQuestionAnswers)) { + return $quizQuestionAnswers[0]->answer == $correct_answer ? $quizQuestion->marks : -($negative_marks); // Return marks in case of correct answer else negative marks + } + return $quizQuestionAnswers->first()->answer == $correct_answer ? $quizQuestion->marks : -($negative_marks); // Return marks in case of correct answer else negative marks } return $quizQuestion->is_optional ? 0 : -$negative_marks; // If the question is optional, then the negative marks will be 0 } - return count($quizQuestionAnswers) ? (float)$quizQuestion->marks : 0; // Incase of no correct answer, if there is any answer then give full marks + return count($quizQuestionAnswers) ? (float) $quizQuestion->marks : 0; // Incase of no correct answer, if there is any answer then give full marks } public static function get_negative_marks_for_question(Quiz $quiz, QuizQuestion $quizQuestion): float @@ -129,34 +133,34 @@ public static function get_negative_marks_for_question(Quiz $quiz, QuizQuestion $negative_marking_settings = $quiz->negative_marking_settings ?? [ 'enable_negative_marks' => true, 'negative_marking_type' => 'fixed', - 'negative_mark_value' => 0, + 'negative_mark_value' => 0, ]; if (!$negative_marking_settings['enable_negative_marks']) { // If negative marking is disabled return 0; } if (!empty($quizQuestion->negative_marks)) { return $negative_marking_settings['negative_marking_type'] == 'fixed' ? - ($quizQuestion->negative_marks < 0 ? -$quizQuestion->negative_marks : $quizQuestion->negative_marks) : ($quizQuestion->marks * (($quizQuestion->negative_marks < 0 ? -$quizQuestion->negative_marks : $quizQuestion->negative_marks) / 100)); + ($quizQuestion->negative_marks < 0 ? -$quizQuestion->negative_marks : $quizQuestion->negative_marks) : ($quizQuestion->marks * (($quizQuestion->negative_marks < 0 ? -$quizQuestion->negative_marks : $quizQuestion->negative_marks) / 100)); } return $negative_marking_settings['negative_marking_type'] == 'fixed' ? ($negative_marking_settings['negative_mark_value'] < 0 ? -$negative_marking_settings['negative_mark_value'] : $negative_marking_settings['negative_mark_value']) : ($quizQuestion->marks * (($negative_marking_settings['negative_mark_value'] < 0 ? -$negative_marking_settings['negative_mark_value'] : $negative_marking_settings['negative_mark_value']) / 100)); } - private function validateQuizQuestion(QuizQuestion $quizQuestion, mixed $data = null): array + private function validateQuizQuestion(QuizAttempt $quizAttempt, QuizQuestion $quizQuestion, mixed $data = null): array { $isCorrect = true; $actualQuestion = $quizQuestion->question; - $answers = $quizQuestion->answers; + $answers = $quizQuestion->answers->where('quiz_attempt_id', $quizAttempt->id); $questionType = $actualQuestion->question_type; $score = call_user_func_array(config('laravel-quiz.get_score_for_question_type')[$questionType->id], [$this, $quizQuestion, $answers ?? [], $data]); if ($score <= 0) { $isCorrect = false; } - list($correctAnswer, $userAnswer) = config('laravel-quiz.render_answers_responses')[$questionType->id]($quizQuestion, $data); + list($correctAnswer, $userAnswer) = config('laravel-quiz.render_answers_responses')[$questionType->id]($quizQuestion, $quizAttempt, $data); return [ - 'score' => $score, - 'is_correct' => $isCorrect, + 'score' => $score, + 'is_correct' => $isCorrect, 'correct_answer' => $correctAnswer, - 'user_answer' => $userAnswer + 'user_answer' => $userAnswer, ]; } @@ -165,7 +169,7 @@ private function validateQuizQuestion(QuizQuestion $quizQuestion, mixed $data = * @param $data mixed|null data to be passed to the user defined function to evaluate different question types * @return array|null [1=>['score'=>10,'is_correct'=>true,'correct_answer'=>'a','user_answer'=>'a']] */ - public function validate(int|null $quizQuestionId = null, mixed $data = null): array|null + public function validate(int | null $quizQuestionId = null, mixed $data = null): array | null { if ($quizQuestionId) { $quizQuestion = QuizQuestion::where(['quiz_id' => $this->quiz_id, 'id' => $quizQuestionId]) @@ -173,7 +177,7 @@ public function validate(int|null $quizQuestionId = null, mixed $data = null): a ->with('answers') ->with('question.options')->first(); if ($quizQuestion) { - return [$quizQuestionId => $this->validateQuizQuestion($quizQuestion, $data)]; + return [$quizQuestionId => $this->validateQuizQuestion($this, $quizQuestion, $data)]; } return null; //QuizQuestion is empty } @@ -183,18 +187,18 @@ public function validate(int|null $quizQuestionId = null, mixed $data = null): a } $result = []; foreach ($quizQuestions as $quizQuestion) { - $result[$quizQuestion->id] = $this->validateQuizQuestion($quizQuestion); + $result[$quizQuestion->id] = $this->validateQuizQuestion($this, $quizQuestion); } return $result; } - public static function renderQuestionType1Answers(QuizQuestion $quizQuestion, mixed $data = null) + public static function renderQuestionType1Answers(QuizQuestion $quizQuestion, QuizAttempt $quizAttempt, mixed $data = null) { /** * @var Question $actualQuestion */ $actualQuestion = $quizQuestion->question; - $answers = $quizQuestion->answers; + $answers = $quizQuestion->answers->where('quiz_attempt_id', $quizAttempt->id); $questionOptions = $actualQuestion->options; $correctAnswer = $actualQuestion->correct_options()->first()?->option; $givenAnswer = $answers->first()?->question_option_id; @@ -207,10 +211,10 @@ public static function renderQuestionType1Answers(QuizQuestion $quizQuestion, mi return [$correctAnswer, $givenAnswer]; } - public static function renderQuestionType2Answers(QuizQuestion $quizQuestion, mixed $data = null) + public static function renderQuestionType2Answers(QuizQuestion $quizQuestion, QuizAttempt $quizAttempt, mixed $data = null) { $actualQuestion = $quizQuestion->question; - $userAnswersCollection = $quizQuestion->answers; + $userAnswersCollection = $quizQuestion->answers->where('quiz_attempt_id', $quizAttempt->id); $correctAnswersCollection = $actualQuestion->correct_options(); $correctAnswers = $userAnswers = []; foreach ($correctAnswersCollection as $correctAnswer) { @@ -222,10 +226,10 @@ public static function renderQuestionType2Answers(QuizQuestion $quizQuestion, mi return [$correctAnswers, $userAnswers]; } - public static function renderQuestionType3Answers(QuizQuestion $quizQuestion, mixed $data = null) + public static function renderQuestionType3Answers(QuizQuestion $quizQuestion, QuizAttempt $quizAttempt, mixed $data = null) { $actualQuestion = $quizQuestion->question; - $userAnswersCollection = $quizQuestion->answers; + $userAnswersCollection = $quizQuestion->answers->where('quiz_attempt_id', $quizAttempt->id); $correctAnswersCollection = $actualQuestion->correct_options(); $userAnswer = $userAnswersCollection->first()?->answer; $correctAnswer = $correctAnswersCollection->first()?->option; diff --git a/tests/Unit/QuizAttemptTest.php b/tests/Unit/QuizAttemptTest.php index 6572536..099dde7 100644 --- a/tests/Unit/QuizAttemptTest.php +++ b/tests/Unit/QuizAttemptTest.php @@ -2,24 +2,23 @@ namespace Harishdurga\LaravelQuiz\Tests\Unit; -use Harishdurga\LaravelQuiz\Models\Quiz; -use Harishdurga\LaravelQuiz\Tests\TestCase; use Harishdurga\LaravelQuiz\Models\Question; -use Harishdurga\LaravelQuiz\Models\QuizAttempt; +use Harishdurga\LaravelQuiz\Models\QuestionOption; use Harishdurga\LaravelQuiz\Models\QuestionType; +use Harishdurga\LaravelQuiz\Models\Quiz; +use Harishdurga\LaravelQuiz\Models\QuizAttempt; +use Harishdurga\LaravelQuiz\Models\QuizAttemptAnswer; use Harishdurga\LaravelQuiz\Models\QuizQuestion; use Harishdurga\LaravelQuiz\Tests\Models\Author; -use Harishdurga\LaravelQuiz\Models\QuestionOption; +use Harishdurga\LaravelQuiz\Tests\TestCase; use Illuminate\Foundation\Testing\RefreshDatabase; -use Harishdurga\LaravelQuiz\Models\QuizAttemptAnswer; use Illuminate\Support\Facades\DB; - class QuizAttemptTest extends TestCase { use RefreshDatabase; - function init($questionType = 1, $enableNegativeMarks = true, $negativeMarkingType = 'fixed', $quizNegativemarkValue = 0, $marks = 1, $negativeMarks = 0) + public function init($questionType = 1, $enableNegativeMarks = true, $negativeMarkingType = 'fixed', $quizNegativemarkValue = 0, $marks = 1, $negativeMarks = 0) { $user = Author::create( ['name' => "John Doe"] @@ -36,115 +35,115 @@ function init($questionType = 1, $enableNegativeMarks = true, $negativeMarkingTy ], [ 'name' => 'fill_the_blank', - ] + ], ] ); if ($questionType == 1) { $question = Question::factory()->create([ - 'name' => 'How many layers in OSI model?', + 'name' => 'How many layers in OSI model?', 'question_type_id' => 1, - 'is_active' => false, + 'is_active' => false, ]); $question_option_one = QuestionOption::factory()->create([ 'question_id' => $question->id, - 'name' => '5', - 'is_correct' => false, + 'name' => '5', + 'is_correct' => false, ]); $question_option_two = QuestionOption::factory()->create([ 'question_id' => $question->id, - 'name' => '8', - 'is_correct' => false, + 'name' => '8', + 'is_correct' => false, ]); $question_option_three = QuestionOption::factory()->create([ 'question_id' => $question->id, - 'name' => '10', - 'is_correct' => false, + 'name' => '10', + 'is_correct' => false, ]); $question_option_four = QuestionOption::factory()->create([ 'question_id' => $question->id, - 'name' => '7', - 'is_correct' => true, + 'name' => '7', + 'is_correct' => true, ]); $options = [$question_option_one, $question_option_two, $question_option_three, $question_option_four]; } elseif ($questionType == 2) { $question = Question::factory()->create([ - 'name' => 'Which of the below is a data structure?', + 'name' => 'Which of the below is a data structure?', 'question_type_id' => 2, - 'is_active' => true, + 'is_active' => true, ]); $question_option_one = QuestionOption::factory()->create([ 'question_id' => $question->id, - 'name' => 'array', - 'is_correct' => true, + 'name' => 'array', + 'is_correct' => true, ]); $question_option_two = QuestionOption::factory()->create([ 'question_id' => $question->id, - 'name' => 'object', - 'is_correct' => true, + 'name' => 'object', + 'is_correct' => true, ]); $question_option_three = QuestionOption::factory()->create([ 'question_id' => $question->id, - 'name' => 'for loop', - 'is_correct' => false, + 'name' => 'for loop', + 'is_correct' => false, ]); $question_option_four = QuestionOption::factory()->create([ 'question_id' => $question->id, - 'name' => 'method', - 'is_correct' => false, + 'name' => 'method', + 'is_correct' => false, ]); $options = [$question_option_one, $question_option_two, $question_option_three, $question_option_four]; } else { $question = Question::factory()->create([ - 'name' => 'Full Form Of CPU', + 'name' => 'Full Form Of CPU', 'question_type_id' => 3, - 'is_active' => true, + 'is_active' => true, ]); $question_option_one = QuestionOption::factory()->create([ 'question_id' => $question->id, - 'name' => 'central processing unit', - 'is_correct' => true, + 'name' => 'central processing unit', + 'is_correct' => true, ]); $options = [$question_option_one]; } $quiz = Quiz::factory()->make()->create([ - 'name' => 'Sample Quiz', - 'slug' => 'sample-quiz', + 'name' => 'Sample Quiz', + 'slug' => 'sample-quiz', 'negative_marking_settings' => [ 'enable_negative_marks' => $enableNegativeMarks, 'negative_marking_type' => $negativeMarkingType, - 'negative_mark_value' => $quizNegativemarkValue, - ] + 'negative_mark_value' => $quizNegativemarkValue, + ], ]); $quizQuestion = QuizQuestion::factory()->create([ - 'quiz_id' => $quiz->id, - 'question_id' => $question->id, - 'marks' => $marks, - 'order' => 1, + 'quiz_id' => $quiz->id, + 'question_id' => $question->id, + 'marks' => $marks, + 'order' => 1, 'negative_marks' => $negativeMarks, ]); $quizAttempt = QuizAttempt::create([ - 'quiz_id' => $quiz->id, - 'participant_id' => $user->id, - 'participant_type' => get_class($user) + 'quiz_id' => $quiz->id, + 'participant_id' => $user->id, + 'participant_type' => get_class($user), ]); return [$user, $question, $options, $quiz, $quizQuestion, $quizAttempt]; } /** @test */ - function get_score_for_type_1_question_no_negative_marks() + public function get_score_for_type_1_question_no_negative_marks() { [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init(1, false, Quiz::PERCENTAGE_NEGATIVE_TYPE, 0, 5, 0); [$question_option_one, $question_option_two, $question_option_three, $question_option_four] = $options; //Quiz Attempt And Answers $quiz_attempt = QuizAttempt::create([ - 'quiz_id' => $quiz->id, - 'participant_id' => $user->id, - 'participant_type' => get_class($user) + 'quiz_id' => $quiz->id, + 'participant_id' => $user->id, + 'participant_type' => get_class($user), ]); $quiz_attempt_answer = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_four->id, ] ); @@ -152,14 +151,14 @@ function get_score_for_type_1_question_no_negative_marks() } /** @test */ - function get_score_for_type_1_question_with_negative_marks_question_fixed() + public function get_score_for_type_1_question_with_negative_marks_question_fixed() { [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init(1, true, Quiz::FIXED_NEGATIVE_TYPE, 0, 5, 1); [$question_option_one, $question_option_two, $question_option_three, $question_option_four] = $options; $quiz_attempt_answer = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_three->id, ] ); @@ -167,14 +166,14 @@ function get_score_for_type_1_question_with_negative_marks_question_fixed() } /** @test */ - function get_score_for_type_1_question_with_negative_marks_question_percentage() + public function get_score_for_type_1_question_with_negative_marks_question_percentage() { [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init(1, true, Quiz::PERCENTAGE_NEGATIVE_TYPE, 0, 5, 10); [$question_option_one, $question_option_two, $question_option_three, $question_option_four] = $options; $quiz_attempt_answer = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_three->id, ] ); @@ -182,15 +181,15 @@ function get_score_for_type_1_question_with_negative_marks_question_percentage() } /** @test */ - function get_score_for_type_1_question_with_negative_marks_quiz_fixed() + public function get_score_for_type_1_question_with_negative_marks_quiz_fixed() { [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init(1, true, Quiz::FIXED_NEGATIVE_TYPE, 2, 10, 0); [$question_option_one, $question_option_two, $question_option_three, $question_option_four] = $options; $quiz_attempt_answer = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_three->id, ] ); @@ -198,14 +197,14 @@ function get_score_for_type_1_question_with_negative_marks_quiz_fixed() } /** @test */ - function get_score_for_type_1_question_with_negative_marks_quiz_percentage() + public function get_score_for_type_1_question_with_negative_marks_quiz_percentage() { [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init(1, true, Quiz::PERCENTAGE_NEGATIVE_TYPE, 5, 10, 0); [$question_option_one, $question_option_two, $question_option_three, $question_option_four] = $options; $quiz_attempt_answer = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_three->id, ] ); @@ -213,22 +212,22 @@ function get_score_for_type_1_question_with_negative_marks_quiz_percentage() } /** @test */ - function get_score_for_type_2_question_no_negative_marks() + public function get_score_for_type_2_question_no_negative_marks() { [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init(2, false, Quiz::PERCENTAGE_NEGATIVE_TYPE, 0, 8, 2); [$question_option_one, $question_option_two, $question_option_three, $question_option_four] = $options; $quiz_attempt_answer_one = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_one->id, ] ); $quiz_attempt_answer_two = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_two->id, ] ); @@ -236,17 +235,16 @@ function get_score_for_type_2_question_no_negative_marks() } /** @test */ - function get_score_for_type_2_question_with_negative_marks_question_fixed() + public function get_score_for_type_2_question_with_negative_marks_question_fixed() { - [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init(2, true, Quiz::FIXED_NEGATIVE_TYPE, 0, 8, 2); [$question_option_one, $question_option_two, $question_option_three, $question_option_four] = $options; $quiz_attempt_answer_one = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_one->id, ] ); @@ -254,15 +252,15 @@ function get_score_for_type_2_question_with_negative_marks_question_fixed() } /** @test */ - function get_score_for_type_2_question_with_negative_marks_question_percentage() + public function get_score_for_type_2_question_with_negative_marks_question_percentage() { [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init(2, true, Quiz::PERCENTAGE_NEGATIVE_TYPE, 0, 8, 2); [$question_option_one, $question_option_two, $question_option_three, $question_option_four] = $options; $quiz_attempt_answer_one = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_one->id, ] ); @@ -270,15 +268,15 @@ function get_score_for_type_2_question_with_negative_marks_question_percentage() } /** @test */ - function get_score_for_type_2_question_with_negative_marks_quiz_fixed() + public function get_score_for_type_2_question_with_negative_marks_quiz_fixed() { [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init(2, true, Quiz::FIXED_NEGATIVE_TYPE, 1, 5, 0); [$question_option_one, $question_option_two, $question_option_three, $question_option_four] = $options; $quiz_attempt_answer_one = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_one->id, ] ); @@ -286,15 +284,15 @@ function get_score_for_type_2_question_with_negative_marks_quiz_fixed() } /** @test */ - function get_score_for_type_2_question_with_negative_marks_quiz_percentage() + public function get_score_for_type_2_question_with_negative_marks_quiz_percentage() { [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init(2, true, Quiz::PERCENTAGE_NEGATIVE_TYPE, 10, 5, 0); [$question_option_one, $question_option_two, $question_option_three, $question_option_four] = $options; $quiz_attempt_answer_one = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_one->id, ] ); @@ -302,157 +300,156 @@ function get_score_for_type_2_question_with_negative_marks_quiz_percentage() } /** @test */ - function get_score_for_type_3_question_no_negative_marks() + public function get_score_for_type_3_question_no_negative_marks() { [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init(3, false, Quiz::PERCENTAGE_NEGATIVE_TYPE, 0, 5, 0); [$question_option_one] = $options; $quiz_attempt_answer_one = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_one->id, - 'answer' => 'central processing unit' + 'answer' => 'central processing unit', ] ); $this->assertEquals(5, QuizAttempt::get_score_for_type_3_question($quiz_attempt, $quiz_question, [$quiz_attempt_answer_one])); } /** @test */ - function get_score_for_type_3_question_with_negative_marks_question_fixed() + public function get_score_for_type_3_question_with_negative_marks_question_fixed() { - [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init(3, true, Quiz::FIXED_NEGATIVE_TYPE, 0, 5, 1); [$question_option_one] = $options; $quiz_attempt_answer_one = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_one->id, - 'answer' => 'cpu' + 'answer' => 'cpu', ] ); $this->assertEquals(-1, QuizAttempt::get_score_for_type_3_question($quiz_attempt, $quiz_question, [$quiz_attempt_answer_one])); } /** @test */ - function get_score_for_type_3_question_with_negative_marks_question_percentage() + public function get_score_for_type_3_question_with_negative_marks_question_percentage() { [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init(3, true, Quiz::PERCENTAGE_NEGATIVE_TYPE, 0, 10, 10); [$question_option_one] = $options; $quiz_attempt_answer_one = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_one->id, - 'answer' => 'cpu' + 'answer' => 'cpu', ] ); $this->assertEquals(-1, QuizAttempt::get_score_for_type_3_question($quiz_attempt, $quiz_question, [$quiz_attempt_answer_one])); } /** @test */ - function get_score_for_type_3_question_with_negative_marks_quiz_fixed() + public function get_score_for_type_3_question_with_negative_marks_quiz_fixed() { [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init(3, true, Quiz::FIXED_NEGATIVE_TYPE, 1, 5, 0); [$question_option_one] = $options; $quiz_attempt_answer_one = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_one->id, - 'answer' => 'cpu' + 'answer' => 'cpu', ] ); $this->assertEquals(-1, QuizAttempt::get_score_for_type_3_question($quiz_attempt, $quiz_question, [$quiz_attempt_answer_one])); } /** @test */ - function get_score_for_type_3_question_with_negative_marks_quiz_percentage() + public function get_score_for_type_3_question_with_negative_marks_quiz_percentage() { [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init(3, true, Quiz::PERCENTAGE_NEGATIVE_TYPE, 10, 5, 0); [$question_option_one] = $options; $quiz_attempt_answer_one = QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_one->id, - 'answer' => 'cpu' + 'answer' => 'cpu', ] ); $this->assertEquals(-0.5, QuizAttempt::get_score_for_type_3_question($quiz_attempt, $quiz_question, [$quiz_attempt_answer_one])); } /** @test */ - function get_negative_marks_for_question() + public function get_negative_marks_for_question() { $testCases = [ [ - 'enable_negative_marks' => false, + 'enable_negative_marks' => false, 'quiz_negative_marks_type' => Quiz::FIXED_NEGATIVE_TYPE, - 'quiz_negative_marks' => 0, - 'question_marks' => 5, - 'question_negative_marks' => 0, - 'expected_negative_marks' => 0 + 'quiz_negative_marks' => 0, + 'question_marks' => 5, + 'question_negative_marks' => 0, + 'expected_negative_marks' => 0, ], [ - 'enable_negative_marks' => true, + 'enable_negative_marks' => true, 'quiz_negative_marks_type' => Quiz::FIXED_NEGATIVE_TYPE, - 'quiz_negative_marks' => 0, - 'question_marks' => 5, - 'question_negative_marks' => 1, - 'expected_negative_marks' => 1 + 'quiz_negative_marks' => 0, + 'question_marks' => 5, + 'question_negative_marks' => 1, + 'expected_negative_marks' => 1, ], [ - 'enable_negative_marks' => true, + 'enable_negative_marks' => true, 'quiz_negative_marks_type' => Quiz::PERCENTAGE_NEGATIVE_TYPE, - 'quiz_negative_marks' => 0, - 'question_marks' => 5, - 'question_negative_marks' => 30, - 'expected_negative_marks' => 1.5 + 'quiz_negative_marks' => 0, + 'question_marks' => 5, + 'question_negative_marks' => 30, + 'expected_negative_marks' => 1.5, ], [ - 'enable_negative_marks' => true, + 'enable_negative_marks' => true, 'quiz_negative_marks_type' => Quiz::FIXED_NEGATIVE_TYPE, - 'quiz_negative_marks' => 1, - 'question_marks' => 5, - 'question_negative_marks' => 0, - 'expected_negative_marks' => 1 + 'quiz_negative_marks' => 1, + 'question_marks' => 5, + 'question_negative_marks' => 0, + 'expected_negative_marks' => 1, ], [ - 'enable_negative_marks' => true, + 'enable_negative_marks' => true, 'quiz_negative_marks_type' => Quiz::PERCENTAGE_NEGATIVE_TYPE, - 'quiz_negative_marks' => 40, - 'question_marks' => 5, - 'question_negative_marks' => 0, - 'expected_negative_marks' => 2 - ] + 'quiz_negative_marks' => 40, + 'question_marks' => 5, + 'question_negative_marks' => 0, + 'expected_negative_marks' => 2, + ], ]; $question = Question::factory()->create([ - 'name' => 'Full Form Of CPU', + 'name' => 'Full Form Of CPU', 'question_type_id' => 3, - 'is_active' => true, + 'is_active' => true, ]); foreach ($testCases as $key => $testCase) { $quiz = Quiz::factory()->make()->create([ - 'name' => 'Sample Quiz', - 'slug' => 'sample-quiz-' . $key, + 'name' => 'Sample Quiz', + 'slug' => 'sample-quiz-' . $key, 'negative_marking_settings' => [ 'enable_negative_marks' => $testCase['enable_negative_marks'], 'negative_marking_type' => $testCase['quiz_negative_marks_type'], - 'negative_mark_value' => $testCase['quiz_negative_marks'], - ] + 'negative_mark_value' => $testCase['quiz_negative_marks'], + ], ]); $quizQuestion = QuizQuestion::factory()->create([ - 'quiz_id' => $quiz->id, - 'question_id' => $question->id, - 'marks' => $testCase['question_marks'], - 'order' => 1, + 'quiz_id' => $quiz->id, + 'question_id' => $question->id, + 'marks' => $testCase['question_marks'], + 'order' => 1, 'negative_marks' => $testCase['question_negative_marks'], ]); $this->assertEquals($testCase['expected_negative_marks'], QuizAttempt::get_negative_marks_for_question($quiz, $quizQuestion)); @@ -460,40 +457,40 @@ function get_negative_marks_for_question() } /** @test */ - function get_quiz_attempt_result_with_and_without_quiz_question() + public function get_quiz_attempt_result_with_and_without_quiz_question() { $testCases = [ [ - 'name' => 'Question type 1 with no negative marks', - 'question_type' => 1, - 'enable_negative_marks' => false, - 'negative_marking_type' => Quiz::PERCENTAGE_NEGATIVE_TYPE, + 'name' => 'Question type 1 with no negative marks', + 'question_type' => 1, + 'enable_negative_marks' => false, + 'negative_marking_type' => Quiz::PERCENTAGE_NEGATIVE_TYPE, 'quiz_negative_mark_value' => 0, - 'marks' => 5, - 'negative_marks' => 0, - 'expected' => [1 => ['score' => 5, 'is_correct' => true, 'correct_answer' => '7', 'user_answer' => 7]] + 'marks' => 5, + 'negative_marks' => 0, + 'expected' => [1 => ['score' => 5, 'is_correct' => true, 'correct_answer' => '7', 'user_answer' => 7]], ], [ - 'name' => 'Question type 2 with no negative marks', - 'question_type' => 2, - 'enable_negative_marks' => false, - 'negative_marking_type' => Quiz::PERCENTAGE_NEGATIVE_TYPE, + 'name' => 'Question type 2 with no negative marks', + 'question_type' => 2, + 'enable_negative_marks' => false, + 'negative_marking_type' => Quiz::PERCENTAGE_NEGATIVE_TYPE, 'quiz_negative_mark_value' => 0, - 'marks' => 5, - 'negative_marks' => 0, - 'expected' => [1 => ['score' => 5, 'is_correct' => true, 'correct_answer' => ['array', 'object'], 'user_answer' => ['array', 'object']]] + 'marks' => 5, + 'negative_marks' => 0, + 'expected' => [1 => ['score' => 5, 'is_correct' => true, 'correct_answer' => ['array', 'object'], 'user_answer' => ['array', 'object']]], ], [ - 'name' => 'Question type 3 with no negative marks', - 'question_type' => 3, - 'enable_negative_marks' => false, - 'negative_marking_type' => Quiz::PERCENTAGE_NEGATIVE_TYPE, + 'name' => 'Question type 3 with no negative marks', + 'question_type' => 3, + 'enable_negative_marks' => false, + 'negative_marking_type' => Quiz::PERCENTAGE_NEGATIVE_TYPE, 'quiz_negative_mark_value' => 0, - 'marks' => 5, - 'negative_marks' => 0, - 'expected' => [1 => ['score' => 5, 'is_correct' => true, 'correct_answer' => 'central processing unit', 'user_answer' => 'central processing unit']] - ] + 'marks' => 5, + 'negative_marks' => 0, + 'expected' => [1 => ['score' => 5, 'is_correct' => true, 'correct_answer' => 'central processing unit', 'user_answer' => 'central processing unit']], + ], ]; foreach ($testCases as $testCase) { [$user, $question, $options, $quiz, $quiz_question, $quiz_attempt] = $this->init($testCase['question_type'], $testCase['enable_negative_marks'], $testCase['negative_marking_type'], $testCase['quiz_negative_mark_value'], $testCase['marks'], $testCase['negative_marks']); @@ -505,33 +502,33 @@ function get_quiz_attempt_result_with_and_without_quiz_question() if ($testCase['question_type'] == 1) { QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_four->id, ] ); } elseif ($testCase['question_type'] == 2) { QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_one->id, ] ); QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_two->id, ] ); } else { QuizAttemptAnswer::create( [ - 'quiz_attempt_id' => $quiz_attempt->id, - 'quiz_question_id' => $quiz_question->id, + 'quiz_attempt_id' => $quiz_attempt->id, + 'quiz_question_id' => $quiz_question->id, 'question_option_id' => $question_option_one->id, - 'answer' => 'central processing unit' + 'answer' => 'central processing unit', ] ); } @@ -550,4 +547,145 @@ function get_quiz_attempt_result_with_and_without_quiz_question() DB::raw("SET foreign_key_checks=1"); } } + + /** @test */ + public function test_quiz_attempt_validate() + { + + $user = Author::create( + ['name' => "John Doe"] + ); + QuestionType::insert( + [ + [ + 'name' => 'multiple_choice_single_answer', + ], + [ + 'name' => 'multiple_choice_multiple_answer', + ], + [ + 'name' => 'fill_the_blank', + ], + ] + ); + $questionsWithOptions = [ + [ + 'question' => 'How many world wonders are there?', + 'answer' => 7, + 'id' => 1, + ], + [ + 'question' => 'What is the biggest desert in the world?', + 'answer' => 'Sahara', + 'id' => 2, + ], + [ + 'question' => 'What is the biggest bird?', + 'answer' => 'Ostrich', + 'id' => 3, + ], + ]; + $quiz = Quiz::factory()->make()->create([ + 'name' => 'Sample Quiz', + 'slug' => 'sample-quiz', + 'negative_marking_settings' => [ + 'enable_negative_marks' => false, + 'negative_marking_type' => Quiz::FIXED_NEGATIVE_TYPE, + 'negative_mark_value' => 1, + ], + ]); + $quizAttemptOne = QuizAttempt::create([ + 'quiz_id' => $quiz->id, + 'participant_id' => $user->id, + 'participant_type' => get_class($user), + ]); + $quizAttemptTwo = QuizAttempt::create([ + 'quiz_id' => $quiz->id, + 'participant_id' => $user->id, + 'participant_type' => get_class($user), + ]); + foreach ($questionsWithOptions as $questionsWithOption) { + Question::factory()->create([ + 'name' => $questionsWithOption['question'], + 'question_type_id' => 3, + 'is_active' => true, + 'id' => $questionsWithOption['id'], + ]); + QuestionOption::factory()->create([ + 'question_id' => $questionsWithOption['id'], + 'name' => $questionsWithOption['answer'], + 'is_correct' => true, + 'id' => $questionsWithOption['id'], + ]); + QuizQuestion::factory()->create([ + 'quiz_id' => $quiz->id, + 'question_id' => $questionsWithOption['id'], + 'marks' => 1, + 'order' => 1, + 'negative_marks' => 0.5, + 'id' => $questionsWithOption['id'], + ]); + QuizAttemptAnswer::create( + [ + 'quiz_attempt_id' => $quizAttemptOne->id, + 'quiz_question_id' => $questionsWithOption['id'], + 'question_option_id' => $questionsWithOption['id'], + 'answer' => $questionsWithOption['answer'], + ] + ); + if ($questionsWithOption['id'] != 3) { //Skipping the third question being attempted + QuizAttemptAnswer::create( + [ + 'quiz_attempt_id' => $quizAttemptTwo->id, + 'quiz_question_id' => $questionsWithOption['id'], + 'question_option_id' => $questionsWithOption['id'], + 'answer' => $questionsWithOption['answer'] . 's', + ] + ); + } + + } + + $this->assertEquals([ + 1 => [ + 'score' => 1, + 'is_correct' => true, + 'correct_answer' => '7', + 'user_answer' => 7, + ], + 2 => [ + 'score' => 1, + 'is_correct' => true, + 'correct_answer' => 'Sahara', + 'user_answer' => 'Sahara', + ], + 3 => [ + 'score' => 1, + 'is_correct' => true, + 'correct_answer' => 'Ostrich', + 'user_answer' => 'Ostrich', + ], + ], $quizAttemptOne->validate(), 'Quiz Attempt With Correct Answers'); + $this->assertEquals([ + 1 => [ + 'score' => 0, + 'is_correct' => false, + 'correct_answer' => '7', + 'user_answer' => '7s', + ], + 2 => [ + 'score' => 0, + 'is_correct' => false, + 'correct_answer' => 'Sahara', + 'user_answer' => 'Saharas', + ], + 3 => [ + 'score' => 0, + 'is_correct' => false, + 'correct_answer' => 'Ostrich', + 'user_answer' => null, + ], + ], $quizAttemptTwo->validate(), 'Quiz Attempt With Wrong Answers'); + + } }