Merge branch 'main' of https://github.com/openedx/frontend-lib-content-components into mashal-m/react-upgrade-to-v17
This commit is contained in:
2
.github/workflows/lockfileversion-check.yml
vendored
2
.github/workflows/lockfileversion-check.yml
vendored
@@ -10,4 +10,4 @@ on:
|
||||
|
||||
jobs:
|
||||
version-check:
|
||||
uses: edx/.github/.github/workflows/lockfileversion-check-v3.yml@master
|
||||
uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master
|
||||
|
||||
52
package-lock.json
generated
52
package-lock.json
generated
@@ -14,6 +14,9 @@
|
||||
"@codemirror/lint": "^6.2.1",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/sortable": "^7.0.2",
|
||||
"@dnd-kit/utilities": "^3.2.1",
|
||||
"@edx/browserslist-config": "^1.1.1",
|
||||
"@reduxjs/toolkit": "^1.8.1",
|
||||
"@tinymce/tinymce-react": "^3.14.0",
|
||||
@@ -2159,6 +2162,55 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/accessibility": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.0.1.tgz",
|
||||
"integrity": "sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/core": {
|
||||
"version": "6.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.0.8.tgz",
|
||||
"integrity": "sha512-lYaoP8yHTQSLlZe6Rr9qogouGUz9oRUj4AHhDQGQzq/hqaJRpFo65X+JKsdHf8oUFBzx5A+SJPUvxAwTF2OabA==",
|
||||
"dependencies": {
|
||||
"@dnd-kit/accessibility": "^3.0.0",
|
||||
"@dnd-kit/utilities": "^3.2.1",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0",
|
||||
"react-dom": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/sortable": {
|
||||
"version": "7.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-7.0.2.tgz",
|
||||
"integrity": "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==",
|
||||
"dependencies": {
|
||||
"@dnd-kit/utilities": "^3.2.0",
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@dnd-kit/core": "^6.0.7",
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@dnd-kit/utilities": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.1.tgz",
|
||||
"integrity": "sha512-OOXqISfvBw/1REtkSK2N3Fi2EQiLMlWUlqnOK/UpOISqBZPWpE6TqL+jcPtMOkE8TqYGiURvRdPSI9hltNUjEA==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@edx/browserslist-config": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@edx/browserslist-config/-/browserslist-config-1.2.0.tgz",
|
||||
|
||||
@@ -67,6 +67,9 @@
|
||||
"@codemirror/lint": "^6.2.1",
|
||||
"@codemirror/state": "^6.0.0",
|
||||
"@codemirror/view": "^6.0.0",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@dnd-kit/sortable": "^7.0.2",
|
||||
"@dnd-kit/utilities": "^3.2.1",
|
||||
"@edx/browserslist-config": "^1.1.1",
|
||||
"@reduxjs/toolkit": "^1.8.1",
|
||||
"@tinymce/tinymce-react": "^3.14.0",
|
||||
|
||||
@@ -28,6 +28,18 @@ export const nonQuestionKeys = [
|
||||
'textline',
|
||||
];
|
||||
|
||||
export const richTextFormats = [
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'div',
|
||||
'p',
|
||||
'pre',
|
||||
];
|
||||
|
||||
export const responseKeys = [
|
||||
'multiplechoiceresponse',
|
||||
'numericalresponse',
|
||||
@@ -62,57 +74,52 @@ export const stripNonTextTags = ({ input, tag }) => {
|
||||
|
||||
export class OLXParser {
|
||||
constructor(olxString) {
|
||||
this.problem = {};
|
||||
this.questionData = {};
|
||||
this.richTextProblem = {};
|
||||
const richTextOptions = {
|
||||
// There are two versions of the parsed XLM because the fields using tinymce require the order
|
||||
// of the parsed data and spacing values to be preserved. However, all the other widgets need
|
||||
// the data grouped by the wrapping tag. Examples of the parsed format can be found here:
|
||||
// https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/docs/v4/2.XMLparseOptions.md
|
||||
const baseParserOptions = {
|
||||
ignoreAttributes: false,
|
||||
alwaysCreateTextNode: true,
|
||||
numberParseOptions: {
|
||||
leadingZeros: false,
|
||||
hex: false,
|
||||
},
|
||||
preserveOrder: true,
|
||||
processEntities: false,
|
||||
};
|
||||
|
||||
// Base Parser
|
||||
this.problem = {};
|
||||
const parserOptions = {
|
||||
ignoreAttributes: false,
|
||||
...baseParserOptions,
|
||||
alwaysCreateTextNode: true,
|
||||
numberParseOptions: {
|
||||
leadingZeros: false,
|
||||
hex: false,
|
||||
},
|
||||
processEntities: false,
|
||||
};
|
||||
const builderOptions = {
|
||||
ignoreAttributes: false,
|
||||
numberParseOptions: {
|
||||
leadingZeros: false,
|
||||
hex: false,
|
||||
},
|
||||
processEntities: false,
|
||||
...baseParserOptions,
|
||||
};
|
||||
const richTextBuilderOptions = {
|
||||
ignoreAttributes: false,
|
||||
numberParseOptions: {
|
||||
leadingZeros: false,
|
||||
hex: false,
|
||||
},
|
||||
preserveOrder: true,
|
||||
processEntities: false,
|
||||
};
|
||||
// There are two versions of the parsed XLM because the fields using tinymce require the order
|
||||
// of the parsed data to be preserved. However, all the other widgets need the data grouped by
|
||||
// the wrapping tag.
|
||||
const richTextParser = new XMLParser(richTextOptions);
|
||||
const parser = new XMLParser(parserOptions);
|
||||
this.builder = new XMLBuilder(builderOptions);
|
||||
this.richTextBuilder = new XMLBuilder(richTextBuilderOptions);
|
||||
this.parsedOLX = parser.parse(olxString);
|
||||
this.richTextOLX = richTextParser.parse(olxString);
|
||||
if (_.has(this.parsedOLX, 'problem')) {
|
||||
this.problem = this.parsedOLX.problem;
|
||||
this.questionData = this.richTextOLX[0].problem;
|
||||
}
|
||||
|
||||
// Parser with `preservedOrder: true` and `trimValues: false`
|
||||
this.richTextProblem = [];
|
||||
const richTextOptions = {
|
||||
...baseParserOptions,
|
||||
alwaysCreateTextNode: true,
|
||||
preserveOrder: true,
|
||||
trimValues: false,
|
||||
};
|
||||
const richTextBuilderOptions = {
|
||||
...baseParserOptions,
|
||||
preserveOrder: true,
|
||||
trimValues: false,
|
||||
};
|
||||
const richTextParser = new XMLParser(richTextOptions);
|
||||
this.richTextBuilder = new XMLBuilder(richTextBuilderOptions);
|
||||
this.richTextOLX = richTextParser.parse(olxString);
|
||||
if (_.has(this.parsedOLX, 'problem')) {
|
||||
this.richTextProblem = this.richTextOLX[0].problem;
|
||||
}
|
||||
}
|
||||
@@ -462,7 +469,7 @@ export class OLXParser {
|
||||
* @return {string} string of OLX
|
||||
*/
|
||||
parseQuestions(problemType) {
|
||||
const problemArray = _.get(this.questionData[0], problemType) || this.questionData;
|
||||
const problemArray = _.get(this.richTextProblem[0], problemType) || this.richTextProblem;
|
||||
|
||||
const questionArray = [];
|
||||
problemArray.forEach(tag => {
|
||||
@@ -478,7 +485,7 @@ export class OLXParser {
|
||||
*/
|
||||
tag[tagName].forEach(subTag => {
|
||||
const subTagName = Object.keys(subTag)[0];
|
||||
if (subTagName === 'label' || subTagName === 'description') {
|
||||
if (subTagName === 'label' || subTagName === 'description' || richTextFormats.includes(subTagName)) {
|
||||
questionArray.push(subTag);
|
||||
}
|
||||
});
|
||||
@@ -503,11 +510,13 @@ export class OLXParser {
|
||||
if (objKeys.includes('demandhint')) {
|
||||
const currentDemandHint = obj.demandhint;
|
||||
currentDemandHint.forEach(hint => {
|
||||
const hintValue = this.richTextBuilder.build(hint.hint);
|
||||
hintsObject.push({
|
||||
id: hintsObject.length,
|
||||
value: hintValue,
|
||||
});
|
||||
if (Object.keys(hint).includes('hint')) {
|
||||
const hintValue = this.richTextBuilder.build(hint.hint);
|
||||
hintsObject.push({
|
||||
id: hintsObject.length,
|
||||
value: hintValue,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -526,21 +535,17 @@ export class OLXParser {
|
||||
getSolutionExplanation(problemType) {
|
||||
if (!_.has(this.problem, `${problemType}.solution`) && !_.has(this.problem, 'solution')) { return null; }
|
||||
const [problemBody] = this.richTextProblem.filter(section => Object.keys(section).includes(problemType));
|
||||
let { solution } = problemBody[problemType].pop();
|
||||
const { div } = solution[0];
|
||||
if (solution.length === 1 && div) {
|
||||
div.forEach((block) => {
|
||||
const [key] = Object.keys(block);
|
||||
const [value] = block[key];
|
||||
if ((key === 'p' || key === 'h2')
|
||||
&& (_.get(value, '#text', null) === 'Explanation')
|
||||
) {
|
||||
div.shift();
|
||||
const [solutionBody] = problemBody[problemType].filter(section => Object.keys(section).includes('solution'));
|
||||
const [divBody] = solutionBody.solution.filter(section => Object.keys(section).includes('div'));
|
||||
const solutionArray = [];
|
||||
if (divBody && divBody.div) {
|
||||
divBody.div.forEach(tag => {
|
||||
if (_.get(Object.values(tag)[0][0], '#text', null) !== 'Explanation') {
|
||||
solutionArray.push(tag);
|
||||
}
|
||||
});
|
||||
solution = div;
|
||||
}
|
||||
const solutionString = this.richTextBuilder.build(solution);
|
||||
const solutionString = this.richTextBuilder.build(solutionArray);
|
||||
return solutionString;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { OLXParser } from './OLXParser';
|
||||
import {
|
||||
checkboxesOLXWithFeedbackAndHintsOLX,
|
||||
getCheckboxesOLXWithFeedbackAndHintsOLX,
|
||||
dropdownOLXWithFeedbackAndHintsOLX,
|
||||
numericInputWithFeedbackAndHintsOLX,
|
||||
textInputWithFeedbackAndHintsOLX,
|
||||
@@ -21,6 +20,7 @@ import {
|
||||
labelDescriptionQuestionOLX,
|
||||
htmlEntityTestOLX,
|
||||
numberParseTestOLX,
|
||||
solutionExplanationTest,
|
||||
} from './mockData/olxTestData';
|
||||
import { ProblemTypeKeys } from '../../../data/constants/problem';
|
||||
|
||||
@@ -261,13 +261,13 @@ describe('OLXParser', () => {
|
||||
const problemType = olxparser.getProblemType();
|
||||
const question = olxparser.parseQuestions(problemType);
|
||||
it('should return an empty string for question', () => {
|
||||
expect(question).toBe(blankQuestionOLX.question);
|
||||
expect(question.trim()).toBe(blankQuestionOLX.question);
|
||||
});
|
||||
});
|
||||
describe('given a simple problem olx', () => {
|
||||
const question = textInputOlxParser.parseQuestions('stringresponse');
|
||||
it('should return a string of HTML', () => {
|
||||
expect(question).toEqual(textInputWithFeedbackAndHintsOLX.question);
|
||||
expect(question.trim()).toEqual(textInputWithFeedbackAndHintsOLX.question);
|
||||
});
|
||||
});
|
||||
describe('given olx with html entities', () => {
|
||||
@@ -275,7 +275,7 @@ describe('OLXParser', () => {
|
||||
const problemType = olxparser.getProblemType();
|
||||
const question = olxparser.parseQuestions(problemType);
|
||||
it('should not encode html entities', () => {
|
||||
expect(question).toEqual(htmlEntityTestOLX.question);
|
||||
expect(question.trim()).toEqual(htmlEntityTestOLX.question);
|
||||
});
|
||||
});
|
||||
describe('given olx with styled content', () => {
|
||||
@@ -283,7 +283,7 @@ describe('OLXParser', () => {
|
||||
const problemType = olxparser.getProblemType();
|
||||
const question = olxparser.parseQuestions(problemType);
|
||||
it('should pase/build correct styling', () => {
|
||||
expect(question).toBe(styledQuestionOLX.question);
|
||||
expect(question.trim()).toBe(styledQuestionOLX.question);
|
||||
});
|
||||
});
|
||||
describe('given olx with label and description tags inside response tag', () => {
|
||||
@@ -291,20 +291,25 @@ describe('OLXParser', () => {
|
||||
const problemType = olxparser.getProblemType();
|
||||
const question = olxparser.parseQuestions(problemType);
|
||||
it('should append the label/description to the question', () => {
|
||||
expect(question).toBe(labelDescriptionQuestionOLX.question);
|
||||
expect(question.trim()).toBe(labelDescriptionQuestionOLX.question);
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('getSolutionExplanation()', () => {
|
||||
describe('for checkbox questions', () => {
|
||||
test('should parse text in p tags', () => {
|
||||
const { rawOLX } = getCheckboxesOLXWithFeedbackAndHintsOLX();
|
||||
const olxparser = new OLXParser(rawOLX);
|
||||
const olxparser = new OLXParser(checkboxesOLXWithFeedbackAndHintsOLX.rawOLX);
|
||||
const problemType = olxparser.getProblemType();
|
||||
const explanation = olxparser.getSolutionExplanation(problemType);
|
||||
const expected = getCheckboxesOLXWithFeedbackAndHintsOLX().solutionExplanation;
|
||||
const expected = checkboxesOLXWithFeedbackAndHintsOLX.solutionExplanation;
|
||||
expect(explanation.replace(/\s/g, '')).toBe(expected.replace(/\s/g, ''));
|
||||
});
|
||||
});
|
||||
it('should parse text with proper spacing', () => {
|
||||
const olxparser = new OLXParser(solutionExplanationTest.rawOLX);
|
||||
const problemType = olxparser.getProblemType();
|
||||
const explanation = olxparser.getSolutionExplanation(problemType);
|
||||
expect(explanation).toBe(solutionExplanationTest.solutionExplanation);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
export const getCheckboxesOLXWithFeedbackAndHintsOLX = () => ({
|
||||
/* eslint-disable */
|
||||
// lint is disabled for this file due to strict spacing
|
||||
|
||||
export const checkboxesOLXWithFeedbackAndHintsOLX = {
|
||||
rawOLX: `<problem>
|
||||
<choiceresponse>
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
|
||||
@@ -8,12 +11,12 @@ export const getCheckboxesOLXWithFeedbackAndHintsOLX = () => ({
|
||||
<choice correct="true"><p>a correct answer</p>
|
||||
<choicehint selected="true"><p>You can specify optional feedback that appears after the learner selects and submits this answer.</p></choicehint>
|
||||
<choicehint selected="false"><p>You can specify optional feedback that appears after the learner clears and submits this answer.</p></choicehint>
|
||||
</choice>
|
||||
</choice>
|
||||
<choice correct="false"><p>an incorrect answer</p></choice>
|
||||
<choice correct="false"><p>an incorrect answer</p>
|
||||
<choicehint selected="true"><p>You can specify optional feedback for none, all, or a subset of the answers.</p></choicehint>
|
||||
<choicehint selected="false"><p>You can specify optional feedback for selected answers, cleared answers, or both.</p></choicehint>
|
||||
</choice>
|
||||
</choice>
|
||||
<choice correct="true"><p>a correct answer</p></choice>
|
||||
<compoundhint value="A B D">You can specify optional feedback for a combination of answers which appears after the specified set of answers is submitted.</compoundhint>
|
||||
<compoundhint value="A B C D">You can specify optional feedback for one, several, or all answer combinations.</compoundhint>
|
||||
@@ -56,7 +59,7 @@ export const getCheckboxesOLXWithFeedbackAndHintsOLX = () => ({
|
||||
answers: [
|
||||
{
|
||||
id: 'A',
|
||||
title: '<p>a correct answer</p>',
|
||||
title: `<p>a correct answer</p>\n \n \n `,
|
||||
correct: true,
|
||||
selectedFeedback: '<p>You can specify optional feedback that appears after the learner selects and submits this answer.</p>',
|
||||
unselectedFeedback: '<p>You can specify optional feedback that appears after the learner clears and submits this answer.</p>',
|
||||
@@ -68,7 +71,7 @@ export const getCheckboxesOLXWithFeedbackAndHintsOLX = () => ({
|
||||
},
|
||||
{
|
||||
id: 'C',
|
||||
title: '<p>an incorrect answer</p>',
|
||||
title: `<p>an incorrect answer</p>\n \n \n `,
|
||||
correct: false,
|
||||
selectedFeedback: '<p>You can specify optional feedback for none, all, or a subset of the answers.</p>',
|
||||
unselectedFeedback: '<p>You can specify optional feedback for selected answers, cleared answers, or both.</p>',
|
||||
@@ -103,45 +106,43 @@ export const getCheckboxesOLXWithFeedbackAndHintsOLX = () => ({
|
||||
},
|
||||
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><label>Add the question text, or prompt, here. This text is required.</label><em>You can add an optional tip or note related to the prompt like this.</em>',
|
||||
buildOLX: `<problem>
|
||||
<choiceresponse>
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<em>You can add an optional tip or note related to the prompt like this.</em>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">
|
||||
<p>a correct answer </p> <choicehint selected="true"><p>You can specify optional feedback that appears after the learner selects and submits this answer.</p></choicehint>
|
||||
<choicehint selected="false"><p>You can specify optional feedback that appears after the learner clears and submits this answer.</p></choicehint>
|
||||
</choice>
|
||||
<choice correct="false"><p>an incorrect answer</p></choice>
|
||||
<choice correct="false">
|
||||
<p>an incorrect answer</p> <choicehint selected="true"><p>You can specify optional feedback for none, all, or a subset of the answers.</p></choicehint>
|
||||
<choicehint selected="false"><p>You can specify optional feedback for selected answers, cleared answers, or both.</p></choicehint>
|
||||
</choice>
|
||||
<choice correct="true"><p>a correct answer</p></choice>
|
||||
<compoundhint value="A B D">You can specify optional feedback for a combination of answers which appears after the specified set of answers is submitted.</compoundhint>
|
||||
<compoundhint value="A B C D">You can specify optional feedback for one, several, or all answer combinations.</compoundhint>
|
||||
</checkboxgroup>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>
|
||||
<choiceresponse>
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for checkboxes with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<em>You can add an optional tip or note related to the prompt like this.</em>
|
||||
<checkboxgroup>
|
||||
<choice correct="true">
|
||||
<p>a correct answer </p> <choicehint selected="true"><p>You can specify optional feedback that appears after the learner selects and submits this answer.</p></choicehint>
|
||||
<choicehint selected="false"><p>You can specify optional feedback that appears after the learner clears and submits this answer.</p></choicehint>
|
||||
</choice>
|
||||
<choice correct="false"><p>an incorrect answer</p></choice>
|
||||
<choice correct="false">
|
||||
<p>an incorrect answer</p> <choicehint selected="true"><p>You can specify optional feedback for none, all, or a subset of the answers.</p></choicehint>
|
||||
<choicehint selected="false"><p>You can specify optional feedback for selected answers, cleared answers, or both.</p></choicehint>
|
||||
</choice>
|
||||
<choice correct="true"><p>a correct answer</p></choice>
|
||||
<compoundhint value="A B D">You can specify optional feedback for a combination of answers which appears after the specified set of answers is submitted.</compoundhint>
|
||||
<compoundhint value="A B C D">You can specify optional feedback for one, several, or all answer combinations.</compoundhint>
|
||||
</checkboxgroup>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p>
|
||||
You can form a voltage divider that evenly divides the input
|
||||
voltage with two identically valued resistors, with the sampled
|
||||
voltage taken in between the two.
|
||||
</p>
|
||||
<p><img src="/static/images/voltage_divider.png" alt=""></img></p>
|
||||
</div>
|
||||
</solution>
|
||||
</choiceresponse>
|
||||
<demandhint>
|
||||
<hint><p>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</p></hint>
|
||||
<hint><p>If you add more than one hint, a different hint appears each time learners select the hint button.</p></hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
`,
|
||||
});
|
||||
|
||||
export const checkboxesOLXWithFeedbackAndHintsOLX = getCheckboxesOLXWithFeedbackAndHintsOLX({});
|
||||
</p>
|
||||
<p><img src="/static/images/voltage_divider.png" alt=""></img></p>
|
||||
</div>
|
||||
</solution>
|
||||
</choiceresponse>
|
||||
<demandhint>
|
||||
<hint><p>You can add an optional hint like this. Problems that have a hint include a hint button, and this text appears the first time learners select the button.</p></hint>
|
||||
<hint><p>If you add more than one hint, a different hint appears each time learners select the hint button.</p></hint>
|
||||
</demandhint>
|
||||
</problem>
|
||||
`,
|
||||
};
|
||||
|
||||
export const multipleChoiceWithoutAnswers = {
|
||||
rawOLX: `<problem>
|
||||
@@ -199,7 +200,7 @@ export const multipleChoiceSingleAnswer = {
|
||||
answers: [
|
||||
{
|
||||
id: 'A',
|
||||
title: '<p>a correct answer</p><div><img src="#"></img>image with<strong>caption</strong>.</div>',
|
||||
title: `<p>a correct answer</p><div><img src="#"></img>image with <strong>caption</strong>.</div>\n \n \n `,
|
||||
correct: true,
|
||||
selectedFeedback: '<p>You can specify optional feedback that appears after the learner selects and submits this answer.</p>',
|
||||
unselectedFeedback: '<p>You can specify optional feedback that appears after the learner clears and submits this answer.</p>',
|
||||
@@ -314,11 +315,9 @@ export const multipleChoiceWithFeedbackAndHintsOLX = {
|
||||
<label>Add the question text, or prompt, here. This text is required.</label>
|
||||
<description>You can add an optional tip or note related to the prompt like this. </description>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false"><p>an incorrect answer</p><choicehint><p>You can specify optional feedback like this, which appears after this answer is submitted.</p></choicehint>
|
||||
</choice>
|
||||
<choice correct="false"><p>an incorrect answer</p><choicehint><p>You can specify optional feedback like this, which appears after this answer is submitted.</p></choicehint></choice>
|
||||
<choice correct="true"><p>the correct answer</p></choice>
|
||||
<choice correct="false"><p>an incorrect answer</p><choicehint><p>You can specify optional feedback for none, a subset, or all of the answers.</></choicehint>
|
||||
</choice>
|
||||
<choice correct="false"><p>an incorrect answer</p><choicehint><p>You can specify optional feedback for none, a subset, or all of the answers.</></choicehint></choice>
|
||||
</choicegroup>
|
||||
<solution>
|
||||
<p>You can add a solution</p>
|
||||
@@ -540,7 +539,7 @@ export const textInputWithFeedbackAndHintsOLX = {
|
||||
},
|
||||
},
|
||||
},
|
||||
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><label>Add the question text, or prompt, here. This text is required.</label><em>You can add an optional tip or note related to the prompt like this.</em>',
|
||||
question: '<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p><label>Add the question text, or prompt, here. This text is required.</label><em>You can add an optional tip or note related to the prompt like this. </em>',
|
||||
buildOLX: `<problem>
|
||||
<stringresponse answer="the correct answer" type="ci">
|
||||
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>
|
||||
@@ -726,7 +725,13 @@ export const styledQuestionOLX = {
|
||||
<textline size="20"/>
|
||||
</stringresponse>
|
||||
</problem>`,
|
||||
question: '<p><strong><span style="background-color: #e03e2d;">test</span></strong></p>',
|
||||
question: `<p>
|
||||
<strong>
|
||||
<span style="background-color: #e03e2d;">
|
||||
test
|
||||
</span>
|
||||
</strong>
|
||||
</p>`,
|
||||
};
|
||||
|
||||
export const shuffleProblemOLX = {
|
||||
@@ -763,7 +768,8 @@ export const labelDescriptionQuestionOLX = {
|
||||
</solution>
|
||||
</problem>`,
|
||||
|
||||
question: '<p style="text-align: center;"><img height="274" width="" src="/static/boiling_eggs_water_system.png" alt="boiling eggs: water system"></img></p><label>Taking the system as just the<b>water</b>, as indicated by the red dashed line, what would be the correct expression for the first law of thermodynamics applied to this system?</label><em>Watch out, boiling water is hot</em>',
|
||||
question: `<p style="text-align: center;"><img height="274" width="" src="/static/boiling_eggs_water_system.png" alt="boiling eggs: water system"></img></p>
|
||||
<label>Taking the system as just the <b>water</b>, as indicated by the red dashed line, what would be the correct expression for the first law of thermodynamics applied to this system?</label><em>Watch out, boiling water is hot</em>`,
|
||||
};
|
||||
|
||||
export const htmlEntityTestOLX = {
|
||||
@@ -798,9 +804,7 @@ export const htmlEntityTestOLX = {
|
||||
},
|
||||
],
|
||||
},
|
||||
// eslint-disable-next-line
|
||||
question: `<p>What is the content of the register x2 after executing the following three lines of instructions?</p><p><span style="font-family: 'courier new', courier;"><strong>Address          assembly instructions<br></br>0x0              addi x1, x0, 1<br></br>0x4              slli x2, x1, 4<br></br>0x8              sub x1, x2, x1</strong></span></p>`,
|
||||
// eslint-disable-next-line
|
||||
question: `<p>What is the content of the register x2 after executing the following three lines of instructions?</p><p><span style="font-family: 'courier new', courier;"><strong>Address          assembly instructions <br></br>0x0              addi x1, x0, 1<br></br>0x4              slli x2, x1, 4<br></br>0x8              sub x1, x2, x1</strong></span></p>`,
|
||||
solutionExplanation: `<p><span style="font-family: 'courier new', courier;"><strong>Address          assembly instructions    comment<br></br>0x0              addi x1, x0, 1           x1 = 0x1<br></br>0x4              slli x2, x1, 4           x2 = x1 << 4 = 0x10<br></br>0x8              sub x1, x2, x1           x1 = x2 - x1 = 0x10 - 0x01 = 0xf</strong></span></p>`,
|
||||
};
|
||||
|
||||
@@ -820,22 +824,22 @@ export const numberParseTestOLX = {
|
||||
answers: [
|
||||
{
|
||||
id: 'A',
|
||||
title: `<span style="font-family: 'courier new', courier;"><strong>0x10</strong></span>`, // eslint-disable-line
|
||||
title: `<span style="font-family: 'courier new', courier;"><strong>0x10</strong></span>`,
|
||||
correct: false,
|
||||
},
|
||||
{
|
||||
id: 'B',
|
||||
title: `<span style="font-family: 'courier new', courier;"><strong>0x0f</strong></span>`, // eslint-disable-line
|
||||
title: `<span style="font-family: 'courier new', courier;"><strong>0x0f</strong></span>`,
|
||||
correct: true,
|
||||
},
|
||||
{
|
||||
id: 'C',
|
||||
title: `<span style="font-family: 'courier new', courier;"><strong>0x07</strong></span>`, // eslint-disable-line
|
||||
title: `<span style="font-family: 'courier new', courier;"><strong>0x07</strong></span>`,
|
||||
correct: false,
|
||||
},
|
||||
{
|
||||
id: 'D',
|
||||
title: `<span style="font-family: 'courier new', courier;"><strong>0009</strong></span>`, // eslint-disable-line
|
||||
title: `<span style="font-family: 'courier new', courier;"><strong>0009</strong></span>`,
|
||||
correct: false,
|
||||
},
|
||||
],
|
||||
@@ -853,3 +857,26 @@ export const numberParseTestOLX = {
|
||||
</multiplechoiceresponse>
|
||||
</problem>`,
|
||||
};
|
||||
|
||||
export const solutionExplanationTest = {
|
||||
rawOLX: `<problem>
|
||||
How <code class="lang-matlab">99</code> long is the array <code class="lang-matlab">q</code> after the following loop runs?
|
||||
<pre><code class="lang-matlab">for i = 1:99
|
||||
q(2*i - 1) = i;
|
||||
end</code></pre>
|
||||
<numericalresponse answer="197">
|
||||
<label/>
|
||||
<description>Enter your answer below. Type "e" if this code would produce an error</description>
|
||||
<formulaequationinput/>
|
||||
<responseparam default="2%" type="tolerance"/>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
This loop will iterate <code class="lang-matlab">99</code> times, but the length of <code class="lang-matlab">q</code> will not be <code class="lang-matlab">99</code> due to indexing with the value <code class="lang-matlab">2*i -1</code>. On the last iteration, <code class="lang-matlab">i = 99</code>, so <code class="lang-matlab">2*i - 1 = 2*78 - 1 = 197</code>. This will be the last position filled in <code class="lang-matlab">q</code>, so the answer is <code class="lang-matlab">197</code>.
|
||||
</div>
|
||||
</solution>
|
||||
</numericalresponse>
|
||||
</problem>`,
|
||||
solutionExplanation: `\n
|
||||
This loop will iterate <code class="lang-matlab">99</code> times, but the length of <code class="lang-matlab">q</code> will not be <code class="lang-matlab">99</code> due to indexing with the value <code class="lang-matlab">2*i -1</code>. On the last iteration, <code class="lang-matlab">i = 99</code>, so <code class="lang-matlab">2*i - 1 = 2*78 - 1 = 197</code>. This will be the last position filled in <code class="lang-matlab">q</code>, so the answer is <code class="lang-matlab">197</code>.\n `,
|
||||
};
|
||||
|
||||
@@ -70,7 +70,7 @@ export const saveBlock = ({ content, returnToUnit }) => (dispatch) => {
|
||||
content,
|
||||
onSuccess: (response) => {
|
||||
dispatch(actions.app.setSaveResponse(response));
|
||||
returnToUnit();
|
||||
returnToUnit(response.data)();
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
@@ -138,7 +138,7 @@ describe('app thunkActions', () => {
|
||||
let returnToUnit;
|
||||
let calls;
|
||||
beforeEach(() => {
|
||||
returnToUnit = jest.fn();
|
||||
returnToUnit = jest.fn((response) => () => response);
|
||||
thunkActions.saveBlock({ content: testValue, returnToUnit })(dispatch);
|
||||
calls = dispatch.mock.calls;
|
||||
});
|
||||
|
||||
@@ -36,7 +36,21 @@ export const fetchBlockById = ({ blockId, studioEndpointUrl }) => {
|
||||
} else if (blockId === 'problem-block-id') {
|
||||
data = {
|
||||
data: `<problem>
|
||||
</problem>`,
|
||||
<multiplechoiceresponse>
|
||||
<p>What is the content of the register x2 after executing the following three lines of instructions?</p>
|
||||
<p><span style="font-family: 'courier new', courier;"><strong>Address          assembly instructions <br />0x0              addi x1, x0, 1<br />0x4              slli x2, x1, 4<br />0x8              sub x1, x2, x1</strong></span></p>
|
||||
<choicegroup type="MultipleChoice">
|
||||
<choice correct="false">answerA</choice>
|
||||
<choice correct="true">answerB</choice>
|
||||
</choicegroup>
|
||||
<solution>
|
||||
<div class="detailed-solution">
|
||||
<p>Explanation</p>
|
||||
<p><span style="font-family: 'courier new', courier;"><strong>Address          assembly instructions    comment<br />0x0              addi x1, x0, 1           x1 = 0x1<br />0x4              slli x2, x1, 4           x2 = x1 << 4 = 0x10<br />0x8              sub x1, x2, x1           x1 = x2 - x1 = 0x10 - 0x01 = 0xf</strong></span></p>
|
||||
</div>
|
||||
</solution>
|
||||
</multiplechoiceresponse>
|
||||
</problem>`,
|
||||
display_name: 'Dropdown',
|
||||
metadata: {
|
||||
markdown: `You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown problems. Edit this component to replace this template with your own assessment.
|
||||
|
||||
@@ -16,7 +16,9 @@ export const returnUrl = ({ studioEndpointUrl, unitUrl, learningContextId }) =>
|
||||
};
|
||||
|
||||
export const block = ({ studioEndpointUrl, blockId }) => (
|
||||
`${studioEndpointUrl}/xblock/${blockId}`
|
||||
blockId.includes('block-v1')
|
||||
? `${studioEndpointUrl}/xblock/${blockId}`
|
||||
: `${studioEndpointUrl}/api/xblock/v2/xblocks/${blockId}`
|
||||
);
|
||||
|
||||
export const blockAncestor = ({ studioEndpointUrl, blockId }) => (
|
||||
|
||||
@@ -21,7 +21,8 @@ import {
|
||||
|
||||
describe('cms url methods', () => {
|
||||
const studioEndpointUrl = 'urLgoeStOstudiO';
|
||||
const blockId = 'blOckIDTeST123';
|
||||
const blockId = 'block-v1-blOckIDTeST123';
|
||||
const v2BlockId = 'blOckIDTeST123';
|
||||
const learningContextId = 'lEarnIngCOntextId123';
|
||||
const courseId = 'course-v1:courseId123';
|
||||
const libraryV1Id = 'library-v1:libaryId123';
|
||||
@@ -62,10 +63,14 @@ describe('cms url methods', () => {
|
||||
});
|
||||
});
|
||||
describe('block', () => {
|
||||
it('returns url with studioEndpointUrl and blockId', () => {
|
||||
it('returns v1 url with studioEndpointUrl and blockId', () => {
|
||||
expect(block({ studioEndpointUrl, blockId }))
|
||||
.toEqual(`${studioEndpointUrl}/xblock/${blockId}`);
|
||||
});
|
||||
it('returns v2 url with studioEndpointUrl and v2BlockId', () => {
|
||||
expect(block({ studioEndpointUrl, blockId: v2BlockId }))
|
||||
.toEqual(`${studioEndpointUrl}/api/xblock/v2/xblocks/${v2BlockId}`);
|
||||
});
|
||||
});
|
||||
describe('blockAncestor', () => {
|
||||
it('returns url with studioEndpointUrl, blockId and ancestor query', () => {
|
||||
|
||||
@@ -21,12 +21,12 @@ export const navigateCallback = ({
|
||||
destination,
|
||||
analyticsEvent,
|
||||
analytics,
|
||||
}) => () => {
|
||||
}) => (response) => {
|
||||
if (process.env.NODE_ENV !== 'development' && analyticsEvent && analytics) {
|
||||
sendTrackEvent(analyticsEvent, analytics);
|
||||
}
|
||||
if (returnFunction) {
|
||||
returnFunction();
|
||||
returnFunction()(response);
|
||||
return;
|
||||
}
|
||||
module.navigateTo(destination);
|
||||
|
||||
@@ -74,7 +74,6 @@ describe('hooks', () => {
|
||||
let output;
|
||||
const SAVED_ENV = process.env;
|
||||
const destination = 'hOmE';
|
||||
const returnFunction = jest.fn();
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
process.env = { ...SAVED_ENV };
|
||||
@@ -102,6 +101,7 @@ describe('hooks', () => {
|
||||
expect(spy).toHaveBeenCalledWith(destination);
|
||||
});
|
||||
it('should call returnFunction and return null', () => {
|
||||
const returnFunction = jest.fn(() => (response) => response);
|
||||
output = hooks.navigateCallback({
|
||||
destination,
|
||||
returnFunction,
|
||||
|
||||
73
src/editors/sharedComponents/DraggableList/DraggableList.jsx
Normal file
73
src/editors/sharedComponents/DraggableList/DraggableList.jsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
KeyboardSensor,
|
||||
PointerSensor,
|
||||
useSensor,
|
||||
useSensors,
|
||||
} from '@dnd-kit/core';
|
||||
import {
|
||||
arrayMove,
|
||||
SortableContext,
|
||||
sortableKeyboardCoordinates,
|
||||
verticalListSortingStrategy,
|
||||
} from '@dnd-kit/sortable';
|
||||
|
||||
const DraggableList = ({
|
||||
itemList,
|
||||
setState,
|
||||
updateOrder,
|
||||
children,
|
||||
}) => {
|
||||
const sensors = useSensors(
|
||||
useSensor(PointerSensor),
|
||||
useSensor(KeyboardSensor, {
|
||||
coordinateGetter: sortableKeyboardCoordinates,
|
||||
}),
|
||||
);
|
||||
|
||||
const handleDragEnd = (event) => {
|
||||
const { active, over } = event;
|
||||
if (active.id !== over.id) {
|
||||
let updatedArray;
|
||||
setState(() => {
|
||||
const [activeElement] = itemList.filter(item => item.id === active.id);
|
||||
const [overElement] = itemList.filter(item => item.id === over.id);
|
||||
const oldIndex = itemList.indexOf(activeElement);
|
||||
const newIndex = itemList.indexOf(overElement);
|
||||
updatedArray = arrayMove(itemList, oldIndex, newIndex);
|
||||
return updatedArray;
|
||||
});
|
||||
updateOrder()(updatedArray);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<DndContext
|
||||
sensors={sensors}
|
||||
collisionDetection={closestCenter}
|
||||
onDragEnd={handleDragEnd}
|
||||
>
|
||||
<SortableContext
|
||||
items={itemList}
|
||||
strategy={verticalListSortingStrategy}
|
||||
>
|
||||
{children}
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
);
|
||||
};
|
||||
|
||||
DraggableList.propTypes = {
|
||||
itemList: PropTypes.arrayOf(PropTypes.shape({
|
||||
id: PropTypes.string.isRequired,
|
||||
})).isRequired,
|
||||
setState: PropTypes.func.isRequired,
|
||||
updateOrder: PropTypes.func.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default DraggableList;
|
||||
63
src/editors/sharedComponents/DraggableList/SortableItem.jsx
Normal file
63
src/editors/sharedComponents/DraggableList/SortableItem.jsx
Normal file
@@ -0,0 +1,63 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { intlShape, injectIntl } from '@edx/frontend-platform/i18n';
|
||||
import { useSortable } from '@dnd-kit/sortable';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
import { Icon, IconButtonWithTooltip, Row } from '@edx/paragon';
|
||||
import { DragIndicator } from '@edx/paragon/icons';
|
||||
import messages from './messages';
|
||||
|
||||
const SortableItem = ({
|
||||
id,
|
||||
componentStyle,
|
||||
children,
|
||||
// injected
|
||||
intl,
|
||||
}) => {
|
||||
const {
|
||||
attributes,
|
||||
listeners,
|
||||
setNodeRef,
|
||||
transform,
|
||||
transition,
|
||||
} = useSortable({ id });
|
||||
|
||||
const style = {
|
||||
transform: CSS.Transform.toString(transform),
|
||||
transition,
|
||||
...componentStyle,
|
||||
};
|
||||
|
||||
return (
|
||||
<Row
|
||||
ref={setNodeRef}
|
||||
style={style}
|
||||
className="mx-0"
|
||||
>
|
||||
{children}
|
||||
<IconButtonWithTooltip
|
||||
key="drag-to-reorder-icon"
|
||||
tooltipPlacement="top"
|
||||
tooltipContent={intl.formatMessage(messages.tooltipContent)}
|
||||
src={DragIndicator}
|
||||
iconAs={Icon}
|
||||
variant="secondary"
|
||||
alt={intl.formatMessage(messages.tooltipContent)}
|
||||
{...attributes}
|
||||
{...listeners}
|
||||
/>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
SortableItem.defaultProps = {
|
||||
componentStyle: null,
|
||||
};
|
||||
SortableItem.propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
children: PropTypes.node.isRequired,
|
||||
componentStyle: PropTypes.shape({}),
|
||||
// injected
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(SortableItem);
|
||||
5
src/editors/sharedComponents/DraggableList/index.jsx
Normal file
5
src/editors/sharedComponents/DraggableList/index.jsx
Normal file
@@ -0,0 +1,5 @@
|
||||
import DraggableList from './DraggableList';
|
||||
import SortableItem from './SortableItem';
|
||||
|
||||
export { SortableItem };
|
||||
export default DraggableList;
|
||||
11
src/editors/sharedComponents/DraggableList/messages.js
Normal file
11
src/editors/sharedComponents/DraggableList/messages.js
Normal file
@@ -0,0 +1,11 @@
|
||||
import { defineMessages } from '@edx/frontend-platform/i18n';
|
||||
|
||||
const messages = defineMessages({
|
||||
tooltipContent: {
|
||||
id: 'authoring.draggableList.tooltip.content',
|
||||
defaultMessage: 'Drag to reorder',
|
||||
description: 'Tooltip content for drag indicator icon',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
@@ -2,6 +2,15 @@ import Placeholder from './Placeholder';
|
||||
import messages from './i18n/index';
|
||||
import EditorPage from './editors/EditorPage';
|
||||
import VideoSelectorPage from './editors/VideoSelectorPage';
|
||||
import DraggableList, { SortableItem } from './editors/sharedComponents/DraggableList';
|
||||
import ErrorAlert from './editors/sharedComponents/ErrorAlerts/ErrorAlert';
|
||||
|
||||
export { messages, EditorPage, VideoSelectorPage };
|
||||
export {
|
||||
messages,
|
||||
EditorPage,
|
||||
VideoSelectorPage,
|
||||
DraggableList,
|
||||
SortableItem,
|
||||
ErrorAlert,
|
||||
};
|
||||
export default Placeholder;
|
||||
|
||||
Reference in New Issue
Block a user