From 13136b9e9110b4f2eda126d66fcc302117500d8c Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Fri, 21 Mar 2025 11:05:16 -0400 Subject: [PATCH] fix bugs that showed in dev --- .../MultipleChoiceQuestionDisplay.test.tsx | 110 ++++++++++++++---- .../pages/ManageRoom/IsCorrect.test.tsx | 6 + .../StudentModeQuiz/StudentModeQuiz.test.tsx | 42 +++---- .../MultipleChoiceAnswersTemplate.ts | 4 +- .../MultipleChoiceQuestionDisplay.tsx | 34 ++++-- .../src/pages/Teacher/ManageRoom/useRooms.ts | 7 +- 6 files changed, 148 insertions(+), 55 deletions(-) diff --git a/client/src/__tests__/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx b/client/src/__tests__/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx index aa91b68..45e9b0a 100644 --- a/client/src/__tests__/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx @@ -12,14 +12,23 @@ const questions = parse( { =Choice 1 ~Choice 2 - }`) as MultipleChoiceQuestion[]; + } + + ::Sample Question 2:: Question stem + { + =Choice 1 + =Choice 2 + ~Choice 3 + } + `) as MultipleChoiceQuestion[]; -const question = questions[0]; +const questionWithOneCorrectChoice = questions[0]; +const questionWithMultipleCorrectChoices = questions[1]; describe('MultipleChoiceQuestionDisplay', () => { const mockHandleOnSubmitAnswer = jest.fn(); - const TestWrapper = ({ showAnswer }: { showAnswer: boolean }) => { + const TestWrapper = ({ showAnswer, question }: { showAnswer: boolean; question: MultipleChoiceQuestion }) => { const [showAnswerState, setShowAnswerState] = useState(showAnswer); const handleOnSubmitAnswer = (answer: AnswerType) => { @@ -38,20 +47,41 @@ describe('MultipleChoiceQuestionDisplay', () => { ); }; - const choices = question.choices; + const twoChoices = questionWithOneCorrectChoice.choices; + const threeChoices = questionWithMultipleCorrectChoices.choices; - beforeEach(() => { - render(); - }); + test('renders a question (that has only one correct choice) and its choices', () => { + render(); - test('renders the question and choices', () => { - expect(screen.getByText(question.formattedStem.text)).toBeInTheDocument(); - choices.forEach((choice) => { + expect(screen.getByText(questionWithOneCorrectChoice.formattedStem.text)).toBeInTheDocument(); + twoChoices.forEach((choice) => { expect(screen.getByText(choice.formattedText.text)).toBeInTheDocument(); }); }); + test('only allows one choice to be selected when question only has one correct answer', () => { + render(); + + const choiceButton1 = screen.getByText('Choice 1').closest('button'); + const choiceButton2 = screen.getByText('Choice 2').closest('button'); + + if (!choiceButton1 || !choiceButton2) throw new Error('Choice buttons not found'); + + // Simulate selecting multiple answers + act(() => { + fireEvent.click(choiceButton1); + }); + act(() => { + fireEvent.click(choiceButton2); + }); + + // Verify that only the last answer is selected + expect(choiceButton1.querySelector('.answer-text.selected')).not.toBeInTheDocument(); + expect(choiceButton2.querySelector('.answer-text.selected')).toBeInTheDocument(); + }); + test('does not submit when no answer is selected', () => { + render(); const submitButton = screen.getByText('Répondre'); act(() => { fireEvent.click(submitButton); @@ -61,6 +91,7 @@ describe('MultipleChoiceQuestionDisplay', () => { }); test('submits the selected answer', () => { + render(); const choiceButton = screen.getByText('Choice 1').closest('button'); if (!choiceButton) throw new Error('Choice button not found'); act(() => { @@ -75,12 +106,43 @@ describe('MultipleChoiceQuestionDisplay', () => { mockHandleOnSubmitAnswer.mockClear(); }); - test('submits multiple selected answers', () => { + + test('renders a question (that has multiple correct choices) and its choices', () => { + render(); + expect(screen.getByText(questionWithMultipleCorrectChoices.formattedStem.text)).toBeInTheDocument(); + threeChoices.forEach((choice) => { + expect(screen.getByText(choice.formattedText.text)).toBeInTheDocument(); + }); + }); + + test('allows multiple choices to be selected when question has multiple correct answers', () => { + render(); const choiceButton1 = screen.getByText('Choice 1').closest('button'); const choiceButton2 = screen.getByText('Choice 2').closest('button'); + const choiceButton3 = screen.getByText('Choice 3').closest('button'); + + if (!choiceButton1 || !choiceButton2 || !choiceButton3) throw new Error('Choice buttons not found'); + + act(() => { + fireEvent.click(choiceButton1); + }); + act(() => { + fireEvent.click(choiceButton2); + }); + + expect(choiceButton1.querySelector('.answer-text.selected')).toBeInTheDocument(); + expect(choiceButton2.querySelector('.answer-text.selected')).toBeInTheDocument(); + expect(choiceButton3.querySelector('.answer-text.selected')).not.toBeInTheDocument(); // didn't click + }); + + test('submits multiple selected answers', () => { + render(); + const choiceButton1 = screen.getByText('Choice 1').closest('button'); + const choiceButton2 = screen.getByText('Choice 2').closest('button'); + if (!choiceButton1 || !choiceButton2) throw new Error('Choice buttons not found'); - + // Simulate selecting multiple answers act(() => { fireEvent.click(choiceButton1); @@ -88,19 +150,20 @@ describe('MultipleChoiceQuestionDisplay', () => { act(() => { fireEvent.click(choiceButton2); }); - + // Simulate submitting the answers const submitButton = screen.getByText('Répondre'); act(() => { fireEvent.click(submitButton); }); - + // Verify that the mockHandleOnSubmitAnswer function is called with both answers expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith(['Choice 1', 'Choice 2']); mockHandleOnSubmitAnswer.mockClear(); }); - + it('should show ✅ next to the correct answer and ❌ next to the wrong answers when showAnswer is true', async () => { + render(); const choiceButton = screen.getByText('Choice 1').closest('button'); if (!choiceButton) throw new Error('Choice button not found'); @@ -116,16 +179,17 @@ describe('MultipleChoiceQuestionDisplay', () => { }); // Wait for the DOM to update - const correctAnswer = screen.getByText("Choice 1").closest('button'); - expect(correctAnswer).toBeInTheDocument(); - expect(correctAnswer?.textContent).toContain('✅'); + const correctAnswer = screen.getByText("Choice 1").closest('button'); + expect(correctAnswer).toBeInTheDocument(); + expect(correctAnswer?.textContent).toContain('✅'); - const wrongAnswer1 = screen.getByText("Choice 2").closest('button'); - expect(wrongAnswer1).toBeInTheDocument(); - expect(wrongAnswer1?.textContent).toContain('❌'); + const wrongAnswer1 = screen.getByText("Choice 2").closest('button'); + expect(wrongAnswer1).toBeInTheDocument(); + expect(wrongAnswer1?.textContent).toContain('❌'); }); - it('should not show ✅ or ❌ when repondre button is not clicked', async () => { + it('should not show ✅ or ❌ when Répondre button is not clicked', async () => { + render(); const choiceButton = screen.getByText('Choice 1').closest('button'); if (!choiceButton) throw new Error('Choice button not found'); @@ -145,5 +209,5 @@ describe('MultipleChoiceQuestionDisplay', () => { expect(wrongAnswer1?.textContent).not.toContain('❌'); }); - }); +}); diff --git a/client/src/__tests__/pages/ManageRoom/IsCorrect.test.tsx b/client/src/__tests__/pages/ManageRoom/IsCorrect.test.tsx index a150c55..80748f3 100644 --- a/client/src/__tests__/pages/ManageRoom/IsCorrect.test.tsx +++ b/client/src/__tests__/pages/ManageRoom/IsCorrect.test.tsx @@ -30,6 +30,12 @@ describe('checkIfIsCorrect', () => { expect(result).toBe(false); }); + test('returns false when all correct answers are selected, but one incorrect is also selected', () => { + const answer: AnswerType = ['Answer1', 'Answer2', 'Answer3']; + const result = checkIfIsCorrect(answer, 1, mockQuestions); + expect(result).toBe(false); + }); + test('returns false when no answers are selected', () => { const answer: AnswerType = []; const result = checkIfIsCorrect(answer, 1, mockQuestions); diff --git a/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx b/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx index ee507b3..11fe682 100644 --- a/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx +++ b/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx @@ -120,27 +120,29 @@ describe('StudentModeQuiz', () => { expect(screen.getByText('Répondre')).toBeInTheDocument(); }); - test('allows multiple answers to be selected for a question', async () => { - // Simulate selecting multiple answers - act(() => { - fireEvent.click(screen.getByText('Option A')); - }); - act(() => { - fireEvent.click(screen.getByText('Option B')); - }); + // le test suivant est fait dans MultipleChoiceQuestionDisplay.test.tsx +// test('allows multiple answers to be selected for a question', async () => { +// // Simulate selecting multiple answers +// act(() => { +// fireEvent.click(screen.getByText('Option A')); +// }); +// act(() => { +// fireEvent.click(screen.getByText('Option B')); +// }); - // Simulate submitting the answers - act(() => { - fireEvent.click(screen.getByText('Répondre')); - }); +// // Simulate submitting the answers +// act(() => { +// fireEvent.click(screen.getByText('Répondre')); +// }); - // Verify that the mockSubmitAnswer function is called with both answers - expect(mockSubmitAnswer).toHaveBeenCalledWith(['Option A', 'Option B'], 1); +// // Verify that the mockSubmitAnswer function is called with both answers +// expect(mockSubmitAnswer).toHaveBeenCalledWith(['Option A', 'Option B'], 1); + +// // Verify that the selected answers are displayed as selected +// const buttonA = screen.getByRole('button', { name: '✅ A Option A' }); +// const buttonB = screen.getByRole('button', { name: '✅ B Option B' }); +// expect(buttonA).toBeInTheDocument(); +// expect(buttonB).toBeInTheDocument(); +// }); - // Verify that the selected answers are displayed as selected - const buttonA = screen.getByRole('button', { name: '✅ A Option A' }); - const buttonB = screen.getByRole('button', { name: '✅ B Option B' }); - expect(buttonA).toBeInTheDocument(); - expect(buttonB).toBeInTheDocument(); - }); }); diff --git a/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts b/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts index 2bc423d..110465b 100644 --- a/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts +++ b/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts @@ -17,7 +17,7 @@ interface AnswerWeightOptions extends TemplateOptions { export default function MultipleChoiceAnswersTemplate({ choices }: MultipleChoiceAnswerOptions) { const id = `id${nanoid(8)}`; - const isMultipleAnswer = choices.filter(({ isCorrect }) => isCorrect === true).length === 0; + const isMultipleAnswer = choices.filter(({ isCorrect }) => isCorrect === true).length != 0; const prompt = `Choisir une réponse${ isMultipleAnswer ? ` ou plusieurs` : `` @@ -32,7 +32,7 @@ export default function MultipleChoiceAnswersTemplate({ choices }: MultipleChoic const inputId = `id${nanoid(6)}`; const isPositiveWeight = (weight != undefined) && (weight > 0); - const isCorrectOption = isMultipleAnswer ? isPositiveWeight : isCorrect; + const isCorrectOption = isMultipleAnswer ? isPositiveWeight || isCorrect : isCorrect; return `
diff --git a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx index 829752d..d023a40 100644 --- a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx @@ -15,7 +15,14 @@ interface Props { const MultipleChoiceQuestionDisplay: React.FC = (props) => { const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = props; - const [answer, setAnswer] = useState(passedAnswer || []); + console.log('MultipleChoiceQuestionDisplay: passedAnswer', JSON.stringify(passedAnswer)); + + const [answer, setAnswer] = useState(() => { + if (passedAnswer && passedAnswer.length > 0) { + return passedAnswer; + } + return []; + }); let disableButton = false; if (handleOnSubmitAnswer === undefined) { @@ -23,19 +30,31 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { } useEffect(() => { + console.log('MultipleChoiceQuestionDisplay: passedAnswer', JSON.stringify(passedAnswer)); if (passedAnswer !== undefined) { setAnswer(passedAnswer); + } else { + setAnswer([]); } - }, [passedAnswer]); + }, [passedAnswer, question.id]); const handleOnClickAnswer = (choice: string) => { setAnswer((prevAnswer) => { - if (prevAnswer.includes(choice)) { - // Remove the choice if it's already selected - return prevAnswer.filter((selected) => selected !== choice); + console.log(`handleOnClickAnswer -- setAnswer(): prevAnswer: ${prevAnswer}, choice: ${choice}`); + const correctAnswersCount = question.choices.filter((c) => c.isCorrect).length; + + if (correctAnswersCount === 1) { + // If only one correct answer, replace the current selection + return prevAnswer.includes(choice) ? [] : [choice]; } else { - // Add the choice if it's not already selected - return [...prevAnswer, choice]; + // Allow multiple selections if there are multiple correct answers + if (prevAnswer.includes(choice)) { + // Remove the choice if it's already selected + return prevAnswer.filter((selected) => selected !== choice); + } else { + // Add the choice if it's not already selected + return [...prevAnswer, choice]; + } } }); }; @@ -50,6 +69,7 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => {
{question.choices.map((choice, i) => { + console.log(`answer: ${answer}, choice: ${choice.formattedText.text}`); const selected = answer.includes(choice.formattedText.text) ? 'selected' : ''; return (
diff --git a/client/src/pages/Teacher/ManageRoom/useRooms.ts b/client/src/pages/Teacher/ManageRoom/useRooms.ts index 6be117c..2dadbfb 100644 --- a/client/src/pages/Teacher/ManageRoom/useRooms.ts +++ b/client/src/pages/Teacher/ManageRoom/useRooms.ts @@ -55,14 +55,15 @@ export function checkIfIsCorrect( (!question.isTrue && simpleAnswerText == 'false') ); } else if (question.type === 'MC') { - const correctAnswers = question.choices.filter((choice) => choice.isCorrect + const correctChoices = question.choices.filter((choice) => choice.isCorrect /* || (choice.weight && choice.weight > 0)*/ // handle weighted answers ); const multipleAnswers = Array.isArray(answer) ? answer : [answer as string]; - if (correctAnswers.length === 0) { + if (correctChoices.length === 0) { return false; } - return correctAnswers.every( + // check if all (and only) correct choices are in the multipleAnswers array + return correctChoices.length === multipleAnswers.length && correctChoices.every( (choice) => multipleAnswers.includes(choice.formattedText.text) ); } else if (question.type === 'Numerical') {