chore: refactor OLXParser and related doc strings (#330)

This commit is contained in:
Kristin Aoki
2023-05-11 13:30:47 -04:00
committed by GitHub
parent 8cd222b9fa
commit 99cd3bf1d9
4 changed files with 456 additions and 219 deletions

View File

@@ -105,6 +105,19 @@ export class OLXParser {
}
}
/** parseMultipleChoiceAnswers(problemType, widgetName, option)
* parseMultipleChoiceAnswers takes a problemType, widgetName, and a valid option. The
* olx for the given problem type and widget is parsed. Depending on the problem
* type, the title for an answer will be parsed differently because of single select and multiselect
* problems are rich text while dropdown answers are plain text. The rich text is parsed into an object
* and is converted back into a string before being added to the answer object. The parsing returns a
* data object with an array of answer objects. If the olx has grouped feedback, this will also be
* included in the data object.
* @param {string} problemType - string of the olx problem type
* @param {string} widgetName - string of the wrapping tag name (optioninput, choicegroup, checkboxgroup)
* @param {string} option - string of the type of answers (choice or option)
* @return {object} object containing an array of answer objects and possibly an array of grouped feedback
*/
parseMultipleChoiceAnswers(problemType, widgetName, option) {
const answers = [];
let data = {};
@@ -167,6 +180,14 @@ export class OLXParser {
return data;
}
/** getAnswerFeedback(choice, hintKey)
* getAnswerFeedback takes a choice and a valid option. The choice object is checked for
* selected and unselected feedback. The respective values are added to the feedback object.
* The feedback object is returned.
* @param {object} choice - object of an answer choice
* @param {string} hintKey - string of the wrapping tag name (optionhint or choicehint)
* @return {object} object containing selected and unselected feedback
*/
getAnswerFeedback(choice, hintKey) {
let feedback = {};
let feedbackKeys = 'selectedFeedback';
@@ -194,6 +215,12 @@ export class OLXParser {
return feedback;
}
/** getGroupedFeedback(choices)
* getGroupedFeedback takes choices. The choices with the attribute compoundhint are parsed for
* the text value and the answers associated with the feedback. The groupFeedback array is returned.
* @param {object} choices - object of problem's subtags
* @return {array} array containing objects of feedback and associated answer ids
*/
getGroupedFeedback(choices) {
const groupFeedback = [];
if (_.has(choices, 'compoundhint')) {
@@ -219,6 +246,15 @@ export class OLXParser {
return groupFeedback;
}
/** parseStringResponse()
* The OLX saved to the class constuctor is parsed for text input answers. There are two
* types of tags with the answer attribute, stringresponse (the problem wrapper) and
* additional_answer. Looping through each tag, the associated title and feedback are added
* to the answers object and appended to the answers array. The array returned in an object
* with the key "answers". The object also conatins additional attributes that belong to the
* string response tag.
* @return {object} object containing an array of answer objects and object of additionalStringAttributes
*/
parseStringResponse() {
const { stringresponse } = this.problem;
const answers = [];
@@ -295,6 +331,14 @@ export class OLXParser {
return data;
}
/** parseNumericResponse()
* The OLX saved to the class constuctor is parsed for numeric answers. There are two
* types of tags for numeric answers, responseparam and additional_answer. Looping through
* each tag, the associated title and feedback and if the answer is an answer range are
* added to the answers object and appended to the answers array. The array returned in
* an object with the key "answers".
* @return {object} object containing an array of answer objects
*/
parseNumericResponse() {
const { numericalresponse } = this.problem;
let answerFeedback = '';
@@ -343,6 +387,15 @@ export class OLXParser {
return { answers };
}
/** parseQuestions(problemType)
* parseQuestions takes a problemType. The problem type is used to determine where the
* text for the question lies (sibling or child to warpping problem type tags).
* Using the XMLBuilder, the question is built with its proper children (including label
* and description). The string version of the OLX is return, replacing the description
* tags with italicized tags for styling purposes.
* @param {string} problemType - string of the olx problem type
* @return {string} string of OLX
*/
parseQuestions(problemType) {
const options = {
ignoreAttributes: false,
@@ -380,6 +433,12 @@ export class OLXParser {
return questionString.replace(/<description>/gm, '<em>').replace(/<\/description>/gm, '</em>');
}
/** getHints()
* The OLX saved to the class constuctor is parsed for demand hint tags with hint subtags. An empty array is returned
* if there are no hints in the OLX. Otherwise the hint tag is parsed and appended to the hintsObject arrary. After
* going through all the hints the hintsObject array is returned.
* @return {array} array of hint objects
*/
getHints() {
const hintsObject = [];
if (_.has(this.problem, 'demandhint.hint')) {
@@ -403,6 +462,14 @@ export class OLXParser {
return hintsObject;
}
/** parseQuestions(problemType)
* parseQuestions takes a problemType. The problem type is used to determine where the
* text for the solution lies (sibling or child to warpping problem type tags).
* Using the XMLBuilder, the solution is built removing the redundant "explanation" that is
* appended for Studio styling purposes. The string version of the OLX is return.
* @param {string} problemType - string of the olx problem type
* @return {string} string of OLX
*/
getSolutionExplanation(problemType) {
if (!_.has(this.problem, `${problemType}.solution`) && !_.has(this.problem, 'solution')) { return null; }
let solution = _.get(this.problem, `${problemType}.solution`, null) || _.get(this.problem, 'solution', null);
@@ -433,6 +500,13 @@ export class OLXParser {
return solutionString;
}
/** getFeedback(xmlElement)
* getFeedback takes xmlElement. The xmlElement is searched for the attribute correcthint.
* An empty string is returned if the parameter is not present. Otherwise a string of the feedback
* is returned.
* @param {object} xmlElement - object of answer attributes
* @return {string} string of feedback
*/
getFeedback(xmlElement) {
if (!_.has(xmlElement, 'correcthint')) { return ''; }
const feedback = _.get(xmlElement, 'correcthint');
@@ -440,6 +514,13 @@ export class OLXParser {
return feedbackString;
}
/** getProblemType()
* The OLX saved to the class constuctor is parsed for a valid problem type (referencing problemKeys).
* For blank problems, it returns null. For OLX problems tags not defined in problemKeys or OLX with
* multiple problem tags, it returns advanced. For defined, single problem tag, it returns the
* associated problem type.
* @return {string} problem type
*/
getProblemType() {
const problemKeys = Object.keys(this.problem);
const problemTypeKeys = problemKeys.filter(key => Object.values(ProblemTypeKeys).indexOf(key) !== -1);
@@ -462,6 +543,14 @@ export class OLXParser {
return problemType;
}
/** getGeneralFeedback({ answers, problemType })
* getGeneralFeedback takes answers and problemType. The problem type determines if the problem should be checked
* for general feedback. The incorrect answers are checked to seee if all of their feedback is the same and
* returns the first incorrect answer's feedback if true. When conditions are unmet, it returns and empty string.
* @param {array} answers - array of answer objects
* @param {string} problemType - string of string of the olx problem type
* @return {string} text for incorrect feedback
*/
getGeneralFeedback({ answers, problemType }) {
/* Feedback is Generalized for a Problem IFF:
1. The problem is of Types: Single Select or Dropdown.

View File

@@ -9,9 +9,9 @@ import {
multipleChoiceWithFeedbackAndHintsOLX,
textInputWithFeedbackAndHintsOLXWithMultipleAnswers,
advancedProblemOlX,
multipleProblemOlX,
multipleProblemTwoOlX,
multipleProblemThreeOlX,
multipleTextInputProblemOlX,
multipleNumericProblemOlX,
NumericAndTextInputProblemOlX,
blankProblemOLX,
blankQuestionOLX,
styledQuestionOLX,
@@ -23,227 +23,275 @@ import {
} from './mockData/olxTestData';
import { ProblemTypeKeys } from '../../../data/constants/problem';
describe('Check OLXParser problem type', () => {
test('Test checkbox with feedback and hints problem type', () => {
const olxparser = new OLXParser(checkboxesOLXWithFeedbackAndHintsOLX.rawOLX);
const problemType = olxparser.getProblemType();
expect(problemType).toBe(ProblemTypeKeys.MULTISELECT);
});
test('Test numeric problem type', () => {
const olxparser = new OLXParser(numericInputWithFeedbackAndHintsOLX.rawOLX);
const problemType = olxparser.getProblemType();
expect(problemType).toBe(ProblemTypeKeys.NUMERIC);
});
test('Test dropdown with feedback and hints problem type', () => {
const olxparser = new OLXParser(dropdownOLXWithFeedbackAndHintsOLX.rawOLX);
const problemType = olxparser.getProblemType();
expect(problemType).toBe(ProblemTypeKeys.DROPDOWN);
});
test('Test multiple choice with feedback and hints problem type', () => {
const olxparser = new OLXParser(multipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const problemType = olxparser.getProblemType();
expect(problemType).toBe(ProblemTypeKeys.SINGLESELECT);
});
test('Test textual problem type', () => {
const olxparser = new OLXParser(textInputWithFeedbackAndHintsOLX.rawOLX);
const problemType = olxparser.getProblemType();
expect(problemType).toBe(ProblemTypeKeys.TEXTINPUT);
});
test('Test Advanced Problem Type', () => {
const olxparser = new OLXParser(advancedProblemOlX.rawOLX);
const problemType = olxparser.getProblemType();
expect(problemType).toBe(ProblemTypeKeys.ADVANCED);
});
test('Test Advanced Problem Type by multiples', () => {
const olxparser = new OLXParser(multipleProblemOlX.rawOLX);
const problemType = olxparser.getProblemType();
expect(problemType).toBe(ProblemTypeKeys.ADVANCED);
});
test('Test Advanced Problem Type by multiples, second example', () => {
const olxparser = new OLXParser(multipleProblemTwoOlX.rawOLX);
const problemType = olxparser.getProblemType();
expect(problemType).toBe(ProblemTypeKeys.ADVANCED);
});
test('Test Advanced Problem Type by multiples, third example', () => {
const olxparser = new OLXParser(multipleProblemThreeOlX.rawOLX);
const problemType = olxparser.getProblemType();
expect(problemType).toBe(ProblemTypeKeys.ADVANCED);
});
test('Test Blank Problem Type', () => {
const olxparser = new OLXParser(blankProblemOLX.rawOLX);
const problemType = olxparser.getProblemType();
expect(problemType).toBe(null);
});
});
const blankOlxParser = new OLXParser(blankProblemOLX.rawOLX);
const checkboxOlxParser = new OLXParser(checkboxesOLXWithFeedbackAndHintsOLX.rawOLX);
const numericOlxParser = new OLXParser(numericInputWithFeedbackAndHintsOLX.rawOLX);
const dropdownOlxParser = new OLXParser(dropdownOLXWithFeedbackAndHintsOLX.rawOLX);
const multipleChoiceOlxParser = new OLXParser(multipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const multipleChoiceWithoutAnswersOlxParser = new OLXParser(multipleChoiceWithoutAnswers.rawOLX);
const textInputOlxParser = new OLXParser(textInputWithFeedbackAndHintsOLX.rawOLX);
const textInputMultipleAnswersOlxParser = new OLXParser(textInputWithFeedbackAndHintsOLXWithMultipleAnswers.rawOLX);
const advancedOlxParser = new OLXParser(advancedProblemOlX.rawOLX);
const multipleTextInputOlxParser = new OLXParser(multipleTextInputProblemOlX.rawOLX);
const multipleNumericOlxParser = new OLXParser(multipleNumericProblemOlX.rawOLX);
const numericAndTextInputOlxParser = new OLXParser(NumericAndTextInputProblemOlX.rawOLX);
const labelDescriptionQuestionOlxParser = new OLXParser(labelDescriptionQuestionOLX.rawOLX);
const shuffleOlxParser = new OLXParser(shuffleProblemOLX.rawOLX);
describe('OLX Parser settings attributes on problem tags', () => {
test('OLX with attributes on the problem tags should error out', () => {
const olxparser = new OLXParser(labelDescriptionQuestionOLX.rawOLX);
try {
olxparser.getParsedOLXData();
} catch (e) {
expect(e).toBeInstanceOf(Error);
expect(e.message).toBe('Misc Attributes asscoiated with problem, opening in advanced editor');
}
describe('OLXParser', () => {
describe('throws error and redirects to advanced editor', () => {
describe('when settings attributes are on problem tags', () => {
it('should throw error and contain message regarding opening advanced editor', () => {
try {
labelDescriptionQuestionOlxParser.getParsedOLXData();
} catch (e) {
expect(e).toBeInstanceOf(Error);
expect(e.message).toBe('Misc Attributes asscoiated with problem, opening in advanced editor');
}
});
});
describe('when settings attributes are on problem tags', () => {
it('should throw error and contain message regarding opening advanced editor', () => {
try {
shuffleOlxParser.getParsedOLXData();
} catch (e) {
expect(e).toBeInstanceOf(Error);
expect(e.message).toBe('Misc Tags, reverting to Advanced Editor');
}
});
});
describe('when question parser finds script tags', () => {
it('should throw error and contain message regarding opening advanced editor', () => {
const olxparser = new OLXParser(scriptProblemOlX.rawOLX);
expect(() => olxparser.parseQuestions('numericalresponse')).toThrow(new Error('Script Tag, reverting to Advanced Editor'));
});
});
});
});
describe('Check OLXParser hints', () => {
test('Test checkbox hints', () => {
const olxparser = new OLXParser(checkboxesOLXWithFeedbackAndHintsOLX.rawOLX);
const hints = olxparser.getHints();
expect(hints).toEqual(checkboxesOLXWithFeedbackAndHintsOLX.hints);
describe('getProblemType()', () => {
describe('given a blank problem', () => {
const problemType = blankOlxParser.getProblemType();
it('should equal ProblemTypeKeys.MULTISELECT', () => {
expect(problemType).toEqual(null);
});
});
describe('given checkbox olx with feedback and hints', () => {
const problemType = checkboxOlxParser.getProblemType();
it('should equal ProblemTypeKeys.MULTISELECT', () => {
expect(problemType).toEqual(ProblemTypeKeys.MULTISELECT);
});
});
describe('given numeric olx with feedback and hints', () => {
const problemType = numericOlxParser.getProblemType();
it('should equal ProblemTypeKeys.NUMERIC', () => {
expect(problemType).toEqual(ProblemTypeKeys.NUMERIC);
});
});
describe('given dropdown olx with feedback and hints', () => {
const problemType = dropdownOlxParser.getProblemType();
it('should equal ProblemTypeKeys.DROPDOWN', () => {
expect(problemType).toEqual(ProblemTypeKeys.DROPDOWN);
});
});
describe('given multiple choice olx with feedback and hints', () => {
const problemType = multipleChoiceOlxParser.getProblemType();
it('should equal ProblemTypeKeys.SINGLESELECT', () => {
expect(problemType).toEqual(ProblemTypeKeys.SINGLESELECT);
});
});
describe('given text input olx with feedback and hints', () => {
const problemType = textInputOlxParser.getProblemType();
it('should equal ProblemTypeKeys.TEXTINPUT', () => {
expect(problemType).toEqual(ProblemTypeKeys.TEXTINPUT);
});
});
describe('given an advanced problem', () => {
const problemType = advancedOlxParser.getProblemType();
it('should equal ProblemTypeKeys.ADVANCED', () => {
expect(problemType).toEqual(ProblemTypeKeys.ADVANCED);
});
});
describe('given a problem with multiple text inputs', () => {
const problemType = multipleTextInputOlxParser.getProblemType();
it('should equal ProblemTypeKeys.ADVANCED', () => {
expect(problemType).toEqual(ProblemTypeKeys.ADVANCED);
});
});
describe('given a problem with multiple numeric inputs', () => {
const problemType = multipleNumericOlxParser.getProblemType();
it('should equal ProblemTypeKeys.ADVANCED', () => {
expect(problemType).toEqual(ProblemTypeKeys.ADVANCED);
});
});
describe('given a problem with both a text and numeric input', () => {
const problemType = numericAndTextInputOlxParser.getProblemType();
it('should equal ProblemTypeKeys.ADVANCED', () => {
expect(problemType).toEqual(ProblemTypeKeys.ADVANCED);
});
});
});
test('Test numeric hints', () => {
const olxparser = new OLXParser(numericInputWithFeedbackAndHintsOLX.rawOLX);
const hints = olxparser.getHints();
expect(hints).toEqual(numericInputWithFeedbackAndHintsOLX.hints);
describe('getHints()', () => {
describe('given a problem with no hints', () => {
const hints = labelDescriptionQuestionOlxParser.getHints();
it('should return an empty array', () => {
expect(hints).toEqual([]);
});
});
describe('given checkbox olx with feedback and hints', () => {
const hints = checkboxOlxParser.getHints();
it('should equal an array of hints', () => {
expect(hints).toEqual(checkboxesOLXWithFeedbackAndHintsOLX.hints);
});
});
describe('given numeric olx with feedback and hints', () => {
const hints = numericOlxParser.getHints();
it('should equal an array of hints', () => {
expect(hints).toEqual(numericInputWithFeedbackAndHintsOLX.hints);
});
});
describe('given dropdown olx with feedback and hints', () => {
const hints = dropdownOlxParser.getHints();
it('should equal an array of hints', () => {
expect(hints).toEqual(dropdownOLXWithFeedbackAndHintsOLX.hints);
});
});
describe('given multiple choice olx with feedback and hints', () => {
const hints = multipleChoiceOlxParser.getHints();
it('should equal an array of hints', () => {
expect(hints).toEqual(multipleChoiceWithFeedbackAndHintsOLX.hints);
});
});
describe('given text input olx with feedback and hints', () => {
const hints = textInputOlxParser.getHints();
it('should equal an array of hints', () => {
expect(hints).toEqual(textInputWithFeedbackAndHintsOLX.hints);
});
});
});
test('Test dropdown with feedback and hints problem type', () => {
const olxparser = new OLXParser(dropdownOLXWithFeedbackAndHintsOLX.rawOLX);
const hints = olxparser.getHints();
expect(hints).toEqual(dropdownOLXWithFeedbackAndHintsOLX.hints);
describe('parseMultipleChoiceAnswers()', () => {
describe('given a problem with no answers', () => {
const { answers } = multipleChoiceWithoutAnswersOlxParser.parseMultipleChoiceAnswers(
'multiplechoiceresponse',
'choicegroup',
'choice',
);
it('should return a default answer', () => {
expect(answers).toEqual(multipleChoiceWithoutAnswers.data.answers);
expect(answers).toHaveLength(1);
});
});
describe('given multiple choice olx with hex numbers and leading zeros', () => {
const olxparser = new OLXParser(numberParseTestOLX.rawOLX);
const { answers } = olxparser.parseMultipleChoiceAnswers('multiplechoiceresponse', 'choicegroup', 'choice');
it('should not parse hex numbers and leading zeros', () => {
expect(answers).toEqual(numberParseTestOLX.data.answers);
});
it('should equal an array of objects with length four', () => {
expect(answers).toHaveLength(4);
});
});
describe('given checkbox olx with feedback and hints', () => {
const { answers } = checkboxOlxParser.parseMultipleChoiceAnswers('choiceresponse', 'checkboxgroup', 'choice');
it('should equal an array of objects with length four', () => {
expect(answers).toEqual(checkboxesOLXWithFeedbackAndHintsOLX.data.answers);
expect(answers).toHaveLength(4);
});
});
describe('given dropdown olx with feedback and hints', () => {
const { answers } = dropdownOlxParser.parseMultipleChoiceAnswers('optionresponse', 'optioninput', 'option');
it('should equal an array of objects with length three', () => {
expect(answers).toEqual(dropdownOLXWithFeedbackAndHintsOLX.data.answers);
expect(answers).toHaveLength(3);
});
});
describe('given multiple choice olx with feedback and hints', () => {
const { answers } = multipleChoiceOlxParser.parseMultipleChoiceAnswers('multiplechoiceresponse', 'choicegroup', 'choice');
it('should equal an array of objects with length three', () => {
expect(answers).toEqual(multipleChoiceWithFeedbackAndHintsOLX.data.answers);
expect(answers).toHaveLength(3);
});
});
});
test('Test multiple choice with feedback and hints problem type', () => {
const olxparser = new OLXParser(multipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const hints = olxparser.getHints();
expect(hints).toEqual(multipleChoiceWithFeedbackAndHintsOLX.hints);
describe('parseStringResponse()', () => {
// describe('given a problem with no answers', () => {
// // TODO
// });
describe('given text input olx with feedback and hints', () => {
const { answers } = textInputOlxParser.parseStringResponse();
it('should equal an array of objects with length three', () => {
expect(answers).toEqual(textInputWithFeedbackAndHintsOLX.data.answers);
expect(answers).toHaveLength(3);
});
});
describe('given text input olx with feedback and hints with multiple answers', () => {
const { answers } = textInputMultipleAnswersOlxParser.parseStringResponse();
it('should equal an array of objects with length four', () => {
expect(answers).toEqual(textInputWithFeedbackAndHintsOLXWithMultipleAnswers.data.answers);
expect(answers).toHaveLength(4);
});
});
});
test('Test textual problem type', () => {
const olxparser = new OLXParser(textInputWithFeedbackAndHintsOLX.rawOLX);
const hints = olxparser.getHints();
expect(hints).toEqual(textInputWithFeedbackAndHintsOLX.hints);
describe('parseNumericResponse()', () => {
// describe('given a problem with no answers', () => {
// // TODDO
// });
describe('given numeric olx with feedback and hints', () => {
const { answers } = numericOlxParser.parseNumericResponse();
it('should equal an array of objects with length two', () => {
expect(answers).toEqual(numericInputWithFeedbackAndHintsOLX.data.answers);
expect(answers).toHaveLength(2);
});
});
});
});
describe('Check OLXParser for answer parsing', () => {
test('Test check single select with empty answers', () => {
const olxparser = new OLXParser(multipleChoiceWithoutAnswers.rawOLX);
const answer = olxparser.parseMultipleChoiceAnswers('multiplechoiceresponse', 'choicegroup', 'choice');
expect(answer).toEqual(multipleChoiceWithoutAnswers.data);
});
test('Test checkbox answer', () => {
const olxparser = new OLXParser(checkboxesOLXWithFeedbackAndHintsOLX.rawOLX);
const answer = olxparser.parseMultipleChoiceAnswers('choiceresponse', 'checkboxgroup', 'choice');
expect(answer).toEqual(checkboxesOLXWithFeedbackAndHintsOLX.data);
});
test('Test checkbox answer', () => {
const olxparser = new OLXParser(checkboxesOLXWithFeedbackAndHintsOLX.rawOLX);
const answer = olxparser.parseMultipleChoiceAnswers('choiceresponse', 'checkboxgroup', 'choice');
expect(answer).toEqual(checkboxesOLXWithFeedbackAndHintsOLX.data);
});
test('Test checkboxs with extraneous tags error out', () => {
const olxparser = new OLXParser(shuffleProblemOLX.rawOLX);
try {
olxparser.parseMultipleChoiceAnswers('choiceresponse', 'checkboxgroup', 'choice');
} catch (e) {
expect(e).toBeInstanceOf(Error);
expect(e.message).toBe('Misc Tags, reverting to Advanced Editor');
}
});
test('Test dropdown answer', () => {
const olxparser = new OLXParser(dropdownOLXWithFeedbackAndHintsOLX.rawOLX);
const answer = olxparser.parseMultipleChoiceAnswers('optionresponse', 'optioninput', 'option');
expect(answer).toEqual(dropdownOLXWithFeedbackAndHintsOLX.data);
});
test('Test multiple choice single select', () => {
const olxparser = new OLXParser(multipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const answer = olxparser.parseMultipleChoiceAnswers('multiplechoiceresponse', 'choicegroup', 'choice');
expect(answer).toEqual(multipleChoiceWithFeedbackAndHintsOLX.data);
});
test('Test string response answers', () => {
const olxparser = new OLXParser(textInputWithFeedbackAndHintsOLX.rawOLX);
const answer = olxparser.parseStringResponse();
expect(answer).toEqual(textInputWithFeedbackAndHintsOLX.data);
});
test('Test string response answers with multiple answers', () => {
const olxparser = new OLXParser(textInputWithFeedbackAndHintsOLXWithMultipleAnswers.rawOLX);
const answer = olxparser.parseStringResponse();
expect(answer).toEqual(textInputWithFeedbackAndHintsOLXWithMultipleAnswers.data);
});
test('Test numerical response answers', () => {
const olxparser = new OLXParser(numericInputWithFeedbackAndHintsOLX.rawOLX);
const answer = olxparser.parseNumericResponse();
expect(answer).toEqual(numericInputWithFeedbackAndHintsOLX.data);
});
});
describe('Check OLXParser for question parsing', () => {
test('Test checkbox question', () => {
const olxparser = new OLXParser(checkboxesOLXWithFeedbackAndHintsOLX.rawOLX);
const question = olxparser.parseQuestions('choiceresponse');
expect(question).toEqual(checkboxesOLXWithFeedbackAndHintsOLX.question);
});
test('Test dropdown question', () => {
const olxparser = new OLXParser(dropdownOLXWithFeedbackAndHintsOLX.rawOLX);
const question = olxparser.parseQuestions('optionresponse');
expect(question).toEqual(dropdownOLXWithFeedbackAndHintsOLX.question);
});
test('Test multiple choice single select question', () => {
const olxparser = new OLXParser(multipleChoiceWithFeedbackAndHintsOLX.rawOLX);
const question = olxparser.parseQuestions('multiplechoiceresponse');
expect(question).toEqual(multipleChoiceWithFeedbackAndHintsOLX.question);
});
test('Test string response question', () => {
const olxparser = new OLXParser(textInputWithFeedbackAndHintsOLX.rawOLX);
const question = olxparser.parseQuestions('stringresponse');
expect(question).toEqual(textInputWithFeedbackAndHintsOLX.question);
});
test('Test numerical response question', () => {
const olxparser = new OLXParser(numericInputWithFeedbackAndHintsOLX.rawOLX);
const question = olxparser.parseQuestions('numericalresponse');
expect(question).toEqual(numericInputWithFeedbackAndHintsOLX.question);
});
test('Test Advanced Problem Type by script tag', () => {
const olxparser = new OLXParser(scriptProblemOlX.rawOLX);
expect(() => olxparser.parseQuestions('numericalresponse')).toThrow(new Error('Script Tag, reverting to Advanced Editor'));
});
test('Test OLX with no question content should have empty string for question', () => {
const olxparser = new OLXParser(blankQuestionOLX.rawOLX);
const problemType = olxparser.getProblemType();
const question = olxparser.parseQuestions(problemType);
expect(question).toBe(blankQuestionOLX.question);
});
test('Test OLX question content with styling should parse/build with correct styling', () => {
const olxparser = new OLXParser(styledQuestionOLX.rawOLX);
const problemType = olxparser.getProblemType();
const question = olxparser.parseQuestions(problemType);
expect(question).toBe(styledQuestionOLX.question);
});
test('Test OLX content with labels and descriptions inside reponse tag should parse correctly, appending the label/description to the question', () => {
const olxparser = new OLXParser(labelDescriptionQuestionOLX.rawOLX);
const problemType = olxparser.getProblemType();
const question = olxparser.parseQuestions(problemType);
expect(question).toBe(labelDescriptionQuestionOLX.question);
});
});
describe('OLXParser for problem with solution tag', () => {
describe('for checkbox questions', () => {
test('should parse text in p tags', () => {
const { rawOLX } = getCheckboxesOLXWithFeedbackAndHintsOLX();
const olxparser = new OLXParser(rawOLX);
describe('parseQuestions()', () => {
describe('given olx with no question content', () => {
const olxparser = new OLXParser(blankQuestionOLX.rawOLX);
const problemType = olxparser.getProblemType();
const explanation = olxparser.getSolutionExplanation(problemType);
const expected = getCheckboxesOLXWithFeedbackAndHintsOLX().solutionExplanation;
expect(explanation.replace(/\s/g, '')).toBe(expected.replace(/\s/g, ''));
const question = olxparser.parseQuestions(problemType);
it('should return an empty string for question', () => {
expect(question).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);
});
});
describe('given olx with html entities', () => {
const olxparser = new OLXParser(htmlEntityTestOLX.rawOLX);
const problemType = olxparser.getProblemType();
const question = olxparser.parseQuestions(problemType);
it('should not encode html entities', () => {
expect(question).toEqual(htmlEntityTestOLX.question);
});
});
describe('given olx with styled content', () => {
const olxparser = new OLXParser(styledQuestionOLX.rawOLX);
const problemType = olxparser.getProblemType();
const question = olxparser.parseQuestions(problemType);
it('should pase/build correct styling', () => {
expect(question).toBe(styledQuestionOLX.question);
});
});
describe('given olx with label and description tags inside response tag', () => {
const olxparser = new OLXParser(labelDescriptionQuestionOLX.rawOLX);
const problemType = olxparser.getProblemType();
const question = olxparser.parseQuestions(problemType);
it('should append the label/description to the question', () => {
expect(question).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 problemType = olxparser.getProblemType();
const explanation = olxparser.getSolutionExplanation(problemType);
const expected = getCheckboxesOLXWithFeedbackAndHintsOLX().solutionExplanation;
expect(explanation.replace(/\s/g, '')).toBe(expected.replace(/\s/g, ''));
});
});
});
});
describe('Check OLXParser for proper encoding', () => {
it('should not encode html entities', () => {
const olxparser = new OLXParser(htmlEntityTestOLX.rawOLX);
const problemType = olxparser.getProblemType();
const question = olxparser.parseQuestions(problemType);
expect(question).toBe(htmlEntityTestOLX.question);
});
it('should not parse hex numbers and leading zeros', () => {
const olxparser = new OLXParser(numberParseTestOLX.rawOLX);
const answer = olxparser.parseMultipleChoiceAnswers('multiplechoiceresponse', 'choicegroup', 'choice');
expect(answer).toEqual(numberParseTestOLX.data);
});
});

View File

@@ -51,6 +51,12 @@ class ReactStateOLXParser {
this.problemState = problemState.problem;
}
/** addHints()
* The editorObject saved to the class constuctor is parsed for the attribute hints. No hints returns an empty object.
* The hints are parsed and appended to the hintsArray as object representations of the hint. The hints array is saved
* to the hint key in the demandHint object and returned.
* @return {object} demandhint object with atrribut hint with array of objects
*/
addHints() {
const hintsArray = [];
const { hints } = this.editorObject;
@@ -73,6 +79,13 @@ class ReactStateOLXParser {
return demandhint;
}
/** addSolution()
* The editorObject saved to the class constuctor is parsed for the attribute solution. If the soltuion is empty, it
* returns an empty object. The solution is parsed and checked if paragraph key's value is a string or array. Studio
* requires a div wrapper with a heading (Explanation). The heading is prepended to the parsed solution object. The
* solution object is returned with the updated div wrapper.
* @return {object} object representation of solution
*/
addSolution() {
const { solution } = this.editorObject;
if (!solution || solution.length <= 0) { return {}; }
@@ -91,6 +104,19 @@ class ReactStateOLXParser {
return solutionObject;
}
/** addMultiSelectAnswers(option)
* addMultiSelectAnswers takes option. Option is used to assign an answers to the
* correct OLX tag. This function is used for multiple choice, checkbox, and
* dropdown problems. The editorObject saved to the class constuctor is parsed for
* answers (titles only), selectFeedback, and unselectedFeedback. The problemState
* saved to the class constructor is parsed for the problemType and answers (full
* object). The answers are looped through to pair feedback with its respective
* OLX tags. While matching feedback tags, answers are also mapped to their
* respective OLX tags. he object representation of the answers is returned with
* the correct wrapping tags. For checkbox problems, compound hints are also returned.
* @param {string} option - string of answer tag name
* @return {object} object representation of answers
*/
addMultiSelectAnswers(option) {
const choice = [];
let compoundhint = [];
@@ -173,6 +199,12 @@ class ReactStateOLXParser {
return widget;
}
/** addGroupFeedbackList()
* The problemState saved to the class constuctor is parsed for the attribute groupFeedbackList.
* No group feedback returns an empty array. Each groupFeedback in the groupFeedback list is
* mapped to a new object and appended to the compoundhint array.
* @return {object} object representation of compoundhints
*/
addGroupFeedbackList() {
const compoundhint = [];
const { groupFeedbackList } = this.problemState;
@@ -185,6 +217,11 @@ class ReactStateOLXParser {
return compoundhint;
}
/** addQuestion()
* The editorObject saved to the class constuctor is parsed for the attribute question. The question is parsed and
* checked for label tags. After the question is fully updated, the questionObject is returned.
* @return {object} object representaion of question
*/
addQuestion() {
const { question } = this.editorObject;
const questionObject = this.questionParser.parse(question);
@@ -207,6 +244,16 @@ class ReactStateOLXParser {
return questionObject;
}
/** buildMultiSelectProblem()
* OLX builder for multiple choice, checkbox, and dropdown problems. The question
* builder has a different format than the other parts (demand hint, answers, and
* solution) of the problem so it has to be inserted into the OLX after the rest
* of the problem is built.
* @param {string} problemType - string of problem type tag
* @param {string} widget - string of answer tag name
* @param {string} option - string of feedback tag name
* @return {string} string of OLX
*/
buildMultiSelectProblem(problemType, widget, option) {
const question = this.addQuestion();
const widgetObject = this.addMultiSelectAnswers(option);
@@ -245,6 +292,12 @@ class ReactStateOLXParser {
return problemString;
}
/** buildTextInput()
* String response OLX builder. The question builder has a different format than the
* other parts (demand hint, answers, and solution) of the problem so it has to be
* inserted into the OLX after the rest of the problem is built.
* @return {string} string of string response OLX
*/
buildTextInput() {
const question = this.addQuestion();
const demandhint = this.addHints();
@@ -270,6 +323,20 @@ class ReactStateOLXParser {
return problemString;
}
/** buildTextInputAnswersFeedback()
* The editorObject saved to the class constuctor is parsed for the attribute
* selectedFeedback. String response problems have two types of feedback tags,
* correcthint and stringequalhint. Correcthint is for feedback associated with
* correct answers and stringequalhint is for feedback associated with wrong
* answers. The answers are fetched from the problemState and looped through to
* pair feedback with its respective OLX tags. While matching feedback tags,
* answers are also mapped to their respective OLX tags. The first correct
* answer is wrapped in stringreponse tag. All other correct answers are wrapped
* in additonal_answer tags. Incorrect answers are wrapped in stringequalhint
* tags. The object representation of the answers is returned with the correct
* wrapping tags.
* @return {object} object representation of answers
*/
buildTextInputAnswersFeedback() {
const { answers } = this.problemState;
const { selectedFeedback } = this.editorObject;
@@ -312,6 +379,12 @@ class ReactStateOLXParser {
return answerObject;
}
/** buildNumericInput()
* Numeric response OLX builder. The question builder has a different format than the
* other parts (demand hint, answers, and solution) of the problem so it has to be
* inserted into the OLX after the rest of the problem is built.
* @return {string} string of numeric response OLX
*/
buildNumericInput() {
const question = this.addQuestion();
const demandhint = this.addHints();
@@ -337,6 +410,18 @@ class ReactStateOLXParser {
return problemString;
}
/** buildNumericalResponse()
* The editorObject saved to the class constuctor is parsed for the attribute
* selectedFeedback. The tolerance is fetched from the problemState settings.
* The answers are fetched from the problemState and looped through to
* pair feedback with its respective OLX tags. While matching feedback tags,
* answers are also mapped to their respective OLX tags. For each answer, if
* it is an answer range, it is santized to be less than to great than. The
* first answer is wrapped in numericresponse tag. All other answers are
* wrapped in additonal_answer tags. The object representation of the answers
* is returned with the correct wrapping tags.
* @return {object} object representation of answers
*/
buildNumericalResponse() {
const { answers } = this.problemState;
const { tolerance } = this.problemState.settings;
@@ -419,6 +504,14 @@ class ReactStateOLXParser {
return answerObject;
}
/** getAnswerHints(feedback)
* getAnswerHints takes feedback. The feedback is checked for definition. If feedback is
* undefined or an empty string, it returns an empty object. The defined feedback is
* parsed and saved to the key correcthint. Correcthint is the tag name for
* numeric response and string response feedback.
* @param {string} feedback - string of feedback
* @return {object} object representaion of feedback
*/
getAnswerHints(feedback) {
let correcthint = {};
if (feedback !== undefined && feedback !== '') {
@@ -432,6 +525,13 @@ class ReactStateOLXParser {
return correcthint;
}
/** hasAttributeWithValue(obj, attr)
* hasAttributeWithValue takes obj and atrr. The obj is checked for the attribute defined by attr.
* Returns true if atrribute is present, otherwise false.
* @param {object} obj - defined object
* @param {string} attr - string of desired attribute
* @return {bool}
*/
hasAttributeWithValue(obj, attr) {
return _.has(obj, attr) && _.get(obj, attr, '').toString().trim() !== '';
}

View File

@@ -599,7 +599,7 @@ export const scriptProblemOlX = {
</numericalresponse>
</problem>`,
};
export const multipleProblemOlX = {
export const multipleTextInputProblemOlX = {
rawOLX: `<problem>
<stringresponse answer="correct answer">
<textline size="20"/>
@@ -609,7 +609,7 @@ export const multipleProblemOlX = {
</stringresponse>
</problem>`,
};
export const multipleProblemTwoOlX = {
export const multipleNumericProblemOlX = {
rawOLX: `<problem>
<numericalresponse answer="100">
<formulaequationinput></formulaequationinput>
@@ -619,7 +619,7 @@ export const multipleProblemTwoOlX = {
</numericalresponse>
</problem>`,
};
export const multipleProblemThreeOlX = {
export const NumericAndTextInputProblemOlX = {
rawOLX: `<problem>
<stringresponse answer="correct answer">
<textline size="20"/>