diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/__snapshots__/index.test.jsx.snap index e0cedda10..b32579568 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/__snapshots__/index.test.jsx.snap @@ -7,19 +7,24 @@ exports[`SettingsWidget snapshot snapshot: renders Settings widget page 1`] = `
-
- +
- + +
+
+
- +
- +
- +
- +
-
@@ -87,19 +92,24 @@ exports[`SettingsWidget snapshot snapshot: renders Settings widget page advanced
-
- +
- + +
+
+
- +
- +
- +
- +
-
diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.jsx index 109cb92e2..0a61d122b 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.jsx @@ -13,17 +13,23 @@ import ResetCard from './settingsComponents/ResetCard'; import MatlabCard from './settingsComponents/MatlabCard'; import TimerCard from './settingsComponents/TimerCard'; import TypeCard from './settingsComponents/TypeCard'; +import GeneralFeedbackCard from './settingsComponents/GeneralFeedback/index'; +import GroupFeedbackCard from './settingsComponents/GroupFeedback/index'; import SwitchToAdvancedEditorCard from './settingsComponents/SwitchToAdvancedEditorCard'; import messages from './messages'; import { showAdvancedSettingsCards } from './hooks'; import './index.scss'; +import { ProblemTypeKeys } from '../../../../../data/constants/problem'; +import Randomization from './settingsComponents/Randomization'; // This widget should be connected, grab all settings from store, update them as needed. export const SettingsWidget = ({ problemType, // redux answers, + generalFeedback, + groupFeedbackList, blockTitle, correctAnswerCount, settings, @@ -33,6 +39,30 @@ export const SettingsWidget = ({ updateAnswer, }) => { const { isAdvancedCardsVisible, showAdvancedCards } = showAdvancedSettingsCards(); + + const feedbackCard = () => { + if (problemType === ProblemTypeKeys.ADVANCED) { + return (<>); + } + if ([ProblemTypeKeys.MULTISELECT, ProblemTypeKeys.TEXTINPUT, ProblemTypeKeys.NUMERIC].includes(problemType)) { + return ( +
+
+ ); + } + return ( +
+
+ ); + }; + return (
@@ -52,7 +82,7 @@ export const SettingsWidget = ({
- + {feedbackCard()}
@@ -80,6 +110,13 @@ export const SettingsWidget = ({
+ { + problemType === ProblemTypeKeys.ADVANCED && ( +
+ +
+ ) + }
@@ -103,6 +140,16 @@ SettingsWidget.propTypes = { title: PropTypes.string, unselectedFeedback: PropTypes.string, })).isRequired, + generalFeedback: PropTypes.string.isRequired, + groupFeedbackList: PropTypes.arrayOf( + PropTypes.shape( + { + id: PropTypes.number, + feedback: PropTypes.string, + answers: PropTypes.arrayOf(PropTypes.string), + }, + ), + ).isRequired, blockTitle: PropTypes.string.isRequired, correctAnswerCount: PropTypes.number.isRequired, problemType: PropTypes.string.isRequired, @@ -115,6 +162,8 @@ SettingsWidget.propTypes = { }; const mapStateToProps = (state) => ({ + generalFeedback: selectors.problem.generalFeedback(state), + groupFeedbackList: selectors.problem.groupFeedbackList(state), settings: selectors.problem.settings(state), answers: selectors.problem.answers(state), blockTitle: selectors.app.blockTitle(state), diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.jsx index d2daba09b..8d1ea256f 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.jsx @@ -9,6 +9,18 @@ jest.mock('./hooks', () => ({ showAdvancedSettingsCards: jest.fn(), })); +jest.mock('./settingsComponents/GeneralFeedback', () => 'GeneralFeedback'); +jest.mock('./settingsComponents/GroupFeedback', () => 'GroupFeedback'); +jest.mock('./settingsComponents/Randomization', () => 'Randomization'); +jest.mock('./settingsComponents/HintsCard', () => 'HintsCard'); +jest.mock('./settingsComponents/MatlabCard', () => 'MatlabCard'); +jest.mock('./settingsComponents/ResetCard', () => 'ResetCard'); +jest.mock('./settingsComponents/ScoringCard', () => 'ScoringCard'); +jest.mock('./settingsComponents/ShowAnswerCard', () => 'ShowAnswerCard'); +jest.mock('./settingsComponents/SwitchToAdvancedEditorCard', () => 'SwitchToAdvancedEditorCard'); +jest.mock('./settingsComponents/TimerCard', () => 'TimerCard'); +jest.mock('./settingsComponents/TypeCard', () => 'TypeCard'); + describe('SettingsWidget', () => { const props = { problemType: ProblemTypeKeys.TEXTINPUT, diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/messages.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/messages.js index 09a6007cf..8fa7dfaed 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/messages.js +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/messages.js @@ -205,5 +205,4 @@ export const messages = { description: 'Solution Explanation text', }, }; - export default messages; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/__snapshots__/index.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/__snapshots__/index.test.jsx.snap new file mode 100644 index 000000000..dd2179a6b --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/__snapshots__/index.test.jsx.snap @@ -0,0 +1,34 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RandomizationCard snapshot snapshot: renders general feedback setting card 1`] = ` + +
+ + + +
+ + + +
+`; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/hooks.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/hooks.js new file mode 100644 index 000000000..813d2b48f --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/hooks.js @@ -0,0 +1,35 @@ +import { useState, useEffect } from 'react'; +import _ from 'lodash-es'; +import messages from './messages'; +import * as module from './hooks'; + +export const state = { + summary: (val) => useState(val), +}; + +export const generalFeedbackHooks = (generalFeedback, updateSettings) => { + const [summary, setSummary] = module.state.summary({ + message: messages.noGeneralFeedbackSummary, values: {}, intl: true, + }); + + useEffect(() => { + if (_.isEmpty(generalFeedback)) { + setSummary({ message: messages.noGeneralFeedbackSummary, values: {}, intl: true }); + } else { + setSummary({ + message: generalFeedback, + values: {}, + intl: false, + }); + } + }, [generalFeedback]); + + const handleChange = (event) => { + updateSettings({ generalFeedback: event.target.value }); + }; + + return { + summary, + handleChange, + }; +}; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/hooks.test.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/hooks.test.js new file mode 100644 index 000000000..0cd35cfee --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/hooks.test.js @@ -0,0 +1,43 @@ +import { useEffect } from 'react'; +import { MockUseState } from '../../../../../../../../testUtils'; +import messages from './messages'; +import * as hooks from './hooks'; + +jest.mock('react', () => { + const updateState = jest.fn(); + return { + updateState, + useEffect: jest.fn(), + useState: jest.fn(val => ([{ state: val }, (newVal) => updateState({ val, newVal })])), + }; +}); + +const state = new MockUseState(hooks); + +describe('Problem settings hooks', () => { + let output; + let updateSettings; + let generalFeedback; + beforeEach(() => { + updateSettings = jest.fn(); + generalFeedback = 'sOmE_vAlUe'; + state.mock(); + }); + afterEach(() => { + state.restore(); + useEffect.mockClear(); + }); + describe('Show advanced settings', () => { + beforeEach(() => { + output = hooks.generalFeedbackHooks(generalFeedback, updateSettings); + }); + test('test default state is false', () => { + expect(output.summary.message).toEqual(messages.noGeneralFeedbackSummary); + }); + test('test showAdvancedCards sets state to true', () => { + const mockEvent = { target: { value: 'sOmE_otheR_ValUe' } }; + output.handleChange(mockEvent); + expect(updateSettings).toHaveBeenCalledWith({ generalFeedback: mockEvent.target.value }); + }); + }); +}); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/index.jsx new file mode 100644 index 000000000..168cd2e6c --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/index.jsx @@ -0,0 +1,44 @@ +import React from 'react'; +import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n'; +import { Form } from '@edx/paragon'; +import PropTypes from 'prop-types'; +import SettingsOption from '../../SettingsOption'; +import messages from './messages'; +import { generalFeedbackHooks } from './hooks'; + +export const GeneralFeedbackCard = ({ + generalFeedback, + updateSettings, + // inject + intl, +}) => { + const { summary, handleChange } = generalFeedbackHooks(generalFeedback, updateSettings); + return ( + +
+ + + +
+ + + +
+ ); +}; + +GeneralFeedbackCard.propTypes = { + generalFeedback: PropTypes.string.isRequired, + updateSettings: PropTypes.func.isRequired, + intl: intlShape.isRequired, +}; + +export default injectIntl(GeneralFeedbackCard); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/index.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/index.test.jsx new file mode 100644 index 000000000..572ce2034 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/index.test.jsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { formatMessage } from '../../../../../../../../testUtils'; +import { GeneralFeedbackCard } from './index'; +import { generalFeedbackHooks } from './hooks'; + +jest.mock('./hooks', () => ({ + generalFeedbackHooks: jest.fn(), +})); + +describe('RandomizationCard', () => { + const props = { + generalFeedback: 'sOmE_vAlUE', + updateSettings: jest.fn().mockName('args.updateSettings'), + intl: { formatMessage }, + }; + + const randomizationCardHooksProps = { + summary: { message: { defaultMessage: 'sUmmary' } }, + handleChange: jest.fn().mockName('randomizationCardHooks.handleChange'), + }; + + generalFeedbackHooks.mockReturnValue(randomizationCardHooksProps); + + describe('behavior', () => { + it(' calls generalFeedbackHooks with props when initialized', () => { + shallow(); + expect(generalFeedbackHooks).toHaveBeenCalledWith( + props.generalFeedback, props.updateSettings, + ); + }); + }); + + describe('snapshot', () => { + test('snapshot: renders general feedback setting card', () => { + expect(shallow()).toMatchSnapshot(); + }); + }); +}); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/messages.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/messages.js new file mode 100644 index 000000000..a0fd27af2 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GeneralFeedback/messages.js @@ -0,0 +1,23 @@ +export const messages = { + generalFeebackSettingTitle: { + id: 'authoring.problemeditor.settings.generalFeebackSettingTitle', + defaultMessage: 'General Feedback', + description: 'label for general feedback setting', + }, + generalFeedbackInputLabel: { + id: 'authoring.problemeditor.settings.generalFeedbackInputLabel', + defaultMessage: 'Enter General Feedback', + description: 'label for general feedback input describing rules', + }, + generalFeedbackDescription: { + id: 'authoring.problemeditor.settings.generalFeedbackInputDescription', + defaultMessage: 'Enter the feedback to appear when a student submits a wrong answer. This will be overridden if you add answer-specific feedback.', + description: 'description for general feedback input, clariying useage', + }, + noGeneralFeedbackSummary: { + id: 'authoring.problemeditor.settings.generalFeedback.noFeedbackSummary', + defaultMessage: 'None', + description: 'message which informs use there is no general feedback set.', + }, +}; +export default messages; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/GroupFeedbackRow.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/GroupFeedbackRow.jsx new file mode 100644 index 000000000..6f7016573 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/GroupFeedbackRow.jsx @@ -0,0 +1,76 @@ +import React from 'react'; +import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { + ActionRow, Form, Icon, IconButton, Row, +} from '@edx/paragon'; +import { DeleteOutline } from '@edx/paragon/icons'; +import PropTypes from 'prop-types'; +import messages from '../../messages'; + +export const GroupFeedbackRow = ({ + value, + handleAnswersSelectedChange, + handleFeedbackChange, + handleDelete, + answers, + // injected + intl, +}) => ( + +
+ + +
+ +
+
+ + + {answers.map((letter) => ( + {letter.id} + + ))} + + +
+ +); + +GroupFeedbackRow.propTypes = { + answers: PropTypes.arrayOf(PropTypes.shape({ + correct: PropTypes.bool, + id: PropTypes.string, + selectedFeedback: PropTypes.string, + title: PropTypes.string, + unselectedFeedback: PropTypes.string, + })).isRequired, + handleAnswersSelectedChange: PropTypes.func.isRequired, + handleFeedbackChange: PropTypes.func.isRequired, + handleDelete: PropTypes.func.isRequired, + value: PropTypes.shape({ + id: PropTypes.number.isRequired, + answers: PropTypes.arrayOf(PropTypes.string), + feedback: PropTypes.string, + }).isRequired, + // injected + intl: intlShape.isRequired, +}; + +export default injectIntl(GroupFeedbackRow); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/GroupFeedbackRow.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/GroupFeedbackRow.test.jsx new file mode 100644 index 000000000..9cd2c5efb --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/GroupFeedbackRow.test.jsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { formatMessage } from '../../../../../../../../testUtils'; +import { GroupFeedbackRow } from './GroupFeedbackRow'; + +describe('GroupFeedbackRow', () => { + const props = { + value: { answers: ['A', 'C'], feedback: 'sOmE FeEDBACK' }, + answers: ['A', 'B', 'C', 'D'], + handleAnswersSelectedChange: jest.fn().mockName('handleAnswersSelectedChange'), + handleFeedbackChange: jest.fn().mockName('handleFeedbackChange'), + handleEmptyFeedback: jest.fn().mockName('handleEmptyFeedback'), + handleDelete: jest.fn().mockName('handleDelete'), + intl: { formatMessage }, + }; + + describe('snapshot', () => { + test('snapshot: renders hints row', () => { + expect(shallow()).toMatchSnapshot(); + }); + }); +}); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/__snapshots__/GroupFeedbackRow.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/__snapshots__/GroupFeedbackRow.test.jsx.snap new file mode 100644 index 000000000..19fecd6e9 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/__snapshots__/GroupFeedbackRow.test.jsx.snap @@ -0,0 +1,60 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`GroupFeedbackRow snapshot snapshot: renders hints row 1`] = ` +
+ + +
+ +
+
+ + + + + + + + +
+`; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/__snapshots__/index.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/__snapshots__/index.test.jsx.snap new file mode 100644 index 000000000..4fe5b19af --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/__snapshots__/index.test.jsx.snap @@ -0,0 +1,168 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`HintsCard snapshot snapshot: renders groupFeedbacks setting card multiple groupFeedbacks 1`] = ` + +
+ +
+ + + +
+`; + +exports[`HintsCard snapshot snapshot: renders groupFeedbacks setting card no groupFeedbacks 1`] = ` + +
+ +
+ +
+`; + +exports[`HintsCard snapshot snapshot: renders groupFeedbacks setting card one groupFeedback 1`] = ` + +
+ +
+ + +
+`; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/hooks.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/hooks.js new file mode 100644 index 000000000..3824e53fd --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/hooks.js @@ -0,0 +1,95 @@ +import { useState, useEffect } from 'react'; +import _ from 'lodash-es'; +import messages from './messages'; +import * as module from './hooks'; + +export const state = { + summary: (val) => useState(val), +}; + +export const groupFeedbackCardHooks = (groupFeedbacks, updateSettings, answerslist) => { + const [summary, setSummary] = module.state.summary({ message: messages.noGroupFeedbackSummary, values: {} }); + + useEffect(() => { + if (groupFeedbacks.length === 0) { + setSummary({ message: messages.noGroupFeedbackSummary, values: {} }); + } else { + const feedbacksInList = groupFeedbacks.map(({ answers, feedback }) => { + const answerIDs = answerslist.map((a) => a.id); + const answersString = answers.filter((value) => answerIDs.includes(value)); + return `${answersString} ${feedback}\n`; + }); + setSummary({ + message: messages.groupFeedbackSummary, + values: { groupFeedback: feedbacksInList }, + }); + } + }, [groupFeedbacks, answerslist]); + + const handleAdd = () => { + let newId = 0; + if (!_.isEmpty(groupFeedbacks)) { + newId = Math.max(...groupFeedbacks.map(feedback => feedback.id)) + 1; + } + const groupFeedback = { id: newId, answers: [], feedback: '' }; + const modifiedGroupFeedbacks = [...groupFeedbacks, groupFeedback]; + updateSettings({ groupFeedbackList: modifiedGroupFeedbacks }); + }; + + return { + summary, + handleAdd, + }; +}; + +export const groupFeedbackRowHooks = ({ id, groupFeedbacks, updateSettings }) => { + // Hooks for the answers associated with a groupfeedback + const addSelectedAnswer = ({ value }) => { + const oldGroupFeedback = groupFeedbacks.find(x => x.id === id); + const newAnswers = [...oldGroupFeedback.answers, value]; + const newFeedback = { ...oldGroupFeedback, answers: newAnswers }; + const remainingFeedbacks = groupFeedbacks.filter((item) => (item.id !== id)); + const updatedFeedbackList = [newFeedback, ...remainingFeedbacks].sort((a, b) => a.id - b.id); + + updateSettings({ groupFeedbackList: updatedFeedbackList }); + }; + const removedSelectedAnswer = ({ value }) => { + const oldGroupFeedback = groupFeedbacks.find(x => x.id === id); + const newAnswers = oldGroupFeedback.answers.filter(item => item !== value); + const newFeedback = { ...oldGroupFeedback, answers: newAnswers }; + const remainingFeedbacks = groupFeedbacks.filter((item) => (item.id !== id)); + const updatedFeedbackList = [newFeedback, ...remainingFeedbacks].sort((a, b) => a.id - b.id); + + updateSettings({ groupFeedbackList: updatedFeedbackList }); + }; + const handleAnswersSelectedChange = (event) => { + const { checked, value } = event.target; + if (checked) { + addSelectedAnswer({ value }); + } else { + removedSelectedAnswer({ value }); + } + }; + + // Delete Button + const handleDelete = () => { + const modifiedGroupFeedbacks = groupFeedbacks.filter((item) => (item.id !== id)); + updateSettings({ groupFeedbackList: modifiedGroupFeedbacks }); + }; + + // Hooks for the feedback associated with a groupfeedback + const handleFeedbackChange = (event) => { + const { value } = event.target; + const modifiedGroupFeedback = groupFeedbacks.map(groupFeedback => { + if (groupFeedback.id === id) { + return { ...groupFeedback, feedback: value }; + } + return groupFeedback; + }); + updateSettings({ groupFeedbackList: modifiedGroupFeedback }); + }; + + return { + handleAnswersSelectedChange, handleFeedbackChange, handleDelete, + }; +}; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/hooks.test.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/hooks.test.js new file mode 100644 index 000000000..553d6b8f1 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/hooks.test.js @@ -0,0 +1,92 @@ +import { useEffect } from 'react'; +import { MockUseState } from '../../../../../../../../testUtils'; +import messages from './messages'; +import * as hooks from './hooks'; + +jest.mock('react', () => { + const updateState = jest.fn(); + return { + updateState, + useEffect: jest.fn(), + useState: jest.fn(val => ([{ state: val }, (newVal) => updateState({ val, newVal })])), + }; +}); + +const state = new MockUseState(hooks); + +describe('groupFeedbackCardHooks', () => { + let output; + let updateSettings; + let groupFeedbacks; + beforeEach(() => { + updateSettings = jest.fn(); + groupFeedbacks = []; + state.mock(); + }); + afterEach(() => { + state.restore(); + useEffect.mockClear(); + }); + describe('Show advanced settings', () => { + beforeEach(() => { + output = hooks.groupFeedbackCardHooks(groupFeedbacks, updateSettings); + }); + test('test default state is false', () => { + expect(output.summary.message).toEqual(messages.noGroupFeedbackSummary); + }); + test('test Event adds a new feedback ', () => { + output.handleAdd(); + expect(updateSettings).toHaveBeenCalledWith({ groupFeedbackList: [{ id: 0, answers: [], feedback: '' }] }); + }); + }); +}); + +describe('groupFeedbackRowHooks', () => { + const mockId = 'iD'; + const mockAnswer = 'moCkAnsweR'; + const mockFeedback = 'mOckFEEdback'; + let groupFeedbacks; + let output; + let updateSettings; + + beforeEach(() => { + updateSettings = jest.fn(); + groupFeedbacks = [{ id: mockId, answers: [mockAnswer], feedback: mockFeedback }]; + state.mock(); + }); + afterEach(() => { + state.restore(); + useEffect.mockClear(); + }); + describe('Show advanced settings', () => { + beforeEach(() => { + output = hooks.groupFeedbackRowHooks({ id: mockId, groupFeedbacks, updateSettings }); + }); + test('test associate an answer with the feedback object', () => { + const mockNewAnswer = 'nEw VAluE'; + output.handleAnswersSelectedChange({ target: { checked: true, value: mockNewAnswer } }); + expect(updateSettings).toHaveBeenCalledWith( + { groupFeedbackList: [{ id: mockId, answers: [mockAnswer, mockNewAnswer], feedback: mockFeedback }] }, + ); + }); + test('test unassociate an answer with the feedback object', () => { + output.handleAnswersSelectedChange({ target: { checked: false, value: mockAnswer } }); + expect(updateSettings).toHaveBeenCalledWith( + { groupFeedbackList: [{ id: mockId, answers: [], feedback: mockFeedback }] }, + ); + }); + test('test update feedback text with a groupfeedback', () => { + const mockNewFeedback = 'nEw fEedBack'; + output.handleFeedbackChange({ target: { checked: false, value: mockNewFeedback } }); + expect(updateSettings).toHaveBeenCalledWith( + { groupFeedbackList: [{ id: mockId, answers: [mockAnswer], feedback: mockNewFeedback }] }, + ); + }); + test('Delete a Row from the list of feedbacks', () => { + output.handleDelete(); + expect(updateSettings).toHaveBeenCalledWith( + { groupFeedbackList: [] }, + ); + }); + }); +}); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/index.jsx new file mode 100644 index 000000000..7c8d08f8c --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/index.jsx @@ -0,0 +1,65 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n'; +import SettingsOption from '../../SettingsOption'; +import messages from './messages'; +import { groupFeedbackCardHooks, groupFeedbackRowHooks } from './hooks'; +import GroupFeedbackRow from './GroupFeedbackRow'; +import Button from '../../../../../../../sharedComponents/Button'; + +export const GroupFeedbackCard = ({ + groupFeedbacks, + updateSettings, + answers, + // inject + intl, +}) => { + const { summary, handleAdd } = groupFeedbackCardHooks(groupFeedbacks, updateSettings, answers); + return ( + +
+ +
+ {groupFeedbacks.map((groupFeedback) => ( + + ))} + +
+ ); +}; + +GroupFeedbackCard.propTypes = { + intl: intlShape.isRequired, + groupFeedbacks: PropTypes.arrayOf(PropTypes.shape({ + id: PropTypes.number.isRequired, + feedback: PropTypes.string.isRequired, + answers: PropTypes.arrayOf(PropTypes.string).isRequired, + })).isRequired, + answers: PropTypes.arrayOf(PropTypes.shape({ + correct: PropTypes.bool, + id: PropTypes.string, + selectedFeedback: PropTypes.string, + title: PropTypes.string, + unselectedFeedback: PropTypes.string, + })).isRequired, + updateSettings: PropTypes.func.isRequired, +}; + +export default injectIntl(GroupFeedbackCard); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/index.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/index.test.jsx new file mode 100644 index 000000000..118529d23 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/index.test.jsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { formatMessage } from '../../../../../../../../testUtils'; +import { GroupFeedbackCard } from './index'; +import { groupFeedbackRowHooks, groupFeedbackCardHooks } from './hooks'; +import messages from './messages'; + +jest.mock('./hooks', () => ({ + groupFeedbackCardHooks: jest.fn(), + groupFeedbackRowHooks: jest.fn(), +})); + +describe('HintsCard', () => { + const answers = ['A', 'B', 'C']; + const groupFeedback1 = { + id: 1, value: 'groupFeedback1', answers: ['A', 'C'], feedback: 'sOmE FeEDBACK', + }; + const groupFeedback2 = { + id: 2, value: '', answers: ['A'], feedback: 'sOmE FeEDBACK oTher FeEdback', + }; + const groupFeedbacks0 = []; + const groupFeedbacks1 = [groupFeedback1]; + const groupFeedbacks2 = [groupFeedback1, groupFeedback2]; + const props = { + intl: { formatMessage }, + groupFeedbacks: groupFeedbacks0, + updateSettings: jest.fn().mockName('args.updateSettings'), + answers, + }; + + const groupFeedbacksRowHooksProps = { props: 'propsValue' }; + groupFeedbackRowHooks.mockReturnValue(groupFeedbacksRowHooksProps); + + describe('behavior', () => { + it(' calls groupFeedbacksCardHooks when initialized', () => { + const groupFeedbacksCardHooksProps = { + summary: { message: messages.noGroupFeedbackSummary }, + handleAdd: jest.fn().mockName('groupFeedbacksCardHooks.handleAdd'), + }; + + groupFeedbackCardHooks.mockReturnValue(groupFeedbacksCardHooksProps); + shallow(); + expect(groupFeedbackCardHooks).toHaveBeenCalledWith(groupFeedbacks0, props.updateSettings, answers); + }); + }); + + describe('snapshot', () => { + test('snapshot: renders groupFeedbacks setting card no groupFeedbacks', () => { + const groupFeedbacksCardHooksProps = { + summary: { message: messages.noGroupFeedbackSummary, values: {} }, + handleAdd: jest.fn().mockName('groupFeedbacksCardHooks.handleAdd'), + }; + + groupFeedbackCardHooks.mockReturnValue(groupFeedbacksCardHooksProps); + expect(shallow()).toMatchSnapshot(); + }); + test('snapshot: renders groupFeedbacks setting card one groupFeedback', () => { + const groupFeedbacksCardHooksProps = { + summary: { + message: messages.groupFeedbackSummary, + values: { groupFeedback: groupFeedback1.value, count: 1 }, + }, + handleAdd: jest.fn().mockName('groupFeedbacksCardHooks.handleAdd'), + }; + + groupFeedbackCardHooks.mockReturnValue(groupFeedbacksCardHooksProps); + expect(shallow()).toMatchSnapshot(); + }); + test('snapshot: renders groupFeedbacks setting card multiple groupFeedbacks', () => { + const groupFeedbacksCardHooksProps = { + summary: { + message: messages.groupFeedbackSummary, + values: { groupFeedback: groupFeedback2.value, count: 2 }, + }, + handleAdd: jest.fn().mockName('groupFeedbacksCardHooks.handleAdd'), + }; + + groupFeedbackCardHooks.mockReturnValue(groupFeedbacksCardHooksProps); + expect(shallow()).toMatchSnapshot(); + }); + }); +}); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/messages.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/messages.js new file mode 100644 index 000000000..454f312e8 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/GroupFeedback/messages.js @@ -0,0 +1,28 @@ +export const messages = { + noGroupFeedbackSummary: { + id: 'authoring.problemeditor.settings.GroupFeedbackSummary.message', + defaultMessage: 'None', + description: 'message to confirm that a user wants to use the advanced editor', + }, + groupFeedbackSummary: { + id: 'authoring.problemeditor.settings.GroupFeedbackSummary.message', + defaultMessage: '{groupFeedback}', + description: 'summary of current feedbacks provided for multiple problems', + }, + addGroupFeedbackButtonText: { + id: 'authoring.problemeditor.settings.addGroupFeedbackButtonText', + defaultMessage: 'Add group feedback', + description: 'addGroupFeedbackButtonText', + }, + groupFeedbackInputLabel: { + id: 'authoring.problemeditor.settings.GroupFeedbackInputLabel', + defaultMessage: 'Group feedback will appear when a student selects a specific set of answers.', + description: 'label for group feedback input', + }, + groupFeedbackSettingTitle: { + id: 'authoring.problemeditor.settings.GroupFeedbackSettingTitle', + defaultMessage: 'Group Feedback', + description: 'label for group feedback setting', + }, +}; +export default messages; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/__snapshots__/index.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/__snapshots__/index.test.jsx.snap new file mode 100644 index 000000000..5b5ced5d8 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/__snapshots__/index.test.jsx.snap @@ -0,0 +1,55 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`RandomizationCard snapshot snapshot: renders reset true setting card 1`] = ` + +
+ + + +
+ + + + + + + + +
+`; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/hooks.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/hooks.js new file mode 100644 index 000000000..ed875f2c4 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/hooks.js @@ -0,0 +1,24 @@ +import { useState, useEffect } from 'react'; +import messages from './messages'; +import { RandomizationTypes } from '../../../../../../../data/constants/problem'; +import * as module from './hooks'; + +export const state = { + summary: (val) => useState(val), +}; + +export const useRandomizationSettingStatus = ({ randomization, updateSettings }) => { + const [summary, setSummary] = module.state.summary({ message: messages.noRandomizationSummary, values: {} }); + useEffect(() => { + setSummary({ + message: randomization ? RandomizationTypes[randomization] : messages.noRandomizationSummary, + }); + }, [randomization]); + + const handleChange = (event) => { + updateSettings({ randomization: event.target.value }); + }; + return { summary, handleChange }; +}; + +export default useRandomizationSettingStatus; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/hooks.test.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/hooks.test.js new file mode 100644 index 000000000..7999c56f9 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/hooks.test.js @@ -0,0 +1,43 @@ +import { useEffect } from 'react'; +import { MockUseState } from '../../../../../../../../testUtils'; +import messages from './messages'; +import * as hooks from './hooks'; + +jest.mock('react', () => { + const updateState = jest.fn(); + return { + updateState, + useEffect: jest.fn(), + useState: jest.fn(val => ([{ state: val }, (newVal) => updateState({ val, newVal })])), + }; +}); + +const state = new MockUseState(hooks); + +describe('Problem settings hooks', () => { + let output; + let updateSettings; + let randomization; + beforeEach(() => { + updateSettings = jest.fn(); + randomization = 'sOmE_vAlUe'; + state.mock(); + }); + afterEach(() => { + state.restore(); + useEffect.mockClear(); + }); + describe('Show advanced settings', () => { + beforeEach(() => { + output = hooks.useRandomizationSettingStatus({ randomization, updateSettings }); + }); + test('test default state is false', () => { + expect(output.summary).toEqual({ message: messages.noRandomizationSummary, values: {} }); + }); + test('test showAdvancedCards sets state to true', () => { + const mockEvent = { target: { value: 'sOmE_otheR_ValUe' } }; + output.handleChange(mockEvent); + expect(updateSettings).toHaveBeenCalledWith({ randomization: mockEvent.target.value }); + }); + }); +}); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/index.jsx new file mode 100644 index 000000000..ff590a8ff --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/index.jsx @@ -0,0 +1,56 @@ +import React from 'react'; +import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n'; +import { Form } from '@edx/paragon'; +import PropTypes from 'prop-types'; +import SettingsOption from '../../SettingsOption'; +import messages from './messages'; +import { useRandomizationSettingStatus } from './hooks'; +import { RandomizationTypesKeys, RandomizationTypes } from '../../../../../../../data/constants/problem'; + +export const RandomizationCard = ({ + randomization, + updateSettings, + // inject + intl, +}) => { + const { summary, handleChange } = useRandomizationSettingStatus({ randomization, updateSettings }); + return ( + +
+ + + +
+ + + + {Object.values(RandomizationTypesKeys).map((randomizationType) => ( + + ))} + + + +
+ ); +}; + +RandomizationCard.propTypes = { + randomization: PropTypes.string.isRequired, + updateSettings: PropTypes.func.isRequired, + intl: intlShape.isRequired, +}; + +export default injectIntl(RandomizationCard); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/index.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/index.test.jsx new file mode 100644 index 000000000..f028ca012 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/index.test.jsx @@ -0,0 +1,39 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { formatMessage } from '../../../../../../../../testUtils'; +import { RandomizationCard } from './index'; +import { useRandomizationSettingStatus } from './hooks'; + +jest.mock('./hooks', () => ({ + useRandomizationSettingStatus: jest.fn(), +})); + +describe('RandomizationCard', () => { + const props = { + randomization: 'sOmE_vAlUE', + updateSettings: jest.fn().mockName('args.updateSettings'), + intl: { formatMessage }, + }; + + const randomizationCardHooksProps = { + summary: { message: { defaultMessage: 'sUmmary' } }, + handleChange: jest.fn().mockName('randomizationCardHooks.handleChange'), + }; + + useRandomizationSettingStatus.mockReturnValue(randomizationCardHooksProps); + + describe('behavior', () => { + it(' calls useRandomizationSettingStatus when initialized', () => { + shallow(); + expect(useRandomizationSettingStatus).toHaveBeenCalledWith( + { updateSettings: props.updateSettings, randomization: props.randomization }, + ); + }); + }); + + describe('snapshot', () => { + test('snapshot: renders reset true setting card', () => { + expect(shallow()).toMatchSnapshot(); + }); + }); +}); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/messages.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/messages.js new file mode 100644 index 000000000..52e980519 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/Randomization/messages.js @@ -0,0 +1,18 @@ +export const messages = { + randomizationSettingTitle: { + id: 'authoring.problemeditor.settings.randomization.SettingTitle', + defaultMessage: 'Randomization', + description: 'Settings Title for Randomization widget', + }, + randomizationSettingText: { + id: 'authoring.problemeditor.settings.randomization.SettingText', + defaultMessage: 'Defines when to randomize the variables specified in the associated Python script. For problems that do not randomize values, specify "Never".', + description: 'Description of Possibilities for value in Randomization widget', + }, + noRandomizationSummary: { + id: 'authoring.problemeditor.settings.randomization.noRandomizationSummary', + defaultMessage: 'No Python based randomization is present in this problem.', + description: 'text shown when no randomization option is given', + }, +}; +export default messages; diff --git a/src/editors/containers/ProblemEditor/data/OLXParser.js b/src/editors/containers/ProblemEditor/data/OLXParser.js index 44d70e73a..3e793d448 100644 --- a/src/editors/containers/ProblemEditor/data/OLXParser.js +++ b/src/editors/containers/ProblemEditor/data/OLXParser.js @@ -373,6 +373,24 @@ export class OLXParser { return problemType; } + getGeneralFeedback({ answers, problemType }) { + /* Feedback is Generalized for a Problem IFF: + 1. The problem is of Types: Single Select or Dropdown. + 2. All the problem's incorrect, if Selected answers are equivalent strings, and there is no other feedback. + */ + if (problemType === ProblemTypeKeys.SINGLESELECT || problemType === ProblemTypeKeys.DROPDOWN) { + const firstIncorrectAnswerText = answers.find(answer => answer.correct === false).selectedFeedback; + const isAllIncorrectSelectedFeedbackTheSame = answers.every(answer => (answer.correct + ? true + : answer?.selectedFeedback === firstIncorrectAnswerText + )); + if (isAllIncorrectSelectedFeedbackTheSame) { + return firstIncorrectAnswerText; + } + } + return ''; + } + getParsedOLXData() { if (_.isEmpty(this.problem)) { return {}; @@ -410,7 +428,7 @@ export class OLXParser { // if problem is unset, return null return {}; } - + const generalFeedback = this.getGeneralFeedback({ answers: answersObject.answers, problemType }); if (_.has(answersObject, 'additionalStringAttributes')) { additionalAttributes = { ...answersObject.additionalStringAttributes }; } @@ -428,6 +446,7 @@ export class OLXParser { answers, problemType, additionalAttributes, + generalFeedback, groupFeedbackList, }; } diff --git a/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js b/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js index 0f7681335..e818fdcc8 100644 --- a/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js +++ b/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js @@ -65,7 +65,21 @@ class ReactStateOLXParser { const choice = []; let compoundhint = []; let widget = {}; - const { answers } = this.problemState; + // eslint-disable-next-line prefer-const + let { answers, generalFeedback } = this.problemState; + // general feedback replaces selected feedback if all incorrect selected feedback is the same. + if (generalFeedback !== '' + && answers.every( + answer => ( + answer.correct + ? true + : answer?.selectedFeedback === answers.find(a => a.correct === false).selectedFeedback + ), + )) { + answers = answers.map(answer => (!answer?.correct + ? { ...answer, selectedFeedback: generalFeedback } + : answer)); + } answers.forEach((answer) => { const feedback = []; let singleAnswer = {}; diff --git a/src/editors/containers/ProblemEditor/data/ReactStateSettingsParser.js b/src/editors/containers/ProblemEditor/data/ReactStateSettingsParser.js index e022bcb7c..05a8f25c7 100644 --- a/src/editors/containers/ProblemEditor/data/ReactStateSettingsParser.js +++ b/src/editors/containers/ProblemEditor/data/ReactStateSettingsParser.js @@ -16,6 +16,7 @@ class ReactStateSettingsParser { settings = popuplateItem(settings, 'afterAttempts', 'attempts_before_showanswer_button', stateSettings.showAnswer); settings = popuplateItem(settings, 'showResetButton', 'show_reset_button', stateSettings); settings = popuplateItem(settings, 'timeBetween', 'submission_wait_seconds', stateSettings); + settings = popuplateItem(settings, 'randomization', 'rerandomize', stateSettings); return settings; } diff --git a/src/editors/containers/ProblemEditor/data/SettingsParser.js b/src/editors/containers/ProblemEditor/data/SettingsParser.js index 1de0708a0..8e6d99bab 100644 --- a/src/editors/containers/ProblemEditor/data/SettingsParser.js +++ b/src/editors/containers/ProblemEditor/data/SettingsParser.js @@ -1,6 +1,6 @@ import _ from 'lodash-es'; -import { ShowAnswerTypes } from '../../../data/constants/problem'; +import { ShowAnswerTypes, RandomizationTypesKeys } from '../../../data/constants/problem'; export const popuplateItem = (parentObject, itemName, statekey, metadata) => { let parent = parentObject; @@ -57,8 +57,13 @@ export const parseSettings = (metadata) => { if (!_.isEmpty(showAnswer)) { settings = { ...settings, showAnswer }; } + + const randomizationType = _.get(metadata, 'rerandomize', {}); + if (!_.isEmpty(randomizationType) && Object.values(RandomizationTypesKeys).includes(randomizationType)) { + settings = popuplateItem(settings, 'rerandomize', 'randomization', metadata); + } + settings = popuplateItem(settings, 'show_reset_button', 'showResetButton', metadata); settings = popuplateItem(settings, 'submission_wait_seconds', 'timeBetween', metadata); - return settings; }; diff --git a/src/editors/data/constants/problem.js b/src/editors/data/constants/problem.js index 6a4623882..392a200d6 100644 --- a/src/editors/data/constants/problem.js +++ b/src/editors/data/constants/problem.js @@ -184,3 +184,30 @@ export const ShowAnswerTypes = StrictDict({ defaultMessage: 'Attempted', }, }); + +export const RandomizationTypesKeys = StrictDict({ + ALWAYS: 'always', + NEVER: 'never', + ONRESET: 'on_reset', + PERSTUDENT: 'per_student', +}); +export const RandomizationTypes = StrictDict({ + [RandomizationTypesKeys.ALWAYS]: + { + id: 'authoring.problemeditor.settings.RandomizationTypes.always', + defaultMessage: 'Always', + }, + [RandomizationTypesKeys.NEVER]: + { + id: 'authoring.problemeditor.settings.RandomizationTypes.never', + defaultMessage: 'Never', + }, + [RandomizationTypesKeys.ONRESET]: { + id: 'authoring.problemeditor.settings.RandomizationTypes.onreset', + defaultMessage: 'On Reset', + }, + [RandomizationTypesKeys.PERSTUDENT]: { + id: 'authoring.problemeditor.settings.RandomizationTypes.perstudent', + defaultMessage: 'Per Student', + }, +}); diff --git a/src/editors/data/redux/problem/reducers.js b/src/editors/data/redux/problem/reducers.js index 9c3d7afd1..0849df4ad 100644 --- a/src/editors/data/redux/problem/reducers.js +++ b/src/editors/data/redux/problem/reducers.js @@ -12,8 +12,10 @@ const initialState = { answers: [], correctAnswerCount: 0, groupFeedbackList: [], + generalFeedback: '', additionalAttributes: {}, settings: { + randomization: null, scoring: { weight: 0, attempts: { diff --git a/src/editors/data/redux/problem/selectors.js b/src/editors/data/redux/problem/selectors.js index a9c267600..bb77a1fa6 100644 --- a/src/editors/data/redux/problem/selectors.js +++ b/src/editors/data/redux/problem/selectors.js @@ -5,6 +5,8 @@ export const problemState = (state) => state.problem; const mkSimpleSelector = (cb) => createSelector([module.problemState], cb); export const simpleSelectors = { problemType: mkSimpleSelector(problemData => problemData.problemType), + generalFeedback: mkSimpleSelector(problemData => problemData.generalFeedback), + groupFeedbackList: mkSimpleSelector(problemData => problemData.groupFeedbackList), answers: mkSimpleSelector(problemData => problemData.answers), correctAnswerCount: mkSimpleSelector(problemData => problemData.correctAnswerCount), settings: mkSimpleSelector(problemData => problemData.settings),