mirror of
https://github.com/ets-cfuhrman-pfe/EvalueTonSavoir.git
synced 2025-08-11 21:23:54 -04:00
Merge pull request #211 from ets-cfuhrman-pfe/gift-pegjs-2.0
Some checks are pending
CI/CD Pipeline for Backend / build_and_push_backend (push) Waiting to run
CI/CD Pipeline for Nginx Router / build_and_push_nginx (push) Waiting to run
CI/CD Pipeline for Frontend / build_and_push_frontend (push) Waiting to run
Tests / tests (client) (push) Waiting to run
Tests / tests (server) (push) Waiting to run
Some checks are pending
CI/CD Pipeline for Backend / build_and_push_backend (push) Waiting to run
CI/CD Pipeline for Nginx Router / build_and_push_nginx (push) Waiting to run
CI/CD Pipeline for Frontend / build_and_push_frontend (push) Waiting to run
Tests / tests (client) (push) Waiting to run
Tests / tests (server) (push) Waiting to run
Gift pegjs 2.0
This commit is contained in:
commit
763f606712
84 changed files with 1693 additions and 1810 deletions
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
|
@ -19,7 +19,7 @@ jobs:
|
|||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '18'
|
||||
node-version: '20'
|
||||
|
||||
- name: Install Dependencies, lint and Run Tests
|
||||
run: |
|
||||
|
|
|
|||
13
client/package-lock.json
generated
13
client/package-lock.json
generated
|
|
@ -21,7 +21,7 @@
|
|||
"axios": "^1.6.7",
|
||||
"dompurify": "^3.2.3",
|
||||
"esbuild": "^0.23.1",
|
||||
"gift-pegjs": "^1.0.2",
|
||||
"gift-pegjs": "^2.0.0-beta.1",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"katex": "^0.16.11",
|
||||
"marked": "^14.1.2",
|
||||
|
|
@ -66,6 +66,9 @@
|
|||
"vite-plugin-environment": "^1.1.3"
|
||||
}
|
||||
},
|
||||
"../GIFT-grammar-PEG.js": {
|
||||
"extraneous": true
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
"version": "4.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.0.tgz",
|
||||
|
|
@ -7410,10 +7413,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/gift-pegjs": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/gift-pegjs/-/gift-pegjs-1.0.2.tgz",
|
||||
"integrity": "sha512-S/A2wBDdia2QWKpB5FtASx1gguep1wg5If5glDWJgUMiABICJT7ogArGfsdgozevhBdbdOiHhrykJP86hbgvRw==",
|
||||
"license": "MIT",
|
||||
"version": "2.0.0-beta.1",
|
||||
"resolved": "https://registry.npmjs.org/gift-pegjs/-/gift-pegjs-2.0.0-beta.1.tgz",
|
||||
"integrity": "sha512-NFWSu3KjpjKrfnbIu/eQOyQqjCgOd/ONDe3+bKhtTQCrTgQPVoybme9cm8tqBmJz1YynloocrPlv9f2syQl/LQ==",
|
||||
"dependencies": {
|
||||
"pegjs": "^0.10.x"
|
||||
}
|
||||
|
|
@ -10689,7 +10691,6 @@
|
|||
"version": "0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/pegjs/-/pegjs-0.10.0.tgz",
|
||||
"integrity": "sha512-qI5+oFNEGi3L5HAxDwN2LA4Gg7irF70Zs25edhjld9QemOgp0CbvMtbFcMvFtEo1OityPrcCzkQFB8JP/hxgow==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"pegjs": "bin/pegjs"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@
|
|||
"axios": "^1.6.7",
|
||||
"dompurify": "^3.2.3",
|
||||
"esbuild": "^0.23.1",
|
||||
"gift-pegjs": "^1.0.2",
|
||||
"gift-pegjs": "^2.0.0-beta.1",
|
||||
"jest-environment-jsdom": "^29.7.0",
|
||||
"katex": "^0.16.11",
|
||||
"marked": "^14.1.2",
|
||||
|
|
@ -69,4 +69,4 @@
|
|||
"vite": "^5.4.5",
|
||||
"vite-plugin-environment": "^1.1.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { GIFTQuestion } from 'gift-pegjs';
|
||||
import { BaseQuestion } from "gift-pegjs";
|
||||
|
||||
export interface QuestionType {
|
||||
question: GIFTQuestion;
|
||||
question: BaseQuestion;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
console.log('constantsMock.tsx is loaded');
|
||||
// console.log('constantsMock.tsx is loaded');
|
||||
|
||||
// constants.tsx
|
||||
const ENV_VARIABLES = {
|
||||
|
|
@ -7,7 +7,7 @@ const ENV_VARIABLES = {
|
|||
VITE_BACKEND_SOCKET_URL: process.env.VITE_BACKEND_SOCKET_URL || "",
|
||||
};
|
||||
|
||||
console.log(`ENV_VARIABLES.VITE_BACKEND_URL=${ENV_VARIABLES.VITE_BACKEND_URL}`);
|
||||
console.log(`ENV_VARIABLES.VITE_BACKEND_SOCKET_URL=${ENV_VARIABLES.VITE_BACKEND_SOCKET_URL}`);
|
||||
// console.log(`ENV_VARIABLES.VITE_BACKEND_URL=${ENV_VARIABLES.VITE_BACKEND_URL}`);
|
||||
// console.log(`ENV_VARIABLES.VITE_BACKEND_SOCKET_URL=${ENV_VARIABLES.VITE_BACKEND_SOCKET_URL}`);
|
||||
|
||||
export { ENV_VARIABLES };
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
//QuestionType.test.tsx
|
||||
import { GIFTQuestion } from 'gift-pegjs';
|
||||
import { QuestionType } from '../../Types/QuestionType';
|
||||
// Superfluous test now that gift-pegjs has TypeScript types (and its own tests)
|
||||
import { Question } from 'gift-pegjs';
|
||||
|
||||
const sampleStem = 'Sample question stem';
|
||||
const options = ['Option A', 'Option B'];
|
||||
|
|
@ -8,30 +8,28 @@ const sampleFormat = 'plain';
|
|||
const sampleType = 'MC';
|
||||
const sampleTitle = 'Sample Question';
|
||||
|
||||
const mockQuestion: GIFTQuestion = {
|
||||
const mockQuestion: Question = {
|
||||
id: '1',
|
||||
type: sampleType,
|
||||
stem: { format: sampleFormat, text: sampleStem },
|
||||
formattedStem: { format: sampleFormat, text: sampleStem },
|
||||
title: sampleTitle,
|
||||
hasEmbeddedAnswers: false,
|
||||
globalFeedback: null,
|
||||
choices: [
|
||||
{ text: { format: sampleFormat, text: options[0] }, isCorrect: true, weight: 1, feedback: null },
|
||||
{ text: { format: sampleFormat, text: options[1] }, isCorrect: false, weight: 0, feedback: null },
|
||||
{ formattedText: { format: sampleFormat, text: options[0] }, isCorrect: true, weight: 1 },
|
||||
{ formattedText: { format: sampleFormat, text: options[1] }, isCorrect: false, weight: 0 },
|
||||
],
|
||||
};
|
||||
|
||||
const mockQuestionType: QuestionType = {
|
||||
question: mockQuestion,
|
||||
};
|
||||
const mockQuestionType = mockQuestion;
|
||||
|
||||
describe('QuestionType', () => {
|
||||
// test seems useless (it's broken) now that gift-pegjs has TypeScript types (and its own tests)
|
||||
describe.skip('QuestionType', () => {
|
||||
test('has the expected structure', () => {
|
||||
expect(mockQuestionType).toEqual(expect.objectContaining({
|
||||
question: expect.any(Object),
|
||||
}));
|
||||
|
||||
expect(mockQuestionType.question).toEqual(expect.objectContaining({
|
||||
expect(mockQuestionType).toEqual(expect.objectContaining({
|
||||
id: expect.any(String),
|
||||
type: expect.any(String),
|
||||
stem: expect.objectContaining({
|
||||
|
|
|
|||
|
|
@ -1,29 +1,30 @@
|
|||
// TextType.test.ts
|
||||
|
||||
import { FormattedTextTemplate } from "src/components/GiftTemplate/templates/TextTypeTemplate";
|
||||
import { TextFormat } from "gift-pegjs";
|
||||
import textType from "src/components/GiftTemplate/templates/TextType";
|
||||
|
||||
describe('TextType', () => {
|
||||
it('should format text with basic characters correctly', () => {
|
||||
const input: TextFormat = {
|
||||
// Text here would already be past the GIFT parsing stage, so we don't need to escape GIFT special characters
|
||||
text: 'Hello, world! 5 > 3, right?',
|
||||
format: 'plain'
|
||||
format: 'moodle'
|
||||
};
|
||||
const expectedOutput = 'Hello, world! 5 > 3, right?';
|
||||
expect(textType({ text: input })).toBe(expectedOutput);
|
||||
expect(FormattedTextTemplate(input)).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
it('should format text with newlines correctly', () => {
|
||||
it('should format text with embedded newlines correctly', () => {
|
||||
const input: TextFormat = {
|
||||
// Text here would already be past the GIFT parsing stage, so we don't need to escape GIFT special characters
|
||||
text: 'Hello,\nworld!\n5 > 3, right?',
|
||||
format: 'plain'
|
||||
format: 'moodle'
|
||||
};
|
||||
const expectedOutput = 'Hello,<br>world!<br>5 > 3, right?';
|
||||
expect(textType({ text: input })).toBe(expectedOutput);
|
||||
const expectedOutput = 'Hello,<br>world!<br>5 > 3, right?';
|
||||
expect(FormattedTextTemplate(input)).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
it('should format text with LaTeX correctly', () => {
|
||||
it('should format text with display-mode LaTeX correctly, with $$ delimiters', () => {
|
||||
const input: TextFormat = {
|
||||
// Text here would already be past the GIFT parsing stage, so we don't need to escape GIFT special characters
|
||||
text: '$$E=mc^2$$',
|
||||
format: 'plain'
|
||||
};
|
||||
|
|
@ -32,28 +33,42 @@ describe('TextType', () => {
|
|||
// Hint -- if the output changes because of a change in the code or library, you can update
|
||||
// by running the test and copying the "Received string:" in jest output
|
||||
// when it fails (assuming the output is correct)
|
||||
const expectedOutput = '<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">E</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8641em;"></span><span class="mord mathnormal">m</span><span class="mord"><span class="mord mathnormal">c</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span>';
|
||||
expect(textType({ text: input })).toContain(expectedOutput);
|
||||
const expectedOutput = '<span class="katex-display"><span class="katex"><span class="katex-mathml"><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow>E=mc^2</math></span><span aria-hidden="true" class="katex-html"><span class="base"><span style="height:0.6833em;" class="strut"></span><span style="margin-right:0.05764em;" class="mord mathnormal">E</span><span style="margin-right:0.2778em;" class="mspace"></span><span class="mrel">=</span><span style="margin-right:0.2778em;" class="mspace"></span></span><span class="base"><span style="height:0.8641em;" class="strut"></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 style="height:0.8641em;" class="vlist"><span style="top:-3.113em;margin-right:0.05em;"><span style="height:2.7em;" class="pstrut"></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(FormattedTextTemplate(input)).toContain(expectedOutput);
|
||||
});
|
||||
|
||||
it('should format text with two equations (inline and separate) correctly', () => {
|
||||
const input: TextFormat = {
|
||||
// Text here would already be past the GIFT parsing stage, so we don't need to escape GIFT special characters
|
||||
text: '$a + b = c$ ? $$E=mc^2$$',
|
||||
format: 'plain'
|
||||
format: 'moodle'
|
||||
};
|
||||
// hint: katex-display is the class that indicates a separate equation
|
||||
const expectedOutput = '<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>a</mi><mo>+</mo><mi>b</mi><mo>=</mo><mi>c</mi></mrow><annotation encoding="application/x-tex">a + b = c</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6667em;vertical-align:-0.0833em;"></span><span class="mord mathnormal">a</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="base"><span class="strut" style="height:0.6944em;"></span><span class="mord mathnormal">b</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.4306em;"></span><span class="mord mathnormal">c</span></span></span></span> ? <span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow><annotation encoding="application/x-tex">E=mc^2</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.05764em;">E</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="base"><span class="strut" style="height:0.8641em;"></span><span class="mord mathnormal">m</span><span class="mord"><span class="mord mathnormal">c</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8641em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span></span></span></span></span></span></span></span></span>';
|
||||
expect(textType({ text: input })).toContain(expectedOutput);
|
||||
const expectedOutput = '<span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mi>a</mi><mo>+</mo><mi>b</mi><mo>=</mo><mi>c</mi></mrow>a + b = c</math></span><span aria-hidden="true" class="katex-html"><span class="base"><span style="height:0.6667em;vertical-align:-0.0833em;" class="strut"></span><span class="mord mathnormal">a</span><span style="margin-right:0.2222em;" class="mspace"></span><span class="mbin">+</span><span style="margin-right:0.2222em;" class="mspace"></span></span><span class="base"><span style="height:0.6944em;" class="strut"></span><span class="mord mathnormal">b</span><span style="margin-right:0.2778em;" class="mspace"></span><span class="mrel">=</span><span style="margin-right:0.2778em;" class="mspace"></span></span><span class="base"><span style="height:0.4306em;" class="strut"></span><span class="mord mathnormal">c</span></span></span></span> ? <span class="katex-display"><span class="katex"><span class="katex-mathml"><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mi>E</mi><mo>=</mo><mi>m</mi><msup><mi>c</mi><mn>2</mn></msup></mrow>E=mc^2</math></span><span aria-hidden="true" class="katex-html"><span class="base"><span style="height:0.6833em;" class="strut"></span><span style="margin-right:0.05764em;" class="mord mathnormal">E</span><span style="margin-right:0.2778em;" class="mspace"></span><span class="mrel">=</span><span style="margin-right:0.2778em;" class="mspace"></span></span><span class="base"><span style="height:0.8641em;" class="strut"></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 style="height:0.8641em;" class="vlist"><span style="top:-3.113em;margin-right:0.05em;"><span style="height:2.7em;" class="pstrut"></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(FormattedTextTemplate(input)).toContain(expectedOutput);
|
||||
});
|
||||
|
||||
it('should format text with a katex matrix correctly', () => {
|
||||
it('should format text with an inline katex matrix correctly', () => {
|
||||
const input: TextFormat = {
|
||||
// eslint-disable-next-line no-useless-escape
|
||||
text: `Donnez le déterminant de la matrice suivante.$$\\begin\{pmatrix\}\n a&b \\\\\n c&d\n\\end\{pmatrix\}`,
|
||||
format: 'plain'
|
||||
// Text here would already be past the GIFT parsing stage, so we don't need to escape GIFT special characters
|
||||
text: `Inline matrix: \\( \\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix} \\)`,
|
||||
format: ''
|
||||
};
|
||||
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);
|
||||
|
||||
// eslint-disable-next-line no-irregular-whitespace
|
||||
// warning: there are zero-width spaces "" in the expected output -- you must enable seeing them with an extension such as Gremlins tracker in VSCode
|
||||
|
||||
// eslint-disable-next-line no-irregular-whitespace
|
||||
const expectedOutput = `Inline matrix: <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em"><mtr><mtd><mstyle displaystyle="false" scriptlevel="0"><mi>a</mi></mstyle></mtd><mtd><mstyle displaystyle="false" scriptlevel="0"><mi>b</mi></mstyle></mtd></mtr><mtr><mtd><mstyle displaystyle="false" scriptlevel="0"><mi>c</mi></mstyle></mtd><mtd><mstyle displaystyle="false" scriptlevel="0"><mi>d</mi></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow> \\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix} </math></span><span aria-hidden="true" class="katex-html"><span class="base"><span style="height:2.4em;vertical-align:-0.95em;" class="strut"></span><span class="minner"><span style="top:0em;" class="mopen delimcenter"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span style="height:1.45em;" class="vlist"><span style="top:-3.61em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span class="mord mathnormal">a</span></span></span><span style="top:-2.41em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span class="mord mathnormal">c</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span style="height:0.95em;" class="vlist"><span></span></span></span></span></span><span style="width:0.5em;" class="arraycolsep"></span><span style="width:0.5em;" class="arraycolsep"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span style="height:1.45em;" class="vlist"><span style="top:-3.61em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span class="mord mathnormal">b</span></span></span><span style="top:-2.41em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span class="mord mathnormal">d</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span style="height:0.95em;" class="vlist"><span></span></span></span></span></span></span></span><span style="top:0em;" class="mclose delimcenter"><span class="delimsizing size3">)</span></span></span></span></span></span>`;
|
||||
expect(FormattedTextTemplate(input)).toContain(expectedOutput);
|
||||
});
|
||||
|
||||
it('should format text with an inline katex matrix correctly, with \\( and \\) as inline delimiters.', () => {
|
||||
const input: TextFormat = {
|
||||
text: `Donnez le déterminant de la matrice suivante.\\( \\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix} \\)`,
|
||||
format: ''
|
||||
};
|
||||
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"><mrow><mo fence="true">(</mo><mtable rowspacing="0.16em"><mtr><mtd><mstyle displaystyle="false" scriptlevel="0"><mi>a</mi></mstyle></mtd><mtd><mstyle displaystyle="false" scriptlevel="0"><mi>b</mi></mstyle></mtd></mtr><mtr><mtd><mstyle displaystyle="false" scriptlevel="0"><mi>c</mi></mstyle></mtd><mtd><mstyle displaystyle="false" scriptlevel="0"><mi>d</mi></mstyle></mtd></mtr></mtable><mo fence="true">)</mo></mrow> \\begin{pmatrix} a & b \\\\ c & d \\end{pmatrix} </math></span><span aria-hidden="true" class="katex-html"><span class="base"><span style="height:2.4em;vertical-align:-0.95em;" class="strut"></span><span class="minner"><span style="top:0em;" class="mopen delimcenter"><span class="delimsizing size3">(</span></span><span class="mord"><span class="mtable"><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span style="height:1.45em;" class="vlist"><span style="top:-3.61em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span class="mord mathnormal">a</span></span></span><span style="top:-2.41em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span class="mord mathnormal">c</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span style="height:0.95em;" class="vlist"><span></span></span></span></span></span><span style="width:0.5em;" class="arraycolsep"></span><span style="width:0.5em;" class="arraycolsep"></span><span class="col-align-c"><span class="vlist-t vlist-t2"><span class="vlist-r"><span style="height:1.45em;" class="vlist"><span style="top:-3.61em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span class="mord mathnormal">b</span></span></span><span style="top:-2.41em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span class="mord mathnormal">d</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span style="height:0.95em;" class="vlist"><span></span></span></span></span></span></span></span><span style="top:0em;" class="mclose delimcenter"><span class="delimsizing size3">)</span></span></span></span></span></span>';
|
||||
expect(FormattedTextTemplate(input)).toContain(expectedOutput);
|
||||
});
|
||||
|
||||
it('should format text with Markdown correctly', () => {
|
||||
|
|
@ -63,7 +78,7 @@ describe('TextType', () => {
|
|||
};
|
||||
// TODO: investigate why the output has an extra newline
|
||||
const expectedOutput = '<strong>Bold</strong>\n';
|
||||
expect(textType({ text: input })).toBe(expectedOutput);
|
||||
expect(FormattedTextTemplate(input)).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
it('should format text with HTML correctly', () => {
|
||||
|
|
@ -72,7 +87,7 @@ describe('TextType', () => {
|
|||
format: 'html'
|
||||
};
|
||||
const expectedOutput = '<em>yes</em>';
|
||||
expect(textType({ text: input })).toBe(expectedOutput);
|
||||
expect(FormattedTextTemplate(input)).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
it('should format plain text correctly', () => {
|
||||
|
|
@ -81,8 +96,16 @@ describe('TextType', () => {
|
|||
format: 'plain'
|
||||
};
|
||||
const expectedOutput = 'Just plain text';
|
||||
expect(textType({ text: input })).toBe(expectedOutput);
|
||||
expect(FormattedTextTemplate(input)).toBe(expectedOutput);
|
||||
});
|
||||
|
||||
// Add more tests for other formats if needed
|
||||
it('should format a resized image correctly', () => {
|
||||
const input: TextFormat = {
|
||||
text: '',
|
||||
format: 'markdown'
|
||||
};
|
||||
const expectedOutput = '<img width="50p" alt="" src="https://www.etsmtl.ca/assets/img/ets.svg">\n';
|
||||
expect(FormattedTextTemplate(input)).toBe(expectedOutput);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import AnswerIcon from 'src/components/GiftTemplate/templates/AnswerIcon';
|
||||
import AnswerIcon from 'src/components/GiftTemplate/templates/AnswerIconTemplate';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
describe('AnswerIcon', () => {
|
||||
|
|
|
|||
|
|
@ -2,99 +2,100 @@ import React from 'react';
|
|||
import { render } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { MultipleChoice } from 'src/components/GiftTemplate/templates';
|
||||
import { TemplateOptions, MultipleChoice as MultipleChoiceType } from 'src/components/GiftTemplate/templates/types';
|
||||
import { TemplateOptions } from 'src/components/GiftTemplate/templates/types';
|
||||
import { MultipleChoiceQuestion } from 'gift-pegjs';
|
||||
|
||||
// Mock the nanoid function
|
||||
jest.mock('nanoid', () => ({
|
||||
nanoid: jest.fn(() => 'mocked-id')
|
||||
}));
|
||||
|
||||
const mockProps: TemplateOptions & MultipleChoiceType = {
|
||||
const mockProps: TemplateOptions & MultipleChoiceQuestion = {
|
||||
type: 'MC',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Title',
|
||||
stem: { format: 'plain' , text: 'Sample Stem'},
|
||||
formattedStem: { format: 'plain' , text: 'Sample Stem'},
|
||||
choices: [
|
||||
{ text: { format: 'plain' , text: 'Choice 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 },
|
||||
{ text: { format: 'plain', text: 'Choice 2' }, isCorrect: false, feedback: { format: 'plain' , text: 'InCorrect!'}, weight: 1 }
|
||||
{ formattedText: { format: 'plain' , text: 'Choice 1'}, isCorrect: true, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 1 },
|
||||
{ formattedText: { format: 'plain', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { format: 'plain' , text: 'InCorrect!'}, weight: 1 }
|
||||
],
|
||||
globalFeedback: { format: 'plain', text: 'Sample Global Feedback' }
|
||||
formattedGlobalFeedback: { format: 'plain', text: 'Sample Global Feedback' }
|
||||
};
|
||||
|
||||
const katekMock: TemplateOptions & MultipleChoiceType = {
|
||||
const katekMock: TemplateOptions & MultipleChoiceQuestion = {
|
||||
type: 'MC',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Title',
|
||||
stem: { format: 'plain' , text: '$$\\frac{zzz}{yyy}$$'},
|
||||
formattedStem: { format: 'plain' , text: '$$\\frac{zzz}{yyy}$$'},
|
||||
choices: [
|
||||
{ text: { format: 'plain' , text: 'Choice 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 },
|
||||
{ text: { format: 'plain', text: 'Choice 2' }, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }
|
||||
{ formattedText: { format: 'plain' , text: 'Choice 1'}, isCorrect: true, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 1 },
|
||||
{ formattedText: { format: 'plain', text: 'Choice 2' }, isCorrect: true, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }
|
||||
],
|
||||
globalFeedback: { format: 'plain', text: 'Sample Global Feedback' }
|
||||
formattedGlobalFeedback: { format: 'plain', text: 'Sample Global Feedback' }
|
||||
};
|
||||
|
||||
const imageMock: TemplateOptions & MultipleChoiceType = {
|
||||
const imageMock: TemplateOptions & MultipleChoiceQuestion = {
|
||||
type: 'MC',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Title with Image',
|
||||
stem: { format: 'plain', text: 'Sample Stem with Image' },
|
||||
formattedStem: { format: 'plain', text: 'Sample Stem with Image' },
|
||||
choices: [
|
||||
{ text: { format: 'plain', text: 'Choice 1' }, isCorrect: true, feedback: { format: 'plain', text: 'Correct!' }, weight: 1 },
|
||||
{ text: { format: 'plain', text: 'Choice 2' }, isCorrect: false, feedback: { format: 'plain', text: 'Incorrect!' }, weight: 1 },
|
||||
{ text: { format: 'plain', text: '<img src="https://via.placeholder.com/150" alt="Sample Image" />' }, isCorrect: false, feedback: { format: 'plain', text: 'Image Feedback' }, weight: 1 }
|
||||
{ formattedText: { format: 'plain', text: 'Choice 1' }, isCorrect: true, formattedFeedback: { format: 'plain', text: 'Correct!' }, weight: 1 },
|
||||
{ formattedText: { format: 'plain', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { format: 'plain', text: 'Incorrect!' }, weight: 1 },
|
||||
{ formattedText: { format: 'plain', text: '<img src="https://via.placeholder.com/150" alt="Sample Image" />' }, isCorrect: false, formattedFeedback: { format: 'plain', text: 'Image Feedback' }, weight: 1 }
|
||||
],
|
||||
globalFeedback: { format: 'plain', text: 'Sample Global Feedback with Image' }
|
||||
formattedGlobalFeedback: { format: 'plain', text: 'Sample Global Feedback with Image' }
|
||||
};
|
||||
|
||||
const mockMoodle: TemplateOptions & MultipleChoiceType = {
|
||||
const mockMoodle: TemplateOptions & MultipleChoiceQuestion = {
|
||||
type: 'MC',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Title',
|
||||
stem: { format: 'moodle' , text: 'Sample Stem'},
|
||||
formattedStem: { format: 'moodle' , text: 'Sample Stem'},
|
||||
choices: [
|
||||
{ text: { format: 'moodle' , text: 'Choice 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 },
|
||||
{ text: { format: 'plain', text: 'Choice 2' }, isCorrect: false, feedback: { format: 'plain' , text: 'InCorrect!'}, weight: 1 }
|
||||
{ formattedText: { format: 'moodle' , text: 'Choice 1'}, isCorrect: true, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 1 },
|
||||
{ formattedText: { format: 'plain', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { format: 'plain' , text: 'InCorrect!'}, weight: 1 }
|
||||
],
|
||||
globalFeedback: { format: 'plain', text: 'Sample Global Feedback' }
|
||||
formattedGlobalFeedback: { format: 'plain', text: 'Sample Global Feedback' }
|
||||
};
|
||||
|
||||
|
||||
const mockHTML: TemplateOptions & MultipleChoiceType = {
|
||||
const mockHTML: TemplateOptions & MultipleChoiceQuestion = {
|
||||
type: 'MC',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Title',
|
||||
stem: { format: 'html' , text: '$$\\frac{zzz}{yyy}$$'},
|
||||
formattedStem: { format: 'html' , text: '$$\\frac{zzz}{yyy}$$'},
|
||||
choices: [
|
||||
{ text: { format: 'html' , text: 'Choice 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 },
|
||||
{ text: { format: 'html', text: 'Choice 2' }, isCorrect: false, feedback: { format: 'plain' , text: 'InCorrect!'}, weight: 1 }
|
||||
{ formattedText: { format: 'html' , text: 'Choice 1'}, isCorrect: true, formattedFeedback: { format: 'plain' , text: 'Correct!'}, weight: 1 },
|
||||
{ formattedText: { format: 'html', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { format: 'plain' , text: 'InCorrect!'}, weight: 1 }
|
||||
],
|
||||
globalFeedback: { format: 'html', text: 'Sample Global Feedback' }
|
||||
formattedGlobalFeedback: { format: 'html', text: 'Sample Global Feedback' }
|
||||
};
|
||||
|
||||
const mockMarkdown: TemplateOptions & MultipleChoiceType = {
|
||||
const mockMarkdown: TemplateOptions & MultipleChoiceQuestion = {
|
||||
type: 'MC',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Title with Image',
|
||||
stem: { format: 'markdown', text: 'Sample Stem with Image' },
|
||||
formattedStem: { format: 'markdown', text: 'Sample Stem with Image' },
|
||||
choices: [
|
||||
{ text: { format: 'markdown', text: 'Choice 1' }, isCorrect: true, feedback: { format: 'plain', text: 'Correct!' }, weight: 1 },
|
||||
{ text: { format: 'markdown', text: 'Choice 2' }, isCorrect: false, feedback: { format: 'plain', text: 'Incorrect!' }, weight: 1 },
|
||||
{ text: { format: 'markdown', text: '<img src="https://via.placeholder.com/150" alt="Sample Image" />' }, isCorrect: false, feedback: { format: 'plain', text: 'Image Feedback' }, weight: 1 }
|
||||
{ formattedText: { format: 'markdown', text: 'Choice 1' }, isCorrect: true, formattedFeedback: { format: 'plain', text: 'Correct!' }, weight: 1 },
|
||||
{ formattedText: { format: 'markdown', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { format: 'plain', text: 'Incorrect!' }, weight: 1 },
|
||||
{ formattedText: { format: 'markdown', text: '<img src="https://via.placeholder.com/150" alt="Sample Image" />' }, isCorrect: false, formattedFeedback: { format: 'plain', text: 'Image Feedback' }, weight: 1 }
|
||||
],
|
||||
globalFeedback: { format: 'markdown', text: 'Sample Global Feedback with Image' }
|
||||
formattedGlobalFeedback: { format: 'markdown', text: 'Sample Global Feedback with Image' }
|
||||
};
|
||||
|
||||
const mockMarkdownTwoImages: TemplateOptions & MultipleChoiceType = {
|
||||
const mockMarkdownTwoImages: TemplateOptions & MultipleChoiceQuestion = {
|
||||
type: 'MC',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Title with Image',
|
||||
stem: { format: 'markdown', text: '<img src="https://via.placeholder.com/150" alt = "Sample Image"/>' },
|
||||
formattedStem: { format: 'markdown', text: '<img src="https://via.placeholder.com/150" alt = "Sample Image"/>' },
|
||||
choices: [
|
||||
{ text: { format: 'markdown', text: 'Choice 1' }, isCorrect: true, feedback: { format: 'plain', text: 'Correct!' }, weight: 1 },
|
||||
{ text: { format: 'markdown', text: 'Choice 2' }, isCorrect: false, feedback: { format: 'plain', text: 'Incorrect!' }, weight: 1 },
|
||||
{ text: { format: 'markdown', text: '<img src="https://via.placeholder.com/150" alt="Sample Image" />' }, isCorrect: false, feedback: { format: 'plain', text: 'Image Feedback' }, weight: 1 }
|
||||
{ formattedText: { format: 'markdown', text: 'Choice 1' }, isCorrect: true, formattedFeedback: { format: 'plain', text: 'Correct!' }, weight: 1 },
|
||||
{ formattedText: { format: 'markdown', text: 'Choice 2' }, isCorrect: false, formattedFeedback: { format: 'plain', text: 'Incorrect!' }, weight: 1 },
|
||||
{ formattedText: { format: 'markdown', text: '<img src="https://via.placeholder.com/150" alt="Sample Image" />' }, isCorrect: false, formattedFeedback: { format: 'plain', text: 'Image Feedback' }, weight: 1 }
|
||||
],
|
||||
globalFeedback: { format: 'markdown', text: 'Sample Global Feedback with Image' }
|
||||
formattedGlobalFeedback: { format: 'markdown', text: 'Sample Global Feedback with Image' }
|
||||
};
|
||||
|
||||
test('MultipleChoice snapshot test', () => {
|
||||
|
|
@ -130,4 +131,4 @@ test('MultipleChoice snapshot test with image using markdown text format', () =>
|
|||
test('MultipleChoice snapshot test with 2 images using markdown text format', () => {
|
||||
const { asFragment } = render(<MultipleChoice {...mockMarkdownTwoImages} />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,62 +1,40 @@
|
|||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import Numerical from 'src/components/GiftTemplate/templates/Numerical';
|
||||
import { TemplateOptions, Numerical as NumericalType } from 'src/components/GiftTemplate/templates/types';
|
||||
import Numerical from 'src/components/GiftTemplate/templates/NumericalTemplate';
|
||||
import { TemplateOptions } from 'src/components/GiftTemplate/templates/types';
|
||||
import { parse, NumericalQuestion } from 'gift-pegjs';
|
||||
|
||||
// Mock the nanoid function
|
||||
jest.mock('nanoid', () => ({
|
||||
nanoid: jest.fn(() => 'mocked-id')
|
||||
}));
|
||||
|
||||
const plainTextMock: TemplateOptions & NumericalType = {
|
||||
type: 'Numerical',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Numerical Title',
|
||||
stem: { format: 'plain', text: 'Sample Stem' },
|
||||
choices: [
|
||||
{ isCorrect: true, weight: 1, text: { type: 'simple', number: 42}, feedback: { format: 'plain', text: 'Correct!' } },
|
||||
{ isCorrect: false, weight: 1, text: { type: 'simple', number: 43}, feedback: { format: 'plain', text: 'Incorrect!' } }
|
||||
],
|
||||
globalFeedback: { format: 'plain', text: 'Sample Global Feedback' }
|
||||
};
|
||||
const plainTextMock: TemplateOptions & NumericalQuestion =
|
||||
parse(`
|
||||
::Sample Numerical Title:: Sample Stem {#=42#Correct!=43#Incorrect!####Sample Global Feedback}
|
||||
`)[0] as NumericalQuestion;
|
||||
|
||||
const htmlMock: TemplateOptions & NumericalType = {
|
||||
type: 'Numerical',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Numerical Title',
|
||||
stem: { format: 'html', text: '$$\\frac{zzz}{yyy}$$' },
|
||||
choices: [
|
||||
{ isCorrect: true, weight: 1, text: { type: 'simple', number: 42}, feedback: { format: 'html', text: 'Correct!' } },
|
||||
{ isCorrect: false, weight: 1, text: { type: 'simple', number: 43}, feedback: { format: 'html', text: 'Incorrect!' } }
|
||||
],
|
||||
globalFeedback: { format: 'html', text: 'Sample Global Feedback' }
|
||||
};
|
||||
const htmlMock: TemplateOptions & NumericalQuestion =
|
||||
parse(`
|
||||
::Sample Numerical Title::
|
||||
[html]$$\\frac\\{zzz\\}\\{yyy\\}$$ {#
|
||||
=42#Correct
|
||||
=43#Incorrect!
|
||||
####Sample Global Feedback
|
||||
}
|
||||
`)[0] as NumericalQuestion;
|
||||
|
||||
const moodleMock: TemplateOptions & NumericalType = {
|
||||
type: 'Numerical',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Numerical Title',
|
||||
stem: { format: 'moodle', text: 'Sample Stem' },
|
||||
choices: [
|
||||
{ isCorrect: true, weight: 1, text: { type: 'simple', number: 42}, feedback: { format: 'moodle', text: 'Correct!' } },
|
||||
{ isCorrect: false, weight: 1, text: { type: 'simple', number: 43}, feedback: { format: 'moodle', text: 'Incorrect!' } }
|
||||
],
|
||||
globalFeedback: { format: 'moodle', text: 'Sample Global Feedback' }
|
||||
};
|
||||
const moodleMock: TemplateOptions & NumericalQuestion =
|
||||
parse(`
|
||||
::Sample Numerical Title::[moodle]Sample Stem {#=42#Correct!=43#Incorrect!####Sample Global Feedback}
|
||||
`)[0] as NumericalQuestion;
|
||||
|
||||
const imageMock: TemplateOptions & NumericalQuestion =
|
||||
parse(`
|
||||
::Sample Numerical Title with Image::[markdown]Sample Stem with Image {#=42#Correct!=43#Incorrect!=44#Also Incorrect! ####Sample Global Feedback with Image}
|
||||
`)[0] as NumericalQuestion;
|
||||
|
||||
const imageMock: TemplateOptions & NumericalType = {
|
||||
type: 'Numerical',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Numerical Title with Image',
|
||||
stem: { format: 'plain', text: 'Sample Stem with Image' },
|
||||
choices: [
|
||||
{ isCorrect: true, weight: 1, text: { type: 'simple', number: 42}, feedback: { format: 'plain', text: 'Correct!' } },
|
||||
{ isCorrect: false, weight: 1, text: { type: 'simple', number: 43}, feedback: { format: 'plain', text: 'Incorrect!' } },
|
||||
{ isCorrect: false, weight: 1, text: { type: 'simple', number: 44}, feedback: { format: 'plain', text: '<img src="https://via.placeholder.com/150" alt="Sample Image" />' } }
|
||||
],
|
||||
globalFeedback: { format: 'plain', text: 'Sample Global Feedback with Image' }
|
||||
};
|
||||
|
||||
test('Numerical snapshot test with plain text', () => {
|
||||
const { asFragment } = render(<Numerical {...plainTextMock} />);
|
||||
|
|
@ -76,4 +54,4 @@ test('Numerical snapshot test with moodle', () => {
|
|||
test('Numerical snapshot test with image', () => {
|
||||
const { asFragment } = render(<Numerical {...imageMock} />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,63 +1,35 @@
|
|||
import React from 'react';
|
||||
import { render } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import ShortAnswer from 'src/components/GiftTemplate/templates/ShortAnswer';
|
||||
import { TemplateOptions, ShortAnswer as ShortAnswerType } from 'src/components/GiftTemplate/templates/types';
|
||||
import ShortAnswer from 'src/components/GiftTemplate/templates/ShortAnswerTemplate';
|
||||
import { TemplateOptions } from 'src/components/GiftTemplate/templates/types';
|
||||
import { parse, ShortAnswerQuestion } from 'gift-pegjs';
|
||||
|
||||
// Mock the nanoid function
|
||||
jest.mock('nanoid', () => ({
|
||||
nanoid: jest.fn(() => 'mocked-id')
|
||||
}));
|
||||
|
||||
const plainTextMock: TemplateOptions & ShortAnswerType = {
|
||||
type: 'Short',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Short Answer Title',
|
||||
stem: { format: 'plain', text: 'Sample Stem' },
|
||||
choices: [
|
||||
{ text: { format: 'plain' , text: 'Answer 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 },
|
||||
{ text: { format: 'plain' , text: 'Answer 2'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }
|
||||
],
|
||||
globalFeedback: { format: 'plain', text: 'Sample Global Feedback' }
|
||||
};
|
||||
const plainTextMock: TemplateOptions & ShortAnswerQuestion =
|
||||
parse(`
|
||||
::Sample Short Answer Title:: Sample Stem {=%1%Answer 1#Correct! =%1%Answer 2#Correct!####Sample Global Feedback}
|
||||
`)[0] as ShortAnswerQuestion;
|
||||
|
||||
const katexMock: TemplateOptions & ShortAnswerType = {
|
||||
type: 'Short',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Short Answer Title',
|
||||
stem: { format: 'html', text: '$$\\frac{zzz}{yyy}$$' },
|
||||
choices: [
|
||||
{ text: { format: 'html' , text: 'Answer 1'}, isCorrect: true, feedback: { format: 'html' , text: 'Correct!'}, weight: 1 },
|
||||
{ text: { format: 'html' , text: 'Answer 2'}, isCorrect: true, feedback: { format: 'moodle' , text: 'Correct!'}, weight: 1 }
|
||||
],
|
||||
globalFeedback: { format: 'html', text: 'Sample Global Feedback' }
|
||||
};
|
||||
const katexMock: TemplateOptions & ShortAnswerQuestion =
|
||||
parse(`
|
||||
::Sample Short Answer Title:: $$\\frac\\{zzz\\}\\{yyy\\}$$ {=%1%Answer 1#Correct! =%1%Answer 2#Correct!####[html]Sample Global Feedback}
|
||||
`)[0] as ShortAnswerQuestion;
|
||||
|
||||
const moodleMock: TemplateOptions & ShortAnswerType = {
|
||||
type: 'Short',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Short Answer Title',
|
||||
stem: { format: 'moodle', text: 'Sample Stem' },
|
||||
choices: [
|
||||
{ text: { format: 'moodle' , text: 'Answer 1'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 },
|
||||
{ text: { format: 'moodle' , text: 'Answer 2'}, isCorrect: true, feedback: { format: 'plain' , text: 'Correct!'}, weight: 1 }
|
||||
],
|
||||
globalFeedback: { format: 'moodle', text: 'Sample Global Feedback' }
|
||||
};
|
||||
|
||||
const imageMock: TemplateOptions & ShortAnswerType = {
|
||||
type: 'Short',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Short Answer Title with Image',
|
||||
stem: { format: 'markdown', text: 'Sample Stem with Image' },
|
||||
choices: [
|
||||
{ text: { format: 'markdown', text: 'Answer 1' }, isCorrect: true, feedback: { format: 'plain', text: 'Correct!' }, weight: 1 },
|
||||
{ text: { format: 'markdown', text: 'Answer 2' }, isCorrect: true, feedback: { format: 'plain', text: 'Correct!' }, weight: 1 },
|
||||
{ text: { format: 'markdown', text: '<img src="https://via.placeholder.com/150" alt="Sample Image" />' }, isCorrect: true, feedback: { format: 'plain', text: 'Correct!' }, weight: 1 }
|
||||
],
|
||||
globalFeedback: { format: 'plain', text: 'Sample Global Feedback with Image' }
|
||||
};
|
||||
const moodleMock: TemplateOptions & ShortAnswerQuestion =
|
||||
parse(`
|
||||
::Sample Short Answer Title:: Sample Stem {=%1%Answer 1#Correct! =%1%Answer 2#Correct!####[moodle]Sample Global Feedback}
|
||||
`)[0] as ShortAnswerQuestion;
|
||||
|
||||
const imageMock: TemplateOptions & ShortAnswerQuestion =
|
||||
parse(`
|
||||
::Sample Short Answer Title with Image::[markdown]Sample Stem with Image  {=%1%Answer 1#Correct! =%1%Answer 2#Correct!####Sample Global Feedback with Image}
|
||||
`)[0] as ShortAnswerQuestion;
|
||||
|
||||
test('ShortAnswer snapshot test with plain text', () => {
|
||||
const { asFragment } = render(<ShortAnswer {...plainTextMock} />);
|
||||
|
|
@ -77,4 +49,4 @@ test('ShortAnswer snapshot test with moodle', () => {
|
|||
test('ShortAnswer snapshot test with image', () => {
|
||||
const { asFragment } = render(<ShortAnswer {...imageMock} />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,56 +2,27 @@ import React from 'react';
|
|||
import { render } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import TrueFalse from 'src/components/GiftTemplate/templates';
|
||||
import { TemplateOptions, TrueFalse as TrueFalseType } from 'src/components/GiftTemplate/templates/types';
|
||||
import { TemplateOptions } from 'src/components/GiftTemplate/templates/types';
|
||||
import { parse, ShortAnswerQuestion, TrueFalseQuestion } from 'gift-pegjs';
|
||||
|
||||
// Mock the nanoid function
|
||||
jest.mock('nanoid', () => ({
|
||||
nanoid: jest.fn(() => 'mocked-id')
|
||||
}));
|
||||
}));
|
||||
|
||||
const plainTextMock: TemplateOptions & TrueFalseType = {
|
||||
type: 'TF',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample True/False Title',
|
||||
stem: { format: 'plain', text: 'Sample Stem' },
|
||||
isTrue: true,
|
||||
trueFeedback: { format: 'plain', text: 'Correct!' },
|
||||
falseFeedback: { format: 'plain', text: 'Incorrect!' },
|
||||
globalFeedback: { format: 'plain', text: 'Sample Global Feedback' }
|
||||
};
|
||||
const plainTextMock: TemplateOptions & TrueFalseQuestion =
|
||||
parse(`::Sample True/False Title::Sample Stem {T#Correct!#Incorrect!####Sample Global Feedback}`)[0] as TrueFalseQuestion;
|
||||
|
||||
const katexMock: TemplateOptions & TrueFalseType = {
|
||||
type: 'TF',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample True/False Title',
|
||||
stem: { format: 'html', text: '$$\\frac{zzz}{yyy}$$' },
|
||||
isTrue: true,
|
||||
trueFeedback: { format: 'moodle', text: 'Correct!' },
|
||||
falseFeedback: { format: 'html', text: 'Incorrect!' },
|
||||
globalFeedback: { format: 'markdown', text: 'Sample Global Feedback' }
|
||||
};
|
||||
const katexMock: TemplateOptions & TrueFalseQuestion =
|
||||
parse(`::Sample True/False Title::$$\\frac\\{zzz\\}\\{yyy\\}$$ {T#Correct!#Incorrect!####Sample Global Feedback}`)[0] as TrueFalseQuestion;
|
||||
|
||||
const moodleMock: TemplateOptions & TrueFalseType = {
|
||||
type: 'TF',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample True/False Title',
|
||||
stem: { format: 'moodle', text: 'Sample Stem' },
|
||||
isTrue: true,
|
||||
trueFeedback: { format: 'moodle', text: 'Correct!' },
|
||||
falseFeedback: { format: 'moodle', text: 'Incorrect!' },
|
||||
globalFeedback: { format: 'moodle', text: 'Sample Global Feedback' }
|
||||
};
|
||||
const moodleMock: TemplateOptions & TrueFalseQuestion =
|
||||
parse(`::Sample True/False Title::[moodle]Sample Stem{TRUE#Correct!#Incorrect!####Sample Global Feedback}`)[0] as TrueFalseQuestion;
|
||||
|
||||
const imageMock: TemplateOptions & TrueFalseType = {
|
||||
type: 'TF',
|
||||
hasEmbeddedAnswers: false,
|
||||
title: 'Sample Short Answer Title with Image',
|
||||
stem: { format: 'plain', text: 'Sample Stem with Image' },
|
||||
trueFeedback: { format: 'moodle', text: 'Correct!' },
|
||||
isTrue: true,
|
||||
falseFeedback: { format: 'moodle', text: 'Incorrect!' },
|
||||
globalFeedback: { format: 'plain', text: '<img src="https://via.placeholder.com/150" alt="Sample Image" />' }
|
||||
};
|
||||
const imageMock: TemplateOptions & ShortAnswerQuestion =
|
||||
parse(`::Sample Short Answer Title with Image::
|
||||
[markdown]Sample Stem with Image 
|
||||
{=A =B =C####[html]<img src\\="https\\://via.placeholder.com/150" alt\\="Sample Image" />}`)[0] as ShortAnswerQuestion;
|
||||
|
||||
test('TrueFalse snapshot test with plain text', () => {
|
||||
const { asFragment } = render(<TrueFalse {...plainTextMock} />);
|
||||
|
|
@ -71,4 +42,4 @@ test('TrueFalse snapshot test with moodle', () => {
|
|||
test('TrueFalse snapshot test with image', () => {
|
||||
const { asFragment } = render(<TrueFalse {...imageMock} />);
|
||||
expect(asFragment()).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,24 +38,24 @@ exports[`MultipleChoice snapshot test 1`] = `
|
|||
">Choix multiple</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Sample Stem</p><span style="
|
||||
" class="present-question-stem">
|
||||
Sample Stem
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<span style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Choisir une réponse:</span>
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(134, 31%, 32%);
|
||||
background-color: hsl(134, 68%, 95%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
|
@ -73,25 +73,13 @@ exports[`MultipleChoice snapshot test 1`] = `
|
|||
width: 1em;
|
||||
color: hsl(120, 39%, 54%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Correct!</span>
|
||||
<span class="feedback-container">Correct!</span>
|
||||
</input>
|
||||
</div>
|
||||
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(35, 51%, 33%);
|
||||
background-color: hsl(36, 84%, 93%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
|
@ -109,9 +97,7 @@ exports[`MultipleChoice snapshot test 1`] = `
|
|||
width: 0.75em;
|
||||
color: hsl(2, 64%, 58%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">InCorrect!</span>
|
||||
<span class="feedback-container">InCorrect!</span>
|
||||
</input>
|
||||
</div>
|
||||
<div style="
|
||||
|
|
@ -167,24 +153,24 @@ exports[`MultipleChoice snapshot test with 2 images using markdown text format 1
|
|||
">Choix multiple</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
"><img src="https://via.placeholder.com/150" alt = "Sample Image"/></p><span style="
|
||||
" class="present-question-stem">
|
||||
<img alt="Sample Image" src="https://via.placeholder.com/150">
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<span style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Choisir une réponse:</span>
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(134, 31%, 32%);
|
||||
background-color: hsl(134, 68%, 95%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
|
@ -203,25 +189,13 @@ exports[`MultipleChoice snapshot test with 2 images using markdown text format 1
|
|||
width: 1em;
|
||||
color: hsl(120, 39%, 54%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Correct!</span>
|
||||
<span class="feedback-container">Correct!</span>
|
||||
</input>
|
||||
</div>
|
||||
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(35, 51%, 33%);
|
||||
background-color: hsl(36, 84%, 93%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
|
@ -240,32 +214,20 @@ exports[`MultipleChoice snapshot test with 2 images using markdown text format 1
|
|||
width: 0.75em;
|
||||
color: hsl(2, 64%, 58%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Incorrect!</span>
|
||||
<span class="feedback-container">Incorrect!</span>
|
||||
</input>
|
||||
</div>
|
||||
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(35, 51%, 33%);
|
||||
background-color: hsl(36, 84%, 93%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
||||
color: hsl(0, 0%, 0%);
|
||||
" for="idmocked-id">
|
||||
<img src="https://via.placeholder.com/150" alt="Sample Image" />
|
||||
<img alt="Sample Image" src="https://via.placeholder.com/150">
|
||||
</label>
|
||||
<svg style="
|
||||
vertical-align: text-bottom;
|
||||
|
|
@ -276,9 +238,7 @@ exports[`MultipleChoice snapshot test with 2 images using markdown text format 1
|
|||
width: 0.75em;
|
||||
color: hsl(2, 64%, 58%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Image Feedback</span>
|
||||
<span class="feedback-container">Image Feedback</span>
|
||||
</input>
|
||||
</div>
|
||||
<div style="
|
||||
|
|
@ -335,24 +295,24 @@ exports[`MultipleChoice snapshot test with Moodle text format 1`] = `
|
|||
">Choix multiple</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Sample Stem</p><span style="
|
||||
" class="present-question-stem">
|
||||
Sample Stem
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<span style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Choisir une réponse:</span>
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(134, 31%, 32%);
|
||||
background-color: hsl(134, 68%, 95%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
|
@ -370,25 +330,13 @@ exports[`MultipleChoice snapshot test with Moodle text format 1`] = `
|
|||
width: 1em;
|
||||
color: hsl(120, 39%, 54%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Correct!</span>
|
||||
<span class="feedback-container">Correct!</span>
|
||||
</input>
|
||||
</div>
|
||||
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(35, 51%, 33%);
|
||||
background-color: hsl(36, 84%, 93%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
|
@ -406,9 +354,7 @@ exports[`MultipleChoice snapshot test with Moodle text format 1`] = `
|
|||
width: 0.75em;
|
||||
color: hsl(2, 64%, 58%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">InCorrect!</span>
|
||||
<span class="feedback-container">InCorrect!</span>
|
||||
</input>
|
||||
</div>
|
||||
<div style="
|
||||
|
|
@ -464,24 +410,24 @@ exports[`MultipleChoice snapshot test with image 1`] = `
|
|||
">Choix multiple</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Sample Stem with Image</p><span style="
|
||||
" class="present-question-stem">
|
||||
Sample Stem with Image
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<span style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Choisir une réponse:</span>
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(134, 31%, 32%);
|
||||
background-color: hsl(134, 68%, 95%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
|
@ -499,25 +445,13 @@ exports[`MultipleChoice snapshot test with image 1`] = `
|
|||
width: 1em;
|
||||
color: hsl(120, 39%, 54%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Correct!</span>
|
||||
<span class="feedback-container">Correct!</span>
|
||||
</input>
|
||||
</div>
|
||||
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(35, 51%, 33%);
|
||||
background-color: hsl(36, 84%, 93%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
|
@ -535,32 +469,20 @@ exports[`MultipleChoice snapshot test with image 1`] = `
|
|||
width: 0.75em;
|
||||
color: hsl(2, 64%, 58%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Incorrect!</span>
|
||||
<span class="feedback-container">Incorrect!</span>
|
||||
</input>
|
||||
</div>
|
||||
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(35, 51%, 33%);
|
||||
background-color: hsl(36, 84%, 93%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
||||
color: hsl(0, 0%, 0%);
|
||||
" for="idmocked-id">
|
||||
<img src="https://via.placeholder.com/150" alt="Sample Image" />
|
||||
<img alt="Sample Image" src="https://via.placeholder.com/150">
|
||||
</label>
|
||||
<svg style="
|
||||
vertical-align: text-bottom;
|
||||
|
|
@ -571,9 +493,7 @@ exports[`MultipleChoice snapshot test with image 1`] = `
|
|||
width: 0.75em;
|
||||
color: hsl(2, 64%, 58%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Image Feedback</span>
|
||||
<span class="feedback-container">Image Feedback</span>
|
||||
</input>
|
||||
</div>
|
||||
<div style="
|
||||
|
|
@ -629,25 +549,25 @@ exports[`MultipleChoice snapshot test with image using markdown text format 1`]
|
|||
">Choix multiple</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Sample Stem with Image
|
||||
</p><span style="
|
||||
" class="present-question-stem">
|
||||
Sample Stem with Image
|
||||
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<span style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Choisir une réponse:</span>
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(134, 31%, 32%);
|
||||
background-color: hsl(134, 68%, 95%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
|
@ -666,25 +586,13 @@ exports[`MultipleChoice snapshot test with image using markdown text format 1`]
|
|||
width: 1em;
|
||||
color: hsl(120, 39%, 54%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Correct!</span>
|
||||
<span class="feedback-container">Correct!</span>
|
||||
</input>
|
||||
</div>
|
||||
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(35, 51%, 33%);
|
||||
background-color: hsl(36, 84%, 93%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
|
@ -703,32 +611,20 @@ exports[`MultipleChoice snapshot test with image using markdown text format 1`]
|
|||
width: 0.75em;
|
||||
color: hsl(2, 64%, 58%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Incorrect!</span>
|
||||
<span class="feedback-container">Incorrect!</span>
|
||||
</input>
|
||||
</div>
|
||||
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(35, 51%, 33%);
|
||||
background-color: hsl(36, 84%, 93%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
||||
color: hsl(0, 0%, 0%);
|
||||
" for="idmocked-id">
|
||||
<img src="https://via.placeholder.com/150" alt="Sample Image" />
|
||||
<img alt="Sample Image" src="https://via.placeholder.com/150">
|
||||
</label>
|
||||
<svg style="
|
||||
vertical-align: text-bottom;
|
||||
|
|
@ -739,9 +635,7 @@ exports[`MultipleChoice snapshot test with image using markdown text format 1`]
|
|||
width: 0.75em;
|
||||
color: hsl(2, 64%, 58%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Image Feedback</span>
|
||||
<span class="feedback-container">Image Feedback</span>
|
||||
</input>
|
||||
</div>
|
||||
<div style="
|
||||
|
|
@ -798,24 +692,24 @@ exports[`MultipleChoice snapshot test with katex 1`] = `
|
|||
">Choix multiple</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mi>z</mi><mi>z</mi><mi>z</mi></mrow><mrow><mi>y</mi><mi>y</mi><mi>y</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">\\frac{zzz}{yyy}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.988em;vertical-align:-0.8804em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.1076em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">yyy</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">zzz</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><span style="
|
||||
" class="present-question-stem">
|
||||
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mfrac><mrow><mi>z</mi><mi>z</mi><mi>z</mi></mrow><mrow><mi>y</mi><mi>y</mi><mi>y</mi></mrow></mfrac></mrow>\\frac{zzz}{yyy}</math></span><span aria-hidden="true" class="katex-html"><span class="base"><span style="height:1.988em;vertical-align:-0.8804em;" class="strut"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span style="height:1.1076em;" class="vlist"><span style="top:-2.314em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span style="margin-right:0.03588em;" class="mord mathnormal">yyy</span></span></span><span style="top:-3.23em;"><span style="height:3em;" class="pstrut"></span><span style="border-bottom-width:0.04em;" class="frac-line"></span></span><span style="top:-3.677em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span style="margin-right:0.04398em;" class="mord mathnormal">zzz</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span style="height:0.8804em;" class="vlist"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span>
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<span style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Choisir une réponse:</span>
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(134, 31%, 32%);
|
||||
background-color: hsl(134, 68%, 95%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
|
@ -833,25 +727,13 @@ exports[`MultipleChoice snapshot test with katex 1`] = `
|
|||
width: 1em;
|
||||
color: hsl(120, 39%, 54%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Correct!</span>
|
||||
<span class="feedback-container">Correct!</span>
|
||||
</input>
|
||||
</div>
|
||||
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(134, 31%, 32%);
|
||||
background-color: hsl(134, 68%, 95%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
|
@ -869,9 +751,7 @@ exports[`MultipleChoice snapshot test with katex 1`] = `
|
|||
width: 1em;
|
||||
color: hsl(120, 39%, 54%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Correct!</span>
|
||||
<span class="feedback-container">Correct!</span>
|
||||
</input>
|
||||
</div>
|
||||
<div style="
|
||||
|
|
@ -927,24 +807,24 @@ exports[`MultipleChoice snapshot test with katex, using html text format 1`] = `
|
|||
">Choix multiple</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mi>z</mi><mi>z</mi><mi>z</mi></mrow><mrow><mi>y</mi><mi>y</mi><mi>y</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">\\frac{zzz}{yyy}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.988em;vertical-align:-0.8804em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.1076em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">yyy</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">zzz</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><span style="
|
||||
" class="present-question-stem">
|
||||
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mfrac><mrow><mi>z</mi><mi>z</mi><mi>z</mi></mrow><mrow><mi>y</mi><mi>y</mi><mi>y</mi></mrow></mfrac></mrow>\\frac{zzz}{yyy}</math></span><span aria-hidden="true" class="katex-html"><span class="base"><span style="height:1.988em;vertical-align:-0.8804em;" class="strut"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span style="height:1.1076em;" class="vlist"><span style="top:-2.314em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span style="margin-right:0.03588em;" class="mord mathnormal">yyy</span></span></span><span style="top:-3.23em;"><span style="height:3em;" class="pstrut"></span><span style="border-bottom-width:0.04em;" class="frac-line"></span></span><span style="top:-3.677em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span style="margin-right:0.04398em;" class="mord mathnormal">zzz</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span style="height:0.8804em;" class="vlist"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span>
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<span style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Choisir une réponse:</span>
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(134, 31%, 32%);
|
||||
background-color: hsl(134, 68%, 95%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
|
@ -962,25 +842,13 @@ exports[`MultipleChoice snapshot test with katex, using html text format 1`] = `
|
|||
width: 1em;
|
||||
color: hsl(120, 39%, 54%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Correct!</span>
|
||||
<span class="feedback-container">Correct!</span>
|
||||
</input>
|
||||
</div>
|
||||
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
<span style="
|
||||
box-shadow: 0px 1px 1px hsl(0, 0%, 74%);
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
|
||||
color: hsl(35, 51%, 33%);
|
||||
background-color: hsl(36, 84%, 93%);
|
||||
">1%</span>
|
||||
<span class="answer-weight-container answer-positive-weight">1%</span>
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
|
@ -998,9 +866,7 @@ exports[`MultipleChoice snapshot test with katex, using html text format 1`] = `
|
|||
width: 0.75em;
|
||||
color: hsl(2, 64%, 58%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">InCorrect!</span>
|
||||
<span class="feedback-container">InCorrect!</span>
|
||||
</input>
|
||||
</div>
|
||||
<div style="
|
||||
|
|
|
|||
|
|
@ -38,13 +38,21 @@ exports[`Numerical snapshot test with html 1`] = `
|
|||
">Numérique</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mi>z</mi><mi>z</mi><mi>z</mi></mrow><mrow><mi>y</mi><mi>y</mi><mi>y</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">\\frac{zzz}{yyy}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.988em;vertical-align:-0.8804em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.1076em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">yyy</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">zzz</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p>
|
||||
<div>
|
||||
<span style="
|
||||
" class="present-question-stem">
|
||||
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mfrac><mrow><mi>z</mi><mi>z</mi><mi>z</mi></mrow><mrow><mi>y</mi><mi>y</mi><mi>y</mi></mrow></mfrac></mrow>\\frac{zzz}{yyy}</math></span><span aria-hidden="true" class="katex-html"><span class="base"><span style="height:1.988em;vertical-align:-0.8804em;" class="strut"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span style="height:1.1076em;" class="vlist"><span style="top:-2.314em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span style="margin-right:0.03588em;" class="mord mathnormal">yyy</span></span></span><span style="top:-3.23em;"><span style="height:3em;" class="pstrut"></span><span style="border-bottom-width:0.04em;" class="frac-line"></span></span><span style="top:-3.677em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span style="margin-right:0.04398em;" class="mord mathnormal">zzz</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span style="height:0.8804em;" class="vlist"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span>
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<div><p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Réponse: </span><input class="gift-input" type="text" style="
|
||||
">Réponse: </p><input class="gift-input" type="text" style="
|
||||
display: inline-block;
|
||||
padding: 0.375rem 0.75rem;
|
||||
line-height: 1.5;
|
||||
|
|
@ -58,9 +66,23 @@ exports[`Numerical snapshot test with html 1`] = `
|
|||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
width: 90%;
|
||||
" placeholder="42, 43">
|
||||
</div>
|
||||
<div style="
|
||||
;width: 100%" placeholder="42"><div class="feedback-container">Correct</div><p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Réponse: </p><input class="gift-input" type="text" style="
|
||||
display: inline-block;
|
||||
padding: 0.375rem 0.75rem;
|
||||
line-height: 1.5;
|
||||
color: hsl(0, 0%, 16%);
|
||||
background-color: hsl(0, 0%, 100%);
|
||||
border: 1px solid hsl(0, 0%, 81%);
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
width: 90%;
|
||||
;width: 100%" placeholder="43"><div class="feedback-container">Incorrect!</div></div><div style="
|
||||
position: relative;
|
||||
margin-top: 1rem;
|
||||
padding: 0 1rem;
|
||||
|
|
@ -113,13 +135,22 @@ exports[`Numerical snapshot test with image 1`] = `
|
|||
">Numérique</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Sample Stem with Image</p>
|
||||
<div>
|
||||
<span style="
|
||||
" class="present-question-stem">
|
||||
Sample Stem with Image <img alt="" src="https://example.com/cat.jpg">
|
||||
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<div><p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Réponse: </span><input class="gift-input" type="text" style="
|
||||
">Réponse: </p><input class="gift-input" type="text" style="
|
||||
display: inline-block;
|
||||
padding: 0.375rem 0.75rem;
|
||||
line-height: 1.5;
|
||||
|
|
@ -133,9 +164,42 @@ exports[`Numerical snapshot test with image 1`] = `
|
|||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
width: 90%;
|
||||
" placeholder="42, 43, 44">
|
||||
</div>
|
||||
<div style="
|
||||
;width: 100%" placeholder="42"><div class="feedback-container">Correct!
|
||||
</div><p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Réponse: </p><input class="gift-input" type="text" style="
|
||||
display: inline-block;
|
||||
padding: 0.375rem 0.75rem;
|
||||
line-height: 1.5;
|
||||
color: hsl(0, 0%, 16%);
|
||||
background-color: hsl(0, 0%, 100%);
|
||||
border: 1px solid hsl(0, 0%, 81%);
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
width: 90%;
|
||||
;width: 100%" placeholder="43"><div class="feedback-container">Incorrect!
|
||||
</div><p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Réponse: </p><input class="gift-input" type="text" style="
|
||||
display: inline-block;
|
||||
padding: 0.375rem 0.75rem;
|
||||
line-height: 1.5;
|
||||
color: hsl(0, 0%, 16%);
|
||||
background-color: hsl(0, 0%, 100%);
|
||||
border: 1px solid hsl(0, 0%, 81%);
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
width: 90%;
|
||||
;width: 100%" placeholder="44"><div class="feedback-container">Also Incorrect! <img alt="" src="https://example.com/cat.jpg">
|
||||
</div></div><div style="
|
||||
position: relative;
|
||||
margin-top: 1rem;
|
||||
padding: 0 1rem;
|
||||
|
|
@ -145,7 +209,8 @@ exports[`Numerical snapshot test with image 1`] = `
|
|||
border-radius: 6px;
|
||||
box-shadow: 0px 2px 5px hsl(0, 0%, 74%);
|
||||
">
|
||||
<p>Sample Global Feedback with Image</p>
|
||||
<p>Sample Global Feedback with Image
|
||||
</p>
|
||||
</div></section>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
|
@ -188,13 +253,21 @@ exports[`Numerical snapshot test with moodle 1`] = `
|
|||
">Numérique</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Sample Stem</p>
|
||||
<div>
|
||||
<span style="
|
||||
" class="present-question-stem">
|
||||
Sample Stem
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<div><p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Réponse: </span><input class="gift-input" type="text" style="
|
||||
">Réponse: </p><input class="gift-input" type="text" style="
|
||||
display: inline-block;
|
||||
padding: 0.375rem 0.75rem;
|
||||
line-height: 1.5;
|
||||
|
|
@ -208,9 +281,23 @@ exports[`Numerical snapshot test with moodle 1`] = `
|
|||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
width: 90%;
|
||||
" placeholder="42, 43">
|
||||
</div>
|
||||
<div style="
|
||||
;width: 100%" placeholder="42"><div class="feedback-container">Correct!</div><p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Réponse: </p><input class="gift-input" type="text" style="
|
||||
display: inline-block;
|
||||
padding: 0.375rem 0.75rem;
|
||||
line-height: 1.5;
|
||||
color: hsl(0, 0%, 16%);
|
||||
background-color: hsl(0, 0%, 100%);
|
||||
border: 1px solid hsl(0, 0%, 81%);
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
width: 90%;
|
||||
;width: 100%" placeholder="43"><div class="feedback-container">Incorrect!</div></div><div style="
|
||||
position: relative;
|
||||
margin-top: 1rem;
|
||||
padding: 0 1rem;
|
||||
|
|
@ -263,13 +350,21 @@ exports[`Numerical snapshot test with plain text 1`] = `
|
|||
">Numérique</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Sample Stem</p>
|
||||
<div>
|
||||
<span style="
|
||||
" class="present-question-stem">
|
||||
Sample Stem
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<div><p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Réponse: </span><input class="gift-input" type="text" style="
|
||||
">Réponse: </p><input class="gift-input" type="text" style="
|
||||
display: inline-block;
|
||||
padding: 0.375rem 0.75rem;
|
||||
line-height: 1.5;
|
||||
|
|
@ -283,9 +378,23 @@ exports[`Numerical snapshot test with plain text 1`] = `
|
|||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
width: 90%;
|
||||
" placeholder="42, 43">
|
||||
</div>
|
||||
<div style="
|
||||
;width: 100%" placeholder="42"><div class="feedback-container">Correct!</div><p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Réponse: </p><input class="gift-input" type="text" style="
|
||||
display: inline-block;
|
||||
padding: 0.375rem 0.75rem;
|
||||
line-height: 1.5;
|
||||
color: hsl(0, 0%, 16%);
|
||||
background-color: hsl(0, 0%, 100%);
|
||||
border: 1px solid hsl(0, 0%, 81%);
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
width: 90%;
|
||||
;width: 100%" placeholder="43"><div class="feedback-container">Incorrect!</div></div><div style="
|
||||
position: relative;
|
||||
margin-top: 1rem;
|
||||
padding: 0 1rem;
|
||||
|
|
|
|||
|
|
@ -38,10 +38,20 @@ exports[`ShortAnswer snapshot test with image 1`] = `
|
|||
">Réponse courte</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Sample Stem with Image
|
||||
</p>
|
||||
" class="present-question-stem">
|
||||
Sample Stem with Image <img alt="" src="https://example.com/cat.jpg">
|
||||
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
|
|
@ -59,9 +69,7 @@ exports[`ShortAnswer snapshot test with image 1`] = `
|
|||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
width: 90%;
|
||||
" placeholder="Answer 1
|
||||
, Answer 2
|
||||
, <img src="https://via.placeholder.com/150" alt="Sample Image" />">
|
||||
" placeholder="Answer 1, Answer 2">
|
||||
</div>
|
||||
<div style="
|
||||
position: relative;
|
||||
|
|
@ -73,7 +81,8 @@ exports[`ShortAnswer snapshot test with image 1`] = `
|
|||
border-radius: 6px;
|
||||
box-shadow: 0px 2px 5px hsl(0, 0%, 74%);
|
||||
">
|
||||
<p>Sample Global Feedback with Image</p>
|
||||
<p>Sample Global Feedback with Image
|
||||
</p>
|
||||
</div></section>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
|
@ -116,9 +125,19 @@ exports[`ShortAnswer snapshot test with katex 1`] = `
|
|||
">Réponse courte</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mi>z</mi><mi>z</mi><mi>z</mi></mrow><mrow><mi>y</mi><mi>y</mi><mi>y</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">\\frac{zzz}{yyy}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.988em;vertical-align:-0.8804em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.1076em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">yyy</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">zzz</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p>
|
||||
" class="present-question-stem">
|
||||
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mfrac><mrow><mi>z</mi><mi>z</mi><mi>z</mi></mrow><mrow><mi>y</mi><mi>y</mi><mi>y</mi></mrow></mfrac></mrow>\\frac{zzz}{yyy}</math></span><span aria-hidden="true" class="katex-html"><span class="base"><span style="height:1.988em;vertical-align:-0.8804em;" class="strut"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span style="height:1.1076em;" class="vlist"><span style="top:-2.314em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span style="margin-right:0.03588em;" class="mord mathnormal">yyy</span></span></span><span style="top:-3.23em;"><span style="height:3em;" class="pstrut"></span><span style="border-bottom-width:0.04em;" class="frac-line"></span></span><span style="top:-3.677em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span style="margin-right:0.04398em;" class="mord mathnormal">zzz</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span style="height:0.8804em;" class="vlist"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span>
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
|
|
@ -191,9 +210,19 @@ exports[`ShortAnswer snapshot test with moodle 1`] = `
|
|||
">Réponse courte</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Sample Stem</p>
|
||||
" class="present-question-stem">
|
||||
Sample Stem
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
|
|
@ -266,9 +295,19 @@ exports[`ShortAnswer snapshot test with plain text 1`] = `
|
|||
">Réponse courte</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Sample Stem</p>
|
||||
" class="present-question-stem">
|
||||
Sample Stem
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
|
|
|
|||
|
|
@ -35,66 +35,43 @@ exports[`TrueFalse snapshot test with image 1`] = `
|
|||
border-radius: 4px;
|
||||
background-color: hsl(0, 0%, 100%);
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Vrai/Faux</span>
|
||||
">Réponse courte</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Sample Stem with Image</p><span style="
|
||||
" class="present-question-stem">
|
||||
Sample Stem with Image <img alt="" src="https://example.com/cat.gif">
|
||||
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Choisir une réponse:</span>
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
||||
color: hsl(0, 0%, 0%);
|
||||
" for="idmocked-id">
|
||||
Vrai
|
||||
</label>
|
||||
<svg style="
|
||||
vertical-align: text-bottom;
|
||||
">Réponse: </span><input class="gift-input" type="text" style="
|
||||
display: inline-block;
|
||||
margin-left: 0.1rem;
|
||||
margin-right: 0.2rem;
|
||||
|
||||
width: 1em;
|
||||
color: hsl(120, 39%, 54%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Correct!</span>
|
||||
</input>
|
||||
padding: 0.375rem 0.75rem;
|
||||
line-height: 1.5;
|
||||
color: hsl(0, 0%, 16%);
|
||||
background-color: hsl(0, 0%, 100%);
|
||||
border: 1px solid hsl(0, 0%, 81%);
|
||||
border-radius: 0.25rem;
|
||||
box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
|
||||
margin: 0;
|
||||
font-family: inherit;
|
||||
font-size: inherit;
|
||||
line-height: inherit;
|
||||
width: 90%;
|
||||
" placeholder="A, B, C">
|
||||
</div>
|
||||
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="radio" id="idmocked-id" name="idmocked-id">
|
||||
|
||||
<label style="
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
|
||||
color: hsl(0, 0%, 0%);
|
||||
" for="idmocked-id">
|
||||
Faux
|
||||
</label>
|
||||
<svg style="
|
||||
vertical-align: text-bottom;
|
||||
display: inline-block;
|
||||
margin-left: 0.1rem;
|
||||
margin-right: 0.2rem;
|
||||
|
||||
width: 0.75em;
|
||||
color: hsl(2, 64%, 58%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Incorrect!</span>
|
||||
</input>
|
||||
</div>
|
||||
<div style="
|
||||
<div style="
|
||||
position: relative;
|
||||
margin-top: 1rem;
|
||||
padding: 0 1rem;
|
||||
|
|
@ -104,7 +81,7 @@ exports[`TrueFalse snapshot test with image 1`] = `
|
|||
border-radius: 6px;
|
||||
box-shadow: 0px 2px 5px hsl(0, 0%, 74%);
|
||||
">
|
||||
<p><img src="https://via.placeholder.com/150" alt="Sample Image" /></p>
|
||||
<p><img alt="Sample Image" src="https://via.placeholder.com/150"></p>
|
||||
</div></section>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
|
@ -147,9 +124,19 @@ exports[`TrueFalse snapshot test with katex 1`] = `
|
|||
">Vrai/Faux</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
"><span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mfrac><mrow><mi>z</mi><mi>z</mi><mi>z</mi></mrow><mrow><mi>y</mi><mi>y</mi><mi>y</mi></mrow></mfrac></mrow><annotation encoding="application/x-tex">\\frac{zzz}{yyy}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="base"><span class="strut" style="height:1.988em;vertical-align:-0.8804em;"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:1.1076em;"><span style="top:-2.314em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.03588em;">yyy</span></span></span><span style="top:-3.23em;"><span class="pstrut" style="height:3em;"></span><span class="frac-line" style="border-bottom-width:0.04em;"></span></span><span style="top:-3.677em;"><span class="pstrut" style="height:3em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.04398em;">zzz</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span class="vlist" style="height:0.8804em;"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span></p><span style="
|
||||
" class="present-question-stem">
|
||||
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math display="block" xmlns="http://www.w3.org/1998/Math/MathML"><mrow><mfrac><mrow><mi>z</mi><mi>z</mi><mi>z</mi></mrow><mrow><mi>y</mi><mi>y</mi><mi>y</mi></mrow></mfrac></mrow>\\frac{zzz}{yyy}</math></span><span aria-hidden="true" class="katex-html"><span class="base"><span style="height:1.988em;vertical-align:-0.8804em;" class="strut"></span><span class="mord"><span class="mopen nulldelimiter"></span><span class="mfrac"><span class="vlist-t vlist-t2"><span class="vlist-r"><span style="height:1.1076em;" class="vlist"><span style="top:-2.314em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span style="margin-right:0.03588em;" class="mord mathnormal">yyy</span></span></span><span style="top:-3.23em;"><span style="height:3em;" class="pstrut"></span><span style="border-bottom-width:0.04em;" class="frac-line"></span></span><span style="top:-3.677em;"><span style="height:3em;" class="pstrut"></span><span class="mord"><span style="margin-right:0.04398em;" class="mord mathnormal">zzz</span></span></span></span><span class="vlist-s"></span></span><span class="vlist-r"><span style="height:0.8804em;" class="vlist"><span></span></span></span></span></span><span class="mclose nulldelimiter"></span></span></span></span></span></span>
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<span style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Choisir une réponse:</span>
|
||||
<div class='multiple-choice-answers-container'>
|
||||
|
|
@ -172,9 +159,7 @@ exports[`TrueFalse snapshot test with katex 1`] = `
|
|||
width: 1em;
|
||||
color: hsl(120, 39%, 54%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Correct!</span>
|
||||
<span class="feedback-container">Correct!</span>
|
||||
</input>
|
||||
</div>
|
||||
|
||||
|
|
@ -198,9 +183,7 @@ exports[`TrueFalse snapshot test with katex 1`] = `
|
|||
width: 0.75em;
|
||||
color: hsl(2, 64%, 58%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Incorrect!</span>
|
||||
<span class="feedback-container">Incorrect!</span>
|
||||
</input>
|
||||
</div>
|
||||
<div style="
|
||||
|
|
@ -213,8 +196,7 @@ exports[`TrueFalse snapshot test with katex 1`] = `
|
|||
border-radius: 6px;
|
||||
box-shadow: 0px 2px 5px hsl(0, 0%, 74%);
|
||||
">
|
||||
<p>Sample Global Feedback
|
||||
</p>
|
||||
<p>Sample Global Feedback</p>
|
||||
</div></section>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
|
@ -257,9 +239,19 @@ exports[`TrueFalse snapshot test with moodle 1`] = `
|
|||
">Vrai/Faux</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Sample Stem</p><span style="
|
||||
" class="present-question-stem">
|
||||
Sample Stem
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<span style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Choisir une réponse:</span>
|
||||
<div class='multiple-choice-answers-container'>
|
||||
|
|
@ -282,9 +274,7 @@ exports[`TrueFalse snapshot test with moodle 1`] = `
|
|||
width: 1em;
|
||||
color: hsl(120, 39%, 54%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Correct!</span>
|
||||
<span class="feedback-container">Correct!</span>
|
||||
</input>
|
||||
</div>
|
||||
|
||||
|
|
@ -308,9 +298,7 @@ exports[`TrueFalse snapshot test with moodle 1`] = `
|
|||
width: 0.75em;
|
||||
color: hsl(2, 64%, 58%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Incorrect!</span>
|
||||
<span class="feedback-container">Incorrect!</span>
|
||||
</input>
|
||||
</div>
|
||||
<div style="
|
||||
|
|
@ -366,9 +354,19 @@ exports[`TrueFalse snapshot test with plain text 1`] = `
|
|||
">Vrai/Faux</span>
|
||||
</span>
|
||||
</div>
|
||||
<p style="
|
||||
|
||||
<div style="
|
||||
display: flex;
|
||||
">
|
||||
<span>
|
||||
<p style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Sample Stem</p><span style="
|
||||
" class="present-question-stem">
|
||||
Sample Stem
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
<span style="
|
||||
color: hsl(0, 0%, 0%);
|
||||
">Choisir une réponse:</span>
|
||||
<div class='multiple-choice-answers-container'>
|
||||
|
|
@ -391,9 +389,7 @@ exports[`TrueFalse snapshot test with plain text 1`] = `
|
|||
width: 1em;
|
||||
color: hsl(120, 39%, 54%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M173.898 439.404l-166.4-166.4c-9.997-9.997-9.997-26.206 0-36.204l36.203-36.204c9.997-9.998 26.207-9.998 36.204 0L192 312.69 432.095 72.596c9.997-9.997 26.207-9.997 36.204 0l36.203 36.204c9.997 9.997 9.997 26.206 0 36.204l-294.4 294.401c-9.998 9.997-26.207 9.997-36.204-.001z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Correct!</span>
|
||||
<span class="feedback-container">Correct!</span>
|
||||
</input>
|
||||
</div>
|
||||
|
||||
|
|
@ -417,9 +413,7 @@ exports[`TrueFalse snapshot test with plain text 1`] = `
|
|||
width: 0.75em;
|
||||
color: hsl(2, 64%, 58%);
|
||||
" role="img" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 352 512"><path fill="currentColor" d="M242.72 256l100.07-100.07c12.28-12.28 12.28-32.19 0-44.48l-22.24-22.24c-12.28-12.28-32.19-12.28-44.48 0L176 189.28 75.93 89.21c-12.28-12.28-32.19-12.28-44.48 0L9.21 111.45c-12.28 12.28-12.28 32.19 0 44.48L109.28 256 9.21 356.07c-12.28 12.28-12.28 32.19 0 44.48l22.24 22.24c12.28 12.28 32.2 12.28 44.48 0L176 322.72l100.07 100.07c12.28 12.28 32.2 12.28 44.48 0l22.24-22.24c12.28-12.28 12.28-32.19 0-44.48L242.72 256z"></path></svg>
|
||||
<span style="
|
||||
color: hsl(180, 15%, 41%);
|
||||
">Incorrect!</span>
|
||||
<span class="feedback-container">Incorrect!</span>
|
||||
</input>
|
||||
</div>
|
||||
<div style="
|
||||
|
|
|
|||
|
|
@ -1,35 +1,43 @@
|
|||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import MultipleChoiceQuestion from 'src/components/Questions/MultipleChoiceQuestion/MultipleChoiceQuestion';
|
||||
import { act } from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { MultipleChoiceQuestion, parse } from 'gift-pegjs';
|
||||
import MultipleChoiceQuestionDisplay from 'src/components/QuestionsDisplay/MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay';
|
||||
|
||||
const questionStem = 'Question stem';
|
||||
const sampleFeedback = 'Feedback';
|
||||
const questions = parse(
|
||||
`::Sample Question 1:: Question stem
|
||||
{
|
||||
=Choice 1
|
||||
~Choice 2
|
||||
}`) as MultipleChoiceQuestion[];
|
||||
|
||||
describe('MultipleChoiceQuestion', () => {
|
||||
const question = questions[0];
|
||||
|
||||
describe('MultipleChoiceQuestionDisplay', () => {
|
||||
const mockHandleOnSubmitAnswer = jest.fn();
|
||||
const choices = [
|
||||
{ feedback: null, isCorrect: true, text: { format: 'plain', text: 'Choice 1' } },
|
||||
{ feedback: null, isCorrect: false, text: { format: 'plain', text: 'Choice 2' } }
|
||||
];
|
||||
const sampleProps = {
|
||||
question: question,
|
||||
handleOnSubmitAnswer: mockHandleOnSubmitAnswer,
|
||||
showAnswer: false
|
||||
};
|
||||
|
||||
const choices = question.choices;
|
||||
|
||||
beforeEach(() => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<MultipleChoiceQuestion
|
||||
globalFeedback={sampleFeedback}
|
||||
choices={choices}
|
||||
handleOnSubmitAnswer={mockHandleOnSubmitAnswer}
|
||||
questionStem={{ text: questionStem, format: 'plain' }} />
|
||||
<MultipleChoiceQuestionDisplay
|
||||
{...sampleProps}
|
||||
/>
|
||||
</MemoryRouter>);
|
||||
});
|
||||
|
||||
test('renders the question and choices', () => {
|
||||
expect(screen.getByText(questionStem)).toBeInTheDocument();
|
||||
expect(screen.getByText(question.formattedStem.text)).toBeInTheDocument();
|
||||
choices.forEach((choice) => {
|
||||
expect(screen.getByText(choice.text.text)).toBeInTheDocument();
|
||||
expect(screen.getByText(choice.formattedText.text)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -1,30 +1,48 @@
|
|||
// NumericalQuestion.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import NumericalQuestion from 'src/components/Questions/NumericalQuestion/NumericalQuestion';
|
||||
import { NumericalQuestion, parse, ParsedGIFTQuestion } from 'gift-pegjs';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import NumericalQuestionDisplay from 'src/components/QuestionsDisplay/NumericalQuestionDisplay/NumericalQuestionDisplay';
|
||||
|
||||
const questions = parse(
|
||||
`
|
||||
::Sample Question 1:: Question stem
|
||||
{
|
||||
#5..10
|
||||
}`
|
||||
) as ParsedGIFTQuestion[];
|
||||
|
||||
const question = questions[0] as NumericalQuestion;
|
||||
|
||||
describe('NumericalQuestion parse', () => {
|
||||
const q = questions[0];
|
||||
|
||||
it('The question is Numerical', () => {
|
||||
expect(q.type).toBe('Numerical');
|
||||
});
|
||||
});
|
||||
|
||||
describe('NumericalQuestion Component', () => {
|
||||
const mockHandleSubmitAnswer = jest.fn();
|
||||
const sampleStem = 'Sample question stem';
|
||||
const mockHandleOnSubmitAnswer = jest.fn();
|
||||
|
||||
const sampleProps = {
|
||||
questionTitle: 'Sample Question',
|
||||
correctAnswers: {
|
||||
numberHigh: 10,
|
||||
numberLow: 5,
|
||||
type: 'high-low'
|
||||
},
|
||||
handleOnSubmitAnswer: mockHandleSubmitAnswer,
|
||||
question: question,
|
||||
handleOnSubmitAnswer: mockHandleOnSubmitAnswer,
|
||||
showAnswer: false
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
render(<NumericalQuestion questionContent={{text: sampleStem, format: 'plain'}} {...sampleProps} />);
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<NumericalQuestionDisplay
|
||||
{...sampleProps}
|
||||
/>
|
||||
</MemoryRouter>);
|
||||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
expect(screen.getByText(sampleStem)).toBeInTheDocument();
|
||||
expect(screen.getByText(question.formattedStem.text)).toBeInTheDocument();
|
||||
expect(screen.getByTestId('number-input')).toBeInTheDocument();
|
||||
expect(screen.getByText('Répondre')).toBeInTheDocument();
|
||||
});
|
||||
|
|
@ -48,7 +66,7 @@ describe('NumericalQuestion Component', () => {
|
|||
|
||||
fireEvent.click(submitButton);
|
||||
|
||||
expect(mockHandleSubmitAnswer).not.toHaveBeenCalled();
|
||||
expect(mockHandleOnSubmitAnswer).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('submits answer correctly', () => {
|
||||
|
|
@ -59,6 +77,6 @@ describe('NumericalQuestion Component', () => {
|
|||
|
||||
fireEvent.click(submitButton);
|
||||
|
||||
expect(mockHandleSubmitAnswer).toHaveBeenCalledWith(7);
|
||||
expect(mockHandleOnSubmitAnswer).toHaveBeenCalledWith(7);
|
||||
});
|
||||
});
|
||||
|
|
@ -1,72 +1,51 @@
|
|||
// Question.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { render, screen, fireEvent, within } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import Questions from 'src/components/Questions/Question';
|
||||
import { GIFTQuestion } from 'gift-pegjs';
|
||||
import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay';
|
||||
import { parse, Question } from 'gift-pegjs';
|
||||
|
||||
//
|
||||
describe('Questions Component', () => {
|
||||
const mockHandleSubmitAnswer = jest.fn();
|
||||
|
||||
const sampleTrueFalseQuestion: GIFTQuestion = {
|
||||
type: 'TF',
|
||||
stem: { format: 'plain', text: 'Sample True/False Question' },
|
||||
isTrue: true,
|
||||
falseFeedback: null,
|
||||
trueFeedback: null,
|
||||
title: 'True/False Question',
|
||||
hasEmbeddedAnswers: false,
|
||||
globalFeedback: null,
|
||||
const sampleTrueFalseQuestion =
|
||||
parse('::Sample True/False Question:: Sample True/False Question {T}')[0];
|
||||
|
||||
const sampleMultipleChoiceQuestion =
|
||||
parse('::Sample Multiple Choice Question:: Sample Multiple Choice Question {=Choice 1 ~Choice 2}')[0];
|
||||
|
||||
const sampleNumericalQuestion =
|
||||
parse('::Sample Numerical Question:: Sample Numerical Question {#5..10}')[0];
|
||||
|
||||
const sampleShortAnswerQuestion =
|
||||
parse('::Sample Short Answer Question:: Sample Short Answer Question {=Correct Answer =Another Answer}')[0];
|
||||
|
||||
const sampleProps = {
|
||||
handleOnSubmitAnswer: mockHandleSubmitAnswer,
|
||||
showAnswer: false
|
||||
};
|
||||
|
||||
const sampleMultipleChoiceQuestion: GIFTQuestion = {
|
||||
type: 'MC',
|
||||
stem: { format: 'plain', text: 'Sample Multiple Choice Question' },
|
||||
title: 'Multiple Choice Question',
|
||||
hasEmbeddedAnswers: false,
|
||||
globalFeedback: null,
|
||||
choices: [
|
||||
{ feedback: null, isCorrect: true, text: { format: 'plain', text: 'Choice 1' }, weight: 1 },
|
||||
{ feedback: null, isCorrect: false, text: { format: 'plain', text: 'Choice 2' }, weight: 0 },
|
||||
],
|
||||
const renderComponent = (question: Question) => {
|
||||
render(<QuestionDisplay question={question} {...sampleProps} />);
|
||||
};
|
||||
|
||||
const sampleNumericalQuestion: GIFTQuestion = {
|
||||
type: 'Numerical',
|
||||
stem: { format: 'plain', text: 'Sample Numerical Question' },
|
||||
title: 'Numerical Question',
|
||||
hasEmbeddedAnswers: false,
|
||||
globalFeedback: null,
|
||||
choices: { numberHigh: 10, numberLow: 5, type: 'high-low' },
|
||||
};
|
||||
describe('question type parsing', () => {
|
||||
it('parses true/false question type correctly', () => {
|
||||
expect(sampleTrueFalseQuestion.type).toBe('TF');
|
||||
});
|
||||
|
||||
const sampleShortAnswerQuestion: GIFTQuestion = {
|
||||
type: 'Short',
|
||||
stem: { format: 'plain', text: 'Sample short answer question' },
|
||||
title: 'Short Answer Question Title',
|
||||
hasEmbeddedAnswers: false,
|
||||
globalFeedback: null,
|
||||
choices: [
|
||||
{
|
||||
feedback: { format: 'html', text: 'Correct answer feedback' },
|
||||
isCorrect: true,
|
||||
text: { format: 'html', text: 'Correct Answer' },
|
||||
weight: 1,
|
||||
},
|
||||
{
|
||||
feedback: { format: 'html', text: 'Incorrect answer feedback' },
|
||||
isCorrect: false,
|
||||
text: { format: 'html', text: 'Incorrect Answer' },
|
||||
weight: 0,
|
||||
},
|
||||
],
|
||||
};
|
||||
it('parses multiple choice question type correctly', () => {
|
||||
expect(sampleMultipleChoiceQuestion.type).toBe('MC');
|
||||
});
|
||||
|
||||
const renderComponent = (question: GIFTQuestion) => {
|
||||
render(<Questions question={question} handleOnSubmitAnswer={mockHandleSubmitAnswer} />);
|
||||
};
|
||||
it('parses numerical question type correctly', () => {
|
||||
expect(sampleNumericalQuestion.type).toBe('Numerical');
|
||||
});
|
||||
|
||||
it('parses short answer question type correctly', () => {
|
||||
expect(sampleShortAnswerQuestion.type).toBe('Short');
|
||||
});
|
||||
});
|
||||
it('renders correctly for True/False question', () => {
|
||||
renderComponent(sampleTrueFalseQuestion);
|
||||
|
||||
|
|
@ -120,15 +99,19 @@ describe('Questions Component', () => {
|
|||
it('renders correctly for Short Answer question', () => {
|
||||
renderComponent(sampleShortAnswerQuestion);
|
||||
|
||||
expect(screen.getByText('Sample short answer question')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('text-input')).toBeInTheDocument();
|
||||
expect(screen.getByText('Sample Short Answer Question')).toBeInTheDocument();
|
||||
const container = screen.getByLabelText('short-answer-input');
|
||||
const inputElement = within(container).getByRole('textbox') as HTMLInputElement;
|
||||
expect(inputElement).toBeInTheDocument();
|
||||
expect(screen.getByText('Répondre')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles input and submission for Short Answer question', () => {
|
||||
renderComponent(sampleShortAnswerQuestion);
|
||||
|
||||
const inputElement = screen.getByTestId('text-input') as HTMLInputElement;
|
||||
const container = screen.getByLabelText('short-answer-input');
|
||||
const inputElement = within(container).getByRole('textbox') as HTMLInputElement;
|
||||
|
||||
fireEvent.change(inputElement, { target: { value: 'User Input' } });
|
||||
|
||||
const submitButton = screen.getByText('Répondre');
|
||||
|
|
@ -1,54 +1,34 @@
|
|||
// ShortAnswerQuestion.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, fireEvent } from '@testing-library/react';
|
||||
import { render, screen, fireEvent, within } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import ShortAnswerQuestion from 'src/components/Questions/ShortAnswerQuestion/ShortAnswerQuestion';
|
||||
import { parse, ShortAnswerQuestion } from 'gift-pegjs';
|
||||
import ShortAnswerQuestionDisplay from 'src/components/QuestionsDisplay/ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay';
|
||||
|
||||
describe('ShortAnswerQuestion Component', () => {
|
||||
const mockHandleSubmitAnswer = jest.fn();
|
||||
const sampleStem = 'Sample question stem';
|
||||
const question =
|
||||
parse('::Sample Short Answer Question:: Sample Short Answer Question {=Correct Answer ~Incorrect Answer}')[0] as ShortAnswerQuestion;
|
||||
|
||||
const sampleProps = {
|
||||
questionTitle: 'Sample Question',
|
||||
choices: [
|
||||
{
|
||||
id: '1',
|
||||
feedback: {
|
||||
format: 'text',
|
||||
text: 'Correct answer feedback'
|
||||
},
|
||||
isCorrect: true,
|
||||
text: {
|
||||
format: 'text',
|
||||
text: 'Correct Answer'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
feedback: null,
|
||||
isCorrect: false,
|
||||
text: {
|
||||
format: 'text',
|
||||
text: 'Incorrect Answer'
|
||||
}
|
||||
}
|
||||
],
|
||||
handleOnSubmitAnswer: mockHandleSubmitAnswer,
|
||||
showAnswer: false
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
render(<ShortAnswerQuestion questionContent={{text: sampleStem, format: 'plain'}} {...sampleProps} />);
|
||||
render(<ShortAnswerQuestionDisplay question={question} {...sampleProps} />);
|
||||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
expect(screen.getByText(sampleStem)).toBeInTheDocument();
|
||||
expect(screen.getByTestId('text-input')).toBeInTheDocument();
|
||||
expect(screen.getByText(question.formattedStem.text)).toBeInTheDocument();
|
||||
const container = screen.getByLabelText('short-answer-input');
|
||||
const inputElement = within(container).getByRole('textbox') as HTMLInputElement;
|
||||
expect(inputElement).toBeInTheDocument();
|
||||
expect(screen.getByText('Répondre')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles input change correctly', () => {
|
||||
const inputElement = screen.getByTestId('text-input') as HTMLInputElement;
|
||||
const container = screen.getByLabelText('short-answer-input');
|
||||
const inputElement = within(container).getByRole('textbox') as HTMLInputElement;
|
||||
|
||||
fireEvent.change(inputElement, { target: { value: 'User Input' } });
|
||||
|
||||
|
|
@ -70,7 +50,10 @@ describe('ShortAnswerQuestion Component', () => {
|
|||
});
|
||||
|
||||
it('submits answer correctly', () => {
|
||||
const inputElement = screen.getByTestId('text-input') as HTMLInputElement;
|
||||
const container = screen.getByLabelText('short-answer-input');
|
||||
const inputElement = within(container).getByRole('textbox') as HTMLInputElement;
|
||||
|
||||
// const inputElement = screen.getByRole('textbox', { name: 'short-answer-input'}) as HTMLInputElement;
|
||||
const submitButton = screen.getByText('Répondre');
|
||||
|
||||
fireEvent.change(inputElement, { target: { value: 'User Input' } });
|
||||
|
|
@ -2,16 +2,18 @@
|
|||
import React from 'react';
|
||||
import { render, fireEvent, screen, act } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import TrueFalseQuestion from 'src/components/Questions/TrueFalseQuestion/TrueFalseQuestion';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import TrueFalseQuestionDisplay from 'src/components/QuestionsDisplay/TrueFalseQuestionDisplay/TrueFalseQuestionDisplay';
|
||||
import { parse, TrueFalseQuestion } from 'gift-pegjs';
|
||||
|
||||
describe('TrueFalseQuestion Component', () => {
|
||||
const mockHandleSubmitAnswer = jest.fn();
|
||||
const sampleStem = 'Sample question stem';
|
||||
const sampleStem = 'Sample True False Question';
|
||||
const trueFalseQuestion =
|
||||
parse(`${sampleStem}{T}`)[0] as TrueFalseQuestion;
|
||||
|
||||
const sampleProps = {
|
||||
questionTitle: 'Sample True/False Question',
|
||||
correctAnswer: true,
|
||||
question: trueFalseQuestion,
|
||||
handleOnSubmitAnswer: mockHandleSubmitAnswer,
|
||||
showAnswer: false
|
||||
};
|
||||
|
|
@ -19,7 +21,7 @@ describe('TrueFalseQuestion Component', () => {
|
|||
beforeEach(() => {
|
||||
render(
|
||||
<MemoryRouter>
|
||||
<TrueFalseQuestion questionContent={{ text: sampleStem, format: 'plain' }} {...sampleProps} />
|
||||
<TrueFalseQuestionDisplay {...sampleProps} />
|
||||
</MemoryRouter>);
|
||||
});
|
||||
|
||||
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import { render, screen, fireEvent, act } from '@testing-library/react';
|
||||
import '@testing-library/jest-dom';
|
||||
import { parse } from 'gift-pegjs';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { QuestionType } from '../../../../Types/QuestionType';
|
||||
import StudentModeQuiz from 'src/components/StudentModeQuiz/StudentModeQuiz';
|
||||
import { BaseQuestion, parse } from 'gift-pegjs';
|
||||
import { QuestionType } from 'src/Types/QuestionType';
|
||||
|
||||
const mockGiftQuestions = parse(
|
||||
`::Sample Question 1:: Sample Question 1 {=Option A ~Option B}
|
||||
|
|
@ -12,11 +12,10 @@ const mockGiftQuestions = parse(
|
|||
::Sample Question 2:: Sample Question 2 {T}`);
|
||||
|
||||
const mockQuestions: QuestionType[] = mockGiftQuestions.map((question, index) => {
|
||||
question.id = (index + 1).toString();
|
||||
const newMockQuestion: QuestionType = {
|
||||
question: question,
|
||||
};
|
||||
return newMockQuestion;
|
||||
if (question.type !== "Category")
|
||||
question.id = (index + 1).toString();
|
||||
const newMockQuestion = question;
|
||||
return {question : newMockQuestion as BaseQuestion};
|
||||
});
|
||||
|
||||
const mockSubmitAnswer = jest.fn();
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import React from 'react';
|
|||
import { render, fireEvent, act } from '@testing-library/react';
|
||||
import { screen } from '@testing-library/dom';
|
||||
import '@testing-library/jest-dom';
|
||||
import { parse } from 'gift-pegjs';
|
||||
import { MultipleChoiceQuestion, parse } from 'gift-pegjs';
|
||||
|
||||
import TeacherModeQuiz from 'src/components/TeacherModeQuiz/TeacherModeQuiz';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
|
@ -14,7 +14,11 @@ const mockGiftQuestions = parse(
|
|||
|
||||
|
||||
describe('TeacherModeQuiz', () => {
|
||||
const mockQuestion = mockGiftQuestions[0];
|
||||
it ('renders the initial question as MultipleChoiceQuestion', () => {
|
||||
expect(mockGiftQuestions[0].type).toBe('MC');
|
||||
});
|
||||
|
||||
const mockQuestion = mockGiftQuestions[0] as MultipleChoiceQuestion;
|
||||
mockQuestion.id = '1';
|
||||
|
||||
const mockSubmitAnswer = jest.fn();
|
||||
|
|
|
|||
30
client/src/__tests__/smoke-test.test.ts
Normal file
30
client/src/__tests__/smoke-test.test.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { parse, NumericalQuestion, SimpleNumericalAnswer, } from "gift-pegjs";
|
||||
import { isSimpleNumericalAnswer } from "gift-pegjs/typeGuards";
|
||||
|
||||
describe('Numerical Question Tests', () => {
|
||||
// ::Ulysses birthdate::When was Ulysses S. Grant born? {#1822}
|
||||
it('should produce a valid Question object for a Numerical question with Title', () => {
|
||||
const input = `
|
||||
::Ulysses birthdate::When was Ulysses S. Grant born? {#1822}
|
||||
`;
|
||||
const result = parse(input);
|
||||
|
||||
// Type assertion to ensure result matches the Question interface
|
||||
const question = result[0];
|
||||
|
||||
// Example assertions to check specific properties
|
||||
expect(question).toHaveProperty('type', 'Numerical');
|
||||
const numericalQuestion = question as NumericalQuestion;
|
||||
expect(numericalQuestion.title).toBe('Ulysses birthdate');
|
||||
expect(numericalQuestion.formattedStem.text).toBe('When was Ulysses S. Grant born?');
|
||||
expect(numericalQuestion.choices).toBeDefined();
|
||||
expect(numericalQuestion.choices).toHaveLength(1);
|
||||
|
||||
const choice = numericalQuestion.choices[0];
|
||||
expect(isSimpleNumericalAnswer(choice)).toBe(true);
|
||||
const c = choice as SimpleNumericalAnswer;
|
||||
expect(c.type).toBe('simple');
|
||||
expect(c.number).toBe(1822);
|
||||
|
||||
});
|
||||
});
|
||||
|
|
@ -23,8 +23,8 @@ const GiftCheatSheet: React.FC = () => {
|
|||
|
||||
const QuestionVraiFaux = "2+2 \\= 4 ? {T}\n// Utilisez les valeurs {T}, {F}, {TRUE} \net {FALSE}.";
|
||||
const QuestionChoixMul = "Quelle ville est la capitale du Canada? {\n~ Toronto\n~ Montréal\n= Ottawa #Bonne réponse!\n}\n// La bonne réponse est Ottawa";
|
||||
const QuestionChoixMulMany = "Quelles villes trouve-t-on au Canada? { \n~ %33.3% Montréal \n ~ %33.3% Ottawa \n ~ %33.3% Vancouver \n ~ %-100% New York \n ~ %-100% Paris \n#### La bonne réponse est Montréal, Ottawa et Vancouver \n}\n/ Utilisez tilde (signe de vague) pour toutes les réponses.\n// On doit indiquer le pourcentage de chaque réponse.";
|
||||
const QuestionCourte ="Avec quoi ouvre-t-on une porte? { \n= clé \n= clef \n}\n/ Permet de fournir plusieurs bonnes réponses.\n// Note: La casse n'est pas prise en compte.";
|
||||
const QuestionChoixMulMany = "Quelles villes trouve-t-on au Canada? { \n~ %33.3% Montréal \n ~ %33.3% Ottawa \n ~ %33.3% Vancouver \n ~ %-100% New York \n ~ %-100% Paris \n#### La bonne réponse est Montréal, Ottawa et Vancouver \n}\n// Utilisez tilde (signe de vague) pour toutes les réponses.\n// On doit indiquer le pourcentage de chaque réponse.";
|
||||
const QuestionCourte ="Avec quoi ouvre-t-on une porte? { \n= clé \n= clef \n}\n// Permet de fournir plusieurs bonnes réponses.\n// Note: La casse n'est pas prise en compte.";
|
||||
const QuestionNum ="// Question de plage mathématique. \n Quel est un nombre de 1 à 5 ? {\n#3:2\n}\n \n// Plage mathématique spécifiée avec des points de fin d'intervalle. \n Quel est un nombre de 1 à 5 ? {\n#1..5\n} \n\n// Réponses numériques multiples avec crédit partiel et commentaires.\nQuand est né Ulysses S. Grant ? {\n# =1822:0 # Correct ! Crédit complet. \n=%50%1822:2 # Il est né en 1822. Demi-crédit pour être proche.\n}";
|
||||
return (
|
||||
<div className="gift-cheat-sheet">
|
||||
|
|
@ -123,7 +123,7 @@ const GiftCheatSheet: React.FC = () => {
|
|||
</div>
|
||||
|
||||
<div className="question-type">
|
||||
<h4> 7. Paramètres optionnels </h4>
|
||||
<h4> 7. Caractères spéciaux </h4>
|
||||
<p>
|
||||
Si vous souhaitez utiliser certains caractères spéciaux dans vos énoncés,
|
||||
réponses ou feedback, vous devez «échapper» ces derniers en ajoutant un \
|
||||
|
|
@ -155,7 +155,7 @@ const GiftCheatSheet: React.FC = () => {
|
|||
|
||||
<div className="question-type">
|
||||
<h4> 9. Images </h4>
|
||||
<p>Pour insérer une image dans une question ou dans une réponse, vous devez utiliser la syntaxe suivante:</p>
|
||||
<p>Il est possible d'insérer une image dans une question, une réponse (choix multiple) et dans une rétroaction. D'abord, <strong>le format de l'élément doit être [markdown]</strong>. Ensuite utilisez la syntaxe suivante :</p>
|
||||
<pre>
|
||||
<code className="question-code-block">
|
||||
{'!['}
|
||||
|
|
@ -173,8 +173,14 @@ const GiftCheatSheet: React.FC = () => {
|
|||
{'[markdown]Ceci est un chat: \n\n{T}'}
|
||||
</code>
|
||||
</pre>
|
||||
<p>Exemple d'une question à choix multiple avec l'image d'un chat dans une rétroaction :</p>
|
||||
<pre>
|
||||
<code className="question-code-block">
|
||||
{`[markdown]Qui a initié le développement d'ÉvalueTonSavoir {=ÉTS#OUI! 
|
||||
~EPFL#Non...}`}
|
||||
</code>
|
||||
</pre>
|
||||
<p>Note : les images étant spécifiées avec la syntaxe Markdown dans GIFT, on doit échapper les caractères spéciales (:) dans l'URL de l'image.</p>
|
||||
<p>Note : On ne peut utiliser les images dans les messages de rétroaction (GIFT), car les rétroactions ne supportent pas le texte avec formatage (Markdown).</p>
|
||||
<p style={{ color: 'red' }}>
|
||||
Attention: l'ancienne fonctionnalité avec les balises <code>{'<img>'}</code> n'est plus
|
||||
supportée.
|
||||
|
|
|
|||
|
|
@ -1,256 +1,21 @@
|
|||
import Template, { ErrorTemplate } from './templates';
|
||||
import { GIFTQuestion } from './templates/types';
|
||||
import './styles.css';
|
||||
import { parse } from 'gift-pegjs';
|
||||
|
||||
const multiple = parse(`
|
||||
Who's buried in Grant's tomb? {~%-50%Grant=%50%Jefferson=%50%no one####Not sure? There are many answers for this question so do not fret. Not sure? There are many answers for this question so do not fret.}
|
||||
|
||||
Grant is _____ in Grant's tomb. {=buried#No one is buried there.=entombed~living}
|
||||
|
||||
Grant is buried in Grant's tomb. {FALSE}
|
||||
|
||||
Who's buried in Grant's tomb? {=no one=nobody}
|
||||
|
||||
When was Ulysses S. Grant born? {#1822:5}
|
||||
|
||||
What is the capital of Canada? {=Canada -> Ottawa =Italy -> Rome =Japan -> Tokyo}
|
||||
`);
|
||||
|
||||
const multiple: GIFTQuestion[] = [
|
||||
{
|
||||
type: 'MC',
|
||||
title: null,
|
||||
stem: { format: 'markdown', text: "Who's buried in Grant's \r\n tomb?" },
|
||||
hasEmbeddedAnswers: false,
|
||||
globalFeedback: {
|
||||
format: 'moodle',
|
||||
text: 'Not sure? There are many answers for this question so do not fret. Not sure? There are many answers for this question so do not fret.'
|
||||
},
|
||||
choices: [
|
||||
{
|
||||
isCorrect: false,
|
||||
weight: -50,
|
||||
text: { format: 'moodle', text: 'Grant' },
|
||||
feedback: null
|
||||
},
|
||||
{
|
||||
isCorrect: true,
|
||||
weight: 50,
|
||||
text: { format: 'moodle', text: 'Jefferson' },
|
||||
feedback: null
|
||||
},
|
||||
{
|
||||
isCorrect: true,
|
||||
weight: 50,
|
||||
text: { format: 'moodle', text: 'no one' },
|
||||
feedback: null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'MC',
|
||||
title: null,
|
||||
stem: { format: 'moodle', text: "Grant is _____ in Grant's tomb." },
|
||||
hasEmbeddedAnswers: true,
|
||||
globalFeedback: null,
|
||||
choices: [
|
||||
{
|
||||
isCorrect: true,
|
||||
weight: null,
|
||||
text: { format: 'moodle', text: 'buried' },
|
||||
feedback: null
|
||||
},
|
||||
{
|
||||
isCorrect: true,
|
||||
weight: null,
|
||||
text: { format: 'moodle', text: 'entombed' },
|
||||
feedback: null
|
||||
},
|
||||
{
|
||||
isCorrect: false,
|
||||
weight: null,
|
||||
text: { format: 'moodle', text: 'living' },
|
||||
feedback: null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'TF',
|
||||
title: null,
|
||||
stem: { format: 'moodle', text: "Grant is buried in Grant's tomb." },
|
||||
hasEmbeddedAnswers: false,
|
||||
globalFeedback: null,
|
||||
isTrue: false,
|
||||
falseFeedback: null,
|
||||
trueFeedback: null
|
||||
},
|
||||
{
|
||||
type: 'Short',
|
||||
title: null,
|
||||
stem: { format: 'moodle', text: "Who's buried in Grant's tomb?" },
|
||||
hasEmbeddedAnswers: false,
|
||||
globalFeedback: null,
|
||||
choices: [
|
||||
{
|
||||
isCorrect: true,
|
||||
weight: null,
|
||||
text: { format: 'moodle', text: 'no one " has got me' },
|
||||
feedback: null
|
||||
},
|
||||
{
|
||||
isCorrect: true,
|
||||
weight: null,
|
||||
text: { format: 'moodle', text: 'nobody' },
|
||||
feedback: null
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'Numerical',
|
||||
title: null,
|
||||
stem: { format: 'moodle', text: 'When was Ulysses S. Grant born?' },
|
||||
hasEmbeddedAnswers: false,
|
||||
globalFeedback: null,
|
||||
choices: {
|
||||
type: 'range',
|
||||
number: 1822,
|
||||
range: 5
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'Matching',
|
||||
title: null,
|
||||
stem: {
|
||||
format: 'moodle',
|
||||
text: 'Match the following countries with their corresponding capitals.'
|
||||
},
|
||||
hasEmbeddedAnswers: false,
|
||||
globalFeedback: null,
|
||||
matchPairs: [
|
||||
{
|
||||
subquestion: { format: 'moodle', text: 'Canada' },
|
||||
subanswer: 'Ottawa'
|
||||
},
|
||||
{
|
||||
subquestion: { format: 'moodle', text: 'Italy' },
|
||||
subanswer: 'Rome'
|
||||
},
|
||||
{
|
||||
subquestion: { format: 'moodle', text: 'Japan' },
|
||||
subanswer: 'Tokyo'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'MC',
|
||||
title: "Grant's Tomb",
|
||||
stem: { format: 'moodle', text: "Grant is _____ in Grant's tomb." },
|
||||
hasEmbeddedAnswers: true,
|
||||
globalFeedback: null,
|
||||
choices: [
|
||||
{
|
||||
isCorrect: false,
|
||||
weight: null,
|
||||
text: { format: 'moodle', text: 'buried' },
|
||||
feedback: { format: 'moodle', text: 'No one is buried there.' }
|
||||
},
|
||||
{
|
||||
isCorrect: true,
|
||||
weight: null,
|
||||
text: { format: 'moodle', text: 'entombed' },
|
||||
feedback: { format: 'moodle', text: 'Right answer!' }
|
||||
},
|
||||
{
|
||||
isCorrect: false,
|
||||
weight: null,
|
||||
text: { format: 'moodle', text: 'living' },
|
||||
feedback: { format: 'moodle', text: 'We hope not!' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'MC',
|
||||
title: null,
|
||||
stem: { format: 'moodle', text: 'Difficult multiple choice question.' },
|
||||
hasEmbeddedAnswers: false,
|
||||
globalFeedback: null,
|
||||
choices: [
|
||||
{
|
||||
isCorrect: false,
|
||||
weight: null,
|
||||
text: { format: 'moodle', text: 'wrong answer' },
|
||||
feedback: { format: 'moodle', text: 'comment on wrong answer' }
|
||||
},
|
||||
{
|
||||
isCorrect: false,
|
||||
weight: 50,
|
||||
text: { format: 'moodle', text: 'half credit answer' },
|
||||
feedback: { format: 'moodle', text: 'comment on answer' }
|
||||
},
|
||||
{
|
||||
isCorrect: true,
|
||||
weight: null,
|
||||
text: { format: 'moodle', text: 'full credit answer' },
|
||||
feedback: { format: 'moodle', text: 'well done!' }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'Short',
|
||||
title: "Jesus' hometown (Short answer ex.)",
|
||||
stem: { format: 'moodle', text: 'Jesus Christ was from _____ .' },
|
||||
hasEmbeddedAnswers: true,
|
||||
globalFeedback: null,
|
||||
choices: [
|
||||
{
|
||||
isCorrect: true,
|
||||
weight: null,
|
||||
text: { format: 'moodle', text: 'Nazareth' },
|
||||
feedback: { format: 'moodle', text: "Yes! That's right!" }
|
||||
},
|
||||
{
|
||||
isCorrect: true,
|
||||
weight: 75,
|
||||
text: { format: 'moodle', text: 'Nazereth' },
|
||||
feedback: { format: 'moodle', text: 'Right, but misspelled.' }
|
||||
},
|
||||
{
|
||||
isCorrect: true,
|
||||
weight: 25,
|
||||
text: { format: 'moodle', text: 'Bethlehem' },
|
||||
feedback: {
|
||||
format: 'moodle',
|
||||
text: 'He was born here, but not raised here.'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'Numerical',
|
||||
title: 'Numerical example',
|
||||
stem: { format: 'moodle', text: 'When was Ulysses S. Grant born?' },
|
||||
hasEmbeddedAnswers: false,
|
||||
globalFeedback: null,
|
||||
choices: [
|
||||
{
|
||||
isCorrect: true,
|
||||
weight: null,
|
||||
text: {
|
||||
type: 'range',
|
||||
number: 1822,
|
||||
range: 0
|
||||
},
|
||||
feedback: { format: 'moodle', text: 'Correct! 100% credit' }
|
||||
},
|
||||
{
|
||||
isCorrect: true,
|
||||
weight: 50,
|
||||
text: {
|
||||
type: 'range',
|
||||
number: 1822,
|
||||
range: 2
|
||||
},
|
||||
feedback: {
|
||||
format: 'moodle',
|
||||
text: 'He was born in 1822. You get 50% credit for being close.'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: 'Essay',
|
||||
title: 'Essay Example',
|
||||
stem: { format: 'moodle', text: 'This is an essay.' },
|
||||
hasEmbeddedAnswers: false,
|
||||
globalFeedback: null
|
||||
}
|
||||
];
|
||||
|
||||
const items = multiple.map((item) => Template(item, { theme: 'dark' })).join('');
|
||||
const errorItemDark = ErrorTemplate('Hello');
|
||||
|
|
|
|||
|
|
@ -72,6 +72,11 @@
|
|||
font-size: 2rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.present-question-stem {
|
||||
margin-bottom: 2vh;
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
margin-bottom: 2vh;
|
||||
width: 60vw;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
})
|
||||
})}`;
|
||||
}
|
||||
|
|
@ -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
|
||||
})
|
||||
})}`;
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
import { TemplateOptions, Description as DescriptionType } from './types';
|
||||
import QuestionContainer from './QuestionContainer';
|
||||
import Title from './Title';
|
||||
import textType from './TextType';
|
||||
import { ParagraphStyle } from '../constants';
|
||||
import { state } from '.';
|
||||
|
||||
type DescriptionOptions = TemplateOptions & DescriptionType;
|
||||
|
||||
export default function Description({ title, stem }: DescriptionOptions): string {
|
||||
return `${QuestionContainer({
|
||||
children: [
|
||||
Title({
|
||||
type: 'Description',
|
||||
title: title
|
||||
}),
|
||||
`<p style="${ParagraphStyle(state.theme)}">${textType({
|
||||
text: stem
|
||||
})}</p>`
|
||||
]
|
||||
})}`;
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
import { TemplateOptions } from './types';
|
||||
import QuestionContainer from './QuestionContainerTemplate';
|
||||
import Title from './TitleTemplate';
|
||||
import { Description } from 'gift-pegjs';
|
||||
import StemTemplate from './StemTemplate';
|
||||
|
||||
type DescriptionOptions = TemplateOptions & Description;
|
||||
|
||||
export default function DescriptionTemplate({ title, formattedStem}: DescriptionOptions): string {
|
||||
return `${QuestionContainer({
|
||||
children: [
|
||||
Title({
|
||||
type: 'Description',
|
||||
title: title
|
||||
}),
|
||||
StemTemplate({formattedStem}),
|
||||
]
|
||||
})}`;
|
||||
}
|
||||
|
|
@ -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 })
|
||||
]
|
||||
})}`;
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { TemplateOptions } from './types';
|
||||
import QuestionContainer from './QuestionContainerTemplate';
|
||||
import Title from './TitleTemplate';
|
||||
import GlobalFeedbackTemplate from './GlobalFeedbackTemplate';
|
||||
import { TextAreaStyle } from '../constants';
|
||||
import { state } from '.';
|
||||
import { EssayQuestion } from 'gift-pegjs';
|
||||
import StemTemplate from './StemTemplate';
|
||||
|
||||
type EssayOptions = TemplateOptions & EssayQuestion;
|
||||
|
||||
export default function EssayTemplate({ title, formattedStem, formattedGlobalFeedback }: EssayOptions): string {
|
||||
return `${QuestionContainer({
|
||||
children: [
|
||||
Title({
|
||||
type: 'Développement',
|
||||
title: title
|
||||
}),
|
||||
StemTemplate({formattedStem}),
|
||||
`<textarea class="gift-textarea" style="${TextAreaStyle(
|
||||
state.theme
|
||||
)}" placeholder="Entrez votre réponse ici..."></textarea>`,
|
||||
(formattedGlobalFeedback && formattedGlobalFeedback.text) ?
|
||||
GlobalFeedbackTemplate(formattedGlobalFeedback) : ``
|
||||
]
|
||||
})}`;
|
||||
}
|
||||
|
|
@ -1,13 +1,12 @@
|
|||
import { TemplateOptions, Question } from './types';
|
||||
import textType from './TextType';
|
||||
import { TemplateOptions } from './types';
|
||||
import {FormattedTextTemplate} from './TextTypeTemplate';
|
||||
import { state } from '.';
|
||||
import { theme } from '../constants';
|
||||
import { TextFormat } from 'gift-pegjs';
|
||||
|
||||
interface GlobalFeedbackOptions extends TemplateOptions {
|
||||
feedback: Question['globalFeedback'];
|
||||
}
|
||||
type GlobalFeedbackOptions = TemplateOptions & TextFormat;
|
||||
|
||||
export default function GlobalFeedback({ feedback }: GlobalFeedbackOptions): string {
|
||||
export default function GlobalFeedbackTemplate({ format, text }: GlobalFeedbackOptions): string {
|
||||
const Container = `
|
||||
position: relative;
|
||||
margin-top: 1rem;
|
||||
|
|
@ -19,9 +18,9 @@ export default function GlobalFeedback({ feedback }: GlobalFeedbackOptions): str
|
|||
box-shadow: 0px 2px 5px ${theme(state.theme, 'gray400', 'black800')};
|
||||
`;
|
||||
|
||||
return feedback !== null
|
||||
return (format && text)
|
||||
? `<div style="${Container}">
|
||||
<p>${textType({ text: feedback })}</p>
|
||||
<p>${FormattedTextTemplate({format: format, text: text})}</p>
|
||||
</div>`
|
||||
: ``;
|
||||
}
|
||||
|
|
@ -1,22 +1,24 @@
|
|||
import { TemplateOptions, Matching as MatchingType } from './types';
|
||||
import QuestionContainer from './QuestionContainer';
|
||||
import Title from './Title';
|
||||
import textType from './TextType';
|
||||
import GlobalFeedback from './GlobalFeedback';
|
||||
import { TemplateOptions } from './types';
|
||||
import QuestionContainer from './QuestionContainerTemplate';
|
||||
import Title from './TitleTemplate';
|
||||
import {FormattedTextTemplate} from './TextTypeTemplate';
|
||||
import GlobalFeedback from './GlobalFeedbackTemplate';
|
||||
import { ParagraphStyle, SelectStyle } from '../constants';
|
||||
import { state } from '.';
|
||||
import { MatchingQuestion } from 'gift-pegjs';
|
||||
import StemTemplate from './StemTemplate';
|
||||
|
||||
type MatchingOptions = TemplateOptions & MatchingType;
|
||||
type MatchingOptions = TemplateOptions & MatchingQuestion;
|
||||
|
||||
interface MatchAnswerOptions extends TemplateOptions {
|
||||
choices: MatchingType['matchPairs'];
|
||||
choices: MatchingQuestion['matchPairs'];
|
||||
}
|
||||
|
||||
export default function Matching({
|
||||
export default function MatchingTemplate({
|
||||
title,
|
||||
stem,
|
||||
formattedStem,
|
||||
matchPairs,
|
||||
globalFeedback
|
||||
formattedGlobalFeedback
|
||||
}: MatchingOptions): string {
|
||||
return `${QuestionContainer({
|
||||
children: [
|
||||
|
|
@ -24,11 +26,9 @@ export default function Matching({
|
|||
type: 'Appariement',
|
||||
title: title
|
||||
}),
|
||||
`<p style="${ParagraphStyle(state.theme)}">${textType({
|
||||
text: stem
|
||||
})}</p>`,
|
||||
StemTemplate({formattedStem}),
|
||||
MatchAnswers({ choices: matchPairs }),
|
||||
GlobalFeedback({ feedback: globalFeedback })
|
||||
formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : ''
|
||||
]
|
||||
})}`;
|
||||
}
|
||||
|
|
@ -64,10 +64,10 @@ function MatchAnswers({ choices }: MatchAnswerOptions): string {
|
|||
const uniqueMatchOptions = Array.from(new Set(choices.map(({ subanswer }) => subanswer)));
|
||||
|
||||
const result = choices
|
||||
.map(({ subquestion }) => {
|
||||
.map(({ formattedSubquestion }) => {
|
||||
return `
|
||||
<div style="${OptionTable} ${ParagraphStyle(state.theme)}">
|
||||
${textType({ text: subquestion })}
|
||||
${FormattedTextTemplate(formattedSubquestion)}
|
||||
</div>
|
||||
<div>
|
||||
<select class="gift-select" style="${SelectStyle(state.theme)} ${Dropdown}">
|
||||
|
|
@ -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 })
|
||||
]
|
||||
})}`;
|
||||
}
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
import { nanoid } from 'nanoid';
|
||||
import { TemplateOptions, TextFormat, Choice, MultipleChoice as MultipleChoiceType } from './types';
|
||||
import textType from './TextType';
|
||||
import AnswerIcon from './AnswerIcon';
|
||||
import { state } from '.';
|
||||
import { ParagraphStyle, theme } from '../constants';
|
||||
|
||||
type MultipleChoiceAnswerOptions = TemplateOptions & Pick<MultipleChoiceType, 'choices'>;
|
||||
|
||||
type AnswerFeedbackOptions = TemplateOptions & Pick<Choice, 'feedback'>;
|
||||
|
||||
interface AnswerWeightOptions extends TemplateOptions {
|
||||
weight: Choice['weight'];
|
||||
correct: Choice['isCorrect'];
|
||||
}
|
||||
|
||||
export default function MultipleChoiceAnswers({ choices }: MultipleChoiceAnswerOptions) {
|
||||
const id = `id${nanoid(8)}`;
|
||||
|
||||
const isMultipleAnswer = choices.filter(({ isCorrect }) => isCorrect === true).length === 0;
|
||||
|
||||
const prompt = `<span style="${ParagraphStyle(state.theme)}">Choisir une réponse${
|
||||
isMultipleAnswer ? ` ou plusieurs` : ``
|
||||
}:</span>`;
|
||||
const result = choices
|
||||
.map(({ weight, isCorrect, text, feedback }) => {
|
||||
const CustomLabel = `
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
`;
|
||||
|
||||
const inputId = `id${nanoid(6)}`;
|
||||
|
||||
const isPositiveWeight = weight !== null && weight > 0;
|
||||
const isCorrectOption = isMultipleAnswer ? isPositiveWeight : isCorrect;
|
||||
|
||||
return `
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="${
|
||||
isMultipleAnswer ? 'checkbox' : 'radio'
|
||||
}" id="${inputId}" name="${id}">
|
||||
${AnswerWeight({ correct: isCorrectOption, weight: weight })}
|
||||
<label style="${CustomLabel} ${ParagraphStyle(state.theme)}" for="${inputId}">
|
||||
${textType({ text: text as TextFormat })}
|
||||
</label>
|
||||
${AnswerIcon({ correct: isCorrectOption })}
|
||||
${AnswerFeedback({ feedback: feedback })}
|
||||
</input>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
return `${prompt}${result}`;
|
||||
}
|
||||
|
||||
function AnswerWeight({ weight, correct }: AnswerWeightOptions): string {
|
||||
const Container = `
|
||||
box-shadow: 0px 1px 1px ${theme(state.theme, 'gray400', 'black900')};
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
`;
|
||||
|
||||
const CorrectWeight = `
|
||||
color: ${theme(state.theme, 'green700', 'green100')};
|
||||
background-color: ${theme(state.theme, 'green100', 'greenGray700')};
|
||||
`;
|
||||
const IncorrectWeight = `
|
||||
color: ${theme(state.theme, 'beige600', 'beige100')};
|
||||
background-color: ${theme(state.theme, 'beige300', 'beigeGray800')};
|
||||
`;
|
||||
|
||||
return weight
|
||||
? `<span style="${Container} ${
|
||||
correct ? `${CorrectWeight}` : `${IncorrectWeight}`
|
||||
}">${weight}%</span>`
|
||||
: ``;
|
||||
}
|
||||
|
||||
function AnswerFeedback({ feedback }: AnswerFeedbackOptions): string {
|
||||
const Container = `
|
||||
color: ${theme(state.theme, 'teal700', 'gray700')};
|
||||
`;
|
||||
|
||||
return feedback ? `<span style="${Container}">${textType({ text: feedback })}</span>` : ``;
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
import { nanoid } from 'nanoid';
|
||||
import { TemplateOptions } from './types';
|
||||
import {FormattedTextTemplate} from './TextTypeTemplate';
|
||||
import AnswerIcon from './AnswerIconTemplate';
|
||||
import { state } from '.';
|
||||
import { ParagraphStyle } from '../constants';
|
||||
import { MultipleChoiceQuestion, TextChoice } from 'gift-pegjs';
|
||||
|
||||
type MultipleChoiceAnswerOptions = TemplateOptions & Pick<MultipleChoiceQuestion, 'choices'>;
|
||||
|
||||
type AnswerFeedbackOptions = TemplateOptions & Pick<TextChoice, 'formattedFeedback'>;
|
||||
|
||||
interface AnswerWeightOptions extends TemplateOptions {
|
||||
weight: TextChoice['weight'];
|
||||
}
|
||||
|
||||
export default function MultipleChoiceAnswersTemplate({ choices }: MultipleChoiceAnswerOptions) {
|
||||
const id = `id${nanoid(8)}`;
|
||||
|
||||
const isMultipleAnswer = choices.filter(({ isCorrect }) => isCorrect === true).length === 0;
|
||||
|
||||
const prompt = `<span style="${ParagraphStyle(state.theme)}">Choisir une réponse${
|
||||
isMultipleAnswer ? ` ou plusieurs` : ``
|
||||
}:</span>`;
|
||||
const result = choices
|
||||
.map(({ weight, isCorrect, formattedText, formattedFeedback }) => {
|
||||
const CustomLabel = `
|
||||
display: inline-block;
|
||||
padding: 0.2em 0 0.2em 0;
|
||||
`;
|
||||
|
||||
const inputId = `id${nanoid(6)}`;
|
||||
|
||||
const isPositiveWeight = (weight != undefined) && (weight > 0);
|
||||
const isCorrectOption = isMultipleAnswer ? isPositiveWeight : isCorrect;
|
||||
|
||||
return `
|
||||
<div class='multiple-choice-answers-container'>
|
||||
<input class="gift-input" type="${
|
||||
isMultipleAnswer ? 'checkbox' : 'radio'
|
||||
}" id="${inputId}" name="${id}">
|
||||
${AnswerWeight({ weight: weight })}
|
||||
<label style="${CustomLabel} ${ParagraphStyle(state.theme)}" for="${inputId}">
|
||||
${FormattedTextTemplate(formattedText)}
|
||||
</label>
|
||||
${AnswerIcon({ correct: isCorrectOption })}
|
||||
${AnswerFeedback({ formattedFeedback: formattedFeedback })}
|
||||
</input>
|
||||
</div>
|
||||
`;
|
||||
})
|
||||
.join('');
|
||||
|
||||
return `${prompt}${result}`;
|
||||
}
|
||||
|
||||
function AnswerWeight({ weight }: AnswerWeightOptions): string {
|
||||
return weight ? `<span class="answer-weight-container ${weight > 0 ? 'answer-positive-weight' : 'answer-zero-or-less-weight'}">${weight}%</span>` : ``;
|
||||
}
|
||||
|
||||
function AnswerFeedback({ formattedFeedback }: AnswerFeedbackOptions): string {
|
||||
return formattedFeedback ? `<span class="feedback-container">${FormattedTextTemplate(formattedFeedback)}</span>` : ``;
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
import { TemplateOptions } from './types';
|
||||
import QuestionContainer from './QuestionContainerTemplate';
|
||||
import GlobalFeedback from './GlobalFeedbackTemplate';
|
||||
import Title from './TitleTemplate';
|
||||
import MultipleChoiceAnswers from './MultipleChoiceAnswersTemplate';
|
||||
import { MultipleChoiceQuestion } from 'gift-pegjs';
|
||||
import StemTemplate from './StemTemplate';
|
||||
|
||||
type MultipleChoiceOptions = TemplateOptions & MultipleChoiceQuestion;
|
||||
|
||||
export default function MultipleChoiceTemplate({
|
||||
title,
|
||||
formattedStem,
|
||||
choices,
|
||||
formattedGlobalFeedback
|
||||
}: MultipleChoiceOptions): string {
|
||||
return `${QuestionContainer({
|
||||
children: [
|
||||
Title({
|
||||
type: 'Choix multiple',
|
||||
title: title
|
||||
}),
|
||||
StemTemplate({formattedStem}),
|
||||
MultipleChoiceAnswers({ choices: choices }),
|
||||
formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : ''
|
||||
]
|
||||
})}`;
|
||||
}
|
||||
|
|
@ -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 ``;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
import { TemplateOptions } from './types';
|
||||
import QuestionContainer from './QuestionContainerTemplate';
|
||||
import Title from './TitleTemplate';
|
||||
import GlobalFeedback from './GlobalFeedbackTemplate';
|
||||
import { ParagraphStyle, InputStyle } from '../constants';
|
||||
import { state } from '.';
|
||||
import { NumericalAnswer, NumericalQuestion, TextFormat } from 'gift-pegjs';
|
||||
import { isHighLowNumericalAnswer, isMultipleNumericalAnswer, isRangeNumericalAnswer, isSimpleNumericalAnswer } from 'gift-pegjs/typeGuards';
|
||||
import StemTemplate from './StemTemplate';
|
||||
import { FormattedTextTemplate } from './TextTypeTemplate';
|
||||
|
||||
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
|
||||
}),
|
||||
StemTemplate({formattedStem}),
|
||||
NumericalAnswers2({ choices: choices }),
|
||||
formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : ''
|
||||
]
|
||||
})}`;
|
||||
}
|
||||
|
||||
function NumericalAnswers2({ choices }: NumericalAnswerOptions): string {
|
||||
|
||||
// for each choice, return a separate <input> for each answer - answer should show weight and feedback
|
||||
const answers = choices.map(choice => {
|
||||
if (isMultipleNumericalAnswer(choice)) {
|
||||
return { answer: Answer(choice.answer), weight: choice.weight, feedback: choice.formattedFeedback };
|
||||
} else {
|
||||
return { answer: Answer(choice) };
|
||||
}
|
||||
});
|
||||
|
||||
let result = '<div>';
|
||||
|
||||
answers.forEach(answer => {
|
||||
const weight = answer.weight ?
|
||||
`<span class="numerical-answer-weight-container ${answer.weight > 0 ? 'answer-positive-weight' : 'answer-zero-or-less-weight'}">${answer.weight}%</span>` :
|
||||
''
|
||||
result +=
|
||||
`<p style="${ParagraphStyle(state.theme)}">Réponse: </p>`
|
||||
+ `<input class="gift-input" type="text" style="${InputStyle(state.theme)};width: 100%" placeholder="${answer.answer}">`
|
||||
+ weight
|
||||
+ (answer.feedback ? `<div class="feedback-container">${FormattedTextTemplate(answer.feedback)}</div>` : '');
|
||||
});
|
||||
|
||||
result += '</div>';
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
// function NumericalAnswers({ choices }: NumericalAnswerOptions): string {
|
||||
// const placeholder = isMultipleNumericalAnswer(choices[0])
|
||||
// ? choices.map(choice => {
|
||||
// console.log(JSON.stringify(choice));
|
||||
// const c = choice as MultipleNumericalAnswer;
|
||||
// return Answer(c.answer, c.formattedFeedback)
|
||||
// }).join(', ')
|
||||
// : Answer(choices[0]);
|
||||
|
||||
// // TODO return a separate <input> for each answer - answer should show weight and feedback
|
||||
// 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, formattedFeedback?: TextFormat): string {
|
||||
const formattedFeedbackString = formattedFeedback ? ` (${FormattedTextTemplate(formattedFeedback)})` : '';
|
||||
switch (true) {
|
||||
case isSimpleNumericalAnswer(choice):
|
||||
return `${choice.number}${formattedFeedbackString}`;
|
||||
case isRangeNumericalAnswer(choice):
|
||||
return `${choice.number} ± ${choice.range}${formattedFeedbackString}`;
|
||||
case isHighLowNumericalAnswer(choice):
|
||||
return `${choice.numberLow}..${choice.numberHigh}${formattedFeedbackString}`;
|
||||
default:
|
||||
return ``;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
import { TemplateOptions, ShortAnswer as ShortAnswerType, TextFormat } from './types';
|
||||
import QuestionContainer from './QuestionContainer';
|
||||
import Title from './Title';
|
||||
import textType from './TextType';
|
||||
import GlobalFeedback from './GlobalFeedback';
|
||||
import { ParagraphStyle, InputStyle } from '../constants';
|
||||
import { state } from './index';
|
||||
|
||||
type ShortAnswerOptions = TemplateOptions & ShortAnswerType;
|
||||
type AnswerOptions = TemplateOptions & Pick<ShortAnswerType, 'choices'>;
|
||||
|
||||
export default function ShortAnswer({
|
||||
title,
|
||||
stem,
|
||||
choices,
|
||||
globalFeedback
|
||||
}: ShortAnswerOptions): string {
|
||||
return `${QuestionContainer({
|
||||
children: [
|
||||
Title({
|
||||
type: 'Réponse courte',
|
||||
title: title
|
||||
}),
|
||||
`<p style="${ParagraphStyle(state.theme)}">${textType({
|
||||
text: stem
|
||||
})}</p>`,
|
||||
Answers({ choices: choices }),
|
||||
GlobalFeedback({ feedback: globalFeedback })
|
||||
]
|
||||
})}`;
|
||||
}
|
||||
|
||||
function Answers({ choices }: AnswerOptions): string {
|
||||
const placeholder = choices
|
||||
.map(({ text }) => textType({ text: text as TextFormat }))
|
||||
.join(', ');
|
||||
return `
|
||||
<div>
|
||||
<span style="${ParagraphStyle(
|
||||
state.theme
|
||||
)}">Réponse: </span><input class="gift-input" type="text" style="${InputStyle(
|
||||
state.theme
|
||||
)}" placeholder="${placeholder}">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
import { TemplateOptions } from './types';
|
||||
import QuestionContainer from './QuestionContainerTemplate';
|
||||
import Title from './TitleTemplate';
|
||||
import {FormattedTextTemplate} from './TextTypeTemplate';
|
||||
import GlobalFeedback from './GlobalFeedbackTemplate';
|
||||
import { ParagraphStyle, InputStyle } from '../constants';
|
||||
import { state } from './index';
|
||||
import { ShortAnswerQuestion } from 'gift-pegjs';
|
||||
import StemTemplate from './StemTemplate';
|
||||
|
||||
type ShortAnswerOptions = TemplateOptions & ShortAnswerQuestion;
|
||||
type AnswerOptions = TemplateOptions & Pick<ShortAnswerQuestion, 'choices'>;
|
||||
|
||||
export default function ShortAnswerTemplate({
|
||||
title,
|
||||
formattedStem,
|
||||
choices,
|
||||
formattedGlobalFeedback
|
||||
}: ShortAnswerOptions): string {
|
||||
return `${QuestionContainer({
|
||||
children: [
|
||||
Title({
|
||||
type: 'Réponse courte',
|
||||
title: title
|
||||
}),
|
||||
StemTemplate({formattedStem}),
|
||||
Answers({ choices: choices }),
|
||||
formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : ''
|
||||
]
|
||||
})}`;
|
||||
}
|
||||
|
||||
function Answers({ choices }: AnswerOptions): string {
|
||||
const placeholder = choices
|
||||
.map(({ text }) => FormattedTextTemplate({ format: '', text: text }))
|
||||
.join(', ');
|
||||
return `
|
||||
<div>
|
||||
<span style="${ParagraphStyle(
|
||||
state.theme
|
||||
)}">Réponse: </span><input class="gift-input" type="text" style="${InputStyle(
|
||||
state.theme
|
||||
)}" placeholder="${placeholder}">
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
26
client/src/components/GiftTemplate/templates/StemTemplate.ts
Normal file
26
client/src/components/GiftTemplate/templates/StemTemplate.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { TemplateOptions } from './types';
|
||||
import { state } from '.';
|
||||
import { ParagraphStyle } from '../constants';
|
||||
import { BaseQuestion } from 'gift-pegjs';
|
||||
import { FormattedTextTemplate } from './TextTypeTemplate';
|
||||
|
||||
// Type is string to allow for custom question type text (e,g, "Multiple Choice")
|
||||
interface StemOptions extends TemplateOptions {
|
||||
formattedStem: BaseQuestion['formattedStem'];
|
||||
}
|
||||
|
||||
export default function StemTemplate({ formattedStem }: StemOptions): string {
|
||||
const Container = `
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
return `
|
||||
<div style="${Container}">
|
||||
<span>
|
||||
<p style="${ParagraphStyle(state.theme)}" class="present-question-stem">
|
||||
${FormattedTextTemplate(formattedStem)}
|
||||
</p>
|
||||
</span>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
|
@ -1,10 +1,8 @@
|
|||
import { marked } from 'marked';
|
||||
import katex from 'katex';
|
||||
import { TemplateOptions, TextFormat } from './types';
|
||||
import marked from 'src/markedConfig';
|
||||
|
||||
interface TextTypeOptions extends TemplateOptions {
|
||||
text: TextFormat;
|
||||
}
|
||||
import katex from 'katex';
|
||||
import { TextFormat } from 'gift-pegjs';
|
||||
import DOMPurify from 'dompurify'; // cleans HTML to prevent XSS attacks, etc.
|
||||
|
||||
export function formatLatex(text: string): string {
|
||||
return text
|
||||
|
|
@ -28,23 +26,29 @@ export function formatLatex(text: string): string {
|
|||
* @see marked
|
||||
* @see katex
|
||||
*/
|
||||
export default function textType({ text }: TextTypeOptions) {
|
||||
const formatText = formatLatex(text.text.trim()); // latex needs pure "&", ">", etc. Must not be escaped
|
||||
export function FormattedTextTemplate(formattedText: TextFormat): string {
|
||||
const formatText = formatLatex(formattedText.text.trim()); // latex needs pure "&", ">", etc. Must not be escaped
|
||||
let parsedText = '';
|
||||
switch (text.format) {
|
||||
let result = '';
|
||||
switch (formattedText.format) {
|
||||
case '':
|
||||
case 'moodle':
|
||||
case 'plain':
|
||||
// Replace newlines with <br> tags
|
||||
return replaceNewlinesOutsideSVG(formatText);
|
||||
result = replaceNewlinesOutsideSVG(formatText);
|
||||
break;
|
||||
case 'html':
|
||||
// Strip outer paragraph tags (not a great approach with regex)
|
||||
return formatText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2');
|
||||
result = formatText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2');
|
||||
break;
|
||||
case 'markdown':
|
||||
parsedText = marked.parse(formatText, { breaks: true }) as string; // https://github.com/markedjs/marked/discussions/3219
|
||||
return parsedText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2');
|
||||
parsedText = marked.parse(formatText, { breaks: true, gfm: true }) as string; // <br> for newlines
|
||||
result = parsedText.replace(/(^<p>)(.*?)(<\/p>)$/gm, '$2');
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported text format: ${text.format}`);
|
||||
throw new Error(`Unsupported text format: ${formattedText.format}`);
|
||||
}
|
||||
return DOMPurify.sanitize(result);
|
||||
}
|
||||
|
||||
// Function to replace \n outside of SVG paths
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { TemplateOptions, Question } from './types';
|
||||
import { TemplateOptions } from './types';
|
||||
import { state } from '.';
|
||||
import { theme } from '../constants';
|
||||
import { Question } from 'gift-pegjs';
|
||||
|
||||
// Type is string to allow for custom question type text (e,g, "Multiple Choice")
|
||||
interface TitleOptions extends TemplateOptions {
|
||||
|
|
@ -43,7 +44,7 @@ export default function Title({ type, title }: TitleOptions): string {
|
|||
<div style="${Container}">
|
||||
<span>
|
||||
${
|
||||
title !== null
|
||||
title
|
||||
? `<span style="${QuestionTitle}">${title}</span>`
|
||||
: `<span style="${OptionalTitle}"><em>(Sans titre)</em></span>`
|
||||
}
|
||||
|
|
@ -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 })
|
||||
]
|
||||
})}`;
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
import { TemplateOptions } from './types';
|
||||
import QuestionContainer from './QuestionContainerTemplate';
|
||||
import GlobalFeedback from './GlobalFeedbackTemplate';
|
||||
import MultipleChoiceAnswersTemplate from './MultipleChoiceAnswersTemplate';
|
||||
import Title from './TitleTemplate';
|
||||
import { TextChoice, TrueFalseQuestion } from 'gift-pegjs';
|
||||
import StemTemplate from './StemTemplate';
|
||||
|
||||
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
|
||||
}),
|
||||
StemTemplate({formattedStem}),
|
||||
MultipleChoiceAnswersTemplate({ choices: choices }),
|
||||
formattedGlobalFeedback ? GlobalFeedback(formattedGlobalFeedback) : ``
|
||||
]
|
||||
})}`;
|
||||
}
|
||||
|
|
@ -1,24 +1,25 @@
|
|||
import Category from './Category';
|
||||
import Description from './Description';
|
||||
import Essay from './Essay';
|
||||
import Matching from './Matching';
|
||||
import MultipleChoice from './MultipleChoice';
|
||||
import Numerical from './Numerical';
|
||||
import ShortAnswer from './ShortAnswer';
|
||||
import TrueFalse from './TrueFalse';
|
||||
import Error from './Error';
|
||||
|
||||
import {
|
||||
GIFTQuestion,
|
||||
ParsedGIFTQuestion as GIFTQuestion,
|
||||
// Category as CategoryType,
|
||||
// Description as DescriptionType,
|
||||
MultipleChoice as MultipleChoiceType,
|
||||
Numerical as NumericalType,
|
||||
ShortAnswer as ShortAnswerType,
|
||||
MultipleChoiceQuestion as MultipleChoiceType,
|
||||
NumericalQuestion as NumericalType,
|
||||
ShortAnswerQuestion as ShortAnswerType,
|
||||
// Essay as EssayType,
|
||||
TrueFalse as TrueFalseType,
|
||||
Matching as MatchingType,
|
||||
DisplayOptions
|
||||
} from './types';
|
||||
TrueFalseQuestion as TrueFalseType,
|
||||
// MatchingQuestion as MatchingType,
|
||||
} from 'gift-pegjs';
|
||||
import { DisplayOptions } from './types';
|
||||
import DescriptionTemplate from './DescriptionTemplate';
|
||||
import EssayTemplate from './EssayTemplate';
|
||||
import MatchingTemplate from './MatchingTemplate';
|
||||
import MultipleChoiceTemplate from './MultipleChoiceTemplate';
|
||||
import NumericalTemplate from './NumericalTemplate';
|
||||
import ShortAnswerTemplate from './ShortAnswerTemplate';
|
||||
import TrueFalseTemplate from './TrueFalseTemplate';
|
||||
import Error from './ErrorTemplate';
|
||||
import CategoryTemplate from './CategoryTemplate';
|
||||
|
||||
export const state: DisplayOptions = { preview: true, theme: 'light' };
|
||||
|
||||
|
|
@ -37,21 +38,21 @@ export default function Template(
|
|||
// ...(keys as DescriptionType)
|
||||
// });
|
||||
case 'MC':
|
||||
return MultipleChoice({
|
||||
return MultipleChoiceTemplate({
|
||||
...(keys as MultipleChoiceType)
|
||||
});
|
||||
case 'Numerical':
|
||||
return Numerical({ ...(keys as NumericalType) });
|
||||
return NumericalTemplate({ ...(keys as NumericalType) });
|
||||
case 'Short':
|
||||
return ShortAnswer({
|
||||
return ShortAnswerTemplate({
|
||||
...(keys as ShortAnswerType)
|
||||
});
|
||||
// case 'Essay':
|
||||
// return Essay({ ...(keys as EssayType) });
|
||||
case 'TF':
|
||||
return TrueFalse({ ...(keys as TrueFalseType) });
|
||||
case 'Matching':
|
||||
return Matching({ ...(keys as MatchingType) });
|
||||
return TrueFalseTemplate({ ...(keys as TrueFalseType) });
|
||||
// case 'Matching':
|
||||
// return Matching({ ...(keys as MatchingType) });
|
||||
default:
|
||||
// TODO: throw error for unsupported question types?
|
||||
// throw new Error(`Unsupported question type: ${type}`);
|
||||
|
|
@ -66,13 +67,13 @@ export function ErrorTemplate(text: string, options?: Partial<DisplayOptions>):
|
|||
}
|
||||
|
||||
export {
|
||||
Category,
|
||||
Description,
|
||||
Essay,
|
||||
Matching,
|
||||
MultipleChoice,
|
||||
Numerical,
|
||||
ShortAnswer,
|
||||
TrueFalse,
|
||||
CategoryTemplate,
|
||||
DescriptionTemplate as Description,
|
||||
EssayTemplate as Essay,
|
||||
MatchingTemplate as Matching,
|
||||
MultipleChoiceTemplate as MultipleChoice,
|
||||
NumericalTemplate as Numerical,
|
||||
ShortAnswerTemplate as ShortAnswer,
|
||||
TrueFalseTemplate as TrueFalse,
|
||||
Error
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,13 +12,13 @@ export interface DisplayOptions {
|
|||
preview: boolean;
|
||||
}
|
||||
|
||||
export {
|
||||
QuestionType, FormatType, NumericalType, TextFormat, NumericalFormat, TextChoice, NumericalChoice, Question, Description, Category, MultipleChoice, ShortAnswer, Numerical, Essay, TrueFalse,
|
||||
Matching, Match, GIFTQuestion } from 'gift-pegjs';
|
||||
// export {
|
||||
// QuestionType, FormatType, NumericalType, TextFormat, NumericalFormat, TextChoice, NumericalChoice, Question, Description, Category, MultipleChoice, ShortAnswer, Numerical, Essay, TrueFalse,
|
||||
// Matching, Match, GIFTQuestion } from 'gift-pegjs';
|
||||
|
||||
export interface Choice {
|
||||
isCorrect: boolean;
|
||||
weight: number | null;
|
||||
text: TextFormat | NumericalFormat;
|
||||
feedback: TextFormat | null;
|
||||
}
|
||||
// export interface Choice {
|
||||
// isCorrect: boolean;
|
||||
// weight: number | null;
|
||||
// text: TextFormat | NumericalFormat;
|
||||
// feedback: TextFormat | null;
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import {
|
|||
TableRow
|
||||
} from '@mui/material';
|
||||
import { StudentType } from '../../Types/StudentType';
|
||||
import { formatLatex } from '../GiftTemplate/templates/TextType';
|
||||
import { formatLatex } from '../GiftTemplate/templates/TextTypeTemplate';
|
||||
|
||||
interface LiveResultsProps {
|
||||
socket: Socket | null;
|
||||
|
|
|
|||
|
|
@ -1,80 +0,0 @@
|
|||
// NumericalQuestion.tsx
|
||||
import React, { useState } from 'react';
|
||||
import '../questionStyle.css';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import textType from '../../GiftTemplate/templates/TextType';
|
||||
import { TextFormat } from '../../GiftTemplate/templates/types';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
type CorrectAnswer = {
|
||||
numberHigh?: number;
|
||||
numberLow?: number;
|
||||
number?: number;
|
||||
type: string;
|
||||
};
|
||||
|
||||
interface Props {
|
||||
questionContent: TextFormat;
|
||||
correctAnswers: CorrectAnswer;
|
||||
globalFeedback?: string | undefined;
|
||||
handleOnSubmitAnswer?: (answer: number) => void;
|
||||
showAnswer?: boolean;
|
||||
}
|
||||
|
||||
const NumericalQuestion: React.FC<Props> = (props) => {
|
||||
const { questionContent, correctAnswers, showAnswer, handleOnSubmitAnswer, globalFeedback } =
|
||||
props;
|
||||
|
||||
const [answer, setAnswer] = useState<number>();
|
||||
|
||||
const correctAnswer =
|
||||
correctAnswers.type === 'high-low'
|
||||
? `Entre ${correctAnswers.numberLow} et ${correctAnswers.numberHigh}`
|
||||
: correctAnswers.number;
|
||||
|
||||
return (
|
||||
<div className="question-wrapper">
|
||||
<div>
|
||||
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({text: questionContent})) }} />
|
||||
</div>
|
||||
{showAnswer ? (
|
||||
<>
|
||||
<div className="correct-answer-text mb-2">{correctAnswer}</div>
|
||||
{globalFeedback && <div className="global-feedback mb-2">{globalFeedback}</div>}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="answer-wrapper mb-1">
|
||||
<TextField
|
||||
type="number"
|
||||
id={questionContent.text}
|
||||
name={questionContent.text}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAnswer(e.target.valueAsNumber);
|
||||
}}
|
||||
inputProps={{ 'data-testid': 'number-input' }}
|
||||
/>
|
||||
</div>
|
||||
{globalFeedback && showAnswer && (
|
||||
<div className="global-feedback mb-2">{globalFeedback}</div>
|
||||
)}
|
||||
{handleOnSubmitAnswer && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() =>
|
||||
answer !== undefined &&
|
||||
handleOnSubmitAnswer &&
|
||||
handleOnSubmitAnswer(answer)
|
||||
}
|
||||
disabled={answer === undefined || isNaN(answer)}
|
||||
>
|
||||
Répondre
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NumericalQuestion;
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
// Question;tsx
|
||||
import React, { useMemo } from 'react';
|
||||
import { GIFTQuestion } from 'gift-pegjs';
|
||||
|
||||
import TrueFalseQuestion from './TrueFalseQuestion/TrueFalseQuestion';
|
||||
import MultipleChoiceQuestion from './MultipleChoiceQuestion/MultipleChoiceQuestion';
|
||||
import NumericalQuestion from './NumericalQuestion/NumericalQuestion';
|
||||
import ShortAnswerQuestion from './ShortAnswerQuestion/ShortAnswerQuestion';
|
||||
import useCheckMobileScreen from '../../services/useCheckMobileScreen';
|
||||
|
||||
interface QuestionProps {
|
||||
question: GIFTQuestion | undefined;
|
||||
handleOnSubmitAnswer?: (answer: string | number | boolean) => void;
|
||||
showAnswer?: boolean;
|
||||
imageUrl?: string;
|
||||
}
|
||||
const Question: React.FC<QuestionProps> = ({
|
||||
question,
|
||||
handleOnSubmitAnswer,
|
||||
showAnswer,
|
||||
imageUrl
|
||||
}) => {
|
||||
const isMobile = useCheckMobileScreen();
|
||||
const imgWidth = useMemo(() => {
|
||||
return isMobile ? '100%' : '20%';
|
||||
}, [isMobile]);
|
||||
|
||||
let questionTypeComponent = null;
|
||||
switch (question?.type) {
|
||||
case 'TF':
|
||||
questionTypeComponent = (
|
||||
<TrueFalseQuestion
|
||||
questionContent={question.stem}
|
||||
correctAnswer={question.isTrue}
|
||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||
showAnswer={showAnswer}
|
||||
globalFeedback={question.globalFeedback?.text}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'MC':
|
||||
questionTypeComponent = (
|
||||
<MultipleChoiceQuestion
|
||||
questionStem={question.stem}
|
||||
choices={question.choices.map((choice, index) => ({ ...choice, id: index.toString() }))}
|
||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||
showAnswer={showAnswer}
|
||||
globalFeedback={question.globalFeedback?.text}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'Numerical':
|
||||
if (question.choices) {
|
||||
if (!Array.isArray(question.choices)) {
|
||||
questionTypeComponent = (
|
||||
<NumericalQuestion
|
||||
questionContent={question.stem}
|
||||
correctAnswers={question.choices}
|
||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||
showAnswer={showAnswer}
|
||||
globalFeedback={question.globalFeedback?.text}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
questionTypeComponent = (
|
||||
<NumericalQuestion
|
||||
questionContent={question.stem}
|
||||
correctAnswers={question.choices[0].text}
|
||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||
showAnswer={showAnswer}
|
||||
globalFeedback={question.globalFeedback?.text}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'Short':
|
||||
questionTypeComponent = (
|
||||
<ShortAnswerQuestion
|
||||
questionContent={question.stem}
|
||||
choices={question.choices.map((choice, index) => ({ ...choice, id: index.toString() }))}
|
||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||
showAnswer={showAnswer}
|
||||
globalFeedback={question.globalFeedback?.text}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<div className="question-container">
|
||||
{questionTypeComponent ? (
|
||||
<>
|
||||
{imageUrl && (
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt="QuestionImage"
|
||||
style={{ width: imgWidth, marginBottom: '2rem' }}
|
||||
/>
|
||||
)}
|
||||
{questionTypeComponent}
|
||||
</>
|
||||
) : (
|
||||
<div>Question de type inconnue</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Question;
|
||||
|
|
@ -1,35 +1,24 @@
|
|||
// MultipleChoiceQuestion.tsx
|
||||
// MultipleChoiceQuestionDisplay.tsx
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import '../questionStyle.css';
|
||||
import { Button } from '@mui/material';
|
||||
import textType, { formatLatex } from '../../GiftTemplate/templates/TextType';
|
||||
import { TextFormat } from '../../GiftTemplate/templates/types';
|
||||
import DOMPurify from 'dompurify';
|
||||
// import Latex from 'react-latex';
|
||||
|
||||
type Choices = {
|
||||
feedback: { format: string; text: string } | null;
|
||||
isCorrect: boolean;
|
||||
text: { format: string; text: string };
|
||||
weigth?: number;
|
||||
};
|
||||
import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate';
|
||||
import { MultipleChoiceQuestion } from 'gift-pegjs';
|
||||
|
||||
interface Props {
|
||||
questionStem: TextFormat;
|
||||
choices: Choices[];
|
||||
globalFeedback?: string | undefined;
|
||||
question: MultipleChoiceQuestion;
|
||||
handleOnSubmitAnswer?: (answer: string) => void;
|
||||
showAnswer?: boolean;
|
||||
}
|
||||
|
||||
const MultipleChoiceQuestion: React.FC<Props> = (props) => {
|
||||
const MultipleChoiceQuestionDisplay: React.FC<Props> = (props) => {
|
||||
|
||||
const { questionStem: questionContent, choices, showAnswer, handleOnSubmitAnswer, globalFeedback } = props;
|
||||
const { question, showAnswer, handleOnSubmitAnswer } = props;
|
||||
const [answer, setAnswer] = useState<string>();
|
||||
|
||||
useEffect(() => {
|
||||
setAnswer(undefined);
|
||||
}, [questionContent]);
|
||||
}, [question]);
|
||||
|
||||
const handleOnClickAnswer = (choice: string) => {
|
||||
setAnswer(choice);
|
||||
|
|
@ -41,38 +30,40 @@ const MultipleChoiceQuestion: React.FC<Props> = (props) => {
|
|||
return (
|
||||
<div className="question-container">
|
||||
<div className="question content">
|
||||
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({text: questionContent})) }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
|
||||
</div>
|
||||
<div className="choices-wrapper mb-1">
|
||||
{choices.map((choice, i) => {
|
||||
const selected = answer === choice.text.text ? 'selected' : '';
|
||||
{question.choices.map((choice, i) => {
|
||||
const selected = answer === choice.formattedText.text ? 'selected' : '';
|
||||
return (
|
||||
<div key={choice.text.text + i} className="choice-container">
|
||||
<div key={choice.formattedText.text + i} className="choice-container">
|
||||
<Button
|
||||
variant="text"
|
||||
className="button-wrapper"
|
||||
onClick={() => !showAnswer && handleOnClickAnswer(choice.text.text)}
|
||||
onClick={() => !showAnswer && handleOnClickAnswer(choice.formattedText.text)}
|
||||
>
|
||||
{choice.feedback === null &&
|
||||
{choice.formattedFeedback === null &&
|
||||
showAnswer &&
|
||||
(choice.isCorrect ? '✅' : '❌')}
|
||||
<div className={`circle ${selected}`}>{alphabet[i]}</div>
|
||||
<div className={`answer-text ${selected}`}>
|
||||
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(formatLatex(choice.text.text)) }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(choice.formattedText) }} />
|
||||
</div>
|
||||
</Button>
|
||||
{choice.feedback && showAnswer && (
|
||||
{choice.formattedFeedback && showAnswer && (
|
||||
<div className="feedback-container mb-1 mt-1/2">
|
||||
{choice.isCorrect ? '✅' : '❌'}
|
||||
{choice.feedback?.text}
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(choice.formattedFeedback) }} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
{globalFeedback && showAnswer && (
|
||||
<div className="global-feedback mb-2">{globalFeedback}</div>
|
||||
{question.formattedGlobalFeedback && showAnswer && (
|
||||
<div className="global-feedback mb-2">
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!showAnswer && handleOnSubmitAnswer && (
|
||||
|
|
@ -92,4 +83,4 @@ const MultipleChoiceQuestion: React.FC<Props> = (props) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default MultipleChoiceQuestion;
|
||||
export default MultipleChoiceQuestionDisplay;
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
// NumericalQuestion.tsx
|
||||
import React, { useState } from 'react';
|
||||
import '../questionStyle.css';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate';
|
||||
import { NumericalQuestion, SimpleNumericalAnswer, RangeNumericalAnswer, HighLowNumericalAnswer } from 'gift-pegjs';
|
||||
import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer, isMultipleNumericalAnswer } from 'gift-pegjs/typeGuards';
|
||||
|
||||
interface Props {
|
||||
question: NumericalQuestion;
|
||||
handleOnSubmitAnswer?: (answer: number) => void;
|
||||
showAnswer?: boolean;
|
||||
}
|
||||
|
||||
const NumericalQuestionDisplay: React.FC<Props> = (props) => {
|
||||
const { question, showAnswer, handleOnSubmitAnswer } =
|
||||
props;
|
||||
|
||||
const [answer, setAnswer] = useState<number>();
|
||||
|
||||
const correctAnswers = question.choices;
|
||||
let correctAnswer = '';
|
||||
|
||||
//const isSingleAnswer = correctAnswers.length === 1;
|
||||
|
||||
if (isSimpleNumericalAnswer(correctAnswers[0])) {
|
||||
correctAnswer = `${(correctAnswers[0] as SimpleNumericalAnswer).number}`;
|
||||
} else if (isRangeNumericalAnswer(correctAnswers[0])) {
|
||||
const choice = correctAnswers[0] as RangeNumericalAnswer;
|
||||
correctAnswer = `Entre ${choice.number - choice.range} et ${choice.number + choice.range}`;
|
||||
} else if (isHighLowNumericalAnswer(correctAnswers[0])) {
|
||||
const choice = correctAnswers[0] as HighLowNumericalAnswer;
|
||||
correctAnswer = `Entre ${choice.numberLow} et ${choice.numberHigh}`;
|
||||
} else if (isMultipleNumericalAnswer(correctAnswers[0])) {
|
||||
correctAnswer = `MultipleNumericalAnswer is not supported yet`;
|
||||
} else {
|
||||
throw new Error('Unknown numerical answer type');
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="question-wrapper">
|
||||
<div>
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
|
||||
</div>
|
||||
{showAnswer ? (
|
||||
<>
|
||||
<div className="correct-answer-text mb-2">{correctAnswer}</div>
|
||||
{question.formattedGlobalFeedback && <div className="global-feedback mb-2">
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
|
||||
</div>}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="answer-wrapper mb-1">
|
||||
<TextField
|
||||
type="number"
|
||||
id={question.formattedStem.text}
|
||||
name={question.formattedStem.text}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setAnswer(e.target.valueAsNumber);
|
||||
}}
|
||||
inputProps={{ 'data-testid': 'number-input' }}
|
||||
/>
|
||||
</div>
|
||||
{question.formattedGlobalFeedback && showAnswer && (
|
||||
<div className="global-feedback mb-2">
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
|
||||
</div>
|
||||
)}
|
||||
{handleOnSubmitAnswer && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={() =>
|
||||
answer !== undefined &&
|
||||
handleOnSubmitAnswer &&
|
||||
handleOnSubmitAnswer(answer)
|
||||
}
|
||||
disabled={answer === undefined || isNaN(answer)}
|
||||
>
|
||||
Répondre
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default NumericalQuestionDisplay;
|
||||
89
client/src/components/QuestionsDisplay/QuestionDisplay.tsx
Normal file
89
client/src/components/QuestionsDisplay/QuestionDisplay.tsx
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
import React from 'react';
|
||||
import { Question } from 'gift-pegjs';
|
||||
|
||||
import TrueFalseQuestionDisplay from './TrueFalseQuestionDisplay/TrueFalseQuestionDisplay';
|
||||
import MultipleChoiceQuestionDisplay from './MultipleChoiceQuestionDisplay/MultipleChoiceQuestionDisplay';
|
||||
import NumericalQuestionDisplay from './NumericalQuestionDisplay/NumericalQuestionDisplay';
|
||||
import ShortAnswerQuestionDisplay from './ShortAnswerQuestionDisplay/ShortAnswerQuestionDisplay';
|
||||
// import useCheckMobileScreen from '../../services/useCheckMobileScreen';
|
||||
|
||||
interface QuestionProps {
|
||||
question: Question;
|
||||
handleOnSubmitAnswer?: (answer: string | number | boolean) => void;
|
||||
showAnswer?: boolean;
|
||||
}
|
||||
const QuestionDisplay: React.FC<QuestionProps> = ({
|
||||
question,
|
||||
handleOnSubmitAnswer,
|
||||
showAnswer,
|
||||
}) => {
|
||||
// const isMobile = useCheckMobileScreen();
|
||||
// const imgWidth = useMemo(() => {
|
||||
// return isMobile ? '100%' : '20%';
|
||||
// }, [isMobile]);
|
||||
|
||||
let questionTypeComponent = null;
|
||||
switch (question?.type) {
|
||||
case 'TF':
|
||||
questionTypeComponent = (
|
||||
<TrueFalseQuestionDisplay
|
||||
question={question}
|
||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||
showAnswer={showAnswer}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'MC':
|
||||
questionTypeComponent = (
|
||||
<MultipleChoiceQuestionDisplay
|
||||
question={question}
|
||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||
showAnswer={showAnswer}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
case 'Numerical':
|
||||
if (question.choices) {
|
||||
if (!Array.isArray(question.choices)) {
|
||||
questionTypeComponent = (
|
||||
<NumericalQuestionDisplay
|
||||
question={question}
|
||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||
showAnswer={showAnswer}
|
||||
/>
|
||||
);
|
||||
} else {
|
||||
questionTypeComponent = ( // TODO fix NumericalQuestion (correctAnswers is borked)
|
||||
<NumericalQuestionDisplay
|
||||
question={question}
|
||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||
showAnswer={showAnswer}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'Short':
|
||||
questionTypeComponent = (
|
||||
<ShortAnswerQuestionDisplay
|
||||
question={question}
|
||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||
showAnswer={showAnswer}
|
||||
/>
|
||||
);
|
||||
break;
|
||||
}
|
||||
return (
|
||||
<div className="question-container">
|
||||
{questionTypeComponent ? (
|
||||
<>
|
||||
{questionTypeComponent}
|
||||
</>
|
||||
) : (
|
||||
<div>Question de type inconnue</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default QuestionDisplay;
|
||||
|
|
@ -1,59 +1,49 @@
|
|||
// ShortAnswerQuestion.tsx
|
||||
import React, { useState } from 'react';
|
||||
import '../questionStyle.css';
|
||||
import { Button, TextField } from '@mui/material';
|
||||
import textType from '../../GiftTemplate/templates/TextType';
|
||||
import { TextFormat } from '../../GiftTemplate/templates/types';
|
||||
import DOMPurify from 'dompurify';
|
||||
|
||||
type Choices = {
|
||||
feedback: { format: string; text: string } | null;
|
||||
isCorrect: boolean;
|
||||
text: { format: string; text: string };
|
||||
weigth?: number;
|
||||
id: string;
|
||||
};
|
||||
import { FormattedTextTemplate } from '../../GiftTemplate/templates/TextTypeTemplate';
|
||||
import { ShortAnswerQuestion } from 'gift-pegjs';
|
||||
|
||||
interface Props {
|
||||
questionContent: TextFormat;
|
||||
choices: Choices[];
|
||||
globalFeedback?: string | undefined;
|
||||
question: ShortAnswerQuestion;
|
||||
handleOnSubmitAnswer?: (answer: string) => void;
|
||||
showAnswer?: boolean;
|
||||
}
|
||||
|
||||
const ShortAnswerQuestion: React.FC<Props> = (props) => {
|
||||
const { questionContent, choices, showAnswer, handleOnSubmitAnswer, globalFeedback } = props;
|
||||
const ShortAnswerQuestionDisplay: React.FC<Props> = (props) => {
|
||||
const { question, showAnswer, handleOnSubmitAnswer } = props;
|
||||
const [answer, setAnswer] = useState<string>();
|
||||
|
||||
return (
|
||||
<div className="question-wrapper">
|
||||
<div className="question content">
|
||||
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({text: questionContent})) }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
|
||||
</div>
|
||||
{showAnswer ? (
|
||||
<>
|
||||
<div className="correct-answer-text mb-1">
|
||||
{choices.map((choice) => (
|
||||
<div key={choice.id} className="mb-1">
|
||||
{choice.text.text}
|
||||
{question.choices.map((choice) => (
|
||||
<div key={choice.text} className="mb-1">
|
||||
{choice.text}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{globalFeedback && <div className="global-feedback mb-2">{globalFeedback}</div>}
|
||||
{question.formattedGlobalFeedback && <div className="global-feedback mb-2">
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
|
||||
</div>}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<div className="answer-wrapper mb-1">
|
||||
<TextField
|
||||
type="text"
|
||||
id={questionContent.text}
|
||||
name={questionContent.text}
|
||||
id={question.formattedStem.text}
|
||||
name={question.formattedStem.text}
|
||||
onChange={(e) => {
|
||||
setAnswer(e.target.value);
|
||||
}}
|
||||
disabled={showAnswer}
|
||||
inputProps={{ 'data-testid': 'text-input' }}
|
||||
aria-label="short-answer-input"
|
||||
/>
|
||||
</div>
|
||||
{handleOnSubmitAnswer && (
|
||||
|
|
@ -75,4 +65,4 @@ const ShortAnswerQuestion: React.FC<Props> = (props) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default ShortAnswerQuestion;
|
||||
export default ShortAnswerQuestionDisplay;
|
||||
|
|
@ -2,33 +2,31 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import '../questionStyle.css';
|
||||
import { Button } from '@mui/material';
|
||||
import textType from '../../GiftTemplate/templates/TextType';
|
||||
import { TextFormat } from '../../GiftTemplate/templates/types';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { TrueFalseQuestion } from 'gift-pegjs';
|
||||
import { FormattedTextTemplate } from 'src/components/GiftTemplate/templates/TextTypeTemplate';
|
||||
|
||||
interface Props {
|
||||
questionContent: TextFormat;
|
||||
correctAnswer: boolean;
|
||||
globalFeedback?: string | undefined;
|
||||
question: TrueFalseQuestion;
|
||||
handleOnSubmitAnswer?: (answer: boolean) => void;
|
||||
showAnswer?: boolean;
|
||||
}
|
||||
|
||||
const TrueFalseQuestion: React.FC<Props> = (props) => {
|
||||
const { questionContent, correctAnswer, showAnswer, handleOnSubmitAnswer, globalFeedback } =
|
||||
const TrueFalseQuestionDisplay: React.FC<Props> = (props) => {
|
||||
const { question, showAnswer, handleOnSubmitAnswer } =
|
||||
props;
|
||||
const [answer, setAnswer] = useState<boolean | undefined>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
setAnswer(undefined);
|
||||
}, [questionContent]);
|
||||
}, [question]);
|
||||
|
||||
const selectedTrue = answer ? 'selected' : '';
|
||||
const selectedFalse = answer !== undefined && !answer ? 'selected' : '';
|
||||
const correctAnswer = question.isTrue === answer;
|
||||
return (
|
||||
<div className="question-container">
|
||||
<div className="question content">
|
||||
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(textType({ text: questionContent })) }} />
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedStem) }} />
|
||||
</div>
|
||||
<div className="choices-wrapper mb-1">
|
||||
<Button
|
||||
|
|
@ -50,8 +48,22 @@ const TrueFalseQuestion: React.FC<Props> = (props) => {
|
|||
<div className={`answer-text ${selectedFalse}`}>Faux</div>
|
||||
</Button>
|
||||
</div>
|
||||
{globalFeedback && showAnswer && (
|
||||
<div className="global-feedback mb-2">{globalFeedback}</div>
|
||||
{/* selected TRUE, show True feedback if it exists */}
|
||||
{showAnswer && answer && question.trueFormattedFeedback && (
|
||||
<div className="true-feedback mb-2">
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.trueFormattedFeedback) }} />
|
||||
</div>
|
||||
)}
|
||||
{/* selected FALSE, show False feedback if it exists */}
|
||||
{showAnswer && !answer && question.falseFormattedFeedback && (
|
||||
<div className="false-feedback mb-2">
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.falseFormattedFeedback) }} />
|
||||
</div>
|
||||
)}
|
||||
{question.formattedGlobalFeedback && showAnswer && (
|
||||
<div className="global-feedback mb-2">
|
||||
<div dangerouslySetInnerHTML={{ __html: FormattedTextTemplate(question.formattedGlobalFeedback) }} />
|
||||
</div>
|
||||
)}
|
||||
{!showAnswer && handleOnSubmitAnswer && (
|
||||
<Button
|
||||
|
|
@ -68,4 +80,4 @@ const TrueFalseQuestion: React.FC<Props> = (props) => {
|
|||
);
|
||||
};
|
||||
|
||||
export default TrueFalseQuestion;
|
||||
export default TrueFalseQuestionDisplay;
|
||||
|
|
@ -76,6 +76,39 @@
|
|||
box-shadow: 0 0 1px #3a3a3a;
|
||||
}
|
||||
|
||||
.answer-weight-container {
|
||||
display: flow;
|
||||
/* float: left; */
|
||||
box-shadow: 0px 1px 1px #3a3a3a;
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
}
|
||||
|
||||
.numerical-answer-weight-container {
|
||||
display: inline;
|
||||
box-shadow: 0px 1px 1px #3a3a3a;
|
||||
border-radius: 3px;
|
||||
padding-left: 0.2rem;
|
||||
padding-right: 0.2rem;
|
||||
padding-top: 0.05rem;
|
||||
padding-bottom: 0.05rem;
|
||||
}
|
||||
|
||||
.answer-positive-weight {
|
||||
background-color: hsl(
|
||||
120, 100%, 90%
|
||||
);
|
||||
}
|
||||
|
||||
.answer-zero-or-less-weight {
|
||||
background-color: hsl(
|
||||
0, 100%, 90%
|
||||
);
|
||||
}
|
||||
|
||||
.answer-text.selected {
|
||||
background-color: var(--main-color);
|
||||
color: white;
|
||||
|
|
@ -88,8 +121,23 @@
|
|||
|
||||
.feedback-container {
|
||||
margin-left: 1.1rem;
|
||||
display: inline-flex !important; /* override the parent */
|
||||
align-items: center;
|
||||
position: relative;
|
||||
padding: 0 0.5rem;
|
||||
background-color: hsl(43, 100%, 94%);
|
||||
color: hsl(43, 95%, 9%);
|
||||
border: hsl(36, 84%, 93%) 1px solid;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0px 2px 5px hsl(0, 0%, 74%);
|
||||
}
|
||||
|
||||
.feedback-container img {
|
||||
vertical-align: middle;
|
||||
/* height: 1em; */
|
||||
}
|
||||
|
||||
|
||||
.global-feedback {
|
||||
position: relative;
|
||||
padding: 0 1rem;
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
// StudentModeQuiz.tsx
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import QuestionComponent from '../Questions/Question';
|
||||
import QuestionComponent from '../QuestionsDisplay/QuestionDisplay';
|
||||
import '../../pages/Student/JoinRoom/joinRoom.css';
|
||||
import { QuestionType } from '../../Types/QuestionType';
|
||||
// import { QuestionService } from '../../services/QuestionService';
|
||||
|
|
@ -8,6 +8,7 @@ import { Button } from '@mui/material';
|
|||
//import QuestionNavigation from '../QuestionNavigation/QuestionNavigation';
|
||||
//import { ChevronLeft, ChevronRight } from '@mui/icons-material';
|
||||
import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
|
||||
import { Question } from 'gift-pegjs';
|
||||
|
||||
interface StudentModeQuizProps {
|
||||
questions: QuestionType[];
|
||||
|
|
@ -63,7 +64,7 @@ const StudentModeQuiz: React.FC<StudentModeQuizProps> = ({
|
|||
</div>
|
||||
<QuestionComponent
|
||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||
question={questionInfos.question}
|
||||
question={questionInfos.question as Question}
|
||||
showAnswer={isAnswerSubmitted}
|
||||
/>
|
||||
<div className="center-h-align mt-1/2">
|
||||
|
|
|
|||
|
|
@ -1,13 +1,14 @@
|
|||
// TeacherModeQuiz.tsx
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import QuestionComponent from '../Questions/Question';
|
||||
import QuestionComponent from '../QuestionsDisplay/QuestionDisplay';
|
||||
|
||||
import '../../pages/Student/JoinRoom/joinRoom.css';
|
||||
import { QuestionType } from '../../Types/QuestionType';
|
||||
// import { QuestionService } from '../../services/QuestionService';
|
||||
import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
|
||||
import { Dialog, DialogTitle, DialogContent, DialogActions, Button } from '@mui/material';
|
||||
import { Question } from 'gift-pegjs';
|
||||
|
||||
interface TeacherModeQuizProps {
|
||||
questionInfos: QuestionType;
|
||||
|
|
@ -63,7 +64,7 @@ const TeacherModeQuiz: React.FC<TeacherModeQuizProps> = ({
|
|||
) : (
|
||||
<QuestionComponent
|
||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||
question={questionInfos.question}
|
||||
question={questionInfos.question as Question}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
@ -76,7 +77,7 @@ const TeacherModeQuiz: React.FC<TeacherModeQuizProps> = ({
|
|||
{feedbackMessage}
|
||||
<QuestionComponent
|
||||
handleOnSubmitAnswer={handleOnSubmitAnswer}
|
||||
question={questionInfos.question}
|
||||
question={questionInfos.question as Question}
|
||||
showAnswer={true}
|
||||
/>
|
||||
</DialogContent>
|
||||
|
|
|
|||
16
client/src/markedConfig.ts
Normal file
16
client/src/markedConfig.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
import { marked, Renderer } from 'marked';
|
||||
|
||||
// Customized renderer to support image width and height
|
||||
// see https://github.com/markedjs/marked/issues/339#issuecomment-1146363560
|
||||
const renderer = new Renderer();
|
||||
|
||||
renderer.image = ({href, title, text}) => {
|
||||
const [width, height] = title?.startsWith('=') ? title.slice(1).split('x').map(v => v.trim()).filter(Boolean) : [];
|
||||
return `<img src="${href}" alt="${text}"${width ? ` width="${width}"` : ''}${height ? ` height="${height}"` : ''}>`;
|
||||
}
|
||||
|
||||
marked.use({
|
||||
renderer: renderer
|
||||
});
|
||||
|
||||
export default marked;
|
||||
|
|
@ -101,12 +101,12 @@ const Dashboard: React.FC = () => {
|
|||
|
||||
if (selectedFolderId == '') {
|
||||
const folders = await ApiService.getUserFolders(); // HACK force user folders to load on first load
|
||||
console.log("show all quizes")
|
||||
//console.log("show all quizzes")
|
||||
let quizzes: QuizType[] = [];
|
||||
|
||||
for (const folder of folders as FolderType[]) {
|
||||
const folderQuizzes = await ApiService.getFolderContent(folder._id);
|
||||
console.log("folder: ", folder.title, " quiz: ", folderQuizzes);
|
||||
//console.log("folder: ", folder.title, " quiz: ", folderQuizzes);
|
||||
// add the folder.title to the QuizType if the folderQuizzes is an array
|
||||
addFolderTitleToQuizzes(folderQuizzes, folder.title);
|
||||
quizzes = quizzes.concat(folderQuizzes as QuizType[])
|
||||
|
|
@ -294,15 +294,25 @@ const Dashboard: React.FC = () => {
|
|||
try {
|
||||
// folderId: string GET THIS FROM CURRENT FOLDER
|
||||
// currentTitle: string GET THIS FROM CURRENT FOLDER
|
||||
const newTitle = prompt('Entrée le nouveau nom du fichier', "Nouveau nom de dossier");
|
||||
const newTitle = prompt('Entrée le nouveau nom du fichier', folders.find((folder) => folder._id === selectedFolderId)?.title);
|
||||
if (newTitle) {
|
||||
await ApiService.renameFolder(selectedFolderId, newTitle);
|
||||
const renamedFolderId = selectedFolderId;
|
||||
const result = await ApiService.renameFolder(selectedFolderId, newTitle);
|
||||
|
||||
if (result !== true ) {
|
||||
window.alert(`Une erreur est survenue: ${result}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const userFolders = await ApiService.getUserFolders();
|
||||
setFolders(userFolders as FolderType[]);
|
||||
|
||||
// refresh the page
|
||||
setSelectedFolderId('');
|
||||
setSelectedFolderId(renamedFolderId);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error renaming folder:', error);
|
||||
alert('Erreur lors du renommage du dossier: ' + error);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
import React, { useEffect, useState } from 'react';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { Socket } from 'socket.io-client';
|
||||
import { GIFTQuestion, parse } from 'gift-pegjs';
|
||||
import { QuestionType } from '../../../Types/QuestionType';
|
||||
import { ParsedGIFTQuestion, BaseQuestion, parse, Question } from 'gift-pegjs';
|
||||
import { isSimpleNumericalAnswer, isRangeNumericalAnswer, isHighLowNumericalAnswer } from "gift-pegjs/typeGuards";
|
||||
import LiveResultsComponent from 'src/components/LiveResults/LiveResults';
|
||||
// import { QuestionService } from '../../../services/QuestionService';
|
||||
import webSocketService, { AnswerReceptionFromBackendType } from '../../../services/WebsocketService';
|
||||
|
|
@ -18,8 +18,9 @@ import { Refresh, Error } from '@mui/icons-material';
|
|||
import StudentWaitPage from 'src/components/StudentWaitPage/StudentWaitPage';
|
||||
import DisconnectButton from 'src/components/DisconnectButton/DisconnectButton';
|
||||
//import QuestionNavigation from 'src/components/QuestionNavigation/QuestionNavigation';
|
||||
import Question from 'src/components/Questions/Question';
|
||||
import QuestionDisplay from 'src/components/QuestionsDisplay/QuestionDisplay';
|
||||
import ApiService from '../../../services/ApiService';
|
||||
import { QuestionType } from 'src/Types/QuestionType';
|
||||
|
||||
const ManageRoom: React.FC = () => {
|
||||
const navigate = useNavigate();
|
||||
|
|
@ -277,7 +278,7 @@ const ManageRoom: React.FC = () => {
|
|||
const parsedQuestions = [] as QuestionType[];
|
||||
|
||||
quizQuestionArray.forEach((question, index) => {
|
||||
parsedQuestions.push({ question: parse(question)[0] });
|
||||
parsedQuestions.push({ question: parse(question)[0] as BaseQuestion });
|
||||
parsedQuestions[index].question.id = (index + 1).toString();
|
||||
});
|
||||
if (parsedQuestions.length === 0) return null;
|
||||
|
|
@ -347,7 +348,7 @@ const ManageRoom: React.FC = () => {
|
|||
|
||||
const answerText = answer.toString();
|
||||
if (questionInfo) {
|
||||
const question = questionInfo.question as GIFTQuestion;
|
||||
const question = questionInfo.question as ParsedGIFTQuestion;
|
||||
if (question.type === 'TF') {
|
||||
return (
|
||||
(question.isTrue && answerText == 'true') ||
|
||||
|
|
@ -355,53 +356,39 @@ const ManageRoom: React.FC = () => {
|
|||
);
|
||||
} else if (question.type === 'MC') {
|
||||
return question.choices.some(
|
||||
(choice) => choice.isCorrect && choice.text.text === answerText
|
||||
(choice) => choice.isCorrect && choice.formattedText.text === answerText
|
||||
);
|
||||
} else if (question.type === 'Numerical') {
|
||||
if (question.choices && !Array.isArray(question.choices)) {
|
||||
if (
|
||||
question.choices.type === 'high-low' &&
|
||||
question.choices.numberHigh &&
|
||||
question.choices.numberLow
|
||||
) {
|
||||
const answerNumber = parseFloat(answerText);
|
||||
if (!isNaN(answerNumber)) {
|
||||
return (
|
||||
answerNumber <= question.choices.numberHigh &&
|
||||
answerNumber >= question.choices.numberLow
|
||||
);
|
||||
}
|
||||
if (isHighLowNumericalAnswer(question.choices[0])) {
|
||||
const choice = question.choices[0];
|
||||
const answerNumber = parseFloat(answerText);
|
||||
if (!isNaN(answerNumber)) {
|
||||
return (
|
||||
answerNumber <= choice.numberHigh &&
|
||||
answerNumber >= choice.numberLow
|
||||
);
|
||||
}
|
||||
}
|
||||
if (question.choices && Array.isArray(question.choices)) {
|
||||
if (
|
||||
question.choices[0].text.type === 'range' &&
|
||||
question.choices[0].text.number &&
|
||||
question.choices[0].text.range
|
||||
) {
|
||||
const answerNumber = parseFloat(answerText);
|
||||
const range = question.choices[0].text.range;
|
||||
const correctAnswer = question.choices[0].text.number;
|
||||
if (!isNaN(answerNumber)) {
|
||||
return (
|
||||
answerNumber <= correctAnswer + range &&
|
||||
answerNumber >= correctAnswer - range
|
||||
);
|
||||
}
|
||||
if (isRangeNumericalAnswer(question.choices[0])) {
|
||||
const answerNumber = parseFloat(answerText);
|
||||
const range = question.choices[0].range;
|
||||
const correctAnswer = question.choices[0].number;
|
||||
if (!isNaN(answerNumber)) {
|
||||
return (
|
||||
answerNumber <= correctAnswer + range &&
|
||||
answerNumber >= correctAnswer - range
|
||||
);
|
||||
}
|
||||
if (
|
||||
question.choices[0].text.type === 'simple' &&
|
||||
question.choices[0].text.number
|
||||
) {
|
||||
const answerNumber = parseFloat(answerText);
|
||||
if (!isNaN(answerNumber)) {
|
||||
return answerNumber === question.choices[0].text.number;
|
||||
}
|
||||
}
|
||||
if (isSimpleNumericalAnswer(question.choices[0])) {
|
||||
const answerNumber = parseFloat(answerText);
|
||||
if (!isNaN(answerNumber)) {
|
||||
return answerNumber === question.choices[0].number;
|
||||
}
|
||||
}
|
||||
} else if (question.type === 'Short') {
|
||||
return question.choices.some(
|
||||
(choice) => choice.text.text.toUpperCase() === answerText.toUpperCase()
|
||||
(choice) => choice.text.toUpperCase() === answerText.toUpperCase()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -474,9 +461,9 @@ const ManageRoom: React.FC = () => {
|
|||
<div className="preview-and-result-container">
|
||||
|
||||
{currentQuestion && (
|
||||
<Question
|
||||
<QuestionDisplay
|
||||
showAnswer={false}
|
||||
question={currentQuestion?.question}
|
||||
question={currentQuestion?.question as Question}
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -419,7 +419,7 @@ class ApiService {
|
|||
*/
|
||||
public async renameFolder(folderId: string, newTitle: string): Promise<ApiResponse> {
|
||||
try {
|
||||
|
||||
console.log(`rename folder: folderId: ${folderId}, newTitle: ${newTitle}`);
|
||||
if (!folderId || !newTitle) {
|
||||
throw new Error(`Le folderId et le nouveau titre sont requis.`);
|
||||
}
|
||||
|
|
@ -428,7 +428,9 @@ class ApiService {
|
|||
const headers = this.constructRequestHeaders();
|
||||
const body = { folderId, newTitle };
|
||||
|
||||
const result: AxiosResponse = await axios.put(url, body, { headers: headers });
|
||||
const result = await axios.put(url, body, { headers: headers });
|
||||
|
||||
console.log(`rename folder: result: ${result.status}, ${result.data}`);
|
||||
if (result.status !== 200) {
|
||||
throw new Error(`Le changement de nom de dossier a échoué. Status: ${result.status}`);
|
||||
}
|
||||
|
|
@ -440,6 +442,7 @@ class ApiService {
|
|||
|
||||
if (axios.isAxiosError(error)) {
|
||||
const err = error as AxiosError;
|
||||
console.log(JSON.stringify(err));
|
||||
const data = err.response?.data as { error: string } | undefined;
|
||||
return data?.error || 'Erreur serveur inconnue lors de la requête.';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ class WebSocketService {
|
|||
private socket: Socket | null = null;
|
||||
|
||||
connect(backendUrl: string): Socket {
|
||||
console.log(`WebSocketService.connect('${backendUrl}')`);
|
||||
// console.log(`WebSocketService.connect('${backendUrl}')`);
|
||||
|
||||
// // Ensure the URL uses wss: if the URL starts with https:
|
||||
// const protocol = backendUrl.startsWith('https:') ? 'wss:' : 'ws:';
|
||||
|
|
|
|||
|
|
@ -2,14 +2,19 @@
|
|||
"compilerOptions": {
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"src/*": ["src/*"]
|
||||
"src/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"target": "ESNext",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"lib": [
|
||||
"ES2020",
|
||||
"DOM",
|
||||
"DOM.Iterable"
|
||||
],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
|
|
@ -17,7 +22,6 @@
|
|||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
|
|
@ -25,6 +29,16 @@
|
|||
"noFallthroughCasesInSwitch": true,
|
||||
"esModuleInterop": true
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": [{ "path": "./tsconfig.node.json" }]
|
||||
// "exclude": [
|
||||
// // "src/components/GiftTemplate/**/*",
|
||||
// // "src/__tests__/components/GiftTemplate/**/*",
|
||||
// ],
|
||||
"include": [
|
||||
"src"
|
||||
],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.node.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -197,32 +197,52 @@ describe('Folders', () => {
|
|||
it('should rename a folder and return true', async () => {
|
||||
const folderId = '60c72b2f9b1d8b3a4c8e4d3b';
|
||||
const newTitle = 'New Folder Name';
|
||||
const userId = '12345';
|
||||
|
||||
// Mock the database response
|
||||
collection.updateOne.mockResolvedValue({ modifiedCount: 1 });
|
||||
|
||||
const result = await folders.rename(folderId, newTitle);
|
||||
const result = await folders.rename(folderId, userId, newTitle);
|
||||
|
||||
expect(db.connect).toHaveBeenCalled();
|
||||
expect(db.collection).toHaveBeenCalledWith('folders');
|
||||
expect(collection.updateOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }, { $set: { title: newTitle } });
|
||||
// { _id: ObjectId.createFromHexString(folderId), userId: userId }, { $set: { title: newTitle } }
|
||||
expect(collection.updateOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId), userId: userId }, { $set: { title: newTitle } });
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false if the folder does not exist', async () => {
|
||||
const folderId = '60c72b2f9b1d8b3a4c8e4d3b';
|
||||
const newTitle = 'New Folder Name';
|
||||
const userId = '12345';
|
||||
|
||||
// Mock the database response
|
||||
collection.updateOne.mockResolvedValue({ modifiedCount: 0 });
|
||||
|
||||
const result = await folders.rename(folderId, newTitle);
|
||||
const result = await folders.rename(folderId, userId, newTitle);
|
||||
|
||||
expect(db.connect).toHaveBeenCalled();
|
||||
expect(db.collection).toHaveBeenCalledWith('folders');
|
||||
expect(collection.updateOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }, { $set: { title: newTitle } });
|
||||
expect(collection.updateOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId), userId: userId }, { $set: { title: newTitle } });
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should throw an error if the new title is already in use', async () => {
|
||||
const folderId = '60c72b2f9b1d8b3a4c8e4d3b';
|
||||
const newTitle = 'Existing Folder';
|
||||
const userId = '12345';
|
||||
|
||||
// Mock the database response
|
||||
collection.findOne.mockResolvedValue({ title: newTitle });
|
||||
collection.updateOne.mockResolvedValue({ modifiedCount: 0 });
|
||||
|
||||
await expect(folders.rename(folderId, userId, newTitle)).rejects.toThrow(`Folder with name '${newTitle}' already exists.`);
|
||||
|
||||
expect(db.connect).toHaveBeenCalled();
|
||||
expect(db.collection).toHaveBeenCalledWith('folders');
|
||||
// expect(collection.updateOne).toHaveBeenCalledWith({ _id: new ObjectId(folderId) }, { $set: { title: newTitle } });
|
||||
expect(collection.findOne).toHaveBeenCalledWith({ userId: userId, title: newTitle });
|
||||
});
|
||||
});
|
||||
|
||||
// duplicate
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ exports.UPDATE_PASSWORD_ERROR = {
|
|||
code: 400
|
||||
}
|
||||
exports.DELETE_USER_ERROR = {
|
||||
message: 'Une erreur s\'est produite lors de supression de l\'utilisateur.',
|
||||
message: 'Une erreur s\'est produite lors de suppression de l\'utilisateur.',
|
||||
code: 400
|
||||
}
|
||||
|
||||
|
|
@ -43,15 +43,15 @@ exports.QUIZ_NOT_FOUND = {
|
|||
code: 404
|
||||
}
|
||||
exports.QUIZ_ALREADY_EXISTS = {
|
||||
message: 'Le quiz existe déja.',
|
||||
message: 'Le quiz existe déjà.',
|
||||
code: 400
|
||||
}
|
||||
exports.UPDATE_QUIZ_ERROR = {
|
||||
message: 'Une erreur s\'est produite lors de la mise à jours du quiz.',
|
||||
message: 'Une erreur s\'est produite lors de la mise à jour du quiz.',
|
||||
code: 400
|
||||
}
|
||||
exports.DELETE_QUIZ_ERROR = {
|
||||
message: 'Une erreur s\'est produite lors de la supression du quiz.',
|
||||
message: 'Une erreur s\'est produite lors de la suppression du quiz.',
|
||||
code: 400
|
||||
}
|
||||
exports.GETTING_QUIZ_ERROR = {
|
||||
|
|
@ -76,15 +76,15 @@ exports.FOLDER_NOT_FOUND = {
|
|||
code: 404
|
||||
}
|
||||
exports.FOLDER_ALREADY_EXISTS = {
|
||||
message: 'Le dossier existe déja.',
|
||||
code: 400
|
||||
message: 'Le dossier existe déjà.',
|
||||
code: 409
|
||||
}
|
||||
exports.UPDATE_FOLDER_ERROR = {
|
||||
message: 'Une erreur s\'est produite lors de la mise à jours du dossier.',
|
||||
message: 'Une erreur s\'est produite lors de la mise à jour du dossier.',
|
||||
code: 400
|
||||
}
|
||||
exports.DELETE_FOLDER_ERROR = {
|
||||
message: 'Une erreur s\'est produite lors de la supression du dossier.',
|
||||
message: 'Une erreur s\'est produite lors de la suppression du dossier.',
|
||||
code: 400
|
||||
}
|
||||
exports.GETTING_FOLDER_ERROR = {
|
||||
|
|
|
|||
|
|
@ -126,8 +126,15 @@ class FoldersController {
|
|||
if (owner != req.user.userId) {
|
||||
throw new AppError(FOLDER_NOT_FOUND);
|
||||
}
|
||||
|
||||
const result = await this.folders.rename(folderId, newTitle);
|
||||
|
||||
// Is this the new title already taken by another folder that I own?
|
||||
const exists = await this.folders.folderExists(newTitle, req.user.userId);
|
||||
|
||||
if (exists) {
|
||||
throw new AppError(FOLDER_ALREADY_EXISTS);
|
||||
}
|
||||
|
||||
const result = await this.folders.rename(folderId, req.user.userId, newTitle);
|
||||
|
||||
if (!result) {
|
||||
throw new AppError(UPDATE_FOLDER_ERROR);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,8 @@ class AppError extends Error {
|
|||
constructor (errorCode) {
|
||||
super(errorCode.message)
|
||||
this.statusCode = errorCode.code;
|
||||
this.isOperational = true; // Optional: to distinguish operational errors from programming errors
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AppError;
|
||||
module.exports = AppError;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
const AppError = require("./AppError");
|
||||
const fs = require('fs');
|
||||
|
||||
const errorHandler = (error, req, res) => {
|
||||
console.log("ERROR", error);
|
||||
const errorHandler = (error, req, res, _next) => {
|
||||
|
||||
if (error instanceof AppError) {
|
||||
logError(error);
|
||||
|
|
|
|||
|
|
@ -10,8 +10,6 @@ class Folders {
|
|||
|
||||
async create(title, userId) {
|
||||
|
||||
console.log("LOG: create", title, userId);
|
||||
|
||||
if (!title || !userId) {
|
||||
throw new Error('Missing required parameter(s)');
|
||||
}
|
||||
|
|
@ -86,13 +84,18 @@ class Folders {
|
|||
return true;
|
||||
}
|
||||
|
||||
async rename(folderId, newTitle) {
|
||||
async rename(folderId, userId, newTitle) {
|
||||
await this.db.connect()
|
||||
const conn = this.db.getConnection();
|
||||
|
||||
const foldersCollection = conn.collection('folders');
|
||||
|
||||
const result = await foldersCollection.updateOne({ _id: ObjectId.createFromHexString(folderId) }, { $set: { title: newTitle } })
|
||||
// see if a folder exists for this user with the new title
|
||||
const existingFolder = await foldersCollection.findOne({ title: newTitle, userId: userId });
|
||||
|
||||
if (existingFolder) throw new Error(`Folder with name '${newTitle}' already exists.`);
|
||||
|
||||
const result = await foldersCollection.updateOne({ _id: ObjectId.createFromHexString(folderId), userId: userId }, { $set: { title: newTitle } })
|
||||
|
||||
if (result.modifiedCount != 1) return false;
|
||||
|
||||
|
|
@ -100,7 +103,6 @@ class Folders {
|
|||
}
|
||||
|
||||
async duplicate(folderId, userId) {
|
||||
console.log("LOG: duplicate", folderId, userId);
|
||||
const conn = this.db.getConnection();
|
||||
const foldersCollection = conn.collection('folders');
|
||||
|
||||
|
|
@ -112,7 +114,7 @@ class Folders {
|
|||
const theUserId = userId;
|
||||
// Use the utility function to generate a unique title
|
||||
const newFolderTitle = await generateUniqueTitle(sourceFolder.title, async (title) => {
|
||||
console.log(`generateUniqueTitle(${title}): userId`, theUserId);
|
||||
// console.log(`generateUniqueTitle(${title}): userId`, theUserId);
|
||||
return await foldersCollection.findOne({ title: title, userId: theUserId });
|
||||
});
|
||||
|
||||
|
|
@ -124,9 +126,9 @@ class Folders {
|
|||
|
||||
// copy the quizzes from source folder to destination folder
|
||||
const content = await this.getContent(folderId);
|
||||
console.log("folders.duplicate: found content", content);
|
||||
// console.log("folders.duplicate: found content", content);
|
||||
for (const quiz of content) {
|
||||
console.log("folders.duplicate: creating quiz (copy)", quiz);
|
||||
// console.log("folders.duplicate: creating quiz (copy)", quiz);
|
||||
const result = await this.quizModel.create(quiz.title, quiz.content, newFolderId.toString(), userId);
|
||||
if (!result) {
|
||||
throw new Error('Failed to create duplicate quiz');
|
||||
|
|
@ -137,14 +139,12 @@ class Folders {
|
|||
}
|
||||
|
||||
async folderExists(title, userId) {
|
||||
console.log("LOG: folderExists", title, userId);
|
||||
await this.db.connect();
|
||||
const conn = this.db.getConnection();
|
||||
|
||||
const foldersCollection = conn.collection('folders');
|
||||
const existingFolder = await foldersCollection.findOne({ title: title, userId: userId });
|
||||
|
||||
return !!existingFolder;
|
||||
const existingFolder = await foldersCollection.findOne({ title: title, userId: userId });
|
||||
return existingFolder ? true : false;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ class Quiz {
|
|||
}
|
||||
|
||||
async create(title, content, folderId, userId) {
|
||||
console.log(`quizzes: create title: ${title}, folderId: ${folderId}, userId: ${userId}`);
|
||||
// console.log(`quizzes: create title: ${title}, folderId: ${folderId}, userId: ${userId}`);
|
||||
await this.db.connect()
|
||||
const conn = this.db.getConnection();
|
||||
|
||||
|
|
@ -31,7 +31,7 @@ class Quiz {
|
|||
}
|
||||
|
||||
const result = await quizCollection.insertOne(newQuiz);
|
||||
console.log("quizzes: create insertOne result", result);
|
||||
// console.log("quizzes: create insertOne result", result);
|
||||
|
||||
return result.insertedId;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
// utils.js
|
||||
async function generateUniqueTitle(baseTitle, existsCallback) {
|
||||
console.log(`generateUniqueTitle(${baseTitle})`);
|
||||
// console.log(`generateUniqueTitle(${baseTitle})`);
|
||||
let newTitle = baseTitle;
|
||||
let counter = 1;
|
||||
|
||||
|
|
@ -19,12 +19,12 @@ async function generateUniqueTitle(baseTitle, existsCallback) {
|
|||
newTitle = `${baseTitle} (${counter})`;
|
||||
}
|
||||
|
||||
console.log(`first check of newTitle: ${newTitle}`);
|
||||
// console.log(`first check of newTitle: ${newTitle}`);
|
||||
|
||||
while (await existsCallback(newTitle)) {
|
||||
counter++;
|
||||
newTitle = `${baseTitle} (${counter})`;
|
||||
console.log(`trying newTitle: ${newTitle}`);
|
||||
// console.log(`trying newTitle: ${newTitle}`);
|
||||
}
|
||||
|
||||
return newTitle;
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@
|
|||
"supertest": "^6.3.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": "18.x"
|
||||
"node": "20.x"
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
|
|
|
|||
|
|
@ -2,17 +2,17 @@ const express = require('express');
|
|||
const router = express.Router();
|
||||
const jwt = require('../middleware/jwtToken.js');
|
||||
const folders = require('../app.js').folders;
|
||||
const asyncHandler = require('./routerUtils.js');
|
||||
|
||||
router.post("/create", jwt.authenticate, folders.create);
|
||||
router.get("/getUserFolders", jwt.authenticate, folders.getUserFolders);
|
||||
router.get("/getFolderContent/:folderId", jwt.authenticate, folders.getFolderContent);
|
||||
router.delete("/delete/:folderId", jwt.authenticate, folders.delete);
|
||||
router.put("/rename", jwt.authenticate, folders.rename);
|
||||
router.post("/create", jwt.authenticate, asyncHandler(folders.create));
|
||||
router.get("/getUserFolders", jwt.authenticate, asyncHandler(folders.getUserFolders));
|
||||
router.get("/getFolderContent/:folderId", jwt.authenticate, asyncHandler(folders.getFolderContent));
|
||||
router.delete("/delete/:folderId", jwt.authenticate, asyncHandler(folders.delete));
|
||||
router.put("/rename", jwt.authenticate, asyncHandler(folders.rename));
|
||||
|
||||
//router.post("/duplicate", jwt.authenticate, foldersController.duplicate);
|
||||
router.post("/duplicate", jwt.authenticate, folders.duplicate);
|
||||
router.post("/duplicate", jwt.authenticate, asyncHandler(folders.duplicate));
|
||||
|
||||
router.post("/copy/:folderId", jwt.authenticate, folders.copy);
|
||||
router.post("/copy/:folderId", jwt.authenticate, asyncHandler(folders.copy));
|
||||
|
||||
module.exports = router;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const images = require('../app.js').images;
|
||||
const asyncHandler = require('./routerUtils.js');
|
||||
|
||||
const jwt = require('../middleware/jwtToken.js');
|
||||
|
||||
|
|
@ -9,7 +10,7 @@ const multer = require('multer');
|
|||
const storage = multer.memoryStorage();
|
||||
const upload = multer({ storage: storage });
|
||||
|
||||
router.post("/upload", jwt.authenticate, upload.single('image'), images.upload);
|
||||
router.get("/get/:id", images.get);
|
||||
router.post("/upload", jwt.authenticate, upload.single('image'), asyncHandler(images.upload));
|
||||
router.get("/get/:id", asyncHandler(images.get));
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
|||
|
|
@ -2,21 +2,22 @@ const express = require('express');
|
|||
const router = express.Router();
|
||||
const quizzes = require('../app.js').quizzes;
|
||||
const jwt = require('../middleware/jwtToken.js');
|
||||
const asyncHandler = require('./routerUtils.js');
|
||||
|
||||
if (!quizzes) {
|
||||
console.error("quizzes is not defined");
|
||||
}
|
||||
|
||||
router.post("/create", jwt.authenticate, quizzes.create);
|
||||
router.get("/get/:quizId", jwt.authenticate, quizzes.get);
|
||||
router.delete("/delete/:quizId", jwt.authenticate, quizzes.delete);
|
||||
router.put("/update", jwt.authenticate, quizzes.update);
|
||||
router.put("/move", jwt.authenticate, quizzes.move);
|
||||
router.post("/create", jwt.authenticate, asyncHandler(quizzes.create));
|
||||
router.get("/get/:quizId", jwt.authenticate, asyncHandler(asyncHandler(quizzes.get)));
|
||||
router.delete("/delete/:quizId", jwt.authenticate, asyncHandler(quizzes.delete));
|
||||
router.put("/update", jwt.authenticate, asyncHandler(quizzes.update));
|
||||
router.put("/move", jwt.authenticate, asyncHandler(quizzes.move));
|
||||
|
||||
router.post("/duplicate", jwt.authenticate, quizzes.duplicate);
|
||||
router.post("/copy/:quizId", jwt.authenticate, quizzes.copy);
|
||||
router.put("/Share", jwt.authenticate, quizzes.share);
|
||||
router.get("/getShare/:quizId", jwt.authenticate, quizzes.getShare);
|
||||
router.post("/receiveShare", jwt.authenticate, quizzes.receiveShare);
|
||||
router.post("/duplicate", jwt.authenticate, asyncHandler(quizzes.duplicate));
|
||||
router.post("/copy/:quizId", jwt.authenticate, asyncHandler(quizzes.copy));
|
||||
router.put("/Share", jwt.authenticate, asyncHandler(quizzes.share));
|
||||
router.get("/getShare/:quizId", jwt.authenticate, asyncHandler(quizzes.getShare));
|
||||
router.post("/receiveShare", jwt.authenticate, asyncHandler(quizzes.receiveShare));
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
|||
6
server/routers/routerUtils.js
Normal file
6
server/routers/routerUtils.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
// asyncHandler is a wrapper for async functions that catch errors and pass them to the next middleware
|
||||
const asyncHandler = fn => (req, res, next) => {
|
||||
Promise.resolve(fn(req, res, next)).catch(next);
|
||||
};
|
||||
|
||||
module.exports = asyncHandler;
|
||||
|
|
@ -2,11 +2,12 @@ const express = require('express');
|
|||
const router = express.Router();
|
||||
const users = require('../app.js').users;
|
||||
const jwt = require('../middleware/jwtToken.js');
|
||||
const asyncHandler = require('./routerUtils.js');
|
||||
|
||||
router.post("/register", users.register);
|
||||
router.post("/login", users.login);
|
||||
router.post("/reset-password", users.resetPassword);
|
||||
router.post("/change-password", jwt.authenticate, users.changePassword);
|
||||
router.post("/delete-user", jwt.authenticate, users.delete);
|
||||
router.post("/register", asyncHandler(users.register));
|
||||
router.post("/login", asyncHandler(users.login));
|
||||
router.post("/reset-password", asyncHandler(users.resetPassword));
|
||||
router.post("/change-password", jwt.authenticate, asyncHandler(users.changePassword));
|
||||
router.post("/delete-user", jwt.authenticate, asyncHandler(users.delete));
|
||||
|
||||
module.exports = router;
|
||||
|
|
|
|||
Loading…
Reference in a new issue