Trying context in JoinRoom

This commit is contained in:
C. Fuhrman 2025-04-03 14:51:42 -04:00
parent ee7a7a0544
commit 128ab6662e
10 changed files with 315 additions and 358 deletions

View file

@ -419,26 +419,26 @@
} }
}, },
"node_modules/@babel/helpers": { "node_modules/@babel/helpers": {
"version": "7.26.9", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
"integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==", "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/template": "^7.26.9", "@babel/template": "^7.27.0",
"@babel/types": "^7.26.9" "@babel/types": "^7.27.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.26.9", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
"integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==", "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.26.9" "@babel/types": "^7.27.0"
}, },
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@ -1893,9 +1893,9 @@
} }
}, },
"node_modules/@babel/runtime": { "node_modules/@babel/runtime": {
"version": "7.26.9", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.26.9.tgz", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz",
"integrity": "sha512-aA63XwOkcl4xxQa3HjPMqOP6LiK0ZDv3mUPYEFXkpHbaFjtGggE1A61FjFzJnB+p7/oy2gA8E+rcBNl/zC1tMg==", "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"regenerator-runtime": "^0.14.0" "regenerator-runtime": "^0.14.0"
@ -1905,14 +1905,14 @@
} }
}, },
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.26.9", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
"integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==", "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.26.2", "@babel/code-frame": "^7.26.2",
"@babel/parser": "^7.26.9", "@babel/parser": "^7.27.0",
"@babel/types": "^7.26.9" "@babel/types": "^7.27.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@ -1946,9 +1946,9 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.26.9", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
"integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==", "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.25.9", "@babel/helper-string-parser": "^7.25.9",
@ -5336,9 +5336,9 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.8.1", "version": "1.8.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
"integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==", "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
@ -12911,9 +12911,9 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "6.2.0", "version": "6.2.5",
"resolved": "https://registry.npmjs.org/vite/-/vite-6.2.0.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.5.tgz",
"integrity": "sha512-7dPxoo+WsT/64rDcwoOjk76XHj+TqNTIvHKcuMQ1k4/SeHDaQt5GFAeLYzrimZrMpn/O6DtdI03WUjdxuPM0oQ==", "integrity": "sha512-j023J/hCAa4pRIUH6J9HemwYfjB5llR2Ps0CWeikOtdR8+pAURAk0DoJC5/mm9kd+UgdnIy7d6HE4EAvlYhPhA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"esbuild": "^0.25.0", "esbuild": "^0.25.0",

View file

