[BUG] Le progrès des étudiants n'est pas sauvegardé lorsqu'ils quittent et reviennent dans le quiz.

Fixes #296
This commit is contained in:
JubaAzul 2025-03-24 16:33:26 -04:00
parent 182402e621
commit e2c4bb0722
2 changed files with 136 additions and 64 deletions

View file

@ -75,36 +75,36 @@ describe('ManageRoom', () => {
</MemoryRouter> </MemoryRouter>
); );
}); });
await act(async () => { await act(async () => {
const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1]; const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1];
createSuccessCallback('Test Room'); createSuccessCallback('Test Room');
}); });
await waitFor(() => { await waitFor(() => {
expect(ApiService.getQuiz).toHaveBeenCalledWith('test-quiz-id'); expect(ApiService.getQuiz).toHaveBeenCalledWith('test-quiz-id');
}); });
const launchButton = screen.getByText('Lancer'); const launchButton = screen.getByText('Lancer');
fireEvent.click(launchButton); fireEvent.click(launchButton);
const rythmeButton = screen.getByText('Rythme du professeur'); const rythmeButton = screen.getByText('Rythme du professeur');
fireEvent.click(rythmeButton); fireEvent.click(rythmeButton);
const secondLaunchButton = screen.getAllByText('Lancer'); const secondLaunchButton = screen.getAllByText('Lancer');
fireEvent.click(secondLaunchButton[1]); fireEvent.click(secondLaunchButton[1]);
await waitFor(() => { await waitFor(() => {
expect(screen.getByText('Test Quiz')).toBeInTheDocument(); expect(screen.getByText('Test Quiz')).toBeInTheDocument();
const roomHeader = document.querySelector('h1'); const roomHeader = document.querySelector('h1');
expect(roomHeader).toHaveTextContent('Salle : TEST ROOM'); expect(roomHeader).toHaveTextContent('Salle : TEST ROOM');
expect(screen.getByText('0/60')).toBeInTheDocument(); expect(screen.getByText('0/60')).toBeInTheDocument();
expect(screen.getByText('Question 1/2')).toBeInTheDocument(); expect(screen.getByText('Question 1/2')).toBeInTheDocument();
}); });
}); });
test('handles create-success event', async () => { test('handles create-success event', async () => {
await act(async () => { await act(async () => {
render( render(
@ -171,30 +171,30 @@ describe('ManageRoom', () => {
</MemoryRouter> </MemoryRouter>
); );
}); });
await act(async () => { await act(async () => {
const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1]; const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1];
createSuccessCallback('Test Room'); createSuccessCallback('Test Room');
}); });
fireEvent.click(screen.getByText('Lancer')); fireEvent.click(screen.getByText('Lancer'));
fireEvent.click(screen.getByText('Rythme du professeur')); fireEvent.click(screen.getByText('Rythme du professeur'));
fireEvent.click(screen.getAllByText('Lancer')[1]); fireEvent.click(screen.getAllByText('Lancer')[1]);
await waitFor(() => { await waitFor(() => {
screen.debug(); screen.debug();
}); });
const nextQuestionButton = await screen.findByRole('button', { name: /Prochaine question/i }); const nextQuestionButton = await screen.findByRole('button', { name: /Prochaine question/i });
expect(nextQuestionButton).toBeInTheDocument(); expect(nextQuestionButton).toBeInTheDocument();
fireEvent.click(nextQuestionButton); fireEvent.click(nextQuestionButton);
await waitFor(() => { await waitFor(() => {
expect(screen.getByText('Question 2/2')).toBeInTheDocument(); expect(screen.getByText('Question 2/2')).toBeInTheDocument();
}); });
}); });
test('handles disconnect', async () => { test('handles disconnect', async () => {
await act(async () => { await act(async () => {
render( render(
@ -230,37 +230,37 @@ describe('ManageRoom', () => {
</MemoryRouter> </MemoryRouter>
); );
}); });
await act(async () => { await act(async () => {
const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1]; const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1];
createSuccessCallback('test-room-name'); createSuccessCallback('test-room-name');
}); });
const launchButton = screen.getByText('Lancer'); const launchButton = screen.getByText('Lancer');
fireEvent.click(launchButton); fireEvent.click(launchButton);
const rythmeButton = screen.getByText('Rythme du professeur'); const rythmeButton = screen.getByText('Rythme du professeur');
fireEvent.click(rythmeButton); fireEvent.click(rythmeButton);
const secondLaunchButton = screen.getAllByText('Lancer'); const secondLaunchButton = screen.getAllByText('Lancer');
fireEvent.click(secondLaunchButton[1]); fireEvent.click(secondLaunchButton[1]);
await act(async () => { await act(async () => {
const userJoinedCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'user-joined')[1]; const userJoinedCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'user-joined')[1];
userJoinedCallback(mockStudents[0]); userJoinedCallback(mockStudents[0]);
}); });
await act(async () => { await act(async () => {
const submitAnswerCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'submit-answer-room')[1]; const submitAnswerCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'submit-answer-room')[1];
submitAnswerCallback(mockAnswerData); submitAnswerCallback(mockAnswerData);
}); });
await waitFor(() => { await waitFor(() => {
expect(consoleSpy).toHaveBeenCalledWith( expect(consoleSpy).toHaveBeenCalledWith(
'Received answer from Student 1 for question 1: Answer1' 'Received answer from Student 1 for question 1: Answer1'
); );
}); });
consoleSpy.mockRestore(); consoleSpy.mockRestore();
}); });
@ -293,4 +293,74 @@ describe('ManageRoom', () => {
expect(screen.queryByText('Student 1')).not.toBeInTheDocument(); expect(screen.queryByText('Student 1')).not.toBeInTheDocument();
}); });
}); });
test('handles user joined and leaves but name still seeable', async () => {
await act(async () => {
render(
<MemoryRouter>
<ManageRoom />
</MemoryRouter>
);
});
await act(async () => {
const createSuccessCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'create-success')[1];
createSuccessCallback('Test Room');
});
await act(async () => {
const userJoinedCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'user-joined')[1];
userJoinedCallback(mockStudents[0]);
});
await act(async () => {
const userJoinedCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'user-joined')[1];
userJoinedCallback(mockStudents[1]);
});
await waitFor(() => {
expect(screen.getByText('Student 1')).toBeInTheDocument();
});
await waitFor(() => {
expect(screen.getByText('Student 2')).toBeInTheDocument();
});
const launchButton = screen.getByText('Lancer');
fireEvent.click(launchButton);
const rythmeButton = screen.getByText('Rythme du professeur');
fireEvent.click(rythmeButton);
const secondLaunchButton = screen.getAllByText('Lancer');
fireEvent.click(secondLaunchButton[1]);
await waitFor(() => {
expect(screen.getByText('2/60')).toBeInTheDocument();
});
await act(async () => {
const userDisconnectedCallback = (mockSocket.on as jest.Mock).mock.calls.find(call => call[0] === 'user-disconnected')[1];
userDisconnectedCallback(mockStudents[1].id);
});
await waitFor(() => {
expect(screen.getByText('Résultats du quiz')).toBeInTheDocument();
});
const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms');
// Toggle the display of usernames back
fireEvent.click(toggleUsernamesSwitch);
expect(screen.getByText('Student 1')).toBeInTheDocument();
expect(screen.getByText('Student 2')).toBeInTheDocument();
await waitFor(() => {
expect(screen.getByText('1/60')).toBeInTheDocument();
});
});
}); });

