diff --git a/package.json b/package.json index 74c4779ab..d8bdcf60e 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "build": "make build", "i18n_extract": "BABEL_ENV=i18n fedx-scripts babel src --quiet > /dev/null", "lint": "fedx-scripts eslint --ext .js --ext .jsx .", + "lint:fix": "fedx-scripts eslint --fix --ext .js --ext .jsx .", "snapshot": "fedx-scripts jest --updateSnapshot", "start": "fedx-scripts webpack-dev-server --progress", "test": "fedx-scripts jest --coverage", diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.js index d8b058d9c..b115d8cd0 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.js +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.js @@ -109,36 +109,40 @@ export const resetCardHooks = (updateSettings) => { }; export const scoringCardHooks = (scoring, updateSettings, defaultValue) => { - const loadedAttemptsNumber = scoring.attempts.number === defaultValue ? `${scoring.attempts.number} (Default)` : scoring.attempts.number; + let loadedAttemptsNumber = scoring.attempts.number; + if ((loadedAttemptsNumber === defaultValue || !_.isFinite(loadedAttemptsNumber)) && _.isFinite(defaultValue)) { + loadedAttemptsNumber = `${defaultValue} (Default)`; + } else if (loadedAttemptsNumber === defaultValue && _.isNil(defaultValue)) { + loadedAttemptsNumber = ''; + } 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 } } }); + updateSettings({ scoring: { ...scoring, attempts: { number: null, unlimited: true } } }); } else { - setAttemptDisplayValue(`${defaultValue} (Default)`); - updateSettings({ scoring: { ...scoring, attempts: { number: defaultValue, unlimited: false } } }); + updateSettings({ scoring: { ...scoring, attempts: { number: null, unlimited: false } } }); } }; + const handleMaxAttemptChange = (event) => { let unlimitedAttempts = false; let attemptNumber = parseInt(event.target.value); - const { value } = event.target; - if (_.isNaN(attemptNumber)) { - if (value === '') { - attemptNumber = defaultValue; + + if (!_.isFinite(attemptNumber) || attemptNumber === defaultValue) { + attemptNumber = null; + if (_.isFinite(defaultValue)) { setAttemptDisplayValue(`${defaultValue} (Default)`); } else { - attemptNumber = ''; + setAttemptDisplayValue(''); 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 } } }); }; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.test.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.test.js index a93ddc294..8b636c0a8 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.test.js +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/hooks.test.js @@ -148,6 +148,26 @@ describe('Problem settings hooks', () => { }, }; const defaultValue = 1; + test('test scoringCardHooks initializes display value when attempts.number is null', () => { + const nilScoring = { ...scoring, attempts: { unlimited: false, number: null } }; + output = hooks.scoringCardHooks(nilScoring, updateSettings, defaultValue); + expect(state.stateVals[state.keys.attemptDisplayValue]).toEqual(`${defaultValue} (Default)`); + }); + test('test scoringCardHooks initializes display value when attempts.number is blank', () => { + const nilScoring = { ...scoring, attempts: { unlimited: false, number: '' } }; + output = hooks.scoringCardHooks(nilScoring, updateSettings, defaultValue); + expect(state.stateVals[state.keys.attemptDisplayValue]).toEqual(`${defaultValue} (Default)`); + }); + test('test scoringCardHooks initializes display value when attempts.number is not null', () => { + const nonNilScoring = { ...scoring, attempts: { unlimited: false, number: 2 } }; + output = hooks.scoringCardHooks(nonNilScoring, updateSettings, defaultValue); + expect(state.stateVals[state.keys.attemptDisplayValue]).toEqual(2); + }); + test('test scoringCardHooks initializes display value when attempts.number and defaultValue is null', () => { + const nonNilScoring = { ...scoring, attempts: { unlimited: false, number: null } }; + output = hooks.scoringCardHooks(nonNilScoring, updateSettings, null); + expect(state.stateVals[state.keys.attemptDisplayValue]).toEqual(''); + }); beforeEach(() => { output = hooks.scoringCardHooks(scoring, updateSettings, defaultValue); }); @@ -155,13 +175,12 @@ describe('Problem settings hooks', () => { output.handleUnlimitedChange({ target: { checked: true } }); expect(state.setState[state.keys.attemptDisplayValue]).toHaveBeenCalledWith(''); expect(updateSettings) - .toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: '', unlimited: true } } }); + .toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: null, 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 } } }); + .toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: null, unlimited: false } } }); }); test('test handleMaxAttemptChange', () => { const value = 6; @@ -175,30 +194,30 @@ describe('Problem settings hooks', () => { expect(updateSettings) .toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: value, unlimited: false } } }); }); - test('test handleMaxAttemptChange set attempts to null value', () => { + test('test handleMaxAttemptChange set attempts to null value when default max_attempts is present', () => { const value = null; output.handleMaxAttemptChange({ target: { value } }); expect(updateSettings) - .toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: '', unlimited: true } } }); + .toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: null, unlimited: false } } }); }); - test('test handleMaxAttemptChange set attempts to default value', () => { + test('test handleMaxAttemptChange set attempts to null when default value is inputted', () => { const value = '1 (Default)'; output.handleMaxAttemptChange({ target: { value } }); expect(updateSettings) - .toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: 1, unlimited: false } } }); + .toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: null, unlimited: false } } }); }); test('test handleMaxAttemptChange set attempts to non-numeric value', () => { const value = 'abc'; output.handleMaxAttemptChange({ target: { value } }); expect(updateSettings) - .toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: '', unlimited: true } } }); + .toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: null, unlimited: false } } }); }); 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 } } }); + .toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: null, unlimited: false } } }); }); test('test handleMaxAttemptChange set attempts to negative value', () => { const value = -1; @@ -206,6 +225,14 @@ describe('Problem settings hooks', () => { expect(updateSettings) .toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: 0, unlimited: false } } }); }); + test('test handleMaxAttemptChange set attempts to empty value with no default', () => { + const value = ''; + output = hooks.scoringCardHooks(scoring, updateSettings, null); + output.handleMaxAttemptChange({ target: { value } }); + expect(state.setState[state.keys.attemptDisplayValue]).toHaveBeenCalledWith(''); + expect(updateSettings) + .toHaveBeenCalledWith({ scoring: { ...scoring, attempts: { number: null, unlimited: true } } }); + }); test('test handleOnChange', () => { const value = 6; output.handleOnChange({ target: { value } }); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/messages.js b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/messages.js index 77b771f6c..26c5414a1 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/messages.js +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/messages.js @@ -109,7 +109,7 @@ const messages = defineMessages({ }, attemptsHint: { id: 'authoring.problemeditor.settings.scoring.attempts.hint', - defaultMessage: 'If a value is not set, unlimited attempts are allowed', + defaultMessage: 'If a default value is not set in advanced settings, unlimited attempts are allowed', description: 'Summary text for scoring weight', }, weightHint: { diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ScoringCard.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ScoringCard.jsx index 74fc79edb..06ca2cb59 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ScoringCard.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ScoringCard.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import _ from 'lodash-es'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; @@ -32,7 +33,7 @@ export const ScoringCard = ({ summary += ` ${String.fromCharCode(183)} `; summary += unlimited ? intl.formatMessage(messages.unlimitedAttemptsSummary) - : intl.formatMessage(messages.attemptsSummary, { attempts }); + : intl.formatMessage(messages.attemptsSummary, { attempts: attempts || defaultValue }); return summary; }; @@ -71,6 +72,7 @@ export const ScoringCard = ({ className="mt-3 decoration-control-label" checked={scoring.attempts.unlimited} onChange={handleUnlimitedChange} + disabled={!_.isNil(defaultValue)} >