Skip to content

Commit 2f9566c

Browse files
papphelixFaraz32123
authored andcommitted
refactor: Problem type handling to support localization
- Updated hooks and components to utilize localized problem titles and descriptions. - Introduced `getProblemTypes` and `getAdvanceProblems` functions for internationalization support. - Enhanced tests to verify localized titles and descriptions for problem types. - Added new messages for various problem types and their descriptions. - Refactored `TypeCard`, `TypeRow`, and `SelectTypeModal` components to use localized strings. - Improved test coverage for problem type selection and rendering.
1 parent 915bd55 commit 2f9566c

File tree

22 files changed

+586
-84
lines changed

22 files changed

+586
-84
lines changed

src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/AnswersContainer.test.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
import React from 'react';
2+
3+
import { ProblemTypeKeys } from '@src/editors/data/constants/problem';
24
import {
35
render, screen, fireEvent, initializeMocks,
46
} from '../../../../../../testUtils';
57
import AnswersContainer from './AnswersContainer';
6-
import { ProblemTypeKeys } from '../../../../../data/constants/problem';
7-
// Import actions after mocking to access mocked functions
88
import { actions } from '../../../../../data/redux';
99

1010
const { useAnswerContainer } = require('./hooks');

src/editors/containers/ProblemEditor/components/EditProblemView/AnswerWidget/index.jsx

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import React from 'react';
22
import PropTypes from 'prop-types';
33
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
44

5+
import { getProblemTypes } from '@src/editors/data/constants/problem';
56
import messages from './messages';
6-
import { ProblemTypes } from '../../../../../data/constants/problem';
77
import AnswersContainer from './AnswersContainer';
88

