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/TextTypeTemplate";
import { textType } from "src/components/GiftTemplate/templates/TextType";
import { TextFormat } from "gift-pegjs"; import { TextFormat } from "gift-pegjs";
describe('TextType', () => { describe('TextType', () => {
it('should format text with basic characters correctly', () => { it('should format text with basic characters correctly', () => {
const input: TextFormat = { const input: TextFormat = {
text: 'Hello, world! 5 > 3, right?', text: 'Hello, world! 5 > 3, right?',
format: 'plain' format: 'moodle'
}; };
const expectedOutput = 'Hello, world! 5 > 3, right?'; 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', () => { it('should format text with newlines correctly', () => {
@ -19,7 +17,7 @@ describe('TextType', () => {
format: 'plain' format: 'plain'
}; };
const expectedOutput = 'Hello,<br>world!<br>5 > 3, right?'; 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', () => { 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 // by running the test and copying the "Received string:" in jest output
// when it fails (assuming the output is correct) // 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>'; 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', () => { it('should format text with two equations (inline and separate) correctly', () => {
const input: TextFormat = { const input: TextFormat = {
text: '$a + b = c$ ? $$E=mc^2$$', text: '$a + b = c$ ? $$E=mc^2$$',
format: 'plain' format: 'moodle'
}; };
// hint: katex-display is the class that indicates a separate equation // 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>'; 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', () => { it('should format text with a katex matrix correctly', () => {
@ -53,7 +51,7 @@ describe('TextType', () => {
format: 'plain' 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}'; 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', () => { it('should format text with Markdown correctly', () => {
@ -63,7 +61,7 @@ describe('TextType', () => {
}; };
// TODO: investigate why the output has an extra newline // TODO: investigate why the output has an extra newline
const expectedOutput = '<strong>Bold</strong>\n'; const expectedOutput = '<strong>Bold</strong>\n';
expect(textType({ text: input })).toBe(expectedOutput); expect(textType(input)).toBe(expectedOutput);
}); });
it('should format text with HTML correctly', () => { it('should format text with HTML correctly', () => {
@ -72,7 +70,7 @@ describe('TextType', () => {
format: 'html' format: 'html'
}; };
const expectedOutput = '<em>yes</em>'; const expectedOutput = '<em>yes</em>';
expect(textType({ text: input })).toBe(expectedOutput); expect(textType(input)).toBe(expectedOutput);
}); });
it('should format plain text correctly', () => { it('should format plain text correctly', () => {
@ -81,7 +79,7 @@ describe('TextType', () => {
format: 'plain' format: 'plain'
}; };
const expectedOutput = 'Just plain text'; const expectedOutput = 'Just plain text';
expect(textType({ text: input })).toBe(expectedOutput); expect(textType(input)).toBe(expectedOutput);
}); });
// Add more tests for other formats if needed // Add more tests for other formats if needed

View file

@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import '@testing-library/jest-dom'; 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'; import DOMPurify from 'dompurify';
describe('AnswerIcon', () => { describe('AnswerIcon', () => {

View file

@ -2,99 +2,100 @@ import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { MultipleChoice } from 'src/components/GiftTemplate/templates'; 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 // Mock the nanoid function
jest.mock('nanoid', () => ({ jest.mock('nanoid', () => ({
nanoid: jest.fn(() => 'mocked-id') nanoid: jest.fn(() => 'mocked-id')
})); }));
const mockProps: TemplateOptions & MultipleChoiceType = { const mockProps: TemplateOptions & MultipleChoiceQuestion = {
type: 'MC', type: 'MC',
hasEmbeddedAnswers: false, hasEmbeddedAnswers: false,
title: 'Sample Title', title: 'Sample Title',
stem: { format: 'plain' , text: 'Sample Stem'}, formattedStem: { format: 'plain' , text: 'Sample Stem'},
choices: [ choices: [
{ text: { format: 'plain' , text: 'Choice 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }, { formattedText: { format: 'plain' , text: 'Choice 1'}, isCorrect: true, formattedFeedback: { 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 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', type: 'MC',
hasEmbeddedAnswers: false, hasEmbeddedAnswers: false,
title: 'Sample Title', title: 'Sample Title',
stem: { format: 'plain' , text: '$$\\frac{zzz}{yyy}$$'}, formattedStem: { format: 'plain' , text: '$$\\frac{zzz}{yyy}$$'},
choices: [ choices: [
{ text: { format: 'plain' , text: 'Choice 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }, { formattedText: { format: 'plain' , text: 'Choice 1'}, isCorrect: true, formattedFeedback: { 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 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', type: 'MC',
hasEmbeddedAnswers: false, hasEmbeddedAnswers: false,
title: 'Sample Title with Image', title: 'Sample Title with Image',
stem: { format: 'plain', text: 'Sample Stem with Image' }, formattedStem: { format: 'plain', text: 'Sample Stem with Image' },
choices: [ choices: [
{ text: { format: 'plain', text: 'Choice 1' }, isCorrect: true, feedback: { format: 'plain', text: 'Correct!' }, weight: 1 }, { formattedText: { format: 'plain', text: 'Choice 1' }, isCorrect: true, formattedFeedback: { 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 2' }, isCorrect: false, formattedFeedback: { 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: '<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', type: 'MC',
hasEmbeddedAnswers: false, hasEmbeddedAnswers: false,
title: 'Sample Title', title: 'Sample Title',
stem: { format: 'moodle' , text: 'Sample Stem'}, formattedStem: { format: 'moodle' , text: 'Sample Stem'},
choices: [ choices: [
{ text: { format: 'moodle' , text: 'Choice 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }, { formattedText: { format: 'moodle' , text: 'Choice 1'}, isCorrect: true, formattedFeedback: { 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 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', type: 'MC',
hasEmbeddedAnswers: false, hasEmbeddedAnswers: false,
title: 'Sample Title', title: 'Sample Title',
stem: { format: 'html' , text: '$$\\frac{zzz}{yyy}$$'}, formattedStem: { format: 'html' , text: '$$\\frac{zzz}{yyy}$$'},
choices: [ choices: [
{ text: { format: 'html' , text: 'Choice 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }, { formattedText: { format: 'html' , text: 'Choice 1'}, isCorrect: true, formattedFeedback: { 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 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', type: 'MC',
hasEmbeddedAnswers: false, hasEmbeddedAnswers: false,
title: 'Sample Title with Image', title: 'Sample Title with Image',
stem: { format: 'markdown', text: 'Sample Stem with Image' }, formattedStem: { format: 'markdown', text: 'Sample Stem with Image' },
choices: [ choices: [
{ text: { format: 'markdown', text: 'Choice 1' }, isCorrect: true, feedback: { format: 'plain', text: 'Correct!' }, weight: 1 }, { formattedText: { format: 'markdown', text: 'Choice 1' }, isCorrect: true, formattedFeedback: { format: 'plain', text: 'Correct!' }, weight: 1 },
{ text: { format: 'markdown', text: 'Choice 2' }, isCorrect: false, feedback: { format: 'plain', text: 'Incorrect!' }, weight: 1 }, { formattedText: { format: 'markdown', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { 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: '<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', type: 'MC',
hasEmbeddedAnswers: false, hasEmbeddedAnswers: false,
title: 'Sample Title with Image', 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: [ choices: [
{ text: { format: 'markdown', text: 'Choice 1' }, isCorrect: true, feedback: { format: 'plain', text: 'Correct!' }, weight: 1 }, { formattedText: { format: 'markdown', text: 'Choice 1' }, isCorrect: true, formattedFeedback: { format: 'plain', text: 'Correct!' }, weight: 1 },
{ text: { format: 'markdown', text: 'Choice 2' }, isCorrect: false, feedback: { format: 'plain', text: 'Incorrect!' }, weight: 1 }, { formattedText: { format: 'markdown', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { 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: '<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', () => { test('MultipleChoice snapshot test', () => {

View file

@ -1,62 +1,40 @@
import React from 'react'; import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import Numerical from 'src/components/GiftTemplate/templates/Numerical'; import Numerical from 'src/components/GiftTemplate/templates/NumericalTemplate';
import { TemplateOptions, Numerical as NumericalType } from 'src/components/GiftTemplate/templates/types'; import { TemplateOptions } from 'src/components/GiftTemplate/templates/types';
import { parse, NumericalQuestion } from 'gift-pegjs';
// Mock the nanoid function // Mock the nanoid function
jest.mock('nanoid', () => ({ jest.mock('nanoid', () => ({
nanoid: jest.fn(() => 'mocked-id') nanoid: jest.fn(() => 'mocked-id')
})); }));
const plainTextMock: TemplateOptions & NumericalType = { const plainTextMock: TemplateOptions & NumericalQuestion =
type: 'Numerical', parse(`
hasEmbeddedAnswers: false, ::Sample Numerical Title:: Sample Stem {#=42#Correct!=43#Incorrect!####Sample Global Feedback}
title: 'Sample Numerical Title', `)[0] as NumericalQuestion;
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 htmlMock: TemplateOptions & NumericalType = { const htmlMock: TemplateOptions & NumericalQuestion =
type: 'Numerical', parse(`
hasEmbeddedAnswers: false, ::Sample Numerical Title::
title: 'Sample Numerical Title', [html]$$\\frac\\{zzz\\}\\{yyy\\}$$ {#
stem: { format: 'html', text: '$$\\frac{zzz}{yyy}$$' }, =42#Correct
choices: [ =43#Incorrect!
{ isCorrect: true, weight: 1, text: { type: 'simple', number: 42}, feedback: { format: 'html', text: 'Correct!' } }, ####Sample Global Feedback
{ isCorrect: false, weight: 1, text: { type: 'simple', number: 43}, feedback: { format: 'html', text: 'Incorrect!' } } }
], `)[0] as NumericalQuestion;
globalFeedback: { format: 'html', text: 'Sample Global Feedback' }
};
const moodleMock: TemplateOptions & NumericalType = { const moodleMock: TemplateOptions & NumericalQuestion =
type: 'Numerical', parse(`
hasEmbeddedAnswers: false, ::Sample Numerical Title::[moodle]Sample Stem {#=42#Correct!=43#Incorrect!####Sample Global Feedback}
title: 'Sample Numerical Title', `)[0] as NumericalQuestion;
stem: { format: 'moodle', text: 'Sample Stem' },
choices: [ const imageMock: TemplateOptions & NumericalQuestion =
{ isCorrect: true, weight: 1, text: { type: 'simple', number: 42}, feedback: { format: 'moodle', text: 'Correct!' } }, parse(`
{ isCorrect: false, weight: 1, text: { type: 'simple', number: 43}, feedback: { format: 'moodle', text: 'Incorrect!' } } ::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;
globalFeedback: { format: 'moodle', text: 'Sample Global Feedback' }
};
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', () => { test('Numerical snapshot test with plain text', () => {
const { asFragment } = render(<Numerical {...plainTextMock} />); const { asFragment } = render(<Numerical {...plainTextMock} />);

View file

@ -1,63 +1,35 @@
import React from 'react'; import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import ShortAnswer from 'src/components/GiftTemplate/templates/ShortAnswer'; import ShortAnswer from 'src/components/GiftTemplate/templates/ShortAnswerTemplate';
import { TemplateOptions, ShortAnswer as ShortAnswerType } from 'src/components/GiftTemplate/templates/types'; import { TemplateOptions } from 'src/components/GiftTemplate/templates/types';
import { parse, ShortAnswerQuestion } from 'gift-pegjs';
// Mock the nanoid function // Mock the nanoid function
jest.mock('nanoid', () => ({ jest.mock('nanoid', () => ({
nanoid: jest.fn(() => 'mocked-id') nanoid: jest.fn(() => 'mocked-id')
})); }));
const plainTextMock: TemplateOptions & ShortAnswerType = { const plainTextMock: TemplateOptions & ShortAnswerQuestion =
type: 'Short', parse(`
hasEmbeddedAnswers: false, ::Sample Short Answer Title:: Sample Stem {=%1%Answer 1#Correct! =%1%Answer 2#Correct!####Sample Global Feedback}
title: 'Sample Short Answer Title', `)[0] as ShortAnswerQuestion;
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 katexMock: TemplateOptions & ShortAnswerType = { const katexMock: TemplateOptions & ShortAnswerQuestion =
type: 'Short', parse(`
hasEmbeddedAnswers: false, ::Sample Short Answer Title:: $$\\frac\\{zzz\\}\\{yyy\\}$$ {=%1%Answer 1#Correct! =%1%Answer 2#Correct!####[html]Sample Global Feedback}
title: 'Sample Short Answer Title', `)[0] as ShortAnswerQuestion;
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 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 = { const moodleMock: TemplateOptions & ShortAnswerQuestion =
type: 'Short', parse(`
hasEmbeddedAnswers: false, ::Sample Short Answer Title:: Sample Stem {=%1%Answer 1#Correct! =%1%Answer 2#Correct!####[moodle]Sample Global Feedback}
title: 'Sample Short Answer Title with Image', `)[0] as ShortAnswerQuestion;
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 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', () => { test('ShortAnswer snapshot test with plain text', () => {
const { asFragment } = render(<ShortAnswer {...plainTextMock} />); const { asFragment } = render(<ShortAnswer {...plainTextMock} />);

View file

@ -2,56 +2,27 @@ import React from 'react';
import { render } from '@testing-library/react'; import { render } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import TrueFalse from 'src/components/GiftTemplate/templates'; 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 // Mock the nanoid function
jest.mock('nanoid', () => ({ jest.mock('nanoid', () => ({
nanoid: jest.fn(() => 'mocked-id') nanoid: jest.fn(() => 'mocked-id')
})); }));
const plainTextMock: TemplateOptions & TrueFalseType = { const plainTextMock: TemplateOptions & TrueFalseQuestion =
type: 'TF', parse(`::Sample True/False Title::Sample Stem {T#Correct!#Incorrect!####Sample Global Feedback}`)[0] as TrueFalseQuestion;
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 katexMock: TemplateOptions & TrueFalseType = { const katexMock: TemplateOptions & TrueFalseQuestion =
type: 'TF', parse(`::Sample True/False Title::$$\\frac{zzz}{yyy}$$ {T#Correct!#Incorrect!####Sample Global Feedback}`)[0] as TrueFalseQuestion;
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 moodleMock: TemplateOptions & TrueFalseType = { const moodleMock: TemplateOptions & TrueFalseQuestion =
type: 'TF', parse(`::Sample True/False Title::[moodle]Sample Stem{TRUE#Correct!#Incorrect!####Sample Global Feedback}`)[0] as TrueFalseQuestion;
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 imageMock: TemplateOptions & TrueFalseType = { const imageMock: TemplateOptions & ShortAnswerQuestion =
type: 'TF', parse(`::Sample Short Answer Title with Image::
hasEmbeddedAnswers: false, [markdown]Sample Stem with Image ![](https\\://example.com/cat.gif)
title: 'Sample Short Answer Title with Image', {=A =B =C####[html]<img src="https\\://via.placeholder.com/150" alt="Sample Image" />}`)[0] as ShortAnswerQuestion;
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" />' }
};
test('TrueFalse snapshot test with plain text', () => { test('TrueFalse snapshot test with plain text', () => {
const { asFragment } = render(<TrueFalse {...plainTextMock} />); const { asFragment } = render(<TrueFalse {...plainTextMock} />);

View file

@ -40,7 +40,7 @@ exports[`ShortAnswer snapshot test with image 1`] = `
&lt;/div&gt; &lt;/div&gt;
&lt;p style=" &lt;p style="
color: hsl(0, 0%, 0%); 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;/p&gt;
&lt;div&gt; &lt;div&gt;
&lt;span style=" &lt;span style="
@ -59,9 +59,7 @@ exports[`ShortAnswer snapshot test with image 1`] = `
font-size: inherit; font-size: inherit;
line-height: inherit; line-height: inherit;
width: 90%; width: 90%;
" placeholder="Answer 1 " placeholder="Answer 1, Answer 2"&gt;
, Answer 2
, &lt;img src="https://via.placeholder.com/150" alt="Sample Image" /&gt;"&gt;
&lt;/div&gt; &lt;/div&gt;
&lt;div style=" &lt;div style="
position: relative; position: relative;
@ -73,7 +71,8 @@ exports[`ShortAnswer snapshot test with image 1`] = `
border-radius: 6px; border-radius: 6px;
box-shadow: 0px 2px 5px hsl(0, 0%, 74%); box-shadow: 0px 2px 5px hsl(0, 0%, 74%);
"&gt; "&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; &lt;/div&gt;&lt;/section&gt;
</DocumentFragment> </DocumentFragment>
`; `;

View file

@ -3,15 +3,17 @@ import React from 'react';
import { render, fireEvent, screen, act } from '@testing-library/react'; import { render, fireEvent, screen, act } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import TrueFalseQuestion from 'src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay'; import TrueFalseQuestionDisplay from 'src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay';
import { parse, TrueFalseQuestion } from 'gift-pegjs';
describe('TrueFalseQuestion Component', () => { describe('TrueFalseQuestion Component', () => {
const mockHandleSubmitAnswer = jest.fn(); 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 = { const sampleProps = {
questionTitle: 'Sample True/False Question', question: trueFalseQuestion,
correctAnswer: true,
handleOnSubmitAnswer: mockHandleSubmitAnswer, handleOnSubmitAnswer: mockHandleSubmitAnswer,
showAnswer: false showAnswer: false
}; };
@ -19,7 +21,7 @@ describe('TrueFalseQuestion Component', () => {
beforeEach(() => { beforeEach(() => {
render( render(
<MemoryRouter> <MemoryRouter>
<TrueFalseQuestion questionContent={{ text: sampleStem, format: 'plain' }} {...sampleProps} /> <TrueFalseQuestionDisplay {...sampleProps} />
</MemoryRouter>); </MemoryRouter>);
}); });

View file

@ -2,20 +2,20 @@ import React from 'react';
import { render, screen, fireEvent, act } from '@testing-library/react'; import { render, screen, fireEvent, act } from '@testing-library/react';
import '@testing-library/jest-dom'; import '@testing-library/jest-dom';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import { Question } from 'gift-pegjs';
import StudentModeQuiz from 'src/components/StudentModeQuiz/StudentModeQuiz'; 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( const mockGiftQuestions = parse(
`::Sample Question 1:: Sample Question 1 {=Option A ~Option B} `::Sample Question 1:: Sample Question 1 {=Option A ~Option B}
::Sample Question 2:: Sample Question 2 {T}`); ::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") if (question.type !== "Category")
question.id = (index + 1).toString(); question.id = (index + 1).toString();
const newMockQuestion = question; const newMockQuestion = question;
return newMockQuestion; return {question : newMockQuestion as BaseQuestion};
}); });
const mockSubmitAnswer = jest.fn(); const mockSubmitAnswer = jest.fn();

View file

@ -1,256 +1,21 @@
import Template, { ErrorTemplate } from './templates'; import Template, { ErrorTemplate } from './templates';
import { GIFTQuestion } from './templates/types';
import './styles.css'; 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 items = multiple.map((item) => Template(item, { theme: 'dark' })).join('');
const errorItemDark = ErrorTemplate('Hello'); 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 { TemplateOptions } from './types';
import QuestionContainer from './QuestionContainer'; import QuestionContainer from './QuestionContainerTemplate';
import Title from './Title'; import Title from './TitleTemplate';
import { textType } from './TextType'; import { textType } from './TextTypeTemplate';
import { ParagraphStyle } from '../constants'; import { ParagraphStyle } from '../constants';
import { state } from '.'; import { state } from '.';
import { Description } from 'gift-pegjs'; 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 { TemplateOptions } from './types';
import textType from './TextType'; import {textType} from './TextTypeTemplate';
import { state } from '.'; import { state } from '.';
import { theme } from '../constants'; import { theme } from '../constants';
import { TextFormat } from 'gift-pegjs';
interface GlobalFeedbackOptions extends TemplateOptions { type GlobalFeedbackOptions = TemplateOptions & TextFormat;
feedback: Question['globalFeedback'];
}
export default function GlobalFeedback({ feedback }: GlobalFeedbackOptions): string { export default function GlobalFeedbackTemplate({ format, text }: GlobalFeedbackOptions): string {
const Container = ` const Container = `
position: relative; position: relative;
margin-top: 1rem; margin-top: 1rem;
@ -19,9 +18,9 @@ export default function GlobalFeedback({ feedback }: GlobalFeedbackOptions): str
box-shadow: 0px 2px 5px ${theme(state.theme, 'gray400', 'black800')}; box-shadow: 0px 2px 5px ${theme(state.theme, 'gray400', 'black800')};
`; `;
return feedback !== null return (format && text)
? `<div style="${Container}"> ? `<div style="${Container}">
<p>${textType({ text: feedback })}</p> <p>${textType({format: format, text: text})}</p>
</div>` </div>`
: ``; : ``;
} }

View file

@ -1,22 +1,23 @@
import { TemplateOptions, Matching as MatchingType } from './types'; import { TemplateOptions } from './types';
import QuestionContainer from './QuestionContainer'; import QuestionContainer from './QuestionContainerTemplate';
import Title from './Title'; import Title from './TitleTemplate';
import textType from './TextType'; import {textType} from './TextTypeTemplate';
import GlobalFeedback from './GlobalFeedback'; import GlobalFeedback from './GlobalFeedbackTemplate';
import { ParagraphStyle, SelectStyle } from '../constants'; import { ParagraphStyle, SelectStyle } from '../constants';
import { state } from '.'; import { state } from '.';
import { MatchingQuestion } from 'gift-pegjs';
type MatchingOptions = TemplateOptions & MatchingType; type MatchingOptions = TemplateOptions & MatchingQuestion;
interface MatchAnswerOptions extends TemplateOptions { interface MatchAnswerOptions extends TemplateOptions {
choices: MatchingType['matchPairs']; choices: MatchingQuestion['matchPairs'];
} }
export default function Matching({ export default function MatchingTemplate({
title, title,
stem, formattedStem,
matchPairs, matchPairs,
globalFeedback formattedGlobalFeedback
}: MatchingOptions): string { }: MatchingOptions): string {
return `${QuestionContainer({ return `${QuestionContainer({
children: [ children: [
@ -24,11 +25,9 @@ export default function Matching({
type: 'Appariement', type: 'Appariement',
title: title title: title
}), }),
`<p style="${ParagraphStyle(state.theme)}">${textType({ `<p style="${ParagraphStyle(state.theme)}">${textType(formattedStem)}</p>`,
text: stem
})}</p>`,
MatchAnswers({ choices: matchPairs }), 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 uniqueMatchOptions = Array.from(new Set(choices.map(({ subanswer }) => subanswer)));
const result = choices const result = choices
.map(({ subquestion }) => { .map(({ formattedSubquestion }) => {
return ` return `
<div style="${OptionTable} ${ParagraphStyle(state.theme)}"> <div style="${OptionTable} ${ParagraphStyle(state.theme)}">
${textType({ text: subquestion })} ${textType(formattedSubquestion)}
</div> </div>
<div> <div>
<select class="gift-select" style="${SelectStyle(state.theme)} ${Dropdown}"> <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 { nanoid } from 'nanoid';
import { TemplateOptions, TextFormat, Choice, MultipleChoice as MultipleChoiceType } from './types'; import { TemplateOptions } from './types';
import textType from './TextType'; import {textType} from './TextTypeTemplate';
import AnswerIcon from './AnswerIcon'; import AnswerIcon from './AnswerIconTemplate';
import { state } from '.'; import { state } from '.';
import { ParagraphStyle, theme } from '../constants'; 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 { interface AnswerWeightOptions extends TemplateOptions {
weight: Choice['weight']; weight: TextChoice['weight'];
correct: Choice['isCorrect']; correct: TextChoice['isCorrect'];
} }
export default function MultipleChoiceAnswers({ choices }: MultipleChoiceAnswerOptions) { export default function MultipleChoiceAnswersTemplate({ choices }: MultipleChoiceAnswerOptions) {
const id = `id${nanoid(8)}`; const id = `id${nanoid(8)}`;
const isMultipleAnswer = choices.filter(({ isCorrect }) => isCorrect === true).length === 0; const isMultipleAnswer = choices.filter(({ isCorrect }) => isCorrect === true).length === 0;
@ -23,7 +24,7 @@ export default function MultipleChoiceAnswers({ choices }: MultipleChoiceAnswerO
isMultipleAnswer ? ` ou plusieurs` : `` isMultipleAnswer ? ` ou plusieurs` : ``
}:</span>`; }:</span>`;
const result = choices const result = choices
.map(({ weight, isCorrect, text, feedback }) => { .map(({ weight, isCorrect, formattedText, formattedFeedback }) => {
const CustomLabel = ` const CustomLabel = `
display: inline-block; display: inline-block;
padding: 0.2em 0 0.2em 0; padding: 0.2em 0 0.2em 0;
@ -31,7 +32,7 @@ export default function MultipleChoiceAnswers({ choices }: MultipleChoiceAnswerO
const inputId = `id${nanoid(6)}`; const inputId = `id${nanoid(6)}`;
const isPositiveWeight = weight !== null && weight > 0; const isPositiveWeight = (weight != undefined) && (weight > 0);
const isCorrectOption = isMultipleAnswer ? isPositiveWeight : isCorrect; const isCorrectOption = isMultipleAnswer ? isPositiveWeight : isCorrect;
return ` return `
@ -41,10 +42,10 @@ export default function MultipleChoiceAnswers({ choices }: MultipleChoiceAnswerO
}" id="${inputId}" name="${id}"> }" id="${inputId}" name="${id}">
${AnswerWeight({ correct: isCorrectOption, weight: weight })} ${AnswerWeight({ correct: isCorrectOption, weight: weight })}
<label style="${CustomLabel} ${ParagraphStyle(state.theme)}" for="${inputId}"> <label style="${CustomLabel} ${ParagraphStyle(state.theme)}" for="${inputId}">
${textType({ text: text as TextFormat })} ${textType(formattedText)}
</label> </label>
${AnswerIcon({ correct: isCorrectOption })} ${AnswerIcon({ correct: isCorrectOption })}
${AnswerFeedback({ feedback: feedback })} ${AnswerFeedback({ formattedFeedback: formattedFeedback })}
</input> </input>
</div> </div>
`; `;
@ -80,10 +81,10 @@ function AnswerWeight({ weight, correct }: AnswerWeightOptions): string {
: ``; : ``;
} }
function AnswerFeedback({ feedback }: AnswerFeedbackOptions): string { function AnswerFeedback({ formattedFeedback }: AnswerFeedbackOptions): string {
const Container = ` const Container = `
color: ${theme(state.theme, 'teal700', 'gray700')}; 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 { TemplateOptions } from './types';
import QuestionContainer from './QuestionContainer'; import QuestionContainer from './QuestionContainerTemplate';
import Title from './Title'; import Title from './TitleTemplate';
import textType from './TextType'; import {textType} from './TextTypeTemplate';
import GlobalFeedback from './GlobalFeedback'; import GlobalFeedback from './GlobalFeedbackTemplate';
import { ParagraphStyle, InputStyle } from '../constants'; import { ParagraphStyle, InputStyle } from '../constants';
import { state } from './index'; import { state } from './index';
import { ShortAnswerQuestion } from 'gift-pegjs';
type ShortAnswerOptions = TemplateOptions & ShortAnswerType; type ShortAnswerOptions = TemplateOptions & ShortAnswerQuestion;
type AnswerOptions = TemplateOptions & Pick<ShortAnswerType, 'choices'>; type AnswerOptions = TemplateOptions & Pick<ShortAnswerQuestion, 'choices'>;
export default function ShortAnswer({ export default function ShortAnswerTemplate({
title, title,
stem, formattedStem,
choices, choices,
globalFeedback formattedGlobalFeedback
}: ShortAnswerOptions): string { }: ShortAnswerOptions): string {
return `${QuestionContainer({ return `${QuestionContainer({
children: [ children: [
@ -21,18 +22,16 @@ export default function ShortAnswer({
type: 'Réponse courte', type: 'Réponse courte',
title: title title: title
}), }),
`<p style="${ParagraphStyle(state.theme)}">${textType({ `<p style="${ParagraphStyle(state.theme)}">${textType(formattedStem)}</p>`,
text: stem
})}</p>`,
Answers({ choices: choices }), Answers({ choices: choices }),
GlobalFeedback({ feedback: globalFeedback }) formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : ''
] ]
})}`; })}`;
} }
function Answers({ choices }: AnswerOptions): string { function Answers({ choices }: AnswerOptions): string {
const placeholder = choices const placeholder = choices
.map(({ text }) => textType({ text: text as TextFormat })) .map(({ text }) => textType({ format: '', text: text }))
.join(', '); .join(', ');
return ` return `
<div> <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 const formatText = formatLatex(formattedText.text.trim()); // latex needs pure "&", ">", etc. Must not be escaped
let parsedText = ''; let parsedText = '';
switch (formattedText.format) { switch (formattedText.format) {
case '':
case 'moodle': case 'moodle':
case 'plain': case 'plain':
// Replace newlines with <br> tags // Replace newlines with <br> tags

View file

@ -1,6 +1,7 @@
import { TemplateOptions, Question } from './types'; import { TemplateOptions } from './types';
import { state } from '.'; import { state } from '.';
import { theme } from '../constants'; import { theme } from '../constants';
import { Question } from 'gift-pegjs';
// Type is string to allow for custom question type text (e,g, "Multiple Choice") // Type is string to allow for custom question type text (e,g, "Multiple Choice")
interface TitleOptions extends TemplateOptions { 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 { import {
GIFTQuestion, ParsedGIFTQuestion as GIFTQuestion,
// Category as CategoryType, // Category as CategoryType,
// Description as DescriptionType, // Description as DescriptionType,
MultipleChoice as MultipleChoiceType, MultipleChoiceQuestion as MultipleChoiceType,
Numerical as NumericalType, NumericalQuestion as NumericalType,
ShortAnswer as ShortAnswerType, ShortAnswerQuestion as ShortAnswerType,
// Essay as EssayType, // Essay as EssayType,
TrueFalse as TrueFalseType, TrueFalseQuestion as TrueFalseType,
Matching as MatchingType, // MatchingQuestion as MatchingType,
DisplayOptions } from 'gift-pegjs';
} from './types'; 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' }; export const state: DisplayOptions = { preview: true, theme: 'light' };
@ -37,21 +38,21 @@ export default function Template(
// ...(keys as DescriptionType) // ...(keys as DescriptionType)
// }); // });
case 'MC': case 'MC':
return MultipleChoice({ return MultipleChoiceTemplate({
...(keys as MultipleChoiceType) ...(keys as MultipleChoiceType)
}); });
case 'Numerical': case 'Numerical':
return Numerical({ ...(keys as NumericalType) }); return NumericalTemplate({ ...(keys as NumericalType) });
case 'Short': case 'Short':
return ShortAnswer({ return ShortAnswerTemplate({
...(keys as ShortAnswerType) ...(keys as ShortAnswerType)
}); });
// case 'Essay': // case 'Essay':
// return Essay({ ...(keys as EssayType) }); // return Essay({ ...(keys as EssayType) });
case 'TF': case 'TF':
return TrueFalse({ ...(keys as TrueFalseType) }); return TrueFalseTemplate({ ...(keys as TrueFalseType) });
case 'Matching': // case 'Matching':
return Matching({ ...(keys as MatchingType) }); // return Matching({ ...(keys as MatchingType) });
default: default:
// TODO: throw error for unsupported question types? // TODO: throw error for unsupported question types?
// throw new Error(`Unsupported question type: ${type}`); // throw new Error(`Unsupported question type: ${type}`);
@ -66,13 +67,13 @@ export function ErrorTemplate(text: string, options?: Partial<DisplayOptions>):
} }
export { export {
Category, CategoryTemplate,
DescriptionTemplate as Description, DescriptionTemplate as Description,
Essay, EssayTemplate as Essay,
Matching, MatchingTemplate as Matching,
MultipleChoice, MultipleChoiceTemplate as MultipleChoice,
Numerical, NumericalTemplate as Numerical,
ShortAnswer, ShortAnswerTemplate as ShortAnswer,
TrueFalse, TrueFalseTemplate as TrueFalse,
Error Error
}; };

View file

@ -20,7 +20,7 @@ import {
TableRow TableRow
} from '@mui/material'; } from '@mui/material';
import { StudentType } from '../../Types/StudentType'; import { StudentType } from '../../Types/StudentType';
import { formatLatex } from '../GiftTemplate/templates/TextType'; import { formatLatex } from '../GiftTemplate/templates/TextTypeTemplate';
interface LiveResultsProps { interface LiveResultsProps {
socket: Socket | null; socket: Socket | null;

View file

@ -2,7 +2,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import '../questionStyle.css'; import '../questionStyle.css';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
import { textType } from '../../GiftTemplate/templates/TextType'; import { textType } from '../../GiftTemplate/templates/TextTypeTemplate';
import { MultipleChoiceQuestion } from 'gift-pegjs'; import { MultipleChoiceQuestion } from 'gift-pegjs';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
@ -31,7 +31,7 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
return ( return (
<div className="question-container"> <div className="question-container">
<div className="question content"> <div className="question content">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({text: question.formattedStem})) }} /> <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedStem)) }} />
</div> </div>
<div className="choices-wrapper mb-1"> <div className="choices-wrapper mb-1">
{question.choices.map((choice, i) => { {question.choices.map((choice, i) => {
@ -48,13 +48,13 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
(choice.isCorrect ? '✅' : '❌')} (choice.isCorrect ? '✅' : '❌')}
<div className={`circle ${selected}`}>{alphabet[i]}</div> <div className={`circle ${selected}`}>{alphabet[i]}</div>
<div className={`answer-text ${selected}`}> <div className={`answer-text ${selected}`}>
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({ text: choice.formattedText })) }} /> <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(choice.formattedText)) }} />
</div> </div>
</Button> </Button>
{choice.formattedFeedback && showAnswer && ( {choice.formattedFeedback && showAnswer && (
<div className="feedback-container mb-1 mt-1/2"> <div className="feedback-container mb-1 mt-1/2">
{choice.isCorrect ? '✅' : '❌'} {choice.isCorrect ? '✅' : '❌'}
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({ text: choice.formattedFeedback })) }} /> <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(choice.formattedFeedback)) }} />
</div> </div>
)} )}
</div> </div>
@ -63,8 +63,8 @@ const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
</div> </div>
{question.formattedGlobalFeedback && showAnswer && ( {question.formattedGlobalFeedback && showAnswer && (
<div className="global-feedback mb-2"> <div className="global-feedback mb-2">
<p>${textType({ text: question.formattedGlobalFeedback })}</p> <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedGlobalFeedback)) }} />
</div> </div>
)} )}
{!showAnswer && handleOnSubmitAnswer && ( {!showAnswer && handleOnSubmitAnswer && (

View file

@ -2,7 +2,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import '../questionStyle.css'; import '../questionStyle.css';
import { Button, TextField } from '@mui/material'; 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 { NumericalQuestion, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs';
import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer } from 'gift-pegjs/typeGuards'; import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer } from 'gift-pegjs/typeGuards';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
@ -41,13 +41,13 @@ const NumericalQuestionDisplay: React.FC<Props> = (props) => {
return ( return (
<div className="question-wrapper"> <div className="question-wrapper">
<div> <div>
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({ text: question.formattedStem })) }} /> <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedStem)) }} />
</div> </div>
{showAnswer ? ( {showAnswer ? (
<> <>
<div className="correct-answer-text mb-2">{correctAnswer}</div> <div className="correct-answer-text mb-2">{correctAnswer}</div>
{question.formattedGlobalFeedback && <div className="global-feedback mb-2"> {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>} </div>}
</> </>
) : ( ) : (
@ -65,7 +65,7 @@ const NumericalQuestionDisplay: React.FC<Props> = (props) => {
</div> </div>
{question.formattedGlobalFeedback && showAnswer && ( {question.formattedGlobalFeedback && showAnswer && (
<div className="global-feedback mb-2"> <div className="global-feedback mb-2">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({ text: question.formattedGlobalFeedback })) }} /> <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedGlobalFeedback)) }} />
</div> </div>
)} )}
{handleOnSubmitAnswer && ( {handleOnSubmitAnswer && (

View file

@ -1,8 +1,7 @@
// Question;tsx
import React from 'react'; import React from 'react';
import { Question } from 'gift-pegjs'; import { Question } from 'gift-pegjs';
import TrueFalseQuestion from './TrueFalseQuestion/TrueFalseQuestion'; import TrueFalseQuestionDisplay from './TrueFalseQuestionDisplay/TrueFalseQuestionDisplay';
import MultipleChoiceQuestionDisplay from './MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay'; import MultipleChoiceQuestionDisplay from './MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay';
import NumericalQuestionDisplay from './NumericalQuestionDisplay/NumericalQuestionDisplay'; import NumericalQuestionDisplay from './NumericalQuestionDisplay/NumericalQuestionDisplay';
import ShortAnswerQuestionDisplay from './ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay'; import ShortAnswerQuestionDisplay from './ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay';
@ -27,12 +26,10 @@ const QuestionDisplay: React.FC<QuestionProps> = ({
switch (question?.type) { switch (question?.type) {
case 'TF': case 'TF':
questionTypeComponent = ( questionTypeComponent = (
<TrueFalseQuestion <TrueFalseQuestionDisplay
questionContent={question.formattedStem} question={question}
correctAnswer={question.isTrue}
handleOnSubmitAnswer={handleOnSubmitAnswer} handleOnSubmitAnswer={handleOnSubmitAnswer}
showAnswer={showAnswer} showAnswer={showAnswer}
globalFeedback={question.formattedGlobalFeedback?.text}
/> />
); );
break; break;

View file

@ -1,7 +1,7 @@
import React, { useState } from 'react'; import React, { useState } from 'react';
import '../questionStyle.css'; import '../questionStyle.css';
import { Button, TextField } from '@mui/material'; import { Button, TextField } from '@mui/material';
import { textType } from '../../GiftTemplate/templates/TextType'; import { textType } from '../../GiftTemplate/templates/TextTypeTemplate';
import { ShortAnswerQuestion } from 'gift-pegjs'; import { ShortAnswerQuestion } from 'gift-pegjs';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
@ -18,7 +18,7 @@ const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
return ( return (
<div className="question-wrapper"> <div className="question-wrapper">
<div className="question content"> <div className="question content">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({text: question.formattedStem})) }} /> <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedStem)) }} />
</div> </div>
{showAnswer ? ( {showAnswer ? (
<> <>
@ -30,7 +30,7 @@ const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
))} ))}
</div> </div>
{question.formattedGlobalFeedback && <div className="global-feedback mb-2"> {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>} </div>}
</> </>
) : ( ) : (

View file

@ -2,33 +2,32 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import '../questionStyle.css'; import '../questionStyle.css';
import { Button } from '@mui/material'; import { Button } from '@mui/material';
import { textType } from '../../GiftTemplate/templates/TextType'; import { TrueFalseQuestion } from 'gift-pegjs';
import { TextFormat } from 'gift-pegjs';
import DOMPurify from 'dompurify'; import DOMPurify from 'dompurify';
import { textType } from 'src/components/GiftTemplate/templates/TextTypeTemplate';
interface Props { interface Props {
questionContent: TextFormat; question: TrueFalseQuestion;
correctAnswer: boolean;
globalFeedback?: string | undefined;
handleOnSubmitAnswer?: (answer: boolean) => void; handleOnSubmitAnswer?: (answer: boolean) => void;
showAnswer?: boolean; showAnswer?: boolean;
} }
const TrueFalseQuestion: React.FC<Props> = (props) => { const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
const { questionContent, correctAnswer, showAnswer, handleOnSubmitAnswer, globalFeedback } = const { question, showAnswer, handleOnSubmitAnswer } =
props; props;
const [answer, setAnswer] = useState<boolean | undefined>(undefined); const [answer, setAnswer] = useState<boolean | undefined>(undefined);
useEffect(() => { useEffect(() => {
setAnswer(undefined); setAnswer(undefined);
}, [questionContent]); }, [question]);
const selectedTrue = answer ? 'selected' : ''; const selectedTrue = answer ? 'selected' : '';
const selectedFalse = answer !== undefined && !answer ? 'selected' : ''; const selectedFalse = answer !== undefined && !answer ? 'selected' : '';
const correctAnswer = question.isTrue === answer;
return ( return (
<div className="question-container"> <div className="question-container">
<div className="question content"> <div className="question content">
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({ text: questionContent })) }} /> <div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType(question.formattedStem)) }} />
</div> </div>
<div className="choices-wrapper mb-1"> <div className="choices-wrapper mb-1">
<Button <Button
@ -50,8 +49,22 @@ const TrueFalseQuestion: React.FC<Props> = (props) => {
<div className={`answer-text ${selectedFalse}`}>Faux</div> <div className={`answer-text ${selectedFalse}`}>Faux</div>
</Button> </Button>
</div> </div>
{globalFeedback && showAnswer && ( {/* selected TRUE, show True feedback if it exists */}
<div className="global-feedback mb-2">{globalFeedback}</div> {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 && ( {!showAnswer && handleOnSubmitAnswer && (
<Button <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 // StudentModeQuiz.tsx
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import QuestionComponent from '../Questions/QuestionDisplay'; import QuestionComponent from '../QuestionsDisplay/QuestionDisplay';
import '../../pages/Student/JoinRoom/joinRoom.css'; import '../../pages/Student/JoinRoom/joinRoom.css';
import { QuestionType } from '../../Types/QuestionType'; import { QuestionType } from '../../Types/QuestionType';
// import { QuestionService } from '../../services/QuestionService'; // import { QuestionService } from '../../services/QuestionService';
@ -8,6 +8,7 @@ import { Button } from '@mui/material';
//import QuestionNavigation from '../QuestionNavigation/QuestionNavigation'; //import QuestionNavigation from '../QuestionNavigation/QuestionNavigation';
//import { ChevronLeft, ChevronRight } from '@mui/icons-material'; //import { ChevronLeft, ChevronRight } from '@mui/icons-material';
import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
import { Question } from 'gift-pegjs';
interface StudentModeQuizProps { interface StudentModeQuizProps {
questions: QuestionType[]; questions: QuestionType[];
@ -63,7 +64,7 @@ const StudentModeQuiz: React.FC<StudentModeQuizProps> = ({
</div> </div>
<QuestionComponent <QuestionComponent
handleOnSubmitAnswer={handleOnSubmitAnswer} handleOnSubmitAnswer={handleOnSubmitAnswer}
question={questionInfos.question} question={questionInfos.question as Question}
showAnswer={isAnswerSubmitted} showAnswer={isAnswerSubmitted}
/> />
<div className="center-h-align mt-1/2"> <div className="center-h-align mt-1/2">

View file

@ -1,13 +1,14 @@
// TeacherModeQuiz.tsx // TeacherModeQuiz.tsx
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import QuestionComponent from '../Questions/QuestionDisplay'; import QuestionComponent from '../QuestionsDisplay/QuestionDisplay';
import '../../pages/Student/JoinRoom/joinRoom.css'; import '../../pages/Student/JoinRoom/joinRoom.css';
import { QuestionType } from '../../Types/QuestionType'; import { QuestionType } from '../../Types/QuestionType';
// import { QuestionService } from '../../services/QuestionService'; // import { QuestionService } from '../../services/QuestionService';
import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material'; import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material';
import { Question } from 'gift-pegjs';
interface TeacherModeQuizProps { interface TeacherModeQuizProps {
questionInfos: QuestionType; questionInfos: QuestionType;
@ -63,7 +64,7 @@ const TeacherModeQuiz: React.FC<TeacherModeQuizProps> = ({
) : ( ) : (
<QuestionComponent <QuestionComponent
handleOnSubmitAnswer={handleOnSubmitAnswer} handleOnSubmitAnswer={handleOnSubmitAnswer}
question={questionInfos.question} question={questionInfos.question as Question}
/> />
)} )}
@ -76,7 +77,7 @@ const TeacherModeQuiz: React.FC<TeacherModeQuizProps> = ({
{feedbackMessage} {feedbackMessage}
<QuestionComponent <QuestionComponent
handleOnSubmitAnswer={handleOnSubmitAnswer} handleOnSubmitAnswer={handleOnSubmitAnswer}
question={questionInfos.question} question={questionInfos.question as Question}
showAnswer={true} showAnswer={true}
/> />
</DialogContent> </DialogContent>

View file

@ -2,8 +2,8 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { Socket } from 'socket.io-client'; import { Socket } from 'socket.io-client';
import { GIFTQuestion, parse } from 'gift-pegjs'; import { ParsedGIFTQuestion, BaseQuestion, parse, Question } from 'gift-pegjs';
import { QuestionType } from '../../../Types/QuestionType'; import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer } from "gift-pegjs/typeGuards";
import LiveResultsComponent from 'src/components/LiveResults/LiveResults'; import LiveResultsComponent from 'src/components/LiveResults/LiveResults';
// import { QuestionService } from '../../../services/QuestionService'; // import { QuestionService } from '../../../services/QuestionService';
import webSocketService, { AnswerReceptionFromBackendType } from '../../../services/WebsocketService'; 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 StudentWaitPage from 'src/components/StudentWaitPage/StudentWaitPage';
import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton'; import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
//import QuestionNavigation from 'src/components/QuestionNavigation/QuestionNavigation'; //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 ApiService from '../../../services/ApiService';
import { QuestionType } from 'src/Types/QuestionType';
const ManageRoom: React.FC = () => { const ManageRoom: React.FC = () => {
const navigate = useNavigate(); const navigate = useNavigate();
@ -277,7 +278,7 @@ const ManageRoom: React.FC = () => {
const parsedQuestions = [] as QuestionType[]; const parsedQuestions = [] as QuestionType[];
quizQuestionArray.forEach((question, index) => { 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(); parsedQuestions[index].question.id = (index + 1).toString();
}); });
if (parsedQuestions.length === 0) return null; if (parsedQuestions.length === 0) return null;
@ -347,7 +348,7 @@ const ManageRoom: React.FC = () => {
const answerText = answer.toString(); const answerText = answer.toString();
if (questionInfo) { if (questionInfo) {
const question = questionInfo.question as GIFTQuestion; const question = questionInfo.question as ParsedGIFTQuestion;
if (question.type === 'TF') { if (question.type === 'TF') {
return ( return (
(question.isTrue && answerText == 'true') || (question.isTrue && answerText == 'true') ||
@ -355,53 +356,39 @@ const ManageRoom: React.FC = () => {
); );
} else if (question.type === 'MC') { } else if (question.type === 'MC') {
return question.choices.some( return question.choices.some(
(choice) => choice.isCorrect && choice.text.text === answerText (choice) => choice.isCorrect && choice.formattedText.text === answerText
); );
} else if (question.type === 'Numerical') { } else if (question.type === 'Numerical') {
if (question.choices && !Array.isArray(question.choices)) { if (isHighLowNumericalAnswer(question.choices[0])) {
if ( const choice = question.choices[0];
question.choices.type === 'high-low' && const answerNumber = parseFloat(answerText);
question.choices.numberHigh && if (!isNaN(answerNumber)) {
question.choices.numberLow return (
) { answerNumber <= choice.numberHigh &&
const answerNumber = parseFloat(answerText); answerNumber >= choice.numberLow
if (!isNaN(answerNumber)) { );
return (
answerNumber <= question.choices.numberHigh &&
answerNumber >= question.choices.numberLow
);
}
} }
} }
if (question.choices && Array.isArray(question.choices)) { if (isRangeNumericalAnswer(question.choices[0])) {
if ( const answerNumber = parseFloat(answerText);
question.choices[0].text.type === 'range' && const range = question.choices[0].range;
question.choices[0].text.number && const correctAnswer = question.choices[0].number;
question.choices[0].text.range if (!isNaN(answerNumber)) {
) { return (
const answerNumber = parseFloat(answerText); answerNumber <= correctAnswer + range &&
const range = question.choices[0].text.range; answerNumber >= correctAnswer - range
const correctAnswer = question.choices[0].text.number; );
if (!isNaN(answerNumber)) {
return (
answerNumber <= correctAnswer + range &&
answerNumber >= correctAnswer - range
);
}
} }
if ( }
question.choices[0].text.type === 'simple' && if (isSimpleNumericalAnswer(question.choices[0])) {
question.choices[0].text.number const answerNumber = parseFloat(answerText);
) { if (!isNaN(answerNumber)) {
const answerNumber = parseFloat(answerText); return answerNumber === question.choices[0].number;
if (!isNaN(answerNumber)) {
return answerNumber === question.choices[0].text.number;
}
} }
} }
} else if (question.type === 'Short') { } else if (question.type === 'Short') {
return question.choices.some( 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 && ( {currentQuestion && (
<QuestionDisplay <QuestionDisplay
showAnswer={false} showAnswer={false}
question={currentQuestion?.question} question={currentQuestion?.question as Question}
/> />
)} )}

View file

@ -2,14 +2,19 @@
"compilerOptions": { "compilerOptions": {
"baseUrl": "./", "baseUrl": "./",
"paths": { "paths": {
"src/*": ["src/*"] "src/*": [
"src/*"
]
}, },
"target": "ESNext", "target": "ESNext",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"], "lib": [
"ES2020",
"DOM",
"DOM.Iterable"
],
"module": "ESNext", "module": "ESNext",
"skipLibCheck": true, "skipLibCheck": true,
/* Bundler mode */ /* Bundler mode */
"moduleResolution": "bundler", "moduleResolution": "bundler",
"allowImportingTsExtensions": true, "allowImportingTsExtensions": true,
@ -17,7 +22,6 @@
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react", "jsx": "react",
/* Linting */ /* Linting */
"strict": true, "strict": true,
"noUnusedLocals": true, "noUnusedLocals": true,
@ -25,6 +29,16 @@
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"esModuleInterop": true "esModuleInterop": true
}, },
"include": ["src"], // "exclude": [
"references": [{ "path": "./tsconfig.node.json" }] // // "src/components/GiftTemplate/**/*",
// // "src/__tests__/components/GiftTemplate/**/*",
// ],
"include": [
"src"
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
} }