From ffd311881ad4b3d983f6b32a341e9319b82a94e9 Mon Sep 17 00:00:00 2001 From: Ken Clary Date: Tue, 13 Jun 2023 15:09:04 -0400 Subject: [PATCH 1/6] feat: v2 xblocks should use v2 url. TNL-10742 --- package-lock.json | 6 +++--- src/editors/data/services/cms/urls.js | 4 +++- src/editors/data/services/cms/urls.test.js | 9 +++++++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index a24ac35fa..7446ef43b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9986,9 +9986,9 @@ "dev": true }, "node_modules/fast-xml-parser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.2.tgz", - "integrity": "sha512-DLzIPtQqmvmdq3VUKR7T6omPK/VCRNqgFlGtbESfyhcH2R4I8EzK1/K6E8PkRCK2EabWrUHK32NjYRbEFnnz0Q==", + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.4.tgz", + "integrity": "sha512-fbfMDvgBNIdDJLdLOwacjFAPYt67tr31H9ZhWSm45CDAxvd0I6WTlSOUo7K2P/K5sA5JgMKG64PI3DMcaFdWpQ==", "funding": [ { "type": "paypal", diff --git a/src/editors/data/services/cms/urls.js b/src/editors/data/services/cms/urls.js index e3c7402eb..337129f7e 100644 --- a/src/editors/data/services/cms/urls.js +++ b/src/editors/data/services/cms/urls.js @@ -16,7 +16,9 @@ export const returnUrl = ({ studioEndpointUrl, unitUrl, learningContextId }) => }; export const block = ({ studioEndpointUrl, blockId }) => ( - `${studioEndpointUrl}/xblock/${blockId}` + blockId.includes('block-v1') + ? `${studioEndpointUrl}/xblock/${blockId}` + : `${studioEndpointUrl}/api/xblock/v2/xblocks/${blockId}` ); export const blockAncestor = ({ studioEndpointUrl, blockId }) => ( diff --git a/src/editors/data/services/cms/urls.test.js b/src/editors/data/services/cms/urls.test.js index a389ae008..27ebcd963 100644 --- a/src/editors/data/services/cms/urls.test.js +++ b/src/editors/data/services/cms/urls.test.js @@ -21,7 +21,8 @@ import { describe('cms url methods', () => { const studioEndpointUrl = 'urLgoeStOstudiO'; - const blockId = 'blOckIDTeST123'; + const blockId = 'block-v1-blOckIDTeST123'; + const v2BlockId = 'blOckIDTeST123'; const learningContextId = 'lEarnIngCOntextId123'; const courseId = 'course-v1:courseId123'; const libraryV1Id = 'library-v1:libaryId123'; @@ -62,10 +63,14 @@ describe('cms url methods', () => { }); }); describe('block', () => { - it('returns url with studioEndpointUrl and blockId', () => { + it('returns v1 url with studioEndpointUrl and blockId', () => { expect(block({ studioEndpointUrl, blockId })) .toEqual(`${studioEndpointUrl}/xblock/${blockId}`); }); + it('returns v2 url with studioEndpointUrl and v2BlockId', () => { + expect(block({ studioEndpointUrl, blockId: v2BlockId })) + .toEqual(`${studioEndpointUrl}/api/xblock/v2/xblocks/${v2BlockId}`); + }); }); describe('blockAncestor', () => { it('returns url with studioEndpointUrl, blockId and ancestor query', () => { From 8eb10b7b1286963dee57e61236c923a8186bf9e2 Mon Sep 17 00:00:00 2001 From: Kristin Aoki <42981026+KristinAoki@users.noreply.github.com> Date: Tue, 13 Jun 2023 15:29:04 -0400 Subject: [PATCH 2/6] feat: update returnToUnit to include api response (#346) --- src/editors/data/redux/thunkActions/app.js | 2 +- src/editors/data/redux/thunkActions/app.test.js | 2 +- src/editors/hooks.js | 4 ++-- src/editors/hooks.test.jsx | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/editors/data/redux/thunkActions/app.js b/src/editors/data/redux/thunkActions/app.js index 99c579567..da5acf2c3 100644 --- a/src/editors/data/redux/thunkActions/app.js +++ b/src/editors/data/redux/thunkActions/app.js @@ -70,7 +70,7 @@ export const saveBlock = ({ content, returnToUnit }) => (dispatch) => { content, onSuccess: (response) => { dispatch(actions.app.setSaveResponse(response)); - returnToUnit(); + returnToUnit(response.data)(); }, })); }; diff --git a/src/editors/data/redux/thunkActions/app.test.js b/src/editors/data/redux/thunkActions/app.test.js index bb8457c6a..5710071f9 100644 --- a/src/editors/data/redux/thunkActions/app.test.js +++ b/src/editors/data/redux/thunkActions/app.test.js @@ -138,7 +138,7 @@ describe('app thunkActions', () => { let returnToUnit; let calls; beforeEach(() => { - returnToUnit = jest.fn(); + returnToUnit = jest.fn((response) => () => response); thunkActions.saveBlock({ content: testValue, returnToUnit })(dispatch); calls = dispatch.mock.calls; }); diff --git a/src/editors/hooks.js b/src/editors/hooks.js index c9dc77665..8ce7147ce 100644 --- a/src/editors/hooks.js +++ b/src/editors/hooks.js @@ -21,12 +21,12 @@ export const navigateCallback = ({ destination, analyticsEvent, analytics, -}) => () => { +}) => (response) => { if (process.env.NODE_ENV !== 'development' && analyticsEvent && analytics) { sendTrackEvent(analyticsEvent, analytics); } if (returnFunction) { - returnFunction(); + returnFunction()(response); return; } module.navigateTo(destination); diff --git a/src/editors/hooks.test.jsx b/src/editors/hooks.test.jsx index 5fc21b04a..df1545827 100644 --- a/src/editors/hooks.test.jsx +++ b/src/editors/hooks.test.jsx @@ -74,7 +74,6 @@ describe('hooks', () => { let output; const SAVED_ENV = process.env; const destination = 'hOmE'; - const returnFunction = jest.fn(); beforeEach(() => { jest.resetModules(); process.env = { ...SAVED_ENV }; @@ -102,6 +101,7 @@ describe('hooks', () => { expect(spy).toHaveBeenCalledWith(destination); }); it('should call returnFunction and return null', () => { + const returnFunction = jest.fn(() => (response) => response); output = hooks.navigateCallback({ destination, returnFunction, From 665a53a713338280887a1173c5e3dd6f373ee3ac Mon Sep 17 00:00:00 2001 From: Kristin Aoki <42981026+KristinAoki@users.noreply.github.com> Date: Tue, 13 Jun 2023 16:31:17 -0400 Subject: [PATCH 3/6] feat: add draggable list (#342) --- package-lock.json | 52 +++++++++++++ package.json | 3 + .../DraggableList/DraggableList.jsx | 73 +++++++++++++++++++ .../DraggableList/SortableItem.jsx | 63 ++++++++++++++++ .../sharedComponents/DraggableList/index.jsx | 5 ++ .../DraggableList/messages.js | 11 +++ src/index.jsx | 9 ++- 7 files changed, 215 insertions(+), 1 deletion(-) create mode 100644 src/editors/sharedComponents/DraggableList/DraggableList.jsx create mode 100644 src/editors/sharedComponents/DraggableList/SortableItem.jsx create mode 100644 src/editors/sharedComponents/DraggableList/index.jsx create mode 100644 src/editors/sharedComponents/DraggableList/messages.js diff --git a/package-lock.json b/package-lock.json index a24ac35fa..0bd8284f2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,9 @@ "@codemirror/lint": "^6.2.1", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", + "@dnd-kit/core": "^6.0.8", + "@dnd-kit/sortable": "^7.0.2", + "@dnd-kit/utilities": "^3.2.1", "@edx/browserslist-config": "^1.1.1", "@reduxjs/toolkit": "^1.8.1", "@tinymce/tinymce-react": "^3.14.0", @@ -2130,6 +2133,55 @@ "node": ">=10.0.0" } }, + "node_modules/@dnd-kit/accessibility": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/accessibility/-/accessibility-3.0.1.tgz", + "integrity": "sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/core": { + "version": "6.0.8", + "resolved": "https://registry.npmjs.org/@dnd-kit/core/-/core-6.0.8.tgz", + "integrity": "sha512-lYaoP8yHTQSLlZe6Rr9qogouGUz9oRUj4AHhDQGQzq/hqaJRpFo65X+JKsdHf8oUFBzx5A+SJPUvxAwTF2OabA==", + "dependencies": { + "@dnd-kit/accessibility": "^3.0.0", + "@dnd-kit/utilities": "^3.2.1", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/sortable": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@dnd-kit/sortable/-/sortable-7.0.2.tgz", + "integrity": "sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==", + "dependencies": { + "@dnd-kit/utilities": "^3.2.0", + "tslib": "^2.0.0" + }, + "peerDependencies": { + "@dnd-kit/core": "^6.0.7", + "react": ">=16.8.0" + } + }, + "node_modules/@dnd-kit/utilities": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@dnd-kit/utilities/-/utilities-3.2.1.tgz", + "integrity": "sha512-OOXqISfvBw/1REtkSK2N3Fi2EQiLMlWUlqnOK/UpOISqBZPWpE6TqL+jcPtMOkE8TqYGiURvRdPSI9hltNUjEA==", + "dependencies": { + "tslib": "^2.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" + } + }, "node_modules/@edx/browserslist-config": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@edx/browserslist-config/-/browserslist-config-1.2.0.tgz", diff --git a/package.json b/package.json index ae4fe25bc..fd6c545e7 100644 --- a/package.json +++ b/package.json @@ -67,6 +67,9 @@ "@codemirror/lint": "^6.2.1", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", + "@dnd-kit/core": "^6.0.8", + "@dnd-kit/sortable": "^7.0.2", + "@dnd-kit/utilities": "^3.2.1", "@edx/browserslist-config": "^1.1.1", "@reduxjs/toolkit": "^1.8.1", "@tinymce/tinymce-react": "^3.14.0", diff --git a/src/editors/sharedComponents/DraggableList/DraggableList.jsx b/src/editors/sharedComponents/DraggableList/DraggableList.jsx new file mode 100644 index 000000000..5279fa1ba --- /dev/null +++ b/src/editors/sharedComponents/DraggableList/DraggableList.jsx @@ -0,0 +1,73 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import { + DndContext, + closestCenter, + KeyboardSensor, + PointerSensor, + useSensor, + useSensors, +} from '@dnd-kit/core'; +import { + arrayMove, + SortableContext, + sortableKeyboardCoordinates, + verticalListSortingStrategy, +} from '@dnd-kit/sortable'; + +const DraggableList = ({ + itemList, + setState, + updateOrder, + children, +}) => { + const sensors = useSensors( + useSensor(PointerSensor), + useSensor(KeyboardSensor, { + coordinateGetter: sortableKeyboardCoordinates, + }), + ); + + const handleDragEnd = (event) => { + const { active, over } = event; + if (active.id !== over.id) { + let updatedArray; + setState(() => { + const [activeElement] = itemList.filter(item => item.id === active.id); + const [overElement] = itemList.filter(item => item.id === over.id); + const oldIndex = itemList.indexOf(activeElement); + const newIndex = itemList.indexOf(overElement); + updatedArray = arrayMove(itemList, oldIndex, newIndex); + return updatedArray; + }); + updateOrder()(updatedArray); + } + }; + + return ( + + + {children} + + + ); +}; + +DraggableList.propTypes = { + itemList: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.string.isRequired, + })).isRequired, + setState: PropTypes.func.isRequired, + updateOrder: PropTypes.func.isRequired, + children: PropTypes.node.isRequired, +}; + +export default DraggableList; diff --git a/src/editors/sharedComponents/DraggableList/SortableItem.jsx b/src/editors/sharedComponents/DraggableList/SortableItem.jsx new file mode 100644 index 000000000..aabe37371 --- /dev/null +++ b/src/editors/sharedComponents/DraggableList/SortableItem.jsx @@ -0,0 +1,63 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { intlShape, injectIntl } from '@edx/frontend-platform/i18n'; +import { useSortable } from '@dnd-kit/sortable'; +import { CSS } from '@dnd-kit/utilities'; +import { Icon, IconButtonWithTooltip, Row } from '@edx/paragon'; +import { DragIndicator } from '@edx/paragon/icons'; +import messages from './messages'; + +const SortableItem = ({ + id, + componentStyle, + children, + // injected + intl, +}) => { + const { + attributes, + listeners, + setNodeRef, + transform, + transition, + } = useSortable({ id }); + + const style = { + transform: CSS.Transform.toString(transform), + transition, + ...componentStyle, + }; + + return ( + + {children} + + + ); +}; +SortableItem.defaultProps = { + componentStyle: null, +}; +SortableItem.propTypes = { + id: PropTypes.string.isRequired, + children: PropTypes.node.isRequired, + componentStyle: PropTypes.shape({}), + // injected + intl: intlShape.isRequired, +}; + +export default injectIntl(SortableItem); diff --git a/src/editors/sharedComponents/DraggableList/index.jsx b/src/editors/sharedComponents/DraggableList/index.jsx new file mode 100644 index 000000000..3055c1fa1 --- /dev/null +++ b/src/editors/sharedComponents/DraggableList/index.jsx @@ -0,0 +1,5 @@ +import DraggableList from './DraggableList'; +import SortableItem from './SortableItem'; + +export { SortableItem }; +export default DraggableList; diff --git a/src/editors/sharedComponents/DraggableList/messages.js b/src/editors/sharedComponents/DraggableList/messages.js new file mode 100644 index 000000000..3a7263280 --- /dev/null +++ b/src/editors/sharedComponents/DraggableList/messages.js @@ -0,0 +1,11 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + tooltipContent: { + id: 'authoring.draggableList.tooltip.content', + defaultMessage: 'Drag to reorder', + description: 'Tooltip content for drag indicator icon', + }, +}); + +export default messages; diff --git a/src/index.jsx b/src/index.jsx index 847deec60..86bfdef30 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -2,6 +2,13 @@ import Placeholder from './Placeholder'; import messages from './i18n/index'; import EditorPage from './editors/EditorPage'; import VideoSelectorPage from './editors/VideoSelectorPage'; +import DraggableList, { SortableItem } from './editors/sharedComponents/DraggableList'; -export { messages, EditorPage, VideoSelectorPage }; +export { + messages, + EditorPage, + VideoSelectorPage, + DraggableList, + SortableItem, +}; export default Placeholder; From 83f034e5004ad72dbb68dfa5b0a2e7b0b6a58394 Mon Sep 17 00:00:00 2001 From: Raymond Zhou <56318341+rayzhou-bit@users.noreply.github.com> Date: Wed, 14 Jun 2023 12:04:44 -0400 Subject: [PATCH 4/6] feat: save spacing for richText parser (#348) --- .../ProblemEditor/data/OLXParser.js | 111 +++++++------- .../ProblemEditor/data/OLXParser.test.js | 23 +-- .../data/mockData/olxTestData.js | 139 +++++++++++------- src/editors/data/services/cms/mockApi.js | 16 +- 4 files changed, 170 insertions(+), 119 deletions(-) diff --git a/src/editors/containers/ProblemEditor/data/OLXParser.js b/src/editors/containers/ProblemEditor/data/OLXParser.js index 984c3429b..8c44dc28b 100644 --- a/src/editors/containers/ProblemEditor/data/OLXParser.js +++ b/src/editors/containers/ProblemEditor/data/OLXParser.js @@ -28,6 +28,18 @@ export const nonQuestionKeys = [ 'textline', ]; +export const richTextFormats = [ + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'div', + 'p', + 'pre', +]; + export const responseKeys = [ 'multiplechoiceresponse', 'numericalresponse', @@ -62,57 +74,52 @@ export const stripNonTextTags = ({ input, tag }) => { export class OLXParser { constructor(olxString) { - this.problem = {}; - this.questionData = {}; - this.richTextProblem = {}; - const richTextOptions = { + // There are two versions of the parsed XLM because the fields using tinymce require the order + // of the parsed data and spacing values to be preserved. However, all the other widgets need + // the data grouped by the wrapping tag. Examples of the parsed format can be found here: + // https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/docs/v4/2.XMLparseOptions.md + const baseParserOptions = { ignoreAttributes: false, - alwaysCreateTextNode: true, numberParseOptions: { leadingZeros: false, hex: false, }, - preserveOrder: true, processEntities: false, }; + + // Base Parser + this.problem = {}; const parserOptions = { - ignoreAttributes: false, + ...baseParserOptions, alwaysCreateTextNode: true, - numberParseOptions: { - leadingZeros: false, - hex: false, - }, - processEntities: false, }; const builderOptions = { - ignoreAttributes: false, - numberParseOptions: { - leadingZeros: false, - hex: false, - }, - processEntities: false, + ...baseParserOptions, }; - const richTextBuilderOptions = { - ignoreAttributes: false, - numberParseOptions: { - leadingZeros: false, - hex: false, - }, - preserveOrder: true, - processEntities: false, - }; - // There are two versions of the parsed XLM because the fields using tinymce require the order - // of the parsed data to be preserved. However, all the other widgets need the data grouped by - // the wrapping tag. - const richTextParser = new XMLParser(richTextOptions); const parser = new XMLParser(parserOptions); this.builder = new XMLBuilder(builderOptions); - this.richTextBuilder = new XMLBuilder(richTextBuilderOptions); this.parsedOLX = parser.parse(olxString); - this.richTextOLX = richTextParser.parse(olxString); if (_.has(this.parsedOLX, 'problem')) { this.problem = this.parsedOLX.problem; - this.questionData = this.richTextOLX[0].problem; + } + + // Parser with `preservedOrder: true` and `trimValues: false` + this.richTextProblem = []; + const richTextOptions = { + ...baseParserOptions, + alwaysCreateTextNode: true, + preserveOrder: true, + trimValues: false, + }; + const richTextBuilderOptions = { + ...baseParserOptions, + preserveOrder: true, + trimValues: false, + }; + const richTextParser = new XMLParser(richTextOptions); + this.richTextBuilder = new XMLBuilder(richTextBuilderOptions); + this.richTextOLX = richTextParser.parse(olxString); + if (_.has(this.parsedOLX, 'problem')) { this.richTextProblem = this.richTextOLX[0].problem; } } @@ -462,7 +469,7 @@ export class OLXParser { * @return {string} string of OLX */ parseQuestions(problemType) { - const problemArray = _.get(this.questionData[0], problemType) || this.questionData; + const problemArray = _.get(this.richTextProblem[0], problemType) || this.richTextProblem; const questionArray = []; problemArray.forEach(tag => { @@ -478,7 +485,7 @@ export class OLXParser { */ tag[tagName].forEach(subTag => { const subTagName = Object.keys(subTag)[0]; - if (subTagName === 'label' || subTagName === 'description') { + if (subTagName === 'label' || subTagName === 'description' || richTextFormats.includes(subTagName)) { questionArray.push(subTag); } }); @@ -503,11 +510,13 @@ export class OLXParser { if (objKeys.includes('demandhint')) { const currentDemandHint = obj.demandhint; currentDemandHint.forEach(hint => { - const hintValue = this.richTextBuilder.build(hint.hint); - hintsObject.push({ - id: hintsObject.length, - value: hintValue, - }); + if (Object.keys(hint).includes('hint')) { + const hintValue = this.richTextBuilder.build(hint.hint); + hintsObject.push({ + id: hintsObject.length, + value: hintValue, + }); + } }); } }); @@ -526,21 +535,17 @@ export class OLXParser { getSolutionExplanation(problemType) { if (!_.has(this.problem, `${problemType}.solution`) && !_.has(this.problem, 'solution')) { return null; } const [problemBody] = this.richTextProblem.filter(section => Object.keys(section).includes(problemType)); - let { solution } = problemBody[problemType].pop(); - const { div } = solution[0]; - if (solution.length === 1 && div) { - div.forEach((block) => { - const [key] = Object.keys(block); - const [value] = block[key]; - if ((key === 'p' || key === 'h2') - && (_.get(value, '#text', null) === 'Explanation') - ) { - div.shift(); + const [solutionBody] = problemBody[problemType].filter(section => Object.keys(section).includes('solution')); + const [divBody] = solutionBody.solution.filter(section => Object.keys(section).includes('div')); + const solutionArray = []; + if (divBody && divBody.div) { + divBody.div.forEach(tag => { + if (_.get(Object.values(tag)[0][0], '#text', null) !== 'Explanation') { + solutionArray.push(tag); } }); - solution = div; } - const solutionString = this.richTextBuilder.build(solution); + const solutionString = this.richTextBuilder.build(solutionArray); return solutionString; } diff --git a/src/editors/containers/ProblemEditor/data/OLXParser.test.js b/src/editors/containers/ProblemEditor/data/OLXParser.test.js index 6f785c906..d3922330a 100644 --- a/src/editors/containers/ProblemEditor/data/OLXParser.test.js +++ b/src/editors/containers/ProblemEditor/data/OLXParser.test.js @@ -1,7 +1,6 @@ import { OLXParser } from './OLXParser'; import { checkboxesOLXWithFeedbackAndHintsOLX, - getCheckboxesOLXWithFeedbackAndHintsOLX, dropdownOLXWithFeedbackAndHintsOLX, numericInputWithFeedbackAndHintsOLX, textInputWithFeedbackAndHintsOLX, @@ -21,6 +20,7 @@ import { labelDescriptionQuestionOLX, htmlEntityTestOLX, numberParseTestOLX, + solutionExplanationTest, } from './mockData/olxTestData'; import { ProblemTypeKeys } from '../../../data/constants/problem'; @@ -261,13 +261,13 @@ describe('OLXParser', () => { const problemType = olxparser.getProblemType(); const question = olxparser.parseQuestions(problemType); it('should return an empty string for question', () => { - expect(question).toBe(blankQuestionOLX.question); + expect(question.trim()).toBe(blankQuestionOLX.question); }); }); describe('given a simple problem olx', () => { const question = textInputOlxParser.parseQuestions('stringresponse'); it('should return a string of HTML', () => { - expect(question).toEqual(textInputWithFeedbackAndHintsOLX.question); + expect(question.trim()).toEqual(textInputWithFeedbackAndHintsOLX.question); }); }); describe('given olx with html entities', () => { @@ -275,7 +275,7 @@ describe('OLXParser', () => { const problemType = olxparser.getProblemType(); const question = olxparser.parseQuestions(problemType); it('should not encode html entities', () => { - expect(question).toEqual(htmlEntityTestOLX.question); + expect(question.trim()).toEqual(htmlEntityTestOLX.question); }); }); describe('given olx with styled content', () => { @@ -283,7 +283,7 @@ describe('OLXParser', () => { const problemType = olxparser.getProblemType(); const question = olxparser.parseQuestions(problemType); it('should pase/build correct styling', () => { - expect(question).toBe(styledQuestionOLX.question); + expect(question.trim()).toBe(styledQuestionOLX.question); }); }); describe('given olx with label and description tags inside response tag', () => { @@ -291,20 +291,25 @@ describe('OLXParser', () => { const problemType = olxparser.getProblemType(); const question = olxparser.parseQuestions(problemType); it('should append the label/description to the question', () => { - expect(question).toBe(labelDescriptionQuestionOLX.question); + expect(question.trim()).toBe(labelDescriptionQuestionOLX.question); }); }); }); describe('getSolutionExplanation()', () => { describe('for checkbox questions', () => { test('should parse text in p tags', () => { - const { rawOLX } = getCheckboxesOLXWithFeedbackAndHintsOLX(); - const olxparser = new OLXParser(rawOLX); + const olxparser = new OLXParser(checkboxesOLXWithFeedbackAndHintsOLX.rawOLX); const problemType = olxparser.getProblemType(); const explanation = olxparser.getSolutionExplanation(problemType); - const expected = getCheckboxesOLXWithFeedbackAndHintsOLX().solutionExplanation; + const expected = checkboxesOLXWithFeedbackAndHintsOLX.solutionExplanation; expect(explanation.replace(/\s/g, '')).toBe(expected.replace(/\s/g, '')); }); }); + it('should parse text with proper spacing', () => { + const olxparser = new OLXParser(solutionExplanationTest.rawOLX); + const problemType = olxparser.getProblemType(); + const explanation = olxparser.getSolutionExplanation(problemType); + expect(explanation).toBe(solutionExplanationTest.solutionExplanation); + }); }); }); diff --git a/src/editors/containers/ProblemEditor/data/mockData/olxTestData.js b/src/editors/containers/ProblemEditor/data/mockData/olxTestData.js index 43a723582..ae3fd2d58 100644 --- a/src/editors/containers/ProblemEditor/data/mockData/olxTestData.js +++ b/src/editors/containers/ProblemEditor/data/mockData/olxTestData.js @@ -1,4 +1,7 @@ -export const getCheckboxesOLXWithFeedbackAndHintsOLX = () => ({ +/* eslint-disable */ +// lint is disabled for this file due to strict spacing + +export const checkboxesOLXWithFeedbackAndHintsOLX = { rawOLX: `

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.

