[FEATURE] Être possible de cacher le tableau du LiveResult

Fixes #294
This commit is contained in:
JubaAzul 2025-03-19 19:05:18 -04:00
parent 4b4a995a4e
commit c13a38af70
4 changed files with 259 additions and 281 deletions

View file

@ -3,6 +3,7 @@ import { render, screen, fireEvent } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import LiveResults from 'src/components/LiveResults/LiveResults'; import LiveResults from 'src/components/LiveResults/LiveResults';
import { QuestionType } from 'src/Types/QuestionType'; import { QuestionType } from 'src/Types/QuestionType';
import { Socket } from 'socket.io-client';
import { StudentType } from 'src/Types/StudentType'; import { StudentType } from 'src/Types/StudentType';
import { BaseQuestion, parse } from 'gift-pegjs'; import { BaseQuestion, parse } from 'gift-pegjs';
@ -11,11 +12,19 @@ const mockGiftQuestions = parse(
::Sample Question 2:: Sample Question 2 {T}`); ::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) => { const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => {
if (question.type !== "Category") if (question.type !== "Category")
question.id = (index + 1).toString(); question.id = (index + 1).toString();
const newMockQuestion = question; const newMockQuestion = question;
return {question : newMockQuestion as BaseQuestion}; return { question: newMockQuestion as BaseQuestion };
}); });
const mockStudents: StudentType[] = [ const mockStudents: StudentType[] = [
@ -26,95 +35,225 @@ const mockStudents: StudentType[] = [
const mockShowSelectedQuestion = jest.fn(); const mockShowSelectedQuestion = jest.fn();
describe('LiveResults', () => { describe('LiveResults', () => {
test('renders LiveResults component', () => { test('renders the component with questions and students', () => {
render( render(
<LiveResults <LiveResults
socket={null} socket={mockSocket}
questions={mockQuestions} questions={mockQuestions}
showSelectedQuestion={mockShowSelectedQuestion} showSelectedQuestion={jest.fn()}
quizMode="teacher"
students={mockStudents}
/>
);
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(
<LiveResults
socket={mockSocket}
questions={mockQuestions}
showSelectedQuestion={jest.fn()}
quizMode="teacher" quizMode="teacher"
students={mockStudents} students={mockStudents}
/> />
); );
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', () => {
<LiveResults render(
socket={null} <LiveResults
questions={mockQuestions} socket={mockSocket}
showSelectedQuestion={mockShowSelectedQuestion} questions={mockQuestions}
quizMode="teacher" showSelectedQuestion={jest.fn()}
students={mockStudents} quizMode="teacher"
/> students={mockStudents}
); />
);
const switchElement = screen.getByLabelText('Afficher les noms'); // Toggle the display of usernames
expect(switchElement).toBeInTheDocument(); const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms');
fireEvent.click(switchElement); // Toggle the display of usernames back
expect(switchElement).toBeChecked(); 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(
<LiveResults
socket={mockSocket}
questions={mockQuestions}
showSelectedQuestion={jest.fn()}
quizMode="teacher"
students={mockStudents}
/>
);
// 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', () => { test('displays the correct answers per question', () => {
render( render(
<LiveResults <LiveResults
socket={null} socket={mockSocket}
questions={mockQuestions} questions={mockQuestions}
showSelectedQuestion={mockShowSelectedQuestion} showSelectedQuestion={jest.fn()}
quizMode="teacher" quizMode="teacher"
students={mockStudents} students={mockStudents}
/> />
); );
const switchElement = screen.getByLabelText('Afficher les réponses'); // Check if the correct answers per question are displayed correctly
expect(switchElement).toBeInTheDocument(); mockQuestions.forEach((_, index) => {
const correctAnswers = mockStudents.filter(student => student.answers.some(answer => answer.idQuestion === index + 1 && answer.isCorrect)).length;
fireEvent.click(switchElement); const correctAnswersPercentage = (correctAnswers / mockStudents.length) * 100;
expect(switchElement).toBeChecked(); const correctAnswersElements = screen.getAllByText(`${correctAnswersPercentage.toFixed()} %`);
}); const correctAnswersElement = correctAnswersElements.find((element) => {
return element.closest('td')?.classList.contains('MuiTableCell-root');
test('calls showSelectedQuestion when a table cell is clicked', () => { });
render( expect(correctAnswersElement).toBeInTheDocument();
<LiveResults
socket={null}
questions={mockQuestions}
showSelectedQuestion={mockShowSelectedQuestion}
quizMode="teacher"
students={mockStudents}
/>
);
const tableCell = screen.getByText('Q1');
fireEvent.click(tableCell);
expect(mockShowSelectedQuestion).toHaveBeenCalled();
});
test('toggles the visibility of content when the arrow button is clicked', () => {
render(<LiveResults
socket={null}
questions={mockQuestions}
showSelectedQuestion={mockShowSelectedQuestion}
quizMode="teacher"
students={mockStudents}
/>);
expect(screen.queryByText('Afficher les noms')).toBeInTheDocument();
expect(screen.queryByText('Afficher les réponses')).toBeInTheDocument();
expect(screen.queryByTestId('table-container')).toBeInTheDocument();
const toggleButton = screen.getByRole('button', { name: /toggle visibility/i });
fireEvent.click(toggleButton);
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(toggleButton);
expect(screen.queryByText('Afficher les noms')).toBeInTheDocument();
expect(screen.queryByText('Afficher les réponses')).toBeInTheDocument();
expect(screen.queryByTestId('table-container')).toBeInTheDocument();
}); });
}); });
test('renders LiveResults component', () => {
render(
<LiveResults
socket={null}
questions={mockQuestions}
showSelectedQuestion={mockShowSelectedQuestion}
quizMode="teacher"
students={mockStudents}
/>
);
expect(screen.getByText('Résultats du quiz')).toBeInTheDocument();
});
test('toggles show usernames switch', () => {
render(
<LiveResults
socket={null}
questions={mockQuestions}
showSelectedQuestion={mockShowSelectedQuestion}
quizMode="teacher"
students={mockStudents}
/>
);
const switchElement = screen.getByLabelText('Afficher les noms');
expect(switchElement).toBeInTheDocument();
fireEvent.click(switchElement);
expect(switchElement).toBeChecked();
});
test('toggles show correct answers switch', () => {
render(
<LiveResults
socket={null}
questions={mockQuestions}
showSelectedQuestion={mockShowSelectedQuestion}
quizMode="teacher"
students={mockStudents}
/>
);
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(
<LiveResults
socket={null}
questions={mockQuestions}
showSelectedQuestion={mockShowSelectedQuestion}
quizMode="teacher"
students={mockStudents}
/>
);
const tableCell = screen.getByText('Q1');
fireEvent.click(tableCell);
expect(mockShowSelectedQuestion).toHaveBeenCalled();
});
test.skip('toggles the visibility of content when the arrow button is clicked', () => {
render(<LiveResults
socket={null}
questions={mockQuestions}
showSelectedQuestion={mockShowSelectedQuestion}
quizMode="teacher"
students={mockStudents}
/>);
const toggleSwitch = screen.getByTestId("liveResults-visibility-switch");
fireEvent.click(toggleSwitch);
expect(toggleSwitch).toBeInTheDocument();
expect(toggleSwitch).toBeChecked();
expect(screen.queryByText('Afficher les noms')).toBeInTheDocument();
expect(screen.queryByText('Afficher les réponses')).toBeInTheDocument();
expect(screen.queryByTestId('table-container')).toBeInTheDocument();
fireEvent.click(toggleSwitch);
expect(toggleSwitch).not.toBeChecked();
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();
});

View file

@ -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(
<LiveResults
socket={mockSocket}
questions={mockQuestions}
showSelectedQuestion={jest.fn()}
quizMode="teacher"
students={mockStudents}
/>
);
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(
<LiveResults
socket={mockSocket}
questions={mockQuestions}
showSelectedQuestion={jest.fn()}
quizMode="teacher"
students={mockStudents}
/>
);
// 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(
<LiveResults
socket={mockSocket}
questions={mockQuestions}
showSelectedQuestion={jest.fn()}
quizMode="teacher"
students={mockStudents}
/>
);
// 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(
<LiveResults
socket={mockSocket}
questions={mockQuestions}
showSelectedQuestion={jest.fn()}
quizMode="teacher"
students={mockStudents}
/>
);
// 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(
<LiveResults
socket={mockSocket}
questions={mockQuestions}
showSelectedQuestion={jest.fn()}
quizMode="teacher"
students={mockStudents}
/>
);
// 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();
});
});

View file

@ -6,13 +6,11 @@ import './liveResult.css';
import { import {
FormControlLabel, FormControlLabel,
FormGroup, FormGroup,
IconButton,
Switch, Switch,
} from '@mui/material'; } from '@mui/material';
import { StudentType } from '../../Types/StudentType'; import { StudentType } from '../../Types/StudentType';
import LiveResultsTable from './LiveResultsTable/LiveResultsTable'; import LiveResultsTable from './LiveResultsTable/LiveResultsTable';
import { ExpandLess, ExpandMore } from '@mui/icons-material';
interface LiveResultsProps { interface LiveResultsProps {
socket: Socket | null; socket: Socket | null;
@ -36,51 +34,54 @@ const LiveResults: React.FC<LiveResultsProps> = ({ questions, showSelectedQuesti
<div> <div>
<div className="action-bar mb-1"> <div className="action-bar">
<div> <div>
<IconButton onClick={toggleExpand} aria-label="toggle visibility"> <Switch
{isExpanded ? <ExpandLess /> : <ExpandMore />} data-testid="liveResults-visibility-switch"
</IconButton> checked={isExpanded}
<span>Résultats du quiz</span> onChange={() => toggleExpand()}
</div> inputProps={{ 'aria-label': 'toggle visibility' }}
{isExpanded && (
<FormGroup row>
<FormControlLabel
label={<div className="text-sm">Afficher les noms</div>}
control={
<Switch
value={showUsernames}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setShowUsernames(e.target.checked)
}
/>
}
/>
<FormControlLabel
label={<div className="text-sm">Afficher les réponses</div>}
control={
<Switch
value={showCorrectAnswers}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setShowCorrectAnswers(e.target.checked)
}
/>
}
/>
</FormGroup>
)}
</div>
{isExpanded && (
<div className="table-container" data-testid="table-container">
<LiveResultsTable
students={students}
questions={questions}
showCorrectAnswers={showCorrectAnswers}
showSelectedQuestion={showSelectedQuestion}
showUsernames={showUsernames}
/> />
<span>Résultats du quiz</span>
</div>
{isExpanded && (
<FormGroup row className='form-group'>
<FormControlLabel
label={<div className="text-sm">Afficher les noms</div>}
control={
<Switch
value={showUsernames}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setShowUsernames(e.target.checked)
}
/>
}
/>
<FormControlLabel
label={<div className="text-sm">Afficher les réponses</div>}
control={
<Switch
value={showCorrectAnswers}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
setShowCorrectAnswers(e.target.checked)
}
/>
}
/>
</FormGroup>
)}
</div> </div>
{isExpanded && (
<div className="table-container" data-testid="table-container">
<LiveResultsTable
students={students}
questions={questions}
showCorrectAnswers={showCorrectAnswers}
showSelectedQuestion={showSelectedQuestion}
showUsernames={showUsernames}
/>
</div>
)} )}
</div> </div>
); );

View file

@ -16,8 +16,8 @@
/* Flexbox container for the action bar */ /* Flexbox container for the action bar */
.action-bar { .action-bar {
display: flex; display: flex;
flex-direction: column; flex-direction: row;
align-items: flex-start; margin-top: 2rem;
} }
/* Flexbox container for the form group */ /* Flexbox container for the form group */
@ -25,6 +25,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: flex-start; align-items: flex-start;
margin-left: 1rem;
} }
.table-cell-border { .table-cell-border {