feat add olx solution support (#225)

This adds support for the tag in OLX and maps it to the description in the settings options on the ShowAnswer card.
https://2u-internal.atlassian.net/browse/TNL-10397
This commit is contained in:
Jesper Hodge
2023-02-01 13:28:14 -05:00
committed by GitHub
parent 83d45a249a
commit 861b47b772
26 changed files with 493 additions and 160 deletions

View File

@@ -8,24 +8,24 @@ import { ProblemTypeKeys } from '../../../data/constants/problem';
export const indexToLetterMap = [...Array(26)].map((val, i) => String.fromCharCode(i + 65));
export const nonQuestionKeys = [
'responseparam',
'formulaequationinput',
'correcthint',
'@_answer',
'optioninput',
'@_type',
'additional_answer',
'checkboxgroup',
'choicegroup',
'additional_answer',
'stringequalhint',
'textline',
'@_type',
'formulaequationinput',
'numericalresponse',
'stringresponse',
'multiplechoiceresponse',
'choiceresponse',
'optionresponse',
'correcthint',
'demandhint',
'formulaequationinput',
'multiplechoiceresponse',
'numericalresponse',
'optioninput',
'optionresponse',
'responseparam',
'solution',
'stringequalhint',
'stringresponse',
'textline',
];
export class OLXParser {
@@ -327,6 +327,38 @@ export class OLXParser {
return hintsObject;
}
#extractTextAndChildren(node) {
const children = [];
let text = null;
if (_.isArray(node)) {
children.push(...node);
} else if (_.isPlainObject(node)) {
text = _.get(node, '#text');
const nodeWithoutText = _.omit(node, '#text');
children.push(...Object.values(nodeWithoutText));
}
return { text, children };
}
getSolutionExplanation() {
if (!_.has(this.problem, 'solution')) { return null; }
const stack = [this.problem.solution];
const texts = [];
let currentNode;
while (stack.length) {
currentNode = stack.pop();
const { text, children } = this.#extractTextAndChildren(currentNode);
if (text) { texts.push(text); }
stack.push(...children);
}
return texts.reverse().join('\n ');
}
getFeedback(xmlElement) {
return _.has(xmlElement, 'correcthint') ? _.get(xmlElement, 'correcthint.#text') : '';
}
@@ -365,6 +397,8 @@ export class OLXParser {
const problemType = this.getProblemType();
const hints = this.getHints();
const question = this.parseQuestions(problemType);
const solutionExplanation = this.getSolutionExplanation();
switch (problemType) {
case ProblemTypeKeys.DROPDOWN:
answersObject = this.parseMultipleChoiceAnswers(ProblemTypeKeys.DROPDOWN, 'optioninput', 'option');
@@ -400,6 +434,8 @@ export class OLXParser {
}
const { answers } = answersObject;
const settings = { hints };
if (solutionExplanation) { settings.solutionExplanation = solutionExplanation; }
return {
question,
settings,

View File

@@ -1,11 +1,12 @@
import { OLXParser } from './OLXParser';
import {
checkboxesOLXWithFeedbackAndHintsOLX,
getCheckboxesOLXWithFeedbackAndHintsOLX,
dropdownOLXWithFeedbackAndHintsOLX,
numericInputWithFeedbackAndHintsOLX,
numericInputWithFeedbackAndHintsOLXException,
textInputWithFeedbackAndHintsOLX,
mutlipleChoiceWithFeedbackAndHintsOLX,
multipleChoiceWithFeedbackAndHintsOLX,
textInputWithFeedbackAndHintsOLXWithMultipleAnswers,
advancedProblemOlX,
multipleProblemOlX,
@@ -31,7 +32,7 @@ describe('Check OLXParser problem type', () => {
expect(problemType).toBe(ProblemTypeKeys.DROPDOWN);
});
test('Test multiple choice with feedback and hints problem type', () => {
const olxparser = new OLXParser(mutlipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const olxparser = new OLXParser(multipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const problemType = olxparser.getProblemType();
expect(problemType).toBe(ProblemTypeKeys.SINGLESELECT);
});
@@ -74,9 +75,9 @@ describe('Check OLXParser hints', () => {
expect(hints).toEqual(dropdownOLXWithFeedbackAndHintsOLX.hints);
});
test('Test multiple choice with feedback and hints problem type', () => {
const olxparser = new OLXParser(mutlipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const olxparser = new OLXParser(multipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const hints = olxparser.getHints();
expect(hints).toEqual(mutlipleChoiceWithFeedbackAndHintsOLX.hints);
expect(hints).toEqual(multipleChoiceWithFeedbackAndHintsOLX.hints);
});
test('Test textual problem type', () => {
const olxparser = new OLXParser(textInputWithFeedbackAndHintsOLX.rawOLX);
@@ -97,9 +98,9 @@ describe('Check OLXParser for answer parsing', () => {
expect(answer).toEqual(dropdownOLXWithFeedbackAndHintsOLX.data);
});
test('Test multiple choice single select', () => {
const olxparser = new OLXParser(mutlipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const olxparser = new OLXParser(multipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const answer = olxparser.parseMultipleChoiceAnswers('multiplechoiceresponse', 'choicegroup', 'choice');
expect(answer).toEqual(mutlipleChoiceWithFeedbackAndHintsOLX.data);
expect(answer).toEqual(multipleChoiceWithFeedbackAndHintsOLX.data);
});
test('Test string response answers', () => {
const olxparser = new OLXParser(textInputWithFeedbackAndHintsOLX.rawOLX);
@@ -135,9 +136,9 @@ describe('Check OLXParser for question parsing', () => {
expect(question).toEqual(dropdownOLXWithFeedbackAndHintsOLX.question);
});
test('Test multiple choice single select question', () => {
const olxparser = new OLXParser(mutlipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const olxparser = new OLXParser(multipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const question = olxparser.parseQuestions('multiplechoiceresponse');
expect(question).toEqual(mutlipleChoiceWithFeedbackAndHintsOLX.question);
expect(question).toEqual(multipleChoiceWithFeedbackAndHintsOLX.question);
});
test('Test string response question', () => {
const olxparser = new OLXParser(textInputWithFeedbackAndHintsOLX.rawOLX);
@@ -161,3 +162,20 @@ describe('Check OLXParser for question parsing', () => {
expect(question).toBe(blankQuestionOLX.question);
});
});
describe('OLXParser for problem with solution tag', () => {
describe('for checkbox questions', () => {
test('should parse simple text', () => {
const olxparser = new OLXParser(checkboxesOLXWithFeedbackAndHintsOLX.rawOLX);
const explanation = olxparser.getSolutionExplanation();
expect(explanation).toEqual(checkboxesOLXWithFeedbackAndHintsOLX.solutionExplanation);
});
test('should parse text in p tags', () => {
const { rawOLX } = getCheckboxesOLXWithFeedbackAndHintsOLX({ solution: 'html' });
const olxparser = new OLXParser(rawOLX);
const explanation = olxparser.getSolutionExplanation();
const expected = getCheckboxesOLXWithFeedbackAndHintsOLX({ solution: 'html' }).solutionExplanation;
expect(explanation.replace(/\s/g, '')).toBe(expected.replace(/\s/g, ''));
});
});
});

View File

@@ -36,6 +36,18 @@ class ReactStateOLXParser {
return demandhint;
}
addSolution() {
if (!_.has(this.problemState, 'settings.solutionExplanation')) { return {}; }
const solutionText = _.get(this.problemState, 'settings.solutionExplanation');
const solutionObject = {
solution: {
'#text': solutionText,
},
};
return solutionObject;
}
addMultiSelectAnswers(option) {
const choice = [];
let compoundhint = [];
@@ -106,6 +118,8 @@ class ReactStateOLXParser {
const question = this.addQuestion();
const widgetObject = this.addMultiSelectAnswers(option);
const demandhint = this.addHints();
const solution = this.addSolution();
const problemObject = {
problem: {
[problemType]: {
@@ -113,6 +127,7 @@ class ReactStateOLXParser {
[widget]: widgetObject,
},
...demandhint,
...solution,
},
};
return this.builder.build(problemObject);
@@ -122,6 +137,8 @@ class ReactStateOLXParser {
const question = this.addQuestion();
const demandhint = this.addHints();
const answerObject = this.buildTextInputAnswersFeedback();
const solution = this.addSolution();
const problemObject = {
problem: {
[ProblemTypeKeys.TEXTINPUT]: {
@@ -129,6 +146,7 @@ class ReactStateOLXParser {
...answerObject,
},
...demandhint,
...solution,
},
};
return this.builder.build(problemObject);
@@ -178,11 +196,14 @@ class ReactStateOLXParser {
const question = this.addQuestion();
const demandhint = this.addHints();
const answerObject = this.buildNumericalResponse();
const solution = this.addSolution();
const problemObject = {
problem: {
...question,
[ProblemTypeKeys.NUMERIC]: answerObject,
...demandhint,
...solution,
},
};
return this.builder.build(problemObject);
@@ -255,6 +276,7 @@ class ReactStateOLXParser {
buildOLX() {
const { problemType } = this.problemState;
let problemString = '';
switch (problemType) {
case ProblemTypeKeys.MULTISELECT:
problemString = this.buildMultiSelectProblem(ProblemTypeKeys.MULTISELECT, 'checkboxgroup', 'choice');

View File

@@ -5,7 +5,7 @@ import {
numericInputWithFeedbackAndHintsOLX,
numericInputWithFeedbackAndHintsOLXException,
textInputWithFeedbackAndHintsOLX,
mutlipleChoiceWithFeedbackAndHintsOLX,
multipleChoiceWithFeedbackAndHintsOLX,
textInputWithFeedbackAndHintsOLXWithMultipleAnswers,
} from './mockData/olxTestData';
import ReactStateOLXParser from './ReactStateOLXParser';
@@ -33,11 +33,11 @@ describe('Check React Sate OLXParser problem', () => {
expect(buildOLX).toEqual(textInputWithFeedbackAndHintsOLX.buildOLX);
});
test('Test multiple choice with feedback and hints problem type', () => {
const olxparser = new OLXParser(mutlipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const olxparser = new OLXParser(multipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const problem = olxparser.getParsedOLXData();
const stateParser = new ReactStateOLXParser({ problem });
const buildOLX = stateParser.buildOLX();
expect(buildOLX).toEqual(mutlipleChoiceWithFeedbackAndHintsOLX.buildOLX);
expect(buildOLX).toEqual(multipleChoiceWithFeedbackAndHintsOLX.buildOLX);
});
test('Test numerical response with feedback and hints problem type', () => {
const olxparser = new OLXParser(numericInputWithFeedbackAndHintsOLX.rawOLX);

View File

@@ -1,38 +1,55 @@
export const checkboxesOLXWithFeedbackAndHintsOLX = {
export const getCheckboxesOLXWithFeedbackAndHintsOLX = ({ solution = 'simple' }) => ({
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>
<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>
<checkboxgroup>
<choice correct="true">a correct answer
<choicehint selected="true">You can specify optional feedback that appears after the learner selects and submits this answer.</choicehint>
<choicehint selected="false">You can specify optional feedback that appears after the learner clears and submits this answer.</choicehint>
</choice>
<choice correct="false">an incorrect answer</choice>
<choice correct="false">an incorrect answer
<choicehint selected="true">You can specify optional feedback for none, all, or a subset of the answers.</choicehint>
<choicehint selected="false">You can specify optional feedback for selected answers, cleared answers, or both.</choicehint>
</choice>
<choice correct="true">a correct answer</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>
</choiceresponse>
<demandhint>
<hint>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.</hint>
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
</demandhint>
</problem>`,
hints: [{
id: 0,
value: '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.',
},
{
id: 1,
value: 'If you add more than one hint, a different hint appears each time learners select the hint button.',
},
<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>
<description>You can add an optional tip or note related to the prompt like this.</description>
<checkboxgroup>
<choice correct="true">a correct answer
<choicehint selected="true">You can specify optional feedback that appears after the learner selects and submits this answer.</choicehint>
<choicehint selected="false">You can specify optional feedback that appears after the learner clears and submits this answer.</choicehint>
</choice>
<choice correct="false">an incorrect answer</choice>
<choice correct="false">an incorrect answer
<choicehint selected="true">You can specify optional feedback for none, all, or a subset of the answers.</choicehint>
<choicehint selected="false">You can specify optional feedback for selected answers, cleared answers, or both.</choicehint>
</choice>
<choice correct="true">a correct answer</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>
</choiceresponse>
<demandhint>
<hint>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.</hint>
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
</demandhint>
${solution === 'simple' ? '<solution>This is a detailed explanation of the solution.</solution>' : (
`<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=""/></p>
</div>
</solution>`
)}
</problem>`,
hints: [
{
id: 0,
value: '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.',
},
{
id: 1,
value: 'If you add more than one hint, a different hint appears each time learners select the hint button.',
},
],
solutionExplanation: solution === 'simple' ? 'This is a detailed explanation of the solution.' : (
'Explanation\n 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.'
),
data: {
answers: [
{
@@ -107,9 +124,19 @@ an incorrect answer <choicehint selected="true">You can specify optional
<hint>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.</hint>
<hint>If you add more than one hint, a different hint appears each time learners select the hint button.</hint>
</demandhint>
${solution === 'simple' ? '<solution>This is a detailed explanation of the solution.</solution>' : (
`<solution>
Explanation\n
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.
</solution>`
)}
</problem>
`,
};
});
export const checkboxesOLXWithFeedbackAndHintsOLX = getCheckboxesOLXWithFeedbackAndHintsOLX({});
export const dropdownOLXWithFeedbackAndHintsOLX = {
rawOLX: `<problem>
@@ -184,7 +211,7 @@ an incorrect answer <optionhint>You can specify optional feedback for non
`,
};
export const mutlipleChoiceWithFeedbackAndHintsOLX = {
export const multipleChoiceWithFeedbackAndHintsOLX = {
rawOLX: `<problem>
<multiplechoiceresponse>
<p>You can use this template as a guide to the simple editor markdown and OLX markup to use for multiple choice with hints and feedback problems. Edit this component to replace this template with your own assessment.</p>