@@ -8,12 +11,12 @@ export const getCheckboxesOLXWithFeedbackAndHintsOLX = () => ({

a correct answer

You can specify optional feedback that appears after the learner selects and submits this answer.

You can specify optional feedback that appears after the learner clears and submits this answer.

-
+

an incorrect answer

an incorrect answer

You can specify optional feedback for none, all, or a subset of the answers.

You can specify optional feedback for selected answers, cleared answers, or both.

-
+

a correct answer

You can specify optional feedback for a combination of answers which appears after the specified set of answers is submitted. You can specify optional feedback for one, several, or all answer combinations. @@ -56,7 +59,7 @@ export const getCheckboxesOLXWithFeedbackAndHintsOLX = () => ({ answers: [ { id: 'A', - title: '

a correct answer

', + title: `

a correct answer

\n \n \n `, correct: true, selectedFeedback: '

You can specify optional feedback that appears after the learner selects and submits this answer.

', unselectedFeedback: '

You can specify optional feedback that appears after the learner clears and submits this answer.

', @@ -68,7 +71,7 @@ export const getCheckboxesOLXWithFeedbackAndHintsOLX = () => ({ }, { id: 'C', - title: '

an incorrect answer

', + title: `

an incorrect answer

\n \n \n `, correct: false, selectedFeedback: '

You can specify optional feedback for none, all, or a subset of the answers.

', unselectedFeedback: '

You can specify optional feedback for selected answers, cleared answers, or both.

', @@ -103,45 +106,43 @@ export const getCheckboxesOLXWithFeedbackAndHintsOLX = () => ({ }, question: '

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.

You can add an optional tip or note related to the prompt like this.', buildOLX: ` - -

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.

- - You can add an optional tip or note related to the prompt like this. - - -

a correct answer

You can specify optional feedback that appears after the learner selects and submits this answer.

-

You can specify optional feedback that appears after the learner clears and submits this answer.

-
-

an incorrect answer

- -

an incorrect answer

You can specify optional feedback for none, all, or a subset of the answers.

-

You can specify optional feedback for selected answers, cleared answers, or both.

-
-

a correct answer

- You can specify optional feedback for a combination of answers which appears after the specified set of answers is submitted. - You can specify optional feedback for one, several, or all answer combinations. -
- -
-

Explanation

-

+ +

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.

+ + You can add an optional tip or note related to the prompt like this. + + +

a correct answer

You can specify optional feedback that appears after the learner selects and submits this answer.

+

You can specify optional feedback that appears after the learner clears and submits this answer.

+
+

an incorrect answer

+ +

an incorrect answer

You can specify optional feedback for none, all, or a subset of the answers.

+

You can specify optional feedback for selected answers, cleared answers, or both.

+
+

a correct answer

+ You can specify optional feedback for a combination of answers which appears after the specified set of answers is submitted. + You can specify optional feedback for one, several, or all answer combinations. +
+ +
+

Explanation

+

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. -

-

-
-
- - -

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.

-

If you add more than one hint, a different hint appears each time learners select the hint button.

-
- -`, -}); - -export const checkboxesOLXWithFeedbackAndHintsOLX = getCheckboxesOLXWithFeedbackAndHintsOLX({}); +

+

+
+
+
+ +

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.

+

If you add more than one hint, a different hint appears each time learners select the hint button.

+
+
+ `, +}; export const multipleChoiceWithoutAnswers = { rawOLX: ` @@ -199,7 +200,7 @@ export const multipleChoiceSingleAnswer = { answers: [ { id: 'A', - title: '

a correct answer

image withcaption.
', + title: `

a correct answer

image with caption.
\n \n \n `, correct: true, selectedFeedback: '

You can specify optional feedback that appears after the learner selects and submits this answer.

', unselectedFeedback: '

You can specify optional feedback that appears after the learner clears and submits this answer.

', @@ -314,11 +315,9 @@ export const multipleChoiceWithFeedbackAndHintsOLX = { You can add an optional tip or note related to the prompt like this. -

an incorrect answer

You can specify optional feedback like this, which appears after this answer is submitted.

-
+

an incorrect answer

You can specify optional feedback like this, which appears after this answer is submitted.

the correct answer

-

an incorrect answer

You can specify optional feedback for none, a subset, or all of the answers. - +

an incorrect answer

You can specify optional feedback for none, a subset, or all of the answers.

You can add a solution

@@ -540,7 +539,7 @@ export const textInputWithFeedbackAndHintsOLX = { }, }, }, - question: '

You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.

You can add an optional tip or note related to the prompt like this.', + question: '

You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.

You can add an optional tip or note related to the prompt like this. ', buildOLX: `

You can use this template as a guide to the simple editor markdown and OLX markup to use for text input with hints and feedback problems. Edit this component to replace this template with your own assessment.

@@ -726,7 +725,13 @@ export const styledQuestionOLX = {
`, - question: '

test

', + question: `

+ + + test + + +

`, }; export const shuffleProblemOLX = { @@ -763,7 +768,8 @@ export const labelDescriptionQuestionOLX = {
`, - question: '

boiling eggs: water system

Watch out, boiling water is hot', + question: `

boiling eggs: water system

+ Watch out, boiling water is hot`, }; export const htmlEntityTestOLX = { @@ -798,9 +804,7 @@ export const htmlEntityTestOLX = { }, ], }, - // eslint-disable-next-line - question: `

What is the content of the register x2 after executing the following three lines of instructions?

Address          assembly instructions

0x0              addi x1, x0, 1

0x4              slli x2, x1, 4

0x8              sub x1, x2, x1

`, - // eslint-disable-next-line + question: `

What is the content of the register x2 after executing the following three lines of instructions?

Address          assembly instructions

0x0              addi x1, x0, 1

0x4              slli x2, x1, 4

0x8              sub x1, x2, x1

`, solutionExplanation: `

Address          assembly instructions    comment

0x0              addi x1, x0, 1           x1 = 0x1

0x4              slli x2, x1, 4           x2 = x1 << 4 = 0x10

0x8              sub x1, x2, x1           x1 = x2 - x1 = 0x10 - 0x01 = 0xf

`, }; @@ -820,22 +824,22 @@ export const numberParseTestOLX = { answers: [ { id: 'A', - title: `0x10`, // eslint-disable-line + title: `0x10`, correct: false, }, { id: 'B', - title: `0x0f`, // eslint-disable-line + title: `0x0f`, correct: true, }, { id: 'C', - title: `0x07`, // eslint-disable-line + title: `0x07`, correct: false, }, { id: 'D', - title: `0009`, // eslint-disable-line + title: `0009`, correct: false, }, ], @@ -853,3 +857,26 @@ export const numberParseTestOLX = {
`, }; + +export const solutionExplanationTest = { + rawOLX: ` + How 99 long is the array q after the following loop runs? +
for i = 1:99
+      q(2*i - 1) = i;
+      end
+ + +
`, + solutionExplanation: `\n + This loop will iterate 99 times, but the length of q will not be 99 due to indexing with the value 2*i -1. On the last iteration, i = 99, so 2*i - 1 = 2*78 - 1 = 197. This will be the last position filled in q, so the answer is 197.\n `, +}; diff --git a/src/editors/data/services/cms/mockApi.js b/src/editors/data/services/cms/mockApi.js index bb407f4e0..bea7fc463 100644 --- a/src/editors/data/services/cms/mockApi.js +++ b/src/editors/data/services/cms/mockApi.js @@ -36,7 +36,21 @@ export const fetchBlockById = ({ blockId, studioEndpointUrl }) => { } else if (blockId === 'problem-block-id') { data = { data: ` - `, + +

What is the content of the register x2 after executing the following three lines of instructions?

+

Address          assembly instructions
0x0              addi x1, x0, 1
0x4              slli x2, x1, 4
0x8              sub x1, x2, x1

+ + answerA + answerB + + +
+

Explanation

+

Address          assembly instructions    comment
0x0              addi x1, x0, 1           x1 = 0x1
0x4              slli x2, x1, 4           x2 = x1 << 4 = 0x10
0x8              sub x1, x2, x1           x1 = x2 - x1 = 0x10 - 0x01 = 0xf

+
+
+
+ `, display_name: 'Dropdown', metadata: { markdown: `You can use this template as a guide to the simple editor markdown and OLX markup to use for dropdown problems. Edit this component to replace this template with your own assessment. From b0fef766eb583d06cf20854e8de0f50c0615ff1e Mon Sep 17 00:00:00 2001 From: Kristin Aoki <42981026+KristinAoki@users.noreply.github.com> Date: Thu, 15 Jun 2023 15:28:12 -0400 Subject: [PATCH 5/6] feat: add ErrorAlert to exports (#349) --- src/index.jsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/index.jsx b/src/index.jsx index 86bfdef30..3dcab4445 100644 --- a/src/index.jsx +++ b/src/index.jsx @@ -3,6 +3,7 @@ import messages from './i18n/index'; import EditorPage from './editors/EditorPage'; import VideoSelectorPage from './editors/VideoSelectorPage'; import DraggableList, { SortableItem } from './editors/sharedComponents/DraggableList'; +import ErrorAlert from './editors/sharedComponents/ErrorAlerts/ErrorAlert'; export { messages, @@ -10,5 +11,6 @@ export { VideoSelectorPage, DraggableList, SortableItem, + ErrorAlert, }; export default Placeholder; From 8a2c725edad93325031a96d9969c37d63edd3adc Mon Sep 17 00:00:00 2001 From: Mashal Malik <107556986+Mashal-m@users.noreply.github.com> Date: Wed, 21 Jun 2023 14:03:50 +0500 Subject: [PATCH 6/6] chore: update lock version file (#351) --- .github/workflows/lockfileversion-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lockfileversion-check.yml b/.github/workflows/lockfileversion-check.yml index fa9371348..d96f1b441 100644 --- a/.github/workflows/lockfileversion-check.yml +++ b/.github/workflows/lockfileversion-check.yml @@ -10,4 +10,4 @@ on: jobs: version-check: - uses: edx/.github/.github/workflows/lockfileversion-check-v3.yml@master + uses: openedx/.github/.github/workflows/lockfileversion-check-v3.yml@master