diff --git a/jest.config.js b/jest.config.js index 141b70a4c..51aefb26e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -11,6 +11,9 @@ module.exports = createConfig('jest', { ], moduleNameMapper: { '^lodash-es$': 'lodash', + // This alias is for any code in the src directory that wants to avoid '../../' style relative imports: + '^@src/(.*)$': '/src/$1', + // This alias is used for plugins in the plugins/ folder only. '^CourseAuthoring/(.*)$': '/src/$1', }, modulePathIgnorePatterns: [ diff --git a/src/editors/EditorContainer.test.jsx b/src/editors/EditorContainer.test.jsx deleted file mode 100644 index a50718eb9..000000000 --- a/src/editors/EditorContainer.test.jsx +++ /dev/null @@ -1,46 +0,0 @@ -// @ts-check -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; -import EditorContainer from './EditorContainer'; -import { mockWaffleFlags } from '../data/apiHooks.mock'; - -mockWaffleFlags(); - -const mockPathname = '/editor/'; -jest.mock('react-router-dom', () => ({ - ...jest.requireActual('react-router-dom'), // use actual for all non-hook parts - useParams: () => ({ - blockId: 'company-id1', - blockType: 'html', - }), - useLocation: () => ({ - pathname: mockPathname, - }), - useSearchParams: () => [{ - get: () => 'lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd', - }], -})); - -jest.mock('@edx/frontend-platform/i18n', () => ({ - ...jest.requireActual('@edx/frontend-platform/i18n'), - useIntl: () => ({ - formatMessage: (message) => message.defaultMessage, - }), -})); - -jest.mock('react-redux', () => ({ - ...jest.requireActual('react-redux'), - useSelector: () => ({ - useReactMarkdownEditor: true, // or false depending on the test - }), -})); - -const props = { learningContextId: 'cOuRsEId' }; - -describe('Editor Container', () => { - describe('snapshots', () => { - test('rendering correctly with expected Input', () => { - expect(shallow().snapshot).toMatchSnapshot(); - }); - }); -}); diff --git a/src/editors/EditorContainer.test.tsx b/src/editors/EditorContainer.test.tsx new file mode 100644 index 000000000..d4d6d8371 --- /dev/null +++ b/src/editors/EditorContainer.test.tsx @@ -0,0 +1,94 @@ +import React from 'react'; +import { getConfig } from '@edx/frontend-platform'; +import { + render, screen, initializeMocks, fireEvent, act, +} from '@src/testUtils'; +import EditorContainer from './EditorContainer'; +import { mockWaffleFlags } from '../data/apiHooks.mock'; +import editorCmsApi from './data/services/cms/api'; + +mockWaffleFlags(); + +const mockPathname = '/editor/'; +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useParams: () => ({ + blockId: 'block-v1:Org+TS100+24+type@fake+block@123456fake', + blockType: 'fake', + }), + useLocation: () => ({ + pathname: mockPathname, + }), + useSearchParams: () => [{ + get: () => 'lb:Axim:TEST:html:571fe018-f3ce-45c9-8f53-5dafcb422fdd', + }], +})); + +jest.mock('react-redux', () => ({ + ...jest.requireActual('react-redux'), + useSelector: () => ({ + useReactMarkdownEditor: true, // or false depending on the test + }), +})); + +// Mock this plugins component: +jest.mock('frontend-components-tinymce-advanced-plugins', () => ({ a11ycheckerCss: '' })); +// Always mock out the "fetch course images" endpoint: +jest.spyOn(editorCmsApi, 'fetchCourseImages').mockImplementation(async () => ( // eslint-disable-next-line + { data: { assets: [], start: 0, end: 0, page: 0, pageSize: 50, totalCount: 0 } } +)); +// Mock out the 'get ancestors' API: +jest.spyOn(editorCmsApi, 'fetchByUnitId').mockImplementation(async () => ({ + status: 200, + data: { + ancestors: [{ + id: 'block-v1:Org+TS100+24+type@vertical+block@parent', + display_name: 'You-Knit? The Test Unit', + category: 'vertical', + has_children: true, + }], + }, +})); +jest.mock('../library-authoring/LibraryBlock', () => ({ + LibraryBlock: jest.fn(() => (
Advanced Editor Iframe
)), +})); + +const props = { learningContextId: 'cOuRsEId' }; + +describe('EditorContainer', () => { + beforeEach(() => { + initializeMocks(); + jest.spyOn(editorCmsApi, 'fetchBlockById').mockImplementationOnce(async () => ( + { + status: 200, + data: { + display_name: 'Fake Un-editable Block', category: 'fake', metadata: {}, data: '', + }, + } + )); + }); + + test('render component', () => { + render(); + expect(screen.getByText('View in Library')).toBeInTheDocument(); + expect(screen.getByText('Advanced Editor Iframe')).toBeInTheDocument(); + }); + + test('should call onClose param when receiving "cancel-clicked" message', () => { + const onCloseMock = jest.fn(); + render(); + const messageEvent = new MessageEvent('message', { + data: { + type: 'xblock-event', + eventName: 'cancel', + }, + origin: getConfig().STUDIO_BASE_URL, + }); + + act(() => { + window.dispatchEvent(messageEvent); + }); + fireEvent.click(screen.getByRole('button', { name: 'Discard Changes and Exit' })); + expect(onCloseMock).toHaveBeenCalled(); + }); +}); diff --git a/src/editors/__snapshots__/EditorContainer.test.jsx.snap b/src/editors/__snapshots__/EditorContainer.test.jsx.snap deleted file mode 100644 index e1654a177..000000000 --- a/src/editors/__snapshots__/EditorContainer.test.jsx.snap +++ /dev/null @@ -1,69 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Editor Container snapshots rendering correctly with expected Input 1`] = ` -
- - View in Library - , - ] - } - className="m-3" - description="Edits made here will only be reflected in this course. These edits may be overridden later if updates are accepted." - icon={[Function]} - show={true} - title="Editing Content from a Library" - variant="warning" - /> - -
-`; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/CollapsibleFormWidget.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/CollapsibleFormWidget.jsx index 954519542..3bdf406e9 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/CollapsibleFormWidget.jsx +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/CollapsibleFormWidget.jsx @@ -1,7 +1,7 @@ import React from 'react'; import PropTypes from 'prop-types'; -import { injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { useIntl } from '@edx/frontend-platform/i18n'; import { Collapsible, Icon, IconButton } from '@openedx/paragon'; import { ExpandLess, ExpandMore, InfoOutline } from '@openedx/paragon/icons'; @@ -20,40 +20,41 @@ const CollapsibleFormWidget = ({ subtitle, title, fontSize, - // injected - intl, -}) => ( - - { + const intl = useIntl(); + return ( + - -
+ + +
+
{title}
+ {subtitle ?
{subtitle}
:
} +
+
+ {isError && } + +
+ +
{title}
- {subtitle ?
{subtitle}
:
} -
-
- {isError && } - -
-
- -
{title}
-
- -
-
- - - {children} - - -); +
+ +
+ + + + {children} + + + ); +}; CollapsibleFormWidget.defaultProps = { subtitle: null, @@ -66,9 +67,6 @@ CollapsibleFormWidget.propTypes = { subtitle: PropTypes.node, title: PropTypes.node.isRequired, fontSize: PropTypes.string, - // injected - intl: intlShape.isRequired, }; -export const CollapsibleFormWidgetInternal = CollapsibleFormWidget; // For testing only -export default injectIntl(CollapsibleFormWidget); +export default CollapsibleFormWidget; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/CollapsibleFormWidget.test.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/CollapsibleFormWidget.test.jsx deleted file mode 100644 index 6cbd4d5b4..000000000 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/CollapsibleFormWidget.test.jsx +++ /dev/null @@ -1,29 +0,0 @@ -import 'CourseAuthoring/editors/setupEditorTest'; -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; - -import { formatMessage } from '../../../../../testUtils'; -import { CollapsibleFormWidgetInternal as CollapsibleFormWidget } from './CollapsibleFormWidget'; - -describe('CollapsibleFormWidget', () => { - const props = { - isError: false, - subtitle: 'SuBTItle', - title: 'tiTLE', - // inject - intl: { formatMessage }, - }; - describe('render', () => { - const testContent = (

Some test string

); - test('snapshots: renders as expected with default props', () => { - expect( - shallow({testContent}).snapshot, - ).toMatchSnapshot(); - }); - test('snapshots: renders with open={true} when there is error', () => { - expect( - shallow({testContent}).snapshot, - ).toMatchSnapshot(); - }); - }); -}); diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/CollapsibleFormWidget.test.tsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/CollapsibleFormWidget.test.tsx new file mode 100644 index 000000000..6e3bcdb44 --- /dev/null +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/CollapsibleFormWidget.test.tsx @@ -0,0 +1,25 @@ +import React from 'react'; +import { + render, screen, initializeMocks, +} from '@src/testUtils'; +import CollapsibleFormWidget from './CollapsibleFormWidget'; + +describe('CollapsibleFormWidget', () => { + const props = { + isError: false, + subtitle: 'Sample subtitle', + title: 'Sample title', + fontSize: 'x-small', + }; + const testContent = (

Some test string

); + + beforeEach(() => { + initializeMocks(); + }); + + test('renders component', () => { + render({testContent}); + expect(screen.getByText('Sample title')).toBeInTheDocument(); + expect(screen.getByText('Some test string')).toBeInTheDocument(); + }); +}); diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/DurationWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/DurationWidget/__snapshots__/index.test.jsx.snap index 1455c89bd..c45dbf05a 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/DurationWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/DurationWidget/__snapshots__/index.test.jsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`DurationWidget render snapshots: renders as expected with default props 1`] = ` -
- + `; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/HandoutWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/HandoutWidget/__snapshots__/index.test.jsx.snap index 056123b55..129fe2e18 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/HandoutWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/HandoutWidget/__snapshots__/index.test.jsx.snap @@ -1,7 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`HandoutWidget snapshots snapshots: renders as expected with default props 1`] = ` - - + `; exports[`HandoutWidget snapshots snapshots: renders as expected with handout 1`] = ` - - + `; exports[`HandoutWidget snapshots snapshots: renders as expected with isLibrary true 1`] = `null`; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/LicenseSelector.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/LicenseSelector.jsx index 7e468982b..7e21f5df3 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/LicenseSelector.jsx +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/LicenseSelector.jsx @@ -3,8 +3,7 @@ import { connect, useDispatch } from 'react-redux'; import PropTypes from 'prop-types'; import { FormattedMessage, - injectIntl, - intlShape, + useIntl, } from '@edx/frontend-platform/i18n'; import { ActionRow, @@ -22,12 +21,11 @@ import { LicenseLevel, LicenseNames, LicenseTypes } from '../../../../../../data const LicenseSelector = ({ license, level, - // injected - intl, // redux courseLicenseType, updateField, }) => { + const intl = useIntl(); const { levelDescription } = hooks.determineText({ level }); const onLicenseChange = hooks.onSelectLicense({ dispatch: useDispatch() }); const ref = React.useRef(); @@ -74,8 +72,6 @@ const LicenseSelector = ({ LicenseSelector.propTypes = { license: PropTypes.string.isRequired, level: PropTypes.string.isRequired, - // injected - intl: intlShape.isRequired, // redux courseLicenseType: PropTypes.string.isRequired, updateField: PropTypes.func.isRequired, @@ -90,4 +86,4 @@ export const mapDispatchToProps = (dispatch) => ({ }); export const LicenseSelectorInternal = LicenseSelector; // For testing only -export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(LicenseSelector)); +export default connect(mapStateToProps, mapDispatchToProps)(LicenseSelector); diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/LicenseSelector.test.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/LicenseSelector.test.jsx deleted file mode 100644 index 2b6ee9a0e..000000000 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/LicenseSelector.test.jsx +++ /dev/null @@ -1,84 +0,0 @@ -import 'CourseAuthoring/editors/setupEditorTest'; -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; - -import { formatMessage } from '../../../../../../testUtils'; -import { actions, selectors } from '../../../../../../data/redux'; -import { LicenseSelectorInternal as LicenseSelector, mapStateToProps, mapDispatchToProps } from './LicenseSelector'; - -jest.mock('react', () => { - const updateState = jest.fn(); - return { - ...jest.requireActual('react'), - updateState, - useContext: jest.fn(() => ({ license: ['error.license', jest.fn().mockName('error.setLicense')] })), - }; -}); - -jest.mock('react-redux', () => { - const dispatchFn = jest.fn(); - return { - ...jest.requireActual('react-redux'), - dispatch: dispatchFn, - useDispatch: jest.fn(() => dispatchFn), - }; -}); - -jest.mock('../../../../../../data/redux', () => ({ - actions: { - video: { - updateField: jest.fn().mockName('actions.video.updateField'), - }, - }, - selectors: { - video: { - courseLicenseType: jest.fn(state => ({ courseLicenseType: state })), - }, - }, -})); - -describe('LicenseSelector', () => { - const props = { - intl: { formatMessage }, - license: 'all-rights-reserved', - level: 'course', - courseLicenseType: 'all-rights-reserved', - updateField: jest.fn().mockName('args.updateField'), - }; - describe('snapshots', () => { - test('snapshots: renders as expected with default props', () => { - expect( - shallow().snapshot, - ).toMatchSnapshot(); - }); - test('snapshots: renders as expected with library level', () => { - expect( - shallow().snapshot, - ).toMatchSnapshot(); - }); - test('snapshots: renders as expected with block level', () => { - expect( - shallow().snapshot, - ).toMatchSnapshot(); - }); - test('snapshots: renders as expected with no license', () => { - expect( - shallow().snapshot, - ).toMatchSnapshot(); - }); - }); - describe('mapStateToProps', () => { - const testState = { A: 'pple', B: 'anana', C: 'ucumber' }; - test('courseLicenseType from video.courseLicenseType', () => { - expect( - mapStateToProps(testState).courseLicenseType, - ).toEqual(selectors.video.courseLicenseType(testState)); - }); - }); - describe('mapDispatchToProps', () => { - const dispatch = jest.fn(); - test('updateField from actions.video.updateField', () => { - expect(mapDispatchToProps.updateField).toEqual(dispatch(actions.video.updateField)); - }); - }); -}); diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/LicenseSelector.test.tsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/LicenseSelector.test.tsx new file mode 100644 index 000000000..b85057bb3 --- /dev/null +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/LicenseSelector.test.tsx @@ -0,0 +1,86 @@ +import React from 'react'; +import { + render, fireEvent, screen, initializeMocks, +} from '@src/testUtils'; +import { LicenseSelectorInternal } from './LicenseSelector'; +import * as hooks from './hooks'; + +jest.mock('./hooks', () => ({ + determineText: jest.fn(() => ({ levelDescription: 'Test level description' })), + onSelectLicense: jest.fn(() => jest.fn()), +})); + +const LicenseTypes = { + select: 'select', + allRightsReserved: 'All Rights Reserved', + creativeCommons: 'Creative Commons', +}; +const LicenseLevel = { course: 'course', video: 'video' }; + +describe('LicenseSelectorInternal', () => { + const updateField = jest.fn(); + const onLicenseChange = jest.fn(); + const props = { + license: LicenseTypes.select, + level: LicenseLevel.video, + courseLicenseType: LicenseTypes.select, + updateField, + }; + + beforeEach(() => { + initializeMocks(); + (hooks.onSelectLicense as jest.Mock).mockReturnValue(onLicenseChange); + (hooks.determineText as jest.Mock).mockReturnValue({ levelDescription: 'Test level description' }); + }); + + it('renders select with correct options and default value', () => { + render(); + const select = screen.getByRole('combobox'); + expect(select).toBeInTheDocument(); + expect((select as HTMLSelectElement).value).toBe(props.license); + expect(screen.getByText(LicenseTypes.allRightsReserved)).toBeInTheDocument(); + expect(screen.getByText(LicenseTypes.creativeCommons)).toBeInTheDocument(); + }); + + it('disables select when level is course', () => { + render(); + expect(screen.getByRole('combobox')).toBeDisabled(); + }); + + it('shows delete button when level is not course', () => { + render(); + expect(screen.getByRole('button')).toBeInTheDocument(); + }); + + it('does not show delete button when level is course', () => { + render(); + expect(screen.queryByRole('button')).not.toBeInTheDocument(); + }); + + it('calls onLicenseChange when select changes', () => { + render(); + fireEvent.change(screen.getByRole('combobox'), { target: { value: LicenseTypes.select } }); + expect(onLicenseChange).toHaveBeenCalledWith(LicenseTypes.select); + }); + + it('calls updateField and resets select when delete button clicked', () => { + render(); + fireEvent.click(screen.getByRole('button')); + expect(updateField).toHaveBeenCalledWith({ licenseType: '', licenseDetails: {} }); + }); + + it('renders level description', () => { + render(); + expect(screen.getByText('Test level description')).toBeInTheDocument(); + }); + + it('renders border when license is not select', () => { + const { container } = render(); + expect(container.querySelector('.border-primary-100')).toBeInTheDocument(); + }); + + it('does not render border when license is select', () => { + const { container } = render(); + expect(container.querySelector('.border-primary-100')).not.toBeInTheDocument(); + }); +}); diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/__snapshots__/LicenseSelector.test.jsx.snap b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/__snapshots__/LicenseSelector.test.jsx.snap deleted file mode 100644 index 558ffc81d..000000000 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/__snapshots__/LicenseSelector.test.jsx.snap +++ /dev/null @@ -1,203 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`LicenseSelector snapshots snapshots: renders as expected with block level 1`] = ` - - - - - - - - - - - } - tooltipPlacement="top" - /> - - -
- -
-
- -`; - -exports[`LicenseSelector snapshots snapshots: renders as expected with default props 1`] = ` - - - - - - - - -
- -
-
- -`; - -exports[`LicenseSelector snapshots snapshots: renders as expected with library level 1`] = ` - - - - - - - - - - - } - tooltipPlacement="top" - /> - - -
- -
-
- -`; - -exports[`LicenseSelector snapshots snapshots: renders as expected with no license 1`] = ` - - - - - - - - -
- -
-
- -`; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/__snapshots__/index.test.jsx.snap index d63bc7705..55c2822c6 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/LicenseWidget/__snapshots__/index.test.jsx.snap @@ -1,7 +1,8 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`LicenseWidget snapshots snapshots: renders as expected with default props 1`] = ` - - @@ -64,11 +65,12 @@ exports[`LicenseWidget snapshots snapshots: renders as expected with default pro - + `; exports[`LicenseWidget snapshots snapshots: renders as expected with isLibrary true 1`] = ` - - @@ -114,11 +116,12 @@ exports[`LicenseWidget snapshots snapshots: renders as expected with isLibrary t /> - + `; exports[`LicenseWidget snapshots snapshots: renders as expected with licenseType defined 1`] = ` - - @@ -164,5 +167,5 @@ exports[`LicenseWidget snapshots snapshots: renders as expected with licenseType /> - + `; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/__snapshots__/index.test.jsx.snap index b654d77b0..31ad92d69 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/SocialShareWidget/__snapshots__/index.test.jsx.snap @@ -7,7 +7,7 @@ exports[`SocialShareWidget rendered with videoSharingEnabled false with videoSha exports[`SocialShareWidget rendered with videoSharingEnabled false with videoSharingEnabledForCourse and isLibrary false and videoSharingEnabledForAll true should return null 1`] = `null`; exports[`SocialShareWidget rendered with videoSharingEnabled true and allowVideoSharing value equals false should have subtitle with text that reads Enabled 1`] = ` -
- + `; exports[`SocialShareWidget rendered with videoSharingEnabled true and allowVideoSharing value equals true should have subtitle with text that reads Enabled 1`] = ` -
- + `; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/ThumbnailWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/ThumbnailWidget/__snapshots__/index.test.jsx.snap index fd175bb11..e5762eab2 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/ThumbnailWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/ThumbnailWidget/__snapshots__/index.test.jsx.snap @@ -3,7 +3,7 @@ exports[`ThumbnailWidget snapshots snapshots: renders as expected where thumbnail uploads are allowed 1`] = `null`; exports[`ThumbnailWidget snapshots snapshots: renders as expected where videoId is valid 1`] = ` - - + `; exports[`ThumbnailWidget snapshots snapshots: renders as expected where videoId is valid and no thumbnail 1`] = ` - - + `; exports[`ThumbnailWidget snapshots snapshots: renders as expected with a thumbnail provided 1`] = ` - - + `; exports[`ThumbnailWidget snapshots snapshots: renders as expected with default props 1`] = `null`; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/ImportTranscriptCard.test.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/ImportTranscriptCard.test.jsx deleted file mode 100644 index 1a55bc98c..000000000 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/ImportTranscriptCard.test.jsx +++ /dev/null @@ -1,58 +0,0 @@ -import 'CourseAuthoring/editors/setupEditorTest'; -import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; -import { Button, IconButton } from '@openedx/paragon'; - -import { thunkActions } from '../../../../../../data/redux'; -import { ImportTranscriptCardInternal as ImportTranscriptCard, mapDispatchToProps, mapStateToProps } from './ImportTranscriptCard'; - -jest.mock('react', () => ({ - ...jest.requireActual('react'), - useContext: jest.fn(() => ({ transcripts: ['error.transcripts', jest.fn().mockName('error.setTranscripts')] })), -})); - -jest.mock('../../../../../../data/redux', () => ({ - thunkActions: { - video: { - importTranscript: jest.fn().mockName('thunkActions.video.importTranscript'), - }, - }, -})); - -describe('ImportTranscriptCard', () => { - const props = { - setOpen: jest.fn().mockName('setOpen'), - importTranscript: jest.fn().mockName('args.importTranscript'), - }; - let el; - describe('snapshots', () => { - test('snapshots: renders as expected with default props', () => { - expect( - shallow().snapshot, - ).toMatchSnapshot(); - }); - }); - describe('behavior inspection', () => { - beforeEach(() => { - el = shallow(); - }); - test('close behavior is linked to IconButton', () => { - expect(el.instance.findByType(IconButton)[0] - .props.onClick).toBeDefined(); - }); - test('import behavior is linked to Button onClick', () => { - expect(el.instance.findByType(Button)[0] - .props.onClick).toEqual(props.importTranscript); - }); - }); - describe('mapStateToProps', () => { - it('returns an empty object', () => { - expect(mapStateToProps()).toEqual({}); - }); - }); - describe('mapDispatchToProps', () => { - test('updateField from thunkActions.video.importTranscript', () => { - expect(mapDispatchToProps.importTranscript).toEqual(thunkActions.video.importTranscript); - }); - }); -}); diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/ImportTranscriptCard.test.tsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/ImportTranscriptCard.test.tsx new file mode 100644 index 000000000..51be87255 --- /dev/null +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/ImportTranscriptCard.test.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { + render, screen, fireEvent, initializeMocks, +} from '@src/testUtils'; +import { ImportTranscriptCardInternal as ImportTranscriptCard } from './ImportTranscriptCard'; + +jest.mock('../../../../../../data/redux', () => ({ + thunkActions: { + video: { + importTranscript: jest.fn().mockName('thunkActions.video.importTranscript'), + }, + }, +})); + +describe('ImportTranscriptCard (RTL)', () => { + const mockSetOpen = jest.fn(); + const mockImportTranscript = jest.fn(); + + beforeEach(() => { + initializeMocks(); + }); + + it('renders header, message, and button', () => { + render( + , + ); + expect(screen.getByText('Import transcript from YouTube?')).toBeInTheDocument(); + expect(screen.getByText('We found transcript for this video on YouTube. Would you like to import it now?')).toBeInTheDocument(); + expect(screen.getByRole('button', { name: 'Import Transcript' })).toBeInTheDocument(); + }); + + it('calls setOpen(false) when close IconButton is clicked', () => { + const { container } = render( + , + ); + const closeButton = container.querySelector('.btn-icon-primary'); + expect(closeButton).toBeInTheDocument(); + fireEvent.click(closeButton!); + expect(mockSetOpen).toHaveBeenCalledWith(false); + }); + + it('calls importTranscript when import button is clicked', () => { + render( + , + ); + const importBtn = screen.getByRole('button', { name: 'Import Transcript' }); + fireEvent.click(importBtn); + expect(mockImportTranscript).toHaveBeenCalled(); + }); +}); diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/Transcript.test.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/Transcript.test.jsx index ed16d2f53..b091d383b 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/Transcript.test.jsx +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/Transcript.test.jsx @@ -1,98 +1,82 @@ -import 'CourseAuthoring/editors/setupEditorTest'; import React from 'react'; -import { shallow } from '@edx/react-unit-test-utils'; +import { + render, fireEvent, screen, initializeMocks, +} from 'CourseAuthoring/testUtils'; +import { TranscriptInternal, hooks } from './Transcript'; -import * as module from './Transcript'; +jest.mock('./TranscriptActionMenu', () => jest.fn(() =>
TranscriptActionMenu
)); +jest.mock('./LanguageSelector', () => jest.fn(() =>
LanguageSelector
)); -import { MockUseState } from '../../../../../../testUtils'; +const defaultProps = { + index: 0, + language: '', + transcriptUrl: undefined, + deleteTranscript: jest.fn(), +}; -const Transcript = module.TranscriptInternal; - -jest.mock('./LanguageSelector', () => 'LanguageSelector'); -jest.mock('./TranscriptActionMenu', () => 'TranscriptActionMenu'); - -describe('Transcript Component', () => { - describe('state hooks', () => { - const state = new MockUseState(module.hooks); - - beforeEach(() => { - jest.clearAllMocks(); - }); - describe('state hooks', () => { - state.testGetter(state.keys.inDeleteConfirmation); - }); - - describe('setUpDeleteConfirmation hook', () => { - beforeEach(() => { - state.mock(); - }); - afterEach(() => { - state.restore(); - }); - test('inDeleteConfirmation: state values', () => { - expect(module.hooks.setUpDeleteConfirmation().inDeleteConfirmation).toEqual(false); - }); - test('inDeleteConfirmation setters: launch', () => { - module.hooks.setUpDeleteConfirmation().launchDeleteConfirmation(); - expect(state.setState[state.keys.inDeleteConfirmation]).toHaveBeenCalledWith(true); - }); - test('inDeleteConfirmation setters: cancel', () => { - module.hooks.setUpDeleteConfirmation().cancelDelete(); - expect(state.setState[state.keys.inDeleteConfirmation]).toHaveBeenCalledWith(false); - }); - }); +describe('TranscriptInternal', () => { + const cancelDelete = jest.fn(); + const deleteTranscript = jest.fn(); + jest.spyOn(hooks, 'setUpDeleteConfirmation').mockReturnValue({ + inDeleteConfirmation: false, + launchDeleteConfirmation: deleteTranscript, + cancelDelete, }); - describe('component', () => { - describe('component', () => { - const props = { - index: 'sOmenUmBer', - language: 'lAnG', - deleteTranscript: jest.fn().mockName('thunkActions.video.deleteTranscript'), - }; - afterAll(() => { - jest.clearAllMocks(); - }); - test('snapshots: renders as expected with default props: dont show confirm delete', () => { - jest.spyOn(module.hooks, 'setUpDeleteConfirmation').mockImplementationOnce(() => ({ - inDeleteConfirmation: false, - launchDeleteConfirmation: jest.fn().mockName('launchDeleteConfirmation'), - cancelDelete: jest.fn().mockName('cancelDelete'), - })); - expect( - shallow().snapshot, - ).toMatchSnapshot(); - }); - test('snapshots: renders as expected with default props: dont show confirm delete, language is blank so delete is shown instead of action menu', () => { - jest.spyOn(module.hooks, 'setUpDeleteConfirmation').mockImplementationOnce(() => ({ - inDeleteConfirmation: false, - launchDeleteConfirmation: jest.fn().mockName('launchDeleteConfirmation'), - cancelDelete: jest.fn().mockName('cancelDelete'), - })); - expect( - shallow().snapshot, - ).toMatchSnapshot(); - }); - test('snapshots: renders as expected with default props: show confirm delete', () => { - jest.spyOn(module.hooks, 'setUpDeleteConfirmation').mockImplementationOnce(() => ({ - inDeleteConfirmation: true, - launchDeleteConfirmation: jest.fn().mockName('launchDeleteConfirmation'), - cancelDelete: jest.fn().mockName('cancelDelete'), - })); - expect( - shallow().snapshot, - ).toMatchSnapshot(); - }); - test('snapshots: renders as expected with transcriptUrl', () => { - jest.spyOn(module.hooks, 'setUpDeleteConfirmation').mockImplementationOnce(() => ({ - inDeleteConfirmation: false, - launchDeleteConfirmation: jest.fn().mockName('launchDeleteConfirmation'), - cancelDelete: jest.fn().mockName('cancelDelete'), - })); - expect( - shallow().snapshot, - ).toMatchSnapshot(); - }); + beforeEach(() => { + initializeMocks(); + }); + + it('renders ActionRow and LanguageSelector when not in delete confirmation', () => { + render(); + expect(screen.getByText('LanguageSelector')).toBeInTheDocument(); + expect(screen.getByRole('button')).toBeInTheDocument(); + }); + + it('renders TranscriptActionMenu when language is not empty', () => { + const props = { language: 'en', transcriptUrl: 'url' }; + render(); + expect(screen.getByText('TranscriptActionMenu')).toBeInTheDocument(); + }); + + it('calls launchDeleteConfirmation when IconButton is clicked', () => { + render(); + fireEvent.click(screen.getByRole('button')); + expect(deleteTranscript).toHaveBeenCalled(); + }); + + it('renders delete confirmation card when inDeleteConfirmation is true', () => { + jest.spyOn(hooks, 'setUpDeleteConfirmation').mockReturnValue({ + inDeleteConfirmation: true, + launchDeleteConfirmation: jest.fn(), + cancelDelete, }); + render(); + expect(screen.getByText('Delete this transcript?')).toBeInTheDocument(); + expect(screen.getByText('Are you sure you want to delete this transcript?')).toBeInTheDocument(); + }); + + it('calls cancelDelete when cancel button is clicked', () => { + jest.spyOn(hooks, 'setUpDeleteConfirmation').mockReturnValue({ + inDeleteConfirmation: true, + launchDeleteConfirmation: jest.fn(), + cancelDelete, + }); + render(); + fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); + expect(cancelDelete).toHaveBeenCalled(); + }); + + it('calls deleteTranscript and cancelDelete when confirm delete is clicked', () => { + jest.spyOn(hooks, 'setUpDeleteConfirmation').mockReturnValue({ + inDeleteConfirmation: true, + launchDeleteConfirmation: jest.fn(), + cancelDelete, + }); + const props = { language: 'es', deleteTranscript }; + render(); + fireEvent.click(screen.getByRole('button', { name: 'Delete' })); + expect(deleteTranscript).toHaveBeenCalledWith({ language: 'es' }); + expect(cancelDelete).toHaveBeenCalled(); }); }); diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/__snapshots__/ImportTranscriptCard.test.jsx.snap b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/__snapshots__/ImportTranscriptCard.test.jsx.snap deleted file mode 100644 index e71ea14cb..000000000 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/__snapshots__/ImportTranscriptCard.test.jsx.snap +++ /dev/null @@ -1,40 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`ImportTranscriptCard snapshots snapshots: renders as expected with default props 1`] = ` - - - - - - - - - -`; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/__snapshots__/Transcript.test.jsx.snap b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/__snapshots__/Transcript.test.jsx.snap deleted file mode 100644 index f7bf72c1a..000000000 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/TranscriptWidget/__snapshots__/Transcript.test.jsx.snap +++ /dev/null @@ -1,103 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Transcript Component component component snapshots: renders as expected with default props: dont show confirm delete 1`] = ` - - - - - - - -`; - -exports[`Transcript Component component component snapshots: renders as expected with default props: dont show confirm delete, language is blank so delete is shown instead of action menu 1`] = ` - - - - - - - -`; - -exports[`Transcript Component component component snapshots: renders as expected with default props: show confirm delete 1`] = ` - - - - } - /> - - - - - - - - - - - -`; - -exports[`Transcript Component component component snapshots: renders as expected with transcriptUrl 1`] = ` - - - - - - - -`; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/__snapshots__/index.test.jsx.snap b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/__snapshots__/index.test.jsx.snap index c912b9b82..663ca8377 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/__snapshots__/index.test.jsx.snap @@ -1,8 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`VideoSourceWidget snapshots snapshots: renders as expected with default props 1`] = ` - - + `; exports[`VideoSourceWidget snapshots snapshots: renders as expected with videoSharingEnabledForCourse=true 1`] = ` - - + `; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/__snapshots__/CollapsibleFormWidget.test.jsx.snap b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/__snapshots__/CollapsibleFormWidget.test.jsx.snap deleted file mode 100644 index 8da3c5f03..000000000 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/__snapshots__/CollapsibleFormWidget.test.jsx.snap +++ /dev/null @@ -1,145 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`CollapsibleFormWidget render snapshots: renders as expected with default props 1`] = ` - - - -
-
- tiTLE -
-
- SuBTItle -
-
-
- -
-
- -
- tiTLE -
-
- -
-
-
- -

- Some test string -

- -
-`; - -exports[`CollapsibleFormWidget render snapshots: renders with open={true} when there is error 1`] = ` - - - -
-
- tiTLE -
-
- SuBTItle -
-
-
- - -
-
- -
- tiTLE -
-
- -
-
-
- -

- Some test string -

- -
-`; diff --git a/tsconfig.json b/tsconfig.json index aa88164ad..212ce4c78 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "outDir": "dist", "baseUrl": "./src", "paths": { - "*": ["*"] + "@src/*": ["./*"], } }, "include": ["*.js", ".eslintrc.js", "src/**/*", "plugins/**/*"], diff --git a/webpack.dev.config.js b/webpack.dev.config.js index 57ee9080e..3d0591d7d 100644 --- a/webpack.dev.config.js +++ b/webpack.dev.config.js @@ -4,6 +4,8 @@ const { createConfig } = require('@openedx/frontend-build'); const config = createConfig('webpack-dev', { resolve: { alias: { + // Within this app, we can use '@src/foo instead of relative URLs like '../../../foo' + '@src': path.resolve(__dirname, 'src/'), // Plugins can use 'CourseAuthoring' as an import alias for this app: CourseAuthoring: path.resolve(__dirname, 'src/'), },