99
// This widget should be connected, grab all answers from store, update them as needed.
@@ -12,15 +12,21 @@ const AnswerWidget = ({
1212
problemType,
1313
}) => {
1414
const intl = useIntl();
15-
const problemStaticData = ProblemTypes[problemType];
15+
16+
const localizedProblemTypes = getProblemTypes(intl.formatMessage);
17+
const localizedProblemStaticData = localizedProblemTypes[problemType];
18+
1619
return (
1720
<div>
1821
<div className="mt-4 text-primary-500">
1922
<div className="h4">
2023
<FormattedMessage {...messages.answerWidgetTitle} />
2124
</div>
2225
<div className="small">
23-
{intl.formatMessage(messages.answerHelperText, { helperText: problemStaticData.description })}
26+
<FormattedMessage
27+
{...messages.answerHelperText}
28+
values={{ helperText: localizedProblemStaticData.description }}
29+
/>
2430
</div>
2531
</div>
2632
<AnswersContainer problemType={problemType} />

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

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,19 @@ import { useState, useEffect } from 'react';
33
import {
44
includes, isEmpty, isFinite, isNaN, isNil,
55
} from 'lodash';
6+
import {
7+
ProblemTypeKeys,
8+
ProblemTypes,
9+
RichTextProblems,
10+
ShowAnswerTypesKeys,
11+
getProblemTypes,
12+
} from '@src/editors/data/constants/problem';
613
// This 'module' self-import hack enables mocking during tests.
714
// See src/editors/decisions/0005-internal-editor-testability-decisions.md. The whole approach to how hooks are tested
815
// should be re-thought and cleaned up to avoid this pattern.
916
// eslint-disable-next-line import/no-self-import
1017
import * as module from './hooks';
1118
import messages from './messages';
12-
import {
13-
ProblemTypeKeys,
14-
ProblemTypes,
15-
RichTextProblems,
16-
ShowAnswerTypesKeys,
17-
} from '../../../../../data/constants/problem';
1819
import { fetchEditorContent } from '../hooks';
1920

2021
export const state = {
@@ -232,6 +233,7 @@ export const typeRowHooks = ({
232233
typeKey,
233234
updateField,
234235
updateAnswer,
236+
formatMessage,
235237
}) => {
236238
const clearPreviouslySelectedAnswers = () => {
237239
let currentAnswerTitles;
@@ -312,8 +314,17 @@ export const typeRowHooks = ({
312314
updateAnswersToCorrect();
313315
}
314316

315-
if (blockTitle === ProblemTypes[problemType].title) {
316-
setBlockTitle(ProblemTypes[typeKey].title);
317+
// Check if blockTitle matches either the localized or non-localized problem type title
318+
const localizedProblemTypes = formatMessage ? getProblemTypes(formatMessage) : null;
319+
const currentTitle = localizedProblemTypes
320+
? localizedProblemTypes[problemType].title
321+
: ProblemTypes[problemType].title;
322+
323+
if (blockTitle === currentTitle) {
324+
const newTitle = localizedProblemTypes
325+
? localizedProblemTypes[typeKey].title
326+
: ProblemTypes[typeKey].title;
327+
setBlockTitle(newTitle);
317328
}
318329
updateField({ problemType: typeKey });
319330
};

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

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import { useEffect } from 'react';
2+
import * as problemConstants from '@src/editors/data/constants/problem';
3+
import { ProblemTypeKeys, ProblemTypes } from '@src/editors/data/constants/problem';
24
import { MockUseState } from '../../../../../testUtils';
35
import messages from './messages';
46
import { keyStore } from '../../../../../utils';
57
import * as hooks from './hooks';
6-
import { ProblemTypeKeys, ProblemTypes } from '../../../../../data/constants/problem';
78
import * as editHooks from '../hooks';
89

910
jest.mock('react', () => {
@@ -381,6 +382,32 @@ describe('Problem settings hooks', () => {
381382
expect(typeRowProps.updateAnswer).toHaveBeenNthCalledWith(3, { ...typeRowProps.answers[2], title: 'testC' });
382383
expect(typeRowProps.updateField).toHaveBeenCalledWith({ problemType: ProblemTypeKeys.TEXTINPUT });
383384
});
385+
386+
test('test typeRowHooks sets localized block title when formatMessage is provided', () => {
387+
const mockSetBlockTitle = jest.fn();
388+
const mockUpdateField = jest.fn();
389+
const mockUpdateAnswer = jest.fn();
390+
const mockFormatMessage = (msg) => `localized-${msg.id || msg.defaultMessage || msg}`;
391+
const props = {
392+
answers: [],
393+
blockTitle: 'localized-problem.multiplechoiceresponse.title', // Simulate a localized title
394+
correctAnswerCount: 1,
395+
problemType: ProblemTypeKeys.SINGLESELECT,
396+
setBlockTitle: mockSetBlockTitle,
397+
typeKey: ProblemTypeKeys.MULTISELECT,
398+
updateField: mockUpdateField,
399+
updateAnswer: mockUpdateAnswer,
400+
formatMessage: mockFormatMessage,
401+
};
402+
jest.spyOn(problemConstants, 'getProblemTypes').mockImplementation((fmt) => ({
403+
[ProblemTypeKeys.SINGLESELECT]: { title: fmt({ id: 'problem.multiplechoiceresponse.title' }) },
404+
[ProblemTypeKeys.MULTISELECT]: { title: fmt({ id: 'problem.choiceresponse.title' }) },
405+
}));
406+
const hook = hooks.typeRowHooks(props);
407+
hook.onClick();
408+
expect(mockSetBlockTitle).toHaveBeenCalledWith('localized-problem.choiceresponse.title');
409+
expect(mockUpdateField).toHaveBeenCalledWith({ problemType: ProblemTypeKeys.MULTISELECT });
410+
});
384411
});
385412
test('test handleConfirmEditorSwitch hook', () => {
386413
const switchEditor = jest.fn();

src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
import React from 'react';
2+
3+
import { ProblemTypeKeys } from '@src/editors/data/constants/problem';
24
import {
35
render, screen, initializeMocks,
46
} from '@src/testUtils';
57
import * as hooks from './hooks';
68
import { SettingsWidgetInternal as SettingsWidget } from '.';
7-
import { ProblemTypeKeys } from '../../../../../data/constants/problem';
89

910
jest.mock('./settingsComponents/GeneralFeedback', () => 'GeneralFeedback');
1011
jest.mock('./settingsComponents/GroupFeedback', () => 'GroupFeedback');

src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/TypeCard.jsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import React from 'react';
22
import PropTypes from 'prop-types';
33
import { useIntl } from '@edx/frontend-platform/i18n';
4+
5+
import { ProblemTypeKeys, getProblemTypes } from '@src/editors/data/constants/problem';
46
import SettingsOption from '../SettingsOption';
5-
import { ProblemTypeKeys, ProblemTypes } from '../../../../../../data/constants/problem';
67
import messages from '../messages';
78
import TypeRow from './TypeRow';
89

@@ -16,14 +17,15 @@ const TypeCard = ({
1617
updateAnswer,
1718
}) => {
1819
const intl = useIntl();
20+
const localizedProblemTypes = getProblemTypes(intl.formatMessage);
1921
const problemTypeKeysArray = Object.values(ProblemTypeKeys).filter(key => key !== ProblemTypeKeys.ADVANCED);
2022

2123
if (problemType === ProblemTypeKeys.ADVANCED) { return null; }
2224

2325
return (
2426
<SettingsOption
2527
title={intl.formatMessage(messages.typeSettingTitle)}
26-
summary={ProblemTypes[problemType].title}
28+
summary={localizedProblemTypes[problemType].title}
2729
>
2830
{problemTypeKeysArray.map((typeKey, i) => (
2931
<TypeRow
@@ -32,7 +34,7 @@ const TypeCard = ({
3234
correctAnswerCount={correctAnswerCount}
3335
key={typeKey}
3436
typeKey={typeKey}
35-
label={ProblemTypes[typeKey].title}
37+
label={localizedProblemTypes[typeKey].title}
3638
selected={typeKey !== problemType}
3739
problemType={problemType}
3840
lastRow={(i + 1) === problemTypeKeysArray.length}

src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/TypeCard.test.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import React from 'react';
2+
3+
import { ProblemTypeKeys } from '@src/editors/data/constants/problem';
24
import { render, screen, initializeMocks } from '@src/testUtils';
35
import TypeCard from './TypeCard';
4-
import { ProblemTypeKeys } from '../../../../../../data/constants/problem';
56

67
describe('TypeCard', () => {
78
const props = {

src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/TypeRow.jsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import React from 'react';
22
import { Icon } from '@openedx/paragon';
33
import PropTypes from 'prop-types';
4+
import { useIntl } from '@edx/frontend-platform/i18n';
45
import { Check } from '@openedx/paragon/icons';
56
import { typeRowHooks } from '../hooks';
67

@@ -19,6 +20,8 @@ const TypeRow = ({
1920
updateField,
2021
updateAnswer,
2122
}) => {
23+
const intl = useIntl();
24+
2225
const { onClick } = typeRowHooks({
2326
answers,
2427
blockTitle,
@@ -28,6 +31,7 @@ const TypeRow = ({
2831
typeKey,
2932
updateField,
3033
updateAnswer,
34+
formatMessage: intl.formatMessage,
3135
});
3236

3337
return (

src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ const SelectTypeWrapper: React.FC<Props> = ({
7171
updateField,
7272
setBlockTitle,
7373
defaultSettings,
74+
formatMessage: intl.formatMessage,
7475
})}
7576
disabled={!selected}
7677
>

src/editors/containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/messages.ts

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,134 @@ const messages = defineMessages({
2727
defaultMessage: 'Select',
2828
description: 'Screen reader label for select button.',
2929
},
30+
31+
// Problem Type Titles
32+
singleSelectTitle: {
33+
id: 'authoring.problemeditor.problemtype.singleselect.title',
34+
defaultMessage: 'Single select',
35+
description: 'Title for single select problem type',
36+
},
37+
multiSelectTitle: {
38+
id: 'authoring.problemeditor.problemtype.multiselect.title',
39+
defaultMessage: 'Multi-select',
40+
description: 'Title for multi-select problem type',
41+
},
42+
dropdownTitle: {
43+
id: 'authoring.problemeditor.problemtype.dropdown.title',
44+
defaultMessage: 'Dropdown',
45+
description: 'Title for dropdown problem type',
46+
},
47+
numericalInputTitle: {
48+
id: 'authoring.problemeditor.problemtype.numeric.title',
49+
defaultMessage: 'Numerical input',
50+
description: 'Title for numerical input problem type',
51+
},
52+
textInputTitle: {
53+
id: 'authoring.problemeditor.problemtype.textinput.title',
54+
defaultMessage: 'Text input',
55+
description: 'Title for text input problem type',
56+
},
57+
advancedProblemTitle: {
58+
id: 'authoring.problemeditor.problemtype.advanced.title',
59+
defaultMessage: 'Advanced Problem',
60+
description: 'Title for advanced problem type',
61+
},
62+
63+
// Advanced Problem Type Titles
64+
blankProblemTitle: {
65+
id: 'authoring.problemeditor.advancedproblemtype.blank.title',
66+
defaultMessage: 'Blank problem',
67+
description: 'Title for blank advanced problem type',
68+
},
69+
circuitSchematicTitle: {
70+
id: 'authoring.problemeditor.advancedproblemtype.circuitschematic.title',
71+
defaultMessage: 'Circuit schematic builder',
72+
description: 'Title for circuit schematic builder advanced problem type',
73+
},
74+
customJavaScriptTitle: {
75+
id: 'authoring.problemeditor.advancedproblemtype.jsinput.title',
76+
defaultMessage: 'Custom JavaScript display and grading',
77+
description: 'Title for custom JavaScript display and grading advanced problem type',
78+
},
79+
customPythonTitle: {
80+
id: 'authoring.problemeditor.advancedproblemtype.customgrader.title',
81+
defaultMessage: 'Custom Python-evaluated input',
82+
description: 'Title for custom Python-evaluated input advanced problem type',
83+
},
84+
imageMappedTitle: {
85+
id: 'authoring.problemeditor.advancedproblemtype.image.title',
86+
defaultMessage: 'Image mapped input',
87+
description: 'Title for image mapped input advanced problem type',
88+
},
89+
mathExpressionTitle: {
90+
id: 'authoring.problemeditor.advancedproblemtype.formula.title',
91+
defaultMessage: 'Math expression input',
92+
description: 'Title for math expression input advanced problem type',
93+
},
94+
problemWithHintTitle: {
95+
id: 'authoring.problemeditor.advancedproblemtype.problemwithhint.title',
96+
defaultMessage: 'Problem with adaptive hint',
97+
description: 'Title for problem with adaptive hint advanced problem type',
98+
},
99+
100+
// Problem Type Descriptions
101+
singleSelectDescription: {
102+
id: 'authoring.problemeditor.problemtype.singleselect.description',
103+
defaultMessage: 'Learners must select the correct answer from a list of possible options.',
104+
description: 'Preview description for single select problem type',
105+
},
106+
multiSelectDescription: {
107+
id: 'authoring.problemeditor.problemtype.multiselect.description',
108+
defaultMessage: 'Learners must select all correct answers from a list of possible options.',
109+
description: 'Preview description for multi-select problem type',
110+
},
111+
dropdownDescription: {
112+
id: 'authoring.problemeditor.problemtype.dropdown.description',
113+
defaultMessage: 'Learners must select the correct answer from a list of possible options',
114+
description: 'Preview description for dropdown problem type',
115+
},
116+
numericalInputDescription: {
117+
id: 'authoring.problemeditor.problemtype.numeric.description',
118+
defaultMessage: 'Specify one or more correct numeric answers, submitted in a response field.',
119+
description: 'Preview description for numerical input problem type',
120+
},
121+
textInputDescription: {
122+
id: 'authoring.problemeditor.problemtype.textinput.description',
123+
defaultMessage: 'Specify one or more correct text answers, including numbers and special characters, submitted in a response field.',
124+
description: 'Preview description for text input problem type',
125+
},
126+
advancedProblemDescription: {
127+
id: 'authoring.problemeditor.problemtype.advanced.description',
128+
defaultMessage: 'An Advanced Problem Type',
129+
description: 'Description for advanced problem type',
130+
},
131+
132+
// Problem Type Instructions
133+
singleSelectInstruction: {
134+
id: 'authoring.problemeditor.problemtype.singleselect.instruction',
135+
defaultMessage: 'Enter your single select answers below and select which choices are correct. Learners must choose one correct answer.',
136+
description: 'Instruction for single select problem type',
137+
},
138+
multiSelectInstruction: {
139+
id: 'authoring.problemeditor.problemtype.multiselect.instruction',
140+
defaultMessage: 'Enter your multi select answers below and select which choices are correct. Learners must choose all correct answers.',
141+
description: 'Instruction for multi-select problem type',
142+
},
143+
dropdownInstruction: {
144+
id: 'authoring.problemeditor.problemtype.dropdown.instruction',
145+
defaultMessage: 'Enter your dropdown answers below and select which choice is correct. Learners must select one correct answer.',
146+
description: 'Instruction for dropdown problem type',
147+
},
148+
numericalInputInstruction: {
149+
id: 'authoring.problemeditor.problemtype.numeric.instruction',
150+
defaultMessage: 'Enter correct numerical input answers below. Learners must enter one correct answer.',
151+
description: 'Instruction for numerical input problem type',
152+
},
153+
textInputInstruction: {
154+
id: 'authoring.problemeditor.problemtype.textinput.instruction',
155+
defaultMessage: 'Enter your text input answers below and select which choices are correct. Learners must enter one correct answer.',
156+
description: 'Instruction for text input problem type',
157+
},
30158
});
31159

32160
export default messages;

0 commit comments

Comments
 (0)