Skip to content

Commit e543ccc

Browse files
authored
fix: parser not saving unlimited attempts (#483)
* fix: default settings not loading for new problems * fix: unlimited attempts not saving
1 parent a959c05 commit e543ccc

File tree

10 files changed

+114
-18
lines changed

10 files changed

+114
-18
lines changed

src/editors/containers/ProblemEditor/components/EditProblemView/hooks.test.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,7 @@ describe('EditProblemView hooks parseState', () => {
305305
});
306306
});
307307

308-
it('returned parseState content.settings should not include default values', () => {
308+
it('returned parseState content.settings should not include default values (not including maxAttempts)', () => {
309309
const problem = {
310310
...problemState,
311311
problemType: ProblemTypeKeys.NUMERIC,
@@ -326,6 +326,7 @@ describe('EditProblemView hooks parseState', () => {
326326
openSaveWarningModal,
327327
});
328328
expect(settings).toEqual({
329+
max_attempts: '',
329330
attempts_before_showanswer_button: 0,
330331
submission_wait_seconds: 0,
331332
weight: 1,

src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/SelectTypeFooter.jsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,13 @@ import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/
1111
import messages from './messages';
1212
import hooks from '../hooks';
1313

14-
import { actions } from '../../../../../data/redux';
14+
import { actions, selectors } from '../../../../../data/redux';
1515

1616
export const SelectTypeFooter = ({
1717
onCancel,
1818
selected,
1919
// redux
20+
defaultSettings,
2021
updateField,
2122
setBlockTitle,
2223
// injected,
@@ -35,7 +36,12 @@ export const SelectTypeFooter = ({
3536
</Button>
3637
<Button
3738
aria-label={intl.formatMessage(messages.selectButtonAriaLabel)}
38-
onClick={hooks.onSelect({ selected, updateField, setBlockTitle })}
39+
onClick={hooks.onSelect({
40+
selected,
41+
updateField,
42+
setBlockTitle,
43+
defaultSettings,
44+
})}
3945
disabled={!selected}
4046
>
4147
<FormattedMessage {...messages.selectButtonLabel} />
@@ -50,6 +56,12 @@ SelectTypeFooter.defaultProps = {
5056
};
5157

5258
SelectTypeFooter.propTypes = {
59+
defaultSettings: PropTypes.shape({
60+
maxAttempts: PropTypes.number,
61+
rerandomize: PropTypes.string,
62+
showResetButton: PropTypes.bool,
63+
showanswer: PropTypes.string,
64+
}).isRequired,
5365
onCancel: PropTypes.func.isRequired,
5466
selected: PropTypes.string,
5567
updateField: PropTypes.func.isRequired,
@@ -58,7 +70,8 @@ SelectTypeFooter.propTypes = {
5870
intl: intlShape.isRequired,
5971
};
6072

61-
export const mapStateToProps = () => ({
73+
export const mapStateToProps = (state) => ({
74+
defaultSettings: selectors.problem.defaultSettings(state),
6275
});
6376

6477
export const mapDispatchToProps = {

src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/SelectTypeFooter.test.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ describe('SelectTypeFooter', () => {
1616
onCancel: jest.fn().mockName('onCancel'),
1717
selected: null,
1818
// redux
19+
defaultSettings: {},
1920
updateField: jest.fn().mockName('UpdateField'),
2021
// inject
2122
intl: { formatMessage },
@@ -45,7 +46,7 @@ describe('SelectTypeFooter', () => {
4546

4647
describe('mapStateToProps', () => {
4748
test('is empty', () => {
48-
expect(module.mapStateToProps()).toEqual({});
49+
expect(module.mapDispatchToProps.defaultSettings).toEqual(actions.problem.defaultSettings);
4950
});
5051
});
5152
describe('mapDispatchToProps', () => {

src/editors/containers/ProblemEditor/components/SelectTypeModal/hooks.js

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
22
import {
33
AdvanceProblemKeys, AdvanceProblems, ProblemTypeKeys, ProblemTypes,
44
} from '../../../../data/constants/problem';
5-
import { StrictDict } from '../../../../utils';
5+
import { StrictDict, snakeCaseKeys } from '../../../../utils';
66
import * as module from './hooks';
77
import { getDataFromOlx } from '../../../../data/redux/thunkActions/problem';
88

@@ -19,14 +19,28 @@ export const selectHooks = () => {
1919
};
2020
};
2121

22-
export const onSelect = ({ selected, updateField, setBlockTitle }) => () => {
22+
export const onSelect = ({
23+
selected,
24+
updateField,
25+
setBlockTitle,
26+
defaultSettings,
27+
}) => () => {
2328
if (Object.values(AdvanceProblemKeys).includes(selected)) {
2429
updateField({ problemType: ProblemTypeKeys.ADVANCED, rawOLX: AdvanceProblems[selected].template });
2530
setBlockTitle(AdvanceProblems[selected].title);
2631
} else {
2732
const newOLX = ProblemTypes[selected].template;
28-
const { settings, ...newState } = getDataFromOlx({ rawOLX: newOLX, rawSettings: {}, defaultSettings: {} });
29-
updateField({ ...newState });
33+
const newState = getDataFromOlx({
34+
rawOLX: newOLX,
35+
rawSettings: {
36+
weight: 1,
37+
attempts_before_showanswer_button: 0,
38+
show_reset_button: null,
39+
showanswer: null,
40+
},
41+
defaultSettings: snakeCaseKeys(defaultSettings),
42+
});
43+
updateField(newState);
3044
setBlockTitle(ProblemTypes[selected].title);
3145
}
3246
};

src/editors/containers/ProblemEditor/components/SelectTypeModal/hooks.test.js

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import React from 'react';
33
import { MockUseState } from '../../../../../testUtils';
44
import * as module from './hooks';
55
import { AdvanceProblems, ProblemTypeKeys, ProblemTypes } from '../../../../data/constants/problem';
6-
import { OLXParser } from '../../data/OLXParser';
6+
import { getDataFromOlx } from '../../../../data/redux/thunkActions/problem';
77

88
jest.mock('react', () => ({
99
...jest.requireActual('react'),
@@ -17,6 +17,12 @@ const mockSelected = 'multiplechoiceresponse';
1717
const mockAdvancedSelected = 'circuitschematic';
1818
const mockSetSelected = jest.fn().mockName('setSelected');
1919
const mocksetBlockTitle = jest.fn().mockName('setBlockTitle');
20+
const mockDefaultSettings = {
21+
max_attempts: null,
22+
rerandomize: 'never',
23+
showR_reset_button: false,
24+
showanswer: 'always',
25+
};
2026

2127
let hook;
2228

@@ -58,13 +64,24 @@ describe('SelectTypeModal hooks', () => {
5864
expect(mocksetBlockTitle).toHaveBeenCalledWith(AdvanceProblems[mockAdvancedSelected].title);
5965
});
6066
test('updateField is called with selected on visual propblems', () => {
61-
module.onSelect({ selected: mockSelected, updateField: mockUpdateField, setBlockTitle: mocksetBlockTitle })();
62-
const testOlXParser = new OLXParser(ProblemTypes[mockSelected].template);
63-
const { settings, ...testState } = testOlXParser.getParsedOLXData();
64-
expect(mockUpdateField).toHaveBeenCalledWith({
65-
...testState,
67+
module.onSelect({
68+
selected: mockSelected,
69+
updateField: mockUpdateField,
70+
setBlockTitle: mocksetBlockTitle,
71+
defaultSettings: mockDefaultSettings,
72+
})();
73+
// const testOlXParser = new OLXParser(ProblemTypes[mockSelected].template);
74+
const testState = getDataFromOlx({
6675
rawOLX: ProblemTypes[mockSelected].template,
67-
});
76+
rawSettings: {
77+
weight: 1,
78+
attempts_before_showanswer_button: 0,
79+
show_reset_button: null,
80+
showanswer: null,
81+
},
82+
defaultSettings: mockDefaultSettings,
83+
});
84+
expect(mockUpdateField).toHaveBeenCalledWith(testState);
6885
expect(mocksetBlockTitle).toHaveBeenCalledWith(ProblemTypes[mockSelected].title);
6986
});
7087
});

src/editors/containers/ProblemEditor/data/SettingsParser.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ export const popuplateItem = (parentObject, itemName, statekey, metadata, defaul
66
let parent = parentObject;
77
const item = _.get(metadata, itemName, null);
88
const equalsDefault = item === defaultValue;
9-
if ((!_.isNil(item) || allowNull) && !equalsDefault) {
9+
if (allowNull) {
10+
parent = { ...parentObject, [statekey]: item };
11+
} else if (!_.isNil(item) && !equalsDefault) {
1012
parent = { ...parentObject, [statekey]: item };
1113
}
1214
return parent;

src/editors/data/redux/problem/reducers.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ const initialState = {
2121
scoring: {
2222
weight: 1,
2323
attempts: {
24-
unlimited: false,
24+
unlimited: true,
2525
number: null,
2626
},
2727
},

src/editors/utils/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export { default as keyStore } from './keyStore';
44
export { default as camelizeKeys } from './camelizeKeys';
55
export { default as removeItemOnce } from './removeOnce';
66
export { default as formatDuration } from './formatDuration';
7+
export { default as snakeCaseKeys } from './snakeCaseKeys';

src/editors/utils/snakeCaseKeys.js

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { snakeCase } from 'lodash-es';
2+
3+
const snakeCaseKeys = (obj) => {
4+
if (Array.isArray(obj)) {
5+
return obj.map(v => snakeCaseKeys(v));
6+
}
7+
if (obj != null && obj.constructor === Object) {
8+
return Object.keys(obj).reduce(
9+
(result, key) => ({ ...result, [snakeCase(key)]: snakeCaseKeys(obj[key]) }),
10+
{},
11+
);
12+
}
13+
return obj;
14+
};
15+
export default snakeCaseKeys;
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { camelizeKeys } from './index';
2+
3+
const snakeCaseObject = {
4+
some_attribute:
5+
{
6+
another_attribute: [
7+
{ a_list: 'a lIsT' },
8+
{ of_attributes: 'iN diFferent' },
9+
{ different_cases: 'to Test' },
10+
],
11+
},
12+
a_final_attribute: null,
13+
a_last_one: undefined,
14+
};
15+
const camelCaseObject = {
16+
someAttribute:
17+
{
18+
anotherAttribute: [
19+
{ aList: 'a lIsT' },
20+
{ ofAttributes: 'iN diFferent' },
21+
{ differentCases: 'to Test' },
22+
],
23+
},
24+
aFinalAttribute: null,
25+
aLastOne: undefined,
26+
};
27+
28+
describe('camelizeKeys', () => {
29+
it('converts keys of objects to be camelCase', () => {
30+
expect(camelizeKeys(snakeCaseObject)).toEqual(camelCaseObject);
31+
});
32+
});

0 commit comments

Comments
 (0)