mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
first cut, with tests
Some checks failed
CI/CD Pipeline for Backend / build_and_push_backend (push) Failing after 1m1s
CI/CD Pipeline for Nginx Router / build_and_push_nginx (push) Failing after 59s
CI/CD Pipeline for Frontend / build_and_push_frontend (push) Failing after 18s
Tests / lint-and-tests (client) (push) Failing after 1m27s
Tests / lint-and-tests (server) (push) Failing after 1m3s
Some checks failed
CI/CD Pipeline for Backend / build_and_push_backend (push) Failing after 1m1s
CI/CD Pipeline for Nginx Router / build_and_push_nginx (push) Failing after 59s
CI/CD Pipeline for Frontend / build_and_push_frontend (push) Failing after 18s
Tests / lint-and-tests (client) (push) Failing after 1m27s
Tests / lint-and-tests (server) (push) Failing after 1m3s
This commit is contained in:
parent
112062c0b2
commit
42e3041830
16 changed files with 725 additions and 226 deletions
|
|
@ -19,8 +19,8 @@ const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) =>
|
|||
});
|
||||
|
||||
const mockStudents: StudentType[] = [
|
||||
{ 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: "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 }] },
|
||||
];
|
||||
|
||||
const mockShowSelectedQuestion = jest.fn();
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) =>
|
|||
|
||||
|
||||
const mockStudents: StudentType[] = [
|
||||
{ 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: "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 }] },
|
||||
];
|
||||
|
||||
const mockShowSelectedQuestion = jest.fn();
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) =>
|
|||
});
|
||||
|
||||
const mockStudents: StudentType[] = [
|
||||
{ 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: "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 }] },
|
||||
];
|
||||
|
||||
const mockGetStudentGrade = jest.fn((student: StudentType) => {
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import LiveResultsTableFooter from 'src/components/LiveResults/LiveResultsTable/
|
|||
|
||||
|
||||
const mockStudents: StudentType[] = [
|
||||
{ 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: "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 }] },
|
||||
];
|
||||
|
||||
const mockGetStudentGrade = jest.fn((student: StudentType) => {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import LiveResults from 'src/components/LiveResults/LiveResults';
|
|||
import { QuestionType } from 'src/Types/QuestionType';
|
||||
import { StudentType } from 'src/Types/StudentType';
|
||||
import { Socket } from 'socket.io-client';
|
||||
import { BaseQuestion,parse } from 'gift-pegjs';
|
||||
import { BaseQuestion, parse } from 'gift-pegjs';
|
||||
|
||||
const mockSocket: Socket = {
|
||||
on: jest.fn(),
|
||||
|
|
@ -19,19 +19,21 @@ const mockGiftQuestions = parse(
|
|||
`::Sample Question 1:: Question stem
|
||||
{
|
||||
=Choice 1
|
||||
~Choice 2
|
||||
=Choice 2
|
||||
~Choice 3
|
||||
}`);
|
||||
|
||||
const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => {
|
||||
if (question.type !== "Category")
|
||||
question.id = (index + 1).toString();
|
||||
const newMockQuestion = question;
|
||||
return {question : newMockQuestion as BaseQuestion};
|
||||
return { question: newMockQuestion as BaseQuestion };
|
||||
});
|
||||
|
||||
const mockStudents: StudentType[] = [
|
||||
{ id: '1', name: 'Student 1', answers: [{ idQuestion: 1, answer: 'Choice 1', isCorrect: true }] },
|
||||
{ id: '2', name: 'Student 2', answers: [{ idQuestion: 1, answer: 'Choice 2', isCorrect: false }] },
|
||||
{ id: '1', name: 'Student 1', answers: [{ idQuestion: 1, answer: ['Choice 1'], isCorrect: false }] },
|
||||
{ id: '2', name: 'Student 2', answers: [{ idQuestion: 1, answer: ['Choice 3'], isCorrect: false }] },
|
||||
{ id: '3', name: 'Student 3', answers: [{ idQuestion: 1, answer: ['Choice 1', 'Choice 2'], isCorrect: true }] },
|
||||
];
|
||||
|
||||
describe('LiveResults', () => {
|
||||
|
|
@ -82,8 +84,7 @@ describe('LiveResults', () => {
|
|||
});
|
||||
});
|
||||
|
||||
});
|
||||
test('calculates and displays the correct student grades', () => {
|
||||
test('calculates and displays the correct student grades', () => {
|
||||
render(
|
||||
<LiveResults
|
||||
socket={mockSocket}
|
||||
|
|
@ -106,9 +107,9 @@ test('calculates and displays the correct student grades', () => {
|
|||
const grade = student.answers.filter(answer => answer.isCorrect).length / mockQuestions.length * 100;
|
||||
expect(screen.getByText(`${grade.toFixed()} %`)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('calculates and displays the class average', () => {
|
||||
test('calculates and displays the class average', () => {
|
||||
render(
|
||||
<LiveResults
|
||||
socket={mockSocket}
|
||||
|
|
@ -137,9 +138,9 @@ test('calculates and displays the class average', () => {
|
|||
return element.closest('td')?.classList.contains('MuiTableCell-footer');
|
||||
});
|
||||
expect(classAverageElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
test('displays the correct answers per question', () => {
|
||||
test('displays the correct answers per question', () => {
|
||||
render(
|
||||
<LiveResults
|
||||
socket={mockSocket}
|
||||
|
|
@ -160,4 +161,6 @@ test('displays the correct answers per question', () => {
|
|||
});
|
||||
expect(correctAnswersElement).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
|
@ -73,6 +73,30 @@ describe('MultipleChoiceQuestionDisplay', () => {
|
|||
expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith('Choice 1');
|
||||
});
|
||||
|
||||
test('submits multiple selected answers', () => {
|
||||
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']);
|
||||
});
|
||||
|
||||
it('should show ✅ next to the correct answer and ❌ next to the wrong answers when showAnswer is true', async () => {
|
||||
const choiceButton = screen.getByText('Choice 1').closest('button');
|
||||
if (!choiceButton) throw new Error('Choice button not found');
|
||||
|
|
|
|||
353
client/src/__tests__/pages/ManageRoom/IsCorrect.test.tsx
Normal file
353
client/src/__tests__/pages/ManageRoom/IsCorrect.test.tsx
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
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 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 = {
|
||||
answer: 'Answer1',
|
||||
answer: ['Answer1'],
|
||||
idQuestion: 1,
|
||||
idUser: '1',
|
||||
username: 'Student 1',
|
||||
|
|
@ -233,7 +233,7 @@ describe('ManageRoom', () => {
|
|||
|
||||
await act(async () => {
|
||||
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');
|
||||
|
|
@ -256,6 +256,7 @@ describe('ManageRoom', () => {
|
|||
});
|
||||
|
||||
await waitFor(() => {
|
||||
// console.info(consoleSpy.mock.calls);
|
||||
expect(consoleSpy).toHaveBeenCalledWith(
|
||||
'Received answer from Student 1 for question 1: Answer1'
|
||||
);
|
||||
|
|
@ -294,3 +295,4 @@ describe('ManageRoom', () => {
|
|||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import { QuestionType } from 'src/Types/QuestionType';
|
|||
import { AnswerSubmissionToBackendType } from 'src/services/WebsocketService';
|
||||
|
||||
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}`);
|
||||
|
||||
|
|
@ -23,9 +23,6 @@ const mockSubmitAnswer = jest.fn();
|
|||
const mockDisconnectWebSocket = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
// Clear local storage before each test
|
||||
// localStorage.clear();
|
||||
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<StudentModeQuiz
|
||||
|
|
@ -54,7 +51,7 @@ describe('StudentModeQuiz', () => {
|
|||
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 () => {
|
||||
|
|
@ -65,13 +62,13 @@ describe('StudentModeQuiz', () => {
|
|||
act(() => {
|
||||
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'});
|
||||
expect(firstButtonA).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();
|
||||
|
||||
// Navigate to the next question
|
||||
|
|
@ -87,12 +84,12 @@ describe('StudentModeQuiz', () => {
|
|||
});
|
||||
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
|
||||
// const buttonA = screen.getByRole("button", {name: '✅ A Option A'});
|
||||
const buttonA = screen.getByRole("button", {name: 'A Option A'});
|
||||
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'});
|
||||
expect(buttonB).toBeInTheDocument();
|
||||
// // "Option A" div inside the name of button should have selected class
|
||||
|
|
@ -122,4 +119,28 @@ describe('StudentModeQuiz', () => {
|
|||
expect(screen.getByText('Sample Question 2')).toBeInTheDocument();
|
||||
expect(screen.getByText('Répondre')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,11 +15,10 @@ interface Props {
|
|||
|
||||
const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
||||
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = props;
|
||||
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || '');
|
||||
|
||||
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || []);
|
||||
|
||||
let disableButton = false;
|
||||
if(handleOnSubmitAnswer === undefined){
|
||||
if (handleOnSubmitAnswer === undefined) {
|
||||
disableButton = true;
|
||||
}
|
||||
|
||||
|
|
@ -30,61 +29,81 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
|||
}, [passedAnswer]);
|
||||
|
||||
const handleOnClickAnswer = (choice: string) => {
|
||||
setAnswer(choice);
|
||||
setAnswer((prevAnswer) => {
|
||||
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 alphabet = alpha.map((x) => String.fromCharCode(x));
|
||||
return (
|
||||
|
||||
return (
|
||||
<div className="question-container">
|
||||
<div className="question content">
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
|
||||
</div>
|
||||
<div className="choices-wrapper mb-1">
|
||||
|
||||
{question.choices.map((choice, i) => {
|
||||
const selected = answer === choice.formattedText.text ? 'selected' : '';
|
||||
const selected = answer.includes(choice.formattedText.text) ? 'selected' : '';
|
||||
return (
|
||||
<div key={choice.formattedText.text + i} className="choice-container">
|
||||
<Button
|
||||
variant="text"
|
||||
className="button-wrapper"
|
||||
disabled={disableButton}
|
||||
onClick={() => !showAnswer && handleOnClickAnswer(choice.formattedText.text)}>
|
||||
{showAnswer? (<div> {(choice.isCorrect ? '✅' : '❌')}</div>)
|
||||
:``}
|
||||
onClick={() => !showAnswer && handleOnClickAnswer(choice.formattedText.text)}
|
||||
>
|
||||
{showAnswer ? (
|
||||
<div>{choice.isCorrect ? '✅' : '❌'}</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
<div className={`circle ${selected}`}>{alphabet[i]}</div>
|
||||
<div className={`answer-text ${selected}`}>
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(choice.formattedText) }} />
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: FormattedTextTemplate(choice.formattedText),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{choice.formattedFeedback && showAnswer && (
|
||||
<div className="feedback-container mb-1 mt-1/2">
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(choice.formattedFeedback) }} />
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: FormattedTextTemplate(choice.formattedFeedback),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{question.formattedGlobalFeedback && showAnswer && (
|
||||
<div className="global-feedback mb-2">
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: FormattedTextTemplate(question.formattedGlobalFeedback),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!showAnswer && handleOnSubmitAnswer && (
|
||||
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() =>
|
||||
answer !== "" && handleOnSubmitAnswer && handleOnSubmitAnswer(answer)
|
||||
answer.length > 0 && handleOnSubmitAnswer && handleOnSubmitAnswer(answer)
|
||||
}
|
||||
disabled={answer === '' || answer === null}
|
||||
disabled={answer.length === 0}
|
||||
>
|
||||
Répondre
|
||||
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ interface Props {
|
|||
const NumericalQuestionDisplay: React.FC<Props> = (props) => {
|
||||
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } =
|
||||
props;
|
||||
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || '');
|
||||
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || []);
|
||||
const correctAnswers = question.choices;
|
||||
let correctAnswer = '';
|
||||
|
||||
|
|
@ -69,7 +69,7 @@ const NumericalQuestionDisplay: React.FC<Props> = (props) => {
|
|||
id={question.formattedStem.text}
|
||||
name={question.formattedStem.text}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAnswer(e.target.valueAsNumber);
|
||||
setAnswer([e.target.valueAsNumber]);
|
||||
}}
|
||||
inputProps={{ 'data-testid': 'number-input' }}
|
||||
/>
|
||||
|
|
@ -87,7 +87,7 @@ const NumericalQuestionDisplay: React.FC<Props> = (props) => {
|
|||
handleOnSubmitAnswer &&
|
||||
handleOnSubmitAnswer(answer)
|
||||
}
|
||||
disabled={answer === "" || isNaN(answer as number)}
|
||||
disabled={answer === undefined || answer === null || isNaN(answer[0] as number)}
|
||||
>
|
||||
Répondre
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ interface Props {
|
|||
const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
|
||||
|
||||
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } = props;
|
||||
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || '');
|
||||
const [answer, setAnswer] = useState<AnswerType>(passedAnswer || []);
|
||||
|
||||
useEffect(() => {
|
||||
if (passedAnswer !== undefined) {
|
||||
|
|
@ -58,7 +58,7 @@ const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
|
|||
id={question.formattedStem.text}
|
||||
name={question.formattedStem.text}
|
||||
onChange={(e) => {
|
||||
setAnswer(e.target.value);
|
||||
setAnswer([e.target.value]);
|
||||
}}
|
||||
disabled={showAnswer}
|
||||
aria-label="short-answer-input"
|
||||
|
|
@ -72,7 +72,7 @@ const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
|
|||
handleOnSubmitAnswer &&
|
||||
handleOnSubmitAnswer(answer)
|
||||
}
|
||||
disabled={answer === null || answer === ''}
|
||||
disabled={answer === null || answer === undefined || answer.length === 0}
|
||||
>
|
||||
Répondre
|
||||
</Button>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
// TrueFalseQuestion.tsx
|
||||
import React, { useState,useEffect } from 'react';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import '../questionStyle.css';
|
||||
import { Button } from '@mui/material';
|
||||
import { TrueFalseQuestion } from 'gift-pegjs';
|
||||
|
|
@ -14,18 +14,18 @@ interface Props {
|
|||
}
|
||||
|
||||
const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
||||
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer} =
|
||||
const { question, showAnswer, handleOnSubmitAnswer, passedAnswer } =
|
||||
props;
|
||||
|
||||
let disableButton = false;
|
||||
if(handleOnSubmitAnswer === undefined){
|
||||
if (handleOnSubmitAnswer === undefined) {
|
||||
disableButton = true;
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
console.log("passedAnswer", answer);
|
||||
if (passedAnswer === true || passedAnswer === false) {
|
||||
setAnswer(passedAnswer);
|
||||
console.log("passedAnswer", passedAnswer);
|
||||
if (passedAnswer && (passedAnswer[0] === true || passedAnswer[0] === false)) {
|
||||
setAnswer(passedAnswer[0]);
|
||||
} else {
|
||||
setAnswer(undefined);
|
||||
}
|
||||
|
|
@ -33,8 +33,8 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
|||
|
||||
const [answer, setAnswer] = useState<boolean | undefined>(() => {
|
||||
|
||||
if (passedAnswer === true || passedAnswer === false) {
|
||||
return passedAnswer;
|
||||
if (passedAnswer && (passedAnswer[0] === true || passedAnswer[0] === false)) {
|
||||
return passedAnswer[0];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
|
@ -58,7 +58,7 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
|||
fullWidth
|
||||
disabled={disableButton}
|
||||
>
|
||||
{showAnswer? (<div> {(question.isTrue ? '✅' : '❌')}</div>):``}
|
||||
{showAnswer ? (<div> {(question.isTrue ? '✅' : '❌')}</div>) : ``}
|
||||
<div className={`circle ${selectedTrue}`}>V</div>
|
||||
<div className={`answer-text ${selectedTrue}`}>Vrai</div>
|
||||
|
||||
|
|
@ -75,7 +75,7 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
|||
disabled={disableButton}
|
||||
|
||||
>
|
||||
{showAnswer? (<div> {(!question.isTrue ? '✅' : '❌')}</div>):``}
|
||||
{showAnswer ? (<div> {(!question.isTrue ? '✅' : '❌')}</div>) : ``}
|
||||
<div className={`circle ${selectedFalse}`}>F</div>
|
||||
<div className={`answer-text ${selectedFalse}`}>Faux</div>
|
||||
|
||||
|
|
@ -95,8 +95,7 @@ const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
|||
<Button
|
||||
variant="contained"
|
||||
onClick={() =>
|
||||
answer !== undefined && handleOnSubmitAnswer && handleOnSubmitAnswer(answer)
|
||||
|
||||
answer !== undefined && handleOnSubmitAnswer && handleOnSubmitAnswer([answer])
|
||||
}
|
||||
disabled={answer === undefined}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import LoginContainer from 'src/components/LoginContainer/LoginContainer'
|
|||
|
||||
import ApiService from '../../../services/ApiService'
|
||||
|
||||
export type AnswerType = string | number | boolean;
|
||||
export type AnswerType = Array<string | number | boolean>;
|
||||
|
||||
const JoinRoom: React.FC = () => {
|
||||
const [roomName, setRoomName] = useState('');
|
||||
|
|
|
|||
|
|
@ -1,12 +1,7 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Socket } from 'socket.io-client';
|
||||
import { ParsedGIFTQuestion, BaseQuestion, parse, Question } from 'gift-pegjs';
|
||||
import {
|
||||
isSimpleNumericalAnswer,
|
||||
isRangeNumericalAnswer,
|
||||
isHighLowNumericalAnswer
|
||||
} from 'gift-pegjs/typeGuards';
|
||||
import { BaseQuestion, parse, Question } from 'gift-pegjs';
|
||||
import LiveResultsComponent from 'src/components/LiveResults/LiveResults';
|
||||
import webSocketService, {
|
||||
AnswerReceptionFromBackendType
|
||||
|
|
@ -24,7 +19,7 @@ import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay';
|
|||
import ApiService from '../../../services/ApiService';
|
||||
import { QuestionType } from 'src/Types/QuestionType';
|
||||
import { Button } from '@mui/material';
|
||||
import { AnswerType } from 'src/pages/Student/JoinRoom/JoinRoom';
|
||||
import { checkIfIsCorrect } from './useRooms';
|
||||
|
||||
const ManageRoom: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -346,63 +341,6 @@ const ManageRoom: React.FC = () => {
|
|||
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) {
|
||||
return (
|
||||
<div className="center">
|
||||
|
|
|
|||
|
|
@ -1,8 +1,15 @@
|
|||
import { useContext } from 'react';
|
||||
import { RoomType } from 'src/Types/RoomType';
|
||||
import { createContext } from 'react';
|
||||
|
||||
//import { RoomContext } from './RoomContext';
|
||||
import { MultipleNumericalAnswer, NumericalAnswer, ParsedGIFTQuestion } from 'gift-pegjs';
|
||||
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 = {
|
||||
rooms: RoomType[];
|
||||
|
|
@ -18,3 +25,136 @@ export const useRooms = () => {
|
|||
if (!context) throw new Error('useRooms must be used within a RoomProvider');
|
||||
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 correctAnswers = question.choices.filter((choice) => choice.isCorrect
|
||||
/* || (choice.weight && choice.weight > 0)*/ // handle weighted answers
|
||||
);
|
||||
const multipleAnswers = Array.isArray(answer) ? answer : [answer as string];
|
||||
if (correctAnswers.length === 0) {
|
||||
return false;
|
||||
}
|
||||
return correctAnswers.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