diff --git a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx index 3431b73..881d510 100644 --- a/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/LiveResults/LiveResults.test.tsx @@ -3,6 +3,7 @@ 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 { Socket } from 'socket.io-client'; import { StudentType } from 'src/Types/StudentType'; import { BaseQuestion, parse } from 'gift-pegjs'; @@ -11,11 +12,19 @@ const mockGiftQuestions = parse( ::Sample Question 2:: Sample Question 2 {T}`); +const mockSocket: Socket = { + on: jest.fn(), + off: jest.fn(), + emit: jest.fn(), + connect: jest.fn(), + disconnect: jest.fn(), +} as unknown as Socket; + 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 }; }); const mockStudents: StudentType[] = [ @@ -26,70 +35,222 @@ const mockStudents: StudentType[] = [ const mockShowSelectedQuestion = jest.fn(); describe('LiveResults', () => { - test('renders LiveResults component', () => { + 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( + ); - expect(screen.getByText('Résultats du quiz')).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 usernames are shown again + mockStudents.forEach((student) => { + expect(screen.getByText(student.name)).toBeInTheDocument(); + }); }); - test('toggles show usernames switch', () => { - render( - - ); +}); +test('calculates and displays the correct student grades', () => { + render( + + ); - const switchElement = screen.getByLabelText('Afficher les noms'); - expect(switchElement).toBeInTheDocument(); + // Toggle the display of usernames + const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms'); - fireEvent.click(switchElement); - expect(switchElement).toBeChecked(); + // 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; + const gradeElements = screen.getAllByText(`${grade.toFixed()} %`); + expect(gradeElements.length).toBeGreaterThan(0);}); +}); + +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('toggles show correct answers switch', () => { - render( - - ); +test('displays the correct answers per question', () => { + render( + + ); - const switchElement = screen.getByLabelText('Afficher les réponses'); - expect(switchElement).toBeInTheDocument(); - - fireEvent.click(switchElement); - expect(switchElement).toBeChecked(); + // 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(); }); +}); +test('renders LiveResults component', () => { + render( + + ); - test('calls showSelectedQuestion when a table cell is clicked', () => { - render( - - ); + expect(screen.getByText('Résultats du quiz')).toBeInTheDocument(); +}); - const tableCell = screen.getByText('Q1'); - fireEvent.click(tableCell); +test('toggles show usernames switch', () => { + render( + + ); - expect(mockShowSelectedQuestion).toHaveBeenCalled(); - }); -}); \ No newline at end of file + const switchElement = screen.getByLabelText('Afficher les noms'); + expect(switchElement).toBeInTheDocument(); + + fireEvent.click(switchElement); + expect(switchElement).toBeChecked(); +}); + +test('toggles show correct answers switch', () => { + render( + + ); + + const switchElement = screen.getByLabelText('Afficher les réponses'); + expect(switchElement).toBeInTheDocument(); + + fireEvent.click(switchElement); + expect(switchElement).toBeChecked(); +}); + +test('calls showSelectedQuestion when a table cell is clicked', () => { + render( + + ); + + const tableCell = screen.getByText('Q1'); + fireEvent.click(tableCell); + + expect(mockShowSelectedQuestion).toHaveBeenCalled(); +}); + +test('toggles the visibility of content when the arrow button is clicked', () => { + render(); + const toggleSwitch = screen.getByTestId("liveResults-switch"); + expect(toggleSwitch).toBeInTheDocument(); + expect(screen.queryByText('Afficher les noms')).toBeInTheDocument(); + expect(screen.queryByText('Afficher les réponses')).toBeInTheDocument(); + expect(screen.queryByTestId('table-container')).toBeInTheDocument(); + + + fireEvent.click(toggleSwitch); + screen.debug(); + + expect(screen.queryByText('Afficher les noms')).not.toBeInTheDocument(); + expect(screen.queryByText('Afficher les réponses')).not.toBeInTheDocument(); + expect(screen.queryByTestId('table-container')).not.toBeInTheDocument(); + + fireEvent.click(toggleSwitch); + expect(screen.queryByText('Afficher les noms')).toBeInTheDocument(); + expect(screen.queryByText('Afficher les réponses')).toBeInTheDocument(); + expect(screen.queryByTestId('table-container')).toBeInTheDocument(); +}); diff --git a/client/src/__tests__/components/LiveResults/LiveResults.test.tsx b/client/src/__tests__/components/LiveResults/LiveResults.test.tsx deleted file mode 100644 index ce6244e..0000000 --- a/client/src/__tests__/components/LiveResults/LiveResults.test.tsx +++ /dev/null @@ -1,163 +0,0 @@ -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 f165e10..05a86ec 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -24,49 +24,68 @@ interface LiveResultsProps { const LiveResults: React.FC = ({ questions, showSelectedQuestion, students }) => { const [showUsernames, setShowUsernames] = useState(false); const [showCorrectAnswers, setShowCorrectAnswers] = useState(false); + const [isExpanded, setIsExpanded] = useState(true); + + const toggleExpand = () => { + setIsExpanded(!isExpanded); + }; return ( - +
-
-
Résultats du quiz
- +
+
Afficher les noms
} + label={
Résultats du quiz
} control={ ) => - setShowUsernames(e.target.checked) - } + data-testid="liveResults-switch" + checked={isExpanded} + onChange={() => toggleExpand()} /> } /> - Afficher les réponses
} - control={ - ) => - setShowCorrectAnswers(e.target.checked) - } - /> - } +
+ {isExpanded && ( + + Afficher les noms
} + control={ + ) => + setShowUsernames(e.target.checked) + } + /> + } + /> + Afficher les réponses} + control={ + ) => + setShowCorrectAnswers(e.target.checked) + } + /> + } + /> + + )} + + {isExpanded && ( +
+ + - -
- -
- - -
+ + )} ); }; diff --git a/client/src/components/LiveResults/liveResult.css b/client/src/components/LiveResults/liveResult.css index c16ca48..42214bc 100644 --- a/client/src/components/LiveResults/liveResult.css +++ b/client/src/components/LiveResults/liveResult.css @@ -16,8 +16,8 @@ /* Flexbox container for the action bar */ .action-bar { display: flex; - flex-direction: column; - align-items: flex-start; + flex-direction: row; + margin-top: 2rem; } /* Flexbox container for the form group */ @@ -25,6 +25,7 @@ display: flex; flex-direction: column; align-items: flex-start; + margin-left: 1rem; } .table-cell-border { diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index 01d9c27..19b9f11 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -30,7 +30,7 @@ const ManageRoom: React.FC = () => { const navigate = useNavigate(); const [socket, setSocket] = useState(null); const [students, setStudents] = useState([]); - const { quizId = '', roomName = '' } = useParams<{ quizId: string, roomName: string }>(); + const { quizId = '', roomName = '' } = useParams<{ quizId: string, roomName: string }>(); const [quizQuestions, setQuizQuestions] = useState(); const [quiz, setQuiz] = useState(null); const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher'); @@ -46,13 +46,13 @@ const ManageRoom: React.FC = () => { if (newlyConnectedUser) { console.log(`Handling newly connected user: ${newlyConnectedUser.name}`); setStudents((prevStudents) => [...prevStudents, newlyConnectedUser]); - + // only send nextQuestion if the quiz has started if (!quizStarted) { console.log(`!quizStarted: returning.... `); return; } - + if (quizMode === 'teacher') { webSocketService.nextQuestion({ roomName: formattedRoomName, @@ -65,7 +65,7 @@ const ManageRoom: React.FC = () => { } else { console.error('Invalid quiz mode:', quizMode); } - + // Reset the newly connected user state setNewlyConnectedUser(null); } @@ -96,7 +96,7 @@ const ManageRoom: React.FC = () => { return () => { disconnectWebSocket(); }; - }, [roomName, navigate]); + }, [roomName, navigate]); useEffect(() => { if (quizId) { @@ -218,14 +218,14 @@ const ManageRoom: React.FC = () => { console.log(`Comparing ${ans.idQuestion} to ${idQuestion}`); return ans.idQuestion === idQuestion ? { - ...ans, - answer, - isCorrect: checkIfIsCorrect( - answer, - idQuestion, - quizQuestions! - ) - } + ...ans, + answer, + isCorrect: checkIfIsCorrect( + answer, + idQuestion, + quizQuestions! + ) + } : ans; }); } else { @@ -258,10 +258,12 @@ const ManageRoom: React.FC = () => { if (nextQuestionIndex === undefined || nextQuestionIndex > quizQuestions.length - 1) return; setCurrentQuestion(quizQuestions[nextQuestionIndex]); - webSocketService.nextQuestion({roomName: formattedRoomName, - questions: quizQuestions, - questionIndex: nextQuestionIndex, - isLaunch: false}); + webSocketService.nextQuestion({ + roomName: formattedRoomName, + questions: quizQuestions, + questionIndex: nextQuestionIndex, + isLaunch: false + }); }; const previousQuestion = () => { @@ -271,7 +273,7 @@ const ManageRoom: React.FC = () => { if (prevQuestionIndex === undefined || prevQuestionIndex < 0) return; setCurrentQuestion(quizQuestions[prevQuestionIndex]); - webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex: prevQuestionIndex, isLaunch: false}); + webSocketService.nextQuestion({ roomName: formattedRoomName, questions: quizQuestions, questionIndex: prevQuestionIndex, isLaunch: false }); }; const initializeQuizQuestion = () => { @@ -299,7 +301,7 @@ const ManageRoom: React.FC = () => { } setCurrentQuestion(quizQuestions[0]); - webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex: 0, isLaunch: true}); + webSocketService.nextQuestion({ roomName: formattedRoomName, questions: quizQuestions, questionIndex: 0, isLaunch: true }); }; const launchStudentMode = () => { @@ -336,7 +338,7 @@ const ManageRoom: React.FC = () => { if (quiz?.content && quizQuestions) { setCurrentQuestion(quizQuestions[questionIndex]); if (quizMode === 'teacher') { - webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex, isLaunch: false}); + webSocketService.nextQuestion({ roomName: formattedRoomName, questions: quizQuestions, questionIndex, isLaunch: false }); } } }; @@ -487,10 +489,34 @@ const ManageRoom: React.FC = () => { )} - + {quizMode === 'teacher' && ( +
+
+ +
+
+ +
+
+ )} { - {quizMode === 'teacher' && ( -
-
- -
-
- -
-
- )} + ) : (