diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/__snapshots__/index.test.jsx.snap new file mode 100644 index 000000000..ad820d715 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/__snapshots__/index.test.jsx.snap @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`SolutionWidget render snapshot: renders correct default 1`] = ` +
+
+ +
+
+ +
+ <[object Object] + editorType="solution" + id="solution" + minHeight={150} + placeholder="Enter your explanation" + setEditorRef={[MockFunction prepareEditorRef.setEditorRef]} + textValue="This is my question" + /> +
+`; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.jsx new file mode 100644 index 000000000..8e164bb55 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.jsx @@ -0,0 +1,51 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n'; +import { selectors } from '../../../../../data/redux'; +import { messages } from './messages'; + +import TinyMceWidget from '../../../../../sharedComponents/TinyMceWidget'; +import { prepareEditorRef } from '../../../../../sharedComponents/TinyMceWidget/hooks'; + +export const ExplanationWidget = ({ + // redux + settings, + // injected + intl, +}) => { + const { editorRef, refReady, setEditorRef } = prepareEditorRef(); + if (!refReady) { return null; } + return ( +
+
+ +
+
+ +
+ +
+ ); +}; + +ExplanationWidget.propTypes = { + // redux + // eslint-disable-next-line + settings: PropTypes.any.isRequired, + // injected + intl: intlShape.isRequired, +}; +export const mapStateToProps = (state) => ({ + settings: selectors.problem.settings(state), +}); + +export default injectIntl(connect(mapStateToProps)(ExplanationWidget)); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.test.jsx new file mode 100644 index 000000000..cef3d4ed5 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.test.jsx @@ -0,0 +1,40 @@ +/* eslint-disable react/prop-types */ +import React from 'react'; +import { shallow } from 'enzyme'; +import { formatMessage } from '../../../../../../testUtils'; +import { selectors } from '../../../../../data/redux'; +import { ExplanationWidget, mapStateToProps } from '.'; + +jest.mock('../../../../../data/redux', () => ({ + selectors: { + problem: { + settings: jest.fn(state => ({ question: state })), + }, + }, +})); + +jest.mock('../../../../../sharedComponents/TinyMceWidget/hooks', () => ({ + prepareEditorRef: jest.fn(() => ({ + refReady: true, + setEditorRef: jest.fn().mockName('prepareEditorRef.setEditorRef'), + })), +})); + +describe('SolutionWidget', () => { + const props = { + settings: { solutionExplanation: 'This is my question' }, + // injected + intl: { formatMessage }, + }; + describe('render', () => { + test('snapshot: renders correct default', () => { + expect(shallow()).toMatchSnapshot(); + }); + }); + describe('mapStateToProps', () => { + const testState = { A: 'pple', B: 'anana', C: 'ucumber' }; + test('question from problem.question', () => { + expect(mapStateToProps(testState).settings).toEqual(selectors.problem.settings(testState)); + }); + }); +}); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/messages.js b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/messages.js new file mode 100644 index 000000000..ef74a6052 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/messages.js @@ -0,0 +1,19 @@ +export const messages = { + solutionWidgetTitle: { + id: 'authoring.problemEditor.explanationwidget.explanationWidgetTitle', + defaultMessage: 'Explanation', + description: 'Explanation Title', + }, + solutionDescriptionText: { + id: 'authoring.problemEditor.solutionwidget.solutionDescriptionText', + defaultMessage: 'Provide an explantion for the correct answer', + description: 'Description of the solution widget', + }, + placeholder: { + id: 'authoring.problemEditor.questionwidget.placeholder', + defaultMessage: 'Enter your explanation', + description: 'Placeholder text for tinyMCE editor', + }, +}; + +export default messages; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/__snapshots__/index.test.jsx.snap index 47e48b425..6a0b69249 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/__snapshots__/index.test.jsx.snap @@ -2,7 +2,7 @@ exports[`QuestionWidget render snapshot: renders correct default 1`] = `
+
@@ -45,8 +44,4 @@ export const mapStateToProps = (state) => ({ question: selectors.problem.question(state), }); -export const mapDispatchToProps = { - updateQuestion: actions.problem.updateQuestion, -}; - -export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(QuestionWidget)); +export default injectIntl(connect(mapStateToProps)(QuestionWidget)); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.scss b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.scss deleted file mode 100644 index 9292bef5f..000000000 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.scss +++ /dev/null @@ -1,28 +0,0 @@ -.question-widget { - .tox-tinymce { - border-radius: 0.375rem; - } - - .tox { - .tox-toolbar__primary { - background: none; - } - } - - .tox-statusbar { - border-top: none; - } - - .tox-toolbar__group:not(:last-of-type) { - // TODO: Find a way to override the border without !important - border-right: none !important; - - &:after { - content: ''; - position: relative; - left: 5px; - border: 1px solid #eae6e5; - height: 24px; - } - } -} diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.jsx index 50e99f27a..561602f53 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.jsx @@ -2,8 +2,8 @@ import React from 'react'; import { shallow } from 'enzyme'; import { formatMessage } from '../../../../../../testUtils'; -import { actions, selectors } from '../../../../../data/redux'; -import { QuestionWidget, mapStateToProps, mapDispatchToProps } from '.'; +import { selectors } from '../../../../../data/redux'; +import { QuestionWidget, mapStateToProps } from '.'; jest.mock('../../../../../data/redux', () => ({ actions: { @@ -49,9 +49,4 @@ describe('QuestionWidget', () => { expect(mapStateToProps(testState).question).toEqual(selectors.problem.question(testState)); }); }); - describe('mapDispatchToProps', () => { - test('updateField from actions.problem.updateQuestion', () => { - expect(mapDispatchToProps.updateQuestion).toEqual(actions.problem.updateQuestion); - }); - }); }); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.js index 8fdfc33f8..739eae823 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.js +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.js @@ -171,14 +171,9 @@ export const useAnswerSettings = (showAnswer, updateSettings) => { updateSettings({ showAnswer: { ...showAnswer, afterAttempts: attempts } }); }; - const handleExplanationChange = (content) => { - updateSettings({ solutionExplanation: content }); - }; - return { handleShowAnswerChange, handleAttemptsChange, - handleExplanationChange, showAttempts, }; }; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.test.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.test.js index 1db1776aa..48de990df 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.test.js +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.test.js @@ -236,11 +236,6 @@ describe('Problem settings hooks', () => { output.handleAttemptsChange({ target: { value } }); expect(updateSettings).toHaveBeenCalledWith({ showAnswer: { ...showAnswer, afterAttempts: parseInt(value) } }); }); - test('handleExplanationChange should update settings', () => { - const value = 'explanation'; - output.handleExplanationChange(value); - expect(updateSettings).toHaveBeenCalledWith({ solutionExplanation: value }); - }); }); describe('Timer card hooks', () => { diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.jsx index 45f57a8e6..e63433a79 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.jsx @@ -93,7 +93,6 @@ export const SettingsWidget = ({
{ diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ShowAnswerCard.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ShowAnswerCard.jsx index 8748cfd8a..2de5a83d4 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ShowAnswerCard.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ShowAnswerCard.jsx @@ -8,11 +8,9 @@ import { ShowAnswerTypes, ShowAnswerTypesKeys } from '../../../../../../data/con import { selectors } from '../../../../../../data/redux'; import messages from '../messages'; import { useAnswerSettings } from '../hooks'; -import ExpandableTextArea from '../../../../../../sharedComponents/ExpandableTextArea'; export const ShowAnswerCard = ({ showAnswer, - solutionExplanation, updateSettings, // inject intl, @@ -23,7 +21,6 @@ export const ShowAnswerCard = ({ const { handleShowAnswerChange, handleAttemptsChange, - handleExplanationChange, showAttempts, } = useAnswerSettings(showAnswer, updateSettings); @@ -69,28 +66,10 @@ export const ShowAnswerCard = ({ ); - const explanationSection = ( - <> -
- - - -
- - - ); - return ( {showAnswerSection} diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/__snapshots__/ShowAnswerCard.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/__snapshots__/ShowAnswerCard.test.jsx.snap index 46d2c884b..72c743950 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/__snapshots__/ShowAnswerCard.test.jsx.snap +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/__snapshots__/ShowAnswerCard.test.jsx.snap @@ -3,33 +3,8 @@ exports[`ShowAnswerCard snapshot snapshot: show answer setting card 1`] = ` -
- - - -
- - , - }, - ] - } - hasExpandableTextArea={true} + extraSections={Array []} + hasExpandableTextArea={false} summary="After Some Number of Attempts" title="Show answer" > diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/__snapshots__/index.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/__snapshots__/index.test.jsx.snap index 245bb0ad1..c22fd2c4c 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/__snapshots__/index.test.jsx.snap @@ -42,6 +42,7 @@ exports[`EditorProblemView component renders simple view 1`] = ` + + )} diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/index.scss b/src/editors/containers/ProblemEditor/components/EditProblemView/index.scss index 693eadd2f..1d97f34e1 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/index.scss +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/index.scss @@ -9,6 +9,35 @@ } } +.tinyMceWidget { + .tox-tinymce { + border-radius: 0.375rem; + } + + .tox { + .tox-toolbar__primary { + background: none; + } + } + + .tox-statusbar { + border-top: none; + } + + .tox-toolbar__group:not(:last-of-type) { + // TODO: Find a way to override the border without !important + border-right: none !important; + + &:after { + content: ''; + position: relative; + left: 5px; + border: 1px solid #eae6e5; + height: 24px; + } + } +} + // overrides paragon in order to make checked radio and checkboxes green $checked-color: '%230D7D4D'; diff --git a/src/editors/containers/ProblemEditor/data/OLXParser.js b/src/editors/containers/ProblemEditor/data/OLXParser.js index 0693c79f6..94756a475 100644 --- a/src/editors/containers/ProblemEditor/data/OLXParser.js +++ b/src/editors/containers/ProblemEditor/data/OLXParser.js @@ -350,7 +350,20 @@ export class OLXParser { getSolutionExplanation(problemType) { if (!_.has(this.problem, `${problemType}.solution`)) { return null; } - const solution = _.get(this.problem, `${problemType}.solution`); + let solution = _.get(this.problem, `${problemType}.solution`); + const wrapper = Object.keys(solution)[0]; + if (Object.keys(solution).length === 1 && wrapper === 'div') { + const parsedSolution = {}; + Object.entries(solution.div).forEach(([key, value]) => { + if (key !== '@_class') { + if (key === 'p') { + value.shift(); + } + parsedSolution[key] = value; + } + }); + solution = parsedSolution; + } const solutionString = this.builder.build(solution); return solutionString; } diff --git a/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js b/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js index 93c2a3567..ea63ed5c7 100644 --- a/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js +++ b/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js @@ -56,10 +56,16 @@ class ReactStateOLXParser { addSolution() { const { solution } = this.editorObject; if (!solution || solution.length <= 0) { return {}; } + const solutionTitle = { '#text': 'Explanation' }; const parsedSolution = this.parser.parse(solution); + const paragraphs = parsedSolution.p; + const withWrapper = _.isArray(paragraphs) ? [solutionTitle, ...paragraphs] : [solutionTitle, paragraphs]; const solutionObject = { solution: { - ...parsedSolution, + div: { + '@_class': 'detailed-solution', + p: withWrapper, + }, }, }; return solutionObject; diff --git a/src/editors/containers/ProblemEditor/data/mockData/editorTestData.js b/src/editors/containers/ProblemEditor/data/mockData/editorTestData.js index 38ab4f4c6..0f9ed33e1 100644 --- a/src/editors/containers/ProblemEditor/data/mockData/editorTestData.js +++ b/src/editors/containers/ProblemEditor/data/mockData/editorTestData.js @@ -1,13 +1,12 @@ export const checkboxesWithFeedbackAndHints = { - solution: `
-

Explanation

+ solution: `

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.

-
`, + `, 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.', answers: { A: '

a correct answer

', @@ -52,7 +51,7 @@ export const dropdownWithFeedbackAndHints = { }; export const multipleChoiceWithFeedbackAndHints = { - solution: '', + solution: '

You can add a solution

', question: '

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

You can add an optional tip or note related to the prompt like this.', answers: { A: '

an incorrect answer

', diff --git a/src/editors/containers/ProblemEditor/data/mockData/olxTestData.js b/src/editors/containers/ProblemEditor/data/mockData/olxTestData.js index ea07668ad..3cbd61a92 100644 --- a/src/editors/containers/ProblemEditor/data/mockData/olxTestData.js +++ b/src/editors/containers/ProblemEditor/data/mockData/olxTestData.js @@ -45,15 +45,13 @@ export const getCheckboxesOLXWithFeedbackAndHintsOLX = () => ({ value: '

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

', }, ], - solutionExplanation: `
-

Explanation

+ solutionExplanation: `

You can form a voltage divider that evenly divides the input voltage with two identically valued resistors, with the sampled voltage taken in between the two.

-

-
`, +

`, data: { answers: [ { @@ -251,6 +249,9 @@ export const multipleChoiceWithFeedbackAndHintsOLX = {

an incorrect answer

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

You can add a solution

+

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.

@@ -266,6 +267,7 @@ export const multipleChoiceWithFeedbackAndHintsOLX = { value: '

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

', }, ], + solutionExplanation: '

You can add a solution

', data: { answers: [ { @@ -302,6 +304,12 @@ export const multipleChoiceWithFeedbackAndHintsOLX = {

an incorrect answer

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

+ +
+

Explanation

+

You can add a solution

+
+

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.

diff --git a/src/editors/data/services/cms/api.js b/src/editors/data/services/cms/api.js index 213a4811b..67ad92eb5 100644 --- a/src/editors/data/services/cms/api.js +++ b/src/editors/data/services/cms/api.js @@ -137,6 +137,7 @@ export const apiMethods = { metadata: { display_name: title }, }; } else if (blockType === 'problem') { + // console.log(type); response = { data: content.olx, category: blockType,