From d739bcbdb55978a10bcfb598ad1716963bd245f6 Mon Sep 17 00:00:00 2001 From: Kristin Aoki <42981026+KristinAoki@users.noreply.github.com> Date: Mon, 8 Aug 2022 15:44:59 -0400 Subject: [PATCH] feat: Prevent image uploads larger than 10 MB and add spinner --- .../FetchErrorAlert.test.jsx.snap | 2 +- .../UploadErrorAlert.test.jsx.snap | 2 +- .../components/ImageUploadModal.jsx | 24 +++-- .../__snapshots__/index.test.jsx.snap | 93 ++++++++++++++++++- .../components/SelectImageModal/hooks.js | 87 ++++++++++++----- .../components/SelectImageModal/hooks.test.js | 63 ++++++++++--- .../components/SelectImageModal/index.jsx | 53 ++++++++--- .../SelectImageModal/index.test.jsx | 42 ++++++++- .../components/SelectImageModal/messages.js | 19 +++- .../ImageUploadModal.test.jsx.snap | 1 + .../data/redux/thunkActions/requests.js | 30 +++--- 11 files changed, 340 insertions(+), 76 deletions(-) diff --git a/src/editors/containers/TextEditor/components/ErrorAlerts/__snapshots__/FetchErrorAlert.test.jsx.snap b/src/editors/containers/TextEditor/components/ErrorAlerts/__snapshots__/FetchErrorAlert.test.jsx.snap index 9cc5c939a..7874b5a9f 100644 --- a/src/editors/containers/TextEditor/components/ErrorAlerts/__snapshots__/FetchErrorAlert.test.jsx.snap +++ b/src/editors/containers/TextEditor/components/ErrorAlerts/__snapshots__/FetchErrorAlert.test.jsx.snap @@ -7,7 +7,7 @@ exports[`FetchErrorAlert Snapshots snapshot: is ErrorAlert with Message error ( isError={true} > diff --git a/src/editors/containers/TextEditor/components/ErrorAlerts/__snapshots__/UploadErrorAlert.test.jsx.snap b/src/editors/containers/TextEditor/components/ErrorAlerts/__snapshots__/UploadErrorAlert.test.jsx.snap index 28f8346d2..f50cb4443 100644 --- a/src/editors/containers/TextEditor/components/ErrorAlerts/__snapshots__/UploadErrorAlert.test.jsx.snap +++ b/src/editors/containers/TextEditor/components/ErrorAlerts/__snapshots__/UploadErrorAlert.test.jsx.snap @@ -7,7 +7,7 @@ exports[`UploadErrorAlert Snapshots snapshot: is ErrorAlert with Message error isError={true} > diff --git a/src/editors/containers/TextEditor/components/ImageUploadModal.jsx b/src/editors/containers/TextEditor/components/ImageUploadModal.jsx index f13798e3a..2cba4fea2 100644 --- a/src/editors/containers/TextEditor/components/ImageUploadModal.jsx +++ b/src/editors/containers/TextEditor/components/ImageUploadModal.jsx @@ -6,9 +6,9 @@ import ImageSettingsModal from './ImageSettingsModal'; import SelectImageModal from './SelectImageModal'; import * as module from './ImageUploadModal'; -export const propsString = (props) => Object.keys(props) - .map(key => `${key}="${props[key]}"`) - .join(' '); +export const propsString = (props) => ( + Object.keys(props).map((key) => `${key}="${props[key]}"`).join(' ') +); export const imgProps = ({ settings, selection }) => ({ src: selection.externalUrl, @@ -19,8 +19,13 @@ export const imgProps = ({ settings, selection }) => ({ export const hooks = { createSaveCallback: ({ - close, editorRef, setSelection, selection, - }) => (settings) => { + close, + editorRef, + setSelection, + selection, + }) => ( + settings, + ) => { editorRef.current.execCommand( tinyMCEKeys.commands.insertContent, false, @@ -67,7 +72,14 @@ export const ImageUploadModal = ({ ); } return ( - + ); }; diff --git a/src/editors/containers/TextEditor/components/SelectImageModal/__snapshots__/index.test.jsx.snap b/src/editors/containers/TextEditor/components/SelectImageModal/__snapshots__/index.test.jsx.snap index 3831a406f..40ffea994 100644 --- a/src/editors/containers/TextEditor/components/SelectImageModal/__snapshots__/index.test.jsx.snap +++ b/src/editors/containers/TextEditor/components/SelectImageModal/__snapshots__/index.test.jsx.snap @@ -21,7 +21,7 @@ exports[`SelectImageModal component snapshot 1`] = ` variant="link" > @@ -35,7 +35,18 @@ exports[`SelectImageModal component snapshot 1`] = ` + + + `; + +exports[`SelectImageModal component snapshot: uploaded image not loaded, show spinner 1`] = ` + + + + } + footerAction={ + + } + isOpen={true} + title="Add an image" +> + + + + + + + + + + + + + + +`; diff --git a/src/editors/containers/TextEditor/components/SelectImageModal/hooks.js b/src/editors/containers/TextEditor/components/SelectImageModal/hooks.js index f3ee8a075..4f098b14f 100644 --- a/src/editors/containers/TextEditor/components/SelectImageModal/hooks.js +++ b/src/editors/containers/TextEditor/components/SelectImageModal/hooks.js @@ -11,6 +11,7 @@ export const state = { showSelectImageError: (val) => React.useState(val), searchString: (val) => React.useState(val), sortBy: (val) => React.useState(val), + showSizeError: (val) => React.useState(val), }; export const searchAndSortHooks = () => { @@ -18,30 +19,32 @@ export const searchAndSortHooks = () => { const [sortBy, setSortBy] = module.state.sortBy(sortKeys.dateNewest); return { searchString, - onSearchChange: e => setSearchString(e.target.value), + onSearchChange: (e) => setSearchString(e.target.value), clearSearchString: () => setSearchString(''), sortBy, - onSortClick: key => () => setSortBy(key), + onSortClick: (key) => () => setSortBy(key), }; }; -export const filteredList = ({ searchString, imageList }) => imageList.filter( - ({ displayName }) => displayName.toLowerCase().includes(searchString.toLowerCase()), +export const filteredList = ({ searchString, imageList }) => ( + imageList.filter(({ displayName }) => displayName.toLowerCase().includes(searchString.toLowerCase())) ); -export const displayList = ({ sortBy, searchString, images }) => module.filteredList({ - searchString, - imageList: Object.values(images), -}).sort(sortFunctions[sortBy in sortKeys ? sortKeys[sortBy] : sortKeys.dateNewest]); +export const displayList = ({ sortBy, searchString, images }) => ( + module.filteredList({ + searchString, + imageList: Object.values(images), + }).sort(sortFunctions[sortBy in sortKeys ? sortKeys[sortBy] : sortKeys.dateNewest])); -export const imgListHooks = ({ - searchSortProps, - setSelection, -}) => { +export const imgListHooks = ({ searchSortProps, setSelection }) => { const dispatch = useDispatch(); const [images, setImages] = module.state.images({}); const [highlighted, setHighlighted] = module.state.highlighted(null); - const [showSelectImageError, setShowSelectImageError] = module.state.showSelectImageError(false); + const [ + showSelectImageError, + setShowSelectImageError, + ] = module.state.showSelectImageError(false); + const [showSizeError, setShowSizeError] = module.state.showSelectImageError(false); const list = module.displayList({ ...searchSortProps, images }); React.useEffect(() => { @@ -49,18 +52,23 @@ export const imgListHooks = ({ }, []); return { - error: { + galleryError: { show: showSelectImageError, set: () => setShowSelectImageError(true), dismiss: () => setShowSelectImageError(false), }, + inputError: { + show: showSizeError, + set: () => setShowSizeError(true), + dismiss: () => setShowSizeError(false), + }, images, galleryProps: { galleryIsEmpty: Object.keys(images).length === 0, searchIsEmpty: list.length === 0, displayList: list, highlighted, - onHighlightChange: e => setHighlighted(e.target.value), + onHighlightChange: (e) => setHighlighted(e.target.value), }, // highlight by id selectBtnProps: { @@ -75,15 +83,40 @@ export const imgListHooks = ({ }; }; -export const fileInputHooks = ({ setSelection }) => { +export const checkValidFileSize = ({ + selectedFile, + clearSelection, + onSizeFail, +}) => { + // Check if the file size is greater than 10 MB, upload size limit + if (selectedFile.size > 1000000) { + clearSelection(); + onSizeFail(); + return false; + } + return true; +}; + +export const fileInputHooks = ({ setSelection, clearSelection, imgList }) => { const dispatch = useDispatch(); const ref = React.useRef(); const click = () => ref.current.click(); const addFile = (e) => { - dispatch(thunkActions.app.uploadImage({ - file: e.target.files[0], - setSelection, - })); + const selectedFile = e.target.files[0]; + if (selectedFile && module.checkValidFileSize({ + selectedFile, + clearSelection, + onSizeFail: () => { + imgList.inputError.set(); + }, + })) { + dispatch( + thunkActions.app.uploadImage({ + file: selectedFile, + setSelection, + }), + ); + } }; return { @@ -93,18 +126,24 @@ export const fileInputHooks = ({ setSelection }) => { }; }; -export const imgHooks = ({ setSelection }) => { +export const imgHooks = ({ setSelection, clearSelection }) => { const searchSortProps = module.searchAndSortHooks(); const imgList = module.imgListHooks({ setSelection, searchSortProps }); - const fileInput = module.fileInputHooks({ setSelection }); + const fileInput = module.fileInputHooks({ + setSelection, + clearSelection, + imgList, + }); const { - error, + galleryError, galleryProps, + inputError, selectBtnProps, } = imgList; return { - error, + galleryError, + inputError, fileInput, galleryProps, searchSortProps, diff --git a/src/editors/containers/TextEditor/components/SelectImageModal/hooks.test.js b/src/editors/containers/TextEditor/components/SelectImageModal/hooks.test.js index 95bb1115a..84465ec8a 100644 --- a/src/editors/containers/TextEditor/components/SelectImageModal/hooks.test.js +++ b/src/editors/containers/TextEditor/components/SelectImageModal/hooks.test.js @@ -54,7 +54,8 @@ jest.mock('../../../../data/redux', () => ({ const state = new MockUseState(hooks); const hookKeys = keyStore(hooks); let hook; -const testValue = 'testVALUE'; +const testValue = 'testVALUEVALIDIMAGE'; +const testValueInvalidImage = { value: 'testVALUEVALIDIMAGE', size: 90000000 }; describe('SelectImageModal hooks', () => { beforeEach(() => { @@ -66,6 +67,7 @@ describe('SelectImageModal hooks', () => { state.testGetter(state.keys.showSelectImageError); state.testGetter(state.keys.searchString); state.testGetter(state.keys.sortBy); + state.testGetter(state.keys.showSizeError); }); describe('using state', () => { @@ -122,6 +124,7 @@ describe('SelectImageModal hooks', () => { images: { p1: 'data1', p2: 'data2', p3: 'other distinct data' }, sortBy: sortKeys.dateNewest, searchString: 'test search string', + }; const load = (loadProps = {}) => { jest.spyOn(hooks, hookKeys.filteredList).mockImplementationOnce( @@ -210,20 +213,20 @@ describe('SelectImageModal hooks', () => { })); }); }); - describe('error', () => { + describe('galleryError', () => { test('show is initialized to false and returns properly', () => { const show = 'sHOWSelectiMaGEeRROr'; - expect(hook.error.show).toEqual(false); + expect(hook.galleryError.show).toEqual(false); state.mockVal(state.keys.showSelectImageError, show); hook = hooks.imgListHooks(props); - expect(hook.error.show).toEqual(show); + expect(hook.galleryError.show).toEqual(show); }); test('set sets showSelectImageError to true', () => { - hook.error.set(); + hook.galleryError.set(); expect(state.setState.showSelectImageError).toHaveBeenCalledWith(true); }); test('dismiss sets showSelectImageError to false', () => { - hook.error.dismiss(); + hook.galleryError.dismiss(); expect(state.setState.showSelectImageError).toHaveBeenCalledWith(false); }); // TODO @@ -238,10 +241,29 @@ describe('SelectImageModal hooks', () => { }); }); }); + describe('checkValidFileSize', () => { + const selectedFileFail = testValueInvalidImage; + const selectedFileSuccess = { value: testValue, size: 2000 }; + const clearSelection = jest.fn(); + const onSizeFail = jest.fn(); + it('returns false for valid file size ', () => { + hook = hooks.checkValidFileSize({ selectedFile: selectedFileFail, clearSelection, onSizeFail }); + expect(clearSelection).toHaveBeenCalled(); + expect(onSizeFail).toHaveBeenCalled(); + expect(hook).toEqual(false); + }); + it('returns true for valid file size', () => { + hook = hooks.checkValidFileSize({ selectedFile: selectedFileSuccess, clearSelection, onSizeFail }); + expect(hook).toEqual(true); + }); + }); describe('fileInputHooks', () => { const setSelection = jest.fn(); + const clearSelection = jest.fn(); + const imgList = { inputError: { show: true, dismiss: jest.fn(), set: jest.fn() } }; + const spies = {}; beforeEach(() => { - hook = hooks.fileInputHooks({ setSelection }); + hook = hooks.fileInputHooks({ setSelection, clearSelection, imgList }); }); it('returns a ref for the file input', () => { expect(hook.ref).toEqual({ current: undefined }); @@ -254,9 +276,23 @@ describe('SelectImageModal hooks', () => { expect(click).toHaveBeenCalled(); }); describe('addFile (uploadImage args)', () => { - const event = { target: { files: [testValue] } }; + const eventSuccess = { target: { files: [{ value: testValue, size: 2000 }] } }; + const eventFailure = { target: { files: [testValueInvalidImage] } }; + it('image fails to upload if file size is greater than 1000000', () => { + const checkValidFileSize = false; + spies.checkValidFileSize = jest.spyOn(hooks, hookKeys.checkValidFileSize) + .mockReturnValueOnce(checkValidFileSize); + hook.addFile(eventFailure); + expect(spies.checkValidFileSize.mock.calls.length).toEqual(1); + expect(spies.checkValidFileSize).toHaveReturnedWith(false); + }); it('dispatches uploadImage thunkAction with the first target file and setSelection', () => { - hook.addFile(event); + const checkValidFileSize = true; + spies.checkValidFileSize = jest.spyOn(hooks, hookKeys.checkValidFileSize) + .mockReturnValueOnce(checkValidFileSize); + hook.addFile(eventSuccess); + expect(spies.checkValidFileSize.mock.calls.length).toEqual(1); + expect(spies.checkValidFileSize).toHaveReturnedWith(true); expect(dispatch).toHaveBeenCalledWith(thunkActions.app.uploadImage({ file: testValue, setSelection, @@ -273,6 +309,7 @@ describe('SelectImageModal hooks', () => { const fileInputHooks = { file: 'input hooks' }; const setSelection = jest.fn(); + const clearSelection = jest.fn(); const spies = {}; beforeEach(() => { spies.imgList = jest.spyOn(hooks, hookKeys.imgListHooks) @@ -281,13 +318,13 @@ describe('SelectImageModal hooks', () => { .mockReturnValueOnce(searchAndSortHooks); spies.file = jest.spyOn(hooks, hookKeys.fileInputHooks) .mockReturnValueOnce(fileInputHooks); - hook = hooks.imgHooks({ setSelection }); + hook = hooks.imgHooks({ setSelection, clearSelection }); }); it('forwards fileInputHooks as fileInput, called with uploadImage prop', () => { expect(hook.fileInput).toEqual(fileInputHooks); expect(spies.file.mock.calls.length).toEqual(1); expect(spies.file).toHaveBeenCalledWith({ - setSelection, + setSelection, clearSelection, imgList: imgListHooks, }); }); it('initializes imgListHooks with setSelection and searchAndSortHooks', () => { @@ -300,7 +337,9 @@ describe('SelectImageModal hooks', () => { it('forwards searchAndSortHooks as searchSortProps', () => { expect(hook.searchSortProps).toEqual(searchAndSortHooks); expect(spies.file.mock.calls.length).toEqual(1); - expect(spies.file).toHaveBeenCalledWith({ setSelection }); + expect(spies.file).toHaveBeenCalledWith({ + setSelection, clearSelection, imgList: imgListHooks, + }); }); it('forwards galleryProps and selectBtnProps from the image list hooks', () => { expect(hook.galleryProps).toEqual(imgListHooks.galleryProps); diff --git a/src/editors/containers/TextEditor/components/SelectImageModal/index.jsx b/src/editors/containers/TextEditor/components/SelectImageModal/index.jsx index 22307ec42..4619b3917 100644 --- a/src/editors/containers/TextEditor/components/SelectImageModal/index.jsx +++ b/src/editors/containers/TextEditor/components/SelectImageModal/index.jsx @@ -1,9 +1,16 @@ import React from 'react'; +import { connect } from 'react-redux'; import PropTypes from 'prop-types'; -import { Button, Stack } from '@edx/paragon'; +import { Button, Stack, Spinner } from '@edx/paragon'; import { Add } from '@edx/paragon/icons'; -import { FormattedMessage, injectIntl, intlShape } from '@edx/frontend-platform/i18n'; +import { + FormattedMessage, + injectIntl, + intlShape, +} from '@edx/frontend-platform/i18n'; +import { selectors } from '../../../../data/redux'; +import { RequestKeys } from '../../../../data/constants/requests'; import hooks from './hooks'; import messages from './messages'; @@ -19,16 +26,21 @@ export const SelectImageModal = ({ isOpen, close, setSelection, + clearSelection, // injected intl, + // redux + inputIsLoading, }) => { const { - error, + galleryError, + inputError, fileInput, galleryProps, searchSortProps, selectBtnProps, - } = hooks.imgHooks({ setSelection }); + } = hooks.imgHooks({ setSelection, clearSelection }); + return ( - {/* Error Alerts */} + + + {/* User Feedback Alerts */} - - + {!inputIsLoading ? : ( + + )} - ); }; @@ -73,8 +95,17 @@ SelectImageModal.propTypes = { isOpen: PropTypes.bool.isRequired, close: PropTypes.func.isRequired, setSelection: PropTypes.func.isRequired, + clearSelection: PropTypes.func.isRequired, // injected intl: intlShape.isRequired, + // redux + inputIsLoading: PropTypes.bool.isRequired, }; -export default injectIntl(SelectImageModal); +export const mapStateToProps = (state) => ({ + inputIsLoading: selectors.requests.isPending(state, { requestKey: RequestKeys.uploadImage }), +}); + +export const mapDispatchToProps = {}; + +export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(SelectImageModal)); diff --git a/src/editors/containers/TextEditor/components/SelectImageModal/index.test.jsx b/src/editors/containers/TextEditor/components/SelectImageModal/index.test.jsx index 1bbfb43e5..807189495 100644 --- a/src/editors/containers/TextEditor/components/SelectImageModal/index.test.jsx +++ b/src/editors/containers/TextEditor/components/SelectImageModal/index.test.jsx @@ -2,12 +2,14 @@ import React from 'react'; import { shallow } from 'enzyme'; import { formatMessage } from '../../../../../testUtils'; +import { RequestKeys } from '../../../../data/constants/requests'; +import { selectors } from '../../../../data/redux'; import BaseModal from '../BaseModal'; import FileInput from './FileInput'; import Gallery from './Gallery'; import SearchSort from './SearchSort'; import hooks from './hooks'; -import { SelectImageModal } from '.'; +import { SelectImageModal, mapStateToProps, mapDispatchToProps } from '.'; jest.mock('../BaseModal', () => 'BaseModal'); jest.mock('./FileInput', () => 'FileInput'); @@ -15,11 +17,17 @@ jest.mock('./Gallery', () => 'Gallery'); jest.mock('./SearchSort', () => 'SearchSort'); jest.mock('../ErrorAlerts/FetchErrorAlert', () => 'FetchErrorAlert'); jest.mock('../ErrorAlerts/UploadErrorAlert', () => 'UploadErrorAlert'); +jest.mock('../ErrorAlerts/ErrorAlert', () => 'ErrorAlert'); jest.mock('./hooks', () => ({ imgHooks: jest.fn(() => ({ - error: { - show: 'ShoWERror', + galleryError: { + show: 'ShoWERror gAlLery', + set: jest.fn(), + dismiss: jest.fn(), + }, + inputError: { + show: 'ShoWERror inPUT', set: jest.fn(), dismiss: jest.fn(), }, @@ -34,13 +42,23 @@ jest.mock('./hooks', () => ({ })), })); +jest.mock('../../../../data/redux', () => ({ + selectors: { + requests: { + isPending: (state, { requestKey }) => ({ isPending: { state, requestKey } }), + }, + }, +})); + describe('SelectImageModal', () => { describe('component', () => { const props = { isOpen: true, close: jest.fn().mockName('props.close'), setSelection: jest.fn().mockName('props.setSelection'), + clearSelection: jest.fn().mockName('props.clearSelection'), intl: { formatMessage }, + inputIsLoading: false, }; let el; const imgHooks = hooks.imgHooks(); @@ -50,6 +68,11 @@ describe('SelectImageModal', () => { test('snapshot', () => { expect(el).toMatchSnapshot(); }); + test('snapshot: uploaded image not loaded, show spinner', () => { + props.inputIsLoading = true; + expect(shallow()).toMatchSnapshot(); + props.inputIsLoading = false; + }); it('provides confirm action, forwarding selectBtnProps from imgHooks', () => { expect(el.find(BaseModal).props().confirmAction.props).toEqual( expect.objectContaining({ ...hooks.imgHooks().selectBtnProps, variant: 'primary' }), @@ -70,4 +93,17 @@ describe('SelectImageModal', () => { expect(el.find(FileInput).props()).toMatchObject({ fileInput: imgHooks.fileInput }); }); }); + describe('mapStateToProps', () => { + const testState = { some: 'testState' }; + test('loads inputIsLoading from requests.isPending selector for uploadImage request', () => { + expect(mapStateToProps(testState).inputIsLoading).toEqual( + selectors.requests.isPending(testState, { requestKey: RequestKeys.uploadImage }), + ); + }); + }); + describe('mapDispatchToProps', () => { + test('is empty', () => { + expect(mapDispatchToProps).toEqual({}); + }); + }); }); diff --git a/src/editors/containers/TextEditor/components/SelectImageModal/messages.js b/src/editors/containers/TextEditor/components/SelectImageModal/messages.js index 1f106c39b..88532fe96 100644 --- a/src/editors/containers/TextEditor/components/SelectImageModal/messages.js +++ b/src/editors/containers/TextEditor/components/SelectImageModal/messages.js @@ -6,7 +6,7 @@ export const messages = { }, uploadButtonLabel: { id: 'authoring.texteditor.selectimagemodal.upload.label', - defaultMessage: 'Upload a new image', + defaultMessage: 'Upload a new image (10 MB max)', description: 'Label for upload button', }, titleLabel: { @@ -55,7 +55,8 @@ export const messages = { }, emptyGalleryLabel: { id: 'authoring.texteditor.selectimagemodal.emptyGalleryLabel', - defaultMessage: 'No images found in your gallery. Please upload an image using the button below.', + defaultMessage: + 'No images found in your gallery. Please upload an image using the button below.', description: 'Label for when image gallery is empty.', }, emptySearchLabel: { @@ -72,18 +73,26 @@ export const messages = { }, uploadImageError: { id: 'authoring.texteditor.selectimagemodal.error.uploadImageError', - defaultMessage: 'Failed to Upload Image. Please Try again.', + defaultMessage: 'Failed to upload image. Please try again.', description: 'Message presented to user when image fails to upload', }, fetchImagesError: { id: 'authoring.texteditor.selectimagemodal.error.fetchImagesError', - defaultMessage: 'Failed to obtain course Images. Please Try again.', + defaultMessage: 'Failed to obtain course images. Please try again.', description: 'Message presented to user when images are not found', }, + fileSizeError: { + id: 'authoring.texteditor.selectimagemodal.error.fileSizeError', + defaultMessage: + 'Images must be 10 MB or less. Please resize image and try again.', + description: + ' Message presented to user when file size of image is larger than 10 MB', + }, selectImageError: { id: 'authoring.texteditor.selectimagemodal.error.selectImageError', defaultMessage: 'Select an image to continue.', - description: 'Message presented to user when clicking Next without selecting an image', + description: + 'Message presented to user when clicking Next without selecting an image', }, }; diff --git a/src/editors/containers/TextEditor/components/__snapshots__/ImageUploadModal.test.jsx.snap b/src/editors/containers/TextEditor/components/__snapshots__/ImageUploadModal.test.jsx.snap index 8a38a33e4..a3c78ef73 100644 --- a/src/editors/containers/TextEditor/components/__snapshots__/ImageUploadModal.test.jsx.snap +++ b/src/editors/containers/TextEditor/components/__snapshots__/ImageUploadModal.test.jsx.snap @@ -2,6 +2,7 @@ exports[`ImageUploadModal component snapshot: no selection (Select Image Modal) 1`] = ` (dispatch) => { dispatch(actions.requests.startRequest(requestKey)); - return promise.then((response) => { - if (onSuccess) { onSuccess(response); } - dispatch(actions.requests.completeRequest({ requestKey, response })); - }).catch((error) => { - if (onFailure) { onFailure(error); } - dispatch(actions.requests.failRequest({ requestKey, error })); - }); + return promise + .then((response) => { + if (onSuccess) { + onSuccess(response); + } + dispatch(actions.requests.completeRequest({ requestKey, response })); + }) + .catch((error) => { + if (onFailure) { + onFailure(error); + } + dispatch(actions.requests.failRequest({ requestKey, error })); + }); }; /** @@ -119,10 +125,12 @@ export const uploadImage = ({ image, ...rest }) => (dispatch, getState) => { export const fetchImages = ({ ...rest }) => (dispatch, getState) => { dispatch(module.networkRequest({ requestKey: RequestKeys.fetchImages, - promise: api.fetchImages({ - studioEndpointUrl: selectors.app.studioEndpointUrl(getState()), - learningContextId: selectors.app.learningContextId(getState()), - }).then((response) => loadImages(response.data.assets)), + promise: api + .fetchImages({ + studioEndpointUrl: selectors.app.studioEndpointUrl(getState()), + learningContextId: selectors.app.learningContextId(getState()), + }) + .then((response) => loadImages(response.data.assets)), ...rest, })); };