mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Merge branch 'main' into JubaAzul/issue295
This commit is contained in:
commit
886b8125d3
25 changed files with 903 additions and 284 deletions
|
|
@ -19,8 +19,8 @@ const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) =>
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockStudents: StudentType[] = [
|
const mockStudents: StudentType[] = [
|
||||||
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] },
|
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: ['Answer 1'], isCorrect: true }] },
|
||||||
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] },
|
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: ['Answer 2'], isCorrect: false }] },
|
||||||
];
|
];
|
||||||
|
|
||||||
const mockShowSelectedQuestion = jest.fn();
|
const mockShowSelectedQuestion = jest.fn();
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) =>
|
||||||
|
|
||||||
|
|
||||||
const mockStudents: StudentType[] = [
|
const mockStudents: StudentType[] = [
|
||||||
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] },
|
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: ['Answer 1'], isCorrect: true }] },
|
||||||
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] },
|
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: ['Answer 2'], isCorrect: false }] },
|
||||||
];
|
];
|
||||||
|
|
||||||
const mockShowSelectedQuestion = jest.fn();
|
const mockShowSelectedQuestion = jest.fn();
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) =>
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockStudents: StudentType[] = [
|
const mockStudents: StudentType[] = [
|
||||||
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] },
|
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: ['Answer 1'], isCorrect: true }] },
|
||||||
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] },
|
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: ['Answer 2'], isCorrect: false }] },
|
||||||
];
|
];
|
||||||
|
|
||||||
const mockGetStudentGrade = jest.fn((student: StudentType) => {
|
const mockGetStudentGrade = jest.fn((student: StudentType) => {
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import LiveResultsTableFooter from 'src/components/LiveResults/LiveResultsTable/
|
||||||
|
|
||||||
|
|
||||||
const mockStudents: StudentType[] = [
|
const mockStudents: StudentType[] = [
|
||||||
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Answer 1', isCorrect: true }] },
|
{ id: "1", name: 'Student 1', answers: [{ idQuestion: 1, answer: ['Answer 1'], isCorrect: true }] },
|
||||||
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: 'Answer 2', isCorrect: false }] },
|
{ id: "2", name: 'Student 2', answers: [{ idQuestion: 2, answer: ['Answer 2'], isCorrect: false }] },
|
||||||
];
|
];
|
||||||
|
|
||||||
const mockGetStudentGrade = jest.fn((student: StudentType) => {
|
const mockGetStudentGrade = jest.fn((student: StudentType) => {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ const katekMock: TemplateOptions & MultipleChoiceQuestion = {
|
||||||
formattedStem: { format: 'plain' , text: '$$\\frac{zzz}{yyy}$$'},
|
formattedStem: { format: 'plain' , text: '$$\\frac{zzz}{yyy}$$'},
|
||||||
choices: [
|
choices: [
|
||||||
{ formattedText: { format: 'plain' , text: 'Choice 1'}, isCorrect: true, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 1 },
|
{ formattedText: { format: 'plain' , text: 'Choice 1'}, isCorrect: true, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 1 },
|
||||||
{ formattedText: { format: 'plain', text: 'Choice 2' }, isCorrect: true, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }
|
{ formattedText: { format: 'plain', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 0 }
|
||||||
],
|
],
|
||||||
formattedGlobalFeedback: { format: 'plain', text: 'Sample Global Feedback' }
|
formattedGlobalFeedback: { format: 'plain', text: 'Sample Global Feedback' }
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -733,7 +733,7 @@ exports[`MultipleChoice snapshot test with katex 1`] = `
|
||||||
|
|
||||||
<div class='multiple-choice-answers-container'>
|
<div class='multiple-choice-answers-container'>
|
||||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
|
||||||
<label style="
|
<label style="
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0.2em 0 0.2em 0;
|
padding: 0.2em 0 0.2em 0;
|
||||||
|
|
@ -742,15 +742,15 @@ exports[`MultipleChoice snapshot test with katex 1`] = `
|
||||||
" for="idmocked-id">
|
" for="idmocked-id">
|
||||||
Choice 2
|
Choice 2
|
||||||
</label>
|
</label>
|
||||||
<svg data-testid="correct-icon" style="
|
<svg data-testid="incorrect-icon" style="
|
||||||
vertical-align: text-bottom;
|
vertical-align: text-bottom;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
margin-left: 0.1rem;
|
margin-left: 0.1rem;
|
||||||
margin-right: 0.2rem;
|
margin-right: 0.2rem;
|
||||||
|
|
||||||
width: 1em;
|
width: 0.75em;
|
||||||
color: hsl(120, 39%, 54%);
|
color: hsl(2, 64%, 58%);
|
||||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>
|
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
|
||||||
<span class="feedback-container">Correct!</span>
|
<span class="feedback-container">Correct!</span>
|
||||||
</input>
|
</input>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import LiveResults from 'src/components/LiveResults/LiveResults';
|
||||||
import { QuestionType } from 'src/Types/QuestionType';
|
import { QuestionType } from 'src/Types/QuestionType';
|
||||||
import { StudentType } from 'src/Types/StudentType';
|
import { StudentType } from 'src/Types/StudentType';
|
||||||
import { Socket } from 'socket.io-client';
|
import { Socket } from 'socket.io-client';
|
||||||
import { BaseQuestion,parse } from 'gift-pegjs';
|
import { BaseQuestion, parse } from 'gift-pegjs';
|
||||||
|
|
||||||
const mockSocket: Socket = {
|
const mockSocket: Socket = {
|
||||||
on: jest.fn(),
|
on: jest.fn(),
|
||||||
|
|
@ -19,19 +19,28 @@ const mockGiftQuestions = parse(
|
||||||
`::Sample Question 1:: Question stem
|
`::Sample Question 1:: Question stem
|
||||||
{
|
{
|
||||||
=Choice 1
|
=Choice 1
|
||||||
~Choice 2
|
=Choice 2
|
||||||
}`);
|
~Choice 3
|
||||||
|
~Choice 4
|
||||||
|
}
|
||||||
|
|
||||||
|
::Sample Question 2:: Question stem {TRUE}
|
||||||
|
`);
|
||||||
|
|
||||||
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 };
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(`mockQuestions: ${JSON.stringify(mockQuestions)}`);
|
||||||
|
|
||||||
|
// each student should have a different score for the tests to pass
|
||||||
const mockStudents: StudentType[] = [
|
const mockStudents: StudentType[] = [
|
||||||
{ id: '1', name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Choice 1', isCorrect: true }] },
|
{ id: '1', name: 'Student 1', answers: [] },
|
||||||
{ id: '2', name: 'Student 2', answers: [{ idQuestion: 1, answer: 'Choice 2', isCorrect: false }] },
|
{ id: '2', name: 'Student 2', answers: [{ idQuestion: 1, answer: ['Choice 3'], isCorrect: false }, { idQuestion: 2, answer: [true], isCorrect: true}] },
|
||||||
|
{ id: '3', name: 'Student 3', answers: [{ idQuestion: 1, answer: ['Choice 1', 'Choice 2'], isCorrect: true }, { idQuestion: 2, answer: [true], isCorrect: true}] },
|
||||||
];
|
];
|
||||||
|
|
||||||
describe('LiveResults', () => {
|
describe('LiveResults', () => {
|
||||||
|
|
@ -82,82 +91,88 @@ describe('LiveResults', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
test('calculates and displays the correct student grades', () => {
|
||||||
test('calculates and displays the correct student grades', () => {
|
render(
|
||||||
render(
|
<LiveResults
|
||||||
<LiveResults
|
socket={mockSocket}
|
||||||
socket={mockSocket}
|
questions={mockQuestions}
|
||||||
questions={mockQuestions}
|
showSelectedQuestion={jest.fn()}
|
||||||
showSelectedQuestion={jest.fn()}
|
quizMode="teacher"
|
||||||
quizMode="teacher"
|
students={mockStudents}
|
||||||
students={mockStudents}
|
/>
|
||||||
/>
|
);
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
// Toggle the display of usernames
|
// Toggle the display of usernames
|
||||||
const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms');
|
const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms');
|
||||||
|
|
||||||
// Toggle the display of usernames back
|
// Toggle the display of usernames back
|
||||||
fireEvent.click(toggleUsernamesSwitch);
|
fireEvent.click(toggleUsernamesSwitch);
|
||||||
|
|
||||||
// Check if the student grades are calculated and displayed correctly
|
// Check if the student grades are calculated and displayed correctly
|
||||||
mockStudents.forEach((student) => {
|
const getByTextInTableCellBody = (text: string) => {
|
||||||
const grade = student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100;
|
const elements = screen.getAllByText(text); // Get all elements with the specified text
|
||||||
expect(screen.getByText(`${grade.toFixed()} %`)).toBeInTheDocument();
|
return elements.find((element) => element.closest('.MuiTableCell-body')); // don't get the footer element(s)
|
||||||
|
};
|
||||||
|
mockStudents.forEach((student) => {
|
||||||
|
const grade = student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100;
|
||||||
|
const element = getByTextInTableCellBody(`${grade.toFixed()} %`);
|
||||||
|
expect(element).toBeInTheDocument();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
test('calculates and displays the class average', () => {
|
test('calculates and displays the class average', () => {
|
||||||
render(
|
render(
|
||||||
<LiveResults
|
<LiveResults
|
||||||
socket={mockSocket}
|
socket={mockSocket}
|
||||||
questions={mockQuestions}
|
questions={mockQuestions}
|
||||||
showSelectedQuestion={jest.fn()}
|
showSelectedQuestion={jest.fn()}
|
||||||
quizMode="teacher"
|
quizMode="teacher"
|
||||||
students={mockStudents}
|
students={mockStudents}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
// Toggle the display of usernames
|
// Toggle the display of usernames
|
||||||
const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms');
|
const toggleUsernamesSwitch = screen.getByLabelText('Afficher les noms');
|
||||||
|
|
||||||
// Toggle the display of usernames back
|
// Toggle the display of usernames back
|
||||||
fireEvent.click(toggleUsernamesSwitch);
|
fireEvent.click(toggleUsernamesSwitch);
|
||||||
|
|
||||||
// Calculate the class average
|
// Calculate the class average
|
||||||
const totalGrades = mockStudents.reduce((total, student) => {
|
const totalGrades = mockStudents.reduce((total, student) => {
|
||||||
return total + (student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100);
|
return total + (student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100);
|
||||||
}, 0);
|
}, 0);
|
||||||
const classAverage = totalGrades / mockStudents.length;
|
const classAverage = totalGrades / mockStudents.length;
|
||||||
|
|
||||||
// Check if the class average is displayed correctly
|
// Check if the class average is displayed correctly
|
||||||
const classAverageElements = screen.getAllByText(`${classAverage.toFixed()} %`);
|
const classAverageElements = screen.getAllByText(`${classAverage.toFixed()} %`);
|
||||||
const classAverageElement = classAverageElements.find((element) => {
|
const classAverageElement = classAverageElements.find((element) => {
|
||||||
return element.closest('td')?.classList.contains('MuiTableCell-footer');
|
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();
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
@ -12,14 +12,23 @@ const questions = parse(
|
||||||
{
|
{
|
||||||
=Choice 1
|
=Choice 1
|
||||||
~Choice 2
|
~Choice 2
|
||||||
}`) as MultipleChoiceQuestion[];
|
}
|
||||||
|
|
||||||
const question = questions[0];
|
::Sample Question 2:: Question stem
|
||||||
|
{
|
||||||
|
=Choice 1
|
||||||
|
=Choice 2
|
||||||
|
~Choice 3
|
||||||
|
}
|
||||||
|
`) as MultipleChoiceQuestion[];
|
||||||
|
|
||||||
|
const questionWithOneCorrectChoice = questions[0];
|
||||||
|
const questionWithMultipleCorrectChoices = questions[1];
|
||||||
|
|
||||||
describe('MultipleChoiceQuestionDisplay', () => {
|
describe('MultipleChoiceQuestionDisplay', () => {
|
||||||
const mockHandleOnSubmitAnswer = jest.fn();
|
const mockHandleOnSubmitAnswer = jest.fn();
|
||||||
|
|
||||||
const TestWrapper = ({ showAnswer }: { showAnswer: boolean }) => {
|
const TestWrapper = ({ showAnswer, question }: { showAnswer: boolean; question: MultipleChoiceQuestion }) => {
|
||||||
const [showAnswerState, setShowAnswerState] = useState(showAnswer);
|
const [showAnswerState, setShowAnswerState] = useState(showAnswer);
|
||||||
|
|
||||||
const handleOnSubmitAnswer = (answer: AnswerType) => {
|
const handleOnSubmitAnswer = (answer: AnswerType) => {
|
||||||
|
|
@ -38,28 +47,51 @@ describe('MultipleChoiceQuestionDisplay', () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const choices = question.choices;
|
const twoChoices = questionWithOneCorrectChoice.choices;
|
||||||
|
const threeChoices = questionWithMultipleCorrectChoices.choices;
|
||||||
|
|
||||||
beforeEach(() => {
|
test('renders a question (that has only one correct choice) and its choices', () => {
|
||||||
render(<TestWrapper showAnswer={false} />);
|
render(<TestWrapper showAnswer={false} question={questionWithOneCorrectChoice} />);
|
||||||
});
|
|
||||||
|
|
||||||
test('renders the question and choices', () => {
|
expect(screen.getByText(questionWithOneCorrectChoice.formattedStem.text)).toBeInTheDocument();
|
||||||
expect(screen.getByText(question.formattedStem.text)).toBeInTheDocument();
|
twoChoices.forEach((choice) => {
|
||||||
choices.forEach((choice) => {
|
|
||||||
expect(screen.getByText(choice.formattedText.text)).toBeInTheDocument();
|
expect(screen.getByText(choice.formattedText.text)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('only allows one choice to be selected when question only has one correct answer', () => {
|
||||||
|
render(<TestWrapper showAnswer={false} question={questionWithOneCorrectChoice} />);
|
||||||
|
|
||||||
|
const choiceButton1 = screen.getByText('Choice 1').closest('button');
|
||||||
|
const choiceButton2 = screen.getByText('Choice 2').closest('button');
|
||||||
|
|
||||||
|
if (!choiceButton1 || !choiceButton2) throw new Error('Choice buttons not found');
|
||||||
|
|
||||||
|
// Simulate selecting multiple answers
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(choiceButton1);
|
||||||
|
});
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(choiceButton2);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify that only the last answer is selected
|
||||||
|
expect(choiceButton1.querySelector('.answer-text.selected')).not.toBeInTheDocument();
|
||||||
|
expect(choiceButton2.querySelector('.answer-text.selected')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
test('does not submit when no answer is selected', () => {
|
test('does not submit when no answer is selected', () => {
|
||||||
|
render(<TestWrapper showAnswer={false} question={questionWithOneCorrectChoice} />);
|
||||||
const submitButton = screen.getByText('Répondre');
|
const submitButton = screen.getByText('Répondre');
|
||||||
act(() => {
|
act(() => {
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
});
|
});
|
||||||
expect(mockHandleOnSubmitAnswer).not.toHaveBeenCalled();
|
expect(mockHandleOnSubmitAnswer).not.toHaveBeenCalled();
|
||||||
|
mockHandleOnSubmitAnswer.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('submits the selected answer', () => {
|
test('submits the selected answer', () => {
|
||||||
|
render(<TestWrapper showAnswer={false} question={questionWithOneCorrectChoice} />);
|
||||||
const choiceButton = screen.getByText('Choice 1').closest('button');
|
const choiceButton = screen.getByText('Choice 1').closest('button');
|
||||||
if (!choiceButton) throw new Error('Choice button not found');
|
if (!choiceButton) throw new Error('Choice button not found');
|
||||||
act(() => {
|
act(() => {
|
||||||
|
|
@ -70,10 +102,68 @@ describe('MultipleChoiceQuestionDisplay', () => {
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith('Choice 1');
|
expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith(['Choice 1']);
|
||||||
|
mockHandleOnSubmitAnswer.mockClear();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('renders a question (that has multiple correct choices) and its choices', () => {
|
||||||
|
render(<TestWrapper showAnswer={false} question={questionWithMultipleCorrectChoices} />);
|
||||||
|
expect(screen.getByText(questionWithMultipleCorrectChoices.formattedStem.text)).toBeInTheDocument();
|
||||||
|
threeChoices.forEach((choice) => {
|
||||||
|
expect(screen.getByText(choice.formattedText.text)).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('allows multiple choices to be selected when question has multiple correct answers', () => {
|
||||||
|
render(<TestWrapper showAnswer={false} question={questionWithMultipleCorrectChoices} />);
|
||||||
|
const choiceButton1 = screen.getByText('Choice 1').closest('button');
|
||||||
|
const choiceButton2 = screen.getByText('Choice 2').closest('button');
|
||||||
|
const choiceButton3 = screen.getByText('Choice 3').closest('button');
|
||||||
|
|
||||||
|
if (!choiceButton1 || !choiceButton2 || !choiceButton3) throw new Error('Choice buttons not found');
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(choiceButton1);
|
||||||
|
});
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(choiceButton2);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(choiceButton1.querySelector('.answer-text.selected')).toBeInTheDocument();
|
||||||
|
expect(choiceButton2.querySelector('.answer-text.selected')).toBeInTheDocument();
|
||||||
|
expect(choiceButton3.querySelector('.answer-text.selected')).not.toBeInTheDocument(); // didn't click
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
test('submits multiple selected answers', () => {
|
||||||
|
render(<TestWrapper showAnswer={false} question={questionWithMultipleCorrectChoices} />);
|
||||||
|
const choiceButton1 = screen.getByText('Choice 1').closest('button');
|
||||||
|
const choiceButton2 = screen.getByText('Choice 2').closest('button');
|
||||||
|
|
||||||
|
if (!choiceButton1 || !choiceButton2) throw new Error('Choice buttons not found');
|
||||||
|
|
||||||
|
// Simulate selecting multiple answers
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(choiceButton1);
|
||||||
|
});
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(choiceButton2);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Simulate submitting the answers
|
||||||
|
const submitButton = screen.getByText('Répondre');
|
||||||
|
act(() => {
|
||||||
|
fireEvent.click(submitButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Verify that the mockHandleOnSubmitAnswer function is called with both answers
|
||||||
|
expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith(['Choice 1', 'Choice 2']);
|
||||||
|
mockHandleOnSubmitAnswer.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 () => {
|
||||||
|
render(<TestWrapper showAnswer={false} question={questionWithOneCorrectChoice} />);
|
||||||
const choiceButton = screen.getByText('Choice 1').closest('button');
|
const choiceButton = screen.getByText('Choice 1').closest('button');
|
||||||
if (!choiceButton) throw new Error('Choice button not found');
|
if (!choiceButton) throw new Error('Choice button not found');
|
||||||
|
|
||||||
|
|
@ -89,16 +179,17 @@ describe('MultipleChoiceQuestionDisplay', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Wait for the DOM to update
|
// Wait for the DOM to update
|
||||||
const correctAnswer = screen.getByText("Choice 1").closest('button');
|
const correctAnswer = screen.getByText("Choice 1").closest('button');
|
||||||
expect(correctAnswer).toBeInTheDocument();
|
expect(correctAnswer).toBeInTheDocument();
|
||||||
expect(correctAnswer?.textContent).toContain('✅');
|
expect(correctAnswer?.textContent).toContain('✅');
|
||||||
|
|
||||||
const wrongAnswer1 = screen.getByText("Choice 2").closest('button');
|
const wrongAnswer1 = screen.getByText("Choice 2").closest('button');
|
||||||
expect(wrongAnswer1).toBeInTheDocument();
|
expect(wrongAnswer1).toBeInTheDocument();
|
||||||
expect(wrongAnswer1?.textContent).toContain('❌');
|
expect(wrongAnswer1?.textContent).toContain('❌');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not show ✅ or ❌ when repondre button is not clicked', async () => {
|
it('should not show ✅ or ❌ when Répondre button is not clicked', async () => {
|
||||||
|
render(<TestWrapper showAnswer={false} question={questionWithOneCorrectChoice} />);
|
||||||
const choiceButton = screen.getByText('Choice 1').closest('button');
|
const choiceButton = screen.getByText('Choice 1').closest('button');
|
||||||
if (!choiceButton) throw new Error('Choice button not found');
|
if (!choiceButton) throw new Error('Choice button not found');
|
||||||
|
|
||||||
|
|
@ -118,5 +209,5 @@ describe('MultipleChoiceQuestionDisplay', () => {
|
||||||
expect(wrongAnswer1?.textContent).not.toContain('❌');
|
expect(wrongAnswer1?.textContent).not.toContain('❌');
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ describe('NumericalQuestion Component', () => {
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
expect(mockHandleOnSubmitAnswer).not.toHaveBeenCalled();
|
expect(mockHandleOnSubmitAnswer).not.toHaveBeenCalled();
|
||||||
|
mockHandleOnSubmitAnswer.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('submits answer correctly', () => {
|
it('submits answer correctly', () => {
|
||||||
|
|
@ -77,6 +78,7 @@ describe('NumericalQuestion Component', () => {
|
||||||
|
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith(7);
|
expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith([7]);
|
||||||
|
mockHandleOnSubmitAnswer.mockClear();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -29,23 +29,24 @@ describe('Questions Component', () => {
|
||||||
render(<QuestionDisplay question={question} {...sampleProps} />);
|
render(<QuestionDisplay question={question} {...sampleProps} />);
|
||||||
};
|
};
|
||||||
|
|
||||||
describe('question type parsing', () => {
|
// describe('question type parsing', () => {
|
||||||
it('parses true/false question type correctly', () => {
|
// it('parses true/false question type correctly', () => {
|
||||||
expect(sampleTrueFalseQuestion.type).toBe('TF');
|
// expect(sampleTrueFalseQuestion.type).toBe('TF');
|
||||||
});
|
// });
|
||||||
|
|
||||||
it('parses multiple choice question type correctly', () => {
|
// it('parses multiple choice question type correctly', () => {
|
||||||
expect(sampleMultipleChoiceQuestion.type).toBe('MC');
|
// expect(sampleMultipleChoiceQuestion.type).toBe('MC');
|
||||||
});
|
// });
|
||||||
|
|
||||||
it('parses numerical question type correctly', () => {
|
// it('parses numerical question type correctly', () => {
|
||||||
expect(sampleNumericalQuestion.type).toBe('Numerical');
|
// expect(sampleNumericalQuestion.type).toBe('Numerical');
|
||||||
});
|
// });
|
||||||
|
|
||||||
|
// it('parses short answer question type correctly', () => {
|
||||||
|
// expect(sampleShortAnswerQuestion.type).toBe('Short');
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
it('parses short answer question type correctly', () => {
|
|
||||||
expect(sampleShortAnswerQuestion.type).toBe('Short');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
it('renders correctly for True/False question', () => {
|
it('renders correctly for True/False question', () => {
|
||||||
renderComponent(sampleTrueFalseQuestion);
|
renderComponent(sampleTrueFalseQuestion);
|
||||||
|
|
||||||
|
|
@ -73,7 +74,8 @@ describe('Questions Component', () => {
|
||||||
const submitButton = screen.getByText('Répondre');
|
const submitButton = screen.getByText('Répondre');
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith('Choice 1');
|
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(['Choice 1']);
|
||||||
|
mockHandleSubmitAnswer.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correctly for Numerical question', () => {
|
it('renders correctly for Numerical question', () => {
|
||||||
|
|
@ -93,7 +95,8 @@ describe('Questions Component', () => {
|
||||||
const submitButton = screen.getByText('Répondre');
|
const submitButton = screen.getByText('Répondre');
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(7);
|
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith([7]);
|
||||||
|
mockHandleSubmitAnswer.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders correctly for Short Answer question', () => {
|
it('renders correctly for Short Answer question', () => {
|
||||||
|
|
@ -117,7 +120,7 @@ describe('Questions Component', () => {
|
||||||
const submitButton = screen.getByText('Répondre');
|
const submitButton = screen.getByText('Répondre');
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith('User Input');
|
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(['User Input']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ describe('ShortAnswerQuestion Component', () => {
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
expect(mockHandleSubmitAnswer).not.toHaveBeenCalled();
|
expect(mockHandleSubmitAnswer).not.toHaveBeenCalled();
|
||||||
|
mockHandleSubmitAnswer.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('submits answer correctly', () => {
|
it('submits answer correctly', () => {
|
||||||
|
|
@ -60,6 +61,7 @@ describe('ShortAnswerQuestion Component', () => {
|
||||||
|
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
|
|
||||||
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith('User Input');
|
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(['User Input']);
|
||||||
|
mockHandleSubmitAnswer.mockClear();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ describe('TrueFalseQuestion Component', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockHandleSubmitAnswer).not.toHaveBeenCalled();
|
expect(mockHandleSubmitAnswer).not.toHaveBeenCalled();
|
||||||
|
mockHandleSubmitAnswer.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('submits answer correctly for True', () => {
|
it('submits answer correctly for True', () => {
|
||||||
|
|
@ -70,7 +71,8 @@ describe('TrueFalseQuestion Component', () => {
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(true);
|
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith([true]);
|
||||||
|
mockHandleSubmitAnswer.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('submits answer correctly for False', () => {
|
it('submits answer correctly for False', () => {
|
||||||
|
|
@ -83,7 +85,8 @@ describe('TrueFalseQuestion Component', () => {
|
||||||
fireEvent.click(submitButton);
|
fireEvent.click(submitButton);
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(false);
|
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith([false]);
|
||||||
|
mockHandleSubmitAnswer.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -112,7 +115,7 @@ describe('TrueFalseQuestion Component', () => {
|
||||||
expect(wrongAnswer1?.textContent).toContain('❌');
|
expect(wrongAnswer1?.textContent).toContain('❌');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not show ✅ or ❌ when repondre 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 choiceButton = screen.getByText('Vrai').closest('button');
|
||||||
if (!choiceButton) throw new Error('Choice button not found');
|
if (!choiceButton) throw new Error('Choice button not found');
|
||||||
|
|
||||||
|
|
|
||||||
359
client/src/__tests__/pages/ManageRoom/IsCorrect.test.tsx
Normal file
359
client/src/__tests__/pages/ManageRoom/IsCorrect.test.tsx
Normal file
|
|
@ -0,0 +1,359 @@
|
||||||
|
import { checkIfIsCorrect } from 'src/pages/Teacher/ManageRoom/useRooms';
|
||||||
|
import { HighLowNumericalAnswer, MultipleChoiceQuestion, MultipleNumericalAnswer, NumericalQuestion, RangeNumericalAnswer, ShortAnswerQuestion, SimpleNumericalAnswer, TrueFalseQuestion } from 'gift-pegjs';
|
||||||
|
import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
|
||||||
|
import { QuestionType } from 'src/Types/QuestionType';
|
||||||
|
|
||||||
|
describe('checkIfIsCorrect', () => {
|
||||||
|
const mockQuestions: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '1',
|
||||||
|
type: 'MC',
|
||||||
|
choices: [
|
||||||
|
{ isCorrect: true, formattedText: { text: 'Answer1' } },
|
||||||
|
{ isCorrect: true, formattedText: { text: 'Answer2' } },
|
||||||
|
{ isCorrect: false, formattedText: { text: 'Answer3' } },
|
||||||
|
],
|
||||||
|
} as MultipleChoiceQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
test('returns true when all selected answers are correct', () => {
|
||||||
|
const answer: AnswerType = ['Answer1', 'Answer2'];
|
||||||
|
const result = checkIfIsCorrect(answer, 1, mockQuestions);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns false when some selected answers are incorrect', () => {
|
||||||
|
const answer: AnswerType = ['Answer1', 'Answer3'];
|
||||||
|
const result = checkIfIsCorrect(answer, 1, mockQuestions);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns false when all correct answers are selected, but one incorrect is also selected', () => {
|
||||||
|
const answer: AnswerType = ['Answer1', 'Answer2', 'Answer3'];
|
||||||
|
const result = checkIfIsCorrect(answer, 1, mockQuestions);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns false when no answers are selected', () => {
|
||||||
|
const answer: AnswerType = [];
|
||||||
|
const result = checkIfIsCorrect(answer, 1, mockQuestions);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns false when no correct answers are provided in the question', () => {
|
||||||
|
const mockQuestionsWithNoCorrectAnswers: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '1',
|
||||||
|
type: 'MC',
|
||||||
|
choices: [
|
||||||
|
{ isCorrect: false, formattedText: { text: 'Answer1' } },
|
||||||
|
{ isCorrect: false, formattedText: { text: 'Answer2' } },
|
||||||
|
],
|
||||||
|
} as MultipleChoiceQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = ['Answer1'];
|
||||||
|
const result = checkIfIsCorrect(answer, 1, mockQuestionsWithNoCorrectAnswers);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns true for a correct true/false answer', () => {
|
||||||
|
const mockQuestionsTF: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '2',
|
||||||
|
type: 'TF',
|
||||||
|
isTrue: true,
|
||||||
|
} as TrueFalseQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = [true];
|
||||||
|
const result = checkIfIsCorrect(answer, 2, mockQuestionsTF);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns false for an incorrect true/false answer', () => {
|
||||||
|
const mockQuestionsTF: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '2',
|
||||||
|
type: 'TF',
|
||||||
|
isTrue: true,
|
||||||
|
} as TrueFalseQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = [false];
|
||||||
|
const result = checkIfIsCorrect(answer, 2, mockQuestionsTF);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns false for a true/false question with no answer', () => {
|
||||||
|
const mockQuestionsTF: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '2',
|
||||||
|
type: 'TF',
|
||||||
|
isTrue: true,
|
||||||
|
} as TrueFalseQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = [];
|
||||||
|
const result = checkIfIsCorrect(answer, 2, mockQuestionsTF);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns true for a correct true/false answer when isTrue is false', () => {
|
||||||
|
const mockQuestionsTF: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '3',
|
||||||
|
type: 'TF',
|
||||||
|
isTrue: false, // Correct answer is false
|
||||||
|
} as TrueFalseQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = [false];
|
||||||
|
const result = checkIfIsCorrect(answer, 3, mockQuestionsTF);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns false for an incorrect true/false answer when isTrue is false', () => {
|
||||||
|
const mockQuestionsTF: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '3',
|
||||||
|
type: 'TF',
|
||||||
|
isTrue: false, // Correct answer is false
|
||||||
|
} as TrueFalseQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = [true];
|
||||||
|
const result = checkIfIsCorrect(answer, 3, mockQuestionsTF);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns true for a correct short answer', () => {
|
||||||
|
const mockQuestionsShort: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '4',
|
||||||
|
type: 'Short',
|
||||||
|
choices: [
|
||||||
|
{ text: 'CorrectAnswer1' },
|
||||||
|
{ text: 'CorrectAnswer2' },
|
||||||
|
],
|
||||||
|
} as ShortAnswerQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = ['CorrectAnswer1'];
|
||||||
|
const result = checkIfIsCorrect(answer, 4, mockQuestionsShort);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns false for an incorrect short answer', () => {
|
||||||
|
const mockQuestionsShort: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '4',
|
||||||
|
type: 'Short',
|
||||||
|
choices: [
|
||||||
|
{ text: 'CorrectAnswer1' },
|
||||||
|
{ text: 'CorrectAnswer2' },
|
||||||
|
],
|
||||||
|
} as ShortAnswerQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = ['WrongAnswer'];
|
||||||
|
const result = checkIfIsCorrect(answer, 4, mockQuestionsShort);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns true for a correct short answer with case insensitivity', () => {
|
||||||
|
const mockQuestionsShort: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '4',
|
||||||
|
type: 'Short',
|
||||||
|
choices: [
|
||||||
|
{ text: 'CorrectAnswer1' },
|
||||||
|
{ text: 'CorrectAnswer2' },
|
||||||
|
],
|
||||||
|
} as ShortAnswerQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = ['correctanswer1']; // Lowercase version of the correct answer
|
||||||
|
const result = checkIfIsCorrect(answer, 4, mockQuestionsShort);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns false for a short answer question with no answer', () => {
|
||||||
|
const mockQuestionsShort: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '4',
|
||||||
|
type: 'Short',
|
||||||
|
choices: [
|
||||||
|
{ text: 'CorrectAnswer1' },
|
||||||
|
{ text: 'CorrectAnswer2' },
|
||||||
|
],
|
||||||
|
} as ShortAnswerQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = [];
|
||||||
|
const result = checkIfIsCorrect(answer, 4, mockQuestionsShort);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('returns true for a correct simple numerical answer', () => {
|
||||||
|
const mockQuestionsNumerical: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '5',
|
||||||
|
type: 'Numerical',
|
||||||
|
choices: [
|
||||||
|
{ type: 'simple', number: 42 } as SimpleNumericalAnswer,
|
||||||
|
],
|
||||||
|
} as NumericalQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = [42]; // User's answer
|
||||||
|
const result = checkIfIsCorrect(answer, 5, mockQuestionsNumerical);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns false for an incorrect simple numerical answer', () => {
|
||||||
|
const mockQuestionsNumerical: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '5',
|
||||||
|
type: 'Numerical',
|
||||||
|
choices: [
|
||||||
|
{ type: 'simple', number: 42 } as SimpleNumericalAnswer,
|
||||||
|
],
|
||||||
|
} as NumericalQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = [43]; // User's answer
|
||||||
|
const result = checkIfIsCorrect(answer, 5, mockQuestionsNumerical);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns true for a correct range numerical answer', () => {
|
||||||
|
const mockQuestionsNumerical: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '6',
|
||||||
|
type: 'Numerical',
|
||||||
|
choices: [
|
||||||
|
{ type: 'range', number: 50, range: 5 } as RangeNumericalAnswer,
|
||||||
|
],
|
||||||
|
} as NumericalQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = [52]; // User's answer within the range (50 ± 5)
|
||||||
|
const result = checkIfIsCorrect(answer, 6, mockQuestionsNumerical);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns false for an out-of-range numerical answer', () => {
|
||||||
|
const mockQuestionsNumerical: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '6',
|
||||||
|
type: 'Numerical',
|
||||||
|
choices: [
|
||||||
|
{ type: 'range', number: 50, range: 5 } as RangeNumericalAnswer,
|
||||||
|
],
|
||||||
|
} as NumericalQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = [56]; // User's answer outside the range (50 ± 5)
|
||||||
|
const result = checkIfIsCorrect(answer, 6, mockQuestionsNumerical);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns true for a correct high-low numerical answer', () => {
|
||||||
|
const mockQuestionsNumerical: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '7',
|
||||||
|
type: 'Numerical',
|
||||||
|
choices: [
|
||||||
|
{ type: 'high-low', numberHigh: 100, numberLow: 90 } as HighLowNumericalAnswer,
|
||||||
|
],
|
||||||
|
} as NumericalQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = [95]; // User's answer within the range (90 to 100)
|
||||||
|
const result = checkIfIsCorrect(answer, 7, mockQuestionsNumerical);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns false for an out-of-range high-low numerical answer', () => {
|
||||||
|
const mockQuestionsNumerical: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '7',
|
||||||
|
type: 'Numerical',
|
||||||
|
choices: [
|
||||||
|
{ type: 'high-low', numberHigh: 100, numberLow: 90 } as HighLowNumericalAnswer,
|
||||||
|
],
|
||||||
|
} as NumericalQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = [105]; // User's answer outside the range (90 to 100)
|
||||||
|
const result = checkIfIsCorrect(answer, 7, mockQuestionsNumerical);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns true for a correct multiple numerical answer', () => {
|
||||||
|
const mockQuestionsNumerical: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '8',
|
||||||
|
type: 'Numerical',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
isCorrect: true,
|
||||||
|
answer: { type: 'simple', number: 42 } as SimpleNumericalAnswer,
|
||||||
|
} as MultipleNumericalAnswer,
|
||||||
|
{
|
||||||
|
isCorrect: false,
|
||||||
|
answer: { type: 'high-low', numberHigh: 100, numberLow: 90 } as HighLowNumericalAnswer,
|
||||||
|
formattedFeedback: { text: 'You guessed way too high' },
|
||||||
|
}
|
||||||
|
],
|
||||||
|
} as NumericalQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = [42]; // User's answer matches the correct multiple numerical answer
|
||||||
|
const result = checkIfIsCorrect(answer, 8, mockQuestionsNumerical);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('returns false for an incorrect multiple numerical answer', () => {
|
||||||
|
const mockQuestionsNumerical: QuestionType[] = [
|
||||||
|
{
|
||||||
|
question: {
|
||||||
|
id: '8',
|
||||||
|
type: 'Numerical',
|
||||||
|
choices: [
|
||||||
|
{
|
||||||
|
type: 'multiple',
|
||||||
|
isCorrect: true,
|
||||||
|
answer: { type: 'simple', number: 42 } as SimpleNumericalAnswer,
|
||||||
|
} as MultipleNumericalAnswer,
|
||||||
|
],
|
||||||
|
} as NumericalQuestion,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const answer: AnswerType = [43]; // User's answer does not match the correct multiple numerical answer
|
||||||
|
const result = checkIfIsCorrect(answer, 8, mockQuestionsNumerical);
|
||||||
|
expect(result).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
@ -44,7 +44,7 @@ const mockStudents: StudentType[] = [
|
||||||
];
|
];
|
||||||
|
|
||||||
const mockAnswerData: AnswerReceptionFromBackendType = {
|
const mockAnswerData: AnswerReceptionFromBackendType = {
|
||||||
answer: 'Answer1',
|
answer: ['Answer1'],
|
||||||
idQuestion: 1,
|
idQuestion: 1,
|
||||||
idUser: '1',
|
idUser: '1',
|
||||||
username: 'Student 1',
|
username: 'Student 1',
|
||||||
|
|
@ -233,7 +233,7 @@ describe('ManageRoom', () => {
|
||||||
|
|
||||||
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');
|
||||||
});
|
});
|
||||||
|
|
||||||
const launchButton = screen.getByText('Lancer');
|
const launchButton = screen.getByText('Lancer');
|
||||||
|
|
@ -256,6 +256,7 @@ describe('ManageRoom', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
|
// console.info(consoleSpy.mock.calls);
|
||||||
expect(consoleSpy).toHaveBeenCalledWith(
|
expect(consoleSpy).toHaveBeenCalledWith(
|
||||||
'Received answer from Student 1 for question 1: Answer1'
|
'Received answer from Student 1 for question 1: Answer1'
|
||||||
);
|
);
|
||||||
|
|
@ -355,3 +356,4 @@ describe('ManageRoom', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import { QuestionType } from 'src/Types/QuestionType';
|
||||||
import { AnswerSubmissionToBackendType } from 'src/services/WebsocketService';
|
import { AnswerSubmissionToBackendType } from 'src/services/WebsocketService';
|
||||||
|
|
||||||
const mockGiftQuestions = parse(
|
const mockGiftQuestions = parse(
|
||||||
`::Sample Question 1:: Sample Question 1 {=Option A ~Option B}
|
`::Sample Question 1:: Sample Question 1 {=Option A =Option B ~Option C}
|
||||||
|
|
||||||
::Sample Question 2:: Sample Question 2 {T}`);
|
::Sample Question 2:: Sample Question 2 {T}`);
|
||||||
|
|
||||||
|
|
@ -23,9 +23,6 @@ const mockSubmitAnswer = jest.fn();
|
||||||
const mockDisconnectWebSocket = jest.fn();
|
const mockDisconnectWebSocket = jest.fn();
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Clear local storage before each test
|
|
||||||
// localStorage.clear();
|
|
||||||
|
|
||||||
render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<StudentModeQuiz
|
<StudentModeQuiz
|
||||||
|
|
@ -54,7 +51,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 shows feedback for an already answered question', async () => {
|
test('handles shows feedback for an already answered question', async () => {
|
||||||
|
|
@ -65,13 +62,13 @@ describe('StudentModeQuiz', () => {
|
||||||
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);
|
||||||
|
|
||||||
const firstButtonA = screen.getByRole("button", {name: '✅ A Option A'});
|
const firstButtonA = screen.getByRole("button", {name: '✅ A Option A'});
|
||||||
expect(firstButtonA).toBeInTheDocument();
|
expect(firstButtonA).toBeInTheDocument();
|
||||||
expect(firstButtonA.querySelector('.selected')).toBeInTheDocument();
|
expect(firstButtonA.querySelector('.selected')).toBeInTheDocument();
|
||||||
|
|
||||||
expect(screen.getByRole("button", {name: '❌ B Option B'})).toBeInTheDocument();
|
expect(screen.getByRole("button", {name: '✅ B Option B'})).toBeInTheDocument();
|
||||||
expect(screen.queryByText('Répondre')).not.toBeInTheDocument();
|
expect(screen.queryByText('Répondre')).not.toBeInTheDocument();
|
||||||
|
|
||||||
// Navigate to the next question
|
// Navigate to the next question
|
||||||
|
|
@ -87,12 +84,12 @@ describe('StudentModeQuiz', () => {
|
||||||
});
|
});
|
||||||
expect(await screen.findByText('Sample Question 1')).toBeInTheDocument();
|
expect(await screen.findByText('Sample Question 1')).toBeInTheDocument();
|
||||||
|
|
||||||
// Since answers are mocked, the it doesn't recognize the question as already answered
|
// Since answers are mocked, it doesn't recognize the question as already answered
|
||||||
// TODO these tests are partially faked, need to be fixed if we can mock the answers
|
// TODO these tests are partially faked, need to be fixed if we can mock the answers
|
||||||
// const buttonA = screen.getByRole("button", {name: '✅ A Option A'});
|
// const buttonA = screen.getByRole("button", {name: '✅ A Option A'});
|
||||||
const buttonA = screen.getByRole("button", {name: 'A Option A'});
|
const buttonA = screen.getByRole("button", {name: 'A Option A'});
|
||||||
expect(buttonA).toBeInTheDocument();
|
expect(buttonA).toBeInTheDocument();
|
||||||
// const buttonB = screen.getByRole("button", {name: '❌ B Option B'});
|
// const buttonB = screen.getByRole("button", {name: '✅ B Option B'});
|
||||||
const buttonB = screen.getByRole("button", {name: 'B Option B'});
|
const buttonB = screen.getByRole("button", {name: 'B Option B'});
|
||||||
expect(buttonB).toBeInTheDocument();
|
expect(buttonB).toBeInTheDocument();
|
||||||
// // "Option A" div inside the name of button should have selected class
|
// // "Option A" div inside the name of button should have selected class
|
||||||
|
|
@ -122,4 +119,30 @@ describe('StudentModeQuiz', () => {
|
||||||
expect(screen.getByText('Sample Question 2')).toBeInTheDocument();
|
expect(screen.getByText('Sample Question 2')).toBeInTheDocument();
|
||||||
expect(screen.getByText('Répondre')).toBeInTheDocument();
|
expect(screen.getByText('Répondre')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// le test suivant est fait dans MultipleChoiceQuestionDisplay.test.tsx
|
||||||
|
// test('allows multiple answers to be selected for a question', async () => {
|
||||||
|
// // Simulate selecting multiple answers
|
||||||
|
// act(() => {
|
||||||
|
// fireEvent.click(screen.getByText('Option A'));
|
||||||
|
// });
|
||||||
|
// act(() => {
|
||||||
|
// fireEvent.click(screen.getByText('Option B'));
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // Simulate submitting the answers
|
||||||
|
// act(() => {
|
||||||
|
// fireEvent.click(screen.getByText('Répondre'));
|
||||||
|
// });
|
||||||
|
|
||||||
|
// // Verify that the mockSubmitAnswer function is called with both answers
|
||||||
|
// expect(mockSubmitAnswer).toHaveBeenCalledWith(['Option A', 'Option B'], 1);
|
||||||
|
|
||||||
|
// // Verify that the selected answers are displayed as selected
|
||||||
|
// const buttonA = screen.getByRole('button', { name: '✅ A Option A' });
|
||||||
|
// const buttonB = screen.getByRole('button', { name: '✅ B Option B' });
|
||||||
|
// expect(buttonA).toBeInTheDocument();
|
||||||
|
// expect(buttonB).toBeInTheDocument();
|
||||||
|
// });
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,8 @@ 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);
|
||||||
|
mockSubmitAnswer.mockClear();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handles shows feedback for an already answered question', () => {
|
test('handles shows feedback for an already answered question', () => {
|
||||||
|
|
@ -74,7 +75,8 @@ 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);
|
||||||
|
mockSubmitAnswer.mockClear();
|
||||||
mockQuestion = mockQuestions[1].question as MultipleChoiceQuestion;
|
mockQuestion = mockQuestions[1].question as MultipleChoiceQuestion;
|
||||||
// Navigate to the next question by re-rendering with new props
|
// Navigate to the next question by re-rendering with new props
|
||||||
act(() => {
|
act(() => {
|
||||||
|
|
|
||||||
|
|
@ -13,14 +13,14 @@ type AnswerFeedbackOptions = TemplateOptions & Pick<TextChoice, 'formattedFeedba
|
||||||
interface AnswerWeightOptions extends TemplateOptions {
|
interface AnswerWeightOptions extends TemplateOptions {
|
||||||
weight: TextChoice['weight'];
|
weight: TextChoice['weight'];
|
||||||
}
|
}
|
||||||
|
// careful -- this template is re-used by True/False questions!
|
||||||
export default function MultipleChoiceAnswersTemplate({ choices }: MultipleChoiceAnswerOptions) {
|
export default function MultipleChoiceAnswersTemplate({ choices }: MultipleChoiceAnswerOptions) {
|
||||||
const id = `id${nanoid(8)}`;
|
const id = `id${nanoid(8)}`;
|
||||||
|
|
||||||
const isMultipleAnswer = choices.filter(({ isCorrect }) => isCorrect === true).length === 0;
|
const hasManyCorrectChoices = choices.filter(({ isCorrect }) => isCorrect === true).length > 1;
|
||||||
|
|
||||||
const prompt = `<span style="${ParagraphStyle(state.theme)}">Choisir une réponse${
|
const prompt = `<span style="${ParagraphStyle(state.theme)}">Choisir une réponse${
|
||||||
isMultipleAnswer ? ` ou plusieurs` : ``
|
hasManyCorrectChoices ? ` ou plusieurs` : ``
|
||||||
}:</span>`;
|
}:</span>`;
|
||||||
const result = choices
|
const result = choices
|
||||||
.map(({ weight, isCorrect, formattedText, formattedFeedback }) => {
|
.map(({ weight, isCorrect, formattedText, formattedFeedback }) => {
|
||||||
|
|
@ -32,12 +32,12 @@ export default function MultipleChoiceAnswersTemplate({ choices }: MultipleChoic
|
||||||
const inputId = `id${nanoid(6)}`;
|
const inputId = `id${nanoid(6)}`;
|
||||||
|
|
||||||
const isPositiveWeight = (weight != undefined) && (weight > 0);
|
const isPositiveWeight = (weight != undefined) && (weight > 0);
|
||||||
const isCorrectOption = isMultipleAnswer ? isPositiveWeight : isCorrect;
|
const isCorrectOption = hasManyCorrectChoices ? isPositiveWeight || isCorrect : isCorrect;
|
||||||
|
|
||||||
return `
|
return `
|
||||||
<div class='multiple-choice-answers-container'>
|
<div class='multiple-choice-answers-container'>
|
||||||
<input class="gift-input" type="${
|
<input class="gift-input" type="${
|
||||||
isMultipleAnswer ? 'checkbox' : 'radio'
|
hasManyCorrectChoices ? 'checkbox' : 'radio'
|
||||||
}" id="${inputId}" name="${id}">
|
}" id="${inputId}" name="${id}">
|
||||||
${AnswerWeight({ weight: weight })}
|
${AnswerWeight({ weight: weight })}
|
||||||
<label style="${CustomLabel} ${ParagraphStyle(state.theme)}" for="${inputId}">
|
<label style="${CustomLabel} ${ParagraphStyle(state.theme)}" for="${inputId}">
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ const LiveResultsTableFooter: React.FC<LiveResultsFooterProps> = ({
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
borderColor: 'rgba(224, 224, 224, 1)',
|
borderColor: 'rgba(224, 224, 224, 1)',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
color: 'rgba(0, 0, 0)'
|
color: 'rgba(0, 0, 0)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{students.length > 0
|
{students.length > 0
|
||||||
|
|
@ -67,7 +67,7 @@ const LiveResultsTableFooter: React.FC<LiveResultsFooterProps> = ({
|
||||||
borderColor: 'rgba(224, 224, 224, 1)',
|
borderColor: 'rgba(224, 224, 224, 1)',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
fontSize: '1rem',
|
fontSize: '1rem',
|
||||||
color: 'rgba(0, 0, 0)'
|
color: 'rgba(0, 0, 0)',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{students.length > 0 ? `${classAverage.toFixed()} %` : '-'}
|
{students.length > 0 ? `${classAverage.toFixed()} %` : '-'}
|
||||||
|
|
|
||||||
|
|
@ -15,76 +15,115 @@ interface Props {
|
||||||
|
|
||||||
const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = props;
|
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = props;
|
||||||
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || '');
|
console.log('MultipleChoiceQuestionDisplay: passedAnswer', JSON.stringify(passedAnswer));
|
||||||
|
|
||||||
|
const [answer, setAnswer] = useState<AnswerType>(() => {
|
||||||
|
if (passedAnswer && passedAnswer.length > 0) {
|
||||||
|
return passedAnswer;
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
|
||||||
let disableButton = false;
|
let disableButton = false;
|
||||||
if(handleOnSubmitAnswer === undefined){
|
if (handleOnSubmitAnswer === undefined) {
|
||||||
disableButton = true;
|
disableButton = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (passedAnswer !== undefined) {
|
console.log('MultipleChoiceQuestionDisplay: passedAnswer', JSON.stringify(passedAnswer));
|
||||||
setAnswer(passedAnswer);
|
if (passedAnswer !== undefined) {
|
||||||
}
|
setAnswer(passedAnswer);
|
||||||
}, [passedAnswer]);
|
} else {
|
||||||
|
setAnswer([]);
|
||||||
|
}
|
||||||
|
}, [passedAnswer, question.id]);
|
||||||
|
|
||||||
const handleOnClickAnswer = (choice: string) => {
|
const handleOnClickAnswer = (choice: string) => {
|
||||||
setAnswer(choice);
|
setAnswer((prevAnswer) => {
|
||||||
|
console.log(`handleOnClickAnswer -- setAnswer(): prevAnswer: ${prevAnswer}, choice: ${choice}`);
|
||||||
|
const correctAnswersCount = question.choices.filter((c) => c.isCorrect).length;
|
||||||
|
|
||||||
|
if (correctAnswersCount === 1) {
|
||||||
|
// If only one correct answer, replace the current selection
|
||||||
|
return prevAnswer.includes(choice) ? [] : [choice];
|
||||||
|
} else {
|
||||||
|
// Allow multiple selections if there are multiple correct answers
|
||||||
|
if (prevAnswer.includes(choice)) {
|
||||||
|
// Remove the choice if it's already selected
|
||||||
|
return prevAnswer.filter((selected) => selected !== choice);
|
||||||
|
} else {
|
||||||
|
// Add the choice if it's not already selected
|
||||||
|
return [...prevAnswer, choice];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const alpha = Array.from(Array(26)).map((_e, i) => i + 65);
|
const alpha = Array.from(Array(26)).map((_e, i) => i + 65);
|
||||||
const alphabet = alpha.map((x) => String.fromCharCode(x));
|
const alphabet = alpha.map((x) => String.fromCharCode(x));
|
||||||
return (
|
|
||||||
|
|
||||||
|
return (
|
||||||
<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">
|
||||||
|
|
||||||
{question.choices.map((choice, i) => {
|
{question.choices.map((choice, i) => {
|
||||||
const selected = answer === choice.formattedText.text ? 'selected' : '';
|
console.log(`answer: ${answer}, choice: ${choice.formattedText.text}`);
|
||||||
|
const selected = answer.includes(choice.formattedText.text) ? 'selected' : '';
|
||||||
return (
|
return (
|
||||||
<div key={choice.formattedText.text + i} className="choice-container">
|
<div key={choice.formattedText.text + i} className="choice-container">
|
||||||
<Button
|
<Button
|
||||||
variant="text"
|
variant="text"
|
||||||
className="button-wrapper"
|
className="button-wrapper"
|
||||||
disabled={disableButton}
|
disabled={disableButton}
|
||||||
onClick={() => !showAnswer && handleOnClickAnswer(choice.formattedText.text)}>
|
onClick={() => !showAnswer && handleOnClickAnswer(choice.formattedText.text)}
|
||||||
{showAnswer? (<div> {(choice.isCorrect ? '✅' : '❌')}</div>)
|
>
|
||||||
:``}
|
{showAnswer ? (
|
||||||
|
<div>{choice.isCorrect ? '✅' : '❌'}</div>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)}
|
||||||
<div className={`circle ${selected}`}>{alphabet[i]}</div>
|
<div className={`circle ${selected}`}>{alphabet[i]}</div>
|
||||||
<div className={`answer-text ${selected}`}>
|
<div className={`answer-text ${selected}`}>
|
||||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(choice.formattedText) }} />
|
<div
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: FormattedTextTemplate(choice.formattedText),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{choice.formattedFeedback && showAnswer && (
|
{choice.formattedFeedback && showAnswer && (
|
||||||
<div className="feedback-container mb-1 mt-1/2">
|
<div className="feedback-container mb-1 mt-1/2">
|
||||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(choice.formattedFeedback) }} />
|
<div
|
||||||
</div>
|
dangerouslySetInnerHTML={{
|
||||||
)}
|
__html: FormattedTextTemplate(choice.formattedFeedback),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
</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
|
||||||
</div>
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: FormattedTextTemplate(question.formattedGlobalFeedback),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!showAnswer && handleOnSubmitAnswer && (
|
{!showAnswer && handleOnSubmitAnswer && (
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
answer !== "" && handleOnSubmitAnswer && handleOnSubmitAnswer(answer)
|
answer.length > 0 && handleOnSubmitAnswer && handleOnSubmitAnswer(answer)
|
||||||
}
|
}
|
||||||
disabled={answer === '' || answer === null}
|
disabled={answer.length === 0}
|
||||||
>
|
>
|
||||||
Répondre
|
Répondre
|
||||||
|
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ interface Props {
|
||||||
const NumericalQuestionDisplay: React.FC<Props> = (props) => {
|
const NumericalQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } =
|
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } =
|
||||||
props;
|
props;
|
||||||
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || '');
|
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || []);
|
||||||
const correctAnswers = question.choices;
|
const correctAnswers = question.choices;
|
||||||
let correctAnswer = '';
|
let correctAnswer = '';
|
||||||
|
|
||||||
|
|
@ -69,7 +69,7 @@ const NumericalQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
id={question.formattedStem.text}
|
id={question.formattedStem.text}
|
||||||
name={question.formattedStem.text}
|
name={question.formattedStem.text}
|
||||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setAnswer(e.target.valueAsNumber);
|
setAnswer([e.target.valueAsNumber]);
|
||||||
}}
|
}}
|
||||||
inputProps={{ 'data-testid': 'number-input' }}
|
inputProps={{ 'data-testid': 'number-input' }}
|
||||||
/>
|
/>
|
||||||
|
|
@ -87,7 +87,7 @@ const NumericalQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
handleOnSubmitAnswer &&
|
handleOnSubmitAnswer &&
|
||||||
handleOnSubmitAnswer(answer)
|
handleOnSubmitAnswer(answer)
|
||||||
}
|
}
|
||||||
disabled={answer === "" || isNaN(answer as number)}
|
disabled={answer === undefined || answer === null || isNaN(answer[0] as number)}
|
||||||
>
|
>
|
||||||
Répondre
|
Répondre
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ interface Props {
|
||||||
const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
|
const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
|
|
||||||
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = props;
|
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = props;
|
||||||
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || '');
|
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (passedAnswer !== undefined) {
|
if (passedAnswer !== undefined) {
|
||||||
|
|
@ -58,7 +58,7 @@ const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
id={question.formattedStem.text}
|
id={question.formattedStem.text}
|
||||||
name={question.formattedStem.text}
|
name={question.formattedStem.text}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setAnswer(e.target.value);
|
setAnswer([e.target.value]);
|
||||||
}}
|
}}
|
||||||
disabled={showAnswer}
|
disabled={showAnswer}
|
||||||
aria-label="short-answer-input"
|
aria-label="short-answer-input"
|
||||||
|
|
@ -72,7 +72,7 @@ const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
handleOnSubmitAnswer &&
|
handleOnSubmitAnswer &&
|
||||||
handleOnSubmitAnswer(answer)
|
handleOnSubmitAnswer(answer)
|
||||||
}
|
}
|
||||||
disabled={answer === null || answer === ''}
|
disabled={answer === null || answer === undefined || answer.length === 0}
|
||||||
>
|
>
|
||||||
Répondre
|
Répondre
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
// TrueFalseQuestion.tsx
|
// TrueFalseQuestion.tsx
|
||||||
import React, { useState,useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import '../questionStyle.css';
|
import '../questionStyle.css';
|
||||||
import { Button } from '@mui/material';
|
import { Button } from '@mui/material';
|
||||||
import { TrueFalseQuestion } from 'gift-pegjs';
|
import { TrueFalseQuestion } from 'gift-pegjs';
|
||||||
|
|
@ -8,38 +8,38 @@ import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
let disableButton = false;
|
|
||||||
if(handleOnSubmitAnswer === undefined){
|
|
||||||
disableButton = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log("passedAnswer", answer);
|
|
||||||
if (passedAnswer === true || passedAnswer === false) {
|
|
||||||
setAnswer(passedAnswer);
|
|
||||||
} else {
|
|
||||||
setAnswer(undefined);
|
|
||||||
}
|
|
||||||
}, [passedAnswer, question.id]);
|
|
||||||
|
|
||||||
const [answer, setAnswer] = useState<boolean | undefined>(() => {
|
const [answer, setAnswer] = useState<boolean | undefined>(() => {
|
||||||
|
|
||||||
if (passedAnswer === true || passedAnswer === false) {
|
if (passedAnswer && (passedAnswer[0] === true || passedAnswer[0] === false)) {
|
||||||
return passedAnswer;
|
return passedAnswer[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let disableButton = false;
|
||||||
|
if (handleOnSubmitAnswer === undefined) {
|
||||||
|
disableButton = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log("passedAnswer", passedAnswer);
|
||||||
|
if (passedAnswer && (passedAnswer[0] === true || passedAnswer[0] === false)) {
|
||||||
|
setAnswer(passedAnswer[0]);
|
||||||
|
} else {
|
||||||
|
setAnswer(undefined);
|
||||||
|
}
|
||||||
|
}, [passedAnswer, question.id]);
|
||||||
|
|
||||||
const handleOnClickAnswer = (choice: boolean) => {
|
const handleOnClickAnswer = (choice: boolean) => {
|
||||||
setAnswer(choice);
|
setAnswer(choice);
|
||||||
};
|
};
|
||||||
|
|
@ -49,7 +49,7 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
return (
|
return (
|
||||||
<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,15 +58,15 @@ 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>
|
||||||
<Button
|
<Button
|
||||||
className="button-wrapper"
|
className="button-wrapper"
|
||||||
|
|
@ -75,15 +75,15 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
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 && (
|
||||||
|
|
@ -95,8 +95,7 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
answer !== undefined && handleOnSubmitAnswer && handleOnSubmitAnswer(answer)
|
answer !== undefined && handleOnSubmitAnswer && handleOnSubmitAnswer([answer])
|
||||||
|
|
||||||
}
|
}
|
||||||
disabled={answer === undefined}
|
disabled={answer === undefined}
|
||||||
>
|
>
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import LoginContainer from 'src/components/LoginContainer/LoginContainer'
|
||||||
|
|
||||||
import ApiService from '../../../services/ApiService'
|
import ApiService from '../../../services/ApiService'
|
||||||
|
|
||||||
export type AnswerType = string | number | boolean;
|
export type AnswerType = Array<string | number | boolean>;
|
||||||
|
|
||||||
const JoinRoom: React.FC = () => {
|
const JoinRoom: React.FC = () => {
|
||||||
const [roomName, setRoomName] = useState('');
|
const [roomName, setRoomName] = useState('');
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,7 @@
|
||||||
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 { ParsedGIFTQuestion, BaseQuestion, parse, Question } from 'gift-pegjs';
|
import { BaseQuestion, parse, Question } from 'gift-pegjs';
|
||||||
import {
|
|
||||||
isSimpleNumericalAnswer,
|
|
||||||
isRangeNumericalAnswer,
|
|
||||||
isHighLowNumericalAnswer
|
|
||||||
} from 'gift-pegjs/typeGuards';
|
|
||||||
import LiveResultsComponent from 'src/components/LiveResults/LiveResults';
|
import LiveResultsComponent from 'src/components/LiveResults/LiveResults';
|
||||||
import webSocketService, {
|
import webSocketService, {
|
||||||
AnswerReceptionFromBackendType
|
AnswerReceptionFromBackendType
|
||||||
|
|
@ -24,7 +19,7 @@ import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay';
|
||||||
import ApiService from '../../../services/ApiService';
|
import ApiService from '../../../services/ApiService';
|
||||||
import { QuestionType } from 'src/Types/QuestionType';
|
import { QuestionType } from 'src/Types/QuestionType';
|
||||||
import { Button, FormControlLabel, Switch } from '@mui/material';
|
import { Button, FormControlLabel, Switch } from '@mui/material';
|
||||||
import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
|
import { checkIfIsCorrect } from './useRooms';
|
||||||
|
|
||||||
const ManageRoom: React.FC = () => {
|
const ManageRoom: React.FC = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
@ -356,63 +351,6 @@ const ManageRoom: React.FC = () => {
|
||||||
navigate('/teacher/dashboard');
|
navigate('/teacher/dashboard');
|
||||||
};
|
};
|
||||||
|
|
||||||
function checkIfIsCorrect(
|
|
||||||
answer: AnswerType,
|
|
||||||
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 ParsedGIFTQuestion;
|
|
||||||
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.formattedText.text === answerText
|
|
||||||
);
|
|
||||||
} else if (question.type === 'Numerical') {
|
|
||||||
if (isHighLowNumericalAnswer(question.choices[0])) {
|
|
||||||
const choice = question.choices[0];
|
|
||||||
const answerNumber = parseFloat(answerText);
|
|
||||||
if (!isNaN(answerNumber)) {
|
|
||||||
return (
|
|
||||||
answerNumber <= choice.numberHigh && answerNumber >= choice.numberLow
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isRangeNumericalAnswer(question.choices[0])) {
|
|
||||||
const answerNumber = parseFloat(answerText);
|
|
||||||
const range = question.choices[0].range;
|
|
||||||
const correctAnswer = question.choices[0].number;
|
|
||||||
if (!isNaN(answerNumber)) {
|
|
||||||
return (
|
|
||||||
answerNumber <= correctAnswer + range &&
|
|
||||||
answerNumber >= correctAnswer - range
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (isSimpleNumericalAnswer(question.choices[0])) {
|
|
||||||
const answerNumber = parseFloat(answerText);
|
|
||||||
if (!isNaN(answerNumber)) {
|
|
||||||
return answerNumber === question.choices[0].number;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (question.type === 'Short') {
|
|
||||||
return question.choices.some(
|
|
||||||
(choice) => choice.text.toUpperCase() === answerText.toUpperCase()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!formattedRoomName) {
|
if (!formattedRoomName) {
|
||||||
return (
|
return (
|
||||||
<div className="center">
|
<div className="center">
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,15 @@
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import { RoomType } from 'src/Types/RoomType';
|
import { RoomType } from 'src/Types/RoomType';
|
||||||
import { createContext } from 'react';
|
import { createContext } from 'react';
|
||||||
|
import { MultipleNumericalAnswer, NumericalAnswer, ParsedGIFTQuestion } from 'gift-pegjs';
|
||||||
//import { RoomContext } from './RoomContext';
|
import { QuestionType } from 'src/Types/QuestionType';
|
||||||
|
import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
|
||||||
|
import {
|
||||||
|
isSimpleNumericalAnswer,
|
||||||
|
isRangeNumericalAnswer,
|
||||||
|
isHighLowNumericalAnswer,
|
||||||
|
isMultipleNumericalAnswer
|
||||||
|
} from 'gift-pegjs/typeGuards';
|
||||||
|
|
||||||
type RoomContextType = {
|
type RoomContextType = {
|
||||||
rooms: RoomType[];
|
rooms: RoomType[];
|
||||||
|
|
@ -18,3 +25,137 @@ export const useRooms = () => {
|
||||||
if (!context) throw new Error('useRooms must be used within a RoomProvider');
|
if (!context) throw new Error('useRooms must be used within a RoomProvider');
|
||||||
return context;
|
return context;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the answer is correct - logic varies by type of question!
|
||||||
|
* True/False: answer must match the isTrue property
|
||||||
|
* Multiple Choice: answer must match the correct choice(s)
|
||||||
|
* Numerical: answer must be within the range or equal to the number (for each type of correct answer)
|
||||||
|
* Short Answer: answer must match the correct choice(s) (case-insensitive)
|
||||||
|
* @param answer
|
||||||
|
* @param idQuestion
|
||||||
|
* @param questions
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
export function checkIfIsCorrect(
|
||||||
|
answer: AnswerType,
|
||||||
|
idQuestion: number,
|
||||||
|
questions: QuestionType[]
|
||||||
|
): boolean {
|
||||||
|
const questionInfo = questions.find((q) =>
|
||||||
|
q.question.id ? q.question.id === idQuestion.toString() : false
|
||||||
|
) as QuestionType | undefined;
|
||||||
|
|
||||||
|
const simpleAnswerText = answer.toString();
|
||||||
|
if (questionInfo) {
|
||||||
|
const question = questionInfo.question as ParsedGIFTQuestion;
|
||||||
|
if (question.type === 'TF') {
|
||||||
|
return (
|
||||||
|
(question.isTrue && simpleAnswerText == 'true') ||
|
||||||
|
(!question.isTrue && simpleAnswerText == 'false')
|
||||||
|
);
|
||||||
|
} else if (question.type === 'MC') {
|
||||||
|
const correctChoices = question.choices.filter((choice) => choice.isCorrect
|
||||||
|
/* || (choice.weight && choice.weight > 0)*/ // handle weighted answers
|
||||||
|
);
|
||||||
|
const multipleAnswers = Array.isArray(answer) ? answer : [answer as string];
|
||||||
|
if (correctChoices.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// check if all (and only) correct choices are in the multipleAnswers array
|
||||||
|
return correctChoices.length === multipleAnswers.length && correctChoices.every(
|
||||||
|
(choice) => multipleAnswers.includes(choice.formattedText.text)
|
||||||
|
);
|
||||||
|
} else if (question.type === 'Numerical') {
|
||||||
|
if (isMultipleNumericalAnswer(question.choices[0])) { // Multiple numerical answers
|
||||||
|
// check to see if answer[0] is a match for any of the choices that isCorrect
|
||||||
|
const correctChoices = question.choices.filter((choice) => isMultipleNumericalAnswer(choice) && choice.isCorrect);
|
||||||
|
if (correctChoices.length === 0) { // weird case where there are multiple numerical answers but none are correct
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return correctChoices.some((choice) => {
|
||||||
|
// narrow choice to MultipleNumericalAnswer type
|
||||||
|
const multipleNumericalChoice = choice as MultipleNumericalAnswer;
|
||||||
|
return isCorrectNumericalAnswer(multipleNumericalChoice.answer, simpleAnswerText);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (isHighLowNumericalAnswer(question.choices[0])) {
|
||||||
|
// const choice = question.choices[0];
|
||||||
|
// const answerNumber = parseFloat(simpleAnswerText);
|
||||||
|
// if (!isNaN(answerNumber)) {
|
||||||
|
// return (
|
||||||
|
// answerNumber <= choice.numberHigh && answerNumber >= choice.numberLow
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
return isCorrectNumericalAnswer(question.choices[0], simpleAnswerText);
|
||||||
|
}
|
||||||
|
if (isRangeNumericalAnswer(question.choices[0])) {
|
||||||
|
// const answerNumber = parseFloat(simpleAnswerText);
|
||||||
|
// const range = question.choices[0].range;
|
||||||
|
// const correctAnswer = question.choices[0].number;
|
||||||
|
// if (!isNaN(answerNumber)) {
|
||||||
|
// return (
|
||||||
|
// answerNumber <= correctAnswer + range &&
|
||||||
|
// answerNumber >= correctAnswer - range
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
return isCorrectNumericalAnswer(question.choices[0], simpleAnswerText);
|
||||||
|
}
|
||||||
|
if (isSimpleNumericalAnswer(question.choices[0])) {
|
||||||
|
// const answerNumber = parseFloat(simpleAnswerText);
|
||||||
|
// if (!isNaN(answerNumber)) {
|
||||||
|
// return answerNumber === question.choices[0].number;
|
||||||
|
// }
|
||||||
|
return isCorrectNumericalAnswer(question.choices[0], simpleAnswerText);
|
||||||
|
}
|
||||||
|
} else if (question.type === 'Short') {
|
||||||
|
return question.choices.some(
|
||||||
|
(choice) => choice.text.toUpperCase() === simpleAnswerText.toUpperCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if a numerical answer is correct based on the type of numerical answer.
|
||||||
|
* @param correctAnswer The correct answer (of type NumericalAnswer).
|
||||||
|
* @param userAnswer The user's answer (as a string or number).
|
||||||
|
* @returns True if the user's answer is correct, false otherwise.
|
||||||
|
*/
|
||||||
|
export function isCorrectNumericalAnswer(
|
||||||
|
correctAnswer: NumericalAnswer,
|
||||||
|
userAnswer: string | number
|
||||||
|
): boolean {
|
||||||
|
const answerNumber = typeof userAnswer === 'string' ? parseFloat(userAnswer) : userAnswer;
|
||||||
|
|
||||||
|
if (isNaN(answerNumber)) {
|
||||||
|
return false; // User's answer is not a valid number
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isSimpleNumericalAnswer(correctAnswer)) {
|
||||||
|
// Exact match for simple numerical answers
|
||||||
|
return answerNumber === correctAnswer.number;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isRangeNumericalAnswer(correctAnswer)) {
|
||||||
|
// Check if the user's answer is within the range
|
||||||
|
const { number, range } = correctAnswer;
|
||||||
|
return answerNumber >= number - range && answerNumber <= number + range;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isHighLowNumericalAnswer(correctAnswer)) {
|
||||||
|
// Check if the user's answer is within the high-low range
|
||||||
|
const { numberLow, numberHigh } = correctAnswer;
|
||||||
|
return answerNumber >= numberLow && answerNumber <= numberHigh;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if (isMultipleNumericalAnswer(correctAnswer)) {
|
||||||
|
// // Check if the user's answer matches any of the multiple numerical answers
|
||||||
|
// return correctAnswer.answer.some((choice) =>
|
||||||
|
// isCorrectNumericalAnswer(choice, answerNumber)
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
|
return false; // Default to false if the answer type is not recognized
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue