diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/CollapsibleFormWidget.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/CollapsibleFormWidget.jsx index bd578754a..9323f7968 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/CollapsibleFormWidget.jsx +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/CollapsibleFormWidget.jsx @@ -23,7 +23,7 @@ export const CollapsibleFormWidget = ({ intl, }) => ( diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget.jsx deleted file mode 100644 index e38166dc7..000000000 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget.jsx +++ /dev/null @@ -1,86 +0,0 @@ -import React from 'react'; -import { useDispatch } from 'react-redux'; -// import PropTypes from 'prop-types'; - -import { - FormCheck, - FormControl, - FormGroup, - FormLabel, - IconButton, - Icon, -} from '@edx/paragon'; -import { Delete } from '@edx/paragon/icons'; - -import hooks from './hooks'; -import CollapsibleFormWidget from './CollapsibleFormWidget'; - -/** - * Collapsible Form widget controlling video source as well as fallback sources - */ -export const VideoSourceWidget = () => { - const dispatch = useDispatch(); - const { - videoSource: source, - fallbackVideos, - allowVideoDownloads: allowDownload, - } = hooks.widgetValues({ - dispatch, - fields: { - [hooks.selectorKeys.videoSource]: hooks.genericWidget, - [hooks.selectorKeys.fallbackVideos]: hooks.arrayWidget, - [hooks.selectorKeys.allowVideoDownloads]: hooks.genericWidget, - }, - }); - - return ( - - -
- Video ID or URL - -
- Fallback videos - - {` - To be sure all learners can access the video, edX - recommends providing additional videos in both .mp4 and - .webm formats. The first listed video compatible with the - learner's device will play. - `} - - {[0, 1].map((index) => ( -
- - -
- ))} - -
-
- ); -}; - -export default VideoSourceWidget; 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 new file mode 100644 index 000000000..7e3afd6fb --- /dev/null +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/__snapshots__/index.test.jsx.snap @@ -0,0 +1,111 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`VideoSourceWidget snapshots snapshots: renders as expected with default props 1`] = ` + + +
+ +
+ + + + + + + + + + + + } + placement="top" + > + + + + + + + + + + + + + } + placement="top" + > + + + +
+ +
+`; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/hooks.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/hooks.jsx new file mode 100644 index 000000000..69b92002a --- /dev/null +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/hooks.jsx @@ -0,0 +1,25 @@ +import { actions } from '../../../../../../data/redux'; + +/** + * deleteFallbackVideo({ fallbackVideos, dispatch })(videoUrl) + * deleteFallbackVideo takes the current array of fallback videos, string of + * deleted video URL and dispatch method, and updates the redux value for + * fallbackVideos. + * @param {array} fallbackVideos - array of current fallback videos + * @param {func} dispatch - redux dispatch method + * @param {string} videoUrl - string of the video URL for the fallabck video that needs to be deleted + */ +export const deleteFallbackVideo = ({ fallbackVideos, dispatch }) => (videoUrl) => { + const updatedFallbackVideos = []; + let firstOccurence = true; + fallbackVideos.forEach(item => { + if (item === videoUrl && firstOccurence) { + firstOccurence = false; + } else { + updatedFallbackVideos.push(item); + } + }); + dispatch(actions.video.updateField({ fallbackVideos: updatedFallbackVideos })); +}; + +export default { deleteFallbackVideo }; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/index.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/index.jsx new file mode 100644 index 000000000..3f42d97f7 --- /dev/null +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/index.jsx @@ -0,0 +1,145 @@ +import React from 'react'; +import { connect, useDispatch } from 'react-redux'; +import PropTypes from 'prop-types'; + +import { + Form, + IconButton, + Icon, + OverlayTrigger, + Tooltip, + Button, +} from '@edx/paragon'; +import { Delete, Info, Add } from '@edx/paragon/icons'; +import { + FormattedMessage, + injectIntl, + intlShape, +} from '@edx/frontend-platform/i18n'; + +import * as widgetHooks from '../hooks'; +import * as module from './hooks'; +import messages from './messages'; +import { actions } from '../../../../../../data/redux'; + +import CollapsibleFormWidget from '../CollapsibleFormWidget'; + +/** + * Collapsible Form widget controlling video source as well as fallback sources + */ +export const VideoSourceWidget = ({ + // error, + // injected + intl, + // redux + updateField, +}) => { + const dispatch = useDispatch(); + const { + videoSource: source, + fallbackVideos, + allowVideoDownloads: allowDownload, + } = widgetHooks.widgetValues({ + dispatch, + fields: { + [widgetHooks.selectorKeys.videoSource]: widgetHooks.genericWidget, + [widgetHooks.selectorKeys.fallbackVideos]: widgetHooks.arrayWidget, + [widgetHooks.selectorKeys.allowVideoDownloads]: widgetHooks.genericWidget, + }, + }); + const deleteFallbackVideo = module.deleteFallbackVideo({ fallbackVideos: fallbackVideos.formValue, dispatch }); + + return ( + + +
+ +
+ + + + + + + {fallbackVideos.formValue.map((videoUrl, index) => ( + + + + + + )} + > + deleteFallbackVideo(videoUrl)} + /> + + + ))} + + + + + + + + + + )} + > + + + +
+ +
+ ); +}; +VideoSourceWidget.defaultProps = { + // error: {}, +}; +VideoSourceWidget.propTypes = { + // error: PropTypes.node, + // injected + intl: intlShape.isRequired, + // redux + updateField: PropTypes.func.isRequired, +}; +export const mapStateToProps = () => ({}); + +export const mapDispatchToProps = (dispatch) => ({ + updateField: (stateUpdate) => dispatch(actions.video.updateField(stateUpdate)), +}); +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(VideoSourceWidget)); diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/index.test.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/index.test.jsx new file mode 100644 index 000000000..c7385bcd4 --- /dev/null +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/index.test.jsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { shallow } from 'enzyme'; + +import { formatMessage } from '../../../../../../../testUtils'; +import { actions } from '../../../../../../data/redux'; +import { VideoSourceWidget, mapDispatchToProps } from '.'; + +jest.mock('../../../../../../data/redux', () => ({ + actions: { + video: { + updateField: jest.fn().mockName('actions.video.updateField'), + }, + }, + selectors: { + video: { + videoSource: jest.fn(state => ({ videoSource: state })), + fallbackVideos: jest.fn(state => ({ fallbackVideos: state })), + allowVideoDownloads: jest.fn(state => ({ allowVideoDownloads: state })), + }, + }, +})); + +jest.mock('../hooks', () => ({ + selectorKeys: ['soMEkEy'], + widgetValues: jest.fn().mockReturnValue({ + videoSource: { onChange: jest.fn(), onBlur: jest.fn(), local: '' }, + fallbackVideos: { + formValue: ['somEUrL'], + onChange: jest.fn(), + onBlur: jest.fn(), + local: '', + }, + allowVideoDownloads: { local: false, onCheckedChange: jest.fn() }, + }), +})); + +describe('VideoSourceWidget', () => { + const props = { + error: {}, + title: 'tiTLE', + // inject + intl: { formatMessage }, + // redux + updateField: jest.fn().mockName('args.updateField'), + }; + + describe('snapshots', () => { + test('snapshots: renders as expected with default props', () => { + expect( + shallow(), + ).toMatchSnapshot(); + }); + }); + 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/VideoSourceWidget/messages.js b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/messages.js new file mode 100644 index 000000000..640a1dbb9 --- /dev/null +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/messages.js @@ -0,0 +1,60 @@ +export const messages = { + titleLabel: { + id: 'authoring.videoeditor.videoSource.title.label', + defaultMessage: 'Video Source', + description: 'Title for the video source widget', + }, + videoIdOrUrlLabel: { + id: 'authoring.videoeditor.videoSource.videoIdOrUrl.label', + defaultMessage: 'Video ID or URL', + description: 'Label for video ID or URL field', + }, + videoIdOrUrlFeedback: { + id: 'authoring.videoeditor.videoSource.videoIdOrUrl.feedback', + defaultMessage: `Your video ID, YouTube URL, or a link to an .mp4, .ogg, or + .webm video file hosted elsewhere on the Internet`, + description: 'Feedback for video ID or URL field', + }, + fallbackVideoTitle: { + id: 'authoring.videoeditor.videoSource.fallbackVideo.title', + defaultMessage: 'Fallback Videos', + description: 'Title for the fallback videos section', + }, + fallbackVideoMessage: { + id: 'authoring.videoeditor.videoSource.fallbackVideo.message', + defaultMessage: `To be sure all learners can access the video, edX + recommends providing additional videos in both .mp4 and + .webm formats. The first listed video compatible with the + learner's device will play.`, + description: 'Test explaining reason for fallback videos', + }, + fallbackVideoLabel: { + id: 'authoring.videoeditor.videoSource.fallbackVideo.label', + defaultMessage: 'Video URL', + description: 'Label for fallback video url field', + }, + deleteFallbackVideo: { + id: 'authoring.videoeditor.videoSource.deleteFallbackVideo', + defaultMessage: 'Delete', + description: 'Message Presented To user for action to delete fallback video', + }, + allowDownloadCheckboxLabel: { + id: 'authoring.videoeditor.videoSource.allowDownloadCheckboxLabel', + defaultMessage: 'Allow video downloads', + description: 'Label for allow video downloads checkbox', + }, + tooltipMessage: { + id: 'authoring.videoeditor.videoSource.fallbackVideo.allowDownloadTooltipMessage', + defaultMessage: `Allow learners to download versions of this video in + different formats if they cannot use the edX video player or do not have + access to YouTube.`, + description: 'Message for allow video downloads checkbox', + }, + addButtonLabel: { + id: 'authoring.videoeditor.videoSource.fallbackVideo.allowDownloadTooltipMessage', + defaultMessage: 'Add a video URL', + description: 'Label for add a video URL button', + }, +}; + +export default messages; 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 index 2ea503592..e89b7c0d2 100644 --- 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 @@ -2,7 +2,7 @@ exports[`CollapsibleFormWidget render snapshots: renders as expected with default props 1`] = ` diff --git a/src/setupTest.js b/src/setupTest.js index 2c01fdbfe..cc621f74e 100644 --- a/src/setupTest.js +++ b/src/setupTest.js @@ -101,6 +101,7 @@ jest.mock('@edx/paragon', () => jest.requireActual('testUtils').mockNestedCompon }, Group: 'Form.Group', Label: 'Form.Label', + Text: 'Form.Text', }, FullscreenModal: 'FullscreenModal', Scrollable: 'Scrollable',