feat: add default advanced setting ui callouts (#285)
This commit is contained in:
@@ -1,17 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`ProblemEditor snapshots assets loaded, block and studio view not yet loaded, Spinner appears 1`] = `
|
||||
<div
|
||||
className="text-center p-6"
|
||||
>
|
||||
<Spinner
|
||||
animation="border"
|
||||
className="m-3"
|
||||
screenreadertext="Loading Problem Editor"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ProblemEditor snapshots block failed, message appears 1`] = `
|
||||
<div
|
||||
className="text-center p-6"
|
||||
@@ -24,36 +12,6 @@ exports[`ProblemEditor snapshots block failed, message appears 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ProblemEditor snapshots block loaded, studio view and assets not yet loaded, Spinner appears 1`] = `
|
||||
<div
|
||||
className="text-center p-6"
|
||||
>
|
||||
<Spinner
|
||||
animation="border"
|
||||
className="m-3"
|
||||
screenreadertext="Loading Problem Editor"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ProblemEditor snapshots renders EditProblemView 1`] = `
|
||||
<div
|
||||
className="text-center p-6"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Problem failed to load"
|
||||
description="Error message for problem block failing to load"
|
||||
id="authoring.problemEditor.blockFailed"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ProblemEditor snapshots renders SelectTypeModal 1`] = `
|
||||
<SelectTypeModal
|
||||
onClose={[MockFunction props.onClose]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`ProblemEditor snapshots renders as expected with default behavior 1`] = `
|
||||
<div
|
||||
className="text-center p-6"
|
||||
@@ -65,15 +23,3 @@ exports[`ProblemEditor snapshots renders as expected with default behavior 1`] =
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`ProblemEditor snapshots studio view loaded, block and assets not yet loaded, Spinner appears 1`] = `
|
||||
<div
|
||||
className="text-center p-6"
|
||||
>
|
||||
<Spinner
|
||||
animation="border"
|
||||
className="m-3"
|
||||
screenreadertext="Loading Problem Editor"
|
||||
/>
|
||||
</div>
|
||||
`;
|
||||
|
||||
@@ -14,7 +14,9 @@ exports[`SettingsWidget snapshot snapshot: renders Settings widget for Advanced
|
||||
<div
|
||||
className="my-3"
|
||||
>
|
||||
<ScoringCard />
|
||||
<ScoringCard
|
||||
defaultValue={2}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="mt-3"
|
||||
@@ -53,7 +55,9 @@ exports[`SettingsWidget snapshot snapshot: renders Settings widget for Advanced
|
||||
<div
|
||||
className="my-3"
|
||||
>
|
||||
<ShowAnswerCard />
|
||||
<ShowAnswerCard
|
||||
defaultValue="finished"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="my-3"
|
||||
@@ -96,7 +100,9 @@ exports[`SettingsWidget snapshot snapshot: renders Settings widget page 1`] = `
|
||||
<div
|
||||
className="my-3"
|
||||
>
|
||||
<ScoringCard />
|
||||
<ScoringCard
|
||||
defaultValue={2}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="mt-3"
|
||||
@@ -135,7 +141,9 @@ exports[`SettingsWidget snapshot snapshot: renders Settings widget page 1`] = `
|
||||
<div
|
||||
className="my-3"
|
||||
>
|
||||
<ShowAnswerCard />
|
||||
<ShowAnswerCard
|
||||
defaultValue="finished"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="my-3"
|
||||
@@ -178,7 +186,9 @@ exports[`SettingsWidget snapshot snapshot: renders Settings widget page advanced
|
||||
<div
|
||||
className="my-3"
|
||||
>
|
||||
<ScoringCard />
|
||||
<ScoringCard
|
||||
defaultValue={2}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="mt-3"
|
||||
@@ -217,7 +227,9 @@ exports[`SettingsWidget snapshot snapshot: renders Settings widget page advanced
|
||||
<div
|
||||
className="my-3"
|
||||
>
|
||||
<ShowAnswerCard />
|
||||
<ShowAnswerCard
|
||||
defaultValue="finished"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="my-3"
|
||||
|
||||
@@ -11,6 +11,7 @@ export const state = {
|
||||
cardCollapsed: (val) => useState(val),
|
||||
summary: (val) => useState(val),
|
||||
showAttempts: (val) => useState(val),
|
||||
attemptDisplayValue: (val) => useState(val),
|
||||
};
|
||||
|
||||
export const showAdvancedSettingsCards = () => {
|
||||
@@ -117,19 +118,52 @@ export const resetCardHooks = (updateSettings) => {
|
||||
};
|
||||
};
|
||||
|
||||
export const scoringCardHooks = (scoring, updateSettings) => {
|
||||
export const scoringCardHooks = (scoring, updateSettings, defaultValue) => {
|
||||
const loadedAttemptsNumber = scoring.attempts.number === defaultValue ? `${scoring.attempts.number} (Default)` : scoring.attempts.number;
|
||||
const [attemptDisplayValue, setAttemptDisplayValue] = module.state.attemptDisplayValue(loadedAttemptsNumber);
|
||||
const handleUnlimitedChange = (event) => {
|
||||
const isUnlimited = event.target.checked;
|
||||
if (isUnlimited) {
|
||||
setAttemptDisplayValue('');
|
||||
updateSettings({ scoring: { ...scoring, attempts: { number: '', unlimited: true } } });
|
||||
} else {
|
||||
setAttemptDisplayValue(`${defaultValue} (Default)`);
|
||||
updateSettings({ scoring: { ...scoring, attempts: { number: defaultValue, unlimited: false } } });
|
||||
}
|
||||
};
|
||||
const handleMaxAttemptChange = (event) => {
|
||||
let unlimitedAttempts = false;
|
||||
let attemptNumber = parseInt(event.target.value);
|
||||
const { value } = event.target;
|
||||
if (_.isNaN(attemptNumber)) {
|
||||
attemptNumber = '';
|
||||
unlimitedAttempts = true;
|
||||
} else if (attemptNumber < 0) {
|
||||
if (value === '') {
|
||||
attemptNumber = defaultValue;
|
||||
setAttemptDisplayValue(`${defaultValue} (Default)`);
|
||||
} else {
|
||||
attemptNumber = '';
|
||||
unlimitedAttempts = true;
|
||||
}
|
||||
} else if (attemptNumber <= 0) {
|
||||
attemptNumber = 0;
|
||||
} else if (attemptNumber === defaultValue) {
|
||||
const attemptNumberStr = value.replace(' (Default)');
|
||||
attemptNumber = parseInt(attemptNumberStr);
|
||||
}
|
||||
updateSettings({ scoring: { ...scoring, attempts: { number: attemptNumber, unlimited: unlimitedAttempts } } });
|
||||
};
|
||||
|
||||
const handleOnChange = (event) => {
|
||||
let newMaxAttempt = parseInt(event.target.value);
|
||||
if (newMaxAttempt === defaultValue) {
|
||||
newMaxAttempt = `${defaultValue} (Default)`;
|
||||
} else if (_.isNaN(newMaxAttempt)) {
|
||||
newMaxAttempt = '';
|
||||
} else if (newMaxAttempt < 0) {
|
||||
newMaxAttempt = 0;
|
||||
}
|
||||
setAttemptDisplayValue(newMaxAttempt);
|
||||
};
|
||||
|
||||
const handleWeightChange = (event) => {
|
||||
let weight = parseFloat(event.target.value);
|
||||
if (_.isNaN(weight)) {
|
||||
@@ -139,7 +173,10 @@ export const scoringCardHooks = (scoring, updateSettings) => {
|
||||
};
|
||||
|
||||
return {
|
||||
attemptDisplayValue,
|
||||
handleUnlimitedChange,
|
||||
handleMaxAttemptChange,
|
||||
handleOnChange,
|
||||
handleWeightChange,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -172,8 +172,21 @@ describe('Problem settings hooks', () => {
|
||||
number: 5,
|
||||
},
|
||||
};
|
||||
const defaultValue = 1;
|
||||
beforeEach(() => {
|
||||
output = hooks.scoringCardHooks(scoring, updateSettings);
|
||||
output = hooks.scoringCardHooks(scoring, updateSettings, defaultValue);
|
||||
});
|
||||
test('test handleUnlimitedChange sets attempts.unlimited to true when checked', () => {
|
||||
output.handleUnlimitedChange({ target: { checked: true } });
|
||||
expect(state.setState[state.keys.attemptDisplayValue]).toHaveBeenCalledWith('');
|
||||
expect(updateSettings)
|
||||
.toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: '', unlimited: true } } });
|
||||
});
|
||||
test('test handleUnlimitedChange sets attempts.unlimited to false when unchecked', () => {
|
||||
output.handleUnlimitedChange({ target: { checked: false } });
|
||||
expect(state.setState[state.keys.attemptDisplayValue]).toHaveBeenCalledWith(`${defaultValue} (Default)`);
|
||||
expect(updateSettings)
|
||||
.toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: defaultValue, unlimited: false } } });
|
||||
});
|
||||
test('test handleMaxAttemptChange', () => {
|
||||
const value = 6;
|
||||
@@ -193,11 +206,11 @@ describe('Problem settings hooks', () => {
|
||||
expect(updateSettings)
|
||||
.toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: '', unlimited: true } } });
|
||||
});
|
||||
test('test handleMaxAttemptChange set attempts to empty string', () => {
|
||||
const value = '';
|
||||
test('test handleMaxAttemptChange set attempts to default value', () => {
|
||||
const value = '1 (Default)';
|
||||
output.handleMaxAttemptChange({ target: { value } });
|
||||
expect(updateSettings)
|
||||
.toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: '', unlimited: true } } });
|
||||
.toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: 1, unlimited: false } } });
|
||||
});
|
||||
test('test handleMaxAttemptChange set attempts to non-numeric value', () => {
|
||||
const value = 'abc';
|
||||
@@ -205,12 +218,49 @@ describe('Problem settings hooks', () => {
|
||||
expect(updateSettings)
|
||||
.toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: '', unlimited: true } } });
|
||||
});
|
||||
test('test handleMaxAttemptChange set attempts to empty value', () => {
|
||||
const value = '';
|
||||
output.handleMaxAttemptChange({ target: { value } });
|
||||
expect(state.setState[state.keys.attemptDisplayValue]).toHaveBeenCalledWith(`${defaultValue} (Default)`);
|
||||
expect(updateSettings)
|
||||
.toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: 1, unlimited: false } } });
|
||||
});
|
||||
test('test handleMaxAttemptChange set attempts to negative value', () => {
|
||||
const value = -1;
|
||||
output.handleMaxAttemptChange({ target: { value } });
|
||||
expect(updateSettings)
|
||||
.toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: 0, unlimited: false } } });
|
||||
});
|
||||
test('test handleOnChange', () => {
|
||||
const value = 6;
|
||||
output.handleOnChange({ target: { value } });
|
||||
expect(state.setState[state.keys.attemptDisplayValue]).toHaveBeenCalledWith(value);
|
||||
});
|
||||
test('test handleOnChange set attempts to zero', () => {
|
||||
const value = 0;
|
||||
output.handleOnChange({ target: { value } });
|
||||
expect(state.setState[state.keys.attemptDisplayValue]).toHaveBeenCalledWith(value);
|
||||
});
|
||||
test('test handleOnChange set attempts to default value from empty string', () => {
|
||||
const value = '';
|
||||
output.handleOnChange({ target: { value } });
|
||||
expect(state.setState[state.keys.attemptDisplayValue]).toHaveBeenCalledWith('');
|
||||
});
|
||||
test('test handleOnChange set attempts to default value', () => {
|
||||
const value = 1;
|
||||
output.handleOnChange({ target: { value } });
|
||||
expect(state.setState[state.keys.attemptDisplayValue]).toHaveBeenCalledWith('1 (Default)');
|
||||
});
|
||||
test('test handleOnChange set attempts to non-numeric value', () => {
|
||||
const value = '';
|
||||
output.handleOnChange({ target: { value } });
|
||||
expect(state.setState[state.keys.attemptDisplayValue]).toHaveBeenCalledWith(value);
|
||||
});
|
||||
test('test handleOnChange set attempts to negative value', () => {
|
||||
const value = -1;
|
||||
output.handleOnChange({ target: { value } });
|
||||
expect(state.setState[state.keys.attemptDisplayValue]).toHaveBeenCalledWith(0);
|
||||
});
|
||||
test('test handleWeightChange', () => {
|
||||
const value = 2;
|
||||
output.handleWeightChange({ target: { value } });
|
||||
|
||||
@@ -36,6 +36,7 @@ export const SettingsWidget = ({
|
||||
updateSettings,
|
||||
updateField,
|
||||
updateAnswer,
|
||||
defaultSettings,
|
||||
}) => {
|
||||
const { isAdvancedCardsVisible, showAdvancedCards } = showAdvancedSettingsCards();
|
||||
|
||||
@@ -77,7 +78,11 @@ export const SettingsWidget = ({
|
||||
</div>
|
||||
)}
|
||||
<div className="my-3">
|
||||
<ScoringCard scoring={settings.scoring} updateSettings={updateSettings} />
|
||||
<ScoringCard
|
||||
scoring={settings.scoring}
|
||||
defaultValue={defaultSettings.maxAttempts}
|
||||
updateSettings={updateSettings}
|
||||
/>
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<HintsCard problemType={problemType} hints={settings.hints} updateSettings={updateSettings} />
|
||||
@@ -103,6 +108,7 @@ export const SettingsWidget = ({
|
||||
<div className="my-3">
|
||||
<ShowAnswerCard
|
||||
showAnswer={settings.showAnswer}
|
||||
defaultValue={defaultSettings.showanswer}
|
||||
updateSettings={updateSettings}
|
||||
/>
|
||||
</div>
|
||||
@@ -159,6 +165,11 @@ SettingsWidget.propTypes = {
|
||||
updateAnswer: PropTypes.func.isRequired,
|
||||
updateField: PropTypes.func.isRequired,
|
||||
updateSettings: PropTypes.func.isRequired,
|
||||
defaultSettings: PropTypes.shape({
|
||||
maxAttempts: PropTypes.number,
|
||||
showanswer: PropTypes.string,
|
||||
showReseButton: PropTypes.bool,
|
||||
}).isRequired,
|
||||
// eslint-disable-next-line
|
||||
settings: PropTypes.any.isRequired,
|
||||
};
|
||||
@@ -169,6 +180,7 @@ const mapStateToProps = (state) => ({
|
||||
answers: selectors.problem.answers(state),
|
||||
blockTitle: selectors.app.blockTitle(state),
|
||||
correctAnswerCount: selectors.problem.correctAnswerCount(state),
|
||||
defaultSettings: selectors.problem.defaultSettings(state),
|
||||
});
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
|
||||
@@ -25,6 +25,11 @@ describe('SettingsWidget', () => {
|
||||
const props = {
|
||||
problemType: ProblemTypeKeys.TEXTINPUT,
|
||||
settings: {},
|
||||
defaultSettings: {
|
||||
maxAttempts: 2,
|
||||
showanswer: 'finished',
|
||||
showResetButton: false,
|
||||
},
|
||||
};
|
||||
|
||||
describe('behavior', () => {
|
||||
|
||||
@@ -114,6 +114,11 @@ export const messages = {
|
||||
defaultMessage: '{attempts, plural, =1 {# attempt} other {# attempts}}',
|
||||
description: 'Summary text for number of attempts',
|
||||
},
|
||||
unlimitedAttemptsCheckboxLabel: {
|
||||
id: 'authoring.problemeditor.settings.scoring.attempts.unlimitedCheckbox',
|
||||
defaultMessage: 'Unlimited attempts',
|
||||
description: 'Label for unlimited attempts checkbox',
|
||||
},
|
||||
weightSummary: {
|
||||
id: 'authoring.problemeditor.settings.scoring.weight',
|
||||
defaultMessage: '{weight, plural, =0 {Ungraded} other {# points}}',
|
||||
|
||||
@@ -15,7 +15,7 @@ export const ResetCard = ({
|
||||
intl,
|
||||
}) => {
|
||||
const { setResetTrue, setResetFalse } = resetCardHooks(updateSettings);
|
||||
const advancedSettingsLink = `${useSelector(selectors.app.studioEndpointUrl)}/settings/advanced/${useSelector(selectors.app.learningContextId)}`;
|
||||
const advancedSettingsLink = `${useSelector(selectors.app.studioEndpointUrl)}/settings/advanced/${useSelector(selectors.app.learningContextId)}#show_reset_button`;
|
||||
return (
|
||||
<SettingsOption
|
||||
title={intl.formatMessage(messages.resetSettingsTitle)}
|
||||
|
||||
@@ -1,47 +1,49 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { connect } from 'react-redux';
|
||||
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
|
||||
import { Form } from '@edx/paragon';
|
||||
import { Form, Hyperlink } from '@edx/paragon';
|
||||
import { selectors } from '../../../../../../data/redux';
|
||||
import SettingsOption from '../SettingsOption';
|
||||
import messages from '../messages';
|
||||
import { scoringCardHooks } from '../hooks';
|
||||
|
||||
export const ScoringCard = ({
|
||||
scoring,
|
||||
defaultValue,
|
||||
updateSettings,
|
||||
// inject
|
||||
intl,
|
||||
// redux
|
||||
studioEndpointUrl,
|
||||
learningContextId,
|
||||
}) => {
|
||||
const { handleMaxAttemptChange, handleWeightChange } = scoringCardHooks(scoring, updateSettings);
|
||||
const {
|
||||
handleUnlimitedChange,
|
||||
handleMaxAttemptChange,
|
||||
handleWeightChange,
|
||||
handleOnChange,
|
||||
attemptDisplayValue,
|
||||
} = scoringCardHooks(scoring, updateSettings, defaultValue);
|
||||
|
||||
const getScoringSummary = (attempts, unlimited, weight) => {
|
||||
let summary = unlimited
|
||||
const getScoringSummary = (weight, attempts, unlimited) => {
|
||||
let summary = intl.formatMessage(messages.weightSummary, { weight });
|
||||
summary += ` ${String.fromCharCode(183)} `;
|
||||
summary += unlimited
|
||||
? intl.formatMessage(messages.unlimitedAttemptsSummary)
|
||||
: intl.formatMessage(messages.attemptsSummary, { attempts });
|
||||
summary += ` ${String.fromCharCode(183)} `;
|
||||
summary += intl.formatMessage(messages.weightSummary, { weight });
|
||||
return summary;
|
||||
};
|
||||
|
||||
return (
|
||||
<SettingsOption
|
||||
title={intl.formatMessage(messages.scoringSettingsTitle)}
|
||||
summary={getScoringSummary(scoring.attempts.number, scoring.attempts.unlimited, scoring.weight)}
|
||||
summary={getScoringSummary(scoring.weight, scoring.attempts.number, scoring.attempts.unlimited)}
|
||||
className="scoringCard"
|
||||
>
|
||||
<Form.Label className="mb-4">
|
||||
<FormattedMessage {...messages.scoringSettingsLabel} />
|
||||
</Form.Label>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
type="number"
|
||||
value={scoring.attempts.number}
|
||||
onChange={handleMaxAttemptChange}
|
||||
floatingLabel={intl.formatMessage(messages.scoringAttemptsInputLabel)}
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage {...messages.attemptsHint} />
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
type="number"
|
||||
@@ -53,6 +55,30 @@ export const ScoringCard = ({
|
||||
<FormattedMessage {...messages.weightHint} />
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
value={attemptDisplayValue}
|
||||
onChange={handleOnChange}
|
||||
onBlur={handleMaxAttemptChange}
|
||||
floatingLabel={intl.formatMessage(messages.scoringAttemptsInputLabel)}
|
||||
disabled={scoring.attempts.unlimited}
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage {...messages.attemptsHint} />
|
||||
</Form.Control.Feedback>
|
||||
<Form.Checkbox
|
||||
className="mt-3 decoration-control-label"
|
||||
checked={scoring.attempts.unlimited}
|
||||
onChange={handleUnlimitedChange}
|
||||
>
|
||||
<div className="small">
|
||||
<FormattedMessage {...messages.unlimitedAttemptsCheckboxLabel} />
|
||||
</div>
|
||||
</Form.Checkbox>
|
||||
</Form.Group>
|
||||
<Hyperlink destination={`${studioEndpointUrl}/settings/advanced/${learningContextId}#max_attempts`} target="_blank">
|
||||
<FormattedMessage {...messages.advancedSettingsLinkText} />
|
||||
</Hyperlink>
|
||||
</SettingsOption>
|
||||
);
|
||||
};
|
||||
@@ -62,6 +88,17 @@ ScoringCard.propTypes = {
|
||||
// eslint-disable-next-line
|
||||
scoring: PropTypes.any.isRequired,
|
||||
updateSettings: PropTypes.func.isRequired,
|
||||
defaultValue: PropTypes.number.isRequired,
|
||||
// redux
|
||||
studioEndpointUrl: PropTypes.string.isRequired,
|
||||
learningContextId: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default injectIntl(ScoringCard);
|
||||
export const mapStateToProps = (state) => ({
|
||||
studioEndpointUrl: selectors.app.studioEndpointUrl(state),
|
||||
learningContextId: selectors.app.learningContextId(state),
|
||||
});
|
||||
|
||||
export const mapDispatchToProps = {};
|
||||
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ScoringCard));
|
||||
|
||||
@@ -16,6 +16,7 @@ describe('ScoringCard', () => {
|
||||
number: 5,
|
||||
},
|
||||
updateSettings: jest.fn().mockName('args.updateSettings'),
|
||||
defaultValue: 1,
|
||||
intl: { formatMessage },
|
||||
};
|
||||
|
||||
@@ -27,6 +28,8 @@ describe('ScoringCard', () => {
|
||||
const scoringCardHooksProps = {
|
||||
handleMaxAttemptChange: jest.fn().mockName('scoringCardHooks.handleMaxAttemptChange'),
|
||||
handleWeightChange: jest.fn().mockName('scoringCardHooks.handleWeightChange'),
|
||||
handleOnChange: jest.fn().mockName('scoringCardHooks.handleOnChange'),
|
||||
local: 5,
|
||||
};
|
||||
|
||||
scoringCardHooks.mockReturnValue(scoringCardHooksProps);
|
||||
@@ -34,7 +37,7 @@ describe('ScoringCard', () => {
|
||||
describe('behavior', () => {
|
||||
it(' calls scoringCardHooks when initialized', () => {
|
||||
shallow(<ScoringCard {...props} />);
|
||||
expect(scoringCardHooks).toHaveBeenCalledWith(scoring, props.updateSettings);
|
||||
expect(scoringCardHooks).toHaveBeenCalledWith(scoring, props.updateSettings, props.defaultValue);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import { useAnswerSettings } from '../hooks';
|
||||
export const ShowAnswerCard = ({
|
||||
showAnswer,
|
||||
updateSettings,
|
||||
defaultValue,
|
||||
// inject
|
||||
intl,
|
||||
// redux
|
||||
@@ -32,7 +33,7 @@ export const ShowAnswerCard = ({
|
||||
</span>
|
||||
</div>
|
||||
<div className="pb-4">
|
||||
<Hyperlink destination={`${studioEndpointUrl}/settings/advanced/${learningContextId}`} target="_blank">
|
||||
<Hyperlink destination={`${studioEndpointUrl}/settings/advanced/${learningContextId}#showanswer`} target="_blank">
|
||||
<FormattedMessage {...messages.advancedSettingsLinkText} />
|
||||
</Hyperlink>
|
||||
</div>
|
||||
@@ -42,14 +43,20 @@ export const ShowAnswerCard = ({
|
||||
value={showAnswer.on}
|
||||
onChange={handleShowAnswerChange}
|
||||
>
|
||||
{Object.values(ShowAnswerTypesKeys).map((answerType) => (
|
||||
<option
|
||||
key={answerType}
|
||||
value={answerType}
|
||||
>
|
||||
{intl.formatMessage(ShowAnswerTypes[answerType])}
|
||||
</option>
|
||||
))}
|
||||
{Object.values(ShowAnswerTypesKeys).map((answerType) => {
|
||||
let optionDisplayName = ShowAnswerTypes[answerType];
|
||||
if (answerType === defaultValue) {
|
||||
optionDisplayName = { ...optionDisplayName, defaultMessage: `${optionDisplayName.defaultMessage} (Default)` };
|
||||
}
|
||||
return (
|
||||
<option
|
||||
key={answerType}
|
||||
value={answerType}
|
||||
>
|
||||
{intl.formatMessage(optionDisplayName)}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Form.Control>
|
||||
</Form.Group>
|
||||
{showAttempts
|
||||
@@ -84,6 +91,7 @@ ShowAnswerCard.propTypes = {
|
||||
updateSettings: PropTypes.func.isRequired,
|
||||
studioEndpointUrl: PropTypes.string.isRequired,
|
||||
learningContextId: PropTypes.string.isRequired,
|
||||
defaultValue: PropTypes.string.isRequired,
|
||||
};
|
||||
ShowAnswerCard.defaultProps = {
|
||||
solutionExplanation: '',
|
||||
|
||||
@@ -27,6 +27,7 @@ describe('ShowAnswerCard', () => {
|
||||
};
|
||||
const props = {
|
||||
showAnswer,
|
||||
defaultValue: 'finished',
|
||||
// injected
|
||||
intl: { formatMessage },
|
||||
// redux
|
||||
|
||||
@@ -23,7 +23,7 @@ exports[`ResetCard snapshot snapshot: renders reset true setting card 1`] = `
|
||||
className="spacedMessage"
|
||||
>
|
||||
<Hyperlink
|
||||
destination="sTuDioEndpOintUrl/settings/advanced/leArningCoNteXtId"
|
||||
destination="sTuDioEndpOintUrl/settings/advanced/leArningCoNteXtId#show_reset_button"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
@@ -86,7 +86,7 @@ exports[`ResetCard snapshot snapshot: renders reset true setting card 2`] = `
|
||||
className="spacedMessage"
|
||||
>
|
||||
<Hyperlink
|
||||
destination="sTuDioEndpOintUrl/settings/advanced/leArningCoNteXtId"
|
||||
destination="sTuDioEndpOintUrl/settings/advanced/leArningCoNteXtId#show_reset_button"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
|
||||
exports[`ScoringCard snapshot snapshot: scoring setting card 1`] = `
|
||||
<SettingsOption
|
||||
className=""
|
||||
className="scoringCard"
|
||||
extraSections={Array []}
|
||||
hasExpandableTextArea={false}
|
||||
summary="{attempts, plural, =1 {# attempt} other {# attempts}} · {weight, plural, =0 {Ungraded} other {# points}}"
|
||||
summary="{weight, plural, =0 {Ungraded} other {# points}} · {attempts, plural, =1 {# attempt} other {# attempts}}"
|
||||
title="Scoring"
|
||||
>
|
||||
<Form.Label
|
||||
@@ -17,21 +17,6 @@ exports[`ScoringCard snapshot snapshot: scoring setting card 1`] = `
|
||||
id="authoring.problemeditor.settings.scoring.label"
|
||||
/>
|
||||
</Form.Label>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
floatingLabel="Attempts"
|
||||
onChange={[MockFunction scoringCardHooks.handleMaxAttemptChange]}
|
||||
type="number"
|
||||
value={5}
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage
|
||||
defaultMessage="If a value is not set, unlimited attempts are allowed"
|
||||
description="Summary text for scoring weight"
|
||||
id="authoring.problemeditor.settings.scoring.attempts.hint"
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
floatingLabel="Points"
|
||||
@@ -47,15 +32,54 @@ exports[`ScoringCard snapshot snapshot: scoring setting card 1`] = `
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
disabled={false}
|
||||
floatingLabel="Attempts"
|
||||
onBlur={[MockFunction scoringCardHooks.handleMaxAttemptChange]}
|
||||
onChange={[MockFunction scoringCardHooks.handleOnChange]}
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage
|
||||
defaultMessage="If a value is not set, unlimited attempts are allowed"
|
||||
description="Summary text for scoring weight"
|
||||
id="authoring.problemeditor.settings.scoring.attempts.hint"
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
<Form.Checkbox
|
||||
checked={false}
|
||||
className="mt-3 decoration-control-label"
|
||||
>
|
||||
<div
|
||||
className="small"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Unlimited attempts"
|
||||
description="Label for unlimited attempts checkbox"
|
||||
id="authoring.problemeditor.settings.scoring.attempts.unlimitedCheckbox"
|
||||
/>
|
||||
</div>
|
||||
</Form.Checkbox>
|
||||
</Form.Group>
|
||||
<Hyperlink
|
||||
destination="undefined/settings/advanced/undefined#max_attempts"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Set a default value in advanced settings"
|
||||
description="Advanced settings link text"
|
||||
id="authoring.problemeditor.settings.advancedSettingLink.text"
|
||||
/>
|
||||
</Hyperlink>
|
||||
</SettingsOption>
|
||||
`;
|
||||
|
||||
exports[`ScoringCard snapshot snapshot: scoring setting card max attempts 1`] = `
|
||||
<SettingsOption
|
||||
className=""
|
||||
className="scoringCard"
|
||||
extraSections={Array []}
|
||||
hasExpandableTextArea={false}
|
||||
summary="Unlimited attempts · {weight, plural, =0 {Ungraded} other {# points}}"
|
||||
summary="{weight, plural, =0 {Ungraded} other {# points}} · Unlimited attempts"
|
||||
title="Scoring"
|
||||
>
|
||||
<Form.Label
|
||||
@@ -67,21 +91,6 @@ exports[`ScoringCard snapshot snapshot: scoring setting card max attempts 1`] =
|
||||
id="authoring.problemeditor.settings.scoring.label"
|
||||
/>
|
||||
</Form.Label>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
floatingLabel="Attempts"
|
||||
onChange={[MockFunction scoringCardHooks.handleMaxAttemptChange]}
|
||||
type="number"
|
||||
value={0}
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage
|
||||
defaultMessage="If a value is not set, unlimited attempts are allowed"
|
||||
description="Summary text for scoring weight"
|
||||
id="authoring.problemeditor.settings.scoring.attempts.hint"
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
floatingLabel="Points"
|
||||
@@ -97,15 +106,54 @@ exports[`ScoringCard snapshot snapshot: scoring setting card max attempts 1`] =
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
disabled={true}
|
||||
floatingLabel="Attempts"
|
||||
onBlur={[MockFunction scoringCardHooks.handleMaxAttemptChange]}
|
||||
onChange={[MockFunction scoringCardHooks.handleOnChange]}
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage
|
||||
defaultMessage="If a value is not set, unlimited attempts are allowed"
|
||||
description="Summary text for scoring weight"
|
||||
id="authoring.problemeditor.settings.scoring.attempts.hint"
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
<Form.Checkbox
|
||||
checked={true}
|
||||
className="mt-3 decoration-control-label"
|
||||
>
|
||||
<div
|
||||
className="small"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Unlimited attempts"
|
||||
description="Label for unlimited attempts checkbox"
|
||||
id="authoring.problemeditor.settings.scoring.attempts.unlimitedCheckbox"
|
||||
/>
|
||||
</div>
|
||||
</Form.Checkbox>
|
||||
</Form.Group>
|
||||
<Hyperlink
|
||||
destination="undefined/settings/advanced/undefined#max_attempts"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Set a default value in advanced settings"
|
||||
description="Advanced settings link text"
|
||||
id="authoring.problemeditor.settings.advancedSettingLink.text"
|
||||
/>
|
||||
</Hyperlink>
|
||||
</SettingsOption>
|
||||
`;
|
||||
|
||||
exports[`ScoringCard snapshot snapshot: scoring setting card zero zero weight 1`] = `
|
||||
<SettingsOption
|
||||
className=""
|
||||
className="scoringCard"
|
||||
extraSections={Array []}
|
||||
hasExpandableTextArea={false}
|
||||
summary="{attempts, plural, =1 {# attempt} other {# attempts}} · {weight, plural, =0 {Ungraded} other {# points}}"
|
||||
summary="{weight, plural, =0 {Ungraded} other {# points}} · {attempts, plural, =1 {# attempt} other {# attempts}}"
|
||||
title="Scoring"
|
||||
>
|
||||
<Form.Label
|
||||
@@ -117,21 +165,6 @@ exports[`ScoringCard snapshot snapshot: scoring setting card zero zero weight 1`
|
||||
id="authoring.problemeditor.settings.scoring.label"
|
||||
/>
|
||||
</Form.Label>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
floatingLabel="Attempts"
|
||||
onChange={[MockFunction scoringCardHooks.handleMaxAttemptChange]}
|
||||
type="number"
|
||||
value={5}
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage
|
||||
defaultMessage="If a value is not set, unlimited attempts are allowed"
|
||||
description="Summary text for scoring weight"
|
||||
id="authoring.problemeditor.settings.scoring.attempts.hint"
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
floatingLabel="Points"
|
||||
@@ -147,5 +180,44 @@ exports[`ScoringCard snapshot snapshot: scoring setting card zero zero weight 1`
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
</Form.Group>
|
||||
<Form.Group>
|
||||
<Form.Control
|
||||
disabled={false}
|
||||
floatingLabel="Attempts"
|
||||
onBlur={[MockFunction scoringCardHooks.handleMaxAttemptChange]}
|
||||
onChange={[MockFunction scoringCardHooks.handleOnChange]}
|
||||
/>
|
||||
<Form.Control.Feedback>
|
||||
<FormattedMessage
|
||||
defaultMessage="If a value is not set, unlimited attempts are allowed"
|
||||
description="Summary text for scoring weight"
|
||||
id="authoring.problemeditor.settings.scoring.attempts.hint"
|
||||
/>
|
||||
</Form.Control.Feedback>
|
||||
<Form.Checkbox
|
||||
checked={false}
|
||||
className="mt-3 decoration-control-label"
|
||||
>
|
||||
<div
|
||||
className="small"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Unlimited attempts"
|
||||
description="Label for unlimited attempts checkbox"
|
||||
id="authoring.problemeditor.settings.scoring.attempts.unlimitedCheckbox"
|
||||
/>
|
||||
</div>
|
||||
</Form.Checkbox>
|
||||
</Form.Group>
|
||||
<Hyperlink
|
||||
destination="undefined/settings/advanced/undefined#max_attempts"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Set a default value in advanced settings"
|
||||
description="Advanced settings link text"
|
||||
id="authoring.problemeditor.settings.advancedSettingLink.text"
|
||||
/>
|
||||
</Hyperlink>
|
||||
</SettingsOption>
|
||||
`;
|
||||
|
||||
@@ -23,7 +23,7 @@ exports[`ShowAnswerCard snapshot snapshot: show answer setting card 1`] = `
|
||||
className="pb-4"
|
||||
>
|
||||
<Hyperlink
|
||||
destination="SoMEeNDpOinT/settings/advanced/sOMEcouRseId"
|
||||
destination="SoMEeNDpOinT/settings/advanced/sOMEcouRseId#showanswer"
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage
|
||||
@@ -69,7 +69,7 @@ exports[`ShowAnswerCard snapshot snapshot: show answer setting card 1`] = `
|
||||
key="finished"
|
||||
value="finished"
|
||||
>
|
||||
Finished
|
||||
Finished (Default)
|
||||
</option>
|
||||
<option
|
||||
key="correct_or_past_due"
|
||||
|
||||
@@ -19,6 +19,7 @@ export const ProblemEditor = ({
|
||||
blockValue,
|
||||
initializeProblemEditor,
|
||||
assetsFinished,
|
||||
advancedSettingsFinished,
|
||||
}) => {
|
||||
React.useEffect(() => {
|
||||
if (blockFinished && studioViewFinished && assetsFinished && !blockFailed) {
|
||||
@@ -26,7 +27,7 @@ export const ProblemEditor = ({
|
||||
}
|
||||
}, [blockFinished, studioViewFinished, assetsFinished, blockFailed]);
|
||||
|
||||
if (!blockFinished || !studioViewFinished || !assetsFinished) {
|
||||
if (!blockFinished || !studioViewFinished || !assetsFinished || !advancedSettingsFinished) {
|
||||
return (
|
||||
<div className="text-center p-6">
|
||||
<Spinner
|
||||
@@ -59,6 +60,7 @@ ProblemEditor.propTypes = {
|
||||
onClose: PropTypes.func.isRequired,
|
||||
// redux
|
||||
assetsFinished: PropTypes.bool,
|
||||
advancedSettingsFinished: PropTypes.bool.isRequired,
|
||||
blockFinished: PropTypes.bool.isRequired,
|
||||
blockFailed: PropTypes.bool.isRequired,
|
||||
studioViewFinished: PropTypes.bool.isRequired,
|
||||
@@ -74,6 +76,7 @@ export const mapStateToProps = (state) => ({
|
||||
problemType: selectors.problem.problemType(state),
|
||||
blockValue: selectors.app.blockValue(state),
|
||||
assetsFinished: selectors.requests.isFinished(state, { requestKey: RequestKeys.fetchAssets }),
|
||||
advancedSettingsFinished: selectors.requests.isFinished(state, { requestKey: RequestKeys.fetchAdvancedSettings }),
|
||||
});
|
||||
|
||||
export const mapDispatchToProps = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
|
||||
import { Spinner } from '@edx/paragon';
|
||||
import { thunkActions, selectors } from '../../data/redux';
|
||||
import { RequestKeys } from '../../data/constants/requests';
|
||||
import { ProblemEditor, mapStateToProps, mapDispatchToProps } from '.';
|
||||
@@ -48,41 +48,59 @@ describe('ProblemEditor', () => {
|
||||
studioViewFinished: false,
|
||||
initializeProblemEditor: jest.fn().mockName('args.intializeProblemEditor'),
|
||||
assetsFinished: false,
|
||||
advancedSettingsFinished: false,
|
||||
};
|
||||
describe('snapshots', () => {
|
||||
test('renders as expected with default behavior', () => {
|
||||
expect(shallow(<ProblemEditor {...props} />)).toMatchSnapshot();
|
||||
});
|
||||
test('block loaded, studio view and assets not yet loaded, Spinner appears', () => {
|
||||
expect(shallow(<ProblemEditor {...props} blockFinished />)).toMatchSnapshot();
|
||||
const wrapper = shallow(<ProblemEditor {...props} blockFinished />);
|
||||
expect(wrapper.containsMatchingElement(<Spinner />)).toEqual(true);
|
||||
});
|
||||
test('studio view loaded, block and assets not yet loaded, Spinner appears', () => {
|
||||
expect(shallow(<ProblemEditor {...props} studioViewFinished />)).toMatchSnapshot();
|
||||
const wrapper = shallow(<ProblemEditor {...props} studioViewFinished />);
|
||||
expect(wrapper.containsMatchingElement(<Spinner />)).toEqual(true);
|
||||
});
|
||||
test('assets loaded, block and studio view not yet loaded, Spinner appears', () => {
|
||||
expect(shallow(<ProblemEditor {...props} assetsFinished />)).toMatchSnapshot();
|
||||
const wrapper = shallow(<ProblemEditor {...props} assetsFinished />);
|
||||
expect(wrapper.containsMatchingElement(<Spinner />)).toEqual(true);
|
||||
});
|
||||
test('advanceSettings loaded, block and studio view not yet loaded, Spinner appears', () => {
|
||||
const wrapper = shallow(<ProblemEditor {...props} advancedSettingsFinished />);
|
||||
expect(wrapper.containsMatchingElement(<Spinner />)).toEqual(true);
|
||||
});
|
||||
test('block failed, message appears', () => {
|
||||
expect(shallow(<ProblemEditor
|
||||
const wrapper = shallow(<ProblemEditor
|
||||
{...props}
|
||||
blockFinished
|
||||
studioViewFinished
|
||||
assetsFinished
|
||||
advancedSettingsFinished
|
||||
blockFailed
|
||||
/>)).toMatchSnapshot();
|
||||
/>);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
test('renders SelectTypeModal', () => {
|
||||
expect(shallow(<ProblemEditor {...props} blockFinished studioViewFinished assetsFinished />)).toMatchSnapshot();
|
||||
const wrapper = shallow(<ProblemEditor
|
||||
{...props}
|
||||
blockFinished
|
||||
studioViewFinished
|
||||
assetsFinished
|
||||
advancedSettingsFinished
|
||||
/>);
|
||||
expect(wrapper.find('SelectTypeModal')).toHaveLength(1);
|
||||
});
|
||||
test('renders EditProblemView', () => {
|
||||
expect(shallow(<ProblemEditor
|
||||
const wrapper = shallow(<ProblemEditor
|
||||
{...props}
|
||||
problemType="multiplechoiceresponse"
|
||||
blockFinished
|
||||
blockFailed
|
||||
studioViewFinished
|
||||
assetsFinished
|
||||
/>)).toMatchSnapshot();
|
||||
advancedSettingsFinished
|
||||
/>);
|
||||
expect(wrapper.find('EditProblemView')).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -113,6 +131,11 @@ describe('ProblemEditor', () => {
|
||||
mapStateToProps(testState).assetsFinished,
|
||||
).toEqual(selectors.requests.isFinished(testState, { requestKey: RequestKeys.fetchAssets }));
|
||||
});
|
||||
test('advancedSettingsFinished from requests.isFinished', () => {
|
||||
expect(
|
||||
mapStateToProps(testState).advancedSettingsFinished,
|
||||
).toEqual(selectors.requests.isFinished(testState, { requestKey: RequestKeys.fetchAdvancedSettings }));
|
||||
});
|
||||
});
|
||||
describe('mapDispatchToProps', () => {
|
||||
test('initializeProblemEditor from thunkActions.problem.initializeProblem', () => {
|
||||
|
||||
@@ -25,6 +25,6 @@ export const RequestKeys = StrictDict({
|
||||
checkTranscriptsForImport: 'checkTranscriptsForImport',
|
||||
importTranscript: 'importTranscript',
|
||||
uploadImage: 'uploadImage',
|
||||
fetchAdvanceSettings: 'fetchAdvanceSettings',
|
||||
fetchAdvancedSettings: 'fetchAdvancedSettings',
|
||||
fetchVideoFeatures: 'fetchVideoFeatures',
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@ const initialState = {
|
||||
groupFeedbackList: [],
|
||||
generalFeedback: '',
|
||||
additionalAttributes: {},
|
||||
defaultSettings: {},
|
||||
settings: {
|
||||
randomization: null,
|
||||
scoring: {
|
||||
@@ -147,10 +148,23 @@ const problem = createSlice({
|
||||
},
|
||||
...payload,
|
||||
}),
|
||||
setEnableTypeSelection: (state) => ({
|
||||
...state,
|
||||
problemType: null,
|
||||
}),
|
||||
setEnableTypeSelection: (state, { payload }) => {
|
||||
const { maxAttempts, showanswer, showResetButton } = payload;
|
||||
const attempts = { number: maxAttempts, unlimited: false };
|
||||
if (!maxAttempts) {
|
||||
attempts.unlimited = true;
|
||||
}
|
||||
return {
|
||||
...state,
|
||||
settings: {
|
||||
...state.settings,
|
||||
scoring: { ...state.settings.scoring, attempts },
|
||||
showAnswer: { ...state.settings.showAnswer, on: showanswer },
|
||||
...showResetButton,
|
||||
},
|
||||
problemType: null,
|
||||
};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -29,8 +29,22 @@ describe('problem reducer', () => {
|
||||
].map(args => setterTest(...args));
|
||||
describe('setEnableTypeSelection', () => {
|
||||
it('sets given problemType to null', () => {
|
||||
expect(reducer(testingState, actions.setEnableTypeSelection())).toEqual({
|
||||
const payload = {
|
||||
maxAttempts: 1,
|
||||
showanswer: 'finished',
|
||||
showResetButton: false,
|
||||
};
|
||||
expect(reducer(testingState, actions.setEnableTypeSelection(payload))).toEqual({
|
||||
...testingState,
|
||||
settings: {
|
||||
...testingState.settings,
|
||||
scoring: {
|
||||
...testingState.settings.scoring,
|
||||
attempts: { number: 1, unlimited: false },
|
||||
},
|
||||
showAnswer: { ...testingState.settings.showAnswer, on: payload.showanswer },
|
||||
...payload.showResetButton,
|
||||
},
|
||||
problemType: null,
|
||||
});
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ export const simpleSelectors = {
|
||||
correctAnswerCount: mkSimpleSelector(problemData => problemData.correctAnswerCount),
|
||||
settings: mkSimpleSelector(problemData => problemData.settings),
|
||||
question: mkSimpleSelector(problemData => problemData.question),
|
||||
defaultSettings: mkSimpleSelector(problemData => problemData.defaultSettings),
|
||||
completeState: mkSimpleSelector(problemData => problemData),
|
||||
};
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ describe('problem selectors unit tests', () => {
|
||||
simpleKeys.correctAnswerCount,
|
||||
simpleKeys.settings,
|
||||
simpleKeys.question,
|
||||
simpleKeys.defaultSettings,
|
||||
].map(testSimpleSelector);
|
||||
});
|
||||
test('simple selector completeState equals the entire state', () => {
|
||||
|
||||
@@ -19,6 +19,7 @@ const initialState = {
|
||||
[RequestKeys.checkTranscriptsForImport]: { status: RequestStates.inactive },
|
||||
[RequestKeys.importTranscript]: { status: RequestStates.inactive },
|
||||
[RequestKeys.fetchVideoFeatures]: { status: RequestStates.inactive },
|
||||
[RequestKeys.fetchAdvancedSettings]: { status: RequestStates.inactive },
|
||||
};
|
||||
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import _ from 'lodash-es';
|
||||
import { actions } from '..';
|
||||
import * as requests from './requests';
|
||||
import { OLXParser } from '../../../containers/ProblemEditor/data/OLXParser';
|
||||
import { parseSettings } from '../../../containers/ProblemEditor/data/SettingsParser';
|
||||
import { ProblemTypeKeys } from '../../constants/problem';
|
||||
import ReactStateOLXParser from '../../../containers/ProblemEditor/data/ReactStateOLXParser';
|
||||
import { blankProblemOLX } from '../../../containers/ProblemEditor/data/mockData/olxTestData';
|
||||
import { camelizeKeys } from '../../../utils';
|
||||
import { fetchEditorContent } from '../../../containers/ProblemEditor/components/EditProblemView/hooks';
|
||||
|
||||
export const switchToAdvancedEditor = () => (dispatch, getState) => {
|
||||
@@ -43,15 +45,35 @@ export const getDataFromOlx = ({ rawOLX, rawSettings }) => {
|
||||
return {};
|
||||
};
|
||||
|
||||
export const initializeProblem = (blockValue) => (dispatch) => {
|
||||
const rawOLX = _.get(blockValue, 'data.data', {});
|
||||
const rawSettings = _.get(blockValue, 'data.metadata', {});
|
||||
|
||||
export const loadProblem = ({ rawOLX, rawSettings, defaultSettings }) => (dispatch) => {
|
||||
if (isBlankProblem({ rawOLX })) {
|
||||
dispatch(actions.problem.setEnableTypeSelection());
|
||||
dispatch(actions.problem.setEnableTypeSelection(camelizeKeys(defaultSettings)));
|
||||
} else {
|
||||
dispatch(actions.problem.load(getDataFromOlx({ rawOLX, rawSettings })));
|
||||
}
|
||||
};
|
||||
|
||||
export default { initializeProblem, switchToAdvancedEditor };
|
||||
export const fetchAdvancedSettings = ({ rawOLX, rawSettings }) => (dispatch) => {
|
||||
const advancedProblemSettingKeys = ['max_attempts', 'showanswer', 'show_reset_button'];
|
||||
dispatch(requests.fetchAdvancedSettings({
|
||||
onSuccess: (response) => {
|
||||
const defaultSettings = {};
|
||||
Object.entries(response.data).forEach(([key, value]) => {
|
||||
if (advancedProblemSettingKeys.includes(key)) {
|
||||
defaultSettings[key] = value.value;
|
||||
}
|
||||
});
|
||||
dispatch(actions.problem.updateField({ defaultSettings: camelizeKeys(defaultSettings) }));
|
||||
loadProblem({ rawOLX, rawSettings, defaultSettings })(dispatch);
|
||||
},
|
||||
onFailure: () => { loadProblem({ rawOLX, rawSettings, defaultSettings: {} })(dispatch); },
|
||||
}));
|
||||
};
|
||||
|
||||
export const initializeProblem = (blockValue) => (dispatch) => {
|
||||
const rawOLX = _.get(blockValue, 'data.data', {});
|
||||
const rawSettings = _.get(blockValue, 'data.metadata', {});
|
||||
dispatch(fetchAdvancedSettings({ rawOLX, rawSettings }));
|
||||
};
|
||||
|
||||
export default { initializeProblem, switchToAdvancedEditor, fetchAdvancedSettings };
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { actions } from '..';
|
||||
import { initializeProblem, switchToAdvancedEditor } from './problem';
|
||||
import * as module from './problem';
|
||||
import { checkboxesOLXWithFeedbackAndHintsOLX, advancedProblemOlX, blankProblemOLX } from '../../../containers/ProblemEditor/data/mockData/olxTestData';
|
||||
import { ProblemTypeKeys } from '../../constants/problem';
|
||||
|
||||
@@ -17,9 +17,25 @@ jest.mock('..', () => ({
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('./requests', () => ({
|
||||
fetchAdvancedSettings: (args) => ({ fetchAdvanceSettings: args }),
|
||||
}));
|
||||
|
||||
const blockValue = {
|
||||
data: {
|
||||
data: checkboxesOLXWithFeedbackAndHintsOLX.rawOLX,
|
||||
metadata: {},
|
||||
},
|
||||
};
|
||||
|
||||
let rawOLX = blockValue.data.data;
|
||||
const rawSettings = {};
|
||||
const defaultSettings = { max_attempts: 1 };
|
||||
|
||||
describe('problem thunkActions', () => {
|
||||
let dispatch;
|
||||
let getState;
|
||||
let dispatchedAction;
|
||||
beforeEach(() => {
|
||||
dispatch = jest.fn((action) => ({ dispatch: action }));
|
||||
getState = jest.fn(() => ({
|
||||
@@ -27,25 +43,51 @@ describe('problem thunkActions', () => {
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
});
|
||||
test('initializeProblem visual Problem :', () => {
|
||||
const blockValue = { data: { data: checkboxesOLXWithFeedbackAndHintsOLX.rawOLX } };
|
||||
initializeProblem(blockValue)(dispatch);
|
||||
expect(dispatch).toHaveBeenCalledWith(actions.problem.load());
|
||||
});
|
||||
test('initializeProblem advanced Problem', () => {
|
||||
const blockValue = { data: { data: advancedProblemOlX.rawOLX } };
|
||||
initializeProblem(blockValue)(dispatch);
|
||||
expect(dispatch).toHaveBeenCalledWith(actions.problem.load());
|
||||
});
|
||||
test('initializeProblem blank Problem', () => {
|
||||
const blockValue = { data: { data: blankProblemOLX.rawOLX } };
|
||||
initializeProblem(blockValue)(dispatch);
|
||||
expect(dispatch).toHaveBeenCalledWith(actions.problem.setEnableTypeSelection());
|
||||
module.initializeProblem(blockValue)(dispatch);
|
||||
expect(dispatch).toHaveBeenCalled();
|
||||
});
|
||||
test('switchToAdvancedEditor visual Problem', () => {
|
||||
switchToAdvancedEditor()(dispatch, getState);
|
||||
module.switchToAdvancedEditor()(dispatch, getState);
|
||||
expect(dispatch).toHaveBeenCalledWith(
|
||||
actions.problem.updateField({ problemType: ProblemTypeKeys.ADVANCED, rawOLX: mockOlx }),
|
||||
);
|
||||
});
|
||||
describe('fetchAdvanceSettings', () => {
|
||||
it('dispatches fetchAdvanceSettings action', () => {
|
||||
module.fetchAdvancedSettings({ rawOLX, rawSettings })(dispatch);
|
||||
[[dispatchedAction]] = dispatch.mock.calls;
|
||||
expect(dispatchedAction.fetchAdvanceSettings).not.toEqual(undefined);
|
||||
});
|
||||
it('dispatches actions.problem.updateField and loadProblem on success', () => {
|
||||
dispatch.mockClear();
|
||||
module.fetchAdvancedSettings({ rawOLX, rawSettings })(dispatch);
|
||||
[[dispatchedAction]] = dispatch.mock.calls;
|
||||
dispatchedAction.fetchAdvanceSettings.onSuccess({ data: { key: 'test', max_attempts: 1 } });
|
||||
expect(dispatch).toHaveBeenCalledWith(actions.problem.load());
|
||||
});
|
||||
it('calls loadProblem on failure', () => {
|
||||
dispatch.mockClear();
|
||||
module.fetchAdvancedSettings({ rawOLX, rawSettings })(dispatch);
|
||||
[[dispatchedAction]] = dispatch.mock.calls;
|
||||
dispatchedAction.fetchAdvanceSettings.onFailure();
|
||||
expect(dispatch).toHaveBeenCalledWith(actions.problem.load());
|
||||
});
|
||||
});
|
||||
describe('loadProblem', () => {
|
||||
test('initializeProblem advanced Problem', () => {
|
||||
rawOLX = advancedProblemOlX.rawOLX;
|
||||
module.loadProblem({ rawOLX, rawSettings, defaultSettings })(dispatch);
|
||||
expect(dispatch).toHaveBeenCalledWith(actions.problem.load());
|
||||
});
|
||||
test('initializeProblem blank Problem', () => {
|
||||
rawOLX = blankProblemOLX.rawOLX;
|
||||
module.loadProblem({ rawOLX, rawSettings, defaultSettings })(dispatch);
|
||||
expect(dispatch).toHaveBeenCalledWith(actions.problem.setEnableTypeSelection());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -261,10 +261,10 @@ export const fetchCourseDetails = ({ ...rest }) => (dispatch, getState) => {
|
||||
}));
|
||||
};
|
||||
|
||||
export const fetchAdvanceSettings = ({ ...rest }) => (dispatch, getState) => {
|
||||
export const fetchAdvancedSettings = ({ ...rest }) => (dispatch, getState) => {
|
||||
dispatch(module.networkRequest({
|
||||
requestKey: RequestKeys.fetchAdvanceSettings,
|
||||
promise: api.fetchAdvanceSettings({
|
||||
requestKey: RequestKeys.fetchAdvancedSettings,
|
||||
promise: api.fetchAdvancedSettings({
|
||||
studioEndpointUrl: selectors.app.studioEndpointUrl(getState()),
|
||||
learningContextId: selectors.app.learningContextId(getState()),
|
||||
}),
|
||||
@@ -299,6 +299,6 @@ export default StrictDict({
|
||||
getTranscriptFile,
|
||||
checkTranscriptsForImport,
|
||||
importTranscript,
|
||||
fetchAdvanceSettings,
|
||||
fetchAdvancedSettings,
|
||||
fetchVideoFeatures,
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ export const apiMethods = {
|
||||
fetchCourseDetails: ({ studioEndpointUrl, learningContextId }) => get(
|
||||
urls.courseDetailsUrl({ studioEndpointUrl, learningContextId }),
|
||||
),
|
||||
fetchAdvanceSettings: ({ studioEndpointUrl, learningContextId }) => get(
|
||||
fetchAdvancedSettings: ({ studioEndpointUrl, learningContextId }) => get(
|
||||
urls.courseAdvanceSettings({ studioEndpointUrl, learningContextId }),
|
||||
),
|
||||
uploadAsset: ({
|
||||
|
||||
@@ -61,6 +61,11 @@ export const problemDataProps = {
|
||||
afterAtempts: PropTypes.number,
|
||||
}),
|
||||
showResetButton: PropTypes.bool,
|
||||
defaultSettings: PropTypes.shape({
|
||||
max_attempts: PropTypes.number,
|
||||
showanswer: PropTypes.string,
|
||||
show_reset_button: PropTypes.bool,
|
||||
}),
|
||||
}),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user