feat: add answer range (#291)

* feat: add answer range

* feat: add margin for doropdown

* fix: improve test converage
This commit is contained in:
connorhaugh
2023-03-30 10:34:45 -04:00
committed by GitHub
parent 98ec415e2b
commit c2b67429d3
16 changed files with 606 additions and 42 deletions

View File

@@ -8,7 +8,7 @@ import {
Form,
} from '@edx/paragon';
import { FeedbackOutline, DeleteOutline } from '@edx/paragon/icons';
import { injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n';
import messages from './messages';
import { selectors } from '../../../../../data/redux';
import { answerOptionProps } from '../../../../../data/services/cms/types';
@@ -38,6 +38,53 @@ export const AnswerOption = ({
const setSelectedFeedback = hooks.setSelectedFeedback({ answer, hasSingleAnswer, dispatch });
const setUnselectedFeedback = hooks.setUnselectedFeedback({ answer, hasSingleAnswer, dispatch });
const { isFeedbackVisible, toggleFeedback } = hooks.useFeedback(answer);
const getInputArea = () => {
if ([ProblemTypeKeys.SINGLESELECT, ProblemTypeKeys.MULTISELECT].includes(problemType)) {
return (
<ExpandableTextArea
value={answer.title}
setContent={setAnswerTitle}
placeholder={intl.formatMessage(messages.answerTextboxPlaceholder)}
id={`answer-${answer.id}`}
/>
);
}
if (problemType !== ProblemTypeKeys.NUMERIC || !answer.isAnswerRange) {
return (
<Form.Control
as="textarea"
className="answer-option-textarea text-gray-500 small"
autoResize
rows={1}
value={answer.title}
onChange={setAnswerTitle}
placeholder={intl.formatMessage(messages.answerTextboxPlaceholder)}
/>
);
}
// Return Answer Range View
return (
<div>
<Form.Control
as="textarea"
className="answer-option-textarea text-gray-500 small"
autoResize
rows={1}
value={answer.title}
onChange={setAnswerTitle}
placeholder={intl.formatMessage(messages.answerRangeTextboxPlaceholder)}
/>
<hr className="d-block" />
<div className="pgn__form-switch-helper-text">
<FormattedMessage {...messages.answerRangeHelperText} />
</div>
</div>
);
};
return (
<Collapsible.Advanced
open={isFeedbackVisible}
@@ -53,24 +100,7 @@ export const AnswerOption = ({
/>
</div>
<div className="ml-1 flex-grow-1">
{[ProblemTypeKeys.SINGLESELECT, ProblemTypeKeys.MULTISELECT].includes(problemType) ? (
<ExpandableTextArea
value={answer.title}
setContent={setAnswerTitle}
placeholder={intl.formatMessage(messages.answerTextboxPlaceholder)}
id={`answer-${answer.id}`}
/>
) : (
<Form.Control
as="textarea"
className="answer-option-textarea text-gray-500 small"
autoResize
rows={1}
value={answer.title}
onChange={setAnswerTitle}
placeholder={intl.formatMessage(messages.answerTextboxPlaceholder)}
/>
)}
{getInputArea()}
<Collapsible.Body>
<FeedbackBox
problemType={problemType}

View File

@@ -26,6 +26,14 @@ describe('AnswerOption', () => {
selectedFeedback: 'selected feedback',
unselectedFeedback: 'unselected feedback',
};
const answerRange = {
id: 'A',
title: 'Answer 1',
correct: true,
selectedFeedback: 'selected feedback',
unselectedFeedback: 'unselected feedback',
isAnswerRange: true,
};
const props = {
hasSingleAnswer: false,
@@ -45,7 +53,11 @@ describe('AnswerOption', () => {
test('snapshot: renders correct option with numeric input problem', () => {
expect(shallow(<AnswerOption {...props} problemType="numericalresponse" />)).toMatchSnapshot();
});
test('snapshot: renders correct option with numeric input problem and answer range', () => {
expect(shallow(<AnswerOption {...props} problemType="numericalresponse" answer={answerRange} />)).toMatchSnapshot();
});
});
describe('mapStateToProps', () => {
const testState = { A: 'pple', B: 'anana', C: 'ucumber' };
test('problemType from problem.problemType', () => {

View File

@@ -3,18 +3,22 @@ import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { FormattedMessage } from '@edx/frontend-platform/i18n';
import { Dropdown, Icon } from '@edx/paragon';
import { Add } from '@edx/paragon/icons';
import messages from './messages';
import { useAnswerContainer, isSingleAnswerProblem } from './hooks';
import { actions, selectors } from '../../../../../data/redux';
import { answerOptionProps } from '../../../../../data/services/cms/types';
import AnswerOption from './AnswerOption';
import Button from '../../../../../sharedComponents/Button';
import { ProblemTypeKeys } from '../../../../../data/constants/problem';
export const AnswersContainer = ({
problemType,
// Redux
answers,
addAnswer,
addAnswerRange,
updateField,
}) => {
const hasSingleAnswer = isSingleAnswerProblem(problemType);
@@ -30,12 +34,45 @@ export const AnswersContainer = ({
answer={answer}
/>
))}
<Button
variant="add"
onClick={addAnswer}
>
<FormattedMessage {...messages.addAnswerButtonText} />
</Button>
{problemType !== ProblemTypeKeys.NUMERIC ? (
<Button
variant="add"
onClick={addAnswer}
>
<FormattedMessage {...messages.addAnswerButtonText} />
</Button>
) : (
<Dropdown>
<Dropdown.Toggle
id="Add-Answer-Or-Answer-Range"
variant="tertiary"
>
<Icon
src={Add}
/>
<FormattedMessage {...messages.addAnswerButtonText} />
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item
key="add-answer"
onClick={addAnswer}
className={`AddAnswerRange ${answers.length === 1 && answers[0].isAnswerRange ? 'disabled' : ''}`}
>
<FormattedMessage {...messages.addAnswerButtonText} />
</Dropdown.Item>
<Dropdown.Item
key="add-answer-range"
onClick={addAnswerRange}
className={`AddAnswerRange ${answers.length > 1 || (answers.length === 1 && answers[0].isAnswerRange) ? 'disabled' : ''}`}
>
<FormattedMessage {...messages.addAnswerRangeButtonText} />
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
)}
</div>
);
};
@@ -44,6 +81,7 @@ AnswersContainer.propTypes = {
problemType: PropTypes.string.isRequired,
answers: PropTypes.arrayOf(answerOptionProps).isRequired,
addAnswer: PropTypes.func.isRequired,
addAnswerRange: PropTypes.func.isRequired,
updateField: PropTypes.func.isRequired,
};
@@ -53,6 +91,7 @@ export const mapStateToProps = (state) => ({
export const mapDispatchToProps = {
addAnswer: actions.problem.addAnswer,
addAnswerRange: actions.problem.addAnswerRange,
updateField: actions.problem.updateField,
};

View File

@@ -8,6 +8,7 @@ import { actions, selectors } from '../../../../../data/redux';
import * as module from './AnswersContainer';
import { AnswersContainer as AnswersContainerWithoutHOC } from './AnswersContainer';
import { ProblemTypeKeys } from '../../../../../data/constants/problem';
jest.mock('@edx/frontend-platform/i18n', () => ({
FormattedMessage: ({ defaultMessage }) => (<p>{defaultMessage}</p>),
@@ -54,6 +55,77 @@ describe('AnswersContainer', () => {
)).toMatchSnapshot();
});
});
test('snapshot: numeric problems: answer range/answer select button: empty', () => {
act(() => {
const emptyAnswerProps = {
problemType: ProblemTypeKeys.NUMERIC,
answers: [],
updateField: jest.fn(),
addAnswer: jest.fn(),
addAnswerRange: jest.fn(),
};
expect(shallow(
<module.AnswersContainer
{...emptyAnswerProps}
/>,
)).toMatchSnapshot();
});
});
test('snapshot: numeric problems: answer range/answer select button: Range disables the additon of more adds', () => {
act(() => {
const answerRangeProps = {
problemType: ProblemTypeKeys.NUMERIC,
answers: [{
id: 'A',
title: 'Answer 1',
correct: true,
selectedFeedback: 'selected feedback',
unselectedFeedback: 'unselected feedback',
isAnswerRange: true,
}],
updateField: jest.fn(),
addAnswer: jest.fn(),
addAnswerRange: jest.fn(),
};
expect(shallow(
<module.AnswersContainer
{...answerRangeProps}
/>,
)).toMatchSnapshot();
});
});
test('snapshot: numeric problems: answer range/answer select button: multiple answers disables range.', () => {
act(() => {
const answersProps = {
problemType: ProblemTypeKeys.NUMERIC,
answers: [{
id: 'A',
title: 'Answer 1',
correct: true,
selectedFeedback: 'selected feedback',
unselectedFeedback: 'unselected feedback',
isAnswerRange: false,
},
{
id: 'B',
title: 'Answer 1',
correct: true,
selectedFeedback: 'selected feedback',
unselectedFeedback: 'unselected feedback',
isAnswerRange: false,
},
],
updateField: jest.fn(),
addAnswer: jest.fn(),
addAnswerRange: jest.fn(),
};
expect(shallow(
<module.AnswersContainer
{...answersProps}
/>,
)).toMatchSnapshot();
});
});
test('useAnswerContainer', async () => {
let container = null;

View File

@@ -151,6 +151,100 @@ exports[`AnswerOption render snapshot: renders correct option with numeric input
</Advanced>
`;
exports[`AnswerOption render snapshot: renders correct option with numeric input problem and answer range 1`] = `
<Advanced
className="answer-option d-flex flex-row justify-content-between flex-nowrap pb-2 pt-2"
onToggle={[Function]}
open={false}
>
<div
className="mr-1 d-flex"
>
<Checker
answer={
Object {
"correct": true,
"id": "A",
"isAnswerRange": true,
"selectedFeedback": "selected feedback",
"title": "Answer 1",
"unselectedFeedback": "unselected feedback",
}
}
disabled={true}
hasSingleAnswer={false}
setAnswer={[Function]}
/>
</div>
<div
className="ml-1 flex-grow-1"
>
<div>
<Form.Control
as="textarea"
autoResize={true}
className="answer-option-textarea text-gray-500 small"
onChange={[Function]}
placeholder="Enter an answer range"
rows={1}
value="Answer 1"
/>
<hr
className="d-block"
/>
<div
className="pgn__form-switch-helper-text"
>
<FormattedMessage
defaultMessage="Enter min and max values separated by a comma. Use a bracket to include the number next to it in the range, or a parenthesis to exclude the number. For example, to identify the correct answers as 5, 6, or 7, but not 8, specify [5,8)."
description="Helper text describing usage of answer ranges"
id="authoring.answerwidget.answer.answerRangeHelperText"
/>
</div>
</div>
<Body>
<injectIntl(ShimmedIntlComponent)
answer={
Object {
"correct": true,
"id": "A",
"isAnswerRange": true,
"selectedFeedback": "selected feedback",
"title": "Answer 1",
"unselectedFeedback": "unselected feedback",
}
}
intl={
Object {
"formatMessage": [Function],
}
}
problemType="numericalresponse"
setSelectedFeedback={[Function]}
setUnselectedFeedback={[Function]}
/>
</Body>
</div>
<div
className="d-flex flex-row flex-nowrap"
>
<Trigger>
<IconButton
alt="Toggle feedback"
iconAs="Icon"
variant="primary"
/>
</Trigger>
<IconButton
alt="Delete answer"
iconAs="Icon"
onClick={[Function]}
variant="primary"
/>
</div>
</Advanced>
`;
exports[`AnswerOption render snapshot: renders correct option with selected unselected feedback 1`] = `
<Advanced
className="answer-option d-flex flex-row justify-content-between flex-nowrap pb-2 pt-2"

View File

@@ -1,5 +1,179 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`AnswersContainer render snapshot: numeric problems: answer range/answer select button: Range disables the additon of more adds 1`] = `
<div
className="answers-container border border-light-700 rounded py-4 pl-4 pr-3"
>
<Component
answer={
Object {
"correct": true,
"id": "A",
"isAnswerRange": true,
"selectedFeedback": "selected feedback",
"title": "Answer 1",
"unselectedFeedback": "unselected feedback",
}
}
hasSingleAnswer={false}
key="A"
/>
<Dropdown>
<Dropdown.Toggle
id="Add-Answer-Or-Answer-Range"
variant="tertiary"
>
<Icon />
<FormattedMessage
defaultMessage="Add answer"
description="Button text to add answer"
id="authoring.answerwidget.answer.addAnswerButton"
/>
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item
className="AddAnswerRange disabled"
key="add-answer"
onClick={[MockFunction]}
>
<FormattedMessage
defaultMessage="Add answer"
description="Button text to add answer"
id="authoring.answerwidget.answer.addAnswerButton"
/>
</Dropdown.Item>
<Dropdown.Item
className="AddAnswerRange disabled"
key="add-answer-range"
onClick={[MockFunction]}
>
<FormattedMessage
defaultMessage="Add answer range"
description="Button text to add a range of answers"
id="authoring.answerwidget.answer.addAnswerRangeButton"
/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</div>
`;
exports[`AnswersContainer render snapshot: numeric problems: answer range/answer select button: empty 1`] = `
<div
className="answers-container border border-light-700 rounded py-4 pl-4 pr-3"
>
<Dropdown>
<Dropdown.Toggle
id="Add-Answer-Or-Answer-Range"
variant="tertiary"
>
<Icon />
<FormattedMessage
defaultMessage="Add answer"
description="Button text to add answer"
id="authoring.answerwidget.answer.addAnswerButton"
/>
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item
className="AddAnswerRange "
key="add-answer"
onClick={[MockFunction]}
>
<FormattedMessage
defaultMessage="Add answer"
description="Button text to add answer"
id="authoring.answerwidget.answer.addAnswerButton"
/>
</Dropdown.Item>
<Dropdown.Item
className="AddAnswerRange "
key="add-answer-range"
onClick={[MockFunction]}
>
<FormattedMessage
defaultMessage="Add answer range"
description="Button text to add a range of answers"
id="authoring.answerwidget.answer.addAnswerRangeButton"
/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</div>
`;
exports[`AnswersContainer render snapshot: numeric problems: answer range/answer select button: multiple answers disables range. 1`] = `
<div
className="answers-container border border-light-700 rounded py-4 pl-4 pr-3"
>
<Component
answer={
Object {
"correct": true,
"id": "A",
"isAnswerRange": false,
"selectedFeedback": "selected feedback",
"title": "Answer 1",
"unselectedFeedback": "unselected feedback",
}
}
hasSingleAnswer={false}
key="A"
/>
<Component
answer={
Object {
"correct": true,
"id": "B",
"isAnswerRange": false,
"selectedFeedback": "selected feedback",
"title": "Answer 1",
"unselectedFeedback": "unselected feedback",
}
}
hasSingleAnswer={false}
key="B"
/>
<Dropdown>
<Dropdown.Toggle
id="Add-Answer-Or-Answer-Range"
variant="tertiary"
>
<Icon />
<FormattedMessage
defaultMessage="Add answer"
description="Button text to add answer"
id="authoring.answerwidget.answer.addAnswerButton"
/>
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item
className="AddAnswerRange "
key="add-answer"
onClick={[MockFunction]}
>
<FormattedMessage
defaultMessage="Add answer"
description="Button text to add answer"
id="authoring.answerwidget.answer.addAnswerButton"
/>
</Dropdown.Item>
<Dropdown.Item
className="AddAnswerRange disabled"
key="add-answer-range"
onClick={[MockFunction]}
>
<FormattedMessage
defaultMessage="Add answer range"
description="Button text to add a range of answers"
id="authoring.answerwidget.answer.addAnswerRangeButton"
/>
</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
</div>
`;
exports[`AnswersContainer render snapshot: renders correct default 1`] = `
<div
className="answers-container border border-light-700 rounded py-4 pl-4 pr-3"

View File

@@ -16,7 +16,7 @@ const AnswerWidget = ({
const problemStaticData = ProblemTypes[problemType];
return (
<div>
<div className="mt-4 mb-3 text-primary-500">
<div className="mt-4 text-primary-500">
<div className="h4">
<FormattedMessage {...messages.answerWidgetTitle} />
</div>

View File

@@ -56,5 +56,22 @@ const messages = defineMessages({
defaultMessage: 'is not selected',
description: 'Bold & underlined text for feedback if option is not selected',
},
addAnswerRangeButtonText: {
id: 'authoring.answerwidget.answer.addAnswerRangeButton',
defaultMessage: 'Add answer range',
description: 'Button text to add a range of answers',
},
answerRangeTextboxPlaceholder: {
id: 'authoring.answerwidget.answer.answerRangeTextboxPlaceholder',
defaultMessage: 'Enter an answer range',
description: 'Text to prompt the user to add an answer range to the textbox.',
},
answerRangeHelperText: {
id: 'authoring.answerwidget.answer.answerRangeHelperText',
defaultMessage: 'Enter min and max values separated by a comma. Use a bracket to include the number next to it in the range, or a parenthesis to exclude the number. For example, to identify the correct answers as 5, 6, or 7, but not 8, specify [5,8).',
description: 'Helper text describing usage of answer ranges',
},
});
export default messages;

View File

@@ -7,13 +7,10 @@ import messages from './messages';
import { ToleranceTypes } from './constants';
// eslint-disable-next-line no-unused-vars
export const isAnswerRangeSet = ({ answers }) =>
// TODO: for TNL 10258
// eslint-disable-next-line implicit-arrow-linebreak
false;
export const isAnswerRangeSet = ({ answers }) => !!answers[0].isAnswerRange;
export const handleToleranceTypeChange = ({ updateSettings, tolerance, answers }) => (event) => {
if (!isAnswerRangeSet(answers)) {
if (!isAnswerRangeSet({ answers })) {
let value;
if (event.target.value === ToleranceTypes.none.type) {
value = null;
@@ -26,7 +23,7 @@ export const handleToleranceTypeChange = ({ updateSettings, tolerance, answers }
};
export const handleToleranceValueChange = ({ updateSettings, tolerance, answers }) => (event) => {
if (!isAnswerRangeSet(answers)) {
if (!isAnswerRangeSet({ answers })) {
const newTolerance = { value: event.target.value, type: tolerance.type };
updateSettings({ tolerance: newTolerance });
}
@@ -52,7 +49,7 @@ export const ToleranceCard = ({
// inject
intl,
}) => {
const canEdit = isAnswerRangeSet({ answers });
const isAnswerRange = isAnswerRangeSet({ answers });
let summary = getSummary({ tolerance, intl });
useEffect(() => { summary = getSummary({ tolerance, intl }); }, [tolerance]);
return (
@@ -61,7 +58,7 @@ export const ToleranceCard = ({
summary={summary}
none={tolerance.type === ToleranceTypes.none.type}
>
{ canEdit
{ isAnswerRange
&& (
<Alert
varaint="info"
@@ -78,7 +75,7 @@ export const ToleranceCard = ({
<Form.Control
as="select"
onChange={handleToleranceTypeChange({ updateSettings, tolerance, answers })}
disabled={canEdit}
disabled={isAnswerRange}
value={tolerance.type}
>
{Object.keys(ToleranceTypes).map((toleranceType) => (
@@ -90,7 +87,7 @@ export const ToleranceCard = ({
</option>
))}
</Form.Control>
{ tolerance?.type !== ToleranceTypes.none.type && !canEdit
{ tolerance?.type !== ToleranceTypes.none.type && !isAnswerRange
&& (
<Form.Control
className="mt-4"

View File

@@ -2,6 +2,7 @@ import {
render, screen, fireEvent,
} from '@testing-library/react';
import React from 'react';
import messages from './messages';
import { ToleranceTypes } from './constants';
import { ToleranceCard } from './index';
import { formatMessage } from '../../../../../../../../testUtils';
@@ -23,10 +24,10 @@ jest.mock('@edx/paragon', () => ({
<div className="PGN-Alert">{children}</div>)),
Form: {
Control: jest.fn(({
children, onChange, as, value,
children, onChange, as, value, disabled,
}) => {
if (as === 'select') {
return (<select className="PGN-Form-Control" data-testid="select" onChange={onChange}>{children}</select>);
return (<select className="PGN-Form-Control" data-testid="select" onChange={onChange} disabled={disabled}>{children}</select>);
}
return (<input type="number" data-testid="input" onChange={onChange} value={value} />);
}),
@@ -49,7 +50,15 @@ describe('ToleranceCard', () => {
};
const props = {
answers: [], // TODO: for TNL 10258
answers: [{
id: 'A',
correct: true,
selectedFeedback: '',
title: 'An Answer',
isAnswerRange: false,
unselectedFeedback: '',
},
],
updateSettings: jest.fn(),
intl: {
formatMessage,
@@ -74,6 +83,32 @@ describe('ToleranceCard', () => {
const NumberText = screen.getByText(`± ${mockToleranceNumber.value}`);
expect(NumberText).toBeDefined();
});
it('If there is an answer range, show message and disable dropdown.', () => {
const rangeprops = {
answers: [{
id: 'A',
correct: true,
selectedFeedback: '',
title: 'An Answer',
isAnswerRange: true,
unselectedFeedback: '',
},
],
updateSettings: jest.fn(),
intl: {
formatMessage,
},
};
render(<ToleranceCard
tolerance={mockToleranceNumber}
{...rangeprops}
/>);
const NumberText = screen.getByText(messages.toleranceAnswerRangeWarning.defaultMessage);
expect(NumberText).toBeDefined();
expect(screen.getByTestId('select').getAttributeNames().includes('disabled')).toBeTruthy();
});
});
describe('Type Select', () => {
it('Renders the types for selection', async () => {

View File

@@ -40,7 +40,7 @@ exports[`EditorProblemView component renders simple view 1`] = `
className="editProblemView d-flex flex-row flex-nowrap justify-content-end"
>
<span
className="flex-grow-1"
className="flex-grow-1 mb-5"
>
<injectIntl(ShimmedIntlComponent) />
<injectIntl(ShimmedIntlComponent) />

View File

@@ -40,7 +40,7 @@ export const EditProblemView = ({
<RawEditor editorRef={editorRef} lang="xml" content={problemState.rawOLX} />
</Container>
) : (
<span className="flex-grow-1">
<span className="flex-grow-1 mb-5">
<QuestionWidget />
<ExplanationWidget />
<AnswerWidget problemType={problemType} />

View File

@@ -263,7 +263,6 @@ export class OLXParser {
let answerFeedback = '';
const answers = [];
let responseParam = {};
// TODO: UI needs to be added to support adding tolerence in numeric response.
const feedback = this.getFeedback(numericalresponse);
if (_.has(numericalresponse, 'responseparam')) {
const type = _.get(numericalresponse, 'responseparam.@_type');
@@ -272,11 +271,13 @@ export class OLXParser {
[type]: defaultValue,
};
}
const isAnswerRange = /[([]\d*,\d*[)\]]/gm.test(numericalresponse['@_answer']);
answers.push({
id: indexToLetterMap[answers.length],
title: numericalresponse['@_answer'],
correct: true,
selectedFeedback: feedback,
isAnswerRange,
...responseParam,
});
@@ -299,6 +300,7 @@ export class OLXParser {
title: additionalAnswer['@_answer'],
correct: true,
selectedFeedback: answerFeedback,
isAnswerRange: false,
});
}
return { answers };

View File

@@ -351,12 +351,14 @@ export const numericInputWithFeedbackAndHintsOLX = {
title: '100',
correct: true,
selectedFeedback: '<p>You can specify optional feedback like this, which appears after this answer is submitted.</p>',
isAnswerRange: false,
tolerance: '5',
},
{
id: 'B',
title: '200',
correct: true,
isAnswerRange: false,
selectedFeedback: '<p>You can specify optional feedback like this, which appears after this answer is submitted.</p>',
},
],

View File

@@ -85,8 +85,24 @@ const problem = createSlice({
deleteAnswer: (state, { payload }) => {
const { id, correct } = payload;
if (state.answers.length <= 1) {
if (state.answers.length > 0 && state.answers[0].isAnswerRange) {
return {
...state,
correctAnswerCount: 1,
answers: [{
id: 'A',
title: '',
selectedFeedback: '',
unselectedFeedback: '',
correct: state.problemType === ProblemTypeKeys.NUMERIC,
isAnswerRange: false,
},
],
};
}
return state;
}
let { correctAnswerCount } = state;
if (correct) {
correctAnswerCount -= 1;
@@ -115,6 +131,7 @@ const problem = createSlice({
selectedFeedback: '',
unselectedFeedback: '',
correct: state.problemType === ProblemTypeKeys.NUMERIC,
isAnswerRange: false,
};
let { correctAnswerCount } = state;
if (state.problemType === ProblemTypeKeys.NUMERIC) {
@@ -131,6 +148,24 @@ const problem = createSlice({
answers,
};
},
addAnswerRange: (state) => {
// As you may only have one answer range at a time, overwrite the answer object.
const newOption = {
id: 'A',
title: '',
selectedFeedback: '',
unselectedFeedback: '',
correct: state.problemType === ProblemTypeKeys.NUMERIC,
isAnswerRange: true,
};
const correctAnswerCount = 1;
return {
...state,
correctAnswerCount,
answers: [newOption],
};
},
updateSettings: (state, { payload }) => ({
...state,
settings: {

View File

@@ -56,6 +56,7 @@ describe('problem reducer', () => {
correct: false,
selectedFeedback: '',
title: '',
isAnswerRange: false,
unselectedFeedback: '',
};
expect(reducer(testingState, actions.addAnswer(answer))).toEqual({
@@ -91,6 +92,7 @@ describe('problem reducer', () => {
correct: false,
selectedFeedback: '',
title: '',
isAnswerRange: false,
unselectedFeedback: '',
};
it('sets answers', () => {
@@ -116,6 +118,26 @@ describe('problem reducer', () => {
});
});
});
describe('addAnswerRange', () => {
const answerRange = {
id: 'A',
correct: true,
selectedFeedback: '',
title: '',
isAnswerRange: true,
unselectedFeedback: '',
};
it('sets answerRange', () => {
expect(reducer({ ...testingState, problemType: ProblemTypeKeys.NUMERIC }, actions.addAnswerRange())).toEqual({
...testingState,
correctAnswerCount: 1,
problemType: ProblemTypeKeys.NUMERIC,
answers: [answerRange],
});
});
});
describe('updateAnswer', () => {
it('sets answers, as well as setting the correctAnswerCount ', () => {
const answer = { id: 'A', correct: true };
@@ -162,6 +184,39 @@ describe('problem reducer', () => {
}],
});
});
it('if you delete an answer range, it will be replaced with a blank answer', () => {
const answer = {
id: 'A',
correct: true,
selectedFeedback: '',
title: '',
isAnswerRange: false,
unselectedFeedback: '',
};
const answerRange = {
id: 'A',
correct: false,
selectedFeedback: '',
title: '',
isAnswerRange: true,
unselectedFeedback: '',
};
expect(reducer(
{
...testingState,
problemType: ProblemTypeKeys.NUMERIC,
correctAnswerCount: 1,
answers: [{ ...answerRange }],
},
actions.deleteAnswer(answer),
)).toEqual({
...testingState,
problemType: ProblemTypeKeys.NUMERIC,
correctAnswerCount: 1,
answers: [{ ...answer }],
});
});
});
});
});