@ -1,40 +1,39 @@
// TrueFalseQuestion.test.tsx import React from 'react';
import React, { useState } from 'react';
import { render, fireEvent, screen, act } from '@testing-library/react'; import { render, fireEvent, screen, act } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import TrueFalseQuestionDisplay from 'src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay'; import TrueFalseQuestionDisplay from 'src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay';
import { parse, TrueFalseQuestion } from 'gift-pegjs'; import { parse, TrueFalseQuestion } from 'gift-pegjs';
import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
import { QuizProvider } from 'src/pages/Student/JoinRoom/QuizProvider';
// import { useQuizContext } from 'src/pages/Student/JoinRoom/QuizContext';
describe('TrueFalseQuestion Component', () => { describe('TrueFalseQuestion Component with QuizContext', () => {
const mockHandleSubmitAnswer = jest.fn(); const mockHandleSubmitAnswer = jest.fn();
const sampleStem = 'Sample True False Question'; const sampleStem = 'Sample True False Question';
const trueFalseQuestion = const trueFalseQuestion =
parse(`${sampleStem}{T}`)[0] as TrueFalseQuestion; parse(`${sampleStem}{T}`)[0] as TrueFalseQuestion;
const TestWrapper = () => {
const TestWrapper = ({ showAnswer }: { showAnswer: boolean }) => {
const [showAnswerState, setShowAnswerState] = useState(showAnswer);
const handleOnSubmitAnswer = (answer: AnswerType) => { const handleOnSubmitAnswer = (answer: AnswerType) => {
mockHandleSubmitAnswer(answer); mockHandleSubmitAnswer(answer);
setShowAnswerState(true); // setShowAnswer(true); // set it in the context
}; };
return ( return (
<QuizProvider>
<MemoryRouter> <MemoryRouter>
<TrueFalseQuestionDisplay <TrueFalseQuestionDisplay
question={trueFalseQuestion} question={trueFalseQuestion}
handleOnSubmitAnswer={handleOnSubmitAnswer} handleOnSubmitAnswer={handleOnSubmitAnswer}
showAnswer={showAnswerState}
/> />
</MemoryRouter> </MemoryRouter>
</QuizProvider>
); );
}; };
beforeEach(() => { beforeEach(() => {
render(<TestWrapper showAnswer={false} />); render(<TestWrapper />);
}); });
it('renders correctly', () => { it('renders correctly', () => {
@ -89,49 +88,48 @@ describe('TrueFalseQuestion Component', () => {
mockHandleSubmitAnswer.mockClear(); mockHandleSubmitAnswer.mockClear();
}); });
it('should show ✅ next to the correct answer and ❌ next to the wrong answers when showAnswer is true', async () => { it('should show ✅ next to the correct answer and ❌ next to the wrong answers when showAnswer is true', async () => {
const choiceButton = screen.getByText('Vrai').closest('button'); const trueButton = screen.getByText('Vrai').closest('button');
if (!choiceButton) throw new Error('T button not found'); if (!trueButton) throw new Error('True button not found');
// Click on choiceButton // Click on trueButton
act(() => { act(() => {
fireEvent.click(choiceButton); fireEvent.click(trueButton);
}); });
const button = screen.getByText("Répondre"); const submitButton = screen.getByText('Répondre');
act(() => { act(() => {
fireEvent.click(button); fireEvent.click(submitButton);
}); });
// Wait for the DOM to update // Wait for the DOM to update
const correctAnswer = screen.getByText("Vrai").closest('button'); const correctAnswer = screen.getByText('Vrai').closest('button');
expect(correctAnswer).toBeInTheDocument(); expect(correctAnswer).toBeInTheDocument();
expect(correctAnswer?.textContent).toContain('✅'); expect(correctAnswer?.textContent).toContain('✅');
const wrongAnswer1 = screen.getByText("Faux").closest('button'); const wrongAnswer = screen.getByText('Faux').closest('button');
expect(wrongAnswer1).toBeInTheDocument(); expect(wrongAnswer).toBeInTheDocument();
expect(wrongAnswer1?.textContent).toContain('❌'); expect(wrongAnswer?.textContent).toContain('❌');
}); });
it('should not show ✅ or ❌ when Répondre button is not clicked', async () => { it('should not show ✅ or ❌ when Répondre button is not clicked', async () => {
const choiceButton = screen.getByText('Vrai').closest('button'); const trueButton = screen.getByText('Vrai').closest('button');
if (!choiceButton) throw new Error('Choice button not found'); if (!trueButton) throw new Error('True button not found');
// Click on choiceButton // Click on trueButton
act(() => { act(() => {
fireEvent.click(choiceButton); fireEvent.click(trueButton);
}); });
// Check for correct answer // Check for correct answer
const correctAnswer = screen.getByText("Vrai").closest('button'); const correctAnswer = screen.getByText('Vrai').closest('button');
expect(correctAnswer).toBeInTheDocument(); expect(correctAnswer).toBeInTheDocument();
expect(correctAnswer?.textContent).not.toContain('✅'); expect(correctAnswer?.textContent).not.toContain('✅');
// Check for wrong answers // Check for wrong answers
const wrongAnswer1 = screen.getByText("Faux"); const wrongAnswer = screen.getByText('Faux').closest('button');
expect(wrongAnswer1).toBeInTheDocument(); expect(wrongAnswer).toBeInTheDocument();
expect(wrongAnswer1?.textContent).not.toContain('❌'); expect(wrongAnswer?.textContent).not.toContain('❌');
}); });
}); });

View file

@ -33,7 +33,7 @@ const QuestionDisplay: React.FC<QuestionProps> = ({
<TrueFalseQuestionDisplay <TrueFalseQuestionDisplay
question={question} question={question}
handleOnSubmitAnswer={handleOnSubmitAnswer} handleOnSubmitAnswer={handleOnSubmitAnswer}
showAnswer={showAnswer} // showAnswer={showAnswer}
passedAnswer={answer} passedAnswer={answer}
/> />
); );

View file

@ -5,24 +5,25 @@ import { Button } from '@mui/material';
import { TrueFalseQuestion } from 'gift-pegjs'; import { TrueFalseQuestion } from 'gift-pegjs';
import { FormattedTextTemplate } from 'src/components/GiftTemplate/templates/TextTypeTemplate'; import { FormattedTextTemplate } from 'src/components/GiftTemplate/templates/TextTypeTemplate';
import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
import { QuizContext } from 'src/pages/Student/JoinRoom/QuizContext';
interface Props { interface Props {
question: TrueFalseQuestion; question: TrueFalseQuestion;
handleOnSubmitAnswer?: (answer: AnswerType) => void; handleOnSubmitAnswer?: (answer: AnswerType) => void;
showAnswer?: boolean; // showAnswer?: boolean;
passedAnswer?: AnswerType; passedAnswer?: AnswerType;
} }
const TrueFalseQuestionDisplay: React.FC<Props> = (props) => { const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = const { question,
// showAnswer,
handleOnSubmitAnswer, passedAnswer } =
props; props;
const [answer, setAnswer] = useState<boolean | undefined>(() => { const [answer, setAnswer] = useState<boolean | undefined>(() => {
if (passedAnswer && (passedAnswer[0] === true || passedAnswer[0] === false)) { if (passedAnswer && (passedAnswer[0] === true || passedAnswer[0] === false)) {
return passedAnswer[0]; return passedAnswer[0];
} }
return undefined; return undefined;
}); });
@ -31,6 +32,8 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
disableButton = true; disableButton = true;
} }
useEffect(() => { useEffect(() => {
console.log("passedAnswer", passedAnswer); console.log("passedAnswer", passedAnswer);
if (passedAnswer && (passedAnswer[0] === true || passedAnswer[0] === false)) { if (passedAnswer && (passedAnswer[0] === true || passedAnswer[0] === false)) {
@ -46,10 +49,17 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
const selectedTrue = answer ? 'selected' : ''; const selectedTrue = answer ? 'selected' : '';
const selectedFalse = answer !== undefined && !answer ? 'selected' : ''; const selectedFalse = answer !== undefined && !answer ? 'selected' : '';
return ( return (
<QuizContext.Consumer>
{({ showAnswer }) => (
<div className="question-container"> <div className="question-container">
<div className="question content"> <div className="question content">
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} /> <div
dangerouslySetInnerHTML={{
__html: FormattedTextTemplate(question.formattedStem),
}}
/>
</div> </div>
<div className="choices-wrapper mb-1"> <div className="choices-wrapper mb-1">
<Button <Button
@ -58,13 +68,23 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
fullWidth fullWidth
disabled={disableButton} disabled={disableButton}
> >
{showAnswer ? (<div> {(question.isTrue ? '✅' : '❌')}</div>) : ``} {showAnswer ? (
<div> {question.isTrue ? '✅' : '❌'}</div>
) : (
''
)}
<div className={`circle ${selectedTrue}`}>V</div> <div className={`circle ${selectedTrue}`}>V</div>
<div className={`answer-text ${selectedTrue}`}>Vrai</div> <div className={`answer-text ${selectedTrue}`}>Vrai</div>
{showAnswer && answer && question.trueFormattedFeedback && ( {showAnswer && answer && question.trueFormattedFeedback && (
<div className="true-feedback mb-2"> <div className="true-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.trueFormattedFeedback) }} /> <div
dangerouslySetInnerHTML={{
__html: FormattedTextTemplate(
question.trueFormattedFeedback
),
}}
/>
</div> </div>
)} )}
</Button> </Button>
@ -73,29 +93,46 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
onClick={() => !showAnswer && handleOnClickAnswer(false)} onClick={() => !showAnswer && handleOnClickAnswer(false)}
fullWidth fullWidth
disabled={disableButton} disabled={disableButton}
> >
{showAnswer ? (<div> {(!question.isTrue ? '✅' : '❌')}</div>) : ``} {showAnswer ? (
<div> {!question.isTrue ? '✅' : '❌'}</div>
) : (
''
)}
<div className={`circle ${selectedFalse}`}>F</div> <div className={`circle ${selectedFalse}`}>F</div>
<div className={`answer-text ${selectedFalse}`}>Faux</div> <div className={`answer-text ${selectedFalse}`}>Faux</div>
{showAnswer && !answer && question.falseFormattedFeedback && ( {showAnswer && !answer && question.falseFormattedFeedback && (
<div className="false-feedback mb-2"> <div className="false-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.falseFormattedFeedback) }} /> <div
dangerouslySetInnerHTML={{
__html: FormattedTextTemplate(
question.falseFormattedFeedback
),
}}
/>
</div> </div>
)} )}
</Button> </Button>
</div> </div>
{question.formattedGlobalFeedback && showAnswer && ( {question.formattedGlobalFeedback && showAnswer && (
<div className="global-feedback mb-2"> <div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} /> <div
dangerouslySetInnerHTML={{
__html: FormattedTextTemplate(
question.formattedGlobalFeedback
),
}}
/>
</div> </div>
)} )}
{!showAnswer && handleOnSubmitAnswer && ( {!showAnswer && handleOnSubmitAnswer && (
<Button <Button
variant="contained" variant="contained"
onClick={() => onClick={() =>
answer !== undefined && handleOnSubmitAnswer && handleOnSubmitAnswer([answer]) answer !== undefined &&
handleOnSubmitAnswer &&
handleOnSubmitAnswer([answer])
} }
disabled={answer === undefined} disabled={answer === undefined}
> >
@ -103,6 +140,8 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
</Button> </Button>
)} )}
</div> </div>
)}
</QuizContext.Consumer>
); );
}; };

View file

@ -4,11 +4,11 @@ import QuestionComponent from '../QuestionsDisplay/QuestionDisplay';
import '../../pages/Student/JoinRoom/joinRoom.css'; import '../../pages/Student/JoinRoom/joinRoom.css';
import { QuestionType } from '../../Types/QuestionType'; import { QuestionType } from '../../Types/QuestionType';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
//import QuestionNavigation from '../QuestionNavigation/QuestionNavigation';
import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
import { Question } from 'gift-pegjs'; import { Question } from 'gift-pegjs';
import { AnswerSubmissionToBackendType } from 'src/services/WebsocketService'; import { AnswerSubmissionToBackendType } from 'src/services/WebsocketService';
import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom'; import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
import { useQuizContext } from 'src/pages/Student/JoinRoom/QuizContext';
interface StudentModeQuizProps { interface StudentModeQuizProps {
questions: QuestionType[]; questions: QuestionType[];
@ -23,21 +23,21 @@ const StudentModeQuiz: React.FC<StudentModeQuizProps> = ({
submitAnswer, submitAnswer,
disconnectWebSocket disconnectWebSocket
}) => { }) => {
//Ajouter type AnswerQuestionType en remplacement de QuestionType const { setShowAnswer } = useQuizContext(); // Access setShowAnswer from context
const [questionInfos, setQuestion] = useState<QuestionType>(questions[0]); const [questionInfos, setQuestion] = useState<QuestionType>(questions[0]);
const [isAnswerSubmitted, setIsAnswerSubmitted] = useState(false); const [isAnswerSubmitted, setIsAnswerSubmitted] = useState(false);
// const [answer, setAnswer] = useState<AnswerType>('');
const previousQuestion = () => { const previousQuestion = () => {
setQuestion(questions[Number(questionInfos.question?.id) - 2]); setQuestion(questions[Number(questionInfos.question?.id) - 2]);
}; };
useEffect(() => { useEffect(() => {
const savedAnswer = answers[Number(questionInfos.question.id)-1]?.answer; const savedAnswer = answers[Number(questionInfos.question.id) - 1]?.answer;
console.log(`StudentModeQuiz: useEffect: savedAnswer: ${savedAnswer}`); console.log(`StudentModeQuiz: useEffect: savedAnswer: ${savedAnswer}`);
setIsAnswerSubmitted(savedAnswer !== undefined); setIsAnswerSubmitted(savedAnswer !== undefined);
}, [questionInfos.question, answers]); setShowAnswer(savedAnswer !== undefined); // Update showAnswer in context
}, [questionInfos.question, answers, setShowAnswer]);
const nextQuestion = () => { const nextQuestion = () => {
setQuestion(questions[Number(questionInfos.question?.id)]); setQuestion(questions[Number(questionInfos.question?.id)]);
@ -47,34 +47,27 @@ const StudentModeQuiz: React.FC<StudentModeQuizProps> = ({
const idQuestion = Number(questionInfos.question.id) || -1; const idQuestion = Number(questionInfos.question.id) || -1;
submitAnswer(answer, idQuestion); submitAnswer(answer, idQuestion);
setIsAnswerSubmitted(true); setIsAnswerSubmitted(true);
setShowAnswer(true); // Update showAnswer in context when an answer is submitted
}; };
return ( return (
<div className='room'> <div className="room">
<div className='roomHeader'> <div className="roomHeader">
<DisconnectButton <DisconnectButton
onReturn={disconnectWebSocket} onReturn={disconnectWebSocket}
message={`Êtes-vous sûr de vouloir quitter?`} /> message={`Êtes-vous sûr de vouloir quitter?`}
/>
</div> </div>
<div > <div>
<b>Question {questionInfos.question.id}/{questions.length}</b> <b>Question {questionInfos.question.id}/{questions.length}</b>
</div> </div>
<div className="overflow-auto"> <div className="overflow-auto">
<div className="question-component-container"> <div className="question-component-container">
<div className="mb-5">
{/* <QuestionNavigation
currentQuestionId={Number(questionInfos.question.id)}
questionsLength={questions.length}
previousQuestion={previousQuestion}
nextQuestion={nextQuestion}
/> */}
</div>
<QuestionComponent <QuestionComponent
handleOnSubmitAnswer={handleOnSubmitAnswer} handleOnSubmitAnswer={handleOnSubmitAnswer}
question={questionInfos.question as Question} question={questionInfos.question as Question}
showAnswer={isAnswerSubmitted} showAnswer={isAnswerSubmitted} // Local state for showing answers
answer={answers[Number(questionInfos.question.id)-1]?.answer} answer={answers[Number(questionInfos.question.id) - 1]?.answer}
/> />
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', marginTop: '1rem' }}> <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center', marginTop: '1rem' }}>
<div> <div>

View file

@ -16,6 +16,7 @@ import LoadingButton from '@mui/lab/LoadingButton';
import LoginContainer from 'src/components/LoginContainer/LoginContainer' import LoginContainer from 'src/components/LoginContainer/LoginContainer'
import ApiService from '../../../services/ApiService' import ApiService from '../../../services/ApiService'
import { QuizProvider } from './QuizProvider';
export type AnswerType = Array<string | number | boolean>; export type AnswerType = Array<string | number | boolean>;
@ -177,12 +178,14 @@ const JoinRoom: React.FC = () => {
switch (quizMode) { switch (quizMode) {
case 'student': case 'student':
return ( return (
<QuizProvider>
<StudentModeQuiz <StudentModeQuiz
questions={questions} questions={questions}
answers={answers} answers={answers}
submitAnswer={handleOnSubmitAnswer} submitAnswer={handleOnSubmitAnswer}
disconnectWebSocket={disconnect} disconnectWebSocket={disconnect}
/> />
</QuizProvider>
); );
case 'teacher': case 'teacher':
return ( return (

View file

@ -0,0 +1,18 @@
import React, { Dispatch, SetStateAction, useContext } from 'react';
export const QuizContext = React.createContext<{
showAnswer: boolean;
setShowAnswer: Dispatch<SetStateAction<boolean>>;
}>({
showAnswer: false,
setShowAnswer: () => {},
});
export const useQuizContext = () => {
const context = useContext(QuizContext);
if (!context) {
throw new Error('useQuizContext must be used within a QuizProvider');
}
return context;
};

View file

@ -0,0 +1,19 @@
import React, { useEffect, useState } from 'react';
import { QuizContext } from './QuizContext';
export const QuizProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
// State for showing answers
const [showAnswer, setShowAnswer] = useState(false);
console.log('QuizProvider: showAnswer:', showAnswer);
useEffect(() => {
console.log('QuizProvider: showAnswer:', showAnswer);
}, [showAnswer]);
return (
<QuizContext.Provider value={{ showAnswer, setShowAnswer }}>
{children}
</QuizContext.Provider>
);
};

199
server/package-lock.json generated
View file

@ -55,68 +55,20 @@
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.22.13", "version": "7.26.2",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz",
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==", "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/highlight": "^7.22.13", "@babel/helper-validator-identifier": "^7.25.9",
"chalk": "^2.4.2" "js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/code-frame/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/code-frame/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/code-frame/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/@babel/code-frame/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"node_modules/@babel/code-frame/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@babel/compat-data": { "node_modules/@babel/compat-data": {
"version": "7.23.3", "version": "7.23.3",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.3.tgz",
@ -342,19 +294,21 @@
} }
}, },
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
"version": "7.22.5", "version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
"integrity": "sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==", "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.22.20", "version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@ -369,88 +323,28 @@
} }
}, },
"node_modules/@babel/helpers": { "node_modules/@babel/helpers": {
"version": "7.23.2", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.2.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz",
"integrity": "sha512-lzchcp8SjTSVe/fPmLwtWVBFC7+Tbn8LGHDVfDp9JGxpAY5opSaEFgt8UQvrnECWOTdji2mOWMz1rOhkHscmGQ==", "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/template": "^7.22.15", "@babel/template": "^7.27.0",
"@babel/traverse": "^7.23.2", "@babel/types": "^7.27.0"
"@babel/types": "^7.23.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/highlight": {
"version": "7.22.20",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.22.20",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/highlight/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/highlight/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/@babel/highlight/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"node_modules/@babel/highlight/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.23.3", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.3.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz",
"integrity": "sha512-uVsWNvlVsIninV2prNz/3lHCb+5CJ+e+IUBfbjToAHODtfGYLfCFuY4AU7TskI+dAKk+njsPiBjq1gKTvZOBaw==", "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.0"
},
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
}, },
@ -636,14 +530,15 @@
} }
}, },
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.22.15", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz",
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==", "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.22.13", "@babel/code-frame": "^7.26.2",
"@babel/parser": "^7.22.15", "@babel/parser": "^7.27.0",
"@babel/types": "^7.22.15" "@babel/types": "^7.27.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@ -704,14 +599,14 @@
"dev": true "dev": true
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.23.3", "version": "7.27.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.3.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz",
"integrity": "sha512-OZnvoH2l8PK5eUvEcUyCt/sXgr/h+UWpVuBbOljwcrAgUl6lpchoQ++PHGyQy1AtYnVA6CEq3y5xeEI10brpXw==", "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.22.5", "@babel/helper-string-parser": "^7.25.9",
"@babel/helper-validator-identifier": "^7.22.20", "@babel/helper-validator-identifier": "^7.25.9"
"to-fast-properties": "^2.0.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@ -4772,7 +4667,8 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"dev": true "dev": true,
"license": "MIT"
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "3.14.1", "version": "3.14.1",
@ -6683,15 +6579,6 @@
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
"dev": true "dev": true
}, },
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",

View file

@ -42,7 +42,7 @@
"supertest": "^6.3.4" "supertest": "^6.3.4"
}, },
"engines": { "engines": {
"node": "20.x" "node": "22.x"
}, },
"jest": { "jest": {
"testEnvironment": "node", "testEnvironment": "node",