Renames, passing many tests

This commit is contained in:
C. Fuhrman 2025-01-25 02:02:18 -05:00
parent 00e45f0f09
commit cb46a18370
42 changed files with 516 additions and 817 deletions

View file

@ -0,0 +1,5 @@
import { BaseQuestion } from "gift-pegjs";
export interface QuestionType {
question: BaseQuestion;
}

View file

@ -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,<br>world!<br>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 = '<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">E</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8641em;"></span><span class="mord mathnormal">m</span><span class="mord"><span class="mord mathnormal">c</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span>';
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 = '<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>a</mi><mo>+</mo><mi>b</mi><mo>=</mo><mi>c</mi></mrow><annotation encoding="application/x-tex">a + b = c</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">a</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">b</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">c</span></span></span></span> ? <span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">E</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8641em;"></span><span class="mord mathnormal">m</span><span class="mord"><span class="mord mathnormal">c</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span>';
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.<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow></mrow><annotation encoding="application/x-tex"></annotation></semantics></math></span><span class="katex-html" aria-hidden="true"></span></span>\\begin{pmatrix}<br> a&b \\\\<br> c&d<br>\\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 = '<strong>Bold</strong>\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 = '<em>yes</em>';
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

View file

@ -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', () => {

View file

@ -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: '<img src="https://via.placeholder.com/150" alt="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: '<img src="https://via.placeholder.com/150" alt="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: '<img src="https://via.placeholder.com/150" alt="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: '<img src="https://via.placeholder.com/150" alt="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: '<img src="https://via.placeholder.com/150" alt = "Sample Image"/>' },
formattedStem: { format: 'markdown', text: '<img src="https://via.placeholder.com/150" alt = "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: '<img src="https://via.placeholder.com/150" alt="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: '<img src="https://via.placeholder.com/150" alt="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', () => {

View file

@ -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: '<img src="https://via.placeholder.com/150" alt="Sample Image" />' } }
],
globalFeedback: { format: 'plain', text: 'Sample Global Feedback with Image' }
};
test('Numerical snapshot test with plain text', () => {
const { asFragment } = render(<Numerical {...plainTextMock} />);

View file

@ -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: '<img src="https://via.placeholder.com/150" alt="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(<ShortAnswer {...plainTextMock} />);

View file

@ -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: '<img src="https://via.placeholder.com/150" alt="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]<img src="https\\://via.placeholder.com/150" alt="Sample Image" />}`)[0] as ShortAnswerQuestion;
test('TrueFalse snapshot test with plain text', () => {
const { asFragment } = render(<TrueFalse {...plainTextMock} />);

View file

@ -40,7 +40,7 @@ exports[`ShortAnswer snapshot test with image 1`] = `
&lt;/div&gt;
&lt;p style="
color: hsl(0, 0%, 0%);
"&gt;Sample Stem with Image
"&gt;Sample Stem with Image &lt;img src="https://example.com/cat.jpg" alt=""&gt;
&lt;/p&gt;
&lt;div&gt;
&lt;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
, &lt;img src="https://via.placeholder.com/150" alt="Sample Image" /&gt;"&gt;
" placeholder="Answer 1, Answer 2"&gt;
&lt;/div&gt;
&lt;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%);
"&gt;
&lt;p&gt;Sample Global Feedback with Image&lt;/p&gt;
&lt;p&gt;Sample Global Feedback with Image
&lt;/p&gt;
&lt;/div&gt;&lt;/section&gt;
</DocumentFragment>
`;

View file

@ -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(
<MemoryRouter>
<TrueFalseQuestion questionContent={{ text: sampleStem, format: 'plain' }} {...sampleProps} />
<TrueFalseQuestionDisplay {...sampleProps} />
</MemoryRouter>);
});

View file

@ -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();

View file

@ -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');

View file

@ -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
})
})}`;
}

View file

@ -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
})
})}`;
}

View file

@ -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';

View file

