diff --git a/.gitignore b/.gitignore index 3e95370..1a696b4 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ vendor/ .phpunit.result.cache logfile.txt .vscode/ -.DS_Store \ No newline at end of file +.DS_Store +.idea/ \ No newline at end of file diff --git a/README.md b/README.md index c625867..4cbe21a 100644 --- a/README.md +++ b/README.md @@ -293,6 +293,65 @@ 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 +`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 => [ + 'score' => 10, + 'is_correct' => true, + 'correct_answer' => ['One','Five','Seven'], + 'user_answer' => ['Five','One','Seven'] + ], + 2 => [ + 'score' => 0, + 'is_correct' => false, + 'correct_answer' => 'Hello There', + 'user_answer' => 'Hello World' + ] +] +``` +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', + 2 => '\Harishdurga\LaravelQuiz\Models\QuizAttempt::renderQuestionType2Answers', + 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 +`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) + { + /** + * @var Question $actualQuestion + */ + $actualQuestion = $quizQuestion->question; + $answers = $quizQuestion->answers; + $questionOptions = $actualQuestion->options; + $correctAnswer = $actualQuestion->correct_options()->first()?->option; + $givenAnswer = $answers->first()?->question_option_id; + foreach ($questionOptions as $questionOption) { + if ($questionOption->id == $givenAnswer) { + $givenAnswer = $questionOption->option; + break; + } + } + 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. + ### Testing ```bash diff --git a/config/config.php b/config/config.php index 02b265a..65fbe8d 100644 --- a/config/config.php +++ b/config/config.php @@ -12,16 +12,16 @@ */ 'table_names' => [ - 'topics' => 'topics', - 'question_types' => 'question_types', - 'questions' => 'questions', - 'topicables' => 'topicables', - 'question_options' => 'question_options', - 'quizzes' => 'quizzes', - 'quiz_questions' => 'quiz_questions', - 'quiz_attempts' => 'quiz_attempts', + 'topics' => 'topics', + 'question_types' => 'question_types', + 'questions' => 'questions', + 'topicables' => 'topicables', + 'question_options' => 'question_options', + 'quizzes' => 'quizzes', + 'quiz_questions' => 'quiz_questions', + 'quiz_attempts' => 'quiz_attempts', 'quiz_attempt_answers' => 'quiz_attempt_answers', - 'quiz_authors' => 'quiz_authors' + 'quiz_authors' => 'quiz_authors' ], /* @@ -103,6 +103,20 @@ 1 => '\Harishdurga\LaravelQuiz\Models\QuizAttempt::get_score_for_type_1_question', 2 => '\Harishdurga\LaravelQuiz\Models\QuizAttempt::get_score_for_type_2_question', 3 => '\Harishdurga\LaravelQuiz\Models\QuizAttempt::get_score_for_type_3_question', + ], + + /* + |-------------------------------------------------------------------------- + | Question type answer/solution render + |-------------------------------------------------------------------------- + | + | Render correct answer and given response for different question types + | + */ + 'render_answers_responses' => [ + 1 => '\Harishdurga\LaravelQuiz\Models\QuizAttempt::renderQuestionType1Answers', + 2 => '\Harishdurga\LaravelQuiz\Models\QuizAttempt::renderQuestionType2Answers', + 3 => '\Harishdurga\LaravelQuiz\Models\QuizAttempt::renderQuestionType3Answers', ] ]; diff --git a/src/Models/QuestionType.php b/src/Models/QuestionType.php index b63a8bc..1422789 100644 --- a/src/Models/QuestionType.php +++ b/src/Models/QuestionType.php @@ -12,6 +12,8 @@ class QuestionType extends Model { use HasFactory, SoftDeletes; + public const MULTIPLE_CHOICE_SINGLE_ANSWER = 1,MULTIPLE_CHOICE_MULTI_ANSWER=2,FILL_IN_BLANK=3; + /** * The attributes that aren't mass assignable. * diff --git a/src/Models/QuizAttempt.php b/src/Models/QuizAttempt.php index 8a0bb08..d22d8ef 100644 --- a/src/Models/QuizAttempt.php +++ b/src/Models/QuizAttempt.php @@ -2,7 +2,7 @@ namespace Harishdurga\LaravelQuiz\Models; -use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; @@ -56,7 +56,7 @@ public function calculate_score($data = null): float $score = 0; $quiz_questions_collection = $this->quiz->questions()->with('question')->orderBy('id', 'ASC')->get(); $quiz_attempt_answers = []; - foreach ($this->answers as $key => $quiz_attempt_answer) { + foreach ($this->answers as $quiz_attempt_answer) { $quiz_attempt_answers[$quiz_attempt_answer->quiz_question_id][] = $quiz_attempt_answer; } foreach ($quiz_questions_collection as $quiz_question) { @@ -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 $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; @@ -77,17 +77,17 @@ public static function get_score_for_type_1_question(QuizAttempt $quizAttempt, Q $negative_marks = self::get_negative_marks_for_question($quiz, $quizQuestion); if (!empty($correct_answer)) { if (count($quizQuestionAnswers)) { - return $quizQuestionAnswers[0]->question_option_id == $correct_answer ? $quizQuestion->marks : - ($negative_marks); // Return marks in case of correct answer else negative marks + return $quizQuestionAnswers[0]->question_option_id == $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 } /** * @param QuizAttemptAnswer[] $quizQuestionAnswers All the answers of the quiz question */ - public static function get_score_for_type_2_question(QuizAttempt $quizAttempt, QuizQuestion $quizQuestion, array $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; @@ -96,20 +96,20 @@ public static function get_score_for_type_2_question(QuizAttempt $quizAttempt, Q if (!empty($correct_answer)) { if (count($quizQuestionAnswers)) { $temp_arr = []; - foreach ($quizQuestionAnswers as $answer) { + foreach ($quizQuestionAnswers as $answer) { $temp_arr[] = $answer->question_option_id; } - return $correct_answer->toArray() == $temp_arr ? $quizQuestion->marks : - ($negative_marks); // Return marks in case of correct answer else negative marks + return $correct_answer->toArray() == $temp_arr ? $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 } /** * @param QuizAttemptAnswer[] $quizQuestionAnswers All the answers of the quiz question */ - public static function get_score_for_type_3_question(QuizAttempt $quizAttempt, QuizQuestion $quizQuestion, array $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; @@ -117,7 +117,7 @@ public static function get_score_for_type_3_question(QuizAttempt $quizAttempt, Q $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 + return $quizQuestionAnswers[0]->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 } @@ -129,7 +129,7 @@ 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; @@ -140,4 +140,95 @@ public static function get_negative_marks_for_question(Quiz $quiz, QuizQuestion } 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 + { + $isCorrect = true; + $actualQuestion = $quizQuestion->question; + $answers = $quizQuestion->answers; + $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); + return [ + 'score' => $score, + 'is_correct' => $isCorrect, + 'correct_answer' => $correctAnswer, + 'user_answer' => $userAnswer + ]; + } + + /** + * @param int|null $quizQuestionId + * @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 + { + if ($quizQuestionId) { + $quizQuestion = QuizQuestion::where(['quiz_id' => $this->quiz_id, 'id' => $quizQuestionId]) + ->with('question') + ->with('answers') + ->with('question.options')->first(); + if ($quizQuestion) { + return [$quizQuestionId => $this->validateQuizQuestion($quizQuestion, $data)]; + } + return null; //QuizQuestion is empty + } + $quizQuestions = QuizQuestion::where(['quiz_id' => $this->quiz_id])->get(); + if ($quizQuestions->count() == 0) { + return null; + } + $result = []; + foreach ($quizQuestions as $quizQuestion) { + $result[$quizQuestion->id] = $this->validateQuizQuestion($quizQuestion); + } + return $result; + } + + public static function renderQuestionType1Answers(QuizQuestion $quizQuestion, mixed $data = null) + { + /** + * @var Question $actualQuestion + */ + $actualQuestion = $quizQuestion->question; + $answers = $quizQuestion->answers; + $questionOptions = $actualQuestion->options; + $correctAnswer = $actualQuestion->correct_options()->first()?->option; + $givenAnswer = $answers->first()?->question_option_id; + foreach ($questionOptions as $questionOption) { + if ($questionOption->id == $givenAnswer) { + $givenAnswer = $questionOption->option; + break; + } + } + return [$correctAnswer, $givenAnswer]; + } + + public static function renderQuestionType2Answers(QuizQuestion $quizQuestion, mixed $data = null) + { + $actualQuestion = $quizQuestion->question; + $userAnswersCollection = $quizQuestion->answers; + $correctAnswersCollection = $actualQuestion->correct_options(); + $correctAnswers = $userAnswers = []; + foreach ($correctAnswersCollection as $correctAnswer) { + $correctAnswers[] = $correctAnswer->option; + } + foreach ($userAnswersCollection as $userAnswer) { + $userAnswers[] = $userAnswer?->question_option?->option; + } + return [$correctAnswers, $userAnswers]; + } + + public static function renderQuestionType3Answers(QuizQuestion $quizQuestion, mixed $data = null) + { + $actualQuestion = $quizQuestion->question; + $userAnswersCollection = $quizQuestion->answers; + $correctAnswersCollection = $actualQuestion->correct_options(); + $userAnswer = $userAnswersCollection->first()?->answer; + $correctAnswer = $correctAnswersCollection->first()?->option; + return [$correctAnswer, $userAnswer]; + } } diff --git a/tests/Unit/QuizAttemptTest.php b/tests/Unit/QuizAttemptTest.php index 15b0b82..6572536 100644 --- a/tests/Unit/QuizAttemptTest.php +++ b/tests/Unit/QuizAttemptTest.php @@ -12,6 +12,7 @@ use Harishdurga\LaravelQuiz\Models\QuestionOption; use Illuminate\Foundation\Testing\RefreshDatabase; use Harishdurga\LaravelQuiz\Models\QuizAttemptAnswer; +use Illuminate\Support\Facades\DB; class QuizAttemptTest extends TestCase @@ -40,90 +41,90 @@ function init($questionType = 1, $enableNegativeMarks = true, $negativeMarkingTy ); 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, + $quizQuestion = QuizQuestion::factory()->create([ + '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, + 'quiz_id' => $quiz->id, + 'participant_id' => $user->id, 'participant_type' => get_class($user) ]); return [$user, $question, $options, $quiz, $quizQuestion, $quizAttempt]; @@ -136,14 +137,14 @@ function get_score_for_type_1_question_no_negative_marks() [$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, + '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, ] ); @@ -157,8 +158,8 @@ function get_score_for_type_1_question_with_negative_marks_question_fixed() [$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, ] ); @@ -172,8 +173,8 @@ function get_score_for_type_1_question_with_negative_marks_question_percentage() [$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, ] ); @@ -188,8 +189,8 @@ function get_score_for_type_1_question_with_negative_marks_quiz_fixed() $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, ] ); @@ -203,8 +204,8 @@ function get_score_for_type_1_question_with_negative_marks_quiz_percentage() [$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, ] ); @@ -217,17 +218,17 @@ 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_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, ] ); @@ -242,10 +243,10 @@ 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_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, ] ); @@ -258,10 +259,10 @@ 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_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, ] ); @@ -274,10 +275,10 @@ 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_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, ] ); @@ -290,10 +291,10 @@ 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_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, ] ); @@ -306,12 +307,12 @@ 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_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])); @@ -325,12 +326,12 @@ 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_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])); @@ -342,12 +343,12 @@ 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_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])); @@ -359,12 +360,12 @@ 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_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])); @@ -376,12 +377,12 @@ 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_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])); @@ -392,69 +393,161 @@ 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, + $quizQuestion = QuizQuestion::factory()->create([ + '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)); } } + + /** @test */ + 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, + 'quiz_negative_mark_value' => 0, + '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, + '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']]] + ], + [ + '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']] + ] + ]; + 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']); + if ($testCase['question_type'] == 1 || $testCase['question_type'] == 2) { + [$question_option_one, $question_option_two, $question_option_three, $question_option_four] = $options; + } else { + [$question_option_one] = $options; + } + if ($testCase['question_type'] == 1) { + QuizAttemptAnswer::create( + [ + '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, + 'question_option_id' => $question_option_one->id, + ] + ); + QuizAttemptAnswer::create( + [ + '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, + 'question_option_id' => $question_option_one->id, + 'answer' => 'central processing unit' + ] + ); + } + $this->assertEquals($testCase['expected'], $quiz_attempt->validate($quiz_question->id)); + $this->assertEquals($testCase['expected'], $quiz_attempt->validate()); + DB::raw("SET foreign_key_checks=0"); + $databaseName = DB::getDatabaseName(); + $tables = config('laravel-quiz.table_names'); + foreach ($tables as $key => $name) { + //if you don't want to truncate migrations + if ($name == 'migrations') { + continue; + } + DB::table($name)->truncate(); + } + DB::raw("SET foreign_key_checks=1"); + } + } }