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 3e636814e..94de9e80f 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 @@ -5,6 +5,17 @@ exports[`VideoSourceWidget snapshots snapshots: renders as expected with default fontSize="x-small" title="Video source" > + + +
- - + - - +
+ + +
- - + - - +
({ +export const state = { + showVideoIdChangeAlert: (args) => React.useState(args), +}; + +export const sourceHooks = ({ dispatch, previousVideoId, setAlert }) => ({ updateVideoURL: (e, videoId) => { const videoUrl = e.target.value; dispatch(actions.video.updateField({ videoSource: videoUrl })); @@ -22,7 +27,13 @@ export const sourceHooks = ({ dispatch }) => ({ })); } }, - updateVideoId: (e) => dispatch(actions.video.updateField({ videoId: e.target.value })), + updateVideoId: (e) => { + const updatedVideoId = e.target.value; + if (previousVideoId !== updatedVideoId && updatedVideoId) { + setAlert(); + } + dispatch(actions.video.updateField({ videoId: updatedVideoId })); + }, }); export const fallbackHooks = ({ fallbackVideos, dispatch }) => ({ @@ -33,7 +44,19 @@ export const fallbackHooks = ({ fallbackVideos, dispatch }) => ({ }, }); +export const videoIdChangeAlert = () => { + const [showVideoIdChangeAlert, setShowVideoIdChangeAlert] = state.showVideoIdChangeAlert(false); + return { + videoIdChangeAlert: { + show: showVideoIdChangeAlert, + set: () => setShowVideoIdChangeAlert(true), + dismiss: () => setShowVideoIdChangeAlert(false), + }, + }; +}; + export default { + videoIdChangeAlert, sourceHooks, fallbackHooks, }; diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/hooks.test.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/hooks.test.jsx index 9315a1caa..7bc8d6ad7 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/hooks.test.jsx +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/hooks.test.jsx @@ -1,8 +1,16 @@ import { dispatch } from 'react-redux'; import { actions } from '../../../../../../data/redux'; +import { MockUseState } from '../../../../../../../testUtils'; import * as requests from '../../../../../../data/redux/thunkActions/requests'; import * as hooks from './hooks'; +jest.mock('react', () => ({ + ...jest.requireActual('react'), + useRef: jest.fn(val => ({ current: val })), + useEffect: jest.fn(), + useCallback: (cb, prereqs) => ({ cb, prereqs }), +})); + jest.mock('react-redux', () => { const dispatchFn = jest.fn(); return { @@ -25,16 +33,24 @@ jest.mock('../../../../../../data/redux/thunkActions/requests', () => ({ checkTranscriptsForImport: jest.fn(), })); +const state = new MockUseState(hooks); + const youtubeId = 'yOuTuBEiD'; const youtubeUrl = `https://youtu.be/${youtubeId}`; describe('VideoEditorHandout hooks', () => { let hook; - + describe('state hooks', () => { + state.testGetter(state.keys.showVideoIdChangeAlert); + }); describe('sourceHooks', () => { const e = { target: { value: 'soMEvALuE' } }; beforeEach(() => { - hook = hooks.sourceHooks({ dispatch }); + hook = hooks.sourceHooks({ + dispatch, + previousVideoId: 'soMEvALuE', + setAlert: jest.fn(), + }); }); afterEach(() => { jest.clearAllMocks(); @@ -82,7 +98,23 @@ describe('VideoEditorHandout hooks', () => { }); describe('updateVideoId', () => { it('dispatches updateField action with new videoId', () => { - hook.updateVideoId(e); + hook.updateVideoId({ target: { value: 'newVideoId' } }); + expect(dispatch).toHaveBeenCalledWith( + actions.video.updateField({ + videoId: e.target.value, + }), + ); + }); + it('dispatches updateField action with empty string', () => { + hook.updateVideoId({ target: { value: '' } }); + expect(dispatch).toHaveBeenCalledWith( + actions.video.updateField({ + videoId: e.target.value, + }), + ); + }); + it('dispatches updateField action with previousVideoId', () => { + hook.updateVideoId({ target: { value: 'soMEvALuE' } }); expect(dispatch).toHaveBeenCalledWith( actions.video.updateField({ videoId: e.target.value, @@ -120,4 +152,23 @@ describe('VideoEditorHandout hooks', () => { }); }); }); + describe('videoIdChangeAlert', () => { + beforeEach(() => { + state.mock(); + }); + afterEach(() => { + state.restore(); + }); + test('showVideoIdChangeAlert: state values', () => { + expect(hooks.videoIdChangeAlert().videoIdChangeAlert.show).toEqual(false); + }); + test('showVideoIdChangeAlert setters: set', () => { + hooks.videoIdChangeAlert().videoIdChangeAlert.set(); + expect(state.setState[state.keys.showVideoIdChangeAlert]).toHaveBeenCalledWith(true); + }); + test('showVideoIdChangeAlert setters: dismiss', () => { + hooks.videoIdChangeAlert().videoIdChangeAlert.dismiss(); + expect(state.setState[state.keys.showVideoIdChangeAlert]).toHaveBeenCalledWith(false); + }); + }); }); diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/index.jsx b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/index.jsx index 2337c0adc..b95f63d59 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/index.jsx +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/index.jsx @@ -10,7 +10,6 @@ import { Button, Tooltip, OverlayTrigger, - FormControlFeedback, } from '@edx/paragon'; import { DeleteOutline, InfoOutline, Add } from '@edx/paragon/icons'; import { @@ -24,6 +23,7 @@ import * as hooks from './hooks'; import messages from './messages'; import { selectors } from '../../../../../../data/redux'; +import { ErrorAlert } from '../../../../../../sharedComponents/ErrorAlerts/ErrorAlert'; import CollapsibleFormWidget from '../CollapsibleFormWidget'; /** @@ -52,7 +52,12 @@ export const VideoSourceWidget = ({ [widgetHooks.selectorKeys.allowVideoSharing]: widgetHooks.genericWidget, }, }); - const { updateVideoId, updateVideoURL } = hooks.sourceHooks({ dispatch }); + const { videoIdChangeAlert } = hooks.videoIdChangeAlert(); + const { updateVideoId, updateVideoURL } = hooks.sourceHooks({ + dispatch, + previousVideoId: videoId.formValue, + setAlert: videoIdChangeAlert.set, + }); const { addFallbackVideo, deleteFallbackVideo, @@ -63,6 +68,13 @@ export const VideoSourceWidget = ({ fontSize="x-small" title={intl.formatMessage(messages.titleLabel)} > + + +
- + - + updateVideoURL(e, videoId.local)} value={source.local} /> - + - +
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 index 427cdcf07..655fd34b6 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/index.test.jsx +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/index.test.jsx @@ -32,6 +32,13 @@ jest.mock('../hooks', () => ({ })); jest.mock('./hooks', () => ({ + videoIdChangeAlert: jest.fn().mockReturnValue({ + videoIdChangeAlert: { + set: (args) => ({ set: args }), + show: false, + dismiss: (args) => ({ dismiss: args }), + }, + }), sourceHooks: jest.fn().mockReturnValue({ updateVideoId: (args) => ({ updateVideoId: args }), updateVideoURL: jest.fn().mockName('updateVideoURL'), @@ -81,20 +88,20 @@ describe('VideoSourceWidget', () => { let el; let hook; beforeEach(() => { + hook = hooks.sourceHooks({ dispatch, previousVideoId: 'someVideoId', setAlert: jest.fn() }); el = shallow(); - hook = hooks.sourceHooks({ dispatch }); }); test('updateVideoId is tied to id field onBlur', () => { const expected = hook.updateVideoId; expect(el // eslint-disable-next-line - .children().at(0).children().at(0).children().at(0) + .children().at(1).children().at(0).children().at(0) .props().onBlur).toEqual(expected); }); test('updateVideoURL is tied to url field onBlur', () => { const { onBlur } = el // eslint-disable-next-line - .children().at(0).children().at(0).children().at(2).props(); + .children().at(1).children().at(0).children().at(2).props(); onBlur('onBlur event'); expect(hook.updateVideoURL).toHaveBeenCalledWith('onBlur event', ''); }); diff --git a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/messages.js b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/messages.js index f5a01138a..61e1d9c5d 100644 --- a/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/messages.js +++ b/src/editors/containers/VideoEditor/components/VideoSettingsModal/components/VideoSourceWidget/messages.js @@ -28,6 +28,11 @@ const messages = defineMessages({ to an .mp4, .ogg, or .webm video file hosted elsewhere on the internet.`, description: 'Feedback for video URL field', }, + videoIdChangeAlert: { + id: 'authoring.videoeditor.videoIdChangeAlert.message', + defaultMessage: 'The Video ID field has changed, please check the Video URL and fallback URL values and update them if necessary.', + description: 'Body message for the alert that appears when the video id has been changed.', + }, fallbackVideoTitle: { id: 'authoring.videoeditor.videoSource.fallbackVideo.title', defaultMessage: 'Fallback videos',