diff --git a/src/editors/containers/VideoUploadEditor/__snapshots__/index.test.jsx.snap b/src/editors/containers/VideoUploadEditor/__snapshots__/index.test.jsx.snap index 975e6dcea..9e3efe377 100644 --- a/src/editors/containers/VideoUploadEditor/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/VideoUploadEditor/__snapshots__/index.test.jsx.snap @@ -1,166 +1,227 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`VideoUploadEditor renders without errors 1`] = ` +exports[`VideoUploader snapshots renders as expected with default behavior 1`] = `
`; -exports[`VideoUploader renders without errors 1`] = ` +exports[`VideoUploader snapshots renders as expected with error message 1`] = `
-
+
-
- -
+
+ + + + + + +
+
+ + OR + +
+
+ +
+
+
+ +
`; + +exports[`VideoUploaderEdirtor snapshots renders as expected with default behavior 1`] = ` + + + +`; diff --git a/src/editors/containers/VideoUploadEditor/hooks.js b/src/editors/containers/VideoUploadEditor/hooks.js index 7db83f47b..415e96992 100644 --- a/src/editors/containers/VideoUploadEditor/hooks.js +++ b/src/editors/containers/VideoUploadEditor/hooks.js @@ -1,66 +1,78 @@ -import * as requests from '../../data/redux/thunkActions/requests'; +import React from 'react'; import * as module from './hooks'; import { selectors } from '../../data/redux'; import store from '../../data/store'; import * as appHooks from '../../hooks'; +const extToMime = { + mp4: 'video/mp4', + mov: 'video/quicktime', +}; + export const { navigateTo, } = appHooks; -export const uploadVideo = async ({ dispatch, supportedFiles }) => { - const data = { files: [] }; - supportedFiles.forEach((file) => { - data.files.push({ - file_name: file.name, - content_type: file.type, - }); - }); - const onFileUploadedHook = module.onFileUploaded(); - dispatch(await requests.uploadVideo({ - data, - onSuccess: async (response) => { - const { files } = response.data; - await Promise.all(Object.values(files).map(async (fileObj) => { - const fileName = fileObj.file_name; - const edxVideoId = fileObj.edx_video_id; - const uploadUrl = fileObj.upload_url; - const uploadFile = supportedFiles.find((file) => file.name === fileName); - - if (!uploadFile) { - console.error(`Could not find file object with name "${fileName}" in supportedFiles array.`); - return; - } - const formData = new FormData(); - formData.append('uploaded-file', uploadFile); - await fetch(uploadUrl, { - method: 'PUT', - body: formData, - headers: { - 'Content-Type': 'multipart/form-data', - }, - }) - .then(() => onFileUploadedHook(edxVideoId)) - .catch((error) => console.error('Error uploading file:', error)); - })); - }, - })); +export const state = { + loading: (val) => React.useState(val), + errorMessage: (val) => React.useState(val), + textInputValue: (val) => React.useState(val), }; -export const onFileUploaded = () => { - const state = store.getState(); - const learningContextId = selectors.app.learningContextId(state); - const blockId = selectors.app.blockId(state); - return (edxVideoId) => navigateTo(`/course/${learningContextId}/editor/video/${blockId}?selectedVideoId=${edxVideoId}`); +export const uploadEditor = () => { + const [loading, setLoading] = module.state.loading(false); + const [errorMessage, setErrorMessage] = module.state.errorMessage(null); + return { + loading, + setLoading, + errorMessage, + setErrorMessage, + }; }; -export const onUrlUploaded = () => { - const state = store.getState(); - const learningContextId = selectors.app.learningContextId(state); - const blockId = selectors.app.blockId(state); +export const uploader = () => { + const [textInputValue, settextInputValue] = module.state.textInputValue(''); + return { + textInputValue, + settextInputValue, + }; +}; + +export const postUploadRedirect = (storeState) => { + const learningContextId = selectors.app.learningContextId(storeState); + const blockId = selectors.app.blockId(storeState); return (videoUrl) => navigateTo(`/course/${learningContextId}/editor/video/${blockId}?selectedVideoUrl=${videoUrl}`); }; -export default { - uploadVideo, +export const onVideoUpload = () => { + const storeState = store.getState(); + return module.postUploadRedirect(storeState); +}; + +const getFileExtension = (filename) => filename.slice(Math.abs(filename.lastIndexOf('.') - 1) + 2); + +export const fileValidator = (setLoading, setErrorMessage, uploadVideo) => (file) => { + const supportedFormats = Object.keys(extToMime); + const ext = getFileExtension(file.name); + const type = extToMime[ext] || ''; + const newFile = new File([file], file.name, { type }); + + if (supportedFormats.includes(ext)) { + uploadVideo({ + supportedFiles: [newFile], + setLoadSpinner: setLoading, + postUploadRedirect: onVideoUpload(), + }); + } else { + const errorMsg = 'Video must be an MP4 or MOV file'; + setErrorMessage(errorMsg); + } +}; + +export default { + postUploadRedirect, + uploadEditor, + uploader, + onVideoUpload, + fileValidator, }; diff --git a/src/editors/containers/VideoUploadEditor/hooks.test.js b/src/editors/containers/VideoUploadEditor/hooks.test.js index c1ef806df..a78978601 100644 --- a/src/editors/containers/VideoUploadEditor/hooks.test.js +++ b/src/editors/containers/VideoUploadEditor/hooks.test.js @@ -1,92 +1,50 @@ -import hooks from './hooks'; -import * as requests from '../../data/redux/thunkActions/requests'; +import * as hooks from './hooks'; +import { MockUseState } from '../../../testUtils'; -jest.mock('../../data/redux/thunkActions/requests'); - -describe('uploadVideo', () => { - const dispatch = jest.fn(); - const supportedFiles = [ - new File(['content1'], 'file1.mp4', { type: 'video/mp4' }), - new File(['content2'], 'file2.mov', { type: 'video/quicktime' }), - ]; +const state = new MockUseState(hooks); +const setLoading = jest.fn(); +const setErrorMessage = jest.fn(); +const uploadVideo = jest.fn(); +describe('Video Upload Editor hooks', () => { beforeEach(() => { - jest.resetAllMocks(); - jest.restoreAllMocks(); + jest.clearAllMocks(); }); - - it('should dispatch uploadVideo action with correct data and onSuccess callback', async () => { - requests.uploadVideo.mockImplementation(() => 'requests.uploadVideo'); - const data = { - files: [ - { file_name: 'file1.mp4', content_type: 'video/mp4' }, - { file_name: 'file2.mov', content_type: 'video/quicktime' }, - ], - }; - - await hooks.uploadVideo({ dispatch, supportedFiles }); - - expect(requests.uploadVideo).toHaveBeenCalledWith({ data, onSuccess: expect.any(Function) }); - expect(dispatch).toHaveBeenCalledWith('requests.uploadVideo'); + describe('state hooks', () => { + state.testGetter(state.keys.loading); + state.testGetter(state.keys.errorMessage); + state.testGetter(state.keys.textInputValue); }); + describe('using state', () => { + beforeEach(() => { state.mock(); }); + afterEach(() => { state.restore(); }); - it('should call fetch with correct arguments for each file', async () => { - const mockResponseData = { success: true }; - const mockFetchResponse = Promise.resolve({ data: mockResponseData }); - global.fetch = jest.fn().mockImplementation(() => mockFetchResponse); - const response = { - files: [ - { file_name: 'file1.mp4', upload_url: 'http://example.com/put_video1' }, - { file_name: 'file2.mov', upload_url: 'http://example.com/put_video2' }, - ], - }; - const mockRequestResponse = { data: response }; - requests.uploadVideo.mockImplementation(async ({ onSuccess }) => { - await onSuccess(mockRequestResponse); - }); - - await hooks.uploadVideo({ dispatch, supportedFiles }); - - expect(fetch).toHaveBeenCalledTimes(2); - response.files.forEach(({ upload_url: uploadUrl }, index) => { - expect(fetch.mock.calls[index][0]).toEqual(uploadUrl); - }); - supportedFiles.forEach((file, index) => { - expect(fetch.mock.calls[index][1].body.get('uploaded-file')).toBe(file); + describe('Hooks for Video Upload', () => { + beforeEach(() => { + hooks.uploadEditor(); + hooks.uploader(); + }); + it('initialize state with correct values', () => { + expect(state.stateVals.loading).toEqual(false); + expect(state.stateVals.errorMessage).toEqual(null); + expect(state.stateVals.textInputValue).toEqual(''); + }); }); }); - - it('should log an error if fetch failed to upload a file', async () => { - const error = new Error('Uh-oh!'); - global.fetch = jest.fn().mockRejectedValue(error); - const response = { - files: [ - { file_name: 'file1.mp4', upload_url: 'http://example.com/put_video1' }, - { file_name: 'file2.mov', upload_url: 'http://example.com/put_video2' }, - ], - }; - const mockRequestResponse = { data: response }; - requests.uploadVideo.mockImplementation(async ({ onSuccess }) => { - await onSuccess(mockRequestResponse); + describe('File Validation', () => { + it('Checks with valid MIME type', () => { + const file = new File(['(⌐□_□)'], 'video.mp4', { type: 'video/mp4' }); + const validator = hooks.fileValidator(setLoading, setErrorMessage, uploadVideo); + validator(file); + expect(uploadVideo).toHaveBeenCalled(); + expect(setErrorMessage).not.toHaveBeenCalled(); }); - - await hooks.uploadVideo({ dispatch, supportedFiles }); - }); - - it('should log an error if file object is not found in supportedFiles array', () => { - const response = { - files: [ - { file_name: 'file2.mov', upload_url: 'http://example.com/put_video2' }, - ], - }; - const mockRequestResponse = { data: response }; - const spyConsoleError = jest.spyOn(console, 'error'); - requests.uploadVideo.mockImplementation(({ onSuccess }) => { - onSuccess(mockRequestResponse); + it('Checks with invalid MIME type', () => { + const file = new File(['(⌐□_□)'], 'video.gif', { type: 'video/mp4' }); + const validator = hooks.fileValidator(setLoading, setErrorMessage, uploadVideo); + validator(file); + expect(uploadVideo).not.toHaveBeenCalled(); + expect(setErrorMessage).toHaveBeenCalled(); }); - - hooks.uploadVideo({ dispatch, supportedFiles: [supportedFiles[0]] }); - - expect(spyConsoleError).toHaveBeenCalledWith('Could not find file object with name "file2.mov" in supportedFiles array.'); }); }); diff --git a/src/editors/containers/VideoUploadEditor/index.jsx b/src/editors/containers/VideoUploadEditor/index.jsx index 54780e719..853b247da 100644 --- a/src/editors/containers/VideoUploadEditor/index.jsx +++ b/src/editors/containers/VideoUploadEditor/index.jsx @@ -1,19 +1,19 @@ -import React, { useState } from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import { useDropzone } from 'react-dropzone'; import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; -import { Icon, IconButton } from '@edx/paragon'; -import './index.scss'; -import { useDispatch } from 'react-redux'; +import { Icon, IconButton, Spinner } from '@edx/paragon'; import { ArrowForward, Close, FileUpload } from '@edx/paragon/icons'; +import { connect } from 'react-redux'; +import { thunkActions } from '../../data/redux'; +import './index.scss'; import * as hooks from './hooks'; -import messages from '../../messages'; +import messages from './messages'; import * as editorHooks from '../EditorContainer/hooks'; export const VideoUploader = ({ onUpload, errorMessage }) => { - const [, setUploadedFile] = useState(); - const [textInputValue, setTextInputValue] = useState(''); - const onUrlUpdatedHook = hooks.onUrlUploaded(); + const { textInputValue, setTextInputValue } = hooks.uploader(); + const onURLUpload = hooks.onVideoUpload(); const { getRootProps, getInputProps, isDragActive } = useDropzone({ accept: 'video/*', @@ -21,7 +21,6 @@ export const VideoUploader = ({ onUpload, errorMessage }) => { onDrop: (acceptedFiles) => { if (acceptedFiles.length > 0) { const uploadfile = acceptedFiles[0]; - setUploadedFile(uploadfile); onUpload(uploadfile); } }, @@ -32,7 +31,7 @@ export const VideoUploader = ({ onUpload, errorMessage }) => { }; const handleSaveButtonClick = () => { - onUrlUpdatedHook(textInputValue); + onURLUpload(textInputValue); }; if (errorMessage) { @@ -85,52 +84,53 @@ VideoUploader.propTypes = { intl: intlShape.isRequired, }; -const VideoUploadEditor = ({ intl, onClose }) => { - const dispatch = useDispatch(); - const [errorMessage, setErrorMessage] = useState(null); - const handleCancel = () => { - editorHooks.handleCancel({ onClose }); - }; +const VideoUploadEditor = ( + { + intl, + onClose, + // Redux states + uploadVideo, + }, +) => { + const { + loading, + setLoading, + errorMessage, + setErrorMessage, + } = hooks.uploadEditor(); + const handleCancel = editorHooks.handleCancel({ onClose }); const handleDrop = (file) => { if (!file) { console.log('No file selected.'); return; } - - const extToMime = { - mp4: 'video/mp4', - mov: 'video/quicktime', - }; - const supportedFormats = Object.keys(extToMime); - - function getFileExtension(filename) { - return filename.slice(Math.abs(filename.lastIndexOf('.') - 1) + 2); - } - - const ext = getFileExtension(file.name); - const type = extToMime[ext] || ''; - const newFile = new File([file], file.name, { type }); - - if (supportedFormats.includes(ext)) { - hooks.uploadVideo({ dispatch, supportedFiles: [newFile] }); - } else { - const errorMsg = 'Video must be an MP4 or MOV file'; - console.log(errorMsg); - setErrorMessage(errorMsg); - } + const validator = hooks.fileValidator(setLoading, setErrorMessage, uploadVideo); + validator(file); }; return ( -
-
- -
- +
+ {(!loading) ? ( +
+
+ +
+ +
+ ) : ( +
+ +
+ )}
); }; @@ -138,6 +138,13 @@ const VideoUploadEditor = ({ intl, onClose }) => { VideoUploadEditor.propTypes = { intl: intlShape.isRequired, onClose: PropTypes.func.isRequired, + uploadVideo: PropTypes.func.isRequired, }; -export default injectIntl(VideoUploadEditor); +export const mapStateToProps = () => ({}); + +export const mapDispatchToProps = { + uploadVideo: thunkActions.video.uploadVideo, +}; + +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(VideoUploadEditor)); diff --git a/src/editors/containers/VideoUploadEditor/index.test.jsx b/src/editors/containers/VideoUploadEditor/index.test.jsx index 8f256e457..3b6e97c90 100644 --- a/src/editors/containers/VideoUploadEditor/index.test.jsx +++ b/src/editors/containers/VideoUploadEditor/index.test.jsx @@ -1,142 +1,37 @@ import React from 'react'; -import { IntlProvider } from '@edx/frontend-platform/i18n'; -import { render, fireEvent, act } from '@testing-library/react'; import '@testing-library/jest-dom'; +import { shallow } from 'enzyme'; import VideoUploadEditor, { VideoUploader } from '.'; -import * as hooks from './hooks'; -import * as appHooks from '../../hooks'; - -const mockDispatch = jest.fn(); -const mockOnUpload = jest.fn(); - -jest.mock('react-redux', () => ({ - useDispatch: () => mockDispatch, -})); -jest.mock('../../hooks', () => ({ - ...jest.requireActual('../../hooks'), - navigateTo: jest.fn((args) => ({ navigateTo: args })), -})); +import { formatMessage } from '../../../testUtils'; const defaultEditorProps = { - intl: {}, + onClose: jest.fn().mockName('props.onClose'), + intl: { formatMessage }, + uploadVideo: jest.fn(), }; const defaultUploaderProps = { - onUpload: mockOnUpload, - errorMessage: '', - intl: {}, + onUpload: jest.fn(), + errorMessage: null, + intl: { formatMessage }, }; -const renderEditorComponent = (props = defaultEditorProps) => render( - - - , -); - -const renderUploaderComponent = (props = defaultUploaderProps) => render( - - - , -); - -describe('VideoUploadEditor', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('renders without errors', () => { - const { container } = renderEditorComponent(); - expect(container).toMatchSnapshot(); - }); - - it('updates the input field value when user types', () => { - const { getByPlaceholderText } = renderEditorComponent(); - const input = getByPlaceholderText('Paste your video ID or URL'); - - fireEvent.change(input, { target: { value: 'test value' } }); - expect(input.value).toBe('test value'); - }); - - it('click on the save button', () => { - const { getByPlaceholderText, getByTestId } = renderEditorComponent(); - const testValue = 'test vale'; - const input = getByPlaceholderText('Paste your video ID or URL'); - fireEvent.change(input, { target: { value: testValue } }); - const button = getByTestId('inputSaveButton'); - fireEvent.click(button); - expect(appHooks.navigateTo).toHaveBeenCalled(); - }); - - it('shows error message with unsupported files', async () => { - const { getByTestId, findByText } = renderEditorComponent(); - const fileInput = getByTestId('fileInput'); - - const unsupportedFile = new File(['(⌐□_□)'], 'unsupported.avi', { type: 'video/avi' }); - fireEvent.change(fileInput, { target: { files: [unsupportedFile] } }); - - const errorMsg = await findByText('Video must be an MP4 or MOV file'); - expect(errorMsg).toBeInTheDocument(); - }); - - it('calls uploadVideo with supported files', async () => { - const uploadVideoSpy = jest.spyOn(hooks, 'uploadVideo'); - const { container } = renderEditorComponent(); - const dropzone = container.querySelector('.dropzone-middle'); - - const supportedFile = new File(['(⌐□_□)'], 'supported.mp4', { type: 'video/mp4' }); - - await act(async () => { - fireEvent.drop(dropzone, { - dataTransfer: { - files: [supportedFile], - types: ['Files'], - }, - }); - }); - - expect(uploadVideoSpy).toHaveBeenCalledWith(expect.objectContaining({ - dispatch: mockDispatch, - supportedFiles: expect.arrayContaining([ - expect.objectContaining({ - name: supportedFile.name, - type: supportedFile.type, - size: supportedFile.size, - }), - ]), - })); - }); -}); - describe('VideoUploader', () => { - afterEach(() => { - jest.clearAllMocks(); - }); - - it('renders without errors', () => { - const { container } = renderUploaderComponent(); - expect(container).toMatchSnapshot(); - }); - - it('renders with an error message', () => { - const errorMessage = 'Video must be an MP4 or MOV file'; - const { getByText } = renderUploaderComponent({ ...defaultUploaderProps, errorMessage }); - expect(getByText(errorMessage)).toBeInTheDocument(); - }); - - it('calls the onUpload function when a supported file is dropped', async () => { - const { container } = renderUploaderComponent(); - const dropzone = container.querySelector('.dropzone-middle'); - const file = new File(['(⌐□_□)'], 'video.mp4', { type: 'video/mp4' }); - - await act(async () => { - fireEvent.drop(dropzone, { - dataTransfer: { - files: [file], - types: ['Files'], - }, - }); + describe('snapshots', () => { + test('renders as expected with default behavior', () => { + expect(shallow()).toMatchSnapshot(); + }); + test('renders as expected with error message', () => { + const defaultUploaderPropsWithError = { ...defaultUploaderProps, errorMessages: 'Some Error' }; + expect(shallow()).toMatchSnapshot(); + }); + }); +}); + +describe('VideoUploaderEdirtor', () => { + describe('snapshots', () => { + test('renders as expected with default behavior', () => { + expect(shallow()).toMatchSnapshot(); }); - - expect(mockOnUpload).toHaveBeenCalledWith(file); }); }); diff --git a/src/editors/containers/VideoUploadEditor/messages.js b/src/editors/containers/VideoUploadEditor/messages.js new file mode 100644 index 000000000..134962cd4 --- /dev/null +++ b/src/editors/containers/VideoUploadEditor/messages.js @@ -0,0 +1,21 @@ +import { defineMessages } from '@edx/frontend-platform/i18n'; + +const messages = defineMessages({ + spinnerScreenReaderText: { + id: 'authoring.videoUpload.spinnerScreenReaderText', + defaultMessage: 'loading', + description: 'Loading message for spinner screenreader text.', + }, + dropVideoFileHere: { + defaultMessage: 'Drag and drop video here or click to upload', + id: 'VideoUploadEditor.dropVideoFileHere', + description: 'Display message for Drag and Drop zone', + }, + info: { + id: 'VideoUploadEditor.uploadInfo', + defaultMessage: 'Upload MP4 or MOV files (5 GB max)', + description: 'Info message for supported formats', + }, +}); + +export default messages; diff --git a/src/editors/data/redux/thunkActions/video.js b/src/editors/data/redux/thunkActions/video.js index d407b9d48..3e2c10ed5 100644 --- a/src/editors/data/redux/thunkActions/video.js +++ b/src/editors/data/redux/thunkActions/video.js @@ -370,6 +370,45 @@ export const replaceTranscript = ({ newFile, newFilename, language }) => (dispat })); }; +export const uploadVideo = ({ supportedFiles, setLoadSpinner, postUploadRedirect }) => (dispatch) => { + const data = { files: [] }; + setLoadSpinner(true); + supportedFiles.forEach((file) => { + data.files.push({ + file_name: file.name, + content_type: file.type, + }); + }); + dispatch(requests.uploadVideo({ + data, + onSuccess: async (response) => { + const { files } = response.data; + await Promise.all(Object.values(files).map(async (fileObj) => { + const fileName = fileObj.file_name; + const edxVideoId = fileObj.edx_video_id; + const uploadUrl = fileObj.upload_url; + const uploadFile = supportedFiles.find((file) => file.name === fileName); + if (!uploadFile) { + console.error(`Could not find file object with name "${fileName}" in supportedFiles array.`); + return; + } + const formData = new FormData(); + formData.append('uploaded-file', uploadFile); + await fetch(uploadUrl, { + method: 'PUT', + body: formData, + headers: { + 'Content-Type': 'multipart/form-data', + }, + }) + .then(() => postUploadRedirect(edxVideoId)) + .catch((error) => console.error('Error uploading file:', error)); + })); + setLoadSpinner(false); + }, + })); +}; + export default { loadVideoData, determineVideoSources, @@ -382,4 +421,5 @@ export default { updateTranscriptLanguage, replaceTranscript, uploadHandout, + uploadVideo, }; diff --git a/src/editors/data/redux/thunkActions/video.test.js b/src/editors/data/redux/thunkActions/video.test.js index 841cb2b36..a8b0b177f 100644 --- a/src/editors/data/redux/thunkActions/video.test.js +++ b/src/editors/data/redux/thunkActions/video.test.js @@ -32,6 +32,7 @@ jest.mock('./requests', () => ({ checkTranscriptsForImport: (args) => ({ checkTranscriptsForImport: args }), importTranscript: (args) => ({ importTranscript: args }), fetchVideoFeatures: (args) => ({ fetchVideoFeatures: args }), + uploadVideo: (args) => ({ uploadVideo: args }), })); jest.mock('../../../utils', () => ({ @@ -669,3 +670,79 @@ describe('video thunkActions', () => { }); }); }); + +describe('uploadVideo', () => { + let dispatch; + let setLoadSpinner; + let postUploadRedirect; + let dispatchedAction; + const supportedFiles = [ + new File(['content1'], 'file1.mp4', { type: 'video/mp4' }), + new File(['content2'], 'file2.mov', { type: 'video/quicktime' }), + ]; + + beforeEach(() => { + dispatch = jest.fn((action) => ({ dispatch: action })); + setLoadSpinner = jest.fn(); + postUploadRedirect = jest.fn(); + jest.resetAllMocks(); + jest.restoreAllMocks(); + }); + + it('dispatch uploadVideo action with right data', async () => { + const data = { + files: [ + { file_name: 'file1.mp4', content_type: 'video/mp4' }, + { file_name: 'file2.mov', content_type: 'video/quicktime' }, + ], + }; + + thunkActions.uploadVideo({ supportedFiles, setLoadSpinner, postUploadRedirect })(dispatch); + [[dispatchedAction]] = dispatch.mock.calls; + expect(dispatchedAction.uploadVideo).not.toEqual(undefined); + expect(setLoadSpinner).toHaveBeenCalled(); + expect(dispatchedAction.uploadVideo.data).toEqual(data); + }); + + it('should call fetch with correct arguments for each file', async () => { + const mockResponseData = { success: true }; + const mockFetchResponse = Promise.resolve({ data: mockResponseData }); + global.fetch = jest.fn().mockImplementation(() => mockFetchResponse); + const response = { + files: [ + { file_name: 'file1.mp4', upload_url: 'http://example.com/put_video1' }, + { file_name: 'file2.mov', upload_url: 'http://example.com/put_video2' }, + ], + }; + const mockRequestResponse = { data: response }; + thunkActions.uploadVideo({ supportedFiles, setLoadSpinner, postUploadRedirect })(dispatch); + [[dispatchedAction]] = dispatch.mock.calls; + + dispatchedAction.uploadVideo.onSuccess(mockRequestResponse); + + expect(fetch).toHaveBeenCalledTimes(2); + response.files.forEach(({ upload_url: uploadUrl }, index) => { + expect(fetch.mock.calls[index][0]).toEqual(uploadUrl); + }); + supportedFiles.forEach((file, index) => { + expect(fetch.mock.calls[index][1].body.get('uploaded-file')).toBe(file); + }); + }); + + it('should log an error if file object is not found in supportedFiles array', () => { + const mockResponseData = { success: true }; + const mockFetchResponse = Promise.resolve({ data: mockResponseData }); + global.fetch = jest.fn().mockImplementation(() => mockFetchResponse); + const response = { + files: [ + { file_name: 'file2.gif', upload_url: 'http://example.com/put_video2' }, + ], + }; + const mockRequestResponse = { data: response }; + const spyConsoleError = jest.spyOn(console, 'error'); + + thunkActions.uploadVideo({ supportedFiles: [supportedFiles[0]], setLoadSpinner, postUploadRedirect })(dispatch); + dispatchedAction.uploadVideo.onSuccess(mockRequestResponse); + expect(spyConsoleError).toHaveBeenCalledWith('Could not find file object with name "file2.gif" in supportedFiles array.'); + }); +});