diff --git a/client/src/__tests__/components/LiveResults/LiveResults.test.tsx b/client/src/__tests__/components/LiveResults/LiveResults.test.tsx new file mode 100644 index 0000000..ce6244e --- /dev/null +++ b/client/src/__tests__/components/LiveResults/LiveResults.test.tsx @@ -0,0 +1,163 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +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'; + +const mockSocket: Socket = { + on: jest.fn(), + off: jest.fn(), + emit: jest.fn(), + connect: jest.fn(), + disconnect: jest.fn(), +} as unknown as Socket; + +const mockGiftQuestions = parse( + `::Sample Question 1:: Question stem + { + =Choice 1 + ~Choice 2 + }`); + +const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => { + if (question.type !== "Category") + question.id = (index + 1).toString(); + const newMockQuestion = question; + return {question : newMockQuestion as BaseQuestion}; +}); + +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 }] }, +]; + +describe('LiveResults', () => { + test('renders the component with questions and students', () => { + render( + + ); + expect(screen.getByText(`Q${1}`)).toBeInTheDocument(); + + // Toggle the display of usernames + const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms'); + + // 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(); + }); + }); + + test('toggles the display of usernames', () => { + render( + + ); + + // Toggle the display of usernames + const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms'); + + // Toggle the display of usernames back + fireEvent.click(toggleUsernamesSwitch); + + // Check if the usernames are shown again + mockStudents.forEach((student) => { + expect(screen.getByText(student.name)).toBeInTheDocument(); + }); + }); + +}); +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 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(); + }); +}); + +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 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; + + // 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(); +}); + +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(); + }); +}); \ No newline at end of file diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index 6bcd7ce..d1e560a 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -20,7 +20,7 @@ import { TableRow } from '@mui/material'; import { StudentType } from '../../Types/StudentType'; -import { formatLatex } from '../GiftTemplate/templates/TextTypeTemplate'; +import { FormattedTextTemplate } from '../GiftTemplate/templates/TextTypeTemplate'; interface LiveResultsProps { socket: Socket | null; @@ -30,117 +30,13 @@ interface LiveResultsProps { students: StudentType[] } -// interface Answer { -// answer: string | number | boolean; -// isCorrect: boolean; -// idQuestion: number; -// } - -// interface StudentResult { -// username: string; -// idUser: string; -// answers: Answer[]; -// } const LiveResults: React.FC = ({ questions, showSelectedQuestion, students }) => { const [showUsernames, setShowUsernames] = useState(false); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); - // const [students, setStudents] = useState(initialStudents); - // const [studentResultsMap, setStudentResultsMap] = useState>(new Map()); const maxQuestions = questions.length; - // useEffect(() => { - // // Initialize the map with the current students - // const newStudentResultsMap = new Map(); - - // for (const student of students) { - // newStudentResultsMap.set(student.id, { username: student.name, idUser: student.id, answers: [] }); - // } - - // setStudentResultsMap(newStudentResultsMap); - // }, []) - - // update when students change - // useEffect(() => { - // // studentResultsMap is inconsistent with students -- need to update - - // for (const student of students as StudentType[]) { - // } - - // }, [students]) - - // useEffect(() => { - // if (socket) { - // const submitAnswerHandler = ({ - // idUser, - // answer, - // idQuestion - // }: { - // idUser: string; - // username: string; - // answer: string | number | boolean; - // idQuestion: number; - // }) => { - // console.log(`Received answer from ${idUser} for question ${idQuestion}: ${answer}`); - - // // print the list of current student names - // console.log('Current students:'); - // students.forEach((student) => { - // console.log(student.name); - // }); - - // // Update the students state using the functional form of setStudents - // setStudents((prevStudents) => { - // let foundStudent = false; - // const updatedStudents = prevStudents.map((student) => { - // if (student.id === idUser) { - // foundStudent = true; - // const updatedAnswers = student.answers.map((ans) => { - // const newAnswer: Answer = { answer, isCorrect: checkIfIsCorrect(answer, idQuestion), idQuestion }; - // console.log(`Updating answer for ${student.name} for question ${idQuestion} to ${answer}`); - // return (ans.idQuestion === idQuestion ? { ...ans, newAnswer } : ans); - // } - // ); - // return { ...student, answers: updatedAnswers }; - // } - // return student; - // }); - // if (!foundStudent) { - // console.log(`Student ${idUser} not found in the list of students in LiveResults`); - // } - // return updatedStudents; - // }); - - - // // make a copy of the students array so we can update it - // // const updatedStudents = [...students]; - - // // const student = updatedStudents.find((student) => student.id === idUser); - // // if (!student) { - // // // this is a bad thing if an answer was submitted but the student isn't in the list - // // console.log(`Student ${idUser} not found in the list of students in LiveResults`); - // // return; - // // } - - // // const isCorrect = checkIfIsCorrect(answer, idQuestion); - // // const newAnswer: Answer = { answer, isCorrect, idQuestion }; - // // student.answers.push(newAnswer); - // // // print list of answers - // // console.log('Answers:'); - // // student.answers.forEach((answer) => { - // // console.log(answer.answer); - // // }); - // // setStudents(updatedStudents); // update the state - // }; - - // socket.on('submit-answer', submitAnswerHandler); - // return () => { - // socket.off('submit-answer'); - // }; - // } - // }, [socket]); - const getStudentGrade = (student: StudentType): number => { if (student.answers.length === 0) { return 0; @@ -185,85 +81,6 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti ); }; - // (studentResults.filter((student) => - // student.answers.some( - // (answer) => - // parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect - // ) - // ).length / - // studentResults.length) * - // 100 - // ); - // }; - - // function checkIfIsCorrect(answer: string | number | boolean, idQuestion: number): boolean { - // const questionInfo = questions.find((q) => - // q.question.id ? q.question.id === idQuestion.toString() : false - // ) as QuestionType | undefined; - - // const answerText = answer.toString(); - // if (questionInfo) { - // const question = questionInfo.question as GIFTQuestion; - // if (question.type === 'TF') { - // return ( - // (question.isTrue && answerText == 'true') || - // (!question.isTrue && answerText == 'false') - // ); - // } else if (question.type === 'MC') { - // return question.choices.some( - // (choice) => choice.isCorrect && choice.text.text === answerText - // ); - // } else if (question.type === 'Numerical') { - // if (question.choices && !Array.isArray(question.choices)) { - // if ( - // question.choices.type === 'high-low' && - // question.choices.numberHigh && - // question.choices.numberLow - // ) { - // const answerNumber = parseFloat(answerText); - // if (!isNaN(answerNumber)) { - // return ( - // answerNumber <= question.choices.numberHigh && - // answerNumber >= question.choices.numberLow - // ); - // } - // } - // } - // if (question.choices && Array.isArray(question.choices)) { - // if ( - // question.choices[0].text.type === 'range' && - // question.choices[0].text.number && - // question.choices[0].text.range - // ) { - // const answerNumber = parseFloat(answerText); - // const range = question.choices[0].text.range; - // const correctAnswer = question.choices[0].text.number; - // if (!isNaN(answerNumber)) { - // return ( - // answerNumber <= correctAnswer + range && - // answerNumber >= correctAnswer - range - // ); - // } - // } - // if ( - // question.choices[0].text.type === 'simple' && - // question.choices[0].text.number - // ) { - // const answerNumber = parseFloat(answerText); - // if (!isNaN(answerNumber)) { - // return answerNumber === question.choices[0].text.number; - // } - // } - // } - // } else if (question.type === 'Short') { - // return question.choices.some( - // (choice) => choice.text.text.toUpperCase() === answerText.toUpperCase() - // ); - // } - // } - // return false; - // } - return (
@@ -370,7 +187,7 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti } > {showCorrectAnswers ? ( -
{formatLatex(answerText)}
+
) : isCorrect ? ( ) : (