diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.jsx index cbe42ee20c..9c0524b700 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.jsx @@ -1,6 +1,5 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; +import { useSelector } from 'react-redux'; import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n'; import { getConfig } from '@edx/frontend-platform'; @@ -9,27 +8,30 @@ import messages from './messages'; import TinyMceWidget from '../../../../../sharedComponents/TinyMceWidget'; import { prepareEditorRef, replaceStaticWithAsset } from '../../../../../sharedComponents/TinyMceWidget/hooks'; -const ExplanationWidget = ({ - // redux - settings, - learningContextId, - images, - isLibrary, - blockId, -}) => { +const ExplanationWidget = () => { const intl = useIntl(); const { editorRef, refReady, setEditorRef } = prepareEditorRef(); + + const settings = useSelector(selectors.problem.settings); + const learningContextId = useSelector(selectors.app.learningContextId); + const images = useSelector(selectors.app.images); + const isLibrary = useSelector(selectors.app.isLibrary); + const blockId = useSelector(selectors.app.blockId); + const initialContent = settings?.solutionExplanation || ''; const newContent = replaceStaticWithAsset({ initialContent, learningContextId, }); const solutionContent = newContent || initialContent; + let staticRootUrl; if (isLibrary) { - staticRootUrl = `${getConfig().STUDIO_BASE_URL }/library_assets/blocks/${ blockId }/`; + staticRootUrl = `${getConfig().STUDIO_BASE_URL}/library_assets/blocks/${blockId}/`; } + if (!refReady) { return null; } + return (
@@ -57,22 +59,4 @@ const ExplanationWidget = ({ ); }; -ExplanationWidget.propTypes = { - // redux - // eslint-disable-next-line - settings: PropTypes.any.isRequired, - learningContextId: PropTypes.string.isRequired, - images: PropTypes.shape({}).isRequired, - isLibrary: PropTypes.bool.isRequired, - blockId: PropTypes.string.isRequired, -}; -export const mapStateToProps = (state) => ({ - settings: selectors.problem.settings(state), - learningContextId: selectors.app.learningContextId(state), - images: selectors.app.images(state), - isLibrary: selectors.app.isLibrary(state), - blockId: selectors.app.blockId(state), -}); - -export const ExplanationWidgetInternal = ExplanationWidget; // For testing only -export default connect(mapStateToProps)(ExplanationWidget); +export default ExplanationWidget; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.test.tsx b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.test.tsx index 616484ee81..b631db883f 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.test.tsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/ExplanationWidget/index.test.tsx @@ -1,27 +1,7 @@ import React from 'react'; -import { render, screen, initializeMocks } from '@src/testUtils'; -import ExplanationWidget from '.'; - -jest.mock('../../../../../data/redux', () => ({ - __esModule: true, - default: jest.fn(), - selectors: { - problem: { - settings: jest.fn(state => ({ question: state })), - }, - app: { - learningContextId: jest.fn(state => ({ learningContextId: state })), - images: jest.fn(state => ({ images: state })), - isLibrary: jest.fn(state => ({ isLibrary: state })), - blockId: jest.fn(state => ({ blockId: state })), - }, - }, - thunkActions: { - video: { - importTranscript: jest.fn(), - }, - }, -})); +import { screen, initializeMocks } from '@src/testUtils'; +import { editorRender } from '../../../../../editorTestRender'; +import ExplanationWidget from './index'; jest.mock('../../../../../sharedComponents/TinyMceWidget/hooks', () => ({ ...jest.requireActual('../../../../../sharedComponents/TinyMceWidget/hooks'), @@ -36,19 +16,24 @@ jest.mock('../../../../../sharedComponents/TinyMceWidget', () => ({ default: () =>
TinyMceWidget
, })); -describe('SolutionWidget', () => { - const props = { +const initialState = { + problem: { settings: { solutionExplanation: 'This is my solution' }, + }, + app: { learningContextId: 'course+org+run', images: {}, isLibrary: false, blockId: 'block-v1:Org+TS100+24+type@html+block@12345', - }; + }, +}; + +describe('SolutionWidget', () => { beforeEach(() => { initializeMocks(); }); test('renders correct default', () => { - render(); + editorRender(, { initialState }); expect(screen.getByText('Explanation')).toBeInTheDocument(); expect(screen.getByText('Provide an explanation for the correct answer')).toBeInTheDocument(); expect(screen.getByText('TinyMceWidget')).toBeInTheDocument(); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.jsx index f8020721a1..e2f5e4674e 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.jsx @@ -1,35 +1,40 @@ import React from 'react'; -import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; +import { useSelector } from 'react-redux'; +import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n'; import { getConfig } from '@edx/frontend-platform'; import { selectors } from '../../../../../data/redux'; import messages from './messages'; import TinyMceWidget from '../../../../../sharedComponents/TinyMceWidget'; -import { prepareEditorRef, replaceStaticWithAsset } from '../../../../../sharedComponents/TinyMceWidget/hooks'; +import { + prepareEditorRef, + replaceStaticWithAsset, +} from '../../../../../sharedComponents/TinyMceWidget/hooks'; -const QuestionWidget = ({ - // redux - question, - learningContextId, - images, - isLibrary, - blockId, -}) => { +const QuestionWidget = () => { const intl = useIntl(); + const question = useSelector(selectors.problem.question); + const learningContextId = useSelector(selectors.app.learningContextId); + const images = useSelector(selectors.app.images); + const isLibrary = useSelector(selectors.app.isLibrary); + const blockId = useSelector(selectors.app.blockId); + const { editorRef, refReady, setEditorRef } = prepareEditorRef(); + const initialContent = question; const newContent = replaceStaticWithAsset({ initialContent, learningContextId, }); const questionContent = newContent || initialContent; + let staticRootUrl; if (isLibrary) { - staticRootUrl = `${getConfig().STUDIO_BASE_URL }/library_assets/blocks/${ blockId }/`; + staticRootUrl = `${getConfig().STUDIO_BASE_URL}/library_assets/blocks/${blockId}/`; } + if (!refReady) { return null; } + return (
@@ -54,21 +59,4 @@ const QuestionWidget = ({ ); }; -QuestionWidget.propTypes = { - // redux - question: PropTypes.string.isRequired, - learningContextId: PropTypes.string.isRequired, - images: PropTypes.shape({}).isRequired, - isLibrary: PropTypes.bool.isRequired, - blockId: PropTypes.string.isRequired, -}; -export const mapStateToProps = (state) => ({ - question: selectors.problem.question(state), - learningContextId: selectors.app.learningContextId(state), - images: selectors.app.images(state), - isLibrary: selectors.app.isLibrary(state), - blockId: selectors.app.blockId(state), -}); - -export const QuestionWidgetInternal = QuestionWidget; // For testing only -export default connect(mapStateToProps)(QuestionWidget); +export default QuestionWidget; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.tsx b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.tsx index 886d7351e7..d48edcb0cf 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.tsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/QuestionWidget/index.test.tsx @@ -1,33 +1,7 @@ import React from 'react'; -import { render, screen, initializeMocks } from '@src/testUtils'; -import { formatMessage } from '@src/editors/testUtils'; -import { QuestionWidgetInternal as QuestionWidget } from '.'; - -jest.mock('@src/editors/data/redux', () => ({ - __esModule: true, - default: jest.fn(), - actions: { - problem: { - updateQuestion: jest.fn().mockName('actions.problem.updateQuestion'), - }, - }, - selectors: { - app: { - learningContextId: jest.fn(state => ({ learningContextId: state })), - images: jest.fn(state => ({ images: state })), - isLibrary: jest.fn(state => ({ isLibrary: state })), - blockId: jest.fn(state => ({ blockId: state })), - }, - problem: { - question: jest.fn(state => ({ question: state })), - }, - }, - thunkActions: { - video: { - importTranscript: jest.fn(), - }, - }, -})); +import { screen, initializeMocks } from '@src/testUtils'; +import { editorRender } from '../../../../../editorTestRender'; +import QuestionWidget from '.'; jest.mock('@src/editors/sharedComponents/TinyMceWidget/hooks', () => ({ ...jest.requireActual('../../../../../sharedComponents/TinyMceWidget/hooks'), @@ -39,23 +13,25 @@ jest.mock('@src/editors/sharedComponents/TinyMceWidget/hooks', () => ({ jest.mock('@src/editors/sharedComponents/TinyMceWidget', () => ('TinyMceWidget')); -describe('QuestionWidget', () => { - const props = { +const initialState = { + problem: { question: 'This is my question', - updateQuestion: jest.fn(), + }, + app: { learningContextId: 'course+org+run', images: {}, isLibrary: false, blockId: '', - // injected - intl: { formatMessage }, - }; + }, +}; + +describe('QuestionWidget', () => { describe('render', () => { beforeEach(() => { initializeMocks(); }); test('renders correct default', () => { - render(); + editorRender(, { initialState }); expect(screen.getByText('Question')).toBeInTheDocument(); }); }); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.tsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.tsx index 33f728d2ea..2a9875619b 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.tsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.test.tsx @@ -1,9 +1,8 @@ import React from 'react'; +import { screen, initializeMocks } from '@src/testUtils'; +import { editorRender, type PartialEditorState } from '@src/editors/editorTestRender'; 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 '.'; @@ -18,6 +17,58 @@ jest.mock('./settingsComponents/SwitchEditorCard', () => 'SwitchEditorCard'); jest.mock('./settingsComponents/TimerCard', () => 'TimerCard'); jest.mock('./settingsComponents/TypeCard', () => 'TypeCard'); +const initialState: PartialEditorState = { + problem: { + groupFeedbackList: [], // <-- not a function + settings: { + hints: [], + scoring: { + weight: 1, + attempts: { + unlimited: false, + number: 2, + }, + }, + showAnswer: { on: 'finished' }, + showResetButton: false, + randomization: 'always' as any, + tolerance: { value: 0.1, type: 'Number' }, + timeBetween: 0, + }, + answers: [], + correctAnswerCount: 0, + defaultSettings: { + maxAttempts: { value: 2 }, + showanswer: 'finished', + showResetButton: false, + rerandomize: 'always', + } as Record, + rawMarkdown: '## Sample markdown', + }, + app: { + blockTitle: 'Sample Block Title', + images: {}, + learningContextId: 'course+org+run', + isMarkdownEditorEnabledForCourse: true, + }, +}; +jest.mock('../../../../../data/redux', () => { + const originalModule = jest.requireActual('../../../../../data/redux'); + return { + ...originalModule, + actions: { + app: { + setBlockTitle: jest.fn(() => ({ type: 'MOCK_SET_BLOCK_TITLE' })), + }, + problem: { + updateSettings: jest.fn(() => ({ type: 'MOCK_UPDATE_SETTINGS' })), + updateField: jest.fn(() => ({ type: 'MOCK_UPDATE_FIELD' })), + updateAnswer: jest.fn(() => ({ type: 'MOCK_UPDATE_ANSWER' })), + }, + }, + }; +}); + describe('SettingsWidget', () => { const showAdvancedSettingsCardsBaseProps = { isAdvancedCardsVisible: false, @@ -27,25 +78,6 @@ describe('SettingsWidget', () => { const props = { problemType: ProblemTypeKeys.TEXTINPUT, - settings: {}, - defaultSettings: { - maxAttempts: 2, - showanswer: 'finished', - showResetButton: false, - }, - images: {}, - isLibrary: false, - learningContextId: 'course+org+run', - setBlockTitle: jest.fn().mockName('setBlockTitle'), - blockTitle: '', - updateAnswer: jest.fn().mockName('updateAnswer'), - updateSettings: jest.fn().mockName('updateSettings'), - updateField: jest.fn().mockName('updateField'), - answers: [], - correctAnswerCount: 0, - groupFeedbackList: [], - showMarkdownEditorButton: false, - }; beforeEach(() => { @@ -55,7 +87,7 @@ describe('SettingsWidget', () => { describe('behavior', () => { it('calls showAdvancedSettingsCards when initialized', () => { jest.spyOn(hooks, 'showAdvancedSettingsCards').mockReturnValue(showAdvancedSettingsCardsBaseProps); - render(); + editorRender(, { initialState }); expect(hooks.showAdvancedSettingsCards).toHaveBeenCalled(); }); }); @@ -63,7 +95,7 @@ describe('SettingsWidget', () => { describe('renders', () => { test('renders Settings widget page', () => { jest.spyOn(hooks, 'showAdvancedSettingsCards').mockReturnValue(showAdvancedSettingsCardsBaseProps); - render(); + editorRender(, { initialState }); expect(screen.getByText('Show advanced settings')).toBeInTheDocument(); }); @@ -73,7 +105,7 @@ describe('SettingsWidget', () => { isAdvancedCardsVisible: true, }; jest.spyOn(hooks, 'showAdvancedSettingsCards').mockReturnValue(showAdvancedSettingsCardsProps); - const { container } = render(); + const { container } = editorRender(, { initialState }); expect(screen.queryByText('Show advanced settings')).not.toBeInTheDocument(); expect(container.querySelector('showanswercard')).toBeInTheDocument(); expect(container.querySelector('resetcard')).toBeInTheDocument(); @@ -85,9 +117,11 @@ describe('SettingsWidget', () => { isAdvancedCardsVisible: true, }; jest.spyOn(hooks, 'showAdvancedSettingsCards').mockReturnValue(showAdvancedSettingsCardsProps); - const { container } = render( - , - ); + const { container } = editorRender(, { initialState }); + expect(container.querySelector('randomization')).toBeInTheDocument(); }); }); @@ -99,7 +133,7 @@ describe('SettingsWidget', () => { }; test('renders Settings widget page', () => { jest.spyOn(hooks, 'showAdvancedSettingsCards').mockReturnValue(showAdvancedSettingsCardsBaseProps); - const { container } = render(); + const { container } = editorRender(, { initialState }); expect(container.querySelector('timercard')).not.toBeInTheDocument(); expect(container.querySelector('resetcard')).not.toBeInTheDocument(); expect(container.querySelector('typecard')).toBeInTheDocument(); @@ -113,10 +147,9 @@ describe('SettingsWidget', () => { isAdvancedCardsVisible: true, }; jest.spyOn(hooks, 'showAdvancedSettingsCards').mockReturnValue(showAdvancedSettingsCardsProps); - const { container } = render(); + const { container } = editorRender(, { initialState }); expect(screen.queryByText('Show advanced settings')).not.toBeInTheDocument(); expect(container.querySelector('showanswearscard')).not.toBeInTheDocument(); - expect(container.querySelector('resetcard')).not.toBeInTheDocument(); expect(container.querySelector('typecard')).toBeInTheDocument(); expect(container.querySelector('hintscard')).toBeInTheDocument(); }); @@ -127,7 +160,10 @@ describe('SettingsWidget', () => { isAdvancedCardsVisible: true, }; jest.spyOn(hooks, 'showAdvancedSettingsCards').mockReturnValue(showAdvancedSettingsCardsProps); - const { container } = render(); + const { container } = editorRender( + , + { initialState }, + ); expect(container.querySelector('randomization')).toBeInTheDocument(); }); }); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.tsx similarity index 52% rename from src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.jsx rename to src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.tsx index ded9e742c7..a140f72025 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/index.tsx @@ -1,7 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; + +import { useDispatch, useSelector } from 'react-redux'; import { FormattedMessage } from '@edx/frontend-platform/i18n'; -import { connect } from 'react-redux'; import { Button, Collapsible, } from '@openedx/paragon'; @@ -17,44 +18,63 @@ import GroupFeedbackCard from './settingsComponents/GroupFeedback/index'; import SwitchEditorCard from './settingsComponents/SwitchEditorCard'; import messages from './messages'; import { showAdvancedSettingsCards } from './hooks'; - -import './index.scss'; import { ProblemTypeKeys } from '../../../../../data/constants/problem'; import Randomization from './settingsComponents/Randomization'; +import type { EditorState } from '../../../../../data/redux'; + +import './index.scss'; -// This widget should be connected, grab all settings from store, update them as needed. const SettingsWidget = ({ problemType, - // redux - answers, - groupFeedbackList, - blockTitle, - correctAnswerCount, - settings, - setBlockTitle, - updateSettings, - updateField, - updateAnswer, - defaultSettings, - images, - isLibrary, - learningContextId, - showMarkdownEditorButton, +}: { + problemType: string; }) => { + const dispatch = useDispatch(); + const { + groupFeedbackList, + settings, + answers, + blockTitle, + correctAnswerCount, + defaultSettings, + images, + isLibrary, + learningContextId, + showMarkdownEditorButton, + } = useSelector((state: EditorState) => ({ + groupFeedbackList: selectors.problem.groupFeedbackList(state), + settings: selectors.problem.settings(state), + answers: selectors.problem.answers(state), + blockTitle: selectors.app.blockTitle(state), + correctAnswerCount: selectors.problem.correctAnswerCount(state), + defaultSettings: selectors.problem.defaultSettings(state), + images: selectors.app.images(state), + isLibrary: selectors.app.isLibrary(state), + learningContextId: selectors.app.learningContextId(state), + showMarkdownEditorButton: selectors.app.isMarkdownEditorEnabledForCourse(state) + && selectors.problem.rawMarkdown(state), + })); + const { isAdvancedCardsVisible, showAdvancedCards } = showAdvancedSettingsCards(); + + const setBlockTitle = (title) => dispatch(actions.app.setBlockTitle(title)); + const updateSettings = (newSettings) => dispatch(actions.problem.updateSettings(newSettings)); + const updateField = (payload) => dispatch(actions.problem.updateField(payload)); + const updateAnswer = (payload) => dispatch(actions.problem.updateAnswer(payload)); + const feedbackCard = () => { - if ([ProblemTypeKeys.MULTISELECT].includes(problemType)) { + if (problemType === ProblemTypeKeys.MULTISELECT) { return ( -
+
+
); } - // eslint-disable-next-line react/jsx-no-useless-fragment - return (<>); + return null; }; return ( @@ -62,7 +82,7 @@ const SettingsWidget = ({
- {ProblemTypeKeys.NUMERIC === problemType - && ( -
- -
- )} + + {problemType === ProblemTypeKeys.NUMERIC && ( +
+ +
+ )} + {!isLibrary && (
)} +
+ {feedbackCard()} +
@@ -117,6 +139,7 @@ const SettingsWidget = ({
+ {!isLibrary && ( @@ -131,14 +154,13 @@ const SettingsWidget = ({ {!isLibrary && (
)} - { - problemType === ProblemTypeKeys.ADVANCED && ( + {problemType === ProblemTypeKeys.ADVANCED && (
- ) - } + )} {!isLibrary && (
- +
)}
- { showMarkdownEditorButton - && ( -
- -
+ {showMarkdownEditorButton && ( +
+ +
)}
@@ -169,63 +192,8 @@ const SettingsWidget = ({ }; SettingsWidget.propTypes = { - answers: PropTypes.arrayOf(PropTypes.shape({ - correct: PropTypes.bool, - id: PropTypes.string, - selectedFeedback: PropTypes.string, - title: PropTypes.string, - unselectedFeedback: PropTypes.string, - })).isRequired, - groupFeedbackList: PropTypes.arrayOf( - PropTypes.shape( - { - id: PropTypes.number, - feedback: PropTypes.string, - answers: PropTypes.arrayOf(PropTypes.string), - }, - ), - ).isRequired, - blockTitle: PropTypes.string.isRequired, - correctAnswerCount: PropTypes.number.isRequired, problemType: PropTypes.string.isRequired, - setBlockTitle: PropTypes.func.isRequired, - updateAnswer: PropTypes.func.isRequired, - updateField: PropTypes.func.isRequired, - updateSettings: PropTypes.func.isRequired, - defaultSettings: PropTypes.shape({ - maxAttempts: PropTypes.number, - showanswer: PropTypes.string, - showResetButton: PropTypes.bool, - rerandomize: PropTypes.string, - }).isRequired, - images: PropTypes.shape({}).isRequired, - learningContextId: PropTypes.string.isRequired, - isLibrary: PropTypes.bool.isRequired, - // eslint-disable-next-line - settings: PropTypes.any.isRequired, - showMarkdownEditorButton: PropTypes.bool.isRequired, -}; - -const mapStateToProps = (state) => ({ - groupFeedbackList: selectors.problem.groupFeedbackList(state), - settings: selectors.problem.settings(state), - answers: selectors.problem.answers(state), - blockTitle: selectors.app.blockTitle(state), - correctAnswerCount: selectors.problem.correctAnswerCount(state), - defaultSettings: selectors.problem.defaultSettings(state), - images: selectors.app.images(state), - isLibrary: selectors.app.isLibrary(state), - learningContextId: selectors.app.learningContextId(state), - showMarkdownEditorButton: selectors.app.isMarkdownEditorEnabledForCourse(state) - && selectors.problem.rawMarkdown(state), -}); - -export const mapDispatchToProps = { - setBlockTitle: actions.app.setBlockTitle, - updateSettings: actions.problem.updateSettings, - updateField: actions.problem.updateField, - updateAnswer: actions.problem.updateAnswer, }; -export const SettingsWidgetInternal = SettingsWidget; // For testing only -export default connect(mapStateToProps, mapDispatchToProps)(SettingsWidget); +export const SettingsWidgetInternal = SettingsWidget; // For testing +export default SettingsWidget; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ScoringCard.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ScoringCard.jsx index 6b12e552a2..5ebaa97e5f 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ScoringCard.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ScoringCard.jsx @@ -1,7 +1,7 @@ import React from 'react'; import isNil from 'lodash/isNil'; import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; +import { useSelector } from 'react-redux'; import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import { Form, Hyperlink } from '@openedx/paragon'; import { selectors } from '../../../../../../data/redux'; @@ -13,12 +13,13 @@ const ScoringCard = ({ scoring, defaultValue, updateSettings, - // redux - studioEndpointUrl, - learningContextId, - isLibrary, }) => { const intl = useIntl(); + + const studioEndpointUrl = useSelector(selectors.app.studioEndpointUrl); + const learningContextId = useSelector(selectors.app.learningContextId); + const isLibrary = useSelector(selectors.app.isLibrary); + const { handleUnlimitedChange, handleMaxAttemptChange, @@ -92,27 +93,19 @@ const ScoringCard = ({ }; ScoringCard.propTypes = { - // eslint-disable-next-line - scoring: PropTypes.any.isRequired, + scoring: PropTypes.shape({ + weight: PropTypes.number.isRequired, + attempts: PropTypes.shape({ + number: PropTypes.number, + unlimited: PropTypes.bool.isRequired, + }).isRequired, + }).isRequired, updateSettings: PropTypes.func.isRequired, defaultValue: PropTypes.number, - // redux - studioEndpointUrl: PropTypes.string.isRequired, - learningContextId: PropTypes.string, - isLibrary: PropTypes.bool.isRequired, }; ScoringCard.defaultProps = { - learningContextId: null, defaultValue: null, }; -export const mapStateToProps = (state) => ({ - studioEndpointUrl: selectors.app.studioEndpointUrl(state), - learningContextId: selectors.app.learningContextId(state), - isLibrary: selectors.app.isLibrary(state), -}); - -export const mapDispatchToProps = {}; - -export default connect(mapStateToProps, mapDispatchToProps)(ScoringCard); +export default ScoringCard; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ScoringCard.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ScoringCard.test.jsx index e5f632a392..61a127c13a 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ScoringCard.test.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ScoringCard.test.jsx @@ -1,9 +1,10 @@ import React from 'react'; import { - render, screen, initializeMocks, fireEvent, + screen, initializeMocks, fireEvent, } from '@src/testUtils'; import ScoringCard from './ScoringCard'; import { selectors } from '../../../../../../data/redux'; +import { editorRender } from '../../../../../../editorTestRender'; const { app } = selectors; @@ -24,33 +25,30 @@ describe('ScoringCard', () => { }; beforeEach(() => { - jest.spyOn(app, 'studioEndpointUrl').mockReturnValue('studioEndpointUrl'); - jest.spyOn(app, 'learningContextId').mockReturnValue('learningContextId'); - jest.spyOn(app, 'isLibrary').mockReturnValue(false); initializeMocks(); }); test('render the component', () => { - render(); + editorRender(); expect(screen.getByText('Scoring')).toBeInTheDocument(); }); test('should not render advance settings link when isLibrary is true', () => { jest.spyOn(app, 'isLibrary').mockReturnValue(true); - render(); + editorRender(); fireEvent.click(screen.getByText('Scoring')); expect(screen.queryByText('Set a default value in advanced settings')).not.toBeInTheDocument(); }); test('should render advance settings link when isLibrary is false', () => { jest.spyOn(app, 'isLibrary').mockReturnValue(false); - render(); + editorRender(); fireEvent.click(screen.getByText('Scoring')); expect(screen.getByText('Set a default value in advanced settings')).toBeInTheDocument(); }); test('should call updateSettings when clicking points button', () => { - render(); + editorRender(); fireEvent.click(screen.getByText('Scoring')); const pointsButton = screen.getByRole('spinbutton', { name: 'Points' }); expect(pointsButton).toBeInTheDocument(); @@ -61,7 +59,7 @@ describe('ScoringCard', () => { test('should call updateSettings when clicking attempts button', () => { const scoringUnlimited = { ...scoring, attempts: { unlimited: true, number: 0 } }; - render(); + editorRender(); fireEvent.click(screen.getByText('Scoring')); fireEvent.click(screen.getByText('Attempts')); const attemptsButton = screen.getByRole('spinbutton', { name: 'Points' }); @@ -73,7 +71,7 @@ describe('ScoringCard', () => { test('should display checked checkbox when unlimited is true', () => { const scoringUnlimited = { ...scoring, attempts: { unlimited: true, number: 0 } }; - render(); + editorRender(); fireEvent.click(screen.getByText('Scoring')); const checkbox = screen.getByRole('checkbox', { name: 'Unlimited attempts' }); expect(checkbox).toBeChecked(); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ShowAnswerCard.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ShowAnswerCard.jsx index 414233d53f..34c805007c 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ShowAnswerCard.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ShowAnswerCard.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { connect } from 'react-redux'; -import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; +import { useSelector } from 'react-redux'; +import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n'; import { Form, Hyperlink } from '@openedx/paragon'; import SettingsOption from '../SettingsOption'; import { ShowAnswerTypes, ShowAnswerTypesKeys } from '../../../../../../data/constants/problem'; @@ -13,12 +13,12 @@ const ShowAnswerCard = ({ showAnswer, updateSettings, defaultValue, - // redux - studioEndpointUrl, - learningContextId, - isLibrary, + intl, }) => { - const intl = useIntl(); + const studioEndpointUrl = useSelector(selectors.app.studioEndpointUrl); + const learningContextId = useSelector(selectors.app.learningContextId); + const isLibrary = useSelector(selectors.app.isLibrary); + const { handleShowAnswerChange, handleAttemptsChange, @@ -50,7 +50,10 @@ const ShowAnswerCard = ({ {Object.values(ShowAnswerTypesKeys).map((answerType) => { let optionDisplayName = ShowAnswerTypes[answerType]; if (answerType === defaultValue) { - optionDisplayName = { ...optionDisplayName, defaultMessage: `${optionDisplayName.defaultMessage} (Default)` }; + optionDisplayName = { + ...optionDisplayName, + defaultMessage: `${optionDisplayName.defaultMessage} (Default)`, + }; } return (