From cb46a1837026fa37d2d164d47bb34cb645c03d23 Mon Sep 17 00:00:00 2001 From: "C. Fuhrman" Date: Sat, 25 Jan 2025 02:02:18 -0500 Subject: [PATCH] Renames, passing many tests --- client/src/Types/QuestionType.tsx | 5 + .../components/GiftTemplate/TextType.test.ts | 24 +- .../templates/AnswerIcon.test.tsx | 2 +- .../templates/MultipleChoice.test.tsx | 81 +++--- .../GiftTemplate/templates/Numerical.test.tsx | 74 ++--- .../templates/ShortAnswer.test.tsx | 68 ++--- .../GiftTemplate/templates/TrueFalse.test.tsx | 57 +--- .../__snapshots__/ShortAnswer.test.tsx.snap | 9 +- .../TrueFalseQuestionDisplay.test.tsx | 12 +- .../StudentModeQuiz/StudentModeQuiz.test.tsx | 8 +- client/src/components/GiftTemplate/index.ts | 265 +----------------- .../{AnswerIcon.ts => AnswerIconTemplate.ts} | 0 .../GiftTemplate/templates/Category.ts | 14 - .../templates/CategoryTemplate.ts | 15 + .../templates/DescriptionTemplate.ts | 6 +- .../templates/{Error.ts => ErrorTemplate.ts} | 0 .../GiftTemplate/templates/Essay.ts | 27 -- .../GiftTemplate/templates/EssayTemplate.ts | 27 ++ ...lFeedback.ts => GlobalFeedbackTemplate.ts} | 15 +- .../{Matching.ts => MatchingTemplate.ts} | 31 +- .../GiftTemplate/templates/MultipleChoice.ts | 31 -- ...rs.ts => MultipleChoiceAnswersTemplate.ts} | 29 +- .../templates/MultipleChoiceTemplate.ts | 30 ++ .../GiftTemplate/templates/Numerical.ts | 60 ---- .../templates/NumericalTemplate.ts | 60 ++++ ...tainer.ts => QuestionContainerTemplate.ts} | 0 ...{ShortAnswer.ts => ShortAnswerTemplate.ts} | 29 +- .../{TextType.ts => TextTypeTemplate.ts} | 1 + .../templates/{Title.ts => TitleTemplate.ts} | 3 +- .../GiftTemplate/templates/TrueFalse.ts | 54 ---- .../templates/TrueFalseTemplate.ts | 48 ++++ .../GiftTemplate/templates/index.ts | 61 ++-- .../components/LiveResults/LiveResults.tsx | 2 +- .../MultipleChoiceQuestionDisplay.tsx | 12 +- .../NumericalQuestionDisplay.tsx | 8 +- .../QuestionsDisplay/QuestionDisplay.tsx | 9 +- .../ShortAnswerQuestionDisplay.tsx | 6 +- .../TrueFalseQuestionDisplay.tsx | 37 ++- .../StudentModeQuiz/StudentModeQuiz.tsx | 5 +- .../TeacherModeQuiz/TeacherModeQuiz.tsx | 7 +- .../pages/Teacher/ManageRoom/ManageRoom.tsx | 75 ++--- client/tsconfig.json | 26 +- 42 files changed, 516 insertions(+), 817 deletions(-) create mode 100644 client/src/Types/QuestionType.tsx rename client/src/components/GiftTemplate/templates/{AnswerIcon.ts => AnswerIconTemplate.ts} (100%) delete mode 100644 client/src/components/GiftTemplate/templates/Category.ts create mode 100644 client/src/components/GiftTemplate/templates/CategoryTemplate.ts rename client/src/components/GiftTemplate/templates/{Error.ts => ErrorTemplate.ts} (100%) delete mode 100644 client/src/components/GiftTemplate/templates/Essay.ts create mode 100644 client/src/components/GiftTemplate/templates/EssayTemplate.ts rename client/src/components/GiftTemplate/templates/{GlobalFeedback.ts => GlobalFeedbackTemplate.ts} (59%) rename client/src/components/GiftTemplate/templates/{Matching.ts => MatchingTemplate.ts} (77%) delete mode 100644 client/src/components/GiftTemplate/templates/MultipleChoice.ts rename client/src/components/GiftTemplate/templates/{MultipleChoiceAnswers.ts => MultipleChoiceAnswersTemplate.ts} (71%) create mode 100644 client/src/components/GiftTemplate/templates/MultipleChoiceTemplate.ts delete mode 100644 client/src/components/GiftTemplate/templates/Numerical.ts create mode 100644 client/src/components/GiftTemplate/templates/NumericalTemplate.ts rename client/src/components/GiftTemplate/templates/{QuestionContainer.ts => QuestionContainerTemplate.ts} (100%) rename client/src/components/GiftTemplate/templates/{ShortAnswer.ts => ShortAnswerTemplate.ts} (53%) rename client/src/components/GiftTemplate/templates/{TextType.ts => TextTypeTemplate.ts} (99%) rename client/src/components/GiftTemplate/templates/{Title.ts => TitleTemplate.ts} (94%) delete mode 100644 client/src/components/GiftTemplate/templates/TrueFalse.ts create mode 100644 client/src/components/GiftTemplate/templates/TrueFalseTemplate.ts diff --git a/client/src/Types/QuestionType.tsx b/client/src/Types/QuestionType.tsx new file mode 100644 index 0000000..94f8cc6 --- /dev/null +++ b/client/src/Types/QuestionType.tsx @@ -0,0 +1,5 @@ +import { BaseQuestion } from "gift-pegjs"; + +export interface QuestionType { + question: BaseQuestion; +} diff --git a/client/src/__tests__/components/GiftTemplate/TextType.test.ts b/client/src/__tests__/components/GiftTemplate/TextType.test.ts index 7a9993f..109852e 100644 --- a/client/src/__tests__/components/GiftTemplate/TextType.test.ts +++ b/client/src/__tests__/components/GiftTemplate/TextType.test.ts @@ -1,16 +1,14 @@ -// TextType.test.ts - -import { textType } from "src/components/GiftTemplate/templates/TextType"; +import { textType } from "src/components/GiftTemplate/templates/TextTypeTemplate"; import { TextFormat } from "gift-pegjs"; describe('TextType', () => { it('should format text with basic characters correctly', () => { const input: TextFormat = { text: 'Hello, world! 5 > 3, right?', - format: 'plain' + format: 'moodle' }; const expectedOutput = 'Hello, world! 5 > 3, right?'; - expect(textType({ text: input })).toBe(expectedOutput); + expect(textType(input)).toBe(expectedOutput); }); it('should format text with newlines correctly', () => { @@ -19,7 +17,7 @@ describe('TextType', () => { format: 'plain' }; const expectedOutput = 'Hello,
world!
5 > 3, right?'; - expect(textType({ text: input })).toBe(expectedOutput); + expect(textType(input)).toBe(expectedOutput); }); it('should format text with LaTeX correctly', () => { @@ -33,17 +31,17 @@ describe('TextType', () => { // by running the test and copying the "Received string:" in jest output // when it fails (assuming the output is correct) const expectedOutput = 'E=mc2E=mc^2'; - expect(textType({ text: input })).toContain(expectedOutput); + expect(textType(input)).toContain(expectedOutput); }); it('should format text with two equations (inline and separate) correctly', () => { const input: TextFormat = { text: '$a + b = c$ ? $$E=mc^2$$', - format: 'plain' + format: 'moodle' }; // hint: katex-display is the class that indicates a separate equation const expectedOutput = 'a+b=ca + b = c ? E=mc2E=mc^2'; - expect(textType({ text: input })).toContain(expectedOutput); + expect(textType(input)).toContain(expectedOutput); }); it('should format text with a katex matrix correctly', () => { @@ -53,7 +51,7 @@ describe('TextType', () => { format: 'plain' }; const expectedOutput = 'Donnez le déterminant de la matrice suivante.\\begin{pmatrix}
a&b \\\\
c&d
\\end{pmatrix}'; - expect(textType({ text: input })).toContain(expectedOutput); + expect(textType(input)).toContain(expectedOutput); }); it('should format text with Markdown correctly', () => { @@ -63,7 +61,7 @@ describe('TextType', () => { }; // TODO: investigate why the output has an extra newline const expectedOutput = 'Bold\n'; - expect(textType({ text: input })).toBe(expectedOutput); + expect(textType(input)).toBe(expectedOutput); }); it('should format text with HTML correctly', () => { @@ -72,7 +70,7 @@ describe('TextType', () => { format: 'html' }; const expectedOutput = 'yes'; - expect(textType({ text: input })).toBe(expectedOutput); + expect(textType(input)).toBe(expectedOutput); }); it('should format plain text correctly', () => { @@ -81,7 +79,7 @@ describe('TextType', () => { format: 'plain' }; const expectedOutput = 'Just plain text'; - expect(textType({ text: input })).toBe(expectedOutput); + expect(textType(input)).toBe(expectedOutput); }); // Add more tests for other formats if needed diff --git a/client/src/__tests__/components/GiftTemplate/templates/AnswerIcon.test.tsx b/client/src/__tests__/components/GiftTemplate/templates/AnswerIcon.test.tsx index 0a9226b..efaed61 100644 --- a/client/src/__tests__/components/GiftTemplate/templates/AnswerIcon.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/templates/AnswerIcon.test.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { render } from '@testing-library/react'; import '@testing-library/jest-dom'; -import AnswerIcon from 'src/components/GiftTemplate/templates/AnswerIcon'; +import AnswerIcon from 'src/components/GiftTemplate/templates/AnswerIconTemplate'; import DOMPurify from 'dompurify'; describe('AnswerIcon', () => { diff --git a/client/src/__tests__/components/GiftTemplate/templates/MultipleChoice.test.tsx b/client/src/__tests__/components/GiftTemplate/templates/MultipleChoice.test.tsx index 53c86c0..bdecb06 100644 --- a/client/src/__tests__/components/GiftTemplate/templates/MultipleChoice.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/templates/MultipleChoice.test.tsx @@ -2,99 +2,100 @@ import React from 'react'; import { render } from '@testing-library/react'; import '@testing-library/jest-dom'; import { MultipleChoice } from 'src/components/GiftTemplate/templates'; -import { TemplateOptions, MultipleChoice as MultipleChoiceType } from 'src/components/GiftTemplate/templates/types'; +import { TemplateOptions } from 'src/components/GiftTemplate/templates/types'; +import { MultipleChoiceQuestion } from 'gift-pegjs'; // Mock the nanoid function jest.mock('nanoid', () => ({ nanoid: jest.fn(() => 'mocked-id') })); -const mockProps: TemplateOptions & MultipleChoiceType = { +const mockProps: TemplateOptions & MultipleChoiceQuestion = { type: 'MC', hasEmbeddedAnswers: false, title: 'Sample Title', - stem: { format: 'plain' , text: 'Sample Stem'}, + formattedStem: { format: 'plain' , text: 'Sample Stem'}, choices: [ - { text: { format: 'plain' , text: 'Choice 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }, - { text: { format: 'plain', text: 'Choice 2' }, isCorrect: false, feedback: { format: 'plain' , text: 'InCorrect!'}, weight: 1 } + { formattedText: { format: 'plain' , text: 'Choice 1'}, isCorrect: true, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }, + { formattedText: { format: 'plain', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { format: 'plain' , text: 'InCorrect!'}, weight: 1 } ], - globalFeedback: { format: 'plain', text: 'Sample Global Feedback' } + formattedGlobalFeedback: { format: 'plain', text: 'Sample Global Feedback' } }; -const katekMock: TemplateOptions & MultipleChoiceType = { +const katekMock: TemplateOptions & MultipleChoiceQuestion = { type: 'MC', hasEmbeddedAnswers: false, title: 'Sample Title', - stem: { format: 'plain' , text: '$$\\frac{zzz}{yyy}$$'}, + formattedStem: { format: 'plain' , text: '$$\\frac{zzz}{yyy}$$'}, choices: [ - { text: { format: 'plain' , text: 'Choice 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }, - { text: { format: 'plain', text: 'Choice 2' }, isCorrect: true, feedback: { 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 } ], - globalFeedback: { format: 'plain', text: 'Sample Global Feedback' } + formattedGlobalFeedback: { format: 'plain', text: 'Sample Global Feedback' } }; -const imageMock: TemplateOptions & MultipleChoiceType = { +const imageMock: TemplateOptions & MultipleChoiceQuestion = { type: 'MC', hasEmbeddedAnswers: false, title: 'Sample Title with Image', - stem: { format: 'plain', text: 'Sample Stem with Image' }, + formattedStem: { format: 'plain', text: 'Sample Stem with Image' }, choices: [ - { text: { format: 'plain', text: 'Choice 1' }, isCorrect: true, feedback: { format: 'plain', text: 'Correct!' }, weight: 1 }, - { text: { format: 'plain', text: 'Choice 2' }, isCorrect: false, feedback: { format: 'plain', text: 'Incorrect!' }, weight: 1 }, - { text: { format: 'plain', text: 'Sample Image' }, isCorrect: false, feedback: { format: 'plain', text: 'Image Feedback' }, weight: 1 } + { formattedText: { format: 'plain', text: 'Choice 1' }, isCorrect: true, formattedFeedback: { format: 'plain', text: 'Correct!' }, weight: 1 }, + { formattedText: { format: 'plain', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { format: 'plain', text: 'Incorrect!' }, weight: 1 }, + { formattedText: { format: 'plain', text: 'Sample Image' }, isCorrect: false, formattedFeedback: { format: 'plain', text: 'Image Feedback' }, weight: 1 } ], - globalFeedback: { format: 'plain', text: 'Sample Global Feedback with Image' } + formattedGlobalFeedback: { format: 'plain', text: 'Sample Global Feedback with Image' } }; -const mockMoodle: TemplateOptions & MultipleChoiceType = { +const mockMoodle: TemplateOptions & MultipleChoiceQuestion = { type: 'MC', hasEmbeddedAnswers: false, title: 'Sample Title', - stem: { format: 'moodle' , text: 'Sample Stem'}, + formattedStem: { format: 'moodle' , text: 'Sample Stem'}, choices: [ - { text: { format: 'moodle' , text: 'Choice 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }, - { text: { format: 'plain', text: 'Choice 2' }, isCorrect: false, feedback: { format: 'plain' , text: 'InCorrect!'}, weight: 1 } + { formattedText: { format: 'moodle' , text: 'Choice 1'}, isCorrect: true, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }, + { formattedText: { format: 'plain', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { format: 'plain' , text: 'InCorrect!'}, weight: 1 } ], - globalFeedback: { format: 'plain', text: 'Sample Global Feedback' } + formattedGlobalFeedback: { format: 'plain', text: 'Sample Global Feedback' } }; -const mockHTML: TemplateOptions & MultipleChoiceType = { +const mockHTML: TemplateOptions & MultipleChoiceQuestion = { type: 'MC', hasEmbeddedAnswers: false, title: 'Sample Title', - stem: { format: 'html' , text: '$$\\frac{zzz}{yyy}$$'}, + formattedStem: { format: 'html' , text: '$$\\frac{zzz}{yyy}$$'}, choices: [ - { text: { format: 'html' , text: 'Choice 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }, - { text: { format: 'html', text: 'Choice 2' }, isCorrect: false, feedback: { format: 'plain' , text: 'InCorrect!'}, weight: 1 } + { formattedText: { format: 'html' , text: 'Choice 1'}, isCorrect: true, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }, + { formattedText: { format: 'html', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { format: 'plain' , text: 'InCorrect!'}, weight: 1 } ], - globalFeedback: { format: 'html', text: 'Sample Global Feedback' } + formattedGlobalFeedback: { format: 'html', text: 'Sample Global Feedback' } }; -const mockMarkdown: TemplateOptions & MultipleChoiceType = { +const mockMarkdown: TemplateOptions & MultipleChoiceQuestion = { type: 'MC', hasEmbeddedAnswers: false, title: 'Sample Title with Image', - stem: { format: 'markdown', text: 'Sample Stem with Image' }, + formattedStem: { format: 'markdown', text: 'Sample Stem with Image' }, choices: [ - { text: { format: 'markdown', text: 'Choice 1' }, isCorrect: true, feedback: { format: 'plain', text: 'Correct!' }, weight: 1 }, - { text: { format: 'markdown', text: 'Choice 2' }, isCorrect: false, feedback: { format: 'plain', text: 'Incorrect!' }, weight: 1 }, - { text: { format: 'markdown', text: 'Sample Image' }, isCorrect: false, feedback: { format: 'plain', text: 'Image Feedback' }, weight: 1 } + { formattedText: { format: 'markdown', text: 'Choice 1' }, isCorrect: true, formattedFeedback: { format: 'plain', text: 'Correct!' }, weight: 1 }, + { formattedText: { format: 'markdown', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { format: 'plain', text: 'Incorrect!' }, weight: 1 }, + { formattedText: { format: 'markdown', text: 'Sample Image' }, isCorrect: false, formattedFeedback: { format: 'plain', text: 'Image Feedback' }, weight: 1 } ], - globalFeedback: { format: 'markdown', text: 'Sample Global Feedback with Image' } + formattedGlobalFeedback: { format: 'markdown', text: 'Sample Global Feedback with Image' } }; -const mockMarkdownTwoImages: TemplateOptions & MultipleChoiceType = { +const mockMarkdownTwoImages: TemplateOptions & MultipleChoiceQuestion = { type: 'MC', hasEmbeddedAnswers: false, title: 'Sample Title with Image', - stem: { format: 'markdown', text: 'Sample Image' }, + formattedStem: { format: 'markdown', text: 'Sample Image' }, choices: [ - { text: { format: 'markdown', text: 'Choice 1' }, isCorrect: true, feedback: { format: 'plain', text: 'Correct!' }, weight: 1 }, - { text: { format: 'markdown', text: 'Choice 2' }, isCorrect: false, feedback: { format: 'plain', text: 'Incorrect!' }, weight: 1 }, - { text: { format: 'markdown', text: 'Sample Image' }, isCorrect: false, feedback: { format: 'plain', text: 'Image Feedback' }, weight: 1 } + { formattedText: { format: 'markdown', text: 'Choice 1' }, isCorrect: true, formattedFeedback: { format: 'plain', text: 'Correct!' }, weight: 1 }, + { formattedText: { format: 'markdown', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { format: 'plain', text: 'Incorrect!' }, weight: 1 }, + { formattedText: { format: 'markdown', text: 'Sample Image' }, isCorrect: false, formattedFeedback: { format: 'plain', text: 'Image Feedback' }, weight: 1 } ], - globalFeedback: { format: 'markdown', text: 'Sample Global Feedback with Image' } + formattedGlobalFeedback: { format: 'markdown', text: 'Sample Global Feedback with Image' } }; test('MultipleChoice snapshot test', () => { @@ -130,4 +131,4 @@ test('MultipleChoice snapshot test with image using markdown text format', () => test('MultipleChoice snapshot test with 2 images using markdown text format', () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); -}); \ No newline at end of file +}); diff --git a/client/src/__tests__/components/GiftTemplate/templates/Numerical.test.tsx b/client/src/__tests__/components/GiftTemplate/templates/Numerical.test.tsx index e28f80f..77a3277 100644 --- a/client/src/__tests__/components/GiftTemplate/templates/Numerical.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/templates/Numerical.test.tsx @@ -1,62 +1,40 @@ import React from 'react'; import { render } from '@testing-library/react'; import '@testing-library/jest-dom'; -import Numerical from 'src/components/GiftTemplate/templates/Numerical'; -import { TemplateOptions, Numerical as NumericalType } from 'src/components/GiftTemplate/templates/types'; +import Numerical from 'src/components/GiftTemplate/templates/NumericalTemplate'; +import { TemplateOptions } from 'src/components/GiftTemplate/templates/types'; +import { parse, NumericalQuestion } from 'gift-pegjs'; // Mock the nanoid function jest.mock('nanoid', () => ({ nanoid: jest.fn(() => 'mocked-id') })); -const plainTextMock: TemplateOptions & NumericalType = { - type: 'Numerical', - hasEmbeddedAnswers: false, - title: 'Sample Numerical Title', - stem: { format: 'plain', text: 'Sample Stem' }, - choices: [ - { isCorrect: true, weight: 1, text: { type: 'simple', number: 42}, feedback: { format: 'plain', text: 'Correct!' } }, - { isCorrect: false, weight: 1, text: { type: 'simple', number: 43}, feedback: { format: 'plain', text: 'Incorrect!' } } - ], - globalFeedback: { format: 'plain', text: 'Sample Global Feedback' } -}; +const plainTextMock: TemplateOptions & NumericalQuestion = + parse(` + ::Sample Numerical Title:: Sample Stem {#=42#Correct!=43#Incorrect!####Sample Global Feedback} + `)[0] as NumericalQuestion; -const htmlMock: TemplateOptions & NumericalType = { - type: 'Numerical', - hasEmbeddedAnswers: false, - title: 'Sample Numerical Title', - stem: { format: 'html', text: '$$\\frac{zzz}{yyy}$$' }, - choices: [ - { isCorrect: true, weight: 1, text: { type: 'simple', number: 42}, feedback: { format: 'html', text: 'Correct!' } }, - { isCorrect: false, weight: 1, text: { type: 'simple', number: 43}, feedback: { format: 'html', text: 'Incorrect!' } } - ], - globalFeedback: { format: 'html', text: 'Sample Global Feedback' } -}; +const htmlMock: TemplateOptions & NumericalQuestion = + parse(` + ::Sample Numerical Title:: + [html]$$\\frac\\{zzz\\}\\{yyy\\}$$ {# + =42#Correct + =43#Incorrect! + ####Sample Global Feedback + } + `)[0] as NumericalQuestion; -const moodleMock: TemplateOptions & NumericalType = { - type: 'Numerical', - hasEmbeddedAnswers: false, - title: 'Sample Numerical Title', - stem: { format: 'moodle', text: 'Sample Stem' }, - choices: [ - { isCorrect: true, weight: 1, text: { type: 'simple', number: 42}, feedback: { format: 'moodle', text: 'Correct!' } }, - { isCorrect: false, weight: 1, text: { type: 'simple', number: 43}, feedback: { format: 'moodle', text: 'Incorrect!' } } - ], - globalFeedback: { format: 'moodle', text: 'Sample Global Feedback' } -}; +const moodleMock: TemplateOptions & NumericalQuestion = + parse(` + ::Sample Numerical Title::[moodle]Sample Stem {#=42#Correct!=43#Incorrect!####Sample Global Feedback} + `)[0] as NumericalQuestion; + +const imageMock: TemplateOptions & NumericalQuestion = + parse(` + ::Sample Numerical Title with Image::[markdown]Sample Stem with Image ![](https\\://example.com/cat.jpg){#=42#Correct!=43#Incorrect! ![](https\\://example.com/cat.jpg)####Sample Global Feedback with Image} + `)[0] as NumericalQuestion; -const imageMock: TemplateOptions & NumericalType = { - type: 'Numerical', - hasEmbeddedAnswers: false, - title: 'Sample Numerical Title with Image', - stem: { format: 'plain', text: 'Sample Stem with Image' }, - choices: [ - { isCorrect: true, weight: 1, text: { type: 'simple', number: 42}, feedback: { format: 'plain', text: 'Correct!' } }, - { isCorrect: false, weight: 1, text: { type: 'simple', number: 43}, feedback: { format: 'plain', text: 'Incorrect!' } }, - { isCorrect: false, weight: 1, text: { type: 'simple', number: 44}, feedback: { format: 'plain', text: 'Sample Image' } } - ], - globalFeedback: { format: 'plain', text: 'Sample Global Feedback with Image' } -}; test('Numerical snapshot test with plain text', () => { const { asFragment } = render(); @@ -76,4 +54,4 @@ test('Numerical snapshot test with moodle', () => { test('Numerical snapshot test with image', () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); -}); \ No newline at end of file +}); diff --git a/client/src/__tests__/components/GiftTemplate/templates/ShortAnswer.test.tsx b/client/src/__tests__/components/GiftTemplate/templates/ShortAnswer.test.tsx index 9928099..34c444b 100644 --- a/client/src/__tests__/components/GiftTemplate/templates/ShortAnswer.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/templates/ShortAnswer.test.tsx @@ -1,63 +1,35 @@ import React from 'react'; import { render } from '@testing-library/react'; import '@testing-library/jest-dom'; -import ShortAnswer from 'src/components/GiftTemplate/templates/ShortAnswer'; -import { TemplateOptions, ShortAnswer as ShortAnswerType } from 'src/components/GiftTemplate/templates/types'; +import ShortAnswer from 'src/components/GiftTemplate/templates/ShortAnswerTemplate'; +import { TemplateOptions } from 'src/components/GiftTemplate/templates/types'; +import { parse, ShortAnswerQuestion } from 'gift-pegjs'; // Mock the nanoid function jest.mock('nanoid', () => ({ nanoid: jest.fn(() => 'mocked-id') })); -const plainTextMock: TemplateOptions & ShortAnswerType = { - type: 'Short', - hasEmbeddedAnswers: false, - title: 'Sample Short Answer Title', - stem: { format: 'plain', text: 'Sample Stem' }, - choices: [ - { text: { format: 'plain' , text: 'Answer 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }, - { text: { format: 'plain' , text: 'Answer 2'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 } - ], - globalFeedback: { format: 'plain', text: 'Sample Global Feedback' } -}; +const plainTextMock: TemplateOptions & ShortAnswerQuestion = + parse(` + ::Sample Short Answer Title:: Sample Stem {=%1%Answer 1#Correct! =%1%Answer 2#Correct!####Sample Global Feedback} + `)[0] as ShortAnswerQuestion; -const katexMock: TemplateOptions & ShortAnswerType = { - type: 'Short', - hasEmbeddedAnswers: false, - title: 'Sample Short Answer Title', - stem: { format: 'html', text: '$$\\frac{zzz}{yyy}$$' }, - choices: [ - { text: { format: 'html' , text: 'Answer 1'}, isCorrect: true, feedback: { format: 'html' , text: 'Correct!'}, weight: 1 }, - { text: { format: 'html' , text: 'Answer 2'}, isCorrect: true, feedback: { format: 'moodle' , text: 'Correct!'}, weight: 1 } - ], - globalFeedback: { format: 'html', text: 'Sample Global Feedback' } -}; +const katexMock: TemplateOptions & ShortAnswerQuestion = + parse(` + ::Sample Short Answer Title:: $$\\frac\\{zzz\\}\\{yyy\\}$$ {=%1%Answer 1#Correct! =%1%Answer 2#Correct!####[html]Sample Global Feedback} + `)[0] as ShortAnswerQuestion; -const moodleMock: TemplateOptions & ShortAnswerType = { - type: 'Short', - hasEmbeddedAnswers: false, - title: 'Sample Short Answer Title', - stem: { format: 'moodle', text: 'Sample Stem' }, - choices: [ - { text: { format: 'moodle' , text: 'Answer 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }, - { text: { format: 'moodle' , text: 'Answer 2'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 } - ], - globalFeedback: { format: 'moodle', text: 'Sample Global Feedback' } -}; -const imageMock: TemplateOptions & ShortAnswerType = { - type: 'Short', - hasEmbeddedAnswers: false, - title: 'Sample Short Answer Title with Image', - stem: { format: 'markdown', text: 'Sample Stem with Image' }, - choices: [ - { text: { format: 'markdown', text: 'Answer 1' }, isCorrect: true, feedback: { format: 'plain', text: 'Correct!' }, weight: 1 }, - { text: { format: 'markdown', text: 'Answer 2' }, isCorrect: true, feedback: { format: 'plain', text: 'Correct!' }, weight: 1 }, - { text: { format: 'markdown', text: 'Sample Image' }, isCorrect: true, feedback: { format: 'plain', text: 'Correct!' }, weight: 1 } - ], - globalFeedback: { format: 'plain', text: 'Sample Global Feedback with Image' } -}; +const moodleMock: TemplateOptions & ShortAnswerQuestion = + parse(` + ::Sample Short Answer Title:: Sample Stem {=%1%Answer 1#Correct! =%1%Answer 2#Correct!####[moodle]Sample Global Feedback} + `)[0] as ShortAnswerQuestion; +const imageMock: TemplateOptions & ShortAnswerQuestion = + parse(` + ::Sample Short Answer Title with Image::[markdown]Sample Stem with Image ![](https\\://example.com/cat.jpg) {=%1%Answer 1#Correct! =%1%Answer 2#Correct!####Sample Global Feedback with Image} + `)[0] as ShortAnswerQuestion; test('ShortAnswer snapshot test with plain text', () => { const { asFragment } = render(); @@ -77,4 +49,4 @@ test('ShortAnswer snapshot test with moodle', () => { test('ShortAnswer snapshot test with image', () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); -}); \ No newline at end of file +}); diff --git a/client/src/__tests__/components/GiftTemplate/templates/TrueFalse.test.tsx b/client/src/__tests__/components/GiftTemplate/templates/TrueFalse.test.tsx index bbb717e..8804c4d 100644 --- a/client/src/__tests__/components/GiftTemplate/templates/TrueFalse.test.tsx +++ b/client/src/__tests__/components/GiftTemplate/templates/TrueFalse.test.tsx @@ -2,56 +2,27 @@ import React from 'react'; import { render } from '@testing-library/react'; import '@testing-library/jest-dom'; import TrueFalse from 'src/components/GiftTemplate/templates'; -import { TemplateOptions, TrueFalse as TrueFalseType } from 'src/components/GiftTemplate/templates/types'; +import { TemplateOptions } from 'src/components/GiftTemplate/templates/types'; +import { parse, ShortAnswerQuestion, TrueFalseQuestion } from 'gift-pegjs'; // Mock the nanoid function jest.mock('nanoid', () => ({ nanoid: jest.fn(() => 'mocked-id') - })); +})); -const plainTextMock: TemplateOptions & TrueFalseType = { - type: 'TF', - hasEmbeddedAnswers: false, - title: 'Sample True/False Title', - stem: { format: 'plain', text: 'Sample Stem' }, - isTrue: true, - trueFeedback: { format: 'plain', text: 'Correct!' }, - falseFeedback: { format: 'plain', text: 'Incorrect!' }, - globalFeedback: { format: 'plain', text: 'Sample Global Feedback' } -}; +const plainTextMock: TemplateOptions & TrueFalseQuestion = + parse(`::Sample True/False Title::Sample Stem {T#Correct!#Incorrect!####Sample Global Feedback}`)[0] as TrueFalseQuestion; -const katexMock: TemplateOptions & TrueFalseType = { - type: 'TF', - hasEmbeddedAnswers: false, - title: 'Sample True/False Title', - stem: { format: 'html', text: '$$\\frac{zzz}{yyy}$$' }, - isTrue: true, - trueFeedback: { format: 'moodle', text: 'Correct!' }, - falseFeedback: { format: 'html', text: 'Incorrect!' }, - globalFeedback: { format: 'markdown', text: 'Sample Global Feedback' } -}; +const katexMock: TemplateOptions & TrueFalseQuestion = + parse(`::Sample True/False Title::$$\\frac{zzz}{yyy}$$ {T#Correct!#Incorrect!####Sample Global Feedback}`)[0] as TrueFalseQuestion; -const moodleMock: TemplateOptions & TrueFalseType = { - type: 'TF', - hasEmbeddedAnswers: false, - title: 'Sample True/False Title', - stem: { format: 'moodle', text: 'Sample Stem' }, - isTrue: true, - trueFeedback: { format: 'moodle', text: 'Correct!' }, - falseFeedback: { format: 'moodle', text: 'Incorrect!' }, - globalFeedback: { format: 'moodle', text: 'Sample Global Feedback' } -}; +const moodleMock: TemplateOptions & TrueFalseQuestion = + parse(`::Sample True/False Title::[moodle]Sample Stem{TRUE#Correct!#Incorrect!####Sample Global Feedback}`)[0] as TrueFalseQuestion; -const imageMock: TemplateOptions & TrueFalseType = { - type: 'TF', - hasEmbeddedAnswers: false, - title: 'Sample Short Answer Title with Image', - stem: { format: 'plain', text: 'Sample Stem with Image' }, - trueFeedback: { format: 'moodle', text: 'Correct!' }, - isTrue: true, - falseFeedback: { format: 'moodle', text: 'Incorrect!' }, - globalFeedback: { format: 'plain', text: 'Sample Image' } -}; +const imageMock: TemplateOptions & ShortAnswerQuestion = + parse(`::Sample Short Answer Title with Image:: + [markdown]Sample Stem with Image ![](https\\://example.com/cat.gif) + {=A =B =C####[html]Sample Image}`)[0] as ShortAnswerQuestion; test('TrueFalse snapshot test with plain text', () => { const { asFragment } = render(); @@ -71,4 +42,4 @@ test('TrueFalse snapshot test with moodle', () => { test('TrueFalse snapshot test with image', () => { const { asFragment } = render(); expect(asFragment()).toMatchSnapshot(); -}); \ No newline at end of file +}); diff --git a/client/src/__tests__/components/GiftTemplate/templates/__snapshots__/ShortAnswer.test.tsx.snap b/client/src/__tests__/components/GiftTemplate/templates/__snapshots__/ShortAnswer.test.tsx.snap index 6204be4..f70b8d5 100644 --- a/client/src/__tests__/components/GiftTemplate/templates/__snapshots__/ShortAnswer.test.tsx.snap +++ b/client/src/__tests__/components/GiftTemplate/templates/__snapshots__/ShortAnswer.test.tsx.snap @@ -40,7 +40,7 @@ exports[`ShortAnswer snapshot test with image 1`] = ` </div> <p style=" color: hsl(0, 0%, 0%); -">Sample Stem with Image +">Sample Stem with Image <img src="https://example.com/cat.jpg" alt=""> </p> <div> <span style=" @@ -59,9 +59,7 @@ exports[`ShortAnswer snapshot test with image 1`] = ` font-size: inherit; line-height: inherit; width: 90%; -" placeholder="Answer 1 -, Answer 2 -, <img src="https://via.placeholder.com/150" alt="Sample Image" />"> +" placeholder="Answer 1, Answer 2"> </div> <div style=" position: relative; @@ -73,7 +71,8 @@ exports[`ShortAnswer snapshot test with image 1`] = ` border-radius: 6px; box-shadow: 0px 2px 5px hsl(0, 0%, 74%); "> - <p>Sample Global Feedback with Image</p> + <p>Sample Global Feedback with Image +</p> </div></section> `; diff --git a/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx b/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx index 9f60d6c..04620a1 100644 --- a/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx +++ b/client/src/__tests__/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.test.tsx @@ -3,15 +3,17 @@ import React from 'react'; import { render, fireEvent, screen, act } from '@testing-library/react'; import '@testing-library/jest-dom'; import { MemoryRouter } from 'react-router-dom'; -import TrueFalseQuestion from 'src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay'; +import TrueFalseQuestionDisplay from 'src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay'; +import { parse, TrueFalseQuestion } from 'gift-pegjs'; describe('TrueFalseQuestion Component', () => { const mockHandleSubmitAnswer = jest.fn(); - const sampleStem = 'Sample question stem'; + const sampleStem = 'Sample True False Question'; + const trueFalseQuestion = + parse(`${sampleStem}{T}`)[0] as TrueFalseQuestion; const sampleProps = { - questionTitle: 'Sample True/False Question', - correctAnswer: true, + question: trueFalseQuestion, handleOnSubmitAnswer: mockHandleSubmitAnswer, showAnswer: false }; @@ -19,7 +21,7 @@ describe('TrueFalseQuestion Component', () => { beforeEach(() => { render( - + ); }); diff --git a/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx b/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx index f031983..801cdd5 100644 --- a/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx +++ b/client/src/__tests__/pages/Student/StudentModeQuiz/StudentModeQuiz.test.tsx @@ -2,20 +2,20 @@ import React from 'react'; import { render, screen, fireEvent, act } from '@testing-library/react'; import '@testing-library/jest-dom'; import { MemoryRouter } from 'react-router-dom'; -import { Question } from 'gift-pegjs'; import StudentModeQuiz from 'src/components/StudentModeQuiz/StudentModeQuiz'; -import { parse } from 'gift-pegjs'; +import { BaseQuestion, parse } from 'gift-pegjs'; +import { QuestionType } from 'src/Types/QuestionType'; const mockGiftQuestions = parse( `::Sample Question 1:: Sample Question 1 {=Option A ~Option B} ::Sample Question 2:: Sample Question 2 {T}`); -const mockQuestions: Question[] = mockGiftQuestions.map((question, index) => { +const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => { if (question.type !== "Category") question.id = (index + 1).toString(); const newMockQuestion = question; - return newMockQuestion; + return {question : newMockQuestion as BaseQuestion}; }); const mockSubmitAnswer = jest.fn(); diff --git a/client/src/components/GiftTemplate/index.ts b/client/src/components/GiftTemplate/index.ts index 1f39302..04de882 100644 --- a/client/src/components/GiftTemplate/index.ts +++ b/client/src/components/GiftTemplate/index.ts @@ -1,256 +1,21 @@ import Template, { ErrorTemplate } from './templates'; -import { GIFTQuestion } from './templates/types'; import './styles.css'; +import { parse } from 'gift-pegjs'; + +const multiple = parse(` +Who's buried in Grant's tomb? {~%-50%Grant=%50%Jefferson=%50%no one####Not sure? There are many answers for this question so do not fret. Not sure? There are many answers for this question so do not fret.} + +Grant is _____ in Grant's tomb. {=buried#No one is buried there.=entombed~living} + +Grant is buried in Grant's tomb. {FALSE} + +Who's buried in Grant's tomb? {=no one=nobody} + +When was Ulysses S. Grant born? {#1822:5} + +What is the capital of Canada? {=Canada -> Ottawa =Italy -> Rome =Japan -> Tokyo} +`); -const multiple: GIFTQuestion[] = [ - { - type: 'MC', - title: null, - stem: { format: 'markdown', text: "Who's buried in Grant's \r\n tomb?" }, - hasEmbeddedAnswers: false, - globalFeedback: { - format: 'moodle', - text: 'Not sure? There are many answers for this question so do not fret. Not sure? There are many answers for this question so do not fret.' - }, - choices: [ - { - isCorrect: false, - weight: -50, - text: { format: 'moodle', text: 'Grant' }, - feedback: null - }, - { - isCorrect: true, - weight: 50, - text: { format: 'moodle', text: 'Jefferson' }, - feedback: null - }, - { - isCorrect: true, - weight: 50, - text: { format: 'moodle', text: 'no one' }, - feedback: null - } - ] - }, - { - type: 'MC', - title: null, - stem: { format: 'moodle', text: "Grant is _____ in Grant's tomb." }, - hasEmbeddedAnswers: true, - globalFeedback: null, - choices: [ - { - isCorrect: true, - weight: null, - text: { format: 'moodle', text: 'buried' }, - feedback: null - }, - { - isCorrect: true, - weight: null, - text: { format: 'moodle', text: 'entombed' }, - feedback: null - }, - { - isCorrect: false, - weight: null, - text: { format: 'moodle', text: 'living' }, - feedback: null - } - ] - }, - { - type: 'TF', - title: null, - stem: { format: 'moodle', text: "Grant is buried in Grant's tomb." }, - hasEmbeddedAnswers: false, - globalFeedback: null, - isTrue: false, - falseFeedback: null, - trueFeedback: null - }, - { - type: 'Short', - title: null, - stem: { format: 'moodle', text: "Who's buried in Grant's tomb?" }, - hasEmbeddedAnswers: false, - globalFeedback: null, - choices: [ - { - isCorrect: true, - weight: null, - text: { format: 'moodle', text: 'no one " has got me' }, - feedback: null - }, - { - isCorrect: true, - weight: null, - text: { format: 'moodle', text: 'nobody' }, - feedback: null - } - ] - }, - { - type: 'Numerical', - title: null, - stem: { format: 'moodle', text: 'When was Ulysses S. Grant born?' }, - hasEmbeddedAnswers: false, - globalFeedback: null, - choices: { - type: 'range', - number: 1822, - range: 5 - } - }, - { - type: 'Matching', - title: null, - stem: { - format: 'moodle', - text: 'Match the following countries with their corresponding capitals.' - }, - hasEmbeddedAnswers: false, - globalFeedback: null, - matchPairs: [ - { - subquestion: { format: 'moodle', text: 'Canada' }, - subanswer: 'Ottawa' - }, - { - subquestion: { format: 'moodle', text: 'Italy' }, - subanswer: 'Rome' - }, - { - subquestion: { format: 'moodle', text: 'Japan' }, - subanswer: 'Tokyo' - } - ] - }, - { - type: 'MC', - title: "Grant's Tomb", - stem: { format: 'moodle', text: "Grant is _____ in Grant's tomb." }, - hasEmbeddedAnswers: true, - globalFeedback: null, - choices: [ - { - isCorrect: false, - weight: null, - text: { format: 'moodle', text: 'buried' }, - feedback: { format: 'moodle', text: 'No one is buried there.' } - }, - { - isCorrect: true, - weight: null, - text: { format: 'moodle', text: 'entombed' }, - feedback: { format: 'moodle', text: 'Right answer!' } - }, - { - isCorrect: false, - weight: null, - text: { format: 'moodle', text: 'living' }, - feedback: { format: 'moodle', text: 'We hope not!' } - } - ] - }, - { - type: 'MC', - title: null, - stem: { format: 'moodle', text: 'Difficult multiple choice question.' }, - hasEmbeddedAnswers: false, - globalFeedback: null, - choices: [ - { - isCorrect: false, - weight: null, - text: { format: 'moodle', text: 'wrong answer' }, - feedback: { format: 'moodle', text: 'comment on wrong answer' } - }, - { - isCorrect: false, - weight: 50, - text: { format: 'moodle', text: 'half credit answer' }, - feedback: { format: 'moodle', text: 'comment on answer' } - }, - { - isCorrect: true, - weight: null, - text: { format: 'moodle', text: 'full credit answer' }, - feedback: { format: 'moodle', text: 'well done!' } - } - ] - }, - { - type: 'Short', - title: "Jesus' hometown (Short answer ex.)", - stem: { format: 'moodle', text: 'Jesus Christ was from _____ .' }, - hasEmbeddedAnswers: true, - globalFeedback: null, - choices: [ - { - isCorrect: true, - weight: null, - text: { format: 'moodle', text: 'Nazareth' }, - feedback: { format: 'moodle', text: "Yes! That's right!" } - }, - { - isCorrect: true, - weight: 75, - text: { format: 'moodle', text: 'Nazereth' }, - feedback: { format: 'moodle', text: 'Right, but misspelled.' } - }, - { - isCorrect: true, - weight: 25, - text: { format: 'moodle', text: 'Bethlehem' }, - feedback: { - format: 'moodle', - text: 'He was born here, but not raised here.' - } - } - ] - }, - { - type: 'Numerical', - title: 'Numerical example', - stem: { format: 'moodle', text: 'When was Ulysses S. Grant born?' }, - hasEmbeddedAnswers: false, - globalFeedback: null, - choices: [ - { - isCorrect: true, - weight: null, - text: { - type: 'range', - number: 1822, - range: 0 - }, - feedback: { format: 'moodle', text: 'Correct! 100% credit' } - }, - { - isCorrect: true, - weight: 50, - text: { - type: 'range', - number: 1822, - range: 2 - }, - feedback: { - format: 'moodle', - text: 'He was born in 1822. You get 50% credit for being close.' - } - } - ] - }, - { - type: 'Essay', - title: 'Essay Example', - stem: { format: 'moodle', text: 'This is an essay.' }, - hasEmbeddedAnswers: false, - globalFeedback: null - } -]; const items = multiple.map((item) => Template(item, { theme: 'dark' })).join(''); const errorItemDark = ErrorTemplate('Hello'); diff --git a/client/src/components/GiftTemplate/templates/AnswerIcon.ts b/client/src/components/GiftTemplate/templates/AnswerIconTemplate.ts similarity index 100% rename from client/src/components/GiftTemplate/templates/AnswerIcon.ts rename to client/src/components/GiftTemplate/templates/AnswerIconTemplate.ts diff --git a/client/src/components/GiftTemplate/templates/Category.ts b/client/src/components/GiftTemplate/templates/Category.ts deleted file mode 100644 index c279b29..0000000 --- a/client/src/components/GiftTemplate/templates/Category.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { TemplateOptions, Category as CategoryType } from './types'; -import QuestionContainer from './QuestionContainer'; -import Title from './Title'; - -type CategoryOptions = TemplateOptions & CategoryType; - -export default function Category({ title }: CategoryOptions): string { - return `${QuestionContainer({ - children: Title({ - type: 'Catégorie', - title: title - }) - })}`; -} diff --git a/client/src/components/GiftTemplate/templates/CategoryTemplate.ts b/client/src/components/GiftTemplate/templates/CategoryTemplate.ts new file mode 100644 index 0000000..ef15ad9 --- /dev/null +++ b/client/src/components/GiftTemplate/templates/CategoryTemplate.ts @@ -0,0 +1,15 @@ +import { TemplateOptions } from './types'; +import QuestionContainer from './QuestionContainerTemplate'; +import Title from './TitleTemplate'; +import { Category } from 'gift-pegjs'; + +type CategoryOptions = TemplateOptions & Category; + +export default function CategoryTemplate( { title }: CategoryOptions): string { + return `${QuestionContainer({ + children: Title({ + type: 'Catégorie', + title: title + }) + })}`; +} diff --git a/client/src/components/GiftTemplate/templates/DescriptionTemplate.ts b/client/src/components/GiftTemplate/templates/DescriptionTemplate.ts index 52c18ed..fa4b70d 100644 --- a/client/src/components/GiftTemplate/templates/DescriptionTemplate.ts +++ b/client/src/components/GiftTemplate/templates/DescriptionTemplate.ts @@ -1,7 +1,7 @@ import { TemplateOptions } from './types'; -import QuestionContainer from './QuestionContainer'; -import Title from './Title'; -import { textType } from './TextType'; +import QuestionContainer from './QuestionContainerTemplate'; +import Title from './TitleTemplate'; +import { textType } from './TextTypeTemplate'; import { ParagraphStyle } from '../constants'; import { state } from '.'; import { Description } from 'gift-pegjs'; diff --git a/client/src/components/GiftTemplate/templates/Error.ts b/client/src/components/GiftTemplate/templates/ErrorTemplate.ts similarity index 100% rename from client/src/components/GiftTemplate/templates/Error.ts rename to client/src/components/GiftTemplate/templates/ErrorTemplate.ts diff --git a/client/src/components/GiftTemplate/templates/Essay.ts b/client/src/components/GiftTemplate/templates/Essay.ts deleted file mode 100644 index a003d78..0000000 --- a/client/src/components/GiftTemplate/templates/Essay.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { TemplateOptions, Essay as EssayType } from './types'; -import QuestionContainer from './QuestionContainer'; -import Title from './Title'; -import textType from './TextType'; -import GlobalFeedback from './GlobalFeedback'; -import { ParagraphStyle, TextAreaStyle } from '../constants'; -import { state } from '.'; - -type EssayOptions = TemplateOptions & EssayType; - -export default function Essay({ title, stem, globalFeedback }: EssayOptions): string { - return `${QuestionContainer({ - children: [ - Title({ - type: 'Développement', - title: title - }), - `

${textType({ - text: stem - })}

`, - ``, - GlobalFeedback({ feedback: globalFeedback }) - ] - })}`; -} diff --git a/client/src/components/GiftTemplate/templates/EssayTemplate.ts b/client/src/components/GiftTemplate/templates/EssayTemplate.ts new file mode 100644 index 0000000..5697846 --- /dev/null +++ b/client/src/components/GiftTemplate/templates/EssayTemplate.ts @@ -0,0 +1,27 @@ +import { TemplateOptions } from './types'; +import QuestionContainer from './QuestionContainerTemplate'; +import Title from './TitleTemplate'; +import {textType} from './TextTypeTemplate'; +import GlobalFeedbackTemplate from './GlobalFeedbackTemplate'; +import { ParagraphStyle, TextAreaStyle } from '../constants'; +import { state } from '.'; +import { EssayQuestion } from 'gift-pegjs'; + +type EssayOptions = TemplateOptions & EssayQuestion; + +export default function EssayTemplate({ title, formattedStem, formattedGlobalFeedback }: EssayOptions): string { + return `${QuestionContainer({ + children: [ + Title({ + type: 'Développement', + title: title + }), + `

${textType(formattedStem)}

`, + ``, + (formattedGlobalFeedback && formattedGlobalFeedback.text) ? + GlobalFeedbackTemplate(formattedGlobalFeedback) : `` + ] + })}`; +} diff --git a/client/src/components/GiftTemplate/templates/GlobalFeedback.ts b/client/src/components/GiftTemplate/templates/GlobalFeedbackTemplate.ts similarity index 59% rename from client/src/components/GiftTemplate/templates/GlobalFeedback.ts rename to client/src/components/GiftTemplate/templates/GlobalFeedbackTemplate.ts index 6826c7c..2c5b6d4 100644 --- a/client/src/components/GiftTemplate/templates/GlobalFeedback.ts +++ b/client/src/components/GiftTemplate/templates/GlobalFeedbackTemplate.ts @@ -1,13 +1,12 @@ -import { TemplateOptions, Question } from './types'; -import textType from './TextType'; +import { TemplateOptions } from './types'; +import {textType} from './TextTypeTemplate'; import { state } from '.'; import { theme } from '../constants'; +import { TextFormat } from 'gift-pegjs'; -interface GlobalFeedbackOptions extends TemplateOptions { - feedback: Question['globalFeedback']; -} +type GlobalFeedbackOptions = TemplateOptions & TextFormat; -export default function GlobalFeedback({ feedback }: GlobalFeedbackOptions): string { +export default function GlobalFeedbackTemplate({ format, text }: GlobalFeedbackOptions): string { const Container = ` position: relative; margin-top: 1rem; @@ -19,9 +18,9 @@ export default function GlobalFeedback({ feedback }: GlobalFeedbackOptions): str box-shadow: 0px 2px 5px ${theme(state.theme, 'gray400', 'black800')}; `; - return feedback !== null + return (format && text) ? `
-

${textType({ text: feedback })}

+

${textType({format: format, text: text})}

` : ``; } diff --git a/client/src/components/GiftTemplate/templates/Matching.ts b/client/src/components/GiftTemplate/templates/MatchingTemplate.ts similarity index 77% rename from client/src/components/GiftTemplate/templates/Matching.ts rename to client/src/components/GiftTemplate/templates/MatchingTemplate.ts index 97e4dce..d36b8ad 100644 --- a/client/src/components/GiftTemplate/templates/Matching.ts +++ b/client/src/components/GiftTemplate/templates/MatchingTemplate.ts @@ -1,22 +1,23 @@ -import { TemplateOptions, Matching as MatchingType } from './types'; -import QuestionContainer from './QuestionContainer'; -import Title from './Title'; -import textType from './TextType'; -import GlobalFeedback from './GlobalFeedback'; +import { TemplateOptions } from './types'; +import QuestionContainer from './QuestionContainerTemplate'; +import Title from './TitleTemplate'; +import {textType} from './TextTypeTemplate'; +import GlobalFeedback from './GlobalFeedbackTemplate'; import { ParagraphStyle, SelectStyle } from '../constants'; import { state } from '.'; +import { MatchingQuestion } from 'gift-pegjs'; -type MatchingOptions = TemplateOptions & MatchingType; +type MatchingOptions = TemplateOptions & MatchingQuestion; interface MatchAnswerOptions extends TemplateOptions { - choices: MatchingType['matchPairs']; + choices: MatchingQuestion['matchPairs']; } -export default function Matching({ +export default function MatchingTemplate({ title, - stem, + formattedStem, matchPairs, - globalFeedback + formattedGlobalFeedback }: MatchingOptions): string { return `${QuestionContainer({ children: [ @@ -24,11 +25,9 @@ export default function Matching({ type: 'Appariement', title: title }), - `

${textType({ - text: stem - })}

`, + `

${textType(formattedStem)}

`, MatchAnswers({ choices: matchPairs }), - GlobalFeedback({ feedback: globalFeedback }) + formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : '' ] })}`; } @@ -64,10 +63,10 @@ function MatchAnswers({ choices }: MatchAnswerOptions): string { const uniqueMatchOptions = Array.from(new Set(choices.map(({ subanswer }) => subanswer))); const result = choices - .map(({ subquestion }) => { + .map(({ formattedSubquestion }) => { return `
- ${textType({ text: subquestion })} + ${textType(formattedSubquestion)}
-
- `; -} - -function Answer({ type, number, range, numberLow, numberHigh }: NumericalFormat): string { - switch (type) { - case 'simple': - return `${number}`; - case 'range': - return `${number} ± ${range}`; - case 'high-low': - return `${numberLow} - ${numberHigh}`; - default: - return ``; - } -} diff --git a/client/src/components/GiftTemplate/templates/NumericalTemplate.ts b/client/src/components/GiftTemplate/templates/NumericalTemplate.ts new file mode 100644 index 0000000..1af6372 --- /dev/null +++ b/client/src/components/GiftTemplate/templates/NumericalTemplate.ts @@ -0,0 +1,60 @@ +import { TemplateOptions } from './types'; +import QuestionContainer from './QuestionContainerTemplate'; +import Title from './TitleTemplate'; +import { textType } from './TextTypeTemplate'; +import GlobalFeedback from './GlobalFeedbackTemplate'; +import { ParagraphStyle, InputStyle } from '../constants'; +import { state } from '.'; +import { NumericalAnswer, NumericalQuestion } from 'gift-pegjs'; +import { isHighLowNumericalAnswer, isRangeNumericalAnswer, isSimpleNumericalAnswer } from 'gift-pegjs/typeGuards'; + +type NumericalOptions = TemplateOptions & NumericalQuestion; +type NumericalAnswerOptions = TemplateOptions & Pick; + +export default function NumericalTemplate({ + title, + formattedStem, + choices, + formattedGlobalFeedback +}: NumericalOptions): string { + return `${QuestionContainer({ + children: [ + Title({ + type: 'Numérique', + title: title + }), + `

${textType(formattedStem)}

`, + NumericalAnswers({ choices: choices }), + formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : '' + ] + })}`; +} + +function NumericalAnswers({ choices }: NumericalAnswerOptions): string { + const placeholder = choices.length > 1 + ? choices.map(choice => {Answer(choice)}).join(', ') + : Answer(choices[0]); + + return ` +
+ Réponse: +
+ `; +} + +function Answer(choice: NumericalAnswer): string { + switch (true) { + case isSimpleNumericalAnswer(choice): + return `${choice.number}`; + case isRangeNumericalAnswer(choice): + return `${choice.number} ± ${choice.range}`; + case isHighLowNumericalAnswer(choice): + return `${choice.numberLow}..${choice.numberHigh}`; + default: + return ``; + } +} diff --git a/client/src/components/GiftTemplate/templates/QuestionContainer.ts b/client/src/components/GiftTemplate/templates/QuestionContainerTemplate.ts similarity index 100% rename from client/src/components/GiftTemplate/templates/QuestionContainer.ts rename to client/src/components/GiftTemplate/templates/QuestionContainerTemplate.ts diff --git a/client/src/components/GiftTemplate/templates/ShortAnswer.ts b/client/src/components/GiftTemplate/templates/ShortAnswerTemplate.ts similarity index 53% rename from client/src/components/GiftTemplate/templates/ShortAnswer.ts rename to client/src/components/GiftTemplate/templates/ShortAnswerTemplate.ts index a46282d..f29cea3 100644 --- a/client/src/components/GiftTemplate/templates/ShortAnswer.ts +++ b/client/src/components/GiftTemplate/templates/ShortAnswerTemplate.ts @@ -1,19 +1,20 @@ -import { TemplateOptions, ShortAnswer as ShortAnswerType, TextFormat } from './types'; -import QuestionContainer from './QuestionContainer'; -import Title from './Title'; -import textType from './TextType'; -import GlobalFeedback from './GlobalFeedback'; +import { TemplateOptions } from './types'; +import QuestionContainer from './QuestionContainerTemplate'; +import Title from './TitleTemplate'; +import {textType} from './TextTypeTemplate'; +import GlobalFeedback from './GlobalFeedbackTemplate'; import { ParagraphStyle, InputStyle } from '../constants'; import { state } from './index'; +import { ShortAnswerQuestion } from 'gift-pegjs'; -type ShortAnswerOptions = TemplateOptions & ShortAnswerType; -type AnswerOptions = TemplateOptions & Pick; +type ShortAnswerOptions = TemplateOptions & ShortAnswerQuestion; +type AnswerOptions = TemplateOptions & Pick; -export default function ShortAnswer({ +export default function ShortAnswerTemplate({ title, - stem, + formattedStem, choices, - globalFeedback + formattedGlobalFeedback }: ShortAnswerOptions): string { return `${QuestionContainer({ children: [ @@ -21,18 +22,16 @@ export default function ShortAnswer({ type: 'Réponse courte', title: title }), - `

${textType({ - text: stem - })}

`, + `

${textType(formattedStem)}

`, Answers({ choices: choices }), - GlobalFeedback({ feedback: globalFeedback }) + formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : '' ] })}`; } function Answers({ choices }: AnswerOptions): string { const placeholder = choices - .map(({ text }) => textType({ text: text as TextFormat })) + .map(({ text }) => textType({ format: '', text: text })) .join(', '); return `
diff --git a/client/src/components/GiftTemplate/templates/TextType.ts b/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts similarity index 99% rename from client/src/components/GiftTemplate/templates/TextType.ts rename to client/src/components/GiftTemplate/templates/TextTypeTemplate.ts index 799981f..0777324 100644 --- a/client/src/components/GiftTemplate/templates/TextType.ts +++ b/client/src/components/GiftTemplate/templates/TextTypeTemplate.ts @@ -29,6 +29,7 @@ export function textType(formattedText: TextFormat): string { const formatText = formatLatex(formattedText.text.trim()); // latex needs pure "&", ">", etc. Must not be escaped let parsedText = ''; switch (formattedText.format) { + case '': case 'moodle': case 'plain': // Replace newlines with
tags diff --git a/client/src/components/GiftTemplate/templates/Title.ts b/client/src/components/GiftTemplate/templates/TitleTemplate.ts similarity index 94% rename from client/src/components/GiftTemplate/templates/Title.ts rename to client/src/components/GiftTemplate/templates/TitleTemplate.ts index 8e89553..da9f4ed 100644 --- a/client/src/components/GiftTemplate/templates/Title.ts +++ b/client/src/components/GiftTemplate/templates/TitleTemplate.ts @@ -1,6 +1,7 @@ -import { TemplateOptions, Question } from './types'; +import { TemplateOptions } from './types'; import { state } from '.'; import { theme } from '../constants'; +import { Question } from 'gift-pegjs'; // Type is string to allow for custom question type text (e,g, "Multiple Choice") interface TitleOptions extends TemplateOptions { diff --git a/client/src/components/GiftTemplate/templates/TrueFalse.ts b/client/src/components/GiftTemplate/templates/TrueFalse.ts deleted file mode 100644 index ff9df73..0000000 --- a/client/src/components/GiftTemplate/templates/TrueFalse.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { TemplateOptions, TextChoice, TrueFalse as TrueFalseType } from './types'; -import QuestionContainer from './QuestionContainer'; -import textType from './TextType'; -import GlobalFeedback from './GlobalFeedback'; -import MultipleChoiceAnswers from './MultipleChoiceAnswers'; -import Title from './Title'; -import { ParagraphStyle } from '../constants'; -import { state } from '.'; - -type TrueFalseOptions = TemplateOptions & TrueFalseType; - -export default function TrueFalse({ - title, - isTrue, - stem, - trueFeedback: trueFeedback, - falseFeedback: falseFeedback, - globalFeedback -}: TrueFalseOptions): string { - const choices: TextChoice[] = [ - { - text: { - format: 'moodle', - text: 'Vrai' - }, - isCorrect: isTrue, - weight: null, - feedback: isTrue ? trueFeedback : falseFeedback - }, - { - text: { - format: 'moodle', - text: 'Faux' - }, - isCorrect: !isTrue, - weight: null, - feedback: !isTrue ? trueFeedback : falseFeedback - } - ]; - - return `${QuestionContainer({ - children: [ - Title({ - type: 'Vrai/Faux', - title: title - }), - `

${textType({ - text: stem - })}

`, - MultipleChoiceAnswers({ choices: choices }), - GlobalFeedback({ feedback: globalFeedback }) - ] - })}`; -} diff --git a/client/src/components/GiftTemplate/templates/TrueFalseTemplate.ts b/client/src/components/GiftTemplate/templates/TrueFalseTemplate.ts new file mode 100644 index 0000000..4effb4f --- /dev/null +++ b/client/src/components/GiftTemplate/templates/TrueFalseTemplate.ts @@ -0,0 +1,48 @@ +import { TemplateOptions } from './types'; +import QuestionContainer from './QuestionContainerTemplate'; +import {textType} from './TextTypeTemplate'; +import GlobalFeedback from './GlobalFeedbackTemplate'; +import MultipleChoiceAnswersTemplate from './MultipleChoiceAnswersTemplate'; +import Title from './TitleTemplate'; +import { TextChoice, TrueFalseQuestion } from 'gift-pegjs'; +import DOMPurify from 'dompurify'; + +type TrueFalseOptions = TemplateOptions & TrueFalseQuestion; + +export default function TrueFalseTemplate({ + isTrue, + title, + formattedStem, + trueFormattedFeedback, falseFormattedFeedback, + formattedGlobalFeedback +}: TrueFalseOptions): string { + const choices: TextChoice[] = [ + { + formattedText: { + format: 'moodle', + text: 'Vrai' + }, + isCorrect: isTrue, + formattedFeedback: trueFormattedFeedback + }, + { + formattedText: { + format: 'moodle', + text: 'Faux' + }, + isCorrect: !isTrue, + formattedFeedback: falseFormattedFeedback + } + ]; + return `${QuestionContainer({ + children: [ + Title({ + type: 'Vrai/Faux', + title: title + }), + `
`, + MultipleChoiceAnswersTemplate({ choices: choices }), + formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : `` + ] + })}`; +} diff --git a/client/src/components/GiftTemplate/templates/index.ts b/client/src/components/GiftTemplate/templates/index.ts index 555b99d..0ed65e0 100644 --- a/client/src/components/GiftTemplate/templates/index.ts +++ b/client/src/components/GiftTemplate/templates/index.ts @@ -1,24 +1,25 @@ -import Category from './Category'; -import DescriptionTemplate from './Description'; -import Essay from './Essay'; -import Matching from './Matching'; -import MultipleChoice from './MultipleChoice'; -import Numerical from './Numerical'; -import ShortAnswer from './ShortAnswer'; -import TrueFalse from './TrueFalse'; -import Error from './Error'; + import { - GIFTQuestion, + ParsedGIFTQuestion as GIFTQuestion, // Category as CategoryType, // Description as DescriptionType, - MultipleChoice as MultipleChoiceType, - Numerical as NumericalType, - ShortAnswer as ShortAnswerType, + MultipleChoiceQuestion as MultipleChoiceType, + NumericalQuestion as NumericalType, + ShortAnswerQuestion as ShortAnswerType, // Essay as EssayType, - TrueFalse as TrueFalseType, - Matching as MatchingType, - DisplayOptions -} from './types'; + TrueFalseQuestion as TrueFalseType, + // MatchingQuestion as MatchingType, +} from 'gift-pegjs'; +import { DisplayOptions } from './types'; +import DescriptionTemplate from './DescriptionTemplate'; +import EssayTemplate from './EssayTemplate'; +import MatchingTemplate from './MatchingTemplate'; +import MultipleChoiceTemplate from './MultipleChoiceTemplate'; +import NumericalTemplate from './NumericalTemplate'; +import ShortAnswerTemplate from './ShortAnswerTemplate'; +import TrueFalseTemplate from './TrueFalseTemplate'; +import Error from './ErrorTemplate'; +import CategoryTemplate from './CategoryTemplate'; export const state: DisplayOptions = { preview: true, theme: 'light' }; @@ -37,21 +38,21 @@ export default function Template( // ...(keys as DescriptionType) // }); case 'MC': - return MultipleChoice({ + return MultipleChoiceTemplate({ ...(keys as MultipleChoiceType) }); case 'Numerical': - return Numerical({ ...(keys as NumericalType) }); + return NumericalTemplate({ ...(keys as NumericalType) }); case 'Short': - return ShortAnswer({ + return ShortAnswerTemplate({ ...(keys as ShortAnswerType) }); // case 'Essay': // return Essay({ ...(keys as EssayType) }); case 'TF': - return TrueFalse({ ...(keys as TrueFalseType) }); - case 'Matching': - return Matching({ ...(keys as MatchingType) }); + return TrueFalseTemplate({ ...(keys as TrueFalseType) }); + // case 'Matching': + // return Matching({ ...(keys as MatchingType) }); default: // TODO: throw error for unsupported question types? // throw new Error(`Unsupported question type: ${type}`); @@ -66,13 +67,13 @@ export function ErrorTemplate(text: string, options?: Partial): } export { - Category, + CategoryTemplate, DescriptionTemplate as Description, - Essay, - Matching, - MultipleChoice, - Numerical, - ShortAnswer, - TrueFalse, + EssayTemplate as Essay, + MatchingTemplate as Matching, + MultipleChoiceTemplate as MultipleChoice, + NumericalTemplate as Numerical, + ShortAnswerTemplate as ShortAnswer, + TrueFalseTemplate as TrueFalse, Error }; diff --git a/client/src/components/LiveResults/LiveResults.tsx b/client/src/components/LiveResults/LiveResults.tsx index b7d1c72..6bcd7ce 100644 --- a/client/src/components/LiveResults/LiveResults.tsx +++ b/client/src/components/LiveResults/LiveResults.tsx @@ -20,7 +20,7 @@ import { TableRow } from '@mui/material'; import { StudentType } from '../../Types/StudentType'; -import { formatLatex } from '../GiftTemplate/templates/TextType'; +import { formatLatex } from '../GiftTemplate/templates/TextTypeTemplate'; interface LiveResultsProps { socket: Socket | null; diff --git a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx index a2eac59..519c110 100644 --- a/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay.tsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from 'react'; import '../questionStyle.css'; import { Button } from '@mui/material'; -import { textType } from '../../GiftTemplate/templates/TextType'; +import { textType } from '../../GiftTemplate/templates/TextTypeTemplate'; import { MultipleChoiceQuestion } from 'gift-pegjs'; import DOMPurify from 'dompurify'; @@ -31,7 +31,7 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { return (
-
+
{question.choices.map((choice, i) => { @@ -48,13 +48,13 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => { (choice.isCorrect ? '✅' : '❌')}
{alphabet[i]}
-
+
{choice.formattedFeedback && showAnswer && (
{choice.isCorrect ? '✅' : '❌'} -
+
)}
@@ -63,8 +63,8 @@ const MultipleChoiceQuestionDisplay: React.FC = (props) => {
{question.formattedGlobalFeedback && showAnswer && (
-

${textType({ text: question.formattedGlobalFeedback })}

-
+
+
)} {!showAnswer && handleOnSubmitAnswer && ( diff --git a/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx index acf0869..b1e8154 100644 --- a/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import '../questionStyle.css'; import { Button, TextField } from '@mui/material'; -import { textType } from '../../GiftTemplate/templates/TextType'; +import { textType } from '../../GiftTemplate/templates/TextTypeTemplate'; import { NumericalQuestion, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs'; import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer } from 'gift-pegjs/typeGuards'; import DOMPurify from 'dompurify'; @@ -41,13 +41,13 @@ const NumericalQuestionDisplay: React.FC = (props) => { return (
-
+
{showAnswer ? ( <>
{correctAnswer}
{question.formattedGlobalFeedback &&
-
+
} ) : ( @@ -65,7 +65,7 @@ const NumericalQuestionDisplay: React.FC = (props) => {
{question.formattedGlobalFeedback && showAnswer && (
-
+
)} {handleOnSubmitAnswer && ( diff --git a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx index 934092f..8dfa1b3 100644 --- a/client/src/components/QuestionsDisplay/QuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/QuestionDisplay.tsx @@ -1,8 +1,7 @@ -// Question;tsx import React from 'react'; import { Question } from 'gift-pegjs'; -import TrueFalseQuestion from './TrueFalseQuestion/TrueFalseQuestion'; +import TrueFalseQuestionDisplay from './TrueFalseQuestionDisplay/TrueFalseQuestionDisplay'; import MultipleChoiceQuestionDisplay from './MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay'; import NumericalQuestionDisplay from './NumericalQuestionDisplay/NumericalQuestionDisplay'; import ShortAnswerQuestionDisplay from './ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay'; @@ -27,12 +26,10 @@ const QuestionDisplay: React.FC = ({ switch (question?.type) { case 'TF': questionTypeComponent = ( - ); break; diff --git a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx index 5fd729d..55cbba4 100644 --- a/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import '../questionStyle.css'; import { Button, TextField } from '@mui/material'; -import { textType } from '../../GiftTemplate/templates/TextType'; +import { textType } from '../../GiftTemplate/templates/TextTypeTemplate'; import { ShortAnswerQuestion } from 'gift-pegjs'; import DOMPurify from 'dompurify'; @@ -18,7 +18,7 @@ const ShortAnswerQuestionDisplay: React.FC = (props) => { return (
-
+
{showAnswer ? ( <> @@ -30,7 +30,7 @@ const ShortAnswerQuestionDisplay: React.FC = (props) => { ))}
{question.formattedGlobalFeedback &&
-
+
} ) : ( diff --git a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx index dd5b3e8..1c4fcdb 100644 --- a/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx +++ b/client/src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay.tsx @@ -2,33 +2,32 @@ import React, { useState, useEffect } from 'react'; import '../questionStyle.css'; import { Button } from '@mui/material'; -import { textType } from '../../GiftTemplate/templates/TextType'; -import { TextFormat } from 'gift-pegjs'; +import { TrueFalseQuestion } from 'gift-pegjs'; import DOMPurify from 'dompurify'; +import { textType } from 'src/components/GiftTemplate/templates/TextTypeTemplate'; interface Props { - questionContent: TextFormat; - correctAnswer: boolean; - globalFeedback?: string | undefined; + question: TrueFalseQuestion; handleOnSubmitAnswer?: (answer: boolean) => void; showAnswer?: boolean; } -const TrueFalseQuestion: React.FC = (props) => { - const { questionContent, correctAnswer, showAnswer, handleOnSubmitAnswer, globalFeedback } = +const TrueFalseQuestionDisplay: React.FC = (props) => { + const { question, showAnswer, handleOnSubmitAnswer } = props; const [answer, setAnswer] = useState(undefined); useEffect(() => { setAnswer(undefined); - }, [questionContent]); + }, [question]); const selectedTrue = answer ? 'selected' : ''; const selectedFalse = answer !== undefined && !answer ? 'selected' : ''; + const correctAnswer = question.isTrue === answer; return (
-
+
- {globalFeedback && showAnswer && ( -
{globalFeedback}
+ {/* selected TRUE, show True feedback if it exists */} + {showAnswer && answer && question.trueFormattedFeedback && ( +
+
+
+ )} + {/* selected FALSE, show False feedback if it exists */} + {showAnswer && !answer && question.falseFormattedFeedback && ( +
+
+
+ )} + {question.formattedGlobalFeedback && showAnswer && ( +
+
+
)} {!showAnswer && handleOnSubmitAnswer && (
diff --git a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx index 850c9ef..ae2d382 100644 --- a/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx +++ b/client/src/components/TeacherModeQuiz/TeacherModeQuiz.tsx @@ -1,13 +1,14 @@ // TeacherModeQuiz.tsx import React, { useEffect, useState } from 'react'; -import QuestionComponent from '../Questions/QuestionDisplay'; +import QuestionComponent from '../QuestionsDisplay/QuestionDisplay'; import '../../pages/Student/JoinRoom/joinRoom.css'; import { QuestionType } from '../../Types/QuestionType'; // import { QuestionService } from '../../services/QuestionService'; import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material'; +import { Question } from 'gift-pegjs'; interface TeacherModeQuizProps { questionInfos: QuestionType; @@ -63,7 +64,7 @@ const TeacherModeQuiz: React.FC = ({ ) : ( )} @@ -76,7 +77,7 @@ const TeacherModeQuiz: React.FC = ({ {feedbackMessage} diff --git a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx index 207143c..ae9bdfa 100644 --- a/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx +++ b/client/src/pages/Teacher/ManageRoom/ManageRoom.tsx @@ -2,8 +2,8 @@ import React, { useEffect, useState } from 'react'; import { useNavigate, useParams } from 'react-router-dom'; import { Socket } from 'socket.io-client'; -import { GIFTQuestion, parse } from 'gift-pegjs'; -import { QuestionType } from '../../../Types/QuestionType'; +import { ParsedGIFTQuestion, BaseQuestion, parse, Question } from 'gift-pegjs'; +import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer } from "gift-pegjs/typeGuards"; import LiveResultsComponent from 'src/components/LiveResults/LiveResults'; // import { QuestionService } from '../../../services/QuestionService'; import webSocketService, { AnswerReceptionFromBackendType } from '../../../services/WebsocketService'; @@ -18,8 +18,9 @@ import { Refresh, Error } from '@mui/icons-material'; import StudentWaitPage from 'src/components/StudentWaitPage/StudentWaitPage'; import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; //import QuestionNavigation from 'src/components/QuestionNavigation/QuestionNavigation'; -import QuestionDisplay from 'src/components/Questions/QuestionDisplay'; +import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay'; import ApiService from '../../../services/ApiService'; +import { QuestionType } from 'src/Types/QuestionType'; const ManageRoom: React.FC = () => { const navigate = useNavigate(); @@ -277,7 +278,7 @@ const ManageRoom: React.FC = () => { const parsedQuestions = [] as QuestionType[]; quizQuestionArray.forEach((question, index) => { - parsedQuestions.push({ question: parse(question)[0] }); + parsedQuestions.push({ question: parse(question)[0] as BaseQuestion }); parsedQuestions[index].question.id = (index + 1).toString(); }); if (parsedQuestions.length === 0) return null; @@ -347,7 +348,7 @@ const ManageRoom: React.FC = () => { const answerText = answer.toString(); if (questionInfo) { - const question = questionInfo.question as GIFTQuestion; + const question = questionInfo.question as ParsedGIFTQuestion; if (question.type === 'TF') { return ( (question.isTrue && answerText == 'true') || @@ -355,53 +356,39 @@ const ManageRoom: React.FC = () => { ); } else if (question.type === 'MC') { return question.choices.some( - (choice) => choice.isCorrect && choice.text.text === answerText + (choice) => choice.isCorrect && choice.formattedText.text === answerText ); } else if (question.type === 'Numerical') { - if (question.choices && !Array.isArray(question.choices)) { - if ( - question.choices.type === 'high-low' && - question.choices.numberHigh && - question.choices.numberLow - ) { - const answerNumber = parseFloat(answerText); - if (!isNaN(answerNumber)) { - return ( - answerNumber <= question.choices.numberHigh && - answerNumber >= question.choices.numberLow - ); - } + if (isHighLowNumericalAnswer(question.choices[0])) { + const choice = question.choices[0]; + const answerNumber = parseFloat(answerText); + if (!isNaN(answerNumber)) { + return ( + answerNumber <= choice.numberHigh && + answerNumber >= choice.numberLow + ); } } - if (question.choices && Array.isArray(question.choices)) { - if ( - question.choices[0].text.type === 'range' && - question.choices[0].text.number && - question.choices[0].text.range - ) { - const answerNumber = parseFloat(answerText); - const range = question.choices[0].text.range; - const correctAnswer = question.choices[0].text.number; - if (!isNaN(answerNumber)) { - return ( - answerNumber <= correctAnswer + range && - answerNumber >= correctAnswer - range - ); - } + if (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 ( - question.choices[0].text.type === 'simple' && - question.choices[0].text.number - ) { - const answerNumber = parseFloat(answerText); - if (!isNaN(answerNumber)) { - return answerNumber === question.choices[0].text.number; - } + } + 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.text.toUpperCase() === answerText.toUpperCase() + (choice) => choice.text.toUpperCase() === answerText.toUpperCase() ); } } @@ -476,7 +463,7 @@ const ManageRoom: React.FC = () => { {currentQuestion && ( )} diff --git a/client/tsconfig.json b/client/tsconfig.json index a6b7592..5115a44 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -2,14 +2,19 @@ "compilerOptions": { "baseUrl": "./", "paths": { - "src/*": ["src/*"] + "src/*": [ + "src/*" + ] }, "target": "ESNext", "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], "module": "ESNext", "skipLibCheck": true, - /* Bundler mode */ "moduleResolution": "bundler", "allowImportingTsExtensions": true, @@ -17,7 +22,6 @@ "isolatedModules": true, "noEmit": true, "jsx": "react", - /* Linting */ "strict": true, "noUnusedLocals": true, @@ -25,6 +29,16 @@ "noFallthroughCasesInSwitch": true, "esModuleInterop": true }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] + // "exclude": [ + // // "src/components/GiftTemplate/**/*", + // // "src/__tests__/components/GiftTemplate/**/*", + // ], + "include": [ + "src" + ], + "references": [ + { + "path": "./tsconfig.node.json" + } + ] }