diff --git a/src/editors/containers/EditorContainer/components/TitleHeader/EditableHeader.jsx b/src/editors/containers/EditorContainer/components/TitleHeader/EditableHeader.jsx index 0483bffe5..a97bb38df 100644 --- a/src/editors/containers/EditorContainer/components/TitleHeader/EditableHeader.jsx +++ b/src/editors/containers/EditorContainer/components/TitleHeader/EditableHeader.jsx @@ -47,5 +47,4 @@ EditableHeader.propTypes = { cancelEdit: PropTypes.func.isRequired, }; -export const EditableHeaderInternal = EditableHeader; // For testing only export default EditableHeader; diff --git a/src/editors/containers/EditorContainer/components/TitleHeader/EditableHeader.test.jsx b/src/editors/containers/EditorContainer/components/TitleHeader/EditableHeader.test.jsx deleted file mode 100644 index 349b89b5a..000000000 --- a/src/editors/containers/EditorContainer/components/TitleHeader/EditableHeader.test.jsx +++ /dev/null @@ -1,33 +0,0 @@ -import 'CourseAuthoring/editors/setupEditorTest'; -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; -import { Form } from '@openedx/paragon'; -import { EditableHeaderInternal as EditableHeader } from './EditableHeader'; -import EditConfirmationButtons from './EditConfirmationButtons'; - -describe('EditableHeader', () => { - const props = { - handleChange: jest.fn().mockName('args.handleChange'), - updateTitle: jest.fn().mockName('args.updateTitle'), - handleKeyDown: jest.fn().mockName('args.handleKeyDown'), - inputRef: jest.fn().mockName('args.inputRef'), - localTitle: 'test-title-text', - cancelEdit: jest.fn().mockName('args.cancelEdit'), - }; - let el; - beforeEach(() => { - el = shallow(); - }); - - describe('snapshot', () => { - test('snapshot', () => { - expect(el.snapshot).toMatchSnapshot(); - }); - test('displays Edit Icon', () => { - const formControl = el.instance.findByType(Form.Control)[0]; - expect(formControl.props.trailingElement).toMatchObject( - , - ); - }); - }); -}); diff --git a/src/editors/containers/EditorContainer/components/TitleHeader/EditableHeader.test.tsx b/src/editors/containers/EditorContainer/components/TitleHeader/EditableHeader.test.tsx new file mode 100644 index 000000000..97147ad44 --- /dev/null +++ b/src/editors/containers/EditorContainer/components/TitleHeader/EditableHeader.test.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { + render, screen, initializeMocks, fireEvent, +} from '@src/testUtils'; +import EditableHeader from './EditableHeader'; + +describe('EditableHeader', () => { + const props = { + handleChange: jest.fn().mockName('args.handleChange'), + updateTitle: jest.fn().mockName('args.updateTitle'), + handleKeyDown: jest.fn().mockName('args.handleKeyDown'), + inputRef: jest.fn().mockName('args.inputRef'), + localTitle: 'test-title-text', + cancelEdit: jest.fn().mockName('args.cancelEdit'), + }; + + beforeEach(() => { + initializeMocks(); + }); + + test('renders input with correct value and placeholder', () => { + render(); + const input = screen.getByPlaceholderText('Title'); + expect(input).toBeInTheDocument(); + expect((input as HTMLInputElement).value).toBe(props.localTitle); + }); + + test('calls handleChange when input changes', () => { + render(); + const input = screen.getByPlaceholderText('Title'); + fireEvent.change(input, { target: { value: 'New title' } }); + expect(props.handleChange).toHaveBeenCalled(); + }); + + test('calls handleKeyDown on keydown', () => { + render(); + const input = screen.getByPlaceholderText('Title'); + fireEvent.keyDown(input, { target: { value: 'New title' } }); + expect(props.handleKeyDown).toHaveBeenCalled(); + }); + + test('calls updateTitle on blur', () => { + render(); + const input = screen.getByPlaceholderText('Title'); + fireEvent.blur(input); + expect(props.updateTitle).toHaveBeenCalled(); + }); + + test('calls inputRef if provided', () => { + const inputRef = jest.fn(); + render(); + expect(inputRef).toHaveBeenCalled(); + }); + + test('renders buttons from trailing element EditConfirmationButtons', () => { + render(); + const cancelButton = screen.getByRole('button', { name: /cancel/i }); + expect(cancelButton).toBeInTheDocument(); + const saveButton = screen.getByRole('button', { name: /save/i }); + expect(saveButton).toBeInTheDocument(); + }); +}); diff --git a/src/editors/containers/EditorContainer/components/TitleHeader/__snapshots__/EditableHeader.test.jsx.snap b/src/editors/containers/EditorContainer/components/TitleHeader/__snapshots__/EditableHeader.test.jsx.snap deleted file mode 100644 index 0d202841d..000000000 --- a/src/editors/containers/EditorContainer/components/TitleHeader/__snapshots__/EditableHeader.test.jsx.snap +++ /dev/null @@ -1,26 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EditableHeader snapshot snapshot 1`] = ` - - - } - value="test-title-text" - /> - -`; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintsCard.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintsCard.jsx index dc7a05907..2f941cd02 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintsCard.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintsCard.jsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { injectIntl, FormattedMessage, intlShape } from '@edx/frontend-platform/i18n'; +import { useIntl, FormattedMessage } from '@edx/frontend-platform/i18n'; import SettingsOption from '../SettingsOption'; import { ProblemTypeKeys } from '../../../../../../data/constants/problem'; import messages from '../messages'; @@ -15,9 +15,8 @@ const HintsCard = ({ images, isLibrary, learningContextId, - // inject - intl, }) => { + const intl = useIntl(); const { summary, handleAdd } = hintsCardHooks(hints, updateSettings); if (problemType === ProblemTypeKeys.ADVANCED) { return null; } @@ -55,7 +54,6 @@ const HintsCard = ({ }; HintsCard.propTypes = { - intl: intlShape.isRequired, hints: PropTypes.arrayOf(PropTypes.shape({ id: PropTypes.string.isRequired, value: PropTypes.string.isRequired, @@ -67,5 +65,4 @@ HintsCard.propTypes = { isLibrary: PropTypes.bool.isRequired, }; -export const HintsCardInternal = HintsCard; // For testing only -export default injectIntl(HintsCard); +export default HintsCard; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintsCard.test.jsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintsCard.test.jsx deleted file mode 100644 index 9dd5e06a4..000000000 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintsCard.test.jsx +++ /dev/null @@ -1,83 +0,0 @@ -import 'CourseAuthoring/editors/setupEditorTest'; -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; -import { formatMessage } from '../../../../../../testUtils'; -import { HintsCardInternal as HintsCard } from './HintsCard'; -import { hintsCardHooks, hintsRowHooks } from '../hooks'; -import messages from '../messages'; - -jest.mock('../hooks', () => ({ - hintsCardHooks: jest.fn(), - hintsRowHooks: jest.fn(), -})); - -describe('HintsCard', () => { - const hint1 = { id: 1, value: 'hint1' }; - const hint2 = { id: 2, value: '' }; - const hints0 = []; - const hints1 = [hint1]; - const hints2 = [hint1, hint2]; - const props = { - intl: { formatMessage }, - hints: hints0, - updateSettings: jest.fn().mockName('args.updateSettings'), - }; - - const hintsRowHooksProps = { - handleChange: jest.fn().mockName('hintsRowHooks.handleChange'), - handleDelete: jest.fn().mockName('hintsRowHooks.handleDelete'), - images: {}, - isLibrary: false, - learningContextId: 'course+org+run', - }; - hintsRowHooks.mockReturnValue(hintsRowHooksProps); - - describe('behavior', () => { - it(' calls hintsCardHooks when initialized', () => { - const hintsCardHooksProps = { - summary: { message: messages.noHintSummary, values: {} }, - handleAdd: jest.fn().mockName('hintsCardHooks.handleAdd'), - }; - - hintsCardHooks.mockReturnValue(hintsCardHooksProps); - shallow(); - expect(hintsCardHooks).toHaveBeenCalledWith(hints0, props.updateSettings); - }); - }); - - describe('snapshot', () => { - test('snapshot: renders hints setting card no hints', () => { - const hintsCardHooksProps = { - summary: { message: messages.noHintSummary, values: {} }, - handleAdd: jest.fn().mockName('hintsCardHooks.handleAdd'), - }; - - hintsCardHooks.mockReturnValue(hintsCardHooksProps); - expect(shallow().snapshot).toMatchSnapshot(); - }); - test('snapshot: renders hints setting card one hint', () => { - const hintsCardHooksProps = { - summary: { - message: messages.hintSummary, - values: { hint: hint1.value, count: 1 }, - }, - handleAdd: jest.fn().mockName('hintsCardHooks.handleAdd'), - }; - - hintsCardHooks.mockReturnValue(hintsCardHooksProps); - expect(shallow().snapshot).toMatchSnapshot(); - }); - test('snapshot: renders hints setting card multiple hints', () => { - const hintsCardHooksProps = { - summary: { - message: messages.hintSummary, - values: { hint: hint2.value, count: 2 }, - }, - handleAdd: jest.fn().mockName('hintsCardHooks.handleAdd'), - }; - - hintsCardHooks.mockReturnValue(hintsCardHooksProps); - expect(shallow().snapshot).toMatchSnapshot(); - }); - }); -}); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintsCard.test.tsx b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintsCard.test.tsx new file mode 100644 index 000000000..3d13b6a71 --- /dev/null +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/HintsCard.test.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { render, screen, initializeMocks } from '@src/testUtils'; +import HintsCard from './HintsCard'; + +jest.mock('./HintRow', () => 'HintRow'); + +describe('HintsCard', () => { + const hint1 = { id: '1', value: 'hint-1' }; + const hint2 = { id: '2', value: 'hint-2' }; + const hints0 = []; + const hints1 = [hint1]; + const hints2 = [hint1, hint2]; + + const props = { + hints: hints0, + updateSettings: jest.fn().mockName('args.updateSettings'), + problemType: 'multiplechoiceresponse', + images: {}, + isLibrary: false, + learningContextId: 'ID+', + }; + + beforeEach(() => { + initializeMocks(); + }); + + describe('HintsCard', () => { + test('renders component', () => { + render(); + expect(screen.getByText('Hints')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Add hint' })).toBeInTheDocument(); + }); + + test('does not render component when problemType is advanced', () => { + render(); + expect(screen.queryByText('Hints')).not.toBeInTheDocument(); + expect(screen.queryByText('button')).not.toBeInTheDocument(); + }); + + test('renders hints setting card one hint', () => { + render(); + expect(document.querySelector('hintrow[value="hint-1"]')).toBeInTheDocument(); + }); + + test('snapshot: renders hints setting card multiple hints', () => { + render(); + expect(document.querySelector('hintrow[value="hint-1"]')).toBeInTheDocument(); + expect(document.querySelector('hintrow[value="hint-2"]')).toBeInTheDocument(); + }); + }); +}); 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 b0b56add1..6b12e552a 100644 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ScoringCard.jsx +++ b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/ScoringCard.jsx @@ -2,7 +2,7 @@ import React from 'react'; import isNil from 'lodash/isNil'; import PropTypes from 'prop-types'; import { connect } from 'react-redux'; -import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import { Form, Hyperlink } from '@openedx/paragon'; import { selectors } from '../../../../../../data/redux'; import SettingsOption from '../SettingsOption'; @@ -13,13 +13,12 @@ const ScoringCard = ({ scoring, defaultValue, updateSettings, - // inject - intl, // redux studioEndpointUrl, learningContextId, isLibrary, }) => { + const intl = useIntl(); const { handleUnlimitedChange, handleMaxAttemptChange, @@ -93,7 +92,6 @@ const ScoringCard = ({ }; ScoringCard.propTypes = { - intl: intlShape.isRequired, // eslint-disable-next-line scoring: PropTypes.any.isRequired, updateSettings: PropTypes.func.isRequired, @@ -117,5 +115,4 @@ export const mapStateToProps = (state) => ({ export const mapDispatchToProps = {}; -export const ScoringCardInternal = ScoringCard; // For testing only -export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(ScoringCard)); +export default connect(mapStateToProps, mapDispatchToProps)(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 29d8e922c..e5f632a39 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,13 +1,11 @@ -import 'CourseAuthoring/editors/setupEditorTest'; import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; -import { formatMessage } from '../../../../../../testUtils'; -import { scoringCardHooks } from '../hooks'; -import { ScoringCardInternal as ScoringCard } from './ScoringCard'; +import { + render, screen, initializeMocks, fireEvent, +} from '@src/testUtils'; +import ScoringCard from './ScoringCard'; +import { selectors } from '../../../../../../data/redux'; -jest.mock('../hooks', () => ({ - scoringCardHooks: jest.fn(), -})); +const { app } = selectors; describe('ScoringCard', () => { const scoring = { @@ -17,55 +15,69 @@ describe('ScoringCard', () => { number: 5, }, updateSettings: jest.fn().mockName('args.updateSettings'), - intl: { formatMessage }, }; const props = { scoring, - intl: { formatMessage }, defaultValue: 1, + updateSettings: jest.fn(), }; - const scoringCardHooksProps = { - handleMaxAttemptChange: jest.fn().mockName('scoringCardHooks.handleMaxAttemptChange'), - handleWeightChange: jest.fn().mockName('scoringCardHooks.handleWeightChange'), - handleOnChange: jest.fn().mockName('scoringCardHooks.handleOnChange'), - local: 5, - }; - - scoringCardHooks.mockReturnValue(scoringCardHooksProps); - - describe('behavior', () => { - it(' calls scoringCardHooks when initialized', () => { - shallow(); - expect(scoringCardHooks).toHaveBeenCalledWith(scoring, props.updateSettings, props.defaultValue); - }); + beforeEach(() => { + jest.spyOn(app, 'studioEndpointUrl').mockReturnValue('studioEndpointUrl'); + jest.spyOn(app, 'learningContextId').mockReturnValue('learningContextId'); + jest.spyOn(app, 'isLibrary').mockReturnValue(false); + initializeMocks(); }); - describe('snapshot', () => { - test('snapshot: scoring setting card', () => { - expect(shallow().snapshot).toMatchSnapshot(); - }); - test('snapshot: scoring setting card zero zero weight', () => { - expect(shallow().snapshot).toMatchSnapshot(); - }); - test('snapshot: scoring setting card max attempts', () => { - expect(shallow().snapshot).toMatchSnapshot(); - }); + test('render the component', () => { + render(); + expect(screen.getByText('Scoring')).toBeInTheDocument(); + }); + + test('should not render advance settings link when isLibrary is true', () => { + jest.spyOn(app, 'isLibrary').mockReturnValue(true); + render(); + 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(); + 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(); + fireEvent.click(screen.getByText('Scoring')); + const pointsButton = screen.getByRole('spinbutton', { name: 'Points' }); + expect(pointsButton).toBeInTheDocument(); + expect(pointsButton.value).toBe('0'); + fireEvent.change(pointsButton, { target: { value: '0.1' } }); + expect(props.updateSettings).toHaveBeenCalled(); + }); + + test('should call updateSettings when clicking attempts button', () => { + const scoringUnlimited = { ...scoring, attempts: { unlimited: true, number: 0 } }; + render(); + fireEvent.click(screen.getByText('Scoring')); + fireEvent.click(screen.getByText('Attempts')); + const attemptsButton = screen.getByRole('spinbutton', { name: 'Points' }); + expect(attemptsButton).toBeInTheDocument(); + expect(attemptsButton.value).toBe('1.5'); + fireEvent.change(attemptsButton, { target: { value: '2' } }); + expect(props.updateSettings).toHaveBeenCalled(); + }); + + test('should display checked checkbox when unlimited is true', () => { + const scoringUnlimited = { ...scoring, attempts: { unlimited: true, number: 0 } }; + render(); + fireEvent.click(screen.getByText('Scoring')); + const checkbox = screen.getByRole('checkbox', { name: 'Unlimited attempts' }); + expect(checkbox).toBeChecked(); + fireEvent.click(checkbox); + expect(props.updateSettings).toHaveBeenCalled(); }); }); diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/__snapshots__/HintsCard.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/__snapshots__/HintsCard.test.jsx.snap deleted file mode 100644 index 64eff3fdc..000000000 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/__snapshots__/HintsCard.test.jsx.snap +++ /dev/null @@ -1,97 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`HintsCard snapshot snapshot: renders hints setting card multiple hints 1`] = ` - - - - - -`; - -exports[`HintsCard snapshot snapshot: renders hints setting card no hints 1`] = ` - - - -`; - -exports[`HintsCard snapshot snapshot: renders hints setting card one hint 1`] = ` - - - - -`; diff --git a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/__snapshots__/ScoringCard.test.jsx.snap b/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/__snapshots__/ScoringCard.test.jsx.snap deleted file mode 100644 index 9f07b02d0..000000000 --- a/src/editors/containers/ProblemEditor/components/EditProblemView/SettingsWidget/settingsComponents/__snapshots__/ScoringCard.test.jsx.snap +++ /dev/null @@ -1,238 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ScoringCard snapshot snapshot: scoring setting card 1`] = ` - -
- -
- - - - - - - - - - - - -
- -
-
-
- - - -
-`; - -exports[`ScoringCard snapshot snapshot: scoring setting card max attempts 1`] = ` - -
- -
- - - - - - - - - - - - -
- -
-
-
- - - -
-`; - -exports[`ScoringCard snapshot snapshot: scoring setting card zero zero weight 1`] = ` - -
- -
- - - - - - - - - - - - -
- -
-
-
- - - -
-`; diff --git a/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/AltTextControls.jsx b/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/AltTextControls.jsx index 5b06c6f8f..e4ece2222 100644 --- a/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/AltTextControls.jsx +++ b/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/AltTextControls.jsx @@ -2,7 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import { Form } from '@openedx/paragon'; -import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import * as hooks from './hooks'; import messages from './messages'; @@ -22,39 +22,40 @@ const AltTextControls = ({ setValue, validation, value, - // inject - intl, -}) => ( - - - - - - {validation.show +}) => { + const intl = useIntl(); + return ( + + + + + + {validation.show && ( )} - - - - - - -); + + + + + + + ); +}; AltTextControls.propTypes = { error: PropTypes.shape({ show: PropTypes.bool, @@ -66,9 +67,7 @@ AltTextControls.propTypes = { show: PropTypes.bool, }).isRequired, value: PropTypes.string.isRequired, - // inject - intl: intlShape.isRequired, }; export const AltTextControlsInternal = AltTextControls; // For testing only -export default injectIntl(AltTextControls); +export default AltTextControls; diff --git a/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/AltTextControls.test.jsx b/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/AltTextControls.test.jsx deleted file mode 100644 index 36780669d..000000000 --- a/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/AltTextControls.test.jsx +++ /dev/null @@ -1,34 +0,0 @@ -import 'CourseAuthoring/editors/setupEditorTest'; -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; - -import { formatMessage } from '../../../testUtils'; -import { AltTextControlsInternal as AltTextControls } from './AltTextControls'; - -jest.mock('./hooks', () => ({ - onInputChange: (handler) => ({ 'hooks.onInputChange': handler }), - onCheckboxChange: (handler) => ({ 'hooks.onCheckboxChange': handler }), -})); - -describe('AltTextControls', () => { - const props = { - isDecorative: true, - value: 'props.value', - // inject - intl: { formatMessage }, - }; - beforeEach(() => { - props.setValue = jest.fn().mockName('props.setValue'); - props.setIsDecorative = jest.fn().mockName('props.setIsDecorative'); - props.validation = { show: true }; - }); - describe('render', () => { - test('snapshot: isDecorative=true errorProps.showAltTextSubmissionError=true', () => { - expect(shallow().snapshot).toMatchSnapshot(); - }); - test('snapshot: isDecorative=true errorProps.showAltTextSubmissionError=false', () => { - props.validation.show = false; - expect(shallow().snapshot).toMatchSnapshot(); - }); - }); -}); diff --git a/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/AltTextControls.test.tsx b/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/AltTextControls.test.tsx new file mode 100644 index 000000000..4c2d2b882 --- /dev/null +++ b/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/AltTextControls.test.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import { + render, screen, initializeMocks, fireEvent, +} from '@src/testUtils'; +import AltTextControls from './AltTextControls'; + +describe('AltTextControls', () => { + const props = { + isDecorative: true, + value: 'props.value', + setValue: jest.fn().mockName('props.setValue'), + setIsDecorative: jest.fn().mockName('props.setIsDecorative'), + validation: { show: false }, + error: { show: false }, + }; + + beforeEach(() => { + initializeMocks(); + }); + + test('renders component on screen', () => { + render(); + expect(screen.getByRole('checkbox', { name: 'This image is decorative (no alt text required).' })).toBeInTheDocument(); + expect(screen.getByRole('textbox', { name: 'Accessibility' })).toBeInTheDocument(); + }); + + test('renders validation feedback when validation.show is true', () => { + const feedbackMessage = 'Enter alt text'; + render(); + expect(screen.getByText(feedbackMessage)).toBeInTheDocument(); + }); + + test('does not render validation feedback when validation.show is false', () => { + const feedbackMessage = 'Enter alt text'; + render(); + expect(screen.queryByText(feedbackMessage)).not.toBeInTheDocument(); + }); + + test('disables textbox when isDecorative is true', () => { + render(); + expect(screen.getByRole('textbox', { name: 'Accessibility' })).toBeDisabled(); + }); + + test('enables textbox when isDecorative is false', () => { + render(); + expect(screen.getByRole('textbox', { name: 'Accessibility' })).not.toBeDisabled(); + }); + + test('calls setValue on textbox change', () => { + render(); + const textbox = screen.getByRole('textbox', { name: 'Accessibility' }); + fireEvent.change(textbox, { target: { value: 'new alt text' } }); + expect(props.setValue).toHaveBeenCalled(); + }); + + test('calls setIsDecorative on checkbox change', () => { + render(); + const checkbox = screen.getByRole('checkbox', { name: 'This image is decorative (no alt text required).' }); + checkbox.click(); + expect(props.setIsDecorative).toHaveBeenCalled(); + }); +}); diff --git a/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/DimensionControls.jsx b/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/DimensionControls.jsx index c05ec4fae..c9b56e07e 100644 --- a/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/DimensionControls.jsx +++ b/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/DimensionControls.jsx @@ -9,7 +9,7 @@ import { Locked, Unlocked, } from '@openedx/paragon/icons'; -import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { FormattedMessage, useIntl } from '@edx/frontend-platform/i18n'; import * as hooks from './hooks'; import messages from './messages'; @@ -32,42 +32,44 @@ const DimensionControls = ({ unlock, updateDimensions, value, - // inject - intl, -}) => ((value !== null) && ( - - - - -
- - - { + const intl = useIntl(); + if (!value) { return null; } + return ( + + + + +
+ + + -
-
-)); + iconAs={Icon} + src={isLocked ? Locked : Unlocked} + onClick={isLocked ? unlock : lock} + /> +
+
+ ); +}; DimensionControls.defaultProps = { value: { height: '100', @@ -85,9 +87,6 @@ DimensionControls.propTypes = ({ lock: PropTypes.func.isRequired, unlock: PropTypes.func.isRequired, updateDimensions: PropTypes.func.isRequired, - // inject - intl: intlShape.isRequired, }); -export const DimensionControlsInternal = DimensionControls; // For testing only -export default injectIntl(DimensionControls); +export default DimensionControls; diff --git a/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/DimensionControls.test.jsx b/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/DimensionControls.test.jsx deleted file mode 100644 index 0cf9a6d27..000000000 --- a/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/DimensionControls.test.jsx +++ /dev/null @@ -1,141 +0,0 @@ -import 'CourseAuthoring/editors/setupEditorTest'; -import React, { useEffect } from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; -import * as paragon from '@openedx/paragon'; -import * as icons from '@openedx/paragon/icons'; - -import { - fireEvent, render, screen, waitFor, -} from '@testing-library/react'; -import { formatMessage } from '../../../testUtils'; -import { DimensionControlsInternal as DimensionControls } from './DimensionControls'; -import * as hooks from './hooks'; - -const WrappedDimensionControls = () => { - const dimensions = hooks.dimensionHooks('altText'); - - useEffect(() => { - dimensions.onImgLoad({ })({ target: { naturalWidth: 1517, naturalHeight: 803 } }); - }, []); - - return ; -}; - -const UnlockedDimensionControls = () => { - const dimensions = hooks.dimensionHooks('altText'); - - useEffect(() => { - dimensions.onImgLoad({ })({ target: { naturalWidth: 1517, naturalHeight: 803 } }); - dimensions.unlock(); - }, []); - - return ; -}; - -describe('DimensionControls', () => { - describe('render', () => { - const props = { - lockAspectRatio: { width: 4, height: 5 }, - locked: { 'props.locked': 'lockedValue' }, - isLocked: true, - value: { width: 20, height: 40 }, - // inject - intl: { formatMessage }, - }; - beforeEach(() => { - jest.spyOn(hooks, 'onInputChange').mockImplementation((handler) => ({ 'hooks.onInputChange': handler })); - props.setWidth = jest.fn().mockName('props.setWidth'); - props.setHeight = jest.fn().mockName('props.setHeight'); - props.lock = jest.fn().mockName('props.lock'); - props.unlock = jest.fn().mockName('props.unlock'); - props.updateDimensions = jest.fn().mockName('props.updateDimensions'); - }); - afterEach(() => { - jest.spyOn(hooks, 'onInputChange').mockRestore(); - }); - test('snapshot', () => { - expect(shallow().snapshot).toMatchSnapshot(); - }); - test('null value: empty snapshot', () => { - const el = shallow(); - expect(el.snapshot).toMatchSnapshot(); - expect(el.isEmptyRender()).toEqual(true); - }); - test('unlocked dimensions', () => { - const el = shallow(); - expect(el.snapshot).toMatchSnapshot(); - }); - }); - describe('component tests for dimensions', () => { - beforeEach(() => { - paragon.Form.Group = jest.fn().mockImplementation(({ children }) => ( -
{children}
- )); - paragon.Form.Label = jest.fn().mockImplementation(({ children }) => ( -
{children}
- )); - // eslint-disable-next-line no-import-assign - paragon.Icon = jest.fn().mockImplementation(({ children }) => ( -
{children}
- )); - // eslint-disable-next-line no-import-assign - paragon.IconButton = jest.fn().mockImplementation(({ children }) => ( -
{children}
- )); - paragon.Form.Control = jest.fn().mockImplementation(({ value, onChange, onBlur }) => ( - - )); - // eslint-disable-next-line no-import-assign - icons.Locked = jest.fn().mockImplementation(() => {}); - // eslint-disable-next-line no-import-assign - icons.Unlocked = jest.fn().mockImplementation(() => {}); - }); - afterEach(() => { - paragon.Form.Group.mockRestore(); - paragon.Form.Label.mockRestore(); - paragon.Form.Control.mockRestore(); - paragon.Icon.mockRestore(); - paragon.IconButton.mockRestore(); - icons.Locked.mockRestore(); - icons.Unlocked.mockRestore(); - }); - - it('renders with initial dimensions', () => { - const { container } = render(); - const widthInput = container.querySelector('.formControl'); - expect(widthInput.value).toBe('1517'); - }); - - it('resizes dimensions proportionally', async () => { - const { container } = render(); - const widthInput = container.querySelector('.formControl'); - expect(widthInput.value).toBe('1517'); - fireEvent.change(widthInput, { target: { value: 758 } }); - await waitFor(() => { - expect(container.querySelectorAll('.formControl')[0].value).toBe('758'); - }); - fireEvent.blur(widthInput); - await waitFor(() => { - expect(container.querySelectorAll('.formControl')[0].value).toBe('758'); - expect(container.querySelectorAll('.formControl')[1].value).toBe('401'); - }); - screen.debug(); - }); - - it('resizes only changed dimension when unlocked', async () => { - const { container } = render(); - const widthInput = container.querySelector('.formControl'); - expect(widthInput.value).toBe('1517'); - fireEvent.change(widthInput, { target: { value: 758 } }); - await waitFor(() => { - expect(container.querySelectorAll('.formControl')[0].value).toBe('758'); - }); - fireEvent.blur(widthInput); - await waitFor(() => { - expect(container.querySelectorAll('.formControl')[0].value).toBe('758'); - expect(container.querySelectorAll('.formControl')[1].value).toBe('803'); - }); - screen.debug(); - }); - }); -}); diff --git a/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/DimensionControls.test.tsx b/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/DimensionControls.test.tsx new file mode 100644 index 000000000..288e847ae --- /dev/null +++ b/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/DimensionControls.test.tsx @@ -0,0 +1,111 @@ +import React, { useEffect } from 'react'; +import { + fireEvent, render, screen, waitFor, initializeMocks, +} from '@src/testUtils'; +import DimensionControls from './DimensionControls'; +import * as hooks from './hooks'; + +const WrappedDimensionControls = () => { + const dimensions = hooks.dimensionHooks('altText'); + + useEffect(() => { + dimensions.onImgLoad({ })({ target: { naturalWidth: 1517, naturalHeight: 803 } }); + }, []); + + return ; +}; + +const UnlockedDimensionControls = () => { + const dimensions = hooks.dimensionHooks('altText'); + + useEffect(() => { + dimensions.onImgLoad({ })({ target: { naturalWidth: 1517, naturalHeight: 803 } }); + dimensions.unlock(); + }, []); + + return ; +}; + +describe('DimensionControls', () => { + describe('render', () => { + const props = { + lockAspectRatio: { width: 4, height: 5 }, + locked: { 'props.locked': 'lockedValue' }, + isLocked: true, + value: { width: '20', height: '40' }, + setWidth: jest.fn(), + setHeight: jest.fn(), + lock: jest.fn(), + unlock: jest.fn(), + updateDimensions: jest.fn(), + }; + beforeEach(() => { + jest.spyOn(hooks, 'onInputChange').mockImplementation((handler) => ({ 'hooks.onInputChange': handler })); + initializeMocks(); + }); + afterEach(() => { + jest.spyOn(hooks, 'onInputChange').mockRestore(); + }); + test('renders component', () => { + render(); + expect(screen.getByText('Image Dimensions')).toBeInTheDocument(); + }); + test('renders nothing with null value', () => { + const reduxProviderWrapper = '
'; + const { container } = render(); + expect(screen.queryByText('Image Dimensions')).not.toBeInTheDocument(); + expect(container.innerHTML).toBe(reduxProviderWrapper); + expect(container.firstChild?.textContent).toBe(''); + }); + + test('renders locked and unlocked icon button according to isLocked prop', () => { + const { rerender } = render(); + expect(screen.getByRole('button', { name: 'lock dimensions' })).toBeInTheDocument(); + rerender(); + expect(screen.getByRole('button', { name: 'unlock dimensions' })).toBeInTheDocument(); + }); + }); + + describe('component tests for dimensions', () => { + beforeEach(() => { + initializeMocks(); + }); + + it('renders with initial dimensions', () => { + const { container } = render(); + const widthInput = container.querySelector('input.form-control'); + expect(widthInput).not.toBeNull(); + expect((widthInput as HTMLInputElement).value).toBe('1517'); + }); + + it('resizes dimensions proportionally', async () => { + const { container } = render(); + const widthInput = container.querySelector('input.form-control') as HTMLInputElement; + expect((widthInput as HTMLInputElement).value).toBe('1517'); + fireEvent.change(widthInput, { target: { value: 758 } }); + await waitFor(() => { + expect((container.querySelectorAll('input.form-control')[0] as HTMLInputElement).value).toBe('758'); + }); + fireEvent.blur(widthInput); + await waitFor(() => { + expect((container.querySelectorAll('input.form-control')[0] as HTMLInputElement).value).toBe('758'); + expect((container.querySelectorAll('input.form-control')[1] as HTMLInputElement).value).toBe('401'); + }); + }); + + it('resizes only changed dimension when unlocked', async () => { + const { container } = render(); + const widthInput = container.querySelector('input.form-control') as HTMLInputElement; + expect(widthInput.value).toBe('1517'); + fireEvent.change(widthInput, { target: { value: 758 } }); + await waitFor(() => { + expect((container.querySelectorAll('input.form-control')[0] as HTMLInputElement).value).toBe('758'); + }); + fireEvent.blur(widthInput); + await waitFor(() => { + expect((container.querySelectorAll('input.form-control')[0] as HTMLInputElement).value).toBe('758'); + expect((container.querySelectorAll('input.form-control')[1] as HTMLInputElement).value).toBe('803'); + }); + }); + }); +}); diff --git a/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/__snapshots__/AltTextControls.test.jsx.snap b/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/__snapshots__/AltTextControls.test.jsx.snap deleted file mode 100644 index 7e0b029a8..000000000 --- a/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/__snapshots__/AltTextControls.test.jsx.snap +++ /dev/null @@ -1,102 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AltTextControls render snapshot: isDecorative=true errorProps.showAltTextSubmissionError=false 1`] = ` - - - - - - - - - - - -`; - -exports[`AltTextControls render snapshot: isDecorative=true errorProps.showAltTextSubmissionError=true 1`] = ` - - - - - - - - - - - - - - -`; diff --git a/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/__snapshots__/DimensionControls.test.jsx.snap b/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/__snapshots__/DimensionControls.test.jsx.snap deleted file mode 100644 index 6f1bf0cdc..000000000 --- a/src/editors/sharedComponents/ImageUploadModal/ImageSettingsModal/__snapshots__/DimensionControls.test.jsx.snap +++ /dev/null @@ -1,97 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DimensionControls render null value: empty snapshot 1`] = `false`; - -exports[`DimensionControls render snapshot 1`] = ` - - - - -
- - - -
-
-`; - -exports[`DimensionControls render unlocked dimensions 1`] = ` - - - - -
- - - -
-
-`;