From 644aca456011191ef57a87ac772ebd5e0c947461 Mon Sep 17 00:00:00 2001 From: Sofia Chukareva Date: Thu, 5 Sep 2024 13:19:32 +0200 Subject: [PATCH 1/8] add Question explanation --- frontend/src/quiz.tsx | 3 +++ frontend/tests/pages/quiz-taking-page.ts | 2 ++ frontend/tests/steps/quiz-question.ts | 5 +++++ specs/QuestionExplanation.feature | 26 ++++++++++++++++++++++++ 4 files changed, 36 insertions(+) create mode 100644 specs/QuestionExplanation.feature diff --git a/frontend/src/quiz.tsx b/frontend/src/quiz.tsx index 7bb291b..4136238 100644 --- a/frontend/src/quiz.tsx +++ b/frontend/src/quiz.tsx @@ -9,6 +9,8 @@ import { preventDefault } from 'helpers.ts' const Feedback = (correct: boolean) =>

{correct ? 'Correct!' : 'Incorrect!'}

+const QuestionExplanation =

{'Question Explanation'}

+ const Question = ({ id, question, answers, quizType }: QuizQuestion) => { const [selectedAnswer, setSelectedAnswer] = createSignal(null) const [isAnswerCorrect, setIsAnswerCorrect] = createSignal(false) @@ -60,6 +62,7 @@ const Question = ({ id, question, answers, quizType }: QuizQuestion) => { + ) } diff --git a/frontend/tests/pages/quiz-taking-page.ts b/frontend/tests/pages/quiz-taking-page.ts index 553b121..cb0a899 100644 --- a/frontend/tests/pages/quiz-taking-page.ts +++ b/frontend/tests/pages/quiz-taking-page.ts @@ -33,4 +33,6 @@ export default class QuizTakingPage { getQuestions = async () => { return this.page.locator('.quiz-questions li').allTextContents() } + + questionExplanationLocator = () => this.page.locator('p.questionExplanation') } diff --git a/frontend/tests/steps/quiz-question.ts b/frontend/tests/steps/quiz-question.ts index a73bbe5..bb1c2d3 100644 --- a/frontend/tests/steps/quiz-question.ts +++ b/frontend/tests/steps/quiz-question.ts @@ -95,3 +95,8 @@ Then('I should see {string}', async (feedback: string) => { const feedbackLocator = world.quizTakingPage.feedbackLocator() await expectTextToBe(feedbackLocator, feedback) }) + +Then('I should see question explanation {string}', async (questionExplanation: string) => { + const questionExplanationLocator = world.quizTakingPage.questionExplanationLocator() + await expectTextToBe(questionExplanationLocator, questionExplanation) +}) diff --git a/specs/QuestionExplanation.feature b/specs/QuestionExplanation.feature new file mode 100644 index 0000000..a046128 --- /dev/null +++ b/specs/QuestionExplanation.feature @@ -0,0 +1,26 @@ +Feature: Question explanation + + Background: + Given a quiz question "What is the capital of Italy?" bookmarked as "Italy" with answers + | Rome | correct | + | Naples | | + | Florence | | + | Palermo | | + And a quiz question "What is the capital of France?" bookmarked as "France" with answers + | Marseille | | + | Lyon | | + | Paris | correct | + | Toulouse | | + + Scenario Outline: + When I visit the "" quiz-taking page + And I select the answer "" + And I submit the quiz + Then I should see "" + And I should see question explanation "" + Examples: + | question | answer | feedback | question explanation | + | Italy | Rome | Correct! | Question Explanation | + | Italy | Palermo | Incorrect! | Question Explanation | + | France | Paris | Correct! | Question Explanation | + | France | Toulouse | Incorrect! | Question Explanation | From 20449f1a315a35d7c92aa29503af8e4c4d50a0a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Milsimer?= Date: Thu, 5 Sep 2024 14:05:02 +0200 Subject: [PATCH 2/8] Provide detailed explanation per answer --- .../quizmaster/quiz/QuizQuestion.java | 4 ++++ frontend/src/model/quiz-question.ts | 1 + frontend/src/quiz.tsx | 17 ++++++++++++++--- frontend/tests/pages/quiz-taking-page.ts | 2 ++ frontend/tests/steps/quiz-question.ts | 17 +++++++++++++---- specs/DetailedFeedbackPerAnswer.feature | 19 +++++++++++++++++++ 6 files changed, 53 insertions(+), 7 deletions(-) create mode 100644 specs/DetailedFeedbackPerAnswer.feature diff --git a/backend/src/main/java/cz/scrumdojo/quizmaster/quiz/QuizQuestion.java b/backend/src/main/java/cz/scrumdojo/quizmaster/quiz/QuizQuestion.java index 5ffb45e..33959f5 100644 --- a/backend/src/main/java/cz/scrumdojo/quizmaster/quiz/QuizQuestion.java +++ b/backend/src/main/java/cz/scrumdojo/quizmaster/quiz/QuizQuestion.java @@ -20,6 +20,10 @@ public class QuizQuestion { @JdbcTypeCode(SqlTypes.ARRAY) private String[] answers; + @Column(name = "explanations", columnDefinition = "text[]") + @JdbcTypeCode(SqlTypes.ARRAY) + private String[] explanations; + private Integer correctAnswer; @Transient diff --git a/frontend/src/model/quiz-question.ts b/frontend/src/model/quiz-question.ts index 88a41a0..3affcdd 100644 --- a/frontend/src/model/quiz-question.ts +++ b/frontend/src/model/quiz-question.ts @@ -2,5 +2,6 @@ export interface QuizQuestion { readonly id: number readonly question: string readonly answers: readonly string[] + readonly explanations: readonly string[] readonly quizType: string } diff --git a/frontend/src/quiz.tsx b/frontend/src/quiz.tsx index 4136238..51dc0d4 100644 --- a/frontend/src/quiz.tsx +++ b/frontend/src/quiz.tsx @@ -9,11 +9,15 @@ import { preventDefault } from 'helpers.ts' const Feedback = (correct: boolean) => +const Explanation = (explanation: string) => {explanation} + const QuestionExplanation =

{'Question Explanation'}

-const Question = ({ id, question, answers, quizType }: QuizQuestion) => { +const Question = ({ id, question, answers, quizType, explanations }: QuizQuestion) => { const [selectedAnswer, setSelectedAnswer] = createSignal(null) const [isAnswerCorrect, setIsAnswerCorrect] = createSignal(false) + const [explanation, setExplanation] = createSignal("") + const [explanationIdx, setExplanationIdx] = createSignal(null) const [submitted, setSubmitted] = createSignal(false) @@ -23,10 +27,14 @@ const Question = ({ id, question, answers, quizType }: QuizQuestion) => { api.isAnswerCorrect(id, selectedAnswerIdx).then(isCorrect => { setSubmitted(true) setIsAnswerCorrect(isCorrect) + setExplanation(explanations[selectedAnswerIdx]) + setExplanationIdx(selectedAnswerIdx) }) }) - const selectAnswer = (answerIdx: number) => () => setSelectedAnswer(answerIdx) + const selectAnswer = (answerIdx: number) => () => { + setSelectedAnswer(answerIdx) + } const Answer = (answer: string, idx: Accessor) => { const answerId = `answer-${idx()}` @@ -49,7 +57,10 @@ const Question = ({ id, question, answers, quizType }: QuizQuestion) => { return (
  • - +
  • ) } diff --git a/frontend/tests/pages/quiz-taking-page.ts b/frontend/tests/pages/quiz-taking-page.ts index cb0a899..ff038eb 100644 --- a/frontend/tests/pages/quiz-taking-page.ts +++ b/frontend/tests/pages/quiz-taking-page.ts @@ -26,6 +26,8 @@ export default class QuizTakingPage { feedbackLocator = () => this.page.locator('p.feedback') + explanationLocator = () => this.page.locator('span.explanation') + getFeedback = async () => { return this.page.locator('.feedback').innerText() } diff --git a/frontend/tests/steps/quiz-question.ts b/frontend/tests/steps/quiz-question.ts index 444b6cd..b2c0c6a 100644 --- a/frontend/tests/steps/quiz-question.ts +++ b/frontend/tests/steps/quiz-question.ts @@ -8,6 +8,7 @@ interface QuizQuestionData { readonly question: string readonly answers: readonly string[] readonly correctAnswer: number + readonly explanations: readonly string[] } interface QuizQuestion { @@ -15,11 +16,13 @@ interface QuizQuestion { readonly quizQuestion: QuizQuestionData } -type AnswerRaw = [string, string] +type AnswerRaw = [string, string, string] type Answers = string[] +type Explanations = string[] const toAnswers = (raw: AnswerRaw[]): Answers => raw.map(([answer]) => answer) const toCorrectAnswer = (raw: AnswerRaw[]): number => raw.findIndex(([, correct]) => correct === 'correct') +const toExplanations = (raw: AnswerRaw[]): Explanations => raw.map(([, , explanation]) => explanation) interface QuizQuestionWorld { questionCreationPage: QuestionCreationPage @@ -56,11 +59,12 @@ Before(() => { Given( 'a quiz question {string} bookmarked as {string} with answers', - async (question: string, bookmark: string, table: TableOf) => { + async (question: string, bookmark: string, answerRawTable: TableOf) => { await bookmarkQuizQuestion(bookmark, { question, - answers: toAnswers(table.raw()), - correctAnswer: toCorrectAnswer(table.raw()), + answers: toAnswers(answerRawTable.raw()), + correctAnswer: toCorrectAnswer(answerRawTable.raw()), + explanations: toExplanations(answerRawTable.raw()), }) }, ) @@ -100,6 +104,11 @@ Then('I should see {string}', async (feedback: string) => { await expectTextToBe(feedbackLocator, feedback) }) +Then('I should see the explanation {string}', async (explanation: string) => { + const explanationLocator = world.quizTakingPage.explanationLocator() + await expectTextToBe(explanationLocator, ` ${explanation}`) +}) + When('I visit the create question page', async () => { await world.questionCreationPage.goto() }) diff --git a/specs/DetailedFeedbackPerAnswer.feature b/specs/DetailedFeedbackPerAnswer.feature new file mode 100644 index 0000000..c243b5b --- /dev/null +++ b/specs/DetailedFeedbackPerAnswer.feature @@ -0,0 +1,19 @@ +Feature: View explanations for answers after responding to a question + + Background: + Given a quiz question "What is the capital of Italy?" bookmarked as "Italy" with answers + | Rome | correct | Rome is the capital of Italy | + | Naples | | Naples is not the capital of Italy | + | Florence | | Florence is not the capital of Italy | + | Palermo | | Palermo is not the capital of Italy | + + Scenario Outline: + When I visit the "" quiz-taking page + And I select the answer "" + And I submit the quiz + Then I should see "" + And I should see the explanation "" + Examples: + | question | answer | feedback | explanation | + | Italy | Rome | Correct! | Rome is the capital of Italy | + | Italy | Palermo | Incorrect! | Palermo is not the capital of Italy | From 705b250f0d9d99138dd5f2ea0817ff383c5b215e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Milsimer?= Date: Thu, 5 Sep 2024 14:29:31 +0200 Subject: [PATCH 3/8] Style Fix --- frontend/src/quiz.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/quiz.tsx b/frontend/src/quiz.tsx index 51dc0d4..25a827b 100644 --- a/frontend/src/quiz.tsx +++ b/frontend/src/quiz.tsx @@ -16,7 +16,7 @@ const QuestionExplanation =

    {'Question Explanatio const Question = ({ id, question, answers, quizType, explanations }: QuizQuestion) => { const [selectedAnswer, setSelectedAnswer] = createSignal(null) const [isAnswerCorrect, setIsAnswerCorrect] = createSignal(false) - const [explanation, setExplanation] = createSignal("") + const [explanation, setExplanation] = createSignal('') const [explanationIdx, setExplanationIdx] = createSignal(null) const [submitted, setSubmitted] = createSignal(false) From 668f6d54842fb5c1fdf9fed08cef0d4eed8b0fe5 Mon Sep 17 00:00:00 2001 From: Zdenek Cejka Date: Thu, 5 Sep 2024 14:25:52 +0200 Subject: [PATCH 4/8] add correct answers into quiz-question table --- .../resources/db/migration/{V00005_quiz.sql => V00005__quiz.sql} | 0 .../db/migration/V00006__quiz_question_answers_validation.sql | 1 + 2 files changed, 1 insertion(+) rename backend/src/main/resources/db/migration/{V00005_quiz.sql => V00005__quiz.sql} (100%) create mode 100644 backend/src/main/resources/db/migration/V00006__quiz_question_answers_validation.sql diff --git a/backend/src/main/resources/db/migration/V00005_quiz.sql b/backend/src/main/resources/db/migration/V00005__quiz.sql similarity index 100% rename from backend/src/main/resources/db/migration/V00005_quiz.sql rename to backend/src/main/resources/db/migration/V00005__quiz.sql diff --git a/backend/src/main/resources/db/migration/V00006__quiz_question_answers_validation.sql b/backend/src/main/resources/db/migration/V00006__quiz_question_answers_validation.sql new file mode 100644 index 0000000..3c70d94 --- /dev/null +++ b/backend/src/main/resources/db/migration/V00006__quiz_question_answers_validation.sql @@ -0,0 +1 @@ +ALTER TABLE quiz_question ADD COLUMN answers_validation boolean[] NOT NULL; \ No newline at end of file From beb8745e53d64282161d6d4404df59eae0136b04 Mon Sep 17 00:00:00 2001 From: "kateryna.ponomarova" Date: Thu, 5 Sep 2024 14:32:21 +0200 Subject: [PATCH 5/8] Update TEST for multiple-choise-feedback-per-answer functionality --- frontend/src/quiz.tsx | 2 +- frontend/tests/pages/quiz-taking-page.ts | 2 ++ frontend/tests/steps/create-quiz.ts | 5 +++-- .../steps/multiple-choise-feedback-per-answer.ts | 5 ----- specs/MultipleChoiseFeedbackPerAnswer.feature | 12 ++++++++++-- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/frontend/src/quiz.tsx b/frontend/src/quiz.tsx index 51dc0d4..25a827b 100644 --- a/frontend/src/quiz.tsx +++ b/frontend/src/quiz.tsx @@ -16,7 +16,7 @@ const QuestionExplanation =

    {'Question Explanatio const Question = ({ id, question, answers, quizType, explanations }: QuizQuestion) => { const [selectedAnswer, setSelectedAnswer] = createSignal(null) const [isAnswerCorrect, setIsAnswerCorrect] = createSignal(false) - const [explanation, setExplanation] = createSignal("") + const [explanation, setExplanation] = createSignal('') const [explanationIdx, setExplanationIdx] = createSignal(null) const [submitted, setSubmitted] = createSignal(false) diff --git a/frontend/tests/pages/quiz-taking-page.ts b/frontend/tests/pages/quiz-taking-page.ts index ff038eb..9506d55 100644 --- a/frontend/tests/pages/quiz-taking-page.ts +++ b/frontend/tests/pages/quiz-taking-page.ts @@ -37,4 +37,6 @@ export default class QuizTakingPage { } questionExplanationLocator = () => this.page.locator('p.questionExplanation') + + getUrl = () => this.page.url() } diff --git a/frontend/tests/steps/create-quiz.ts b/frontend/tests/steps/create-quiz.ts index 5fd188d..4098d98 100644 --- a/frontend/tests/steps/create-quiz.ts +++ b/frontend/tests/steps/create-quiz.ts @@ -59,8 +59,9 @@ When('quiz taker clicks the link', async () => { }) Then('quiz taker is on the quiz page', async () => { - const pageTitle = await world.quizTakingPage.getTitle() - expect(pageTitle).toBe('Take Quiz') + const currentUrl = await world.quizTakingPage.getUrl() + const urlPattern = /^http:\/\/localhost:(5173|8080)\/quiz\/\d+$/ + expect(currentUrl).toMatch(urlPattern) }) Then('quiz taker sees a correct list of the questions', async () => { diff --git a/frontend/tests/steps/multiple-choise-feedback-per-answer.ts b/frontend/tests/steps/multiple-choise-feedback-per-answer.ts index 3260212..3b24532 100644 --- a/frontend/tests/steps/multiple-choise-feedback-per-answer.ts +++ b/frontend/tests/steps/multiple-choise-feedback-per-answer.ts @@ -9,11 +9,6 @@ interface MultipleChoiceWorld { const world = worldAs() -Then('quiz taker is on the quiz page', async () => { - const pageTitle = await world.quizTakingPage.getTitle() - expect(pageTitle).toBe('Take Quiz') -}) - Then('quiz taker sees question with multiple choice', async () => { const questionType = await world.quizTakingPage.getQuestionType() expect(questionType).toBe('multiple choice') diff --git a/specs/MultipleChoiseFeedbackPerAnswer.feature b/specs/MultipleChoiseFeedbackPerAnswer.feature index 9181bc2..c90eee5 100644 --- a/specs/MultipleChoiseFeedbackPerAnswer.feature +++ b/specs/MultipleChoiseFeedbackPerAnswer.feature @@ -1,9 +1,17 @@ Feature: Multiple choice - feedback per answer feature + Background: + Given a quiz question "what countries are in Europe?" bookmarked as "Europe" with answers + | Italy | correct | + | France | correct | + | Morocco | | + | Spain | | + + Scenario: Multiple choice - feedback per answer - happy path -# Given I visit the "Europe" quiz-taking page -# Then quiz taker is on the quiz page + Given I visit the "Europe" quiz-taking page + Then quiz taker is on the quiz page # And quiz taker sees question with multiple choise # # When quiz taker chooses From 4d677215678bb6d2c155beae3c2c7376142e93ae Mon Sep 17 00:00:00 2001 From: "anna.kasimov" Date: Thu, 5 Sep 2024 14:26:39 +0200 Subject: [PATCH 6/8] add getAllQuestions API --- .../quizmaster/quiz/QuizQuestionController.java | 13 ++++++++++--- .../quizmaster/quiz/QuizQuestionControllerTest.java | 8 ++++++++ 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/backend/src/main/java/cz/scrumdojo/quizmaster/quiz/QuizQuestionController.java b/backend/src/main/java/cz/scrumdojo/quizmaster/quiz/QuizQuestionController.java index 5ffa44c..8621210 100644 --- a/backend/src/main/java/cz/scrumdojo/quizmaster/quiz/QuizQuestionController.java +++ b/backend/src/main/java/cz/scrumdojo/quizmaster/quiz/QuizQuestionController.java @@ -1,13 +1,13 @@ package cz.scrumdojo.quizmaster.quiz; -import java.util.List; -import java.util.Optional; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; +import java.util.List; +import java.util.Optional; + @RestController @RequestMapping("/api") public class QuizQuestionController { @@ -48,6 +48,13 @@ public ResponseEntity answerQuestionV2(@PathVariable Integer id, @Reque return response(findQuestion(id).map(quizQuestion -> answers.contains(quizQuestion.getCorrectAnswer()))); } + @Transactional + @GetMapping("/quiz-question/all") + public ResponseEntity> getAllQuestionList() { + List quizQuestions = quizQuestionRepository.findAll(); + return ResponseEntity.ok().body(quizQuestions); + } + private Optional findQuestion(Integer id) { return quizQuestionRepository.findById(id); } diff --git a/backend/src/test/java/cz/scrumdojo/quizmaster/quiz/QuizQuestionControllerTest.java b/backend/src/test/java/cz/scrumdojo/quizmaster/quiz/QuizQuestionControllerTest.java index 0ceb95c..f4f4d2e 100644 --- a/backend/src/test/java/cz/scrumdojo/quizmaster/quiz/QuizQuestionControllerTest.java +++ b/backend/src/test/java/cz/scrumdojo/quizmaster/quiz/QuizQuestionControllerTest.java @@ -87,4 +87,12 @@ public void answerNonExistingQuestion() { assertEquals(HttpStatus.NOT_FOUND, response.getStatusCode()); } + + @Test + public void returnAllQuestions() { + ResponseEntity response = quizQuestionController.getAllQuestionList(); + + assertEquals(HttpStatus.OK, response.getStatusCode()); + assertNotNull(response.getBody()); + } } From a7149ec0733cb5b454a805a975294212a24fb2c2 Mon Sep 17 00:00:00 2001 From: Zdenek Cejka Date: Thu, 5 Sep 2024 14:50:23 +0200 Subject: [PATCH 7/8] add answers validation into quiz question table --- .../db/migration/V00006__quiz_question_answers_validation.sql | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/main/resources/db/migration/V00006__quiz_question_answers_validation.sql b/backend/src/main/resources/db/migration/V00006__quiz_question_answers_validation.sql index 3c70d94..c2cbf3f 100644 --- a/backend/src/main/resources/db/migration/V00006__quiz_question_answers_validation.sql +++ b/backend/src/main/resources/db/migration/V00006__quiz_question_answers_validation.sql @@ -1 +1 @@ -ALTER TABLE quiz_question ADD COLUMN answers_validation boolean[] NOT NULL; \ No newline at end of file +ALTER TABLE quiz_question ADD COLUMN answers_validation boolean[] NULL; \ No newline at end of file From 616ed37dec54345f2cde0bba2709ae773cc96180 Mon Sep 17 00:00:00 2001 From: "kateryna.ponomarova" Date: Thu, 5 Sep 2024 15:01:16 +0200 Subject: [PATCH 8/8] Update TEST for multiple-choise-feedback-per-answer functionality --- .../steps/multiple-choise-feedback-per-answer.ts | 11 ----------- frontend/tests/steps/quiz-question.ts | 4 +++- specs/MultipleChoiseFeedbackPerAnswer.feature | 9 +++++---- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/frontend/tests/steps/multiple-choise-feedback-per-answer.ts b/frontend/tests/steps/multiple-choise-feedback-per-answer.ts index 3b24532..3313df1 100644 --- a/frontend/tests/steps/multiple-choise-feedback-per-answer.ts +++ b/frontend/tests/steps/multiple-choise-feedback-per-answer.ts @@ -14,17 +14,6 @@ Then('quiz taker sees question with multiple choice', async () => { expect(questionType).toBe('multiple choice') }) -When('quiz taker chooses {string}', async (answers: string) => { - const answerList = answers.split(',').map(answer => answer.trim()) - for (const answer of answerList) { - await world.quizTakingPage.selectAnswer(answer) - } -}) - -When('quiz taker clicks on submit button', async () => { - await world.quizTakingPage.submit() -}) - Then('quiz taker sees the {string}', async (result: string) => { const feedback = await world.quizTakingPage.getFeedback() expect(feedback).toContain(result) diff --git a/frontend/tests/steps/quiz-question.ts b/frontend/tests/steps/quiz-question.ts index b2c0c6a..1a77bc5 100644 --- a/frontend/tests/steps/quiz-question.ts +++ b/frontend/tests/steps/quiz-question.ts @@ -71,7 +71,9 @@ Given( When('I visit the {string} quiz-taking page', async (bookmark: string) => { world.activeBookmark = bookmark - await world.quizTakingPage.goto(world.bookmarks[bookmark].quizQuestionId) + const quizId = world.bookmarks[bookmark].quizQuestionId + console.log(`Navigating to the quiz-taking page: ${quizId}`) + await world.quizTakingPage.goto(quizId) }) When('I select the answer {string}', async (answer: string) => { diff --git a/specs/MultipleChoiseFeedbackPerAnswer.feature b/specs/MultipleChoiseFeedbackPerAnswer.feature index c90eee5..0dc2ff1 100644 --- a/specs/MultipleChoiseFeedbackPerAnswer.feature +++ b/specs/MultipleChoiseFeedbackPerAnswer.feature @@ -12,10 +12,11 @@ Feature: Multiple choice - feedback per answer feature Scenario: Multiple choice - feedback per answer - happy path Given I visit the "Europe" quiz-taking page Then quiz taker is on the quiz page -# And quiz taker sees question with multiple choise -# -# When quiz taker chooses -# And quiz taker clicks on submit button + And quiz taker sees question with multiple choise + + When I select the answer "France" + And I select the answer "Italy" + And I submit the quiz # Then quiz taker sees the # # Examples: