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.
This commit is contained in:
committed by
Muhammad Faraz Maqsood
parent
915bd559e0
commit
2f9566c4f5
@@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ProblemTypeKeys } from '@src/editors/data/constants/problem';
|
||||
import {
|
||||
render, screen, fireEvent, initializeMocks,
|
||||
} from '../../../../../../testUtils';
|
||||
import AnswersContainer from './AnswersContainer';
|
||||
import { ProblemTypeKeys } from '../../../../../data/constants/problem';
|
||||
// Import actions after mocking to access mocked functions
|
||||
import { actions } from '../../../../../data/redux';
|
||||
|
||||
const { useAnswerContainer } = require('./hooks');
|
||||
|
||||
@@ -2,8 +2,8 @@ import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { getProblemTypes } from '@src/editors/data/constants/problem';
|
||||
import messages from './messages';
|
||||
import { ProblemTypes } from '../../../../../data/constants/problem';
|
||||
import AnswersContainer from './AnswersContainer';
|
||||
|
||||
// This widget should be connected, grab all answers from store, update them as needed.
|
||||
@@ -12,7 +12,10 @@ const AnswerWidget = ({
|
||||
problemType,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const problemStaticData = ProblemTypes[problemType];
|
||||
|
||||
const localizedProblemTypes = getProblemTypes(intl.formatMessage);
|
||||
const localizedProblemStaticData = localizedProblemTypes[problemType];
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="mt-4 text-primary-500">
|
||||
@@ -20,7 +23,10 @@ const AnswerWidget = ({
|
||||
<FormattedMessage {...messages.answerWidgetTitle} />
|
||||
</div>
|
||||
<div className="small">
|
||||
{intl.formatMessage(messages.answerHelperText, { helperText: problemStaticData.description })}
|
||||
<FormattedMessage
|
||||
{...messages.answerHelperText}
|
||||
values={{ helperText: localizedProblemStaticData.description }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<AnswersContainer problemType={problemType} />
|
||||
|
||||
@@ -3,18 +3,19 @@ import { useState, useEffect } from 'react';
|
||||
import {
|
||||
includes, isEmpty, isFinite, isNaN, isNil,
|
||||
} from 'lodash';
|
||||
import {
|
||||
ProblemTypeKeys,
|
||||
ProblemTypes,
|
||||
RichTextProblems,
|
||||
ShowAnswerTypesKeys,
|
||||
getProblemTypes,
|
||||
} from '@src/editors/data/constants/problem';
|
||||
// This 'module' self-import hack enables mocking during tests.
|
||||
// See src/editors/decisions/0005-internal-editor-testability-decisions.md. The whole approach to how hooks are tested
|
||||
// should be re-thought and cleaned up to avoid this pattern.
|
||||
// eslint-disable-next-line import/no-self-import
|
||||
import * as module from './hooks';
|
||||
import messages from './messages';
|
||||
import {
|
||||
ProblemTypeKeys,
|
||||
ProblemTypes,
|
||||
RichTextProblems,
|
||||
ShowAnswerTypesKeys,
|
||||
} from '../../../../../data/constants/problem';
|
||||
import { fetchEditorContent } from '../hooks';
|
||||
|
||||
export const state = {
|
||||
@@ -232,6 +233,7 @@ export const typeRowHooks = ({
|
||||
typeKey,
|
||||
updateField,
|
||||
updateAnswer,
|
||||
formatMessage,
|
||||
}) => {
|
||||
const clearPreviouslySelectedAnswers = () => {
|
||||
let currentAnswerTitles;
|
||||
@@ -312,8 +314,17 @@ export const typeRowHooks = ({
|
||||
updateAnswersToCorrect();
|
||||
}
|
||||
|
||||
if (blockTitle === ProblemTypes[problemType].title) {
|
||||
setBlockTitle(ProblemTypes[typeKey].title);
|
||||
// Check if blockTitle matches either the localized or non-localized problem type title
|
||||
const localizedProblemTypes = formatMessage ? getProblemTypes(formatMessage) : null;
|
||||
const currentTitle = localizedProblemTypes
|
||||
? localizedProblemTypes[problemType].title
|
||||
: ProblemTypes[problemType].title;
|
||||
|
||||
if (blockTitle === currentTitle) {
|
||||
const newTitle = localizedProblemTypes
|
||||
? localizedProblemTypes[typeKey].title
|
||||
: ProblemTypes[typeKey].title;
|
||||
setBlockTitle(newTitle);
|
||||
}
|
||||
updateField({ problemType: typeKey });
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { useEffect } from 'react';
|
||||
import * as problemConstants from '@src/editors/data/constants/problem';
|
||||
import { ProblemTypeKeys, ProblemTypes } from '@src/editors/data/constants/problem';
|
||||
import { MockUseState } from '../../../../../testUtils';
|
||||
import messages from './messages';
|
||||
import { keyStore } from '../../../../../utils';
|
||||
import * as hooks from './hooks';
|
||||
import { ProblemTypeKeys, ProblemTypes } from '../../../../../data/constants/problem';
|
||||
import * as editHooks from '../hooks';
|
||||
|
||||
jest.mock('react', () => {
|
||||
@@ -381,6 +382,32 @@ describe('Problem settings hooks', () => {
|
||||
expect(typeRowProps.updateAnswer).toHaveBeenNthCalledWith(3, { ...typeRowProps.answers[2], title: 'testC' });
|
||||
expect(typeRowProps.updateField).toHaveBeenCalledWith({ problemType: ProblemTypeKeys.TEXTINPUT });
|
||||
});
|
||||
|
||||
test('test typeRowHooks sets localized block title when formatMessage is provided', () => {
|
||||
const mockSetBlockTitle = jest.fn();
|
||||
const mockUpdateField = jest.fn();
|
||||
const mockUpdateAnswer = jest.fn();
|
||||
const mockFormatMessage = (msg) => `localized-${msg.id || msg.defaultMessage || msg}`;
|
||||
const props = {
|
||||
answers: [],
|
||||
blockTitle: 'localized-problem.multiplechoiceresponse.title', // Simulate a localized title
|
||||
correctAnswerCount: 1,
|
||||
problemType: ProblemTypeKeys.SINGLESELECT,
|
||||
setBlockTitle: mockSetBlockTitle,
|
||||
typeKey: ProblemTypeKeys.MULTISELECT,
|
||||
updateField: mockUpdateField,
|
||||
updateAnswer: mockUpdateAnswer,
|
||||
formatMessage: mockFormatMessage,
|
||||
};
|
||||
jest.spyOn(problemConstants, 'getProblemTypes').mockImplementation((fmt) => ({
|
||||
[ProblemTypeKeys.SINGLESELECT]: { title: fmt({ id: 'problem.multiplechoiceresponse.title' }) },
|
||||
[ProblemTypeKeys.MULTISELECT]: { title: fmt({ id: 'problem.choiceresponse.title' }) },
|
||||
}));
|
||||
const hook = hooks.typeRowHooks(props);
|
||||
hook.onClick();
|
||||
expect(mockSetBlockTitle).toHaveBeenCalledWith('localized-problem.choiceresponse.title');
|
||||
expect(mockUpdateField).toHaveBeenCalledWith({ problemType: ProblemTypeKeys.MULTISELECT });
|
||||
});
|
||||
});
|
||||
test('test handleConfirmEditorSwitch hook', () => {
|
||||
const switchEditor = jest.fn();
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ProblemTypeKeys } from '@src/editors/data/constants/problem';
|
||||
import {
|
||||
render, screen, initializeMocks,
|
||||
} from '@src/testUtils';
|
||||
import * as hooks from './hooks';
|
||||
import { SettingsWidgetInternal as SettingsWidget } from '.';
|
||||
import { ProblemTypeKeys } from '../../../../../data/constants/problem';
|
||||
|
||||
jest.mock('./settingsComponents/GeneralFeedback', () => 'GeneralFeedback');
|
||||
jest.mock('./settingsComponents/GroupFeedback', () => 'GroupFeedback');
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
|
||||
import { ProblemTypeKeys, getProblemTypes } from '@src/editors/data/constants/problem';
|
||||
import SettingsOption from '../SettingsOption';
|
||||
import { ProblemTypeKeys, ProblemTypes } from '../../../../../../data/constants/problem';
|
||||
import messages from '../messages';
|
||||
import TypeRow from './TypeRow';
|
||||
|
||||
@@ -16,6 +17,7 @@ const TypeCard = ({
|
||||
updateAnswer,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
const localizedProblemTypes = getProblemTypes(intl.formatMessage);
|
||||
const problemTypeKeysArray = Object.values(ProblemTypeKeys).filter(key => key !== ProblemTypeKeys.ADVANCED);
|
||||
|
||||
if (problemType === ProblemTypeKeys.ADVANCED) { return null; }
|
||||
@@ -23,7 +25,7 @@ const TypeCard = ({
|
||||
return (
|
||||
<SettingsOption
|
||||
title={intl.formatMessage(messages.typeSettingTitle)}
|
||||
summary={ProblemTypes[problemType].title}
|
||||
summary={localizedProblemTypes[problemType].title}
|
||||
>
|
||||
{problemTypeKeysArray.map((typeKey, i) => (
|
||||
<TypeRow
|
||||
@@ -32,7 +34,7 @@ const TypeCard = ({
|
||||
correctAnswerCount={correctAnswerCount}
|
||||
key={typeKey}
|
||||
typeKey={typeKey}
|
||||
label={ProblemTypes[typeKey].title}
|
||||
label={localizedProblemTypes[typeKey].title}
|
||||
selected={typeKey !== problemType}
|
||||
problemType={problemType}
|
||||
lastRow={(i + 1) === problemTypeKeysArray.length}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ProblemTypeKeys } from '@src/editors/data/constants/problem';
|
||||
import { render, screen, initializeMocks } from '@src/testUtils';
|
||||
import TypeCard from './TypeCard';
|
||||
import { ProblemTypeKeys } from '../../../../../../data/constants/problem';
|
||||
|
||||
describe('TypeCard', () => {
|
||||
const props = {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
import { Icon } from '@openedx/paragon';
|
||||
import PropTypes from 'prop-types';
|
||||
import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
import { Check } from '@openedx/paragon/icons';
|
||||
import { typeRowHooks } from '../hooks';
|
||||
|
||||
@@ -19,6 +20,8 @@ const TypeRow = ({
|
||||
updateField,
|
||||
updateAnswer,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const { onClick } = typeRowHooks({
|
||||
answers,
|
||||
blockTitle,
|
||||
@@ -28,6 +31,7 @@ const TypeRow = ({
|
||||
typeKey,
|
||||
updateField,
|
||||
updateAnswer,
|
||||
formatMessage: intl.formatMessage,
|
||||
});
|
||||
|
||||
return (
|
||||
|
||||
@@ -71,6 +71,7 @@ const SelectTypeWrapper: React.FC<Props> = ({
|
||||
updateField,
|
||||
setBlockTitle,
|
||||
defaultSettings,
|
||||
formatMessage: intl.formatMessage,
|
||||
})}
|
||||
disabled={!selected}
|
||||
>
|
||||
|
||||
@@ -27,6 +27,134 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Select',
|
||||
description: 'Screen reader label for select button.',
|
||||
},
|
||||
|
||||
// Problem Type Titles
|
||||
singleSelectTitle: {
|
||||
id: 'authoring.problemeditor.problemtype.singleselect.title',
|
||||
defaultMessage: 'Single select',
|
||||
description: 'Title for single select problem type',
|
||||
},
|
||||
multiSelectTitle: {
|
||||
id: 'authoring.problemeditor.problemtype.multiselect.title',
|
||||
defaultMessage: 'Multi-select',
|
||||
description: 'Title for multi-select problem type',
|
||||
},
|
||||
dropdownTitle: {
|
||||
id: 'authoring.problemeditor.problemtype.dropdown.title',
|
||||
defaultMessage: 'Dropdown',
|
||||
description: 'Title for dropdown problem type',
|
||||
},
|
||||
numericalInputTitle: {
|
||||
id: 'authoring.problemeditor.problemtype.numeric.title',
|
||||
defaultMessage: 'Numerical input',
|
||||
description: 'Title for numerical input problem type',
|
||||
},
|
||||
textInputTitle: {
|
||||
id: 'authoring.problemeditor.problemtype.textinput.title',
|
||||
defaultMessage: 'Text input',
|
||||
description: 'Title for text input problem type',
|
||||
},
|
||||
advancedProblemTitle: {
|
||||
id: 'authoring.problemeditor.problemtype.advanced.title',
|
||||
defaultMessage: 'Advanced Problem',
|
||||
description: 'Title for advanced problem type',
|
||||
},
|
||||
|
||||
// Advanced Problem Type Titles
|
||||
blankProblemTitle: {
|
||||
id: 'authoring.problemeditor.advancedproblemtype.blank.title',
|
||||
defaultMessage: 'Blank problem',
|
||||
description: 'Title for blank advanced problem type',
|
||||
},
|
||||
circuitSchematicTitle: {
|
||||
id: 'authoring.problemeditor.advancedproblemtype.circuitschematic.title',
|
||||
defaultMessage: 'Circuit schematic builder',
|
||||
description: 'Title for circuit schematic builder advanced problem type',
|
||||
},
|
||||
customJavaScriptTitle: {
|
||||
id: 'authoring.problemeditor.advancedproblemtype.jsinput.title',
|
||||
defaultMessage: 'Custom JavaScript display and grading',
|
||||
description: 'Title for custom JavaScript display and grading advanced problem type',
|
||||
},
|
||||
customPythonTitle: {
|
||||
id: 'authoring.problemeditor.advancedproblemtype.customgrader.title',
|
||||
defaultMessage: 'Custom Python-evaluated input',
|
||||
description: 'Title for custom Python-evaluated input advanced problem type',
|
||||
},
|
||||
imageMappedTitle: {
|
||||
id: 'authoring.problemeditor.advancedproblemtype.image.title',
|
||||
defaultMessage: 'Image mapped input',
|
||||
description: 'Title for image mapped input advanced problem type',
|
||||
},
|
||||
mathExpressionTitle: {
|
||||
id: 'authoring.problemeditor.advancedproblemtype.formula.title',
|
||||
defaultMessage: 'Math expression input',
|
||||
description: 'Title for math expression input advanced problem type',
|
||||
},
|
||||
problemWithHintTitle: {
|
||||
id: 'authoring.problemeditor.advancedproblemtype.problemwithhint.title',
|
||||
defaultMessage: 'Problem with adaptive hint',
|
||||
description: 'Title for problem with adaptive hint advanced problem type',
|
||||
},
|
||||
|
||||
// Problem Type Descriptions
|
||||
singleSelectDescription: {
|
||||
id: 'authoring.problemeditor.problemtype.singleselect.description',
|
||||
defaultMessage: 'Learners must select the correct answer from a list of possible options.',
|
||||
description: 'Preview description for single select problem type',
|
||||
},
|
||||
multiSelectDescription: {
|
||||
id: 'authoring.problemeditor.problemtype.multiselect.description',
|
||||
defaultMessage: 'Learners must select all correct answers from a list of possible options.',
|
||||
description: 'Preview description for multi-select problem type',
|
||||
},
|
||||
dropdownDescription: {
|
||||
id: 'authoring.problemeditor.problemtype.dropdown.description',
|
||||
defaultMessage: 'Learners must select the correct answer from a list of possible options',
|
||||
description: 'Preview description for dropdown problem type',
|
||||
},
|
||||
numericalInputDescription: {
|
||||
id: 'authoring.problemeditor.problemtype.numeric.description',
|
||||
defaultMessage: 'Specify one or more correct numeric answers, submitted in a response field.',
|
||||
description: 'Preview description for numerical input problem type',
|
||||
},
|
||||
textInputDescription: {
|
||||
id: 'authoring.problemeditor.problemtype.textinput.description',
|
||||
defaultMessage: 'Specify one or more correct text answers, including numbers and special characters, submitted in a response field.',
|
||||
description: 'Preview description for text input problem type',
|
||||
},
|
||||
advancedProblemDescription: {
|
||||
id: 'authoring.problemeditor.problemtype.advanced.description',
|
||||
defaultMessage: 'An Advanced Problem Type',
|
||||
description: 'Description for advanced problem type',
|
||||
},
|
||||
|
||||
// Problem Type Instructions
|
||||
singleSelectInstruction: {
|
||||
id: 'authoring.problemeditor.problemtype.singleselect.instruction',
|
||||
defaultMessage: 'Enter your single select answers below and select which choices are correct. Learners must choose one correct answer.',
|
||||
description: 'Instruction for single select problem type',
|
||||
},
|
||||
multiSelectInstruction: {
|
||||
id: 'authoring.problemeditor.problemtype.multiselect.instruction',
|
||||
defaultMessage: 'Enter your multi select answers below and select which choices are correct. Learners must choose all correct answers.',
|
||||
description: 'Instruction for multi-select problem type',
|
||||
},
|
||||
dropdownInstruction: {
|
||||
id: 'authoring.problemeditor.problemtype.dropdown.instruction',
|
||||
defaultMessage: 'Enter your dropdown answers below and select which choice is correct. Learners must select one correct answer.',
|
||||
description: 'Instruction for dropdown problem type',
|
||||
},
|
||||
numericalInputInstruction: {
|
||||
id: 'authoring.problemeditor.problemtype.numeric.instruction',
|
||||
defaultMessage: 'Enter correct numerical input answers below. Learners must enter one correct answer.',
|
||||
description: 'Instruction for numerical input problem type',
|
||||
},
|
||||
textInputInstruction: {
|
||||
id: 'authoring.problemeditor.problemtype.textinput.instruction',
|
||||
defaultMessage: 'Enter your text input answers below and select which choices are correct. Learners must enter one correct answer.',
|
||||
description: 'Instruction for text input problem type',
|
||||
},
|
||||
});
|
||||
|
||||
export default messages;
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ProblemTypeKeys, AdvanceProblems } from '@src/editors/data/constants/problem';
|
||||
import {
|
||||
render, screen, fireEvent, initializeMocks,
|
||||
} from '../../../../../../testUtils';
|
||||
import AdvanceTypeSelect from './AdvanceTypeSelect';
|
||||
import { ProblemTypeKeys, AdvanceProblems } from '../../../../../data/constants/problem';
|
||||
|
||||
describe('AdvanceTypeSelect', () => {
|
||||
const setSelected = jest.fn();
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
AdvanceProblems,
|
||||
ProblemType,
|
||||
ProblemTypeKeys,
|
||||
} from '../../../../../data/constants/problem';
|
||||
} from '@src/editors/data/constants/problem';
|
||||
import messages from './messages';
|
||||
|
||||
interface Props {
|
||||
@@ -30,6 +30,7 @@ const AdvanceTypeSelect: React.FC<Props> = ({
|
||||
setSelected,
|
||||
}) => {
|
||||
const intl = useIntl();
|
||||
|
||||
const handleChange = e => { setSelected(e.target.value); };
|
||||
return (
|
||||
<Col xs={12} md={8} className="justify-content-center">
|
||||
@@ -53,12 +54,12 @@ const AdvanceTypeSelect: React.FC<Props> = ({
|
||||
value={selected}
|
||||
className="px-4"
|
||||
>
|
||||
{Object.entries(AdvanceProblems).map(([type, data]) => {
|
||||
if (data.status !== '') {
|
||||
{Object.entries(AdvanceProblems).map(([type, problemData]) => {
|
||||
if (problemData.status !== '') {
|
||||
return (
|
||||
<ActionRow className="border-primary-100 border-bottom m-0 py-3 w-100" key={type}>
|
||||
<Form.Radio id={type} value={type}>
|
||||
{intl.formatMessage(messages.advanceProblemTypeLabel, { problemType: data.title })}
|
||||
<FormattedMessage {...messages[`problemType.${type}.title`]} />
|
||||
</Form.Radio>
|
||||
<ActionRow.Spacer />
|
||||
<OverlayTrigger
|
||||
@@ -66,13 +67,13 @@ const AdvanceTypeSelect: React.FC<Props> = ({
|
||||
overlay={(
|
||||
<Tooltip id={`tooltip-adv-${type}`}>
|
||||
<div className="text-left">
|
||||
{intl.formatMessage(messages.supportStatusTooltipMessage, { supportStatus: data.status.replace(' ', '_') })}
|
||||
{intl.formatMessage(messages.supportStatusTooltipMessage, { supportStatus: problemData.status.replace(' ', '_') })}
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
>
|
||||
<div className="text-gray-500">
|
||||
{intl.formatMessage(messages.problemSupportStatus, { supportStatus: data.status })}
|
||||
{intl.formatMessage(messages.problemSupportStatus, { supportStatus: problemData.status })}
|
||||
</div>
|
||||
</OverlayTrigger>
|
||||
</ActionRow>
|
||||
@@ -81,7 +82,7 @@ const AdvanceTypeSelect: React.FC<Props> = ({
|
||||
return (
|
||||
<ActionRow className="border-primary-100 border-bottom m-0 py-3 w-100" key={type}>
|
||||
<Form.Radio id={type} value={type}>
|
||||
{intl.formatMessage(messages.advanceProblemTypeLabel, { problemType: data.title })}
|
||||
<FormattedMessage {...messages[`problemType.${type}.title`]} />
|
||||
</Form.Radio>
|
||||
<ActionRow.Spacer />
|
||||
</ActionRow>
|
||||
|
||||
@@ -15,23 +15,25 @@ const Preview = ({
|
||||
if (problemType === null) {
|
||||
return null;
|
||||
}
|
||||
const data = ProblemTypes[problemType];
|
||||
|
||||
const staticData = ProblemTypes[problemType];
|
||||
|
||||
return (
|
||||
<Container style={{ width: '494px', height: '400px' }} className="bg-light-300 rounded p-4">
|
||||
<div className="small">
|
||||
{intl.formatMessage(messages.previewTitle, { previewTitle: data.title })}
|
||||
<FormattedMessage {...messages[`problemType.${problemType}.title`]} /> problem
|
||||
</div>
|
||||
<Image
|
||||
fluid
|
||||
className="my-3"
|
||||
src={data.preview}
|
||||
src={staticData.preview}
|
||||
alt={intl.formatMessage(messages.previewAltText, { problemType })}
|
||||
/>
|
||||
<div className="mb-3">
|
||||
{intl.formatMessage(messages.previewDescription, { previewDescription: data.previewDescription })}
|
||||
<FormattedMessage {...messages[`problemType.${problemType}.description`]} />
|
||||
</div>
|
||||
<Hyperlink
|
||||
destination={data.helpLink}
|
||||
destination={staticData.helpLink}
|
||||
target="_blank"
|
||||
>
|
||||
<FormattedMessage {...messages.learnMoreButtonLabel} />
|
||||
|
||||
@@ -1,48 +1,77 @@
|
||||
import React from 'react';
|
||||
import { render, screen, initializeMocks } from '../../../../../../testUtils';
|
||||
import { IntlProvider } from '@edx/frontend-platform/i18n';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import Preview from './Preview';
|
||||
import messages from './messages';
|
||||
|
||||
// Mock ProblemTypes and messages
|
||||
jest.mock('../../../../../data/constants/problem', () => ({
|
||||
ProblemTypes: {
|
||||
example: {
|
||||
title: 'Example Title',
|
||||
preview: 'example.png',
|
||||
previewDescription: 'Example description',
|
||||
helpLink: 'https://help.example.com',
|
||||
// Mock ProblemTypes to provide test data
|
||||
jest.mock('@src/editors/data/constants/problem', () => {
|
||||
const actualModule = jest.requireActual('@src/editors/data/constants/problem');
|
||||
|
||||
return {
|
||||
...actualModule,
|
||||
ProblemTypes: {
|
||||
multiplechoiceresponse: {
|
||||
title: 'Single select',
|
||||
preview: 'singleselect.png',
|
||||
previewDescription: 'Learners must select the correct answer from a list of possible options.',
|
||||
helpLink: 'https://help.example.com/singleselect',
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
};
|
||||
});
|
||||
|
||||
// Helper to render component with proper IntlProvider
|
||||
const renderWithIntl = (component: React.ReactElement) => {
|
||||
// Convert message objects to message strings for IntlProvider
|
||||
const messageStrings = Object.fromEntries(
|
||||
Object.entries(messages).map(([key, value]) => [key, (value as any).defaultMessage]),
|
||||
);
|
||||
|
||||
return render(
|
||||
<IntlProvider locale="en" messages={messageStrings}>
|
||||
{component}
|
||||
</IntlProvider>,
|
||||
);
|
||||
};
|
||||
|
||||
describe('Preview', () => {
|
||||
beforeEach(() => {
|
||||
initializeMocks();
|
||||
});
|
||||
|
||||
it('renders nothing if problemType is null', () => {
|
||||
const { container } = render(<Preview problemType={null} />);
|
||||
const reduxProviderDiv = container.querySelector('div[data-testid="redux-provider"]');
|
||||
expect(reduxProviderDiv?.innerHTML).toBe('');
|
||||
const { container } = renderWithIntl(<Preview problemType={null} />);
|
||||
expect(container.firstChild).toBeNull();
|
||||
});
|
||||
|
||||
it('renders preview with correct data for a valid problemType', () => {
|
||||
render(<Preview problemType="example" />);
|
||||
expect(screen.getByText('Example Title problem')).toBeInTheDocument();
|
||||
expect(screen.getByText('Example description')).toBeInTheDocument();
|
||||
expect(screen.getByRole('img')).toHaveAttribute('src', 'example.png');
|
||||
expect(screen.getByRole('img')).toHaveAttribute('alt', 'A preview illustration of a null problem');
|
||||
expect(screen.getByRole('link', { name: 'Learn more in a new tab' })).toHaveAttribute('href', 'https://help.example.com');
|
||||
renderWithIntl(<Preview problemType="multiplechoiceresponse" />);
|
||||
|
||||
// Check that the title is rendered correctly
|
||||
expect(screen.getByText('Single select problem')).toBeInTheDocument();
|
||||
|
||||
// Check that the description is rendered correctly
|
||||
expect(screen.getByText('Learners must select the correct answer from a list of possible options.')).toBeInTheDocument();
|
||||
|
||||
// Check that the image has correct src attribute
|
||||
const image = screen.getByRole('img');
|
||||
expect(image).toHaveAttribute('src', 'singleselect.png');
|
||||
|
||||
// Check that the learn more link is rendered correctly
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('href', 'https://help.example.com/singleselect');
|
||||
expect(link).toHaveAttribute('target', '_blank');
|
||||
expect(screen.getByText('Learn more')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders the help link with target="_blank"', () => {
|
||||
render(<Preview problemType="example" />);
|
||||
const link = screen.getByRole('link', { name: 'Learn more in a new tab' });
|
||||
renderWithIntl(<Preview problemType="multiplechoiceresponse" />);
|
||||
const link = screen.getByRole('link');
|
||||
expect(link).toHaveAttribute('target', '_blank');
|
||||
expect(link).toHaveAttribute('href', 'https://help.example.com/singleselect');
|
||||
});
|
||||
|
||||
it('renders the correct title and description', () => {
|
||||
render(<Preview problemType="example" />);
|
||||
expect(screen.getByText('Example Title problem')).toBeInTheDocument();
|
||||
expect(screen.getByText('Example description')).toBeInTheDocument();
|
||||
it('displays the correct image source and alt text', () => {
|
||||
renderWithIntl(<Preview problemType="multiplechoiceresponse" />);
|
||||
const image = screen.getByRole('img');
|
||||
expect(image).toHaveAttribute('src', 'singleselect.png');
|
||||
expect(image).toHaveAttribute('alt', 'A preview illustration of a single select problem');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React from 'react';
|
||||
|
||||
import { ProblemTypeKeys } from '@src/editors/data/constants/problem';
|
||||
import {
|
||||
render, screen, fireEvent, initializeMocks,
|
||||
} from '../../../../../../testUtils';
|
||||
import { ProblemTypeKeys } from '../../../../../data/constants/problem';
|
||||
import ProblemTypeSelect from './ProblemTypeSelect';
|
||||
|
||||
describe('ProblemTypeSelect', () => {
|
||||
|
||||
@@ -2,15 +2,14 @@ import React from 'react';
|
||||
import { Button, Container } from '@openedx/paragon';
|
||||
import { FormattedMessage } from '@edx/frontend-platform/i18n';
|
||||
|
||||
// SelectableBox in paragon has a bug where you can't change selection. So we override it
|
||||
import SelectableBox from '../../../../../sharedComponents/SelectableBox';
|
||||
import {
|
||||
ProblemTypes,
|
||||
ProblemTypeKeys,
|
||||
AdvanceProblemKeys,
|
||||
AdvancedProblemType,
|
||||
ProblemType,
|
||||
} from '../../../../../data/constants/problem';
|
||||
} from '@src/editors/data/constants/problem';
|
||||
// SelectableBox in paragon has a bug where you can't change selection. So we override it
|
||||
import SelectableBox from '../../../../../sharedComponents/SelectableBox';
|
||||
import messages from './messages';
|
||||
|
||||
interface Props {
|
||||
@@ -45,7 +44,7 @@ const ProblemTypeSelect: React.FC<Props> = ({
|
||||
value={key}
|
||||
{...settings}
|
||||
>
|
||||
{ProblemTypes[key].title}
|
||||
<FormattedMessage {...messages[`problemType.${key}.title`]} />
|
||||
</SelectableBox>
|
||||
)
|
||||
: null
|
||||
|
||||
@@ -17,10 +17,104 @@ const messages = defineMessages({
|
||||
defaultMessage: 'Go back',
|
||||
description: 'Return to the previous menu that shows basic problem types',
|
||||
},
|
||||
advanceProblemTypeLabel: {
|
||||
id: 'authoring.problemEditor.advanceProblem.problemType.label',
|
||||
defaultMessage: '{problemType}',
|
||||
description: 'Label for advance problem type radio select',
|
||||
|
||||
// Direct problem type message pattern - replacing redundant advanceProblemTypeLabel
|
||||
'problemType.blankadvanced.title': {
|
||||
id: 'authoring.problemeditor.advancedproblemtype.blank.title',
|
||||
defaultMessage: 'Blank problem',
|
||||
description: 'Title for blank advanced problem type',
|
||||
},
|
||||
'problemType.circuitschematic.title': {
|
||||
id: 'authoring.problemeditor.advancedproblemtype.circuitschematic.title',
|
||||
defaultMessage: 'Circuit schematic builder',
|
||||
description: 'Title for circuit schematic builder advanced problem type',
|
||||
},
|
||||
'problemType.jsinputresponse.title': {
|
||||
id: 'authoring.problemeditor.advancedproblemtype.jsinput.title',
|
||||
defaultMessage: 'Custom JavaScript display and grading',
|
||||
description: 'Title for custom JavaScript display and grading advanced problem type',
|
||||
},
|
||||
'problemType.customgrader.title': {
|
||||
id: 'authoring.problemeditor.advancedproblemtype.customgrader.title',
|
||||
defaultMessage: 'Custom Python-evaluated input',
|
||||
description: 'Title for custom Python-evaluated input advanced problem type',
|
||||
},
|
||||
'problemType.imageresponse.title': {
|
||||
id: 'authoring.problemeditor.advancedproblemtype.image.title',
|
||||
defaultMessage: 'Image mapped input',
|
||||
description: 'Title for image mapped input advanced problem type',
|
||||
},
|
||||
'problemType.formularesponse.title': {
|
||||
id: 'authoring.problemeditor.advancedproblemtype.formula.title',
|
||||
defaultMessage: 'Math expression input',
|
||||
description: 'Title for math expression input advanced problem type',
|
||||
},
|
||||
'problemType.problemwithhint.title': {
|
||||
id: 'authoring.problemeditor.advancedproblemtype.problemwithhint.title',
|
||||
defaultMessage: 'Problem with adaptive hint',
|
||||
description: 'Title for problem with adaptive hint advanced problem type',
|
||||
},
|
||||
|
||||
// Basic Problem Type Messages by Key
|
||||
'problemType.multiplechoiceresponse.title': {
|
||||
id: 'authoring.problemeditor.problemtype.singleselect.title',
|
||||
defaultMessage: 'Single select',
|
||||
description: 'Title for single select problem type',
|
||||
},
|
||||
'problemType.multiplechoiceresponse.description': {
|
||||
id: 'authoring.problemeditor.problemtype.singleselect.description',
|
||||
defaultMessage: 'Learners must select the correct answer from a list of possible options.',
|
||||
description: 'Preview description for single select problem type',
|
||||
},
|
||||
'problemType.choiceresponse.title': {
|
||||
id: 'authoring.problemeditor.problemtype.multiselect.title',
|
||||
defaultMessage: 'Multi-select',
|
||||
description: 'Title for multi-select problem type',
|
||||
},
|
||||
'problemType.choiceresponse.description': {
|
||||
id: 'authoring.problemeditor.problemtype.multiselect.description',
|
||||
defaultMessage: 'Learners must select all correct answers from a list of possible options.',
|
||||
description: 'Preview description for multi-select problem type',
|
||||
},
|
||||
'problemType.optionresponse.title': {
|
||||
id: 'authoring.problemeditor.problemtype.dropdown.title',
|
||||
defaultMessage: 'Dropdown',
|
||||
description: 'Title for dropdown problem type',
|
||||
},
|
||||
'problemType.optionresponse.description': {
|
||||
id: 'authoring.problemeditor.problemtype.dropdown.description',
|
||||
defaultMessage: 'Learners must select the correct answer from a list of possible options',
|
||||
description: 'Preview description for dropdown problem type',
|
||||
},
|
||||
'problemType.numericalresponse.title': {
|
||||
id: 'authoring.problemeditor.problemtype.numeric.title',
|
||||
defaultMessage: 'Numerical input',
|
||||
description: 'Title for numerical input problem type',
|
||||
},
|
||||
'problemType.numericalresponse.description': {
|
||||
id: 'authoring.problemeditor.problemtype.numeric.description',
|
||||
defaultMessage: 'Specify one or more correct numeric answers, submitted in a response field.',
|
||||
description: 'Preview description for numerical input problem type',
|
||||
},
|
||||
'problemType.stringresponse.title': {
|
||||
id: 'authoring.problemeditor.problemtype.textinput.title',
|
||||
defaultMessage: 'Text input',
|
||||
description: 'Title for text input problem type',
|
||||
},
|
||||
'problemType.stringresponse.description': {
|
||||
id: 'authoring.problemeditor.problemtype.textinput.description',
|
||||
defaultMessage: 'Specify one or more correct text answers, including numbers and special characters, submitted in a response field.',
|
||||
description: 'Preview description for text input problem type',
|
||||
},
|
||||
'problemType.advanced.title': {
|
||||
id: 'authoring.problemeditor.problemtype.advanced.title',
|
||||
defaultMessage: 'Advanced Problem',
|
||||
description: 'Title for advanced problem type',
|
||||
},
|
||||
'problemType.advanced.description': {
|
||||
id: 'authoring.problemeditor.problemtype.advanced.description',
|
||||
defaultMessage: 'An Advanced Problem Type',
|
||||
description: 'Description for advanced problem type',
|
||||
},
|
||||
problemSupportStatus: {
|
||||
id: 'authoring.problemEditor.advanceProblem.supportStatus',
|
||||
@@ -41,7 +135,7 @@ const messages = defineMessages({
|
||||
in the future. They are not recommened for use in courses due to non-compliance with one
|
||||
or more of the base requirements, such as testing, accessibility, internationalization,
|
||||
and documentation.}
|
||||
other { }
|
||||
other { }
|
||||
}`,
|
||||
description: 'Message for support status tooltip',
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect } from 'react';
|
||||
import {
|
||||
AdvanceProblemKeys, AdvanceProblems, ProblemTypeKeys, ProblemTypes,
|
||||
} from '../../../../data/constants/problem';
|
||||
AdvanceProblemKeys, AdvanceProblems, ProblemTypeKeys, ProblemTypes, getProblemTypes, getAdvanceProblems,
|
||||
} from '@src/editors/data/constants/problem';
|
||||
import { snakeCaseKeys } from '../../../../utils';
|
||||
import { getDataFromOlx } from '../../../../data/redux/thunkActions/problem';
|
||||
|
||||
@@ -10,10 +10,16 @@ export const onSelect = ({
|
||||
updateField,
|
||||
setBlockTitle,
|
||||
defaultSettings,
|
||||
formatMessage,
|
||||
}) => () => {
|
||||
if (Object.values(AdvanceProblemKeys).includes(selected)) {
|
||||
updateField({ problemType: ProblemTypeKeys.ADVANCED, rawOLX: AdvanceProblems[selected].template });
|
||||
setBlockTitle(AdvanceProblems[selected].title);
|
||||
if (formatMessage) {
|
||||
const localizedAdvanceProblems = getAdvanceProblems(formatMessage);
|
||||
setBlockTitle(localizedAdvanceProblems[selected].title);
|
||||
} else {
|
||||
setBlockTitle(AdvanceProblems[selected].title);
|
||||
}
|
||||
} else {
|
||||
const newOLX = ProblemTypes[selected].template;
|
||||
const newMarkdown = ProblemTypes[selected].markdownTemplate;
|
||||
@@ -28,7 +34,12 @@ export const onSelect = ({
|
||||
defaultSettings: snakeCaseKeys(defaultSettings),
|
||||
});
|
||||
updateField({ ...newState, rawMarkdown: newMarkdown });
|
||||
setBlockTitle(ProblemTypes[selected].title);
|
||||
if (formatMessage) {
|
||||
const localizedProblemTypes = getProblemTypes(formatMessage);
|
||||
setBlockTitle(localizedProblemTypes[selected].title);
|
||||
} else {
|
||||
setBlockTitle(ProblemTypes[selected].title);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
/* eslint-disable prefer-destructuring */
|
||||
import React from 'react';
|
||||
|
||||
import * as problemConstants from '@src/editors/data/constants/problem';
|
||||
import { AdvanceProblems, ProblemTypeKeys, ProblemTypes } from '@src/editors/data/constants/problem';
|
||||
import * as hooks from './hooks';
|
||||
import { AdvanceProblems, ProblemTypeKeys, ProblemTypes } from '../../../../data/constants/problem';
|
||||
import { getDataFromOlx } from '../../../../data/redux/thunkActions/problem';
|
||||
|
||||
jest.mock('react', () => ({
|
||||
@@ -65,6 +67,26 @@ describe('SelectTypeModal hooks', () => {
|
||||
});
|
||||
expect(mocksetBlockTitle).toHaveBeenCalledWith(ProblemTypes[mockSelected].title);
|
||||
});
|
||||
test('onSelect sets localized advanced problem title when formatMessage is provided', () => {
|
||||
const mockSetBlockTitle2 = jest.fn();
|
||||
const mockUpdateField2 = jest.fn();
|
||||
const mockFormatMessage2 = (msg) => `localized-${msg.id || msg.defaultMessage || msg}`;
|
||||
const mockSelected2 = 'circuitschematic';
|
||||
jest.spyOn(problemConstants, 'getAdvanceProblems').mockImplementation((fmt) => ({
|
||||
circuitschematic: { title: fmt({ id: 'problem.circuitschematic.title' }) },
|
||||
}));
|
||||
hooks.onSelect({
|
||||
selected: mockSelected2,
|
||||
updateField: mockUpdateField2,
|
||||
setBlockTitle: mockSetBlockTitle2,
|
||||
formatMessage: mockFormatMessage2,
|
||||
})();
|
||||
expect(mockSetBlockTitle2).toHaveBeenCalledWith('localized-problem.circuitschematic.title');
|
||||
expect(mockUpdateField2).toHaveBeenCalledWith({
|
||||
problemType: ProblemTypeKeys.ADVANCED,
|
||||
rawOLX: AdvanceProblems[mockSelected2].template,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('useArrowNav', () => {
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
import React from 'react';
|
||||
|
||||
import { Row, Stack } from '@openedx/paragon';
|
||||
import ProblemTypeSelect from './content/ProblemTypeSelect';
|
||||
import Preview from './content/Preview';
|
||||
import AdvanceTypeSelect from './content/AdvanceTypeSelect';
|
||||
import SelectTypeWrapper from './SelectTypeWrapper';
|
||||
import * as hooks from './hooks';
|
||||
|
||||
import {
|
||||
AdvancedProblemType,
|
||||
isAdvancedProblemType,
|
||||
ProblemType,
|
||||
ProblemTypeKeys,
|
||||
} from '../../../../data/constants/problem';
|
||||
} from '@src/editors/data/constants/problem';
|
||||
import ProblemTypeSelect from './content/ProblemTypeSelect';
|
||||
import Preview from './content/Preview';
|
||||
import AdvanceTypeSelect from './content/AdvanceTypeSelect';
|
||||
import SelectTypeWrapper from './SelectTypeWrapper';
|
||||
import * as hooks from './hooks';
|
||||
|
||||
interface Props {
|
||||
onClose: (() => void) | null;
|
||||
|
||||
13
src/editors/data/constants/problem.test.ts
Normal file
13
src/editors/data/constants/problem.test.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { getProblemTitles } from '@src/editors/data/constants/problem';
|
||||
|
||||
describe('getProblemTitles', () => {
|
||||
it('returns a set of localized problem titles', () => {
|
||||
const formatMessage = (msg) => msg.id || msg.defaultMessage || String(msg);
|
||||
const titles = getProblemTitles(formatMessage);
|
||||
|
||||
expect(titles.size).toBeGreaterThan(0);
|
||||
for (const title of titles) {
|
||||
expect(typeof title).toBe('string');
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -6,6 +6,7 @@ import numericalInput from '../images/numericalInput.png';
|
||||
import textInput from '../images/textInput.png';
|
||||
import advancedOlxTemplates from './advancedOlxTemplates';
|
||||
import basicProblemTemplates from './basicProblemTemplates';
|
||||
import problemMessages from '../../containers/ProblemEditor/components/SelectTypeModal/SelectTypeWrapper/messages';
|
||||
|
||||
export const ProblemTypeKeys = StrictDict({
|
||||
SINGLESELECT: 'multiplechoiceresponse',
|
||||
@@ -17,6 +18,87 @@ export const ProblemTypeKeys = StrictDict({
|
||||
} as const);
|
||||
export type ProblemType = typeof ProblemTypeKeys[keyof typeof ProblemTypeKeys];
|
||||
|
||||
/**
|
||||
* Get problem types with internationalized strings.
|
||||
* @param {Function} formatMessage - The intl.formatMessage function
|
||||
* @returns {Object} ProblemTypes object with localized strings
|
||||
*
|
||||
* Usage in React components:
|
||||
*
|
||||
* import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
* import { getProblemTypes } from '../path/to/problem';
|
||||
*
|
||||
* const MyComponent = () => {
|
||||
* const intl = useIntl();
|
||||
* const localizedProblemTypes = getProblemTypes(intl.formatMessage);
|
||||
*
|
||||
* return <div>{localizedProblemTypes[ProblemTypeKeys.SINGLESELECT].title}</div>;
|
||||
* };
|
||||
*/
|
||||
export const getProblemTypes = (formatMessage) => ({
|
||||
[ProblemTypeKeys.SINGLESELECT]: {
|
||||
title: formatMessage(problemMessages.singleSelectTitle),
|
||||
preview: singleSelect,
|
||||
previewDescription: formatMessage(problemMessages.singleSelectDescription),
|
||||
description: formatMessage(problemMessages.singleSelectInstruction),
|
||||
helpLink: 'https://docs.openedx.org/en/latest/educators/concepts/exercise_tools/about_multi_select.html',
|
||||
prev: ProblemTypeKeys.TEXTINPUT,
|
||||
next: ProblemTypeKeys.MULTISELECT,
|
||||
template: basicProblemTemplates.singleSelect.olx,
|
||||
markdownTemplate: basicProblemTemplates.singleSelect.markdown,
|
||||
},
|
||||
[ProblemTypeKeys.MULTISELECT]: {
|
||||
title: formatMessage(problemMessages.multiSelectTitle),
|
||||
preview: multiSelect,
|
||||
previewDescription: formatMessage(problemMessages.multiSelectDescription),
|
||||
description: formatMessage(problemMessages.multiSelectInstruction),
|
||||
helpLink: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/exercise_tools/add_multi_select.html',
|
||||
next: ProblemTypeKeys.DROPDOWN,
|
||||
prev: ProblemTypeKeys.SINGLESELECT,
|
||||
template: basicProblemTemplates.multiSelect.olx,
|
||||
markdownTemplate: basicProblemTemplates.multiSelect.markdown,
|
||||
},
|
||||
[ProblemTypeKeys.DROPDOWN]: {
|
||||
title: formatMessage(problemMessages.dropdownTitle),
|
||||
preview: dropdown,
|
||||
previewDescription: formatMessage(problemMessages.dropdownDescription),
|
||||
description: formatMessage(problemMessages.dropdownInstruction),
|
||||
helpLink: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/exercise_tools/add_dropdown.html',
|
||||
next: ProblemTypeKeys.NUMERIC,
|
||||
prev: ProblemTypeKeys.MULTISELECT,
|
||||
template: basicProblemTemplates.dropdown.olx,
|
||||
markdownTemplate: basicProblemTemplates.dropdown.markdown,
|
||||
},
|
||||
[ProblemTypeKeys.NUMERIC]: {
|
||||
title: formatMessage(problemMessages.numericalInputTitle),
|
||||
preview: numericalInput,
|
||||
previewDescription: formatMessage(problemMessages.numericalInputDescription),
|
||||
description: formatMessage(problemMessages.numericalInputInstruction),
|
||||
helpLink: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/exercise_tools/manage_numerical_input_problem.html',
|
||||
next: ProblemTypeKeys.TEXTINPUT,
|
||||
prev: ProblemTypeKeys.DROPDOWN,
|
||||
template: basicProblemTemplates.numeric.olx,
|
||||
markdownTemplate: basicProblemTemplates.numeric.markdown,
|
||||
},
|
||||
[ProblemTypeKeys.TEXTINPUT]: {
|
||||
title: formatMessage(problemMessages.textInputTitle),
|
||||
preview: textInput,
|
||||
previewDescription: formatMessage(problemMessages.textInputDescription),
|
||||
description: formatMessage(problemMessages.textInputInstruction),
|
||||
helpLink: 'https://docs.openedx.org/en/latest/educators/how-tos/course_development/exercise_tools/add_text_input.html',
|
||||
prev: ProblemTypeKeys.NUMERIC,
|
||||
next: ProblemTypeKeys.SINGLESELECT,
|
||||
template: basicProblemTemplates.textInput.olx,
|
||||
markdownTemplate: basicProblemTemplates.textInput.markdown,
|
||||
},
|
||||
[ProblemTypeKeys.ADVANCED]: {
|
||||
title: formatMessage(problemMessages.advancedProblemTitle),
|
||||
preview: ('<div />'),
|
||||
description: formatMessage(problemMessages.advancedProblemDescription),
|
||||
helpLink: 'something.com',
|
||||
},
|
||||
});
|
||||
|
||||
export const ProblemTypes = StrictDict({
|
||||
[ProblemTypeKeys.SINGLESELECT]: {
|
||||
title: 'Single select',
|
||||
@@ -96,6 +178,61 @@ export function isAdvancedProblemType(pt: ProblemType | AdvancedProblemType): pt
|
||||
return Object.values(AdvanceProblemKeys).includes(pt as any);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get advanced problem types with internationalized strings.
|
||||
* @param {Function} formatMessage - The intl.formatMessage function
|
||||
* @returns {Object} AdvanceProblems object with localized strings
|
||||
*
|
||||
* Usage in React components:
|
||||
*
|
||||
* import { useIntl } from '@edx/frontend-platform/i18n';
|
||||
* import { getAdvanceProblems } from '../path/to/problem';
|
||||
*
|
||||
* const MyComponent = () => {
|
||||
* const intl = useIntl();
|
||||
* const localizedAdvanceProblems = getAdvanceProblems(intl.formatMessage);
|
||||
*
|
||||
* return <div>{localizedAdvanceProblems[AdvanceProblemKeys.BLANK].title}</div>;
|
||||
* };
|
||||
*/
|
||||
export const getAdvanceProblems = (formatMessage) => ({
|
||||
[AdvanceProblemKeys.BLANK]: {
|
||||
title: formatMessage(problemMessages.blankProblemTitle),
|
||||
status: '',
|
||||
template: '<problem></problem>',
|
||||
},
|
||||
[AdvanceProblemKeys.CIRCUITSCHEMATIC]: {
|
||||
title: formatMessage(problemMessages.circuitSchematicTitle),
|
||||
status: 'Not supported',
|
||||
template: advancedOlxTemplates.circuitSchematic,
|
||||
},
|
||||
[AdvanceProblemKeys.JSINPUT]: {
|
||||
title: formatMessage(problemMessages.customJavaScriptTitle),
|
||||
status: '',
|
||||
template: advancedOlxTemplates.jsInputResponse,
|
||||
},
|
||||
[AdvanceProblemKeys.CUSTOMGRADER]: {
|
||||
title: formatMessage(problemMessages.customPythonTitle),
|
||||
status: 'Provisional',
|
||||
template: advancedOlxTemplates.customGrader,
|
||||
},
|
||||
[AdvanceProblemKeys.IMAGE]: {
|
||||
title: formatMessage(problemMessages.imageMappedTitle),
|
||||
status: 'Not supported',
|
||||
template: advancedOlxTemplates.imageResponse,
|
||||
},
|
||||
[AdvanceProblemKeys.FORMULA]: {
|
||||
title: formatMessage(problemMessages.mathExpressionTitle),
|
||||
status: '',
|
||||
template: advancedOlxTemplates.formulaResponse,
|
||||
},
|
||||
[AdvanceProblemKeys.PROBLEMWITHHINT]: {
|
||||
title: formatMessage(problemMessages.problemWithHintTitle),
|
||||
status: 'Not supported',
|
||||
template: advancedOlxTemplates.problemWithHint,
|
||||
},
|
||||
});
|
||||
|
||||
export const AdvanceProblems = StrictDict({
|
||||
[AdvanceProblemKeys.BLANK]: {
|
||||
title: 'Blank problem',
|
||||
@@ -248,3 +385,13 @@ export const ignoredOlxAttributes = [
|
||||
// Useful for the block creation workflow.
|
||||
export const problemTitles = new Set([...Object.values(ProblemTypes).map((problem) => problem.title),
|
||||
...Object.values(AdvanceProblems).map((problem) => problem.title)]);
|
||||
|
||||
/**
|
||||
* Get problem titles with internationalization support
|
||||
* @param {Function} formatMessage - The intl.formatMessage function
|
||||
* @returns {Set<string>} Set of localized problem titles
|
||||
*/
|
||||
export const getProblemTitles = (formatMessage) => new Set([
|
||||
...Object.values(getProblemTypes(formatMessage)).map((problem) => problem.title),
|
||||
...Object.values(getAdvanceProblems(formatMessage)).map((problem) => problem.title),
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user