diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.jsx
index a0ee440b62..07d7130045 100644
--- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.jsx
+++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswerOption.jsx
@@ -18,6 +18,7 @@ import { FeedbackBox } from './components/Feedback';
import * as hooks from './hooks';
import { ProblemTypeKeys } from '../../../../../data/constants/problem';
import ExpandableTextArea from '../../../../../sharedComponents/ExpandableTextArea';
+import { answerRangeFormatRegex } from '../../../data/OLXParser';
const AnswerOption = ({
answer,
@@ -47,6 +48,11 @@ const AnswerOption = ({
staticRootUrl = `${getConfig().STUDIO_BASE_URL }/library_assets/blocks/${ blockId }/`;
}
+ const validateAnswerTitle = (value) => {
+ const cleanedValue = value.replace(/^\s+|\s+$/g, '');
+ return !cleanedValue.length || answerRangeFormatRegex.test(cleanedValue);
+ };
+
const getInputArea = () => {
if ([ProblemTypeKeys.SINGLESELECT, ProblemTypeKeys.MULTISELECT].includes(problemType)) {
return (
@@ -78,8 +84,9 @@ const AnswerOption = ({
);
}
// Return Answer Range View
+ const isValidValue = validateAnswerTitle(answer.title);
return (
-
+
+ {!isValidValue && (
+
+
+
+ )}
-
-
+
);
};
diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/messages.js b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/messages.js
index 5f58109137..d69bc876d7 100644
--- a/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/messages.js
+++ b/src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/messages.js
@@ -77,6 +77,11 @@ const messages = defineMessages({
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',
},
+ answerRangeErrorText: {
+ id: 'authoring.answerwidget.answer.answerRangeErrorText',
+ defaultMessage: 'Error: Invalid range format. Use brackets or parentheses with values separated by a comma.',
+ description: 'Error text describing wrong format of answer ranges',
+ },
});
export default messages;
diff --git a/src/editors/containers/ProblemEditor/data/OLXParser.js b/src/editors/containers/ProblemEditor/data/OLXParser.js
index 4a390e68cc..b59f57efde 100644
--- a/src/editors/containers/ProblemEditor/data/OLXParser.js
+++ b/src/editors/containers/ProblemEditor/data/OLXParser.js
@@ -57,6 +57,8 @@ export const responseKeys = [
'choicetextresponse',
];
+export const answerRangeFormatRegex = /^[([]\s*\d+(\.\d+)?\s*,\s*\d+(\.\d+)?\s*[)\]]$/m;
+
export const stripNonTextTags = ({ input, tag }) => {
const stripedTags = {};
Object.entries(input).forEach(([key, value]) => {
@@ -432,7 +434,7 @@ export class OLXParser {
[type]: defaultValue,
};
}
- const isAnswerRange = /[([]\s*\d*,\s*\d*\s*[)\]]/gm.test(numericalresponse['@_answer']);
+ const isAnswerRange = answerRangeFormatRegex.test(numericalresponse['@_answer']);
answers.push({
id: indexToLetterMap[answers.length],
title: numericalresponse['@_answer'],
diff --git a/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js b/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js
index 5f2d8ca9f7..279294750a 100644
--- a/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js
+++ b/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.js
@@ -413,16 +413,18 @@ class ReactStateOLXParser {
const lowerBoundFloat = Number(numerator) / Number(denominator);
lowerBoundInt = lowerBoundFloat;
} else {
- // these regex replaces remove everything that is not a decimal or positive/negative numer
+ // these regex replaces remove everything that is not a decimal or positive/negative number
lowerBoundInt = Number(rawLowerBound.replace(/[^0-9-.]/gm, ''));
}
- if (rawUpperBound.includes('/')) {
+ if (!rawUpperBound) {
+ upperBoundInt = lowerBoundInt;
+ } else if (rawUpperBound.includes('/')) {
upperBoundFraction = rawUpperBound.replace(/[^0-9-/]/gm, '');
const [numerator, denominator] = upperBoundFraction.split('/');
const upperBoundFloat = Number(numerator) / Number(denominator);
upperBoundInt = upperBoundFloat;
} else {
- // these regex replaces remove everything that is not a decimal or positive/negative numer
+ // these regex replaces remove everything that is not a decimal or positive/negative number
upperBoundInt = Number(rawUpperBound.replace(/[^0-9-.]/gm, ''));
}
if (lowerBoundInt > upperBoundInt) {
diff --git a/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.test.js b/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.test.js
index 416fafdcd8..7b852ca64b 100644
--- a/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.test.js
+++ b/src/editors/containers/ProblemEditor/data/ReactStateOLXParser.test.js
@@ -18,6 +18,10 @@ import {
numericInputWithAnswerRange,
textInputWithFeedbackAndHintsWithMultipleAnswers,
numberParseTest,
+ numericInputWithFractionBounds,
+ numericInputWithEmptyUpperBound,
+ numericInputWithSwappedBounds,
+ numericInputWithMissingUpperBound,
} from './mockData/editorTestData';
import ReactStateOLXParser from './ReactStateOLXParser';
@@ -147,4 +151,41 @@ describe('Check React State OLXParser problem', () => {
);
});
});
+ describe('ReactStateOLXParser numerical response range parsing', () => {
+ test('handles empty upper bound as same as lower', () => {
+ const parser = new ReactStateOLXParser({
+ problem: numericInputWithEmptyUpperBound,
+ editorObject: numericInputWithEmptyUpperBound,
+ });
+ const result = parser.buildNumericalResponse();
+ expect(result[':@']['@_answer']).toBe('[0,1.5]');
+ });
+
+ test('handles swapped bounds and corrects order', () => {
+ const parser = new ReactStateOLXParser({
+ problem: numericInputWithSwappedBounds,
+ editorObject: numericInputWithSwappedBounds,
+ });
+ const result = parser.buildNumericalResponse();
+ expect(result[':@']['@_answer']).toBe('[2,5]');
+ });
+
+ test('fixes swapped fraction bounds and preserves brackets', () => {
+ const parser = new ReactStateOLXParser({
+ problem: numericInputWithFractionBounds,
+ editorObject: numericInputWithFractionBounds,
+ });
+ const result = parser.buildNumericalResponse();
+ expect(result[':@']['@_answer']).toBe('(1/2,3/2)');
+ });
+
+ test('sets upper bound = lower bound if upper bound missing', () => {
+ const parser = new ReactStateOLXParser({
+ problem: numericInputWithMissingUpperBound,
+ editorObject: numericInputWithMissingUpperBound,
+ });
+ const result = parser.buildNumericalResponse();
+ expect(result[':@']['@_answer']).toBe('[,2.5]');
+ });
+ });
});
diff --git a/src/editors/containers/ProblemEditor/data/mockData/editorTestData.js b/src/editors/containers/ProblemEditor/data/mockData/editorTestData.js
index a96230c46e..a33d947e41 100644
--- a/src/editors/containers/ProblemEditor/data/mockData/editorTestData.js
+++ b/src/editors/containers/ProblemEditor/data/mockData/editorTestData.js
@@ -130,3 +130,51 @@ export const numberParseTest = {
hints: [],
question: 'What is the content of the register x2 after executing the following three lines of instructions?
',
};
+
+export const numericInputWithEmptyUpperBound = {
+ answers: [
+ {
+ id: 'a1',
+ title: '[1.5,]',
+ correct: true,
+ },
+ ],
+ problemType: 'numericalresponse',
+ settings: {},
+};
+
+export const numericInputWithSwappedBounds = {
+ answers: [
+ {
+ id: 'a1',
+ title: '[5,2]',
+ correct: true,
+ },
+ ],
+ problemType: 'numericalresponse',
+ settings: {},
+};
+
+export const numericInputWithFractionBounds = {
+ answers: [
+ {
+ id: 'a1',
+ title: '(3/2,1/2)',
+ correct: true,
+ },
+ ],
+ problemType: 'numericalresponse',
+ settings: {},
+};
+
+export const numericInputWithMissingUpperBound = {
+ answers: [
+ {
+ id: 'a1',
+ title: '[,2.5]',
+ correct: true,
+ },
+ ],
+ problemType: 'numericalresponse',
+ settings: {},
+};