From 9c9c17cd0f82c73145c85e3af839b507180a3529 Mon Sep 17 00:00:00 2001 From: JubaAzul <118773284+JubaAzul@users.noreply.github.com> Date: Wed, 12 Feb 2025 14:01:36 -0500 Subject: [PATCH] Refactoriser la LiveResultTable en fonction de ses composants Fixes #236 --- .../LiveResultsTable.test.tsx | 2 +- .../LiveResultsTableBody.test.tsx | 95 ++++++++ .../LiveResultsTableFooter.test.tsx | 55 +++++ .../LiveResultsTableHeader.test.tsx | 51 +++++ .../components/LiveResults/LiveResults.tsx | 20 +- .../LiveResults/LiveResultsTable.tsx | 215 ------------------ .../LiveResultsTable/LiveResultsTable.tsx | 75 ++++++ .../TableComponents/LiveResultTableFooter.tsx | 79 +++++++ .../TableComponents/LiveResultsTableBody.tsx | 94 ++++++++ .../LiveResultsTableHeader.tsx | 50 ++++ 10 files changed, 509 insertions(+), 227 deletions(-) rename client/src/__tests__/components/GiftTemplate/LiveResults/{ => LiveResultsTable}/LiveResultsTable.test.tsx (99%) create mode 100644 client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.test.tsx create mode 100644 client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableFooter.test.tsx create mode 100644 client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.test.tsx delete mode 100644 client/src/components/LiveResults/LiveResultsTable.tsx create mode 100644 client/src/components/LiveResults/LiveResultsTable/LiveResultsTable.tsx create mode 100644 client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultTableFooter.tsx create mode 100644 client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.tsx create mode 100644 client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.tsx diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/LiveResultsTable.test.tsx similarity index 99% rename from client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable.test.tsx rename to client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/LiveResultsTable.test.tsx index 7f4c8a0..021b82d 100644 --- a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/LiveResultsTable.test.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { render, screen, fireEvent } from '@testing-library/react'; import '@testing-library/jest-dom'; import { StudentType } from 'src/Types/StudentType'; -import LiveResultsTable from 'src/components/LiveResults/LiveResultsTable'; +import LiveResultsTable from 'src/components/LiveResults/LiveResultsTable/LiveResultsTable'; import { QuestionType } from 'src/Types/QuestionType'; import { BaseQuestion, parse } from 'gift-pegjs'; 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 new file mode 100644 index 0000000..11e41f1 --- /dev/null +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.test.tsx @@ -0,0 +1,95 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { StudentType } from 'src/Types/StudentType'; +import LiveResultsTableBody from 'src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody'; +import { QuestionType } from 'src/Types/QuestionType'; +import { BaseQuestion, parse } from 'gift-pegjs'; + + +const mockGiftQuestions = parse( + `::Sample Question 1:: Sample Question 1 {=Answer 1 ~Answer 2} + + ::Sample Question 2:: Sample Question 2 {T}`); + +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: 'Answer 1', isCorrect: true }] }, + { id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] }, +]; + +const mockGetStudentGrade = jest.fn((student: StudentType) => { + const correctAnswers = student.answers.filter(answer => answer.isCorrect).length; + return (correctAnswers / mockQuestions.length) * 100; +}); + +describe('LiveResultsTableBody', () => { + test('renders LiveResultsTableBody component', () => { + render( + + ); + + expect(screen.getByText('Student 1')).toBeInTheDocument(); + expect(screen.getByText('Student 2')).toBeInTheDocument(); + }); + + test('displays correct and incorrect answers', () => { + render( + + ); + + expect(screen.getByText('Answer 1')).toBeInTheDocument(); + expect(screen.getByText('Answer 2')).toBeInTheDocument(); + }); + + test('displays icons for correct and incorrect answers when showCorrectAnswers is false', () => { + render( + + ); + + expect(screen.getByLabelText('correct')).toBeInTheDocument(); + expect(screen.getByLabelText('incorrect')).toBeInTheDocument(); + }); + + test('hides usernames when showUsernames is false', () => { + render( + + ); + + expect(screen.getAllByText('******').length).toBe(2); + }); +}); \ No newline at end of file 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 new file mode 100644 index 0000000..99a6dc3 --- /dev/null +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableFooter.test.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import { StudentType } from 'src/Types/StudentType'; +import LiveResultsTableFooter from 'src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultTableFooter'; + + +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 }] }, +]; + +const mockGetStudentGrade = jest.fn((student: StudentType) => { + const correctAnswers = student.answers.filter(answer => answer.isCorrect).length; + return (correctAnswers / 2) * 100; // Assuming there are 2 questions +}); + +describe('LiveResultsTableFooter', () => { + test('renders LiveResultsTableFooter component', () => { + render( + + ); + + expect(screen.getByText('% réussite')).toBeInTheDocument(); + }); + + test('calculates and displays correct answers per question', () => { + render( + + ); + + expect(screen.getByText('50 %')).toBeInTheDocument(); + expect(screen.getByText('0 %')).toBeInTheDocument(); + }); + + test('calculates and displays class average', () => { + render( + + ); + + expect(screen.getByText('50 %')).toBeInTheDocument(); + }); +}); \ No newline at end of file diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.test.tsx new file mode 100644 index 0000000..5dff41a --- /dev/null +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.test.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { render, screen, fireEvent } from '@testing-library/react'; +import '@testing-library/jest-dom'; +import LiveResultsTableHeader from 'src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader'; + + +const mockShowSelectedQuestion = jest.fn(); + +describe('LiveResultsTableHeader', () => { + test('renders LiveResultsTableHeader component', () => { + render( + + ); + + expect(screen.getByText("Nom d'utilisateur")).toBeInTheDocument(); + for (let i = 1; i <= 5; i++) { + expect(screen.getByText(`Q${i}`)).toBeInTheDocument(); + } + expect(screen.getByText('% réussite')).toBeInTheDocument(); + }); + + test('calls showSelectedQuestion when a question header is clicked', () => { + render( + + ); + + const questionHeader = screen.getByText('Q1'); + fireEvent.click(questionHeader); + + expect(mockShowSelectedQuestion).toHaveBeenCalledWith(0); + }); + + test('renders the correct number of question headers', () => { + render( + + ); + + for (let i = 1; i <= 3; i++) { + expect(screen.getByText(`Q${i}`)).toBeInTheDocument(); + } + }); +}); \ No newline at end of file diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index 13611eb..44c998f 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -2,7 +2,6 @@ import React, { useState } from 'react'; import { Socket } from 'socket.io-client'; import { QuestionType } from '../../Types/QuestionType'; - import './liveResult.css'; import { FormControlLabel, @@ -10,7 +9,7 @@ import { Switch, } from '@mui/material'; import { StudentType } from '../../Types/StudentType'; -import LiveResultsTable from './LiveResultsTable'; +import LiveResultsTable from './LiveResultsTable/LiveResultsTable'; interface LiveResultsProps { @@ -21,12 +20,11 @@ interface LiveResultsProps { students: StudentType[] } - const LiveResults: React.FC = ({ questions, showSelectedQuestion, students }) => { const [showUsernames, setShowUsernames] = useState(false); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); - + return (
@@ -58,13 +56,13 @@ const LiveResults: React.FC = ({ questions, showSelectedQuesti
- +
); diff --git a/client/src/components/LiveResults/LiveResultsTable.tsx b/client/src/components/LiveResults/LiveResultsTable.tsx deleted file mode 100644 index bb81b80..0000000 --- a/client/src/components/LiveResults/LiveResultsTable.tsx +++ /dev/null @@ -1,215 +0,0 @@ -import React, { useMemo } from 'react'; -import { Paper, Table, TableBody, TableCell, TableContainer, TableFooter, TableHead, TableRow } from '@mui/material'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons'; -import { StudentType } from 'src/Types/StudentType'; -import { QuestionType } from '../../Types/QuestionType'; -import { FormattedTextTemplate } from '../GiftTemplate/templates/TextTypeTemplate'; - -interface LiveResultsTableProps { - students: StudentType[]; - questions: QuestionType[]; - showCorrectAnswers: boolean; - showSelectedQuestion: (index: number) => void; - showUsernames: boolean; -} - -const LiveResultsTable: React.FC = ({ - questions, - students, - showCorrectAnswers, - showSelectedQuestion, - showUsernames -}) => { - - const maxQuestions = questions.length; - - const getStudentGrade = (student: StudentType): number => { - if (student.answers.length === 0) { - return 0; - } - - const uniqueQuestions = new Set(); - let correctAnswers = 0; - - for (const answer of student.answers) { - const { idQuestion, isCorrect } = answer; - - if (!uniqueQuestions.has(idQuestion)) { - uniqueQuestions.add(idQuestion); - - if (isCorrect) { - correctAnswers++; - } - } - } - - return (correctAnswers / questions.length) * 100; - }; - - const classAverage: number = useMemo(() => { - let classTotal = 0; - - students.forEach((student) => { - classTotal += getStudentGrade(student); - }); - - return classTotal / students.length; - }, [students]); - - const getCorrectAnswersPerQuestion = (index: number): number => { - return ( - (students.filter((student) => - student.answers.some( - (answer) => - parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect - ) - ).length / students.length) * 100 - ); - }; - - return ( - - - - - -
Nom d'utilisateur
-
- {Array.from({ length: maxQuestions }, (_, index) => ( - showSelectedQuestion(index)} - > -
{`Q${index + 1}`}
-
- ))} - -
% réussite
-
-
-
- - {students.map((student) => ( - - -
- {showUsernames ? student.name : '******'} -
-
- {Array.from({ length: maxQuestions }, (_, index) => { - const answer = student.answers.find( - (answer) => parseInt(answer.idQuestion.toString()) === index + 1 - ); - const answerText = answer ? answer.answer.toString() : ''; - const isCorrect = answer ? answer.isCorrect : false; - - return ( - - {showCorrectAnswers ? ( -
- ) : isCorrect ? ( - - ) : ( - answerText !== '' && ( - - ) - )} -
- ); - })} - - {getStudentGrade(student).toFixed()} % - -
- ))} -
- - - -
% réussite
-
- {Array.from({ length: maxQuestions }, (_, index) => ( - - {students.length > 0 - ? `${getCorrectAnswersPerQuestion(index).toFixed()} %` - : '-'} - - ))} - - {students.length > 0 ? `${classAverage.toFixed()} %` : '-'} - -
-
-
-
- ); -}; - -export default LiveResultsTable; \ No newline at end of file diff --git a/client/src/components/LiveResults/LiveResultsTable/LiveResultsTable.tsx b/client/src/components/LiveResults/LiveResultsTable/LiveResultsTable.tsx new file mode 100644 index 0000000..e23c4a6 --- /dev/null +++ b/client/src/components/LiveResults/LiveResultsTable/LiveResultsTable.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { Paper, Table, TableContainer } from '@mui/material'; +import { StudentType } from 'src/Types/StudentType'; +import { QuestionType } from '../../../Types/QuestionType'; +import LiveResultsTableFooter from './TableComponents/LiveResultTableFooter'; +import LiveResultsTableHeader from './TableComponents/LiveResultsTableHeader'; +import LiveResultsTableBody from './TableComponents/LiveResultsTableBody'; + +interface LiveResultsTableProps { + students: StudentType[]; + questions: QuestionType[]; + showCorrectAnswers: boolean; + showSelectedQuestion: (index: number) => void; + showUsernames: boolean; +} + +const LiveResultsTable: React.FC = ({ + questions, + students, + showSelectedQuestion, + showUsernames, + showCorrectAnswers +}) => { + + const maxQuestions = questions.length; + + const getStudentGrade = (student: StudentType): number => { + if (student.answers.length === 0) { + return 0; + } + + const uniqueQuestions = new Set(); + let correctAnswers = 0; + + for (const answer of student.answers) { + const { idQuestion, isCorrect } = answer; + + if (!uniqueQuestions.has(idQuestion)) { + uniqueQuestions.add(idQuestion); + + if (isCorrect) { + correctAnswers++; + } + } + } + + return (correctAnswers / questions.length) * 100; + }; + + + return ( + + + + + +
+
+ ); +}; + +export default LiveResultsTable; \ No newline at end of file diff --git a/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultTableFooter.tsx b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultTableFooter.tsx new file mode 100644 index 0000000..a24694e --- /dev/null +++ b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultTableFooter.tsx @@ -0,0 +1,79 @@ +import { TableCell, TableFooter, TableRow } from "@mui/material"; +import React, { useMemo } from "react"; +import { StudentType } from "src/Types/StudentType"; + +interface LiveResultsFooterProps { + students: StudentType[]; + maxQuestions: number; + getStudentGrade: (student: StudentType) => number; +} + +const LiveResultsTableFooter: React.FC = ({ + maxQuestions, + students, + getStudentGrade + +}) => { + + const getCorrectAnswersPerQuestion = (index: number): number => { + return ( + (students.filter((student) => + student.answers.some( + (answer) => + parseInt(answer.idQuestion.toString()) === index + 1 && answer.isCorrect + ) + ).length / students.length) * 100 + ); + }; + + const classAverage: number = useMemo(() => { + let classTotal = 0; + + students.forEach((student) => { + classTotal += getStudentGrade(student); + }); + + return classTotal / students.length; + }, [students]); + + return ( + + + +
% réussite
+
+ {Array.from({ length: maxQuestions }, (_, index) => ( + + {students.length > 0 + ? `${getCorrectAnswersPerQuestion(index).toFixed()} %` + : '-'} + + ))} + + {students.length > 0 ? `${classAverage.toFixed()} %` : '-'} + +
+
+ ); +}; +export default LiveResultsTableFooter; \ No newline at end of file diff --git a/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.tsx b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.tsx new file mode 100644 index 0000000..a0c67f7 --- /dev/null +++ b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableBody.tsx @@ -0,0 +1,94 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { TableBody, TableCell, TableRow } from "@mui/material"; +import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons'; +import { FormattedTextTemplate } from '../../../GiftTemplate/templates/TextTypeTemplate'; +import React from "react"; +import { StudentType } from "src/Types/StudentType"; + +interface LiveResultsFooterProps { + maxQuestions: number; + students: StudentType[]; + showUsernames: boolean; + showCorrectAnswers: boolean; + getStudentGrade: (student: StudentType) => number; + +} + +const LiveResultsTableFooter: React.FC = ({ + maxQuestions, + students, + showUsernames, + showCorrectAnswers, + getStudentGrade +}) => { + + return ( + + {students.map((student) => ( + + +
+ {showUsernames ? student.name : '******'} +
+
+ {Array.from({ length: maxQuestions }, (_, index) => { + const answer = student.answers.find( + (answer) => parseInt(answer.idQuestion.toString()) === index + 1 + ); + const answerText = answer ? answer.answer.toString() : ''; + const isCorrect = answer ? answer.isCorrect : false; + + return ( + + {showCorrectAnswers ? ( +
+ ) : isCorrect ? ( + + ) : ( + answerText !== '' && ( + + ) + )} +
+ ); + })} + + {getStudentGrade(student).toFixed()} % + +
+ ))} +
+ ); +}; +export default LiveResultsTableFooter; \ No newline at end of file diff --git a/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.tsx b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.tsx new file mode 100644 index 0000000..fb4219b --- /dev/null +++ b/client/src/components/LiveResults/LiveResultsTable/TableComponents/LiveResultsTableHeader.tsx @@ -0,0 +1,50 @@ +import { TableCell, TableHead, TableRow } from "@mui/material"; +import React from "react"; + +interface LiveResultsFooterProps { + maxQuestions: number; + showSelectedQuestion: (index: number) => void; +} + +const LiveResultsTableFooter: React.FC = ({ + maxQuestions, + showSelectedQuestion, +}) => { + + return ( + + + +
Nom d'utilisateur
+
+ {Array.from({ length: maxQuestions }, (_, index) => ( + showSelectedQuestion(index)} + > +
{`Q${index + 1}`}
+
+ ))} + +
% réussite
+
+
+
+ ); +}; +export default LiveResultsTableFooter; \ No newline at end of file