diff --git a/src/editors/containers/ProblemEditor/data/OLXParser.js b/src/editors/containers/ProblemEditor/data/OLXParser.js index 3fdd5eb30..8c667e99e 100644 --- a/src/editors/containers/ProblemEditor/data/OLXParser.js +++ b/src/editors/containers/ProblemEditor/data/OLXParser.js @@ -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(//gm, '').replace(/<\/description>/gm, ''); } + /** 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. diff --git a/src/editors/containers/ProblemEditor/data/OLXParser.test.js b/src/editors/containers/ProblemEditor/data/OLXParser.test.js index 8d16ccc02..2b5768bb7 100644 --- a/src/editors/containers/ProblemEditor/data/OLXParser.test.js +++ b/src/editors/containers/ProblemEditor/data/OLXParser.test.js @@ -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('Check OLXParser hints', () => { - test('Test checkbox hints', () => { - const olxparser = new OLXParser(checkboxesOLXWithFeedbackAndHintsOLX.rawOLX); - const hints = olxparser.getHints(); - expect(hints).toEqual(checkboxesOLXWithFeedbackAndHintsOLX.hints); - }); - test('Test numeric hints', () => { - const olxparser = new OLXParser(numericInputWithFeedbackAndHintsOLX.rawOLX); - const hints = olxparser.getHints(); - expect(hints).toEqual(numericInputWithFeedbackAndHintsOLX.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); - }); - 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); - }); - test('Test textual problem type', () => { - const olxparser = new OLXParser(textInputWithFeedbackAndHintsOLX.rawOLX); - const hints = olxparser.getHints(); - expect(hints).toEqual(textInputWithFeedbackAndHintsOLX.hints); - }); -}); - -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); +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')); + }); + }); }); - test('Test numerical response question', () => { - const olxparser = new OLXParser(numericInputWithFeedbackAndHintsOLX.rawOLX); - const question = olxparser.parseQuestions('numericalresponse'); - expect(question).toEqual(numericInputWithFeedbackAndHintsOLX.question); + 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 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')); + 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 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); + 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 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); + 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 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('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('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('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); + 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, '')); + }); + }); }); }); diff --git a/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js b/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js index 91891e798..24947f877 100644 --- a/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js +++ b/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js @@ -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() !== ''; } diff --git a/src/editors/containers/ProblemEditor/data/mockData/olxTestData.js b/src/editors/containers/ProblemEditor/data/mockData/olxTestData.js index 2ee4202b7..fa0e44acb 100644 --- a/src/editors/containers/ProblemEditor/data/mockData/olxTestData.js +++ b/src/editors/containers/ProblemEditor/data/mockData/olxTestData.js @@ -599,7 +599,7 @@ export const scriptProblemOlX = { `, }; -export const multipleProblemOlX = { +export const multipleTextInputProblemOlX = { rawOLX: ` @@ -609,7 +609,7 @@ export const multipleProblemOlX = { `, }; -export const multipleProblemTwoOlX = { +export const multipleNumericProblemOlX = { rawOLX: ` @@ -619,7 +619,7 @@ export const multipleProblemTwoOlX = { `, }; -export const multipleProblemThreeOlX = { +export const NumericAndTextInputProblemOlX = { rawOLX: `