Redesign how answers are submitted and updated (react state management)
This commit is contained in:
C. Fuhrman 2024-09-26 00:34:30 -04:00
parent ab18283db0
commit c0cc4d01e0
10 changed files with 531 additions and 320 deletions

View file

@ -48,7 +48,7 @@ describe('StudentModeQuiz', () => {
fireEvent.click(screen.getByText('Répondre')); fireEvent.click(screen.getByText('Répondre'));
}); });
expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', '1'); expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', 1);
}); });
test('handles quit button click', async () => { test('handles quit button click', async () => {

View file

@ -47,7 +47,7 @@ describe('TeacherModeQuiz', () => {
act(() => { act(() => {
fireEvent.click(screen.getByText('Répondre')); fireEvent.click(screen.getByText('Répondre'));
}); });
expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', '1'); expect(mockSubmitAnswer).toHaveBeenCalledWith('Option A', 1);
expect(screen.getByText('Votre réponse est "Option A".')).toBeInTheDocument(); expect(screen.getByText('Votre réponse est "Option A".')).toBeInTheDocument();
}); });

View file

@ -1,7 +1,6 @@
// LiveResults.tsx // LiveResults.tsx
import React, { useEffect, useMemo, useState } from 'react'; import React, { useMemo, useState } from 'react';
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
import { GIFTQuestion } from 'gift-pegjs';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons'; import { faCheck, faCircleXmark } from '@fortawesome/free-solid-svg-icons';
import { QuestionType } from '../../Types/QuestionType'; import { QuestionType } from '../../Types/QuestionType';
@ -15,11 +14,12 @@ import {
Table, Table,
TableBody, TableBody,
TableCell, TableCell,
TableContainer,
TableFooter, TableFooter,
TableHead, TableHead,
TableRow TableRow
} from '@mui/material'; } from '@mui/material';
import { StudentType, Answer } from '../../Types/StudentType'; import { StudentType } from '../../Types/StudentType';
import { formatLatex } from '../GiftTemplate/templates/TextType'; import { formatLatex } from '../GiftTemplate/templates/TextType';
interface LiveResultsProps { interface LiveResultsProps {
@ -27,7 +27,7 @@ interface LiveResultsProps {
questions: QuestionType[]; questions: QuestionType[];
showSelectedQuestion: (index: number) => void; showSelectedQuestion: (index: number) => void;
quizMode: 'teacher' | 'student'; quizMode: 'teacher' | 'student';
connectedStudents: StudentType[] students: StudentType[]
} }
// interface Answer { // interface Answer {
@ -42,10 +42,10 @@ interface LiveResultsProps {
// answers: Answer[]; // answers: Answer[];
// } // }
const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelectedQuestion, connectedStudents }) => { const LiveResults: React.FC<LiveResultsProps> = ({ questions, showSelectedQuestion, students }) => {
const [showUsernames, setShowUsernames] = useState<boolean>(false); const [showUsernames, setShowUsernames] = useState<boolean>(false);
const [showCorrectAnswers, setShowCorrectAnswers] = useState<boolean>(false); const [showCorrectAnswers, setShowCorrectAnswers] = useState<boolean>(false);
const [students, setStudents] = useState<StudentType[]>(connectedStudents); // const [students, setStudents] = useState<StudentType[]>(initialStudents);
// const [studentResultsMap, setStudentResultsMap] = useState<Map<string, StudentResult>>(new Map()); // const [studentResultsMap, setStudentResultsMap] = useState<Map<string, StudentResult>>(new Map());
const maxQuestions = questions.length; const maxQuestions = questions.length;
@ -69,83 +69,77 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
// } // }
// }, [students]) // }, [students])
useEffect(() => {
// Update the students state when the initialStudents prop changes
setStudents(connectedStudents);
console.log(`useEffect: connectedStudents: ${JSON.stringify(connectedStudents)}`);
}, [connectedStudents]);
// 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}`);
useEffect(() => { // // print the list of current student names
if (socket) { // console.log('Current students:');
const submitAnswerHandler = ({ // students.forEach((student) => {
idUser, // console.log(student.name);
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); // // Update the students state using the functional form of setStudents
return () => { // setStudents((prevStudents) => {
socket.off('submit-answer'); // let foundStudent = false;
}; // const updatedStudents = prevStudents.map((student) => {
} // if (student.id === idUser) {
}, [socket]); // 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 => { const getStudentGrade = (student: StudentType): number => {
if (student.answers.length === 0) { if (student.answers.length === 0) {
@ -202,73 +196,73 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
// ); // );
// }; // };
function checkIfIsCorrect(answer: string | number | boolean, idQuestion: number): boolean { // function checkIfIsCorrect(answer: string | number | boolean, idQuestion: number): boolean {
const questionInfo = questions.find((q) => // const questionInfo = questions.find((q) =>
q.question.id ? q.question.id === idQuestion.toString() : false // q.question.id ? q.question.id === idQuestion.toString() : false
) as QuestionType | undefined; // ) as QuestionType | undefined;
const answerText = answer.toString(); // const answerText = answer.toString();
if (questionInfo) { // if (questionInfo) {
const question = questionInfo.question as GIFTQuestion; // const question = questionInfo.question as GIFTQuestion;
if (question.type === 'TF') { // if (question.type === 'TF') {
return ( // return (
(question.isTrue && answerText == 'true') || // (question.isTrue && answerText == 'true') ||
(!question.isTrue && answerText == 'false') // (!question.isTrue && answerText == 'false')
); // );
} else if (question.type === 'MC') { // } else if (question.type === 'MC') {
return question.choices.some( // return question.choices.some(
(choice) => choice.isCorrect && choice.text.text === answerText // (choice) => choice.isCorrect && choice.text.text === answerText
); // );
} else if (question.type === 'Numerical') { // } else if (question.type === 'Numerical') {
if (question.choices && !Array.isArray(question.choices)) { // if (question.choices && !Array.isArray(question.choices)) {
if ( // if (
question.choices.type === 'high-low' && // question.choices.type === 'high-low' &&
question.choices.numberHigh && // question.choices.numberHigh &&
question.choices.numberLow // question.choices.numberLow
) { // ) {
const answerNumber = parseFloat(answerText); // const answerNumber = parseFloat(answerText);
if (!isNaN(answerNumber)) { // if (!isNaN(answerNumber)) {
return ( // return (
answerNumber <= question.choices.numberHigh && // answerNumber <= question.choices.numberHigh &&
answerNumber >= question.choices.numberLow // answerNumber >= question.choices.numberLow
); // );
} // }
} // }
} // }
if (question.choices && Array.isArray(question.choices)) { // if (question.choices && Array.isArray(question.choices)) {
if ( // if (
question.choices[0].text.type === 'range' && // question.choices[0].text.type === 'range' &&
question.choices[0].text.number && // question.choices[0].text.number &&
question.choices[0].text.range // question.choices[0].text.range
) { // ) {
const answerNumber = parseFloat(answerText); // const answerNumber = parseFloat(answerText);
const range = question.choices[0].text.range; // const range = question.choices[0].text.range;
const correctAnswer = question.choices[0].text.number; // const correctAnswer = question.choices[0].text.number;
if (!isNaN(answerNumber)) { // if (!isNaN(answerNumber)) {
return ( // return (
answerNumber <= correctAnswer + range && // answerNumber <= correctAnswer + range &&
answerNumber >= correctAnswer - range // answerNumber >= correctAnswer - range
); // );
} // }
} // }
if ( // if (
question.choices[0].text.type === 'simple' && // question.choices[0].text.type === 'simple' &&
question.choices[0].text.number // question.choices[0].text.number
) { // ) {
const answerNumber = parseFloat(answerText); // const answerNumber = parseFloat(answerText);
if (!isNaN(answerNumber)) { // if (!isNaN(answerNumber)) {
return answerNumber === question.choices[0].text.number; // return answerNumber === question.choices[0].text.number;
} // }
} // }
} // }
} else if (question.type === 'Short') { // } else if (question.type === 'Short') {
return question.choices.some( // return question.choices.some(
(choice) => choice.text.text.toUpperCase() === answerText.toUpperCase() // (choice) => choice.text.text.toUpperCase() === answerText.toUpperCase()
); // );
} // }
} // }
return false; // return false;
} // }
return ( return (
<div> <div>
@ -301,7 +295,8 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
</div> </div>
<div className="table-container"> <div className="table-container">
<Table size="small" component={Paper}> <TableContainer component={Paper}>
<Table size="small">
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell className="sticky-column"> <TableCell className="sticky-column">
@ -439,6 +434,7 @@ const LiveResults: React.FC<LiveResultsProps> = ({ socket, questions, showSelect
</TableRow> </TableRow>
</TableFooter> </TableFooter>
</Table> </Table>
</TableContainer>
</div> </div>
</div> </div>
); );

View file

@ -12,7 +12,7 @@ import DisconnectButton from '../../components/DisconnectButton/DisconnectButton
interface StudentModeQuizProps { interface StudentModeQuizProps {
questions: QuestionType[]; questions: QuestionType[];
submitAnswer: (answer: string | number | boolean, idQuestion: string) => void; submitAnswer: (answer: string | number | boolean, idQuestion: number) => void;
disconnectWebSocket: () => void; disconnectWebSocket: () => void;
} }
@ -38,7 +38,7 @@ const StudentModeQuiz: React.FC<StudentModeQuizProps> = ({
}; };
const handleOnSubmitAnswer = (answer: string | number | boolean) => { const handleOnSubmitAnswer = (answer: string | number | boolean) => {
const idQuestion = questionInfos.question.id || '-1'; const idQuestion = Number(questionInfos.question.id) || -1;
submitAnswer(answer, idQuestion); submitAnswer(answer, idQuestion);
setIsAnswerSubmitted(true); setIsAnswerSubmitted(true);
}; };

View file

@ -11,7 +11,7 @@ import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/
interface TeacherModeQuizProps { interface TeacherModeQuizProps {
questionInfos: QuestionType; questionInfos: QuestionType;
submitAnswer: (answer: string | number | boolean, idQuestion: string) => void; submitAnswer: (answer: string | number | boolean, idQuestion: number) => void;
disconnectWebSocket: () => void; disconnectWebSocket: () => void;
} }
@ -29,7 +29,7 @@ const TeacherModeQuiz: React.FC<TeacherModeQuizProps> = ({
}, [questionInfos]); }, [questionInfos]);
const handleOnSubmitAnswer = (answer: string | number | boolean) => { const handleOnSubmitAnswer = (answer: string | number | boolean) => {
const idQuestion = questionInfos.question.id || '-1'; const idQuestion = Number(questionInfos.question.id) || -1;
submitAnswer(answer, idQuestion); submitAnswer(answer, idQuestion);
setFeedbackMessage(`Votre réponse est "${answer.toString()}".`); setFeedbackMessage(`Votre réponse est "${answer.toString()}".`);
setIsFeedbackDialogOpen(true); setIsFeedbackDialogOpen(true);

View file

@ -5,7 +5,7 @@ import { ENV_VARIABLES } from '../../../constants';
import StudentModeQuiz from '../../../components/StudentModeQuiz/StudentModeQuiz'; import StudentModeQuiz from '../../../components/StudentModeQuiz/StudentModeQuiz';
import TeacherModeQuiz from '../../../components/TeacherModeQuiz/TeacherModeQuiz'; import TeacherModeQuiz from '../../../components/TeacherModeQuiz/TeacherModeQuiz';
import webSocketService from '../../../services/WebsocketService'; import webSocketService, { AnswerSubmissionToBackendType } from '../../../services/WebsocketService';
import DisconnectButton from '../../../components/DisconnectButton/DisconnectButton'; import DisconnectButton from '../../../components/DisconnectButton/DisconnectButton';
import './joinRoom.css'; import './joinRoom.css';
@ -99,8 +99,15 @@ const JoinRoom: React.FC = () => {
} }
}; };
const handleOnSubmitAnswer = (answer: string | number | boolean, idQuestion: string) => { const handleOnSubmitAnswer = (answer: string | number | boolean, idQuestion: number) => {
webSocketService.submitAnswer(roomName, answer, username, idQuestion); const answerData: AnswerSubmissionToBackendType = {
roomName: roomName,
answer: answer,
username: username,
idQuestion: idQuestion
};
webSocketService.submitAnswer(answerData);
}; };
if (isWaitingForTeacher) { if (isWaitingForTeacher) {

View file

@ -2,16 +2,16 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
import { parse } from 'gift-pegjs'; import { GIFTQuestion, parse } from 'gift-pegjs';
import { QuestionType } from '../../../Types/QuestionType'; import { QuestionType } from '../../../Types/QuestionType';
import LiveResultsComponent from '../../../components/LiveResults/LiveResults'; import LiveResultsComponent from '../../../components/LiveResults/LiveResults';
// import { QuestionService } from '../../../services/QuestionService'; // import { QuestionService } from '../../../services/QuestionService';
import webSocketService from '../../../services/WebsocketService'; import webSocketService, { AnswerReceptionFromBackendType } from '../../../services/WebsocketService';
import { QuizType } from '../../../Types/QuizType'; import { QuizType } from '../../../Types/QuizType';
import './manageRoom.css'; import './manageRoom.css';
import { ENV_VARIABLES } from '../../../constants'; import { ENV_VARIABLES } from '../../../constants';
import { StudentType } from '../../../Types/StudentType'; import { StudentType, Answer } from '../../../Types/StudentType';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
import LoadingCircle from '../../../components/LoadingCircle/LoadingCircle'; import LoadingCircle from '../../../components/LoadingCircle/LoadingCircle';
import { Refresh, Error } from '@mui/icons-material'; import { Refresh, Error } from '@mui/icons-material';
@ -121,10 +121,9 @@ const ManageRoom: React.FC = () => {
useEffect(() => { useEffect(() => {
// This is here to make sure the correct value is sent when user join // This is here to make sure the correct value is sent when user join
if (socket) { if (socket) {
console.log(`Listening for user-joined in room ${roomName}`);
socket.on('user-joined', (_student: StudentType) => { socket.on('user-joined', (_student: StudentType) => {
// setUsers((prevUsers) => [...prevUsers, user]);
if (quizMode === 'teacher') { if (quizMode === 'teacher') {
webSocketService.nextQuestion(roomName, currentQuestion); webSocketService.nextQuestion(roomName, currentQuestion);
} else if (quizMode === 'student') { } else if (quizMode === 'student') {
@ -132,7 +131,122 @@ const ManageRoom: React.FC = () => {
} }
}); });
} }
}, [currentQuestion, quizQuestions]);
if (socket) {
// handle the case where user submits an answer
console.log(`Listening for submit-answer-room in room ${roomName}`);
socket.on('submit-answer-room', (answerData: AnswerReceptionFromBackendType) => {
const { answer, idQuestion, idUser, username } = answerData;
console.log(`Received answer from ${username} for question ${idQuestion}: ${answer}`);
if (!quizQuestions) {
console.log('Quiz questions not found (cannot update answers without them).');
return;
}
// Update the students state using the functional form of setStudents
setStudents((prevStudents) => {
// print the list of current student names
console.log('Current students:');
prevStudents.forEach((student) => {
console.log(student.name);
});
let foundStudent = false;
const updatedStudents = prevStudents.map((student) => {
console.log(`Comparing ${student.id} to ${idUser}`);
if (student.id === idUser) {
foundStudent = true;
const existingAnswer = student.answers.find((ans) => ans.idQuestion === idQuestion);
let updatedAnswers: Answer[] = [];
if (existingAnswer) {
// Update the existing answer
updatedAnswers = student.answers.map((ans) => {
console.log(`Comparing ${ans.idQuestion} to ${idQuestion}`);
return (ans.idQuestion === idQuestion ? { ...ans, answer, isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!) } : ans);
});
} else {
// Add a new answer
const newAnswer = { idQuestion, answer, isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!) };
updatedAnswers = [...student.answers, newAnswer];
}
return { ...student, answers: updatedAnswers };
}
return student;
});
if (!foundStudent) {
console.log(`Student ${username} not found in the list.`);
}
return updatedStudents;
});
});
setSocket(socket);
}
}, [socket, currentQuestion, quizQuestions]);
// useEffect(() => {
// if (socket) {
// const submitAnswerHandler = (answerData: answerSubmissionType) => {
// const { answer, idQuestion, username } = answerData;
// console.log(`Received answer from ${username} 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 === username) {
// foundStudent = true;
// const updatedAnswers = student.answers.map((ans) => {
// const newAnswer: Answer = { answer, isCorrect: checkIfIsCorrect(answer, idQuestion, quizQuestions!), 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 ${username} 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 nextQuestion = () => { const nextQuestion = () => {
if (!quizQuestions || !currentQuestion || !quiz?.content) return; if (!quizQuestions || !currentQuestion || !quiz?.content) return;
@ -173,8 +287,12 @@ const ManageRoom: React.FC = () => {
const launchTeacherMode = () => { const launchTeacherMode = () => {
const quizQuestions = initializeQuizQuestion(); const quizQuestions = initializeQuizQuestion();
console.log('launchTeacherMode - quizQuestions:', quizQuestions);
if (!quizQuestions) return; if (!quizQuestions) {
console.log('Error launching quiz (launchTeacherMode). No questions found.');
return;
}
setCurrentQuestion(quizQuestions[0]); setCurrentQuestion(quizQuestions[0]);
webSocketService.nextQuestion(roomName, quizQuestions[0]); webSocketService.nextQuestion(roomName, quizQuestions[0]);
@ -182,18 +300,20 @@ const ManageRoom: React.FC = () => {
const launchStudentMode = () => { const launchStudentMode = () => {
const quizQuestions = initializeQuizQuestion(); const quizQuestions = initializeQuizQuestion();
console.log('launchStudentMode - quizQuestions:', quizQuestions);
if (!quizQuestions) { if (!quizQuestions) {
console.log('Error launching quiz (launchStudentMode). No questions found.');
return; return;
} }
setQuizQuestions(quizQuestions);
webSocketService.launchStudentModeQuiz(roomName, quizQuestions); webSocketService.launchStudentModeQuiz(roomName, quizQuestions);
}; };
const launchQuiz = () => { const launchQuiz = () => {
if (!socket || !roomName || !quiz?.content || quiz?.content.length === 0) { if (!socket || !roomName || !quiz?.content || quiz?.content.length === 0) {
// TODO: This error happens when token expires! Need to handle it properly // TODO: This error happens when token expires! Need to handle it properly
console.log('Error launching quiz. No socket, room name or no questions.'); console.log(`Error launching quiz. socket: ${socket}, roomName: ${roomName}, quiz: ${quiz}`);
return; return;
} }
switch (quizMode) { switch (quizMode) {
@ -219,6 +339,75 @@ const ManageRoom: React.FC = () => {
navigate('/teacher/dashboard'); navigate('/teacher/dashboard');
}; };
function checkIfIsCorrect(answer: string | number | boolean, idQuestion: number, questions: QuestionType[]): 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;
}
if (!roomName) { if (!roomName) {
return ( return (
<div className="center"> <div className="center">
@ -295,7 +484,7 @@ const ManageRoom: React.FC = () => {
socket={socket} socket={socket}
questions={quizQuestions} questions={quizQuestions}
showSelectedQuestion={showSelectedQuestion} showSelectedQuestion={showSelectedQuestion}
connectedStudents={students} students={students}
></LiveResultsComponent> ></LiveResultsComponent>
</div> </div>

View file

@ -1,6 +1,22 @@
// WebSocketService.tsx // WebSocketService.tsx
import { io, Socket } from 'socket.io-client'; import { io, Socket } from 'socket.io-client';
// Must (manually) sync these types to server/socket/socket.js
export type AnswerSubmissionToBackendType = {
roomName: string;
username: string;
answer: string | number | boolean;
idQuestion: number;
};
export type AnswerReceptionFromBackendType = {
idUser: string;
username: string;
answer: string | number | boolean;
idQuestion: number;
};
class WebSocketService { class WebSocketService {
private socket: Socket | null = null; private socket: Socket | null = null;
@ -51,19 +67,22 @@ class WebSocketService {
} }
} }
submitAnswer( submitAnswer(answerData: AnswerSubmissionToBackendType
roomName: string, // roomName: string,
answer: string | number | boolean, // answer: string | number | boolean,
username: string, // username: string,
idQuestion: string // idQuestion: string
) { ) {
if (this.socket) { if (this.socket) {
this.socket?.emit('submit-answer', { this.socket?.emit('submit-answer',
answer: answer, // {
roomName: roomName, // answer: answer,
username: username, // roomName: roomName,
idQuestion: idQuestion // username: username,
}); // idQuestion: idQuestion
// }
answerData
);
} }
} }
} }

View file

@ -125,7 +125,7 @@ describe("websocket server", () => {
answer: "answer1", answer: "answer1",
idQuestion: 1, idQuestion: 1,
}); });
teacherSocket.on("submit-answer", (answer) => { teacherSocket.on("submit-answer-room", (answer) => {
expect(answer).toEqual({ expect(answer).toEqual({
idUser: studentSocket.id, idUser: studentSocket.id,
username: "student1", username: "student1",

View file

@ -101,7 +101,7 @@ const setupWebsocket = (io) => {
}); });
socket.on("submit-answer", ({ roomName, username, answer, idQuestion }) => { socket.on("submit-answer", ({ roomName, username, answer, idQuestion }) => {
socket.to(roomName).emit("submit-answer", { socket.to(roomName).emit("submit-answer-room", {
idUser: socket.id, idUser: socket.id,
username, username,
answer, answer,