View file

@ -29,9 +29,9 @@ import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
const ManageRoom: React.FC = () => { const ManageRoom: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
const [socket, setSocket] = useState<Socket | null>(null); const [socket, setSocket] = useState<Socket | null>(null);
const [students, setStudents] = useState<StudentType[]>([]); const [connectedStudents, setConnectedStudents] = useState<StudentType[]>([]);
const [allStudents, setAllStudents] = useState<StudentType[]>([]); const [totalStudents, setTotalStudents] = useState<StudentType[]>([]);
const { quizId = '', roomName = '' } = useParams<{ quizId: string, roomName: string }>(); const { quizId = '', roomName = '' } = useParams<{ quizId: string, roomName: string }>();
const [quizQuestions, setQuizQuestions] = useState<QuestionType[] | undefined>(); const [quizQuestions, setQuizQuestions] = useState<QuestionType[] | undefined>();
const [quiz, setQuiz] = useState<QuizType | null>(null); const [quiz, setQuiz] = useState<QuizType | null>(null);
const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher'); const [quizMode, setQuizMode] = useState<'teacher' | 'student'>('teacher');
@ -46,15 +46,15 @@ const ManageRoom: React.FC = () => {
useEffect(() => { useEffect(() => {
if (newlyConnectedUser) { if (newlyConnectedUser) {
console.log(`Handling newly connected user: ${newlyConnectedUser.name}`); console.log(`Handling newly connected user: ${newlyConnectedUser.name}`);
setStudents((prevStudents) => [...prevStudents, newlyConnectedUser]); setConnectedStudents((prevStudents) => [...prevStudents, newlyConnectedUser]);
setAllStudents((prevStudents) => [...prevStudents, newlyConnectedUser]); setTotalStudents((prevStudents) => [...prevStudents, newlyConnectedUser]);
// only send nextQuestion if the quiz has started // only send nextQuestion if the quiz has started
if (!quizStarted) { if (!quizStarted) {
console.log(`!quizStarted: returning.... `); console.log(`!quizStarted: returning.... `);
return; return;
} }
if (quizMode === 'teacher') { if (quizMode === 'teacher') {
webSocketService.nextQuestion({ webSocketService.nextQuestion({
roomName: formattedRoomName, roomName: formattedRoomName,
@ -67,7 +67,7 @@ const ManageRoom: React.FC = () => {
} else { } else {
console.error('Invalid quiz mode:', quizMode); console.error('Invalid quiz mode:', quizMode);
} }
// Reset the newly connected user state // Reset the newly connected user state
setNewlyConnectedUser(null); setNewlyConnectedUser(null);
} }
@ -98,7 +98,7 @@ const ManageRoom: React.FC = () => {
return () => { return () => {
disconnectWebSocket(); disconnectWebSocket();
}; };
}, [roomName, navigate]); }, [roomName, navigate]);
useEffect(() => { useEffect(() => {
if (quizId) { if (quizId) {
@ -135,8 +135,8 @@ const ManageRoom: React.FC = () => {
setSocket(null); setSocket(null);
setQuizQuestions(undefined); setQuizQuestions(undefined);
setCurrentQuestion(undefined); setCurrentQuestion(undefined);
setStudents(new Array<StudentType>()); setConnectedStudents(new Array<StudentType>());
setAllStudents(new Array<StudentType>()); setTotalStudents(new Array<StudentType>());
} }
}; };
@ -181,7 +181,7 @@ const ManageRoom: React.FC = () => {
socket.on('user-disconnected', (userId: string) => { socket.on('user-disconnected', (userId: string) => {
console.log(`Student left: id = ${userId}`); console.log(`Student left: id = ${userId}`);
setStudents((prevUsers) => prevUsers.filter((user) => user.id !== userId)); setConnectedStudents((prevUsers) => prevUsers.filter((user) => user.id !== userId));
}); });
setSocket(socket); setSocket(socket);
@ -202,7 +202,7 @@ const ManageRoom: React.FC = () => {
} }
// Update the students state using the functional form of setStudents // Update the students state using the functional form of setStudents
setStudents((prevStudents) => { setConnectedStudents((prevStudents) => {
console.log('Current students:'); console.log('Current students:');
prevStudents.forEach((student) => { prevStudents.forEach((student) => {
console.log(student.name); console.log(student.name);
@ -222,14 +222,14 @@ const ManageRoom: React.FC = () => {
console.log(`Comparing ${ans.idQuestion} to ${idQuestion}`); console.log(`Comparing ${ans.idQuestion} to ${idQuestion}`);
return ans.idQuestion === idQuestion return ans.idQuestion === idQuestion
? { ? {
...ans, ...ans,
answer, answer,
isCorrect: checkIfIsCorrect( isCorrect: checkIfIsCorrect(
answer, answer,
idQuestion, idQuestion,
quizQuestions! quizQuestions!
) )
} }
: ans; : ans;
}); });
} else { } else {
@ -249,7 +249,7 @@ const ManageRoom: React.FC = () => {
} }
return updatedStudents; return updatedStudents;
}); });
setAllStudents((prevStudents) => { setTotalStudents((prevStudents) => {
let foundStudent = false; let foundStudent = false;
const updatedStudents = prevStudents.map((student) => { const updatedStudents = prevStudents.map((student) => {
console.log(`Comparing ${student.id} to ${idUser}`); console.log(`Comparing ${student.id} to ${idUser}`);
@ -264,14 +264,14 @@ const ManageRoom: React.FC = () => {
console.log(`Comparing ${ans.idQuestion} to ${idQuestion}`); console.log(`Comparing ${ans.idQuestion} to ${idQuestion}`);
return ans.idQuestion === idQuestion return ans.idQuestion === idQuestion
? { ? {
...ans, ...ans,
answer, answer,
isCorrect: checkIfIsCorrect( isCorrect: checkIfIsCorrect(
answer, answer,
idQuestion, idQuestion,
quizQuestions! quizQuestions!
) )
} }
: ans; : ans;
}); });
} else { } else {
@ -304,10 +304,12 @@ const ManageRoom: React.FC = () => {
if (nextQuestionIndex === undefined || nextQuestionIndex > quizQuestions.length - 1) return; if (nextQuestionIndex === undefined || nextQuestionIndex > quizQuestions.length - 1) return;
setCurrentQuestion(quizQuestions[nextQuestionIndex]); setCurrentQuestion(quizQuestions[nextQuestionIndex]);
webSocketService.nextQuestion({roomName: formattedRoomName, webSocketService.nextQuestion({
questions: quizQuestions, roomName: formattedRoomName,
questionIndex: nextQuestionIndex, questions: quizQuestions,
isLaunch: false}); questionIndex: nextQuestionIndex,
isLaunch: false
});
}; };
const previousQuestion = () => { const previousQuestion = () => {
@ -317,7 +319,7 @@ const ManageRoom: React.FC = () => {
if (prevQuestionIndex === undefined || prevQuestionIndex < 0) return; if (prevQuestionIndex === undefined || prevQuestionIndex < 0) return;
setCurrentQuestion(quizQuestions[prevQuestionIndex]); 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 = () => { const initializeQuizQuestion = () => {
@ -345,7 +347,7 @@ const ManageRoom: React.FC = () => {
} }
setCurrentQuestion(quizQuestions[0]); 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 = () => { const launchStudentMode = () => {
@ -382,7 +384,7 @@ const ManageRoom: React.FC = () => {
if (quiz?.content && quizQuestions) { if (quiz?.content && quizQuestions) {
setCurrentQuestion(quizQuestions[questionIndex]); setCurrentQuestion(quizQuestions[questionIndex]);
if (quizMode === 'teacher') { if (quizMode === 'teacher') {
webSocketService.nextQuestion({roomName: formattedRoomName, questions: quizQuestions, questionIndex, isLaunch: false}); webSocketService.nextQuestion({ roomName: formattedRoomName, questions: quizQuestions, questionIndex, isLaunch: false });
} }
} }
}; };
@ -496,7 +498,7 @@ const ManageRoom: React.FC = () => {
style={{ display: "flex", justifyContent: "flex-end" }} style={{ display: "flex", justifyContent: "flex-end" }}
> >
<GroupIcon style={{ marginRight: '5px' }} /> <GroupIcon style={{ marginRight: '5px' }} />
{students.length}/60 {connectedStudents.length}/60
</div> </div>
)} )}
</div> </div>
@ -533,7 +535,7 @@ const ManageRoom: React.FC = () => {
<QuestionDisplay <QuestionDisplay
showAnswer={false} showAnswer={false}
question={currentQuestion?.question as Question} question={currentQuestion?.question as Question}
/> />
)} )}
@ -542,7 +544,7 @@ const ManageRoom: React.FC = () => {
socket={socket} socket={socket}
questions={quizQuestions} questions={quizQuestions}
showSelectedQuestion={showSelectedQuestion} showSelectedQuestion={showSelectedQuestion}
students={allStudents} students={totalStudents}
></LiveResultsComponent> ></LiveResultsComponent>
</div> </div>
</div> </div>
@ -578,7 +580,7 @@ const ManageRoom: React.FC = () => {
</div> </div>
) : ( ) : (
<StudentWaitPage <StudentWaitPage
students={students} students={connectedStudents}
launchQuiz={launchQuiz} launchQuiz={launchQuiz}
setQuizMode={setQuizMode} setQuizMode={setQuizMode}
/> />