fix: parser not saving unlimited attempts (#483)
* fix: default settings not loading for new problems * fix: unlimited attempts not saving
This commit is contained in:
@@ -305,7 +305,7 @@ describe('EditProblemView hooks parseState', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('returned parseState content.settings should not include default values', () => {
|
||||
it('returned parseState content.settings should not include default values (not including maxAttempts)', () => {
|
||||
const problem = {
|
||||
...problemState,
|
||||
problemType: ProblemTypeKeys.NUMERIC,
|
||||
@@ -326,6 +326,7 @@ describe('EditProblemView hooks parseState', () => {
|
||||
openSaveWarningModal,
|
||||
});
|
||||
expect(settings).toEqual({
|
||||
max_attempts: '',
|
||||
attempts_before_showanswer_button: 0,
|
||||
submission_wait_seconds: 0,
|
||||
weight: 1,
|
||||
|
||||
@@ -11,12 +11,13 @@ import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/
|
||||
import messages from './messages';
|
||||
import hooks from '../hooks';
|
||||
|
||||
import { actions } from '../../../../../data/redux';
|
||||
import { actions, selectors } from '../../../../../data/redux';
|
||||
|
||||
export const SelectTypeFooter = ({
|
||||
onCancel,
|
||||
selected,
|
||||
// redux
|
||||
defaultSettings,
|
||||
updateField,
|
||||
setBlockTitle,
|
||||
// injected,
|
||||
@@ -35,7 +36,12 @@ export const SelectTypeFooter = ({
|
||||
</Button>
|
||||
<Button
|
||||
aria-label={intl.formatMessage(messages.selectButtonAriaLabel)}
|
||||
onClick={hooks.onSelect({ selected, updateField, setBlockTitle })}
|
||||
onClick={hooks.onSelect({
|
||||
selected,
|
||||
updateField,
|
||||
setBlockTitle,
|
||||
defaultSettings,
|
||||
})}
|
||||
disabled={!selected}
|
||||
>
|
||||
<FormattedMessage {...messages.selectButtonLabel} />
|
||||
@@ -50,6 +56,12 @@ SelectTypeFooter.defaultProps = {
|
||||
};
|
||||
|
||||
SelectTypeFooter.propTypes = {
|
||||
defaultSettings: PropTypes.shape({
|
||||
maxAttempts: PropTypes.number,
|
||||
rerandomize: PropTypes.string,
|
||||
showResetButton: PropTypes.bool,
|
||||
showanswer: PropTypes.string,
|
||||
}).isRequired,
|
||||
onCancel: PropTypes.func.isRequired,
|
||||
selected: PropTypes.string,
|
||||
updateField: PropTypes.func.isRequired,
|
||||
@@ -58,7 +70,8 @@ SelectTypeFooter.propTypes = {
|
||||
intl: intlShape.isRequired,
|
||||
};
|
||||
|
||||
export const mapStateToProps = () => ({
|
||||
export const mapStateToProps = (state) => ({
|
||||
defaultSettings: selectors.problem.defaultSettings(state),
|
||||
});
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
|
||||
@@ -16,6 +16,7 @@ describe('SelectTypeFooter', () => {
|
||||
onCancel: jest.fn().mockName('onCancel'),
|
||||
selected: null,
|
||||
// redux
|
||||
defaultSettings: {},
|
||||
updateField: jest.fn().mockName('UpdateField'),
|
||||
// inject
|
||||
intl: { formatMessage },
|
||||
@@ -45,7 +46,7 @@ describe('SelectTypeFooter', () => {
|
||||
|
||||
describe('mapStateToProps', () => {
|
||||
test('is empty', () => {
|
||||
expect(module.mapStateToProps()).toEqual({});
|
||||
expect(module.mapDispatchToProps.defaultSettings).toEqual(actions.problem.defaultSettings);
|
||||
});
|
||||
});
|
||||
describe('mapDispatchToProps', () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
||||
import {
|
||||
AdvanceProblemKeys, AdvanceProblems, ProblemTypeKeys, ProblemTypes,
|
||||
} from '../../../../data/constants/problem';
|
||||
import { StrictDict } from '../../../../utils';
|
||||
import { StrictDict, snakeCaseKeys } from '../../../../utils';
|
||||
import * as module from './hooks';
|
||||
import { getDataFromOlx } from '../../../../data/redux/thunkActions/problem';
|
||||
|
||||
@@ -19,14 +19,28 @@ export const selectHooks = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const onSelect = ({ selected, updateField, setBlockTitle }) => () => {
|
||||
export const onSelect = ({
|
||||
selected,
|
||||
updateField,
|
||||
setBlockTitle,
|
||||
defaultSettings,
|
||||
}) => () => {
|
||||
if (Object.values(AdvanceProblemKeys).includes(selected)) {
|
||||
updateField({ problemType: ProblemTypeKeys.ADVANCED, rawOLX: AdvanceProblems[selected].template });
|
||||
setBlockTitle(AdvanceProblems[selected].title);
|
||||
} else {
|
||||
const newOLX = ProblemTypes[selected].template;
|
||||
const { settings, ...newState } = getDataFromOlx({ rawOLX: newOLX, rawSettings: {}, defaultSettings: {} });
|
||||
updateField({ ...newState });
|
||||
const newState = getDataFromOlx({
|
||||
rawOLX: newOLX,
|
||||
rawSettings: {
|
||||
weight: 1,
|
||||
attempts_before_showanswer_button: 0,
|
||||
show_reset_button: null,
|
||||
showanswer: null,
|
||||
},
|
||||
defaultSettings: snakeCaseKeys(defaultSettings),
|
||||
});
|
||||
updateField(newState);
|
||||
setBlockTitle(ProblemTypes[selected].title);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from 'react';
|
||||
import { MockUseState } from '../../../../../testUtils';
|
||||
import * as module from './hooks';
|
||||
import { AdvanceProblems, ProblemTypeKeys, ProblemTypes } from '../../../../data/constants/problem';
|
||||
import { OLXParser } from '../../data/OLXParser';
|
||||
import { getDataFromOlx } from '../../../../data/redux/thunkActions/problem';
|
||||
|
||||
jest.mock('react', () => ({
|
||||
...jest.requireActual('react'),
|
||||
@@ -17,6 +17,12 @@ const mockSelected = 'multiplechoiceresponse';
|
||||
const mockAdvancedSelected = 'circuitschematic';
|
||||
const mockSetSelected = jest.fn().mockName('setSelected');
|
||||
const mocksetBlockTitle = jest.fn().mockName('setBlockTitle');
|
||||
const mockDefaultSettings = {
|
||||
max_attempts: null,
|
||||
rerandomize: 'never',
|
||||
showR_reset_button: false,
|
||||
showanswer: 'always',
|
||||
};
|
||||
|
||||
let hook;
|
||||
|
||||
@@ -58,13 +64,24 @@ describe('SelectTypeModal hooks', () => {
|
||||
expect(mocksetBlockTitle).toHaveBeenCalledWith(AdvanceProblems[mockAdvancedSelected].title);
|
||||
});
|
||||
test('updateField is called with selected on visual propblems', () => {
|
||||
module.onSelect({ selected: mockSelected, updateField: mockUpdateField, setBlockTitle: mocksetBlockTitle })();
|
||||
const testOlXParser = new OLXParser(ProblemTypes[mockSelected].template);
|
||||
const { settings, ...testState } = testOlXParser.getParsedOLXData();
|
||||
expect(mockUpdateField).toHaveBeenCalledWith({
|
||||
...testState,
|
||||
module.onSelect({
|
||||
selected: mockSelected,
|
||||
updateField: mockUpdateField,
|
||||
setBlockTitle: mocksetBlockTitle,
|
||||
defaultSettings: mockDefaultSettings,
|
||||
})();
|
||||
// const testOlXParser = new OLXParser(ProblemTypes[mockSelected].template);
|
||||
const testState = getDataFromOlx({
|
||||
rawOLX: ProblemTypes[mockSelected].template,
|
||||
rawSettings: {
|
||||
weight: 1,
|
||||
attempts_before_showanswer_button: 0,
|
||||
show_reset_button: null,
|
||||
showanswer: null,
|
||||
},
|
||||
defaultSettings: mockDefaultSettings,
|
||||
});
|
||||
expect(mockUpdateField).toHaveBeenCalledWith(testState);
|
||||
expect(mocksetBlockTitle).toHaveBeenCalledWith(ProblemTypes[mockSelected].title);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,7 +6,9 @@ export const popuplateItem = (parentObject, itemName, statekey, metadata, defaul
|
||||
let parent = parentObject;
|
||||
const item = _.get(metadata, itemName, null);
|
||||
const equalsDefault = item === defaultValue;
|
||||
if ((!_.isNil(item) || allowNull) && !equalsDefault) {
|
||||
if (allowNull) {
|
||||
parent = { ...parentObject, [statekey]: item };
|
||||
} else if (!_.isNil(item) && !equalsDefault) {
|
||||
parent = { ...parentObject, [statekey]: item };
|
||||
}
|
||||
return parent;
|
||||
|
||||
@@ -21,7 +21,7 @@ const initialState = {
|
||||
scoring: {
|
||||
weight: 1,
|
||||
attempts: {
|
||||
unlimited: false,
|
||||
unlimited: true,
|
||||
number: null,
|
||||
},
|
||||
},
|
||||
|
||||
@@ -4,3 +4,4 @@ export { default as keyStore } from './keyStore';
|
||||
export { default as camelizeKeys } from './camelizeKeys';
|
||||
export { default as removeItemOnce } from './removeOnce';
|
||||
export { default as formatDuration } from './formatDuration';
|
||||
export { default as snakeCaseKeys } from './snakeCaseKeys';
|
||||
|
||||
15
src/editors/utils/snakeCaseKeys.js
Normal file
15
src/editors/utils/snakeCaseKeys.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import { snakeCase } from 'lodash-es';
|
||||
|
||||
const snakeCaseKeys = (obj) => {
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(v => snakeCaseKeys(v));
|
||||
}
|
||||
if (obj != null && obj.constructor === Object) {
|
||||
return Object.keys(obj).reduce(
|
||||
(result, key) => ({ ...result, [snakeCase(key)]: snakeCaseKeys(obj[key]) }),
|
||||
{},
|
||||
);
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
export default snakeCaseKeys;
|
||||
32
src/editors/utils/snakeCaseKeys.test.js
Normal file
32
src/editors/utils/snakeCaseKeys.test.js
Normal file
@@ -0,0 +1,32 @@
|
||||
import { camelizeKeys } from './index';
|
||||
|
||||
const snakeCaseObject = {
|
||||
some_attribute:
|
||||
{
|
||||
another_attribute: [
|
||||
{ a_list: 'a lIsT' },
|
||||
{ of_attributes: 'iN diFferent' },
|
||||
{ different_cases: 'to Test' },
|
||||
],
|
||||
},
|
||||
a_final_attribute: null,
|
||||
a_last_one: undefined,
|
||||
};
|
||||
const camelCaseObject = {
|
||||
someAttribute:
|
||||
{
|
||||
anotherAttribute: [
|
||||
{ aList: 'a lIsT' },
|
||||
{ ofAttributes: 'iN diFferent' },
|
||||
{ differentCases: 'to Test' },
|
||||
],
|
||||
},
|
||||
aFinalAttribute: null,
|
||||
aLastOne: undefined,
|
||||
};
|
||||
|
||||
describe('camelizeKeys', () => {
|
||||
it('converts keys of objects to be camelCase', () => {
|
||||
expect(camelizeKeys(snakeCaseObject)).toEqual(camelCaseObject);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user