@ -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
}),
`<p style="${ParagraphStyle(state.theme)}">${textType({
text: stem
})}</p>`,
`<textarea class="gift-textarea" style="${TextAreaStyle(
state.theme
)}" placeholder="Entrez votre réponse ici..."></textarea>`,
GlobalFeedback({ feedback: globalFeedback })
]
})}`;
}

View file

@ -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
}),
`<p style="${ParagraphStyle(state.theme)}">${textType(formattedStem)}</p>`,
`<textarea class="gift-textarea" style="${TextAreaStyle(
state.theme
)}" placeholder="Entrez votre réponse ici..."></textarea>`,
(formattedGlobalFeedback && formattedGlobalFeedback.text) ?
GlobalFeedbackTemplate(formattedGlobalFeedback) : ``
]
})}`;
}

View file

@ -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)
? `<div style="${Container}">
<p>${textType({ text: feedback })}</p>
<p>${textType({format: format, text: text})}</p>
</div>`
: ``;
}

View file

@ -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
}),
`<p style="${ParagraphStyle(state.theme)}">${textType({
text: stem
})}</p>`,
`<p style="${ParagraphStyle(state.theme)}">${textType(formattedStem)}</p>`,
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 `
<div style="${OptionTable} ${ParagraphStyle(state.theme)}">
${textType({ text: subquestion })}
${textType(formattedSubquestion)}
</div>
<div>
<select class="gift-select" style="${SelectStyle(state.theme)} ${Dropdown}">

View file

@ -1,31 +0,0 @@
import { TemplateOptions, MultipleChoice as MultipleChoiceType } from './types';
import QuestionContainer from './QuestionContainer';
import GlobalFeedback from './GlobalFeedback';
import Title from './Title';
import textType from './TextType';
import MultipleChoiceAnswers from './MultipleChoiceAnswers';
import { ParagraphStyle } from '../constants';
import { state } from '.';
type MultipleChoiceOptions = TemplateOptions & MultipleChoiceType;
export default function MultipleChoice({
title,
stem,
choices,
globalFeedback
}: MultipleChoiceOptions): string {
return `${QuestionContainer({
children: [
Title({
type: 'Choix multiple',
title: title
}),
`<p style="${ParagraphStyle(state.theme)}">${textType({
text: stem
})}</p>`,
MultipleChoiceAnswers({ choices: choices }),
GlobalFeedback({ feedback: globalFeedback })
]
})}`;
}

View file

@ -1,20 +1,21 @@
import { nanoid } from 'nanoid';
import { TemplateOptions, TextFormat, Choice, MultipleChoice as MultipleChoiceType } from './types';
import textType from './TextType';
import AnswerIcon from './AnswerIcon';
import { TemplateOptions } from './types';
import {textType} from './TextTypeTemplate';
import AnswerIcon from './AnswerIconTemplate';
import { state } from '.';
import { ParagraphStyle, theme } from '../constants';
import { MultipleChoiceQuestion, TextChoice } from 'gift-pegjs';
type MultipleChoiceAnswerOptions = TemplateOptions & Pick<MultipleChoiceType, 'choices'>;
type MultipleChoiceAnswerOptions = TemplateOptions & Pick<MultipleChoiceQuestion, 'choices'>;
type AnswerFeedbackOptions = TemplateOptions & Pick<Choice, 'feedback'>;
type AnswerFeedbackOptions = TemplateOptions & Pick<TextChoice, 'formattedFeedback'>;
interface AnswerWeightOptions extends TemplateOptions {
weight: Choice['weight'];
correct: Choice['isCorrect'];
weight: TextChoice['weight'];
correct: TextChoice['isCorrect'];
}
export default function MultipleChoiceAnswers({ choices }: MultipleChoiceAnswerOptions) {
export default function MultipleChoiceAnswersTemplate({ choices }: MultipleChoiceAnswerOptions) {
const id = `id${nanoid(8)}`;
const isMultipleAnswer = choices.filter(({ isCorrect }) => isCorrect === true).length === 0;
@ -23,7 +24,7 @@ export default function MultipleChoiceAnswers({ choices }: MultipleChoiceAnswerO
isMultipleAnswer ? ` ou plusieurs` : ``
}:</span>`;
const result = choices
.map(({ weight, isCorrect, text, feedback }) => {
.map(({ weight, isCorrect, formattedText, formattedFeedback }) => {
const CustomLabel = `
display: inline-block;
padding: 0.2em 0 0.2em 0;
@ -31,7 +32,7 @@ export default function MultipleChoiceAnswers({ choices }: MultipleChoiceAnswerO
const inputId = `id${nanoid(6)}`;
const isPositiveWeight = weight !== null && weight > 0;
const isPositiveWeight = (weight != undefined) && (weight > 0);
const isCorrectOption = isMultipleAnswer ? isPositiveWeight : isCorrect;
return `
@ -41,10 +42,10 @@ export default function MultipleChoiceAnswers({ choices }: MultipleChoiceAnswerO
}" id="${inputId}" name="${id}">
${AnswerWeight({ correct: isCorrectOption, weight: weight })}
<label style="${CustomLabel} ${ParagraphStyle(state.theme)}" for="${inputId}">
${textType({ text: text as TextFormat })}
${textType(formattedText)}
</label>
${AnswerIcon({ correct: isCorrectOption })}
${AnswerFeedback({ feedback: feedback })}
${AnswerFeedback({ formattedFeedback: formattedFeedback })}
</input>
</div>
`;
@ -80,10 +81,10 @@ function AnswerWeight({ weight, correct }: AnswerWeightOptions): string {
: ``;
}
function AnswerFeedback({ feedback }: AnswerFeedbackOptions): string {
function AnswerFeedback({ formattedFeedback }: AnswerFeedbackOptions): string {
const Container = `
color: ${theme(state.theme, 'teal700', 'gray700')};
`;
return feedback ? `<span style="${Container}">${textType({ text: feedback })}</span>` : ``;
return formattedFeedback ? `<span style="${Container}">${textType(formattedFeedback)}</span>` : ``;
}

View file

@ -0,0 +1,30 @@
import { TemplateOptions } from './types';
import QuestionContainer from './QuestionContainerTemplate';
import GlobalFeedback from './GlobalFeedbackTemplate';
import Title from './TitleTemplate';
import {textType} from './TextTypeTemplate';
import MultipleChoiceAnswers from './MultipleChoiceAnswersTemplate';
import { ParagraphStyle } from '../constants';
import { state } from '.';
import { MultipleChoiceQuestion } from 'gift-pegjs';
type MultipleChoiceOptions = TemplateOptions & MultipleChoiceQuestion;
export default function MultipleChoiceTemplate({
title,
formattedStem,
choices,
formattedGlobalFeedback
}: MultipleChoiceOptions): string {
return `${QuestionContainer({
children: [
Title({
type: 'Choix multiple',
title: title
}),
`<p style="${ParagraphStyle(state.theme)}">${textType(formattedStem)}</p>`,
MultipleChoiceAnswers({ choices: choices }),
formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : ''
]
})}`;
}

View file

@ -1,60 +0,0 @@
import { TemplateOptions, Numerical as NumericalType, NumericalFormat } from './types';
import QuestionContainer from './QuestionContainer';
import Title from './Title';
import textType from './TextType';
import GlobalFeedback from './GlobalFeedback';
import { ParagraphStyle, InputStyle } from '../constants';
import { state } from '.';
type NumericalOptions = TemplateOptions & NumericalType;
type NumericalAnswerOptions = TemplateOptions & Pick<NumericalType, 'choices'>;
export default function Numerical({
title,
stem,
choices,
globalFeedback
}: NumericalOptions): string {
return `${QuestionContainer({
children: [
Title({
type: 'Numérique',
title: title
}),
`<p style="${ParagraphStyle(state.theme)}">${textType({
text: stem
})}</p>`,
NumericalAnswers({ choices: choices }),
GlobalFeedback({ feedback: globalFeedback })
]
})}`;
}
function NumericalAnswers({ choices }: NumericalAnswerOptions): string {
const placeholder = Array.isArray(choices)
? choices.map(({ text }) => Answer(text)).join(', ')
: Answer(choices);
return `
<div>
<span style="${ParagraphStyle(
state.theme
)}">Réponse: </span><input class="gift-input" type="text" style="${InputStyle(
state.theme
)}" placeholder="${placeholder}">
</div>
`;
}
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 ``;
}
}

View file

@ -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<NumericalQuestion, 'choices'>;
export default function NumericalTemplate({
title,
formattedStem,
choices,
formattedGlobalFeedback
}: NumericalOptions): string {
return `${QuestionContainer({
children: [
Title({
type: 'Numérique',
title: title
}),
`<p style="${ParagraphStyle(state.theme)}">${textType(formattedStem)}</p>`,
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 `
<div>
<span style="${ParagraphStyle(
state.theme
)}">Réponse: </span><input class="gift-input" type="text" style="${InputStyle(
state.theme
)}" placeholder="${placeholder}">
</div>
`;
}
function Answer(choice: NumericalAnswer): string {
switch (true) {
case isSimpleNumericalAnswer(choice):
return `${choice.number}`;
case isRangeNumericalAnswer(choice):
return `${choice.number} &plusmn; ${choice.range}`;
case isHighLowNumericalAnswer(choice):
return `${choice.numberLow}..${choice.numberHigh}`;
default:
return ``;
}
}

View file

@ -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<ShortAnswerType, 'choices'>;
type ShortAnswerOptions = TemplateOptions & ShortAnswerQuestion;
type AnswerOptions = TemplateOptions & Pick<ShortAnswerQuestion, 'choices'>;
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
}),
`<p style="${ParagraphStyle(state.theme)}">${textType({
text: stem
})}</p>`,
`<p style="${ParagraphStyle(state.theme)}">${textType(formattedStem)}</p>`,
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 `
<div>

View file

@ -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 <br> tags

View file

@ -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 {

View file

@ -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
}),
`<p style="${ParagraphStyle(state.theme)}">${textType({
text: stem
})}</p>`,
MultipleChoiceAnswers({ choices: choices }),
GlobalFeedback({ feedback: globalFeedback })
]
})}`;
}

View file

@ -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
}),
`<div dangerouslySetInnerHTML={{ __html: ${DOMPurify.sanitize(textType(formattedStem))} }} />`,
MultipleChoiceAnswersTemplate({ choices: choices }),
formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : ``
]
})}`;
}

View file

@ -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<DisplayOptions>):
}
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
};

View file

@ -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;

View file

@ -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> = (props) => {
return (
<div className="question-container">
<div className="question content">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({text: question.formattedStem})) }} />
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedStem)) }} />
</div>
<div className="choices-wrapper mb-1">
{question.choices.map((choice, i) => {
@ -48,13 +48,13 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
(choice.isCorrect ? '✅' : '❌')}
<div className={`circle ${selected}`}>{alphabet[i]}</div>
<div className={`answer-text ${selected}`}>
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({ text: choice.formattedText })) }} />
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(choice.formattedText)) }} />
</div>
</Button>
{choice.formattedFeedback && showAnswer && (
<div className="feedback-container mb-1 mt-1/2">
{choice.isCorrect ? '✅' : '❌'}
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({ text: choice.formattedFeedback })) }} />
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(choice.formattedFeedback)) }} />
</div>
)}
</div>
@ -63,7 +63,7 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
</div>
{question.formattedGlobalFeedback && showAnswer && (
<div className="global-feedback mb-2">
<p>${textType({ text: question.formattedGlobalFeedback })}</p>
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedGlobalFeedback)) }} />
</div>
)}

View file

@ -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> = (props) => {
return (
<div className="question-wrapper">
<div>
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({ text: question.formattedStem })) }} />
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedStem)) }} />
</div>
{showAnswer ? (
<>
<div className="correct-answer-text mb-2">{correctAnswer}</div>
{question.formattedGlobalFeedback && <div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({ text: question.formattedGlobalFeedback })) }} />
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedGlobalFeedback)) }} />
</div>}
</>
) : (
@ -65,7 +65,7 @@ const NumericalQuestionDisplay: React.FC<Props> = (props) => {
</div>
{question.formattedGlobalFeedback && showAnswer && (
<div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({ text: question.formattedGlobalFeedback })) }} />
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedGlobalFeedback)) }} />
</div>
)}
{handleOnSubmitAnswer && (

View file

@ -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<QuestionProps> = ({
switch (question?.type) {
case 'TF':
questionTypeComponent = (
<TrueFalseQuestion
questionContent={question.formattedStem}
correctAnswer={question.isTrue}
<TrueFalseQuestionDisplay
question={question}
handleOnSubmitAnswer={handleOnSubmitAnswer}
showAnswer={showAnswer}
globalFeedback={question.formattedGlobalFeedback?.text}
/>
);
break;

View file

@ -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> = (props) => {
return (
<div className="question-wrapper">
<div className="question content">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({text: question.formattedStem})) }} />
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedStem)) }} />
</div>
{showAnswer ? (
<>
@ -30,7 +30,7 @@ const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
))}
</div>
{question.formattedGlobalFeedback && <div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({text: question.formattedGlobalFeedback})) }} />
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedGlobalFeedback)) }} />
</div>}
</>
) : (

View file

@ -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> = (props) => {
const { questionContent, correctAnswer, showAnswer, handleOnSubmitAnswer, globalFeedback } =
const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
const { question, showAnswer, handleOnSubmitAnswer } =
props;
const [answer, setAnswer] = useState<boolean | undefined>(undefined);
useEffect(() => {
setAnswer(undefined);
}, [questionContent]);
}, [question]);
const selectedTrue = answer ? 'selected' : '';
const selectedFalse = answer !== undefined && !answer ? 'selected' : '';
const correctAnswer = question.isTrue === answer;
return (
<div className="question-container">
<div className="question content">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({ text: questionContent })) }} />
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedStem)) }} />
</div>
<div className="choices-wrapper mb-1">
<Button
@ -50,8 +49,22 @@ const TrueFalseQuestion: React.FC<Props> = (props) => {
<div className={`answer-text ${selectedFalse}`}>Faux</div>
</Button>
</div>
{globalFeedback && showAnswer && (
<div className="global-feedback mb-2">{globalFeedback}</div>
{/* selected TRUE, show True feedback if it exists */}
{showAnswer && answer && question.trueFormattedFeedback && (
<div className="true-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.trueFormattedFeedback)) }} />
</div>
)}
{/* selected FALSE, show False feedback if it exists */}
{showAnswer && !answer && question.falseFormattedFeedback && (
<div className="false-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.falseFormattedFeedback)) }} />
</div>
)}
{question.formattedGlobalFeedback && showAnswer && (
<div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedGlobalFeedback)) }} />
</div>
)}
{!showAnswer && handleOnSubmitAnswer && (
<Button
@ -68,4 +81,4 @@ const TrueFalseQuestion: React.FC<Props> = (props) => {
);
};
export default TrueFalseQuestion;
export default TrueFalseQuestionDisplay;

View file

@ -1,6 +1,6 @@
// StudentModeQuiz.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';
@ -8,6 +8,7 @@ import { Button } from '@mui/material';
//import QuestionNavigation from '../QuestionNavigation/QuestionNavigation';
//import { ChevronLeft, ChevronRight } from '@mui/icons-material';
import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
import { Question } from 'gift-pegjs';
interface StudentModeQuizProps {
questions: QuestionType[];
@ -63,7 +64,7 @@ const StudentModeQuiz: React.FC<StudentModeQuizProps> = ({
</div>
<QuestionComponent
handleOnSubmitAnswer={handleOnSubmitAnswer}
question={questionInfos.question}
question={questionInfos.question as Question}
showAnswer={isAnswerSubmitted}
/>
<div className="center-h-align mt-1/2">

View file

@ -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<TeacherModeQuizProps> = ({
) : (
<QuestionComponent
handleOnSubmitAnswer={handleOnSubmitAnswer}
question={questionInfos.question}
question={questionInfos.question as Question}
/>
)}
@ -76,7 +77,7 @@ const TeacherModeQuiz: React.FC<TeacherModeQuizProps> = ({
{feedbackMessage}
<QuestionComponent
handleOnSubmitAnswer={handleOnSubmitAnswer}
question={questionInfos.question}
question={questionInfos.question as Question}
showAnswer={true}
/>
</DialogContent>

View file

@ -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,33 +356,23 @@ 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
) {
if (isHighLowNumericalAnswer(question.choices[0])) {
const choice = question.choices[0];
const answerNumber = parseFloat(answerText);
if (!isNaN(answerNumber)) {
return (
answerNumber <= question.choices.numberHigh &&
answerNumber >= question.choices.numberLow
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
) {
if (isRangeNumericalAnswer(question.choices[0])) {
const answerNumber = parseFloat(answerText);
const range = question.choices[0].text.range;
const correctAnswer = question.choices[0].text.number;
const range = question.choices[0].range;
const correctAnswer = question.choices[0].number;
if (!isNaN(answerNumber)) {
return (
answerNumber <= correctAnswer + range &&
@ -389,19 +380,15 @@ const ManageRoom: React.FC = () => {
);
}
}
if (
question.choices[0].text.type === 'simple' &&
question.choices[0].text.number
) {
if (isSimpleNumericalAnswer(question.choices[0])) {
const answerNumber = parseFloat(answerText);
if (!isNaN(answerNumber)) {
return answerNumber === question.choices[0].text.number;
}
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 && (
<QuestionDisplay
showAnswer={false}
question={currentQuestion?.question}
question={currentQuestion?.question as Question}
/>
)}

View file

@ -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"
}
]
}