diff --git a/README.fr-ca.md b/README.fr-ca.md new file mode 100644 index 0000000..0710768 --- /dev/null +++ b/README.fr-ca.md @@ -0,0 +1,30 @@ +[![CI/CD Pipeline for Frontend](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/frontend-deploy.yml/badge.svg)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/frontend-deploy.yml) +[![CI/CD Pipeline for Backend](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/backend-deploy.yml/badge.svg)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/backend-deploy.yml) +[![CI/CD Pipeline for Nginx Router](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/deploy.yml/badge.svg)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/deploy.yml) + + +[![en](https://img.shields.io/badge/lang-en-red.svg)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/blob/master/README.md) + +# EvalueTonSavoir + +EvalueTonSavoir est une plateforme open source et auto-hébergée qui poursuit le développement du code provenant de https://github.com/ETS-PFE004-Plateforme-sondage-minitest. Cette plateforme minimaliste est conçue comme un outil d'apprentissage et d'enseignement, offrant une solution simple et efficace pour la création de quiz utilisant le format GIFT, similaire à Moodle. + +## Fonctionnalités clés + +* Open Source et Auto-hébergé : Possédez et contrôlez vos données en déployant la plateforme sur votre propre infrastructure. +* Compatibilité GIFT : Créez des quiz facilement en utilisant le format GIFT, permettant une intégration transparente avec d'autres systèmes d'apprentissage. +* Minimaliste et Efficace : Une approche bare bones pour garantir la simplicité et la facilité d'utilisation, mettant l'accent sur l'essentiel de l'apprentissage. + +## Contribution + +Actuellement, il n'y a pas de modèle établi pour les contributions. Si vous constatez quelque chose de manquant ou si vous pensez qu'une amélioration est possible, n'hésitez pas à ouvrir un issue et/ou une PR) + +## Liens utiles + +* [Dépôt d'origine Frontend](https://github.com/ETS-PFE004-Plateforme-sondage-minitest/ETS-PFE004-EvalueTonSavoir-Frontend) +* [Dépôt d'origine Backend](https://github.com/ETS-PFE004-Plateforme-sondage-minitest/ETS-PFE004-EvalueTonSavoir-Backend) +* [Documentation (Wiki)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/wiki) + +## License + +EvalueTonSavoir is open-sourced and licensed under the [MIT License](/LICENSE). diff --git a/README.md b/README.md index b935ea0..bb4f7dc 100644 --- a/README.md +++ b/README.md @@ -2,24 +2,26 @@ [![CI/CD Pipeline for Backend](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/backend-deploy.yml/badge.svg)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/backend-deploy.yml) [![CI/CD Pipeline for Nginx Router](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/deploy.yml/badge.svg)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/actions/workflows/deploy.yml) -# EvalueTonSavoir +[![fr-ca](https://img.shields.io/badge/lang-fr--ca-green.svg)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/blob/main/README.fr-ca.md) -EvalueTonSavoir est une plateforme open source et auto-hébergée qui poursuit le développement du code provenant de https://github.com/ETS-PFE004-Plateforme-sondage-minitest. Cette plateforme minimaliste est conçue comme un outil d'apprentissage et d'enseignement, offrant une solution simple et efficace pour la création de quiz utilisant le format GIFT, similaire à Moodle. +# EvalueTonSavoir -## Fonctionnalités clés +EvalueTonSavoir is an open-source and self-hosted platform that continues the development of the code from https://github.com/ETS-PFE004-Plateforme-sondage-minitest. This minimalist platform is designed as a learning and teaching tool, offering a simple and effective solution for creating quizzes using the GIFT format, similar to Moodle. -* Open Source et Auto-hébergé : Possédez et contrôlez vos données en déployant la plateforme sur votre propre infrastructure. -* Compatibilité GIFT : Créez des quiz facilement en utilisant le format GIFT, permettant une intégration transparente avec d'autres systèmes d'apprentissage. -* Minimaliste et Efficace : Une approche bare bones pour garantir la simplicité et la facilité d'utilisation, mettant l'accent sur l'essentiel de l'apprentissage. +## Key Features + +* **Open Source and Self-Hosted**: Own and control your data by deploying the platform on your own infrastructure. +* **GIFT Compatibility**: Easily create quizzes using the GIFT format, enabling seamless integration with other learning systems. +* **Minimalist and Efficient**: A bare-bones approach to ensure simplicity and ease of use, focusing on the essentials of learning. ## Contribution -Actuellement, il n'y a pas de modèle établi pour les contributions. Si vous constatez quelque chose de manquant ou si vous pensez qu'une amélioration est possible, n'hésitez pas à ouvrir un issue et/ou une PR) +Currently, there is no established model for contributions. If you notice something missing or think an improvement is possible, feel free to open an issue and/or a PR. -## Liens utiles +## Useful Links -* [Dépôt d'origine Frontend](https://github.com/ETS-PFE004-Plateforme-sondage-minitest/ETS-PFE004-EvalueTonSavoir-Frontend) -* [Dépôt d'origine Backend](https://github.com/ETS-PFE004-Plateforme-sondage-minitest/ETS-PFE004-EvalueTonSavoir-Backend) +* [Original Frontend Repository](https://github.com/ETS-PFE004-Plateforme-sondage-minitest/ETS-PFE004-EvalueTonSavoir-Frontend) +* [Original Backend Repository](https://github.com/ETS-PFE004-Plateforme-sondage-minitest/ETS-PFE004-EvalueTonSavoir-Backend) * [Documentation (Wiki)](https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir/wiki) ## License diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx index 3431b73..b9b6b9f 100644 --- a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx @@ -19,8 +19,8 @@ const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => }); const mockStudents: StudentType[] = [ - { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] }, - { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] }, + { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: ['Answer 1'], isCorrect: true }] }, + { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: ['Answer 2'], isCorrect: false }] }, ]; const mockShowSelectedQuestion = jest.fn(); @@ -92,4 +92,4 @@ describe('LiveResults', () => { expect(mockShowSelectedQuestion).toHaveBeenCalled(); }); -}); \ No newline at end of file +}); diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/LiveResultsTable.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/LiveResultsTable.test.tsx index d6f41d6..fe26173 100644 --- a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/LiveResultsTable.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/LiveResultsTable.test.tsx @@ -20,8 +20,8 @@ const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => const mockStudents: StudentType[] = [ - { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] }, - { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] }, + { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: ['Answer 1'], isCorrect: true }] }, + { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: ['Answer 2'], isCorrect: false }] }, ]; const mockShowSelectedQuestion = jest.fn(); diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.test.tsx index 9d4fb5c..ce10e1b 100644 --- a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.test.tsx @@ -20,8 +20,8 @@ const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => }); const mockStudents: StudentType[] = [ - { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] }, - { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] }, + { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: ['Answer 1'], isCorrect: true }] }, + { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: ['Answer 2'], isCorrect: false }] }, ]; const mockGetStudentGrade = jest.fn((student: StudentType) => { diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableFooter.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableFooter.test.tsx index 99a6dc3..dd7b54a 100644 --- a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableFooter.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableFooter.test.tsx @@ -6,8 +6,8 @@ import LiveResultsTableFooter from 'src/components/LiveResults/LiveResultsTable/ const mockStudents: StudentType[] = [ - { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] }, - { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] }, + { id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: ['Answer 1'], isCorrect: true }] }, + { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: ['Answer 2'], isCorrect: false }] }, ]; const mockGetStudentGrade = jest.fn((student: StudentType) => { @@ -52,4 +52,4 @@ describe('LiveResultsTableFooter', () => { expect(screen.getByText('50 %')).toBeInTheDocument(); }); -}); \ No newline at end of file +}); diff --git a/client/src/__tests__/components/GiftTemplate/templates/MultipleChoice.test.tsx b/client/src/__tests__/components/GiftTemplate/templates/MultipleChoice.test.tsx index bdecb06..2770267 100644 --- a/client/src/__tests__/components/GiftTemplate/templates/MultipleChoice.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/templates/MultipleChoice.test.tsx @@ -29,7 +29,7 @@ const katekMock: TemplateOptions & MultipleChoiceQuestion = { formattedStem: { format: 'plain' , text: '$$\\frac{zzz}{yyy}$$'}, choices: [ { formattedText: { format: 'plain' , text: 'Choice 1'}, isCorrect: true, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }, - { formattedText: { format: 'plain', text: 'Choice 2' }, isCorrect: true, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 1 } + { formattedText: { format: 'plain', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 0 } ], formattedGlobalFeedback: { format: 'plain', text: 'Sample Global Feedback' } }; diff --git a/client/src/__tests__/components/GiftTemplate/templates/__snapshots__/MultipleChoice.test.tsx.snap b/client/src/__tests__/components/GiftTemplate/templates/__snapshots__/MultipleChoice.test.tsx.snap index 0a17bf6..d32efd4 100644 --- a/client/src/__tests__/components/GiftTemplate/templates/__snapshots__/MultipleChoice.test.tsx.snap +++ b/client/src/__tests__/components/GiftTemplate/templates/__snapshots__/MultipleChoice.test.tsx.snap @@ -733,7 +733,7 @@ exports[`MultipleChoice snapshot test with katex 1`] = ` <div class='multiple-choice-answers-container'> <input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id"> - <span class="answer-weight-container answer-positive-weight">1%</span> + <label style=" display: inline-block; padding: 0.2em 0 0.2em 0; @@ -742,15 +742,15 @@ exports[`MultipleChoice snapshot test with katex 1`] = ` " for="idmocked-id"> Choice 2 </label> - <svg data-testid="correct-icon" style=" + <svg data-testid="incorrect-icon" style=" vertical-align: text-bottom; display: inline-block; margin-left: 0.1rem; margin-right: 0.2rem; - width: 1em; - color: hsl(120, 39%, 54%); - " role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg> + width: 0.75em; + color: hsl(2, 64%, 58%); + " role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg> <span class="feedback-container">Correct!</span> </input> </div> diff --git a/client/src/__tests__/components/LiveResults/LiveResults.test.tsx b/client/src/__tests__/components/LiveResults/LiveResults.test.tsx index ce6244e..269d83b 100644 --- a/client/src/__tests__/components/LiveResults/LiveResults.test.tsx +++ b/client/src/__tests__/components/LiveResults/LiveResults.test.tsx @@ -5,7 +5,7 @@ import LiveResults from 'src/components/LiveResults/LiveResults'; import { QuestionType } from 'src/Types/QuestionType'; import { StudentType } from 'src/Types/StudentType'; import { Socket } from 'socket.io-client'; -import { BaseQuestion,parse } from 'gift-pegjs'; +import { BaseQuestion, parse } from 'gift-pegjs'; const mockSocket: Socket = { on: jest.fn(), @@ -19,19 +19,28 @@ const mockGiftQuestions = parse( `::Sample Question 1:: Question stem { =Choice 1 - ~Choice 2 - }`); + =Choice 2 + ~Choice 3 + ~Choice 4 + } + + ::Sample Question 2:: Question stem {TRUE} + `); const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => { if (question.type !== "Category") question.id = (index + 1).toString(); const newMockQuestion = question; - return {question : newMockQuestion as BaseQuestion}; + return { question: newMockQuestion as BaseQuestion }; }); +console.log(`mockQuestions: ${JSON.stringify(mockQuestions)}`); + +// each student should have a different score for the tests to pass const mockStudents: StudentType[] = [ - { id: '1', name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Choice 1', isCorrect: true }] }, - { id: '2', name: 'Student 2', answers: [{ idQuestion: 1, answer: 'Choice 2', isCorrect: false }] }, + { id: '1', name: 'Student 1', answers: [] }, + { id: '2', name: 'Student 2', answers: [{ idQuestion: 1, answer: ['Choice 3'], isCorrect: false }, { idQuestion: 2, answer: [true], isCorrect: true}] }, + { id: '3', name: 'Student 3', answers: [{ idQuestion: 1, answer: ['Choice 1', 'Choice 2'], isCorrect: true }, { idQuestion: 2, answer: [true], isCorrect: true}] }, ]; describe('LiveResults', () => { @@ -52,7 +61,7 @@ describe('LiveResults', () => { // Toggle the display of usernames back fireEvent.click(toggleUsernamesSwitch); - + // Check if the component renders the students mockStudents.forEach((student) => { expect(screen.getByText(student.name)).toBeInTheDocument(); @@ -82,82 +91,88 @@ describe('LiveResults', () => { }); }); -}); -test('calculates and displays the correct student grades', () => { - render( - - ); + test('calculates and displays the correct student grades', () => { + render( + + ); - // Toggle the display of usernames - const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms'); + // Toggle the display of usernames + const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms'); - // Toggle the display of usernames back - fireEvent.click(toggleUsernamesSwitch); - - // Check if the student grades are calculated and displayed correctly - mockStudents.forEach((student) => { - const grade = student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100; - expect(screen.getByText(`${grade.toFixed()} %`)).toBeInTheDocument(); + // Toggle the display of usernames back + fireEvent.click(toggleUsernamesSwitch); + + // Check if the student grades are calculated and displayed correctly + const getByTextInTableCellBody = (text: string) => { + const elements = screen.getAllByText(text); // Get all elements with the specified text + return elements.find((element) => element.closest('.MuiTableCell-body')); // don't get the footer element(s) + }; + mockStudents.forEach((student) => { + const grade = student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100; + const element = getByTextInTableCellBody(`${grade.toFixed()} %`); + expect(element).toBeInTheDocument(); + }); }); -}); -test('calculates and displays the class average', () => { - render( - - ); + test('calculates and displays the class average', () => { + render( + + ); - // Toggle the display of usernames - const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms'); + // Toggle the display of usernames + const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms'); - // Toggle the display of usernames back - fireEvent.click(toggleUsernamesSwitch); - - // Calculate the class average - const totalGrades = mockStudents.reduce((total, student) => { - return total + (student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100); - }, 0); - const classAverage = totalGrades / mockStudents.length; + // Toggle the display of usernames back + fireEvent.click(toggleUsernamesSwitch); - // Check if the class average is displayed correctly - const classAverageElements = screen.getAllByText(`${classAverage.toFixed()} %`); - const classAverageElement = classAverageElements.find((element) => { - return element.closest('td')?.classList.contains('MuiTableCell-footer'); - }); - expect(classAverageElement).toBeInTheDocument(); -}); + // Calculate the class average + const totalGrades = mockStudents.reduce((total, student) => { + return total + (student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100); + }, 0); + const classAverage = totalGrades / mockStudents.length; -test('displays the correct answers per question', () => { - render( - - ); - - // Check if the correct answers per question are displayed correctly - mockQuestions.forEach((_, index) => { - const correctAnswers = mockStudents.filter(student => student.answers.some(answer => answer.idQuestion === index + 1 && answer.isCorrect)).length; - const correctAnswersPercentage = (correctAnswers / mockStudents.length) * 100; - const correctAnswersElements = screen.getAllByText(`${correctAnswersPercentage.toFixed()} %`); - const correctAnswersElement = correctAnswersElements.find((element) => { - return element.closest('td')?.classList.contains('MuiTableCell-root'); + // Check if the class average is displayed correctly + const classAverageElements = screen.getAllByText(`${classAverage.toFixed()} %`); + const classAverageElement = classAverageElements.find((element) => { + return element.closest('td')?.classList.contains('MuiTableCell-footer'); }); - expect(correctAnswersElement).toBeInTheDocument(); + expect(classAverageElement).toBeInTheDocument(); }); -}); \ No newline at end of file + + test('displays the correct answers per question', () => { + render( + + ); + + // Check if the correct answers per question are displayed correctly + mockQuestions.forEach((_, index) => { + const correctAnswers = mockStudents.filter(student => student.answers.some(answer => answer.idQuestion === index + 1 && answer.isCorrect)).length; + const correctAnswersPercentage = (correctAnswers / mockStudents.length) * 100; + const correctAnswersElements = screen.getAllByText(`${correctAnswersPercentage.toFixed()} %`); + const correctAnswersElement = correctAnswersElements.find((element) => { + return element.closest('td')?.classList.contains('MuiTableCell-root'); + }); + expect(correctAnswersElement).toBeInTheDocument(); + }); + }); + +}); diff --git a/client/src/__tests__/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx b/client/src/__tests__/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.test.tsx index 8751e8b..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,28 +47,51 @@ 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); }); expect(mockHandleOnSubmitAnswer).not.toHaveBeenCalled(); + mockHandleOnSubmitAnswer.mockClear(); }); test('submits the selected answer', () => { + render(); const choiceButton = screen.getByText('Choice 1').closest('button'); if (!choiceButton) throw new Error('Choice button not found'); act(() => { @@ -70,10 +102,68 @@ describe('MultipleChoiceQuestionDisplay', () => { fireEvent.click(submitButton); }); - expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith('Choice 1'); + expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith(['Choice 1']); + mockHandleOnSubmitAnswer.mockClear(); + }); + + + 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); + }); + 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'); @@ -89,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'); @@ -118,5 +209,5 @@ describe('MultipleChoiceQuestionDisplay', () => { expect(wrongAnswer1?.textContent).not.toContain('❌'); }); - }); +}); diff --git a/client/src/__tests__/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx b/client/src/__tests__/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx index 639537a..5c32547 100644 --- a/client/src/__tests__/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.test.tsx @@ -67,6 +67,7 @@ describe('NumericalQuestion Component', () => { fireEvent.click(submitButton); expect(mockHandleOnSubmitAnswer).not.toHaveBeenCalled(); + mockHandleOnSubmitAnswer.mockClear(); }); it('submits answer correctly', () => { @@ -77,6 +78,7 @@ describe('NumericalQuestion Component', () => { fireEvent.click(submitButton); - expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith(7); + expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith([7]); + mockHandleOnSubmitAnswer.mockClear(); }); }); diff --git a/client/src/__tests__/components/QuestionsDisplay/Question.test.tsx b/client/src/__tests__/components/QuestionsDisplay/Question.test.tsx index 8c7546f..142e563 100644 --- a/client/src/__tests__/components/QuestionsDisplay/Question.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/Question.test.tsx @@ -29,23 +29,24 @@ describe('Questions Component', () => { render(); }; - describe('question type parsing', () => { - it('parses true/false question type correctly', () => { - expect(sampleTrueFalseQuestion.type).toBe('TF'); - }); + // describe('question type parsing', () => { + // it('parses true/false question type correctly', () => { + // expect(sampleTrueFalseQuestion.type).toBe('TF'); + // }); - it('parses multiple choice question type correctly', () => { - expect(sampleMultipleChoiceQuestion.type).toBe('MC'); - }); + // it('parses multiple choice question type correctly', () => { + // expect(sampleMultipleChoiceQuestion.type).toBe('MC'); + // }); - it('parses numerical question type correctly', () => { - expect(sampleNumericalQuestion.type).toBe('Numerical'); - }); + // it('parses numerical question type correctly', () => { + // expect(sampleNumericalQuestion.type).toBe('Numerical'); + // }); + + // it('parses short answer question type correctly', () => { + // expect(sampleShortAnswerQuestion.type).toBe('Short'); + // }); + // }); - it('parses short answer question type correctly', () => { - expect(sampleShortAnswerQuestion.type).toBe('Short'); - }); - }); it('renders correctly for True/False question', () => { renderComponent(sampleTrueFalseQuestion); @@ -73,7 +74,8 @@ describe('Questions Component', () => { const submitButton = screen.getByText('Répondre'); fireEvent.click(submitButton); - expect(mockHandleSubmitAnswer).toHaveBeenCalledWith('Choice 1'); + expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(['Choice 1']); + mockHandleSubmitAnswer.mockClear(); }); it('renders correctly for Numerical question', () => { @@ -93,7 +95,8 @@ describe('Questions Component', () => { const submitButton = screen.getByText('Répondre'); fireEvent.click(submitButton); - expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(7); + expect(mockHandleSubmitAnswer).toHaveBeenCalledWith([7]); + mockHandleSubmitAnswer.mockClear(); }); it('renders correctly for Short Answer question', () => { @@ -117,7 +120,7 @@ describe('Questions Component', () => { const submitButton = screen.getByText('Répondre'); fireEvent.click(submitButton); - expect(mockHandleSubmitAnswer).toHaveBeenCalledWith('User Input'); + expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(['User Input']); }); }); diff --git a/client/src/__tests__/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.test.tsx b/client/src/__tests__/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.test.tsx index c4326eb..57e9da5 100644 --- a/client/src/__tests__/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.test.tsx @@ -47,6 +47,7 @@ describe('ShortAnswerQuestion Component', () => { fireEvent.click(submitButton); expect(mockHandleSubmitAnswer).not.toHaveBeenCalled(); + mockHandleSubmitAnswer.mockClear(); }); it('submits answer correctly', () => { @@ -60,6 +61,7 @@ describe('ShortAnswerQuestion Component', () => { fireEvent.click(submitButton); - expect(mockHandleSubmitAnswer).toHaveBeenCalledWith('User Input'); + expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(['User Input']); + mockHandleSubmitAnswer.mockClear(); }); }); diff --git a/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx b/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx index e6910d4..f79d1da 100644 --- a/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx @@ -56,6 +56,7 @@ describe('TrueFalseQuestion Component', () => { }); expect(mockHandleSubmitAnswer).not.toHaveBeenCalled(); + mockHandleSubmitAnswer.mockClear(); }); it('submits answer correctly for True', () => { @@ -70,7 +71,8 @@ describe('TrueFalseQuestion Component', () => { fireEvent.click(submitButton); }); - expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(true); + expect(mockHandleSubmitAnswer).toHaveBeenCalledWith([true]); + mockHandleSubmitAnswer.mockClear(); }); it('submits answer correctly for False', () => { @@ -83,7 +85,8 @@ describe('TrueFalseQuestion Component', () => { fireEvent.click(submitButton); }); - expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(false); + expect(mockHandleSubmitAnswer).toHaveBeenCalledWith([false]); + mockHandleSubmitAnswer.mockClear(); }); @@ -112,7 +115,7 @@ describe('TrueFalseQuestion Component', () => { 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 () => { const choiceButton = screen.getByText('Vrai').closest('button'); if (!choiceButton) throw new Error('Choice button not found'); diff --git a/client/src/__tests__/pages/ManageRoom/IsCorrect.test.tsx b/client/src/__tests__/pages/ManageRoom/IsCorrect.test.tsx new file mode 100644 index 0000000..80748f3 --- /dev/null +++ b/client/src/__tests__/pages/ManageRoom/IsCorrect.test.tsx @@ -0,0 +1,359 @@ +import { checkIfIsCorrect } from 'src/pages/Teacher/ManageRoom/useRooms'; +import { HighLowNumericalAnswer, MultipleChoiceQuestion, MultipleNumericalAnswer, NumericalQuestion, RangeNumericalAnswer, ShortAnswerQuestion, SimpleNumericalAnswer, TrueFalseQuestion } from 'gift-pegjs'; +import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; +import { QuestionType } from 'src/Types/QuestionType'; + +describe('checkIfIsCorrect', () => { + const mockQuestions: QuestionType[] = [ + { + question: { + id: '1', + type: 'MC', + choices: [ + { isCorrect: true, formattedText: { text: 'Answer1' } }, + { isCorrect: true, formattedText: { text: 'Answer2' } }, + { isCorrect: false, formattedText: { text: 'Answer3' } }, + ], + } as MultipleChoiceQuestion, + }, + ]; + + test('returns true when all selected answers are correct', () => { + const answer: AnswerType = ['Answer1', 'Answer2']; + const result = checkIfIsCorrect(answer, 1, mockQuestions); + expect(result).toBe(true); + }); + + test('returns false when some selected answers are incorrect', () => { + const answer: AnswerType = ['Answer1', 'Answer3']; + const result = checkIfIsCorrect(answer, 1, mockQuestions); + 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); + expect(result).toBe(false); + }); + + test('returns false when no correct answers are provided in the question', () => { + const mockQuestionsWithNoCorrectAnswers: QuestionType[] = [ + { + question: { + id: '1', + type: 'MC', + choices: [ + { isCorrect: false, formattedText: { text: 'Answer1' } }, + { isCorrect: false, formattedText: { text: 'Answer2' } }, + ], + } as MultipleChoiceQuestion, + }, + ]; + const answer: AnswerType = ['Answer1']; + const result = checkIfIsCorrect(answer, 1, mockQuestionsWithNoCorrectAnswers); + expect(result).toBe(false); + }); + + test('returns true for a correct true/false answer', () => { + const mockQuestionsTF: QuestionType[] = [ + { + question: { + id: '2', + type: 'TF', + isTrue: true, + } as TrueFalseQuestion, + }, + ]; + const answer: AnswerType = [true]; + const result = checkIfIsCorrect(answer, 2, mockQuestionsTF); + expect(result).toBe(true); + }); + + test('returns false for an incorrect true/false answer', () => { + const mockQuestionsTF: QuestionType[] = [ + { + question: { + id: '2', + type: 'TF', + isTrue: true, + } as TrueFalseQuestion, + }, + ]; + const answer: AnswerType = [false]; + const result = checkIfIsCorrect(answer, 2, mockQuestionsTF); + expect(result).toBe(false); + }); + + test('returns false for a true/false question with no answer', () => { + const mockQuestionsTF: QuestionType[] = [ + { + question: { + id: '2', + type: 'TF', + isTrue: true, + } as TrueFalseQuestion, + }, + ]; + const answer: AnswerType = []; + const result = checkIfIsCorrect(answer, 2, mockQuestionsTF); + expect(result).toBe(false); + }); + + test('returns true for a correct true/false answer when isTrue is false', () => { + const mockQuestionsTF: QuestionType[] = [ + { + question: { + id: '3', + type: 'TF', + isTrue: false, // Correct answer is false + } as TrueFalseQuestion, + }, + ]; + const answer: AnswerType = [false]; + const result = checkIfIsCorrect(answer, 3, mockQuestionsTF); + expect(result).toBe(true); + }); + + test('returns false for an incorrect true/false answer when isTrue is false', () => { + const mockQuestionsTF: QuestionType[] = [ + { + question: { + id: '3', + type: 'TF', + isTrue: false, // Correct answer is false + } as TrueFalseQuestion, + }, + ]; + const answer: AnswerType = [true]; + const result = checkIfIsCorrect(answer, 3, mockQuestionsTF); + expect(result).toBe(false); + }); + + test('returns true for a correct short answer', () => { + const mockQuestionsShort: QuestionType[] = [ + { + question: { + id: '4', + type: 'Short', + choices: [ + { text: 'CorrectAnswer1' }, + { text: 'CorrectAnswer2' }, + ], + } as ShortAnswerQuestion, + }, + ]; + const answer: AnswerType = ['CorrectAnswer1']; + const result = checkIfIsCorrect(answer, 4, mockQuestionsShort); + expect(result).toBe(true); + }); + + test('returns false for an incorrect short answer', () => { + const mockQuestionsShort: QuestionType[] = [ + { + question: { + id: '4', + type: 'Short', + choices: [ + { text: 'CorrectAnswer1' }, + { text: 'CorrectAnswer2' }, + ], + } as ShortAnswerQuestion, + }, + ]; + const answer: AnswerType = ['WrongAnswer']; + const result = checkIfIsCorrect(answer, 4, mockQuestionsShort); + expect(result).toBe(false); + }); + + test('returns true for a correct short answer with case insensitivity', () => { + const mockQuestionsShort: QuestionType[] = [ + { + question: { + id: '4', + type: 'Short', + choices: [ + { text: 'CorrectAnswer1' }, + { text: 'CorrectAnswer2' }, + ], + } as ShortAnswerQuestion, + }, + ]; + const answer: AnswerType = ['correctanswer1']; // Lowercase version of the correct answer + const result = checkIfIsCorrect(answer, 4, mockQuestionsShort); + expect(result).toBe(true); + }); + + test('returns false for a short answer question with no answer', () => { + const mockQuestionsShort: QuestionType[] = [ + { + question: { + id: '4', + type: 'Short', + choices: [ + { text: 'CorrectAnswer1' }, + { text: 'CorrectAnswer2' }, + ], + } as ShortAnswerQuestion, + }, + ]; + const answer: AnswerType = []; + const result = checkIfIsCorrect(answer, 4, mockQuestionsShort); + expect(result).toBe(false); + }); + + + test('returns true for a correct simple numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '5', + type: 'Numerical', + choices: [ + { type: 'simple', number: 42 } as SimpleNumericalAnswer, + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [42]; // User's answer + const result = checkIfIsCorrect(answer, 5, mockQuestionsNumerical); + expect(result).toBe(true); + }); + + test('returns false for an incorrect simple numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '5', + type: 'Numerical', + choices: [ + { type: 'simple', number: 42 } as SimpleNumericalAnswer, + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [43]; // User's answer + const result = checkIfIsCorrect(answer, 5, mockQuestionsNumerical); + expect(result).toBe(false); + }); + + test('returns true for a correct range numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '6', + type: 'Numerical', + choices: [ + { type: 'range', number: 50, range: 5 } as RangeNumericalAnswer, + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [52]; // User's answer within the range (50 ± 5) + const result = checkIfIsCorrect(answer, 6, mockQuestionsNumerical); + expect(result).toBe(true); + }); + + test('returns false for an out-of-range numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '6', + type: 'Numerical', + choices: [ + { type: 'range', number: 50, range: 5 } as RangeNumericalAnswer, + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [56]; // User's answer outside the range (50 ± 5) + const result = checkIfIsCorrect(answer, 6, mockQuestionsNumerical); + expect(result).toBe(false); + }); + + test('returns true for a correct high-low numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '7', + type: 'Numerical', + choices: [ + { type: 'high-low', numberHigh: 100, numberLow: 90 } as HighLowNumericalAnswer, + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [95]; // User's answer within the range (90 to 100) + const result = checkIfIsCorrect(answer, 7, mockQuestionsNumerical); + expect(result).toBe(true); + }); + + test('returns false for an out-of-range high-low numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '7', + type: 'Numerical', + choices: [ + { type: 'high-low', numberHigh: 100, numberLow: 90 } as HighLowNumericalAnswer, + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [105]; // User's answer outside the range (90 to 100) + const result = checkIfIsCorrect(answer, 7, mockQuestionsNumerical); + expect(result).toBe(false); + }); + + test('returns true for a correct multiple numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '8', + type: 'Numerical', + choices: [ + { + isCorrect: true, + answer: { type: 'simple', number: 42 } as SimpleNumericalAnswer, + } as MultipleNumericalAnswer, + { + isCorrect: false, + answer: { type: 'high-low', numberHigh: 100, numberLow: 90 } as HighLowNumericalAnswer, + formattedFeedback: { text: 'You guessed way too high' }, + } + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [42]; // User's answer matches the correct multiple numerical answer + const result = checkIfIsCorrect(answer, 8, mockQuestionsNumerical); + expect(result).toBe(true); + }); + + test('returns false for an incorrect multiple numerical answer', () => { + const mockQuestionsNumerical: QuestionType[] = [ + { + question: { + id: '8', + type: 'Numerical', + choices: [ + { + type: 'multiple', + isCorrect: true, + answer: { type: 'simple', number: 42 } as SimpleNumericalAnswer, + } as MultipleNumericalAnswer, + ], + } as NumericalQuestion, + }, + ]; + const answer: AnswerType = [43]; // User's answer does not match the correct multiple numerical answer + const result = checkIfIsCorrect(answer, 8, mockQuestionsNumerical); + expect(result).toBe(false); + }); + +}); diff --git a/client/src/__tests__/pages/ManageRoom/ManageRoom.test.tsx b/client/src/__tests__/pages/ManageRoom/ManageRoom.test.tsx index fdad0ef..1a708a8 100644 --- a/client/src/__tests__/pages/ManageRoom/ManageRoom.test.tsx +++ b/client/src/__tests__/pages/ManageRoom/ManageRoom.test.tsx @@ -49,7 +49,7 @@ const mockStudents: StudentType[] = [ ]; const mockAnswerData: AnswerReceptionFromBackendType = { - answer: 'Answer1', + answer: ['Answer1'], idQuestion: 1, idUser: '1', username: 'Student 1', @@ -238,7 +238,7 @@ describe('ManageRoom', () => { await act(async () => { const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1]; - createSuccessCallback('test-room-name'); + createSuccessCallback('Test Room'); }); const launchButton = screen.getByText('Lancer'); @@ -261,6 +261,7 @@ describe('ManageRoom', () => { }); await waitFor(() => { + // console.info(consoleSpy.mock.calls); expect(consoleSpy).toHaveBeenCalledWith( 'Received answer from Student 1 for question 1: Answer1' ); @@ -347,3 +348,4 @@ describe('ManageRoom', () => { expect(screen.getByTestId('qr-code')).toHaveTextContent(roomUrl); }); }); + diff --git a/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx b/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx index 1218694..11fe682 100644 --- a/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx +++ b/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx @@ -8,7 +8,7 @@ import { QuestionType } from 'src/Types/QuestionType'; import { AnswerSubmissionToBackendType } from 'src/services/WebsocketService'; const mockGiftQuestions = parse( - `::Sample Question 1:: Sample Question 1 {=Option A ~Option B} + `::Sample Question 1:: Sample Question 1 {=Option A =Option B ~Option C} ::Sample Question 2:: Sample Question 2 {T}`); @@ -23,9 +23,6 @@ const mockSubmitAnswer = jest.fn(); const mockDisconnectWebSocket = jest.fn(); beforeEach(() => { - // Clear local storage before each test - // localStorage.clear(); - render( { fireEvent.click(screen.getByText('Répondre')); }); - expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', 1); + expect(mockSubmitAnswer).toHaveBeenCalledWith(['Option A'], 1); }); test('handles shows feedback for an already answered question', async () => { @@ -65,13 +62,13 @@ describe('StudentModeQuiz', () => { act(() => { fireEvent.click(screen.getByText('Répondre')); }); - expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', 1); + expect(mockSubmitAnswer).toHaveBeenCalledWith(['Option A'], 1); const firstButtonA = screen.getByRole("button", {name: '✅ A Option A'}); expect(firstButtonA).toBeInTheDocument(); expect(firstButtonA.querySelector('.selected')).toBeInTheDocument(); - expect(screen.getByRole("button", {name: '❌ B Option B'})).toBeInTheDocument(); + expect(screen.getByRole("button", {name: '✅ B Option B'})).toBeInTheDocument(); expect(screen.queryByText('Répondre')).not.toBeInTheDocument(); // Navigate to the next question @@ -87,12 +84,12 @@ describe('StudentModeQuiz', () => { }); expect(await screen.findByText('Sample Question 1')).toBeInTheDocument(); - // Since answers are mocked, the it doesn't recognize the question as already answered + // Since answers are mocked, it doesn't recognize the question as already answered // TODO these tests are partially faked, need to be fixed if we can mock the answers // const buttonA = screen.getByRole("button", {name: '✅ A Option A'}); const buttonA = screen.getByRole("button", {name: 'A Option A'}); expect(buttonA).toBeInTheDocument(); - // const buttonB = screen.getByRole("button", {name: '❌ B Option B'}); + // const buttonB = screen.getByRole("button", {name: '✅ B Option B'}); const buttonB = screen.getByRole("button", {name: 'B Option B'}); expect(buttonB).toBeInTheDocument(); // // "Option A" div inside the name of button should have selected class @@ -122,4 +119,30 @@ describe('StudentModeQuiz', () => { expect(screen.getByText('Sample Question 2')).toBeInTheDocument(); expect(screen.getByText('Répondre')).toBeInTheDocument(); }); + + // 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')); +// }); + +// // 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(); +// }); + }); diff --git a/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx b/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx index 6a4ec59..4cd5a8d 100644 --- a/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx +++ b/client/src/__tests__/pages/Student/TeacherModeQuiz/TeacherModeQuiz.test.tsx @@ -63,7 +63,8 @@ describe('TeacherModeQuiz', () => { act(() => { fireEvent.click(screen.getByText('Répondre')); }); - expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', 1); + expect(mockSubmitAnswer).toHaveBeenCalledWith(['Option A'], 1); + mockSubmitAnswer.mockClear(); }); test('handles shows feedback for an already answered question', () => { @@ -74,7 +75,8 @@ describe('TeacherModeQuiz', () => { act(() => { fireEvent.click(screen.getByText('Répondre')); }); - expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', 1); + expect(mockSubmitAnswer).toHaveBeenCalledWith(['Option A'], 1); + mockSubmitAnswer.mockClear(); mockQuestion = mockQuestions[1].question as MultipleChoiceQuestion; // Navigate to the next question by re-rendering with new props act(() => { diff --git a/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts b/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts index 2bc423d..76c151d 100644 --- a/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts +++ b/client/src/components/GiftTemplate/templates/MultipleChoiceAnswersTemplate.ts @@ -13,14 +13,14 @@ type AnswerFeedbackOptions = TemplateOptions & Pick isCorrect === true).length === 0; + const hasManyCorrectChoices = choices.filter(({ isCorrect }) => isCorrect === true).length > 1; const prompt = `Choisir une réponse${ - isMultipleAnswer ? ` ou plusieurs` : `` + hasManyCorrectChoices ? ` ou plusieurs` : `` }:`; const result = choices .map(({ weight, isCorrect, formattedText, formattedFeedback }) => { @@ -32,12 +32,12 @@ export default function MultipleChoiceAnswersTemplate({ choices }: MultipleChoic const inputId = `id${nanoid(6)}`; const isPositiveWeight = (weight != undefined) && (weight > 0); - const isCorrectOption = isMultipleAnswer ? isPositiveWeight : isCorrect; + const isCorrectOption = hasManyCorrectChoices ? isPositiveWeight || isCorrect : isCorrect; return `
${AnswerWeight({ weight: weight })}