From 0529a7aee430f8c6e8f7cacf7005585b21c6a7dd Mon Sep 17 00:00:00 2001 From: Caleb Pollman Date: Fri, 20 Sep 2024 13:14:53 -0700 Subject: [PATCH 1/5] fix(file-uploader): fix duplicate upload requests --- .../components/FileUploader/FileUploader.tsx | 5 +- .../useFileUploader/__tests__/reducer.test.ts | 13 ++- .../__tests__/useFileUploader.test.ts | 13 +-- .../hooks/useFileUploader/actions.ts | 21 ++-- .../hooks/useFileUploader/reducer.ts | 35 +++--- .../hooks/useFileUploader/types.ts | 22 ++-- .../hooks/useFileUploader/useFileUploader.ts | 108 +++++++----------- .../__tests__/useUploadFiles.spec.ts | 3 +- .../hooks/useUploadFiles/useUploadFiles.ts | 19 +-- .../src/components/FileUploader/types.ts | 4 +- .../components/FileUploader/utils/getInput.ts | 7 -- .../FileUploader/utils/uploadFile.ts | 1 - 12 files changed, 95 insertions(+), 156 deletions(-) diff --git a/packages/react-storage/src/components/FileUploader/FileUploader.tsx b/packages/react-storage/src/components/FileUploader/FileUploader.tsx index 34b396b2bde..3ce0def4ac2 100644 --- a/packages/react-storage/src/components/FileUploader/FileUploader.tsx +++ b/packages/react-storage/src/components/FileUploader/FileUploader.tsx @@ -111,7 +111,6 @@ const FileUploaderBase = React.forwardRef(function FileUploader( files, removeUpload, queueFiles, - setProcessedKey, setUploadingFile, setUploadPaused, setUploadProgress, @@ -149,7 +148,6 @@ const FileUploaderBase = React.forwardRef(function FileUploader( onUploadError, onUploadSuccess, onUploadStart, - onProcessFileSuccess: setProcessedKey, setUploadingFile, setUploadProgress, setUploadSuccess, @@ -205,8 +203,7 @@ const FileUploaderBase = React.forwardRef(function FileUploader( if (typeof onFileRemove === 'function') { const file = files.find((file) => file.id === id); if (file) { - // return `processedKey` if available and `processFile` is provided - const key = (processFile && file?.processedKey) ?? file.key; + const key = file.resolvedKey ?? file.key; onFileRemove({ key }); } } diff --git a/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/__tests__/reducer.test.ts b/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/__tests__/reducer.test.ts index 9086d4796ae..32c5fba1d7b 100644 --- a/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/__tests__/reducer.test.ts +++ b/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/__tests__/reducer.test.ts @@ -310,7 +310,7 @@ describe('fileUploaderStateReducer', () => { expect(result.current.state.files).toEqual([file]); }); - it('updates the key of a target file on SET_PROCESSED_FILE_KEY', () => { + it('updates the resolvedKey of a target file on SET_STATUS_SUCESS', () => { const file: StorageFile = { id: imageFile.name, file: imageFile, @@ -328,18 +328,19 @@ describe('fileUploaderStateReducer', () => { return { state, dispatch }; }); - const processedKey = `processed-${imageFile.name}`; + const resolvedKey = `processed-${imageFile.name}`; const action: Action = { - type: FileUploaderActionTypes.SET_PROCESSED_FILE_KEY, + type: FileUploaderActionTypes.SET_STATUS_UPLOADED, id: imageFile.name, - processedKey, + resolvedKey, + status: FileStatus.UPLOADED, }; - expect(result.current.state.files[0].processedKey).toBeUndefined(); + expect(result.current.state.files[0].resolvedKey).toBeUndefined(); act(() => result.current.dispatch(action)); - expect(result.current.state.files[0].processedKey).toBe(processedKey); + expect(result.current.state.files[0].resolvedKey).toBe(resolvedKey); }); it('should only change added files to queued in QUEUE_FILES action', () => { diff --git a/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/__tests__/useFileUploader.test.ts b/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/__tests__/useFileUploader.test.ts index dd8e7fd71dc..66ffda6ca44 100644 --- a/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/__tests__/useFileUploader.test.ts +++ b/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/__tests__/useFileUploader.test.ts @@ -124,6 +124,7 @@ describe('useUploadFiles', () => { act(() => result.current.setUploadSuccess({ id: 'file1', + resolvedKey: 'file1', }) ); @@ -166,18 +167,6 @@ describe('useUploadFiles', () => { expect(result.current.files.length).toBe(1); }); - it('should update a target file key', () => { - const { result } = renderHook(() => useFileUploader(defaultFiles)); - - const processedKey = 'processedKey'; - - expect(result.current.files[0].processedKey).toBeUndefined(); - - act(() => result.current.setProcessedKey({ id: 'file1', processedKey })); - - expect(result.current.files[0].processedKey).toBe(processedKey); - }); - describe('defaultFiles', () => { it('should handle good defaultFiles', () => { const { result } = renderHook(() => diff --git a/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/actions.ts b/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/actions.ts index 9ee45720360..53afad8801d 100644 --- a/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/actions.ts +++ b/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/actions.ts @@ -22,14 +22,6 @@ export const queueFilesAction = (): Action => ({ type: FileUploaderActionTypes.QUEUE_FILES, }); -export const setProcessedKeyAction = (input: { - id: string; - processedKey: string; -}): Action => ({ - ...input, - type: FileUploaderActionTypes.SET_PROCESSED_FILE_KEY, -}); - export const setUploadingFileAction = ({ id, uploadTask, @@ -63,6 +55,19 @@ export const setUploadStatusAction = ({ status, }); +export const setUploadSuccessAction = ({ + id, + resolvedKey, +}: { + id: string; + resolvedKey: string; +}): Action => ({ + type: FileUploaderActionTypes.SET_STATUS_UPLOADED, + id, + resolvedKey, + status: FileStatus.UPLOADED, +}); + export const removeUploadAction = ({ id }: { id: string }): Action => ({ type: FileUploaderActionTypes.REMOVE_UPLOAD, id, diff --git a/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/reducer.ts b/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/reducer.ts index c255401481d..cf4456334c4 100644 --- a/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/reducer.ts +++ b/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/reducer.ts @@ -46,8 +46,8 @@ export function fileUploaderStateReducer( case FileUploaderActionTypes.QUEUE_FILES: { const { files } = state; - const newFiles = files.reduce((files, currentFile) => { - return [ + const newFiles = files.reduce( + (files, currentFile) => [ ...files, { ...currentFile, @@ -55,12 +55,10 @@ export function fileUploaderStateReducer( ? { status: FileStatus.QUEUED } : {}), }, - ]; - }, []); - return { - ...state, - files: newFiles, - }; + ], + [] + ); + return { ...state, files: newFiles }; } case FileUploaderActionTypes.SET_STATUS_UPLOADING: { const { id, uploadTask } = action; @@ -72,11 +70,9 @@ export function fileUploaderStateReducer( return { ...state, files }; } - case FileUploaderActionTypes.SET_PROCESSED_FILE_KEY: { - const { processedKey, id } = action; - const files = updateFiles(state.files, { processedKey, id }); - - return { files }; + case FileUploaderActionTypes.SET_STATUS_UPLOADED: { + const files = updateFiles(state.files, action); + return { ...state, files }; } case FileUploaderActionTypes.SET_UPLOAD_PROGRESS: { const { id, progress } = action; @@ -95,16 +91,11 @@ export function fileUploaderStateReducer( const { files } = state; const newFiles = files.reduce((files, currentFile) => { - if (currentFile.id === id) { - // remove by not returning currentFile - return [...files]; - } - return [...files, currentFile]; + // remove by not returning currentFile + return currentFile.id === id ? [...files] : [...files, currentFile]; }, []); - return { - ...state, - files: newFiles, - }; + + return { ...state, files: newFiles }; } } } diff --git a/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/types.ts b/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/types.ts index 4c92334de0a..49db943542c 100644 --- a/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/types.ts +++ b/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/types.ts @@ -6,14 +6,15 @@ export interface UseFileUploaderState { } export enum FileUploaderActionTypes { - ADD_FILES = 'ADD_FILES', - CLEAR_FILES = 'CLEAR_FILES', - QUEUE_FILES = 'QUEUE_FILES', - SET_STATUS = 'SET_STATUS', - SET_PROCESSED_FILE_KEY = 'SET_PROCESSED_FILE_KEY', - SET_STATUS_UPLOADING = 'SET_STATUS_UPLOADING', - SET_UPLOAD_PROGRESS = 'SET_UPLOAD_PROGRESS', - REMOVE_UPLOAD = 'REMOVE_UPLOAD', + ADD_FILES, + CLEAR_FILES, + QUEUE_FILES, + REMOVE_UPLOAD, + SET_STATUS, + SET_PROCESSED_FILE_KEY, + SET_STATUS_UPLOADED, + SET_STATUS_UPLOADING, + SET_UPLOAD_PROGRESS, } export type GetFileErrorMessage = (file: File) => string; @@ -47,9 +48,10 @@ export type Action = progress: number; } | { - type: FileUploaderActionTypes.SET_PROCESSED_FILE_KEY; + type: FileUploaderActionTypes.SET_STATUS_UPLOADED; id: string; - processedKey: string; + resolvedKey: string; + status: FileStatus.UPLOADED; } | { type: FileUploaderActionTypes.REMOVE_UPLOAD; diff --git a/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/useFileUploader.ts b/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/useFileUploader.ts index 550704085ce..be4e9a2c395 100644 --- a/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/useFileUploader.ts +++ b/packages/react-storage/src/components/FileUploader/hooks/useFileUploader/useFileUploader.ts @@ -10,10 +10,10 @@ import { clearFilesAction, queueFilesAction, removeUploadAction, - setProcessedKeyAction, setUploadingFileAction, setUploadProgressAction, setUploadStatusAction, + setUploadSuccessAction, } from './actions'; import { TaskHandler } from '../../utils'; @@ -24,15 +24,14 @@ export interface UseFileUploader { getFileErrorMessage: GetFileErrorMessage; }) => void; clearFiles: () => void; + files: StorageFiles; queueFiles: () => void; + removeUpload: (params: { id: string }) => void; setUploadingFile: TaskHandler; - setProcessedKey: (params: { id: string; processedKey: string }) => void; + setUploadPaused: (params: { id: string }) => void; setUploadProgress: (params: { id: string; progress: number }) => void; - setUploadSuccess: (params: { id: string }) => void; setUploadResumed: (params: { id: string }) => void; - setUploadPaused: (params: { id: string }) => void; - removeUpload: (params: { id: string }) => void; - files: StorageFiles; + setUploadSuccess: (params: { id: string; resolvedKey: string }) => void; } const isDefaultFile = (file: unknown): file is DefaultFile => @@ -54,67 +53,38 @@ export function useFileUploader( : []) as StorageFiles, }); - const addFiles: UseFileUploader['addFiles'] = ({ - files, - status, - getFileErrorMessage, - }) => { - dispatch(addFilesAction({ files, status, getFileErrorMessage })); - }; - - const clearFiles: UseFileUploader['clearFiles'] = () => { - dispatch(clearFilesAction()); - }; - - const queueFiles: UseFileUploader['queueFiles'] = () => { - dispatch(queueFilesAction()); - }; - - const setUploadingFile: UseFileUploader['setUploadingFile'] = ({ - uploadTask, - id, - }) => { - dispatch(setUploadingFileAction({ id, uploadTask })); - }; - - const setProcessedKey: UseFileUploader['setProcessedKey'] = (input) => { - dispatch(setProcessedKeyAction(input)); - }; - - const setUploadProgress: UseFileUploader['setUploadProgress'] = ({ - progress, - id, - }) => { - dispatch(setUploadProgressAction({ id, progress })); - }; - - const setUploadSuccess: UseFileUploader['setUploadSuccess'] = ({ id }) => { - dispatch(setUploadStatusAction({ id, status: FileStatus.UPLOADED })); - }; - - const setUploadPaused: UseFileUploader['setUploadPaused'] = ({ id }) => { - dispatch(setUploadStatusAction({ id, status: FileStatus.PAUSED })); - }; - - const setUploadResumed: UseFileUploader['setUploadPaused'] = ({ id }) => { - dispatch(setUploadStatusAction({ id, status: FileStatus.UPLOADING })); - }; - - const removeUpload: UseFileUploader['removeUpload'] = ({ id }) => { - dispatch(removeUploadAction({ id })); - }; - - return { - removeUpload, - setProcessedKey, - setUploadPaused, - setUploadProgress, - setUploadResumed, - setUploadSuccess, - setUploadingFile, - queueFiles, - addFiles, - clearFiles, - files, - }; + const dispatchers: Omit = React.useMemo( + () => ({ + addFiles: (params) => { + dispatch(addFilesAction(params)); + }, + clearFiles: () => { + dispatch(clearFilesAction()); + }, + queueFiles: () => { + dispatch(queueFilesAction()); + }, + setUploadingFile: (params) => { + dispatch(setUploadingFileAction(params)); + }, + setUploadProgress: (params) => { + dispatch(setUploadProgressAction(params)); + }, + setUploadSuccess: (params) => { + dispatch(setUploadSuccessAction(params)); + }, + setUploadPaused: ({ id }) => { + dispatch(setUploadStatusAction({ id, status: FileStatus.PAUSED })); + }, + setUploadResumed: ({ id }) => { + dispatch(setUploadStatusAction({ id, status: FileStatus.UPLOADING })); + }, + removeUpload: ({ id }) => { + dispatch(removeUploadAction({ id })); + }, + }), + [] + ); + + return { ...dispatchers, files }; } diff --git a/packages/react-storage/src/components/FileUploader/hooks/useUploadFiles/__tests__/useUploadFiles.spec.ts b/packages/react-storage/src/components/FileUploader/hooks/useUploadFiles/__tests__/useUploadFiles.spec.ts index 08ee387c1b1..78bc3af2e97 100644 --- a/packages/react-storage/src/components/FileUploader/hooks/useUploadFiles/__tests__/useUploadFiles.spec.ts +++ b/packages/react-storage/src/components/FileUploader/hooks/useUploadFiles/__tests__/useUploadFiles.spec.ts @@ -39,7 +39,6 @@ const mockQueuedFile: StorageFile = { file: imageFile, }; -const mockOnProcessFileSuccess = jest.fn(); const mockOnUploadError = jest.fn(); const mockOnUploadStart = jest.fn(); const mockSetUploadingFile = jest.fn(); @@ -48,7 +47,6 @@ const mockSetUploadSuccess = jest.fn(); const props: Omit = { accessLevel: 'guest', maxFileCount: 2, - onProcessFileSuccess: mockOnProcessFileSuccess, onUploadError: mockOnUploadError, onUploadStart: mockOnUploadStart, setUploadingFile: mockSetUploadingFile, @@ -80,6 +78,7 @@ describe('useUploadFiles', () => { expect(mockSetUploadSuccess).toHaveBeenCalledTimes(1); expect(mockSetUploadSuccess).toHaveBeenCalledWith({ id: mockQueuedFile.id, + resolvedKey: 'key', }); expect(mockSetUploadSuccess).not.toHaveBeenCalledWith({ id: mockUploadingFile.id, diff --git a/packages/react-storage/src/components/FileUploader/hooks/useUploadFiles/useUploadFiles.ts b/packages/react-storage/src/components/FileUploader/hooks/useUploadFiles/useUploadFiles.ts index 757f30c6091..26ad1ba119c 100644 --- a/packages/react-storage/src/components/FileUploader/hooks/useUploadFiles/useUploadFiles.ts +++ b/packages/react-storage/src/components/FileUploader/hooks/useUploadFiles/useUploadFiles.ts @@ -25,7 +25,6 @@ export interface UseUploadFilesProps 'setUploadingFile' | 'setUploadProgress' | 'setUploadSuccess' | 'files' > { accessLevel?: FileUploaderProps['accessLevel']; - onProcessFileSuccess: (input: { id: string; processedKey: string }) => void; path?: string | PathCallback; } @@ -34,7 +33,6 @@ export function useUploadFiles({ files, isResumable, maxFileCount, - onProcessFileSuccess, onUploadError, onUploadStart, onUploadSuccess, @@ -68,14 +66,10 @@ export function useUploadFiles({ }; if (file) { - const handleProcessFileSuccess = (input: { processedKey: string }) => - onProcessFileSuccess({ id, ...input }); - const input = getInput({ accessLevel, file, key, - onProcessFileSuccess: handleProcessFileSuccess, onProgress, path, processFile, @@ -85,14 +79,14 @@ export function useUploadFiles({ uploadFile({ input, onComplete: (event) => { + const resolvedKey = + (event as { key: string }).key ?? + (event as { path: string }).path; + if (isFunction(onUploadSuccess)) { - onUploadSuccess({ - key: - (event as { key: string }).key ?? - (event as { path: string }).path, - }); + onUploadSuccess({ key: resolvedKey }); } - setUploadSuccess({ id }); + setUploadSuccess({ id, resolvedKey }); }, onError: ({ key, error }) => { if (isFunction(onUploadError)) { @@ -115,7 +109,6 @@ export function useUploadFiles({ setUploadProgress, setUploadingFile, onUploadError, - onProcessFileSuccess, onUploadSuccess, onUploadStart, maxFileCount, diff --git a/packages/react-storage/src/components/FileUploader/types.ts b/packages/react-storage/src/components/FileUploader/types.ts index 83167fa0c7e..0fa3cfb43dc 100644 --- a/packages/react-storage/src/components/FileUploader/types.ts +++ b/packages/react-storage/src/components/FileUploader/types.ts @@ -26,8 +26,8 @@ export interface StorageFile { file?: File; status: FileStatus; progress: number; - // only present after `processFile` complete - processedKey?: string; + // only present after an upload completes + resolvedKey?: string; uploadTask?: UploadTask; key: string; error: string; diff --git a/packages/react-storage/src/components/FileUploader/utils/getInput.ts b/packages/react-storage/src/components/FileUploader/utils/getInput.ts index 9f810d1d1b1..7a6f29b6d19 100644 --- a/packages/react-storage/src/components/FileUploader/utils/getInput.ts +++ b/packages/react-storage/src/components/FileUploader/utils/getInput.ts @@ -12,7 +12,6 @@ export interface GetInputParams { accessLevel: StorageAccessLevel | undefined; file: File; key: string; - onProcessFileSuccess: (input: { processedKey: string }) => void; onProgress: NonNullable['onProgress']; path: string | PathCallback | undefined; processFile: ProcessFile | undefined; @@ -23,7 +22,6 @@ export const getInput = ({ accessLevel, file, key, - onProcessFileSuccess, onProgress, path, processFile, @@ -67,11 +65,6 @@ export const getInput = ({ inputResult = { data: file, path: resolvedPath, options }; } - if (processFile) { - // provide post-processing value of target `key` - onProcessFileSuccess({ processedKey }); - } - return inputResult; }; }; diff --git a/packages/react-storage/src/components/FileUploader/utils/uploadFile.ts b/packages/react-storage/src/components/FileUploader/utils/uploadFile.ts index 762dbdca2ee..8484539e4e7 100644 --- a/packages/react-storage/src/components/FileUploader/utils/uploadFile.ts +++ b/packages/react-storage/src/components/FileUploader/utils/uploadFile.ts @@ -50,7 +50,6 @@ export async function uploadFile({ onComplete, }: UploadFileProps): Promise { const resolvedInput = await input(); - const uploadTask = (uploadData as UploadData)(resolvedInput); const key = From f1cce6733f25d7676034688894c834b72dc12511 Mon Sep 17 00:00:00 2001 From: Caleb Pollman Date: Fri, 20 Sep 2024 14:57:34 -0700 Subject: [PATCH 2/5] fix unit tests --- .../__tests__/FileUploader.test.tsx | 45 +++++++++++---- .../utils/__tests__/getInput.spec.ts | 57 ------------------- 2 files changed, 34 insertions(+), 68 deletions(-) diff --git a/packages/react-storage/src/components/FileUploader/__tests__/FileUploader.test.tsx b/packages/react-storage/src/components/FileUploader/__tests__/FileUploader.test.tsx index abc5533c1ed..9a63d2b5603 100644 --- a/packages/react-storage/src/components/FileUploader/__tests__/FileUploader.test.tsx +++ b/packages/react-storage/src/components/FileUploader/__tests__/FileUploader.test.tsx @@ -15,6 +15,7 @@ import { FileUploader, MISSING_REQUIRED_PROPS_MESSAGE, ACCESS_LEVEL_DEPRECATION_MESSAGE, + ACCESS_LEVEL_WITH_PATH_CALLBACK_MESSAGE, } from '../FileUploader'; import { FileUploaderProps, FileUploaderHandle, FileStatus } from '../types'; import { defaultFileUploaderDisplayText } from '../utils'; @@ -28,7 +29,10 @@ const uploadDataSpy = jest pause: jest.fn(), resume: jest.fn(), state: 'SUCCESS', - result: Promise.resolve({ key: input.key, data: input.data }), + result: Promise.resolve({ + key: (input as { path?: string })?.path ?? input.key, + data: input.data, + }), })); const fileUploaderProps: FileUploaderProps = { @@ -155,6 +159,22 @@ describe('FileUploader', () => { expect(getByText('Select Files')).toBeVisible(); }); + it('displays error message when provided with `path` callback and `accessLevel` props', () => { + // turn off error logging in console for test output + jest.spyOn(console, 'error').mockImplementation(); + + expect(() => + render( + // @ts-expect-error providign callback `path` and `accessLevel` are disallowd by TS + 'path'} + maxFileCount={12} + /> + ) + ).toThrow(ACCESS_LEVEL_WITH_PATH_CALLBACK_MESSAGE); + }); + it('displays error message when file exceeds max file size', () => { const maxFileSize = 0; const { getByText } = render( @@ -324,7 +344,7 @@ describe('FileUploader', () => { }); }); - it('provides the processed file key on a remove file event after upload when processFile is provided', async () => { + it('provides the resolved file key on a remove file event after upload when processFile is provided', async () => { const onFileRemove = jest.fn(); const processedKey = 'processedKey'; @@ -369,6 +389,7 @@ describe('FileUploader', () => { it('provides the processed file key on a remove file event after upload when processFile is provided with a path function', async () => { const onFileRemove = jest.fn(); + const path = () => 'my-path'; const processedKey = 'processedKey'; const processFile: FileUploaderProps['processFile'] = (input) => ({ @@ -381,7 +402,7 @@ describe('FileUploader', () => { {...fileUploaderProps} onFileRemove={onFileRemove} processFile={processFile} - path={() => 'my-path'} + path={path} accessLevel={undefined} /> ); @@ -398,17 +419,19 @@ describe('FileUploader', () => { // Wait for the file to be uploaded await waitFor(() => { expect(uploadDataSpy).toHaveBeenCalled(); + }); - const removeButton = getByTestId( - container, - 'storage-manager-remove-button' - ); - expect(removeButton).toBeDefined(); + const removeButton = getByTestId( + container, + 'storage-manager-remove-button' + ); + expect(removeButton).toBeDefined(); - fireEvent.click(removeButton); + fireEvent.click(removeButton); - expect(onFileRemove).toHaveBeenCalledTimes(1); - expect(onFileRemove).toHaveBeenCalledWith({ key: processedKey }); + expect(onFileRemove).toHaveBeenCalledTimes(1); + expect(onFileRemove).toHaveBeenCalledWith({ + key: `${path()}processedKey`, }); }); diff --git a/packages/react-storage/src/components/FileUploader/utils/__tests__/getInput.spec.ts b/packages/react-storage/src/components/FileUploader/utils/__tests__/getInput.spec.ts index 901b261ee8b..fa5537ea91e 100644 --- a/packages/react-storage/src/components/FileUploader/utils/__tests__/getInput.spec.ts +++ b/packages/react-storage/src/components/FileUploader/utils/__tests__/getInput.spec.ts @@ -20,13 +20,11 @@ const processFile: GetInputParams['processFile'] = ({ key, ...rest }) => ({ const stringPath = 'my-path/'; -const onProcessFileSuccess = jest.fn(); const inputBase: Omit = { file, key, onProgress, processFile: undefined, - onProcessFileSuccess, }; const pathStringInput: GetInputParams = { ...inputBase, @@ -55,7 +53,6 @@ const accessLevelWithPathInput: GetInputParams = { describe('getInput', () => { beforeEach(() => { - onProcessFileSuccess.mockClear(); fetchAuthSpy.mockClear(); }); @@ -151,33 +148,6 @@ describe('getInput', () => { expect(output).toStrictEqual(expected); }); - it('calls `onProcessFileSuccess` when `processFile` is provided', async () => { - const processedKey = `processedKey`; - - const input = getInput({ - ...pathStringInput, - processFile: ({ key: _, ...rest }) => ({ - key: processedKey, - ...rest, - }), - }); - - await input(); - - expect(onProcessFileSuccess).toHaveBeenCalledTimes(1); - expect(onProcessFileSuccess).toHaveBeenCalledWith({ - processedKey, - }); - }); - - it('does not call `onProcessFileSuccess` when `processFile` is not provided', async () => { - const input = getInput(pathStringInput); - - await input(); - - expect(onProcessFileSuccess).not.toHaveBeenCalled(); - }); - it('includes additional values returned from `processFile` in `options`', async () => { const contentDisposition = 'attachment'; const metadata = { key }; @@ -213,33 +183,6 @@ describe('getInput', () => { ); }); - it('calls `onProcessFileSuccess` after fetchAuthSession', async () => { - const processedKey = `processedKey`; - - const input = getInput({ - ...pathCallbackInput, - processFile: ({ key: _, ...rest }) => ({ - key: processedKey, - ...rest, - }), - }); - - await input(); - - const fetchAuthSessionCallOrder = fetchAuthSpy.mock.invocationCallOrder[0]; - const onProcessFileSuccessCallORder = - onProcessFileSuccess.mock.invocationCallOrder[0]; - expect(fetchAuthSessionCallOrder).toBeLessThan( - onProcessFileSuccessCallORder - ); - - expect(fetchAuthSpy).toHaveBeenCalledTimes(1); - expect(onProcessFileSuccess).toHaveBeenCalledTimes(1); - expect(onProcessFileSuccess).toHaveBeenCalledWith({ - processedKey, - }); - }); - it('defaults `options.contentType` to "binary/octet-stream" when no file type is provided', async () => { const data = new File(['hello'], 'hello.png'); const expected: UploadDataWithPathInput = { From b6ca9b8355e1f2b788bd0e913c7aa9113dd328ed Mon Sep 17 00:00:00 2001 From: Caleb Pollman Date: Fri, 20 Sep 2024 16:42:52 -0700 Subject: [PATCH 3/5] remove duplicated StorageManager utils and hooks --- .../StorageManager/StorageManager.tsx | 18 +- .../__tests__/StorageManager.test.tsx | 82 ++-- .../components/StorageManager/hooks/index.ts | 2 - .../__tests__/actions.test.ts | 103 ----- .../__tests__/reducer.test.ts | 399 ------------------ .../__tests__/useStorageManager.test.ts | 209 --------- .../hooks/useStorageManager/actions.ts | 73 ---- .../hooks/useStorageManager/index.ts | 1 - .../hooks/useStorageManager/reducer.ts | 114 ----- .../hooks/useStorageManager/types.ts | 63 --- .../useStorageManager/useStorageManager.ts | 123 ------ .../__tests__/useUploadFiles.spec.ts | 217 ---------- .../hooks/useUploadFiles/index.ts | 1 - .../hooks/useUploadFiles/useUploadFiles.ts | 127 ------ .../src/components/StorageManager/types.ts | 17 +- .../ui/DropZone/__tests__/DropZone.test.tsx | 2 +- .../StorageManager/ui/DropZone/types.ts | 2 +- .../ui/FileList/FileControl.tsx | 3 +- .../StorageManager/ui/FileList/FileList.tsx | 2 +- .../ui/FileList/FileStatusMessage.tsx | 2 +- .../FileList/__tests__/FileControl.test.tsx | 5 +- .../ui/FileList/__tests__/FileList.test.tsx | 4 +- .../__tests__/FileStatusMessage.test.tsx | 2 +- .../StorageManager/ui/FileList/types.ts | 7 +- .../ui/FileListFooter/FileListFooter.tsx | 2 +- .../ui/FileListHeader/FileListHeader.tsx | 3 +- .../__tests__/FileListHeader.test.tsx | 2 +- .../utils/__tests__/checkMaxFileSize.test.ts | 38 -- .../__tests__/filterAllowedFiles.test.ts | 72 ---- .../utils/__tests__/getInput.spec.ts | 284 ------------- .../utils/__tests__/uploadFile.test.ts | 161 ------- .../StorageManager/utils/checkMaxFileSize.ts | 17 - .../StorageManager/utils/displayText.ts | 65 --- .../utils/filterAllowedFiles.ts | 30 -- .../StorageManager/utils/getInput.ts | 77 ---- .../components/StorageManager/utils/index.ts | 16 - .../StorageManager/utils/resolveFile.ts | 23 - .../StorageManager/utils/uploadFile.ts | 76 ---- 38 files changed, 87 insertions(+), 2357 deletions(-) delete mode 100644 packages/react-storage/src/components/StorageManager/hooks/index.ts delete mode 100644 packages/react-storage/src/components/StorageManager/hooks/useStorageManager/__tests__/actions.test.ts delete mode 100644 packages/react-storage/src/components/StorageManager/hooks/useStorageManager/__tests__/reducer.test.ts delete mode 100644 packages/react-storage/src/components/StorageManager/hooks/useStorageManager/__tests__/useStorageManager.test.ts delete mode 100644 packages/react-storage/src/components/StorageManager/hooks/useStorageManager/actions.ts delete mode 100644 packages/react-storage/src/components/StorageManager/hooks/useStorageManager/index.ts delete mode 100644 packages/react-storage/src/components/StorageManager/hooks/useStorageManager/reducer.ts delete mode 100644 packages/react-storage/src/components/StorageManager/hooks/useStorageManager/types.ts delete mode 100644 packages/react-storage/src/components/StorageManager/hooks/useStorageManager/useStorageManager.ts delete mode 100644 packages/react-storage/src/components/StorageManager/hooks/useUploadFiles/__tests__/useUploadFiles.spec.ts delete mode 100644 packages/react-storage/src/components/StorageManager/hooks/useUploadFiles/index.ts delete mode 100644 packages/react-storage/src/components/StorageManager/hooks/useUploadFiles/useUploadFiles.ts delete mode 100644 packages/react-storage/src/components/StorageManager/utils/__tests__/checkMaxFileSize.test.ts delete mode 100644 packages/react-storage/src/components/StorageManager/utils/__tests__/filterAllowedFiles.test.ts delete mode 100644 packages/react-storage/src/components/StorageManager/utils/__tests__/getInput.spec.ts delete mode 100644 packages/react-storage/src/components/StorageManager/utils/__tests__/uploadFile.test.ts delete mode 100644 packages/react-storage/src/components/StorageManager/utils/checkMaxFileSize.ts delete mode 100644 packages/react-storage/src/components/StorageManager/utils/displayText.ts delete mode 100644 packages/react-storage/src/components/StorageManager/utils/filterAllowedFiles.ts delete mode 100644 packages/react-storage/src/components/StorageManager/utils/getInput.ts delete mode 100644 packages/react-storage/src/components/StorageManager/utils/index.ts delete mode 100644 packages/react-storage/src/components/StorageManager/utils/resolveFile.ts delete mode 100644 packages/react-storage/src/components/StorageManager/utils/uploadFile.ts diff --git a/packages/react-storage/src/components/StorageManager/StorageManager.tsx b/packages/react-storage/src/components/StorageManager/StorageManager.tsx index 0eb13627e40..13373e1402c 100644 --- a/packages/react-storage/src/components/StorageManager/StorageManager.tsx +++ b/packages/react-storage/src/components/StorageManager/StorageManager.tsx @@ -8,9 +8,10 @@ import { } from '@aws-amplify/ui-react-core'; import { useDropZone } from '@aws-amplify/ui-react/internal'; -import { useStorageManager, useUploadFiles } from './hooks'; +import { useFileUploader, useUploadFiles } from '../FileUploader/hooks'; +import { FileStatus } from '../FileUploader/types'; + import { - FileStatus, StorageManagerProps, StorageManagerPathProps, StorageManagerHandle, @@ -25,10 +26,10 @@ import { } from './ui'; import { checkMaxFileSize, - defaultStorageManagerDisplayText, + defaultFileUploaderDisplayText, filterAllowedFiles, TaskHandler, -} from './utils'; +} from '../FileUploader/utils'; import { VERSION } from '../../version'; const logger = getLogger('Storage'); @@ -97,7 +98,7 @@ const StorageManagerBase = React.forwardRef(function StorageManager( (typeof maxFileCount === 'number' && maxFileCount > 1); const displayText = { - ...defaultStorageManagerDisplayText, + ...defaultFileUploaderDisplayText, ...overrideDisplayText, }; @@ -117,13 +118,12 @@ const StorageManagerBase = React.forwardRef(function StorageManager( files, removeUpload, queueFiles, - setProcessedKey, setUploadingFile, setUploadPaused, setUploadProgress, setUploadSuccess, setUploadResumed, - } = useStorageManager(defaultFiles); + } = useFileUploader(defaultFiles); React.useImperativeHandle(ref, () => ({ clearFiles })); @@ -155,7 +155,6 @@ const StorageManagerBase = React.forwardRef(function StorageManager( onUploadError, onUploadSuccess, onUploadStart, - onProcessFileSuccess: setProcessedKey, setUploadingFile, setUploadProgress, setUploadSuccess, @@ -211,8 +210,7 @@ const StorageManagerBase = React.forwardRef(function StorageManager( if (typeof onFileRemove === 'function') { const file = files.find((file) => file.id === id); if (file) { - // return `processedKey` if available and `processFile` is provided - const key = (processFile && file?.processedKey) ?? file.key; + const key = file.resolvedKey ?? file.key; onFileRemove({ key }); } } diff --git a/packages/react-storage/src/components/StorageManager/__tests__/StorageManager.test.tsx b/packages/react-storage/src/components/StorageManager/__tests__/StorageManager.test.tsx index 8128408d32f..1cf46a6b226 100644 --- a/packages/react-storage/src/components/StorageManager/__tests__/StorageManager.test.tsx +++ b/packages/react-storage/src/components/StorageManager/__tests__/StorageManager.test.tsx @@ -10,18 +10,16 @@ import * as Storage from 'aws-amplify/storage'; import { ComponentClassName } from '@aws-amplify/ui'; -import * as StorageHooks from '../hooks'; +import * as StorageHooks from '../../FileUploader/hooks'; +import { FileStatus } from '../../FileUploader/types'; import { StorageManager, + ACCESS_LEVEL_WITH_PATH_CALLBACK_MESSAGE, MISSING_REQUIRED_PROPS_MESSAGE, ACCESS_LEVEL_DEPRECATION_MESSAGE, } from '../StorageManager'; -import { - StorageManagerProps, - StorageManagerHandle, - FileStatus, -} from '../types'; -import { defaultStorageManagerDisplayText } from '../utils'; +import { StorageManagerProps, StorageManagerHandle } from '../types'; +import { defaultFileUploaderDisplayText } from '../../FileUploader/utils'; const warnSpy = jest.spyOn(console, 'warn').mockImplementation(); @@ -32,7 +30,10 @@ const uploadDataSpy = jest pause: jest.fn(), resume: jest.fn(), state: 'SUCCESS', - result: Promise.resolve({ key: input.key, data: input.data }), + result: Promise.resolve({ + key: (input as { path?: string })?.path ?? input.key, + data: input.data, + }), })); const storeManagerProps: StorageManagerProps = { @@ -76,10 +77,10 @@ describe('StorageManager', () => { ).toHaveLength(1); expect( - getByText(defaultStorageManagerDisplayText.browseFilesText) + getByText(defaultFileUploaderDisplayText.browseFilesText) ).toBeVisible(); expect( - getByText(defaultStorageManagerDisplayText.dropFilesText) + getByText(defaultFileUploaderDisplayText.dropFilesText) ).toBeVisible(); expect(warnSpy).toHaveBeenCalledTimes(1); @@ -116,10 +117,10 @@ describe('StorageManager', () => { ).toHaveLength(1); expect( - getByText(defaultStorageManagerDisplayText.browseFilesText) + getByText(defaultFileUploaderDisplayText.browseFilesText) ).toBeVisible(); expect( - getByText(defaultStorageManagerDisplayText.dropFilesText) + getByText(defaultFileUploaderDisplayText.dropFilesText) ).toBeVisible(); expect(warnSpy).not.toHaveBeenCalled(); @@ -137,16 +138,16 @@ describe('StorageManager', () => { fireEvent.change(hiddenInput, { target: { files: [mockFile] } }); expect( - getByText(defaultStorageManagerDisplayText.clearAllButtonText) + getByText(defaultFileUploaderDisplayText.clearAllButtonText) ).toBeVisible(); expect( - getByText(defaultStorageManagerDisplayText.getSelectedFilesText(1)) + getByText(defaultFileUploaderDisplayText.getSelectedFilesText(1)) ).toBeVisible(); }); it('renders as expected with override display text', () => { const displayText = { - ...defaultStorageManagerDisplayText, + ...defaultFileUploaderDisplayText, dropFilesText: 'Drag and drop files here, or click to select files', browseFilesText: 'Select Files', }; @@ -159,6 +160,22 @@ describe('StorageManager', () => { expect(getByText('Select Files')).toBeVisible(); }); + it('displays error message when provided with `path` callback and `accessLevel` props', () => { + // turn off error logging in console for test output + jest.spyOn(console, 'error').mockImplementation(); + + expect(() => + render( + // @ts-expect-error providign callback `path` and `accessLevel` are disallowd by TS + 'path'} + maxFileCount={12} + /> + ) + ).toThrow(ACCESS_LEVEL_WITH_PATH_CALLBACK_MESSAGE); + }); + it('displays error message when file exceeds max file size', () => { const maxFileSize = 0; const { getByText } = render( @@ -174,7 +191,7 @@ describe('StorageManager', () => { fireEvent.change(hiddenInput, { target: { files: [mockFile] } }); expect( - getByText(defaultStorageManagerDisplayText.getFileSizeErrorText('0 B')) + getByText(defaultFileUploaderDisplayText.getFileSizeErrorText('0 B')) ).toBeVisible(); }); @@ -195,7 +212,7 @@ describe('StorageManager', () => { expect( getByText( - defaultStorageManagerDisplayText.getMaxFilesErrorText(maxFileCount) + defaultFileUploaderDisplayText.getMaxFilesErrorText(maxFileCount) ) ).toBeVisible(); }); @@ -331,7 +348,7 @@ describe('StorageManager', () => { }); }); - it('provides the processed file key on a remove file event after upload when processFile is provided', async () => { + it('provides the resolved file key on a remove file event after upload when processFile is provided', async () => { const onFileRemove = jest.fn(); const processedKey = 'processedKey'; @@ -376,6 +393,7 @@ describe('StorageManager', () => { it('provides the processed file key on a remove file event after upload when processFile is provided with a path function', async () => { const onFileRemove = jest.fn(); + const path = () => 'my-path'; const processedKey = 'processedKey'; const processFile: StorageManagerProps['processFile'] = (input) => ({ @@ -388,7 +406,7 @@ describe('StorageManager', () => { {...storeManagerProps} onFileRemove={onFileRemove} processFile={processFile} - path={() => 'my-path'} + path={path} accessLevel={undefined} /> ); @@ -405,17 +423,19 @@ describe('StorageManager', () => { // Wait for the file to be uploaded await waitFor(() => { expect(uploadDataSpy).toHaveBeenCalled(); + }); - const removeButton = getByTestId( - container, - 'storage-manager-remove-button' - ); - expect(removeButton).toBeDefined(); + const removeButton = getByTestId( + container, + 'storage-manager-remove-button' + ); + expect(removeButton).toBeDefined(); - fireEvent.click(removeButton); + fireEvent.click(removeButton); - expect(onFileRemove).toHaveBeenCalledTimes(1); - expect(onFileRemove).toHaveBeenCalledWith({ key: processedKey }); + expect(onFileRemove).toHaveBeenCalledTimes(1); + expect(onFileRemove).toHaveBeenCalledWith({ + key: `${path()}processedKey`, }); }); @@ -436,11 +456,11 @@ describe('StorageManager', () => { it('should trigger hidden input onChange', async () => { const mockAddFiles = jest.fn(); - jest.spyOn(StorageHooks, 'useStorageManager').mockReturnValue({ + jest.spyOn(StorageHooks, 'useFileUploader').mockReturnValue({ addFiles: mockAddFiles, files: [], status: FileStatus.QUEUED, - } as unknown as StorageHooks.UseStorageManager); + } as unknown as StorageHooks.UseFileUploader); const { findByRole } = render(); @@ -474,11 +494,11 @@ describe('StorageManager', () => { const ref = React.createRef(); const mockAddFiles = jest.fn(); const mockClearFiles = jest.fn(); - jest.spyOn(StorageHooks, 'useStorageManager').mockReturnValue({ + jest.spyOn(StorageHooks, 'useFileUploader').mockReturnValue({ addFiles: mockAddFiles, clearFiles: mockClearFiles, files: [], - } as unknown as StorageHooks.UseStorageManager); + } as unknown as StorageHooks.UseFileUploader); render(); const hiddenInput = document.querySelector( diff --git a/packages/react-storage/src/components/StorageManager/hooks/index.ts b/packages/react-storage/src/components/StorageManager/hooks/index.ts deleted file mode 100644 index ec5cabcdfa8..00000000000 --- a/packages/react-storage/src/components/StorageManager/hooks/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { useStorageManager, UseStorageManager } from './useStorageManager'; -export { useUploadFiles } from './useUploadFiles'; diff --git a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/__tests__/actions.test.ts b/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/__tests__/actions.test.ts deleted file mode 100644 index ac9920c8df6..00000000000 --- a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/__tests__/actions.test.ts +++ /dev/null @@ -1,103 +0,0 @@ -import { UploadDataOutput } from 'aws-amplify/storage'; - -import { FileStatus } from '../../../types'; -import { - addFilesAction, - clearFilesAction, - queueFilesAction, - removeUploadAction, - setUploadingFileAction, - setUploadProgressAction, - setUploadStatusAction, -} from '../actions'; -import { StorageManagerActionTypes } from '../types'; - -describe('addFilesAction', () => { - it('creates an action with the ADD_FILES type and the given files and error message', () => { - const files = [new File(['file contents'], 'filename')]; - const status = FileStatus.QUEUED; - const getFileErrorMessage = () => 'Something went wrong'; - const expectedAction = { - type: StorageManagerActionTypes.ADD_FILES, - files, - status, - getFileErrorMessage, - }; - const action = addFilesAction({ files, status, getFileErrorMessage }); - expect(action).toEqual(expectedAction); - }); -}); - -describe('queueFilesAction', () => { - it('creates an action with the QUEUE_FILES type', () => { - const expectedAction = { - type: StorageManagerActionTypes.QUEUE_FILES, - }; - const action = queueFilesAction(); - expect(action).toEqual(expectedAction); - }); -}); - -describe('clearFilesAction', () => { - it('creates an action with the CLEAR_FILES type', () => { - const expectedAction = { - type: StorageManagerActionTypes.CLEAR_FILES, - }; - const action = clearFilesAction(); - expect(action).toEqual(expectedAction); - }); -}); - -describe('setUploadingFileAction', () => { - it('creates an action with the SET_STATUS_UPLOADING type and the given id and upload task', () => { - const id = 'test-id'; - const uploadTask = {} as UploadDataOutput; - const expectedAction = { - type: StorageManagerActionTypes.SET_STATUS_UPLOADING, - id, - uploadTask, - }; - const action = setUploadingFileAction({ id, uploadTask }); - expect(action).toEqual(expectedAction); - }); -}); - -describe('setUploadProgressAction', () => { - it('creates an action with the SET_UPLOAD_PROGRESS type and the given id and progress', () => { - const id = 'test-id'; - const progress = 50; - const expectedAction = { - type: StorageManagerActionTypes.SET_UPLOAD_PROGRESS, - id, - progress, - }; - const action = setUploadProgressAction({ id, progress }); - expect(action).toEqual(expectedAction); - }); -}); - -describe('setUploadStatusAction', () => { - it('creates an action with the SET_STATUS type and the given file status', () => { - const id = 'test-id'; - const status = FileStatus.PAUSED; - const expectedAction = { - type: StorageManagerActionTypes.SET_STATUS, - id, - status, - }; - const action = setUploadStatusAction({ id, status }); - expect(action).toEqual(expectedAction); - }); -}); - -describe('removeUploadAction', () => { - it('creates an action with the REMOVE_UPLOAD type', () => { - const id = 'test-id'; - const expectedAction = { - type: StorageManagerActionTypes.REMOVE_UPLOAD, - id, - }; - const action = removeUploadAction({ id }); - expect(action).toEqual(expectedAction); - }); -}); diff --git a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/__tests__/reducer.test.ts b/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/__tests__/reducer.test.ts deleted file mode 100644 index 64278d384b3..00000000000 --- a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/__tests__/reducer.test.ts +++ /dev/null @@ -1,399 +0,0 @@ -import { renderHook, act } from '@testing-library/react-hooks'; -import { useReducer } from 'react'; - -import { UploadDataOutput } from 'aws-amplify/storage'; - -import { storageManagerStateReducer } from '../reducer'; -import { - Action, - StorageManagerActionTypes, - UseStorageManagerState, -} from '../types'; -import { FileStatus, StorageFile, StorageFiles } from '../../../types'; - -const imageFile = new File(['hello'], 'hello.png', { type: 'image/png' }); -const initialState: UseStorageManagerState = { - files: [], -}; - -// mock Date.now() so we can get accurate file IDs -const dateSpy = jest.spyOn(Date, 'now').mockImplementation(() => 1487076708000); - -describe('storageManagerStateReducer', () => { - beforeEach(() => { - dateSpy.mockClear(); - }); - - it('should add files to state on ADD_FILES action', () => { - const addFilesAction: Action = { - type: StorageManagerActionTypes.ADD_FILES, - files: [imageFile], - status: FileStatus.QUEUED, - getFileErrorMessage: jest.fn().mockReturnValue('Test error'), - }; - - const expectedFiles: StorageFiles = [ - { - id: `${Date.now()}-${imageFile.name}`, - file: imageFile, - error: 'Test error', - key: imageFile.name, - status: FileStatus.ERROR, - isImage: true, - progress: -1, - }, - ]; - const { result } = renderHook(() => { - const [state, dispatch] = useReducer( - storageManagerStateReducer, - initialState - ); - return { state, dispatch }; - }); - - expect(result.current.state.files).toStrictEqual([]); - - act(() => result.current.dispatch(addFilesAction)); - - expect(result.current.state.files).toStrictEqual(expectedFiles); - }); - - it('should clear files from state on CLEAR_FILES action', () => { - const { result } = renderHook(() => { - const [state, dispatch] = useReducer(storageManagerStateReducer, { - files: [ - { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.UPLOADING, - isImage: true, - progress: -1, - }, - ], - }); - return { state, dispatch }; - }); - - const clearFilesAction: Action = { - type: StorageManagerActionTypes.CLEAR_FILES, - }; - act(() => result.current.dispatch(clearFilesAction)); - - expect(result.current.state.files).toEqual([]); - }); - - it('should set uploading status and progress on SET_STATUS_UPLOADING action', () => { - const { result } = renderHook(() => { - const [state, dispatch] = useReducer(storageManagerStateReducer, { - files: [ - { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.QUEUED, - isImage: true, - progress: -1, - }, - ], - }); - return { state, dispatch }; - }); - - const testUploadTask = {} as UploadDataOutput; - const uploadingAction: Action = { - type: StorageManagerActionTypes.SET_STATUS_UPLOADING, - id: imageFile.name, - uploadTask: testUploadTask, - }; - act(() => result.current.dispatch(uploadingAction)); - - const expectedFiles: StorageFiles = [ - { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.UPLOADING, - isImage: true, - progress: 0, - uploadTask: testUploadTask, - }, - ]; - expect(result.current.state.files).toEqual(expectedFiles); - }); - - it('should set upload progress of a file on SET_UPLOAD_PROGRESS action', () => { - const { result } = renderHook(() => { - const [state, dispatch] = useReducer(storageManagerStateReducer, { - files: [ - { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.UPLOADING, - isImage: true, - progress: -1, - }, - ], - }); - return { state, dispatch }; - }); - - const uploadProgressAction: Action = { - type: StorageManagerActionTypes.SET_UPLOAD_PROGRESS, - id: imageFile.name, - progress: 50, - }; - act(() => result.current.dispatch(uploadProgressAction)); - - const expectedFiles: StorageFiles = [ - { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.UPLOADING, - isImage: true, - progress: 50, - }, - ]; - expect(result.current.state.files).toEqual(expectedFiles); - }); - - it('should return previous state if file not found on SET_UPLOAD_PROGRESS action', () => { - const file: StorageFile = { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.UPLOADING, - isImage: true, - progress: -1, - }; - const { result } = renderHook(() => { - const [state, dispatch] = useReducer(storageManagerStateReducer, { - files: [file], - }); - return { state, dispatch }; - }); - - const uploadProgressAction: Action = { - type: StorageManagerActionTypes.SET_UPLOAD_PROGRESS, - id: 'not-found', - progress: 50, - }; - act(() => result.current.dispatch(uploadProgressAction)); - - expect(result.current.state.files).toEqual([file]); - }); - - it('should update the status of a file progress of a file on SET_STATUS action', () => { - const { result } = renderHook(() => { - const [state, dispatch] = useReducer(storageManagerStateReducer, { - files: [ - { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.UPLOADING, - isImage: true, - progress: -1, - }, - ], - }); - return { state, dispatch }; - }); - - const setStatusAction: Action = { - type: StorageManagerActionTypes.SET_STATUS, - id: imageFile.name, - status: FileStatus.UPLOADED, - }; - act(() => result.current.dispatch(setStatusAction)); - - const expectedFiles: StorageFiles = [ - { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.UPLOADED, - isImage: true, - progress: -1, - }, - ]; - expect(result.current.state.files).toEqual(expectedFiles); - }); - - it('should return previous state if file not found on SET_STATUS action', () => { - const file: StorageFile = { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.UPLOADING, - isImage: true, - progress: -1, - }; - const { result } = renderHook(() => { - const [state, dispatch] = useReducer(storageManagerStateReducer, { - files: [file], - }); - return { state, dispatch }; - }); - - const setStatusAction: Action = { - type: StorageManagerActionTypes.SET_STATUS, - id: 'not-found', - status: FileStatus.UPLOADED, - }; - act(() => result.current.dispatch(setStatusAction)); - - expect(result.current.state.files).toEqual([file]); - }); - - it('should remove file from state on REMOVE_UPLOAD action', () => { - const { result } = renderHook(() => { - const [state, dispatch] = useReducer(storageManagerStateReducer, { - files: [ - { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.UPLOADING, - isImage: true, - progress: -1, - }, - ], - }); - return { state, dispatch }; - }); - - const removeUploadAction: Action = { - type: StorageManagerActionTypes.REMOVE_UPLOAD, - id: imageFile.name, - }; - act(() => result.current.dispatch(removeUploadAction)); - - expect(result.current.state.files).toEqual([]); - }); - - it('should return previous state if file not found on REMOVE_UPLOAD action', () => { - const file: StorageFile = { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.UPLOADING, - isImage: true, - progress: -1, - }; - const { result } = renderHook(() => { - const [state, dispatch] = useReducer(storageManagerStateReducer, { - files: [file], - }); - return { state, dispatch }; - }); - - const removeUploadAction: Action = { - type: StorageManagerActionTypes.REMOVE_UPLOAD, - id: 'not-found', - }; - act(() => result.current.dispatch(removeUploadAction)); - - expect(result.current.state.files).toEqual([file]); - }); - - it('updates the key of a target file on SET_PROCESSED_FILE_KEY', () => { - const file: StorageFile = { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.QUEUED, - isImage: true, - progress: -1, - }; - - const { result } = renderHook(() => { - const [state, dispatch] = useReducer(storageManagerStateReducer, { - files: [file], - }); - return { state, dispatch }; - }); - - const processedKey = `processed-${imageFile.name}`; - const action: Action = { - type: StorageManagerActionTypes.SET_PROCESSED_FILE_KEY, - id: imageFile.name, - processedKey, - }; - - expect(result.current.state.files[0].processedKey).toBeUndefined(); - - act(() => result.current.dispatch(action)); - - expect(result.current.state.files[0].processedKey).toBe(processedKey); - }); - - it('should only change added files to queued in QUEUE_FILES action', () => { - const { result } = renderHook(() => { - const [state, dispatch] = useReducer(storageManagerStateReducer, { - files: [ - { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.ADDED, - isImage: true, - progress: -1, - }, - { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.UPLOADED, - isImage: true, - progress: 100, - }, - ], - }); - return { state, dispatch }; - }); - - const queueFilesAction: Action = { - type: StorageManagerActionTypes.QUEUE_FILES, - }; - - act(() => result.current.dispatch(queueFilesAction)); - - expect(result.current.state.files).toEqual([ - { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.QUEUED, - isImage: true, - progress: -1, - }, - { - id: imageFile.name, - file: imageFile, - error: '', - key: imageFile.name, - status: FileStatus.UPLOADED, - isImage: true, - progress: 100, - }, - ]); - }); -}); diff --git a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/__tests__/useStorageManager.test.ts b/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/__tests__/useStorageManager.test.ts deleted file mode 100644 index b133a9ca2fc..00000000000 --- a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/__tests__/useStorageManager.test.ts +++ /dev/null @@ -1,209 +0,0 @@ -import { act, renderHook } from '@testing-library/react-hooks'; - -import { DefaultFile, FileStatus } from '../../../types'; -import { useStorageManager } from '../useStorageManager'; - -jest.mock('aws-amplify/storage'); - -const defaultFiles: DefaultFile[] = [{ key: 'file1' }, { key: 'file2' }]; - -describe('useUploadFiles', () => { - afterEach(() => jest.clearAllMocks()); - - it('should initialize with default files', () => { - const { result } = renderHook(() => useStorageManager(defaultFiles)); - - expect(result.current.files.length).toBe(2); - - result.current.files.forEach((file) => { - expect(file.id).toBe(file.key); - expect(file.status).toBe(FileStatus.UPLOADED); - }); - }); - - it('should add files', () => { - const { result } = renderHook(() => useStorageManager()); - const status = FileStatus.QUEUED; - - expect(result.current.files.length).toBe(0); - act(() => - result.current.addFiles({ - files: [ - new File(['test1'], 'test1.txt', { type: 'text/plain' }), - new File(['test2'], 'test2.txt', { type: 'text/plain' }), - ], - status, - getFileErrorMessage: () => '', - }) - ); - - expect(result.current.files.length).toBe(2); - - expect(result.current.files[0].status).toStrictEqual(status); - expect(result.current.files[1].status).toStrictEqual(status); - }); - - it('should queue files', () => { - const { result } = renderHook(() => useStorageManager()); - const status = FileStatus.ADDED; - - act(() => - result.current.addFiles({ - files: [ - new File(['test1'], 'test1.txt', { type: 'text/plain' }), - new File(['test2'], 'test2.txt', { type: 'text/plain' }), - ], - status, - getFileErrorMessage: () => '', - }) - ); - - act(() => result.current.queueFiles()); - - expect(result.current.files.length).toBe(2); - - expect(result.current.files[0].status).toStrictEqual(FileStatus.QUEUED); - expect(result.current.files[1].status).toStrictEqual(FileStatus.QUEUED); - }); - - it('should clear files', () => { - const { result } = renderHook(() => useStorageManager(defaultFiles)); - act(() => - result.current.addFiles({ - files: [ - new File(['test1'], 'test1.txt', { type: 'text/plain' }), - new File(['test2'], 'test2.txt', { type: 'text/plain' }), - ], - status: FileStatus.QUEUED, - getFileErrorMessage: () => '', - }) - ); - - expect(result.current.files.length).toBe(4); - act(() => result.current.clearFiles()); - expect(result.current.files.length).toBe(0); - }); - - it('should set uploading file', () => { - const { result } = renderHook(() => useStorageManager(defaultFiles)); - - act(() => - result.current.setUploadingFile({ - id: 'file1', - uploadTask: { - cancel: jest.fn(), - pause: jest.fn(), - resume: jest.fn(), - state: 'IN_PROGRESS', - result: Promise.resolve({ - key: 'key', - }), - }, - }) - ); - - expect(result.current.files[0].status).toStrictEqual(FileStatus.UPLOADING); - }); - - it('should set upload progress', () => { - const { result } = renderHook(() => useStorageManager(defaultFiles)); - - act(() => - result.current.setUploadProgress({ - id: 'file1', - progress: 50, - }) - ); - - expect(result.current.files[0].progress).toStrictEqual(50); - }); - - it('should set upload success', () => { - const { result } = renderHook(() => useStorageManager(defaultFiles)); - - act(() => - result.current.setUploadSuccess({ - id: 'file1', - }) - ); - - expect(result.current.files[0].status).toStrictEqual(FileStatus.UPLOADED); - }); - - it('should set upload paused', () => { - const { result } = renderHook(() => useStorageManager(defaultFiles)); - - act(() => - result.current.setUploadPaused({ - id: 'file1', - }) - ); - - expect(result.current.files[0].status).toStrictEqual(FileStatus.PAUSED); - }); - - it('should set upload resumed', () => { - const { result } = renderHook(() => useStorageManager(defaultFiles)); - - act(() => - result.current.setUploadResumed({ - id: 'file1', - }) - ); - - expect(result.current.files[0].status).toStrictEqual(FileStatus.UPLOADING); - }); - - it('should remove upload', () => { - const { result } = renderHook(() => useStorageManager(defaultFiles)); - - expect(result.current.files.length).toBe(2); - act(() => - result.current.removeUpload({ - id: 'file1', - }) - ); - expect(result.current.files.length).toBe(1); - }); - - it('should update a target file key', () => { - const { result } = renderHook(() => useStorageManager(defaultFiles)); - - const processedKey = 'processedKey'; - - expect(result.current.files[0].processedKey).toBeUndefined(); - - act(() => result.current.setProcessedKey({ id: 'file1', processedKey })); - - expect(result.current.files[0].processedKey).toBe(processedKey); - }); - - describe('defaultFiles', () => { - it('should handle good defaultFiles', () => { - const { result } = renderHook(() => - useStorageManager([{ key: 'file.jpg' }]) - ); - expect(result.current.files).toHaveLength(1); - }); - - it('should handle null defaultFiles', () => { - // @ts-expect-error - const { result } = renderHook(() => useStorageManager(null)); - expect(result.current.files).toHaveLength(0); - }); - - it('should handle bad defaultFiles', () => { - const { result } = renderHook(() => - useStorageManager([ - // @ts-expect-error - null, - // @ts-expect-error - { key: null }, - // @ts-expect-error - { foo: 'bar' }, - ]) - ); - expect(result.current.files).toHaveLength(0); - }); - }); -}); diff --git a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/actions.ts b/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/actions.ts deleted file mode 100644 index 6d2c641bf9a..00000000000 --- a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/actions.ts +++ /dev/null @@ -1,73 +0,0 @@ -import { FileStatus } from '../../types'; - -import { - Action, - AddFilesActionParams, - StorageManagerActionTypes, -} from './types'; -import { TaskEvent } from '../../utils'; - -export const addFilesAction = ({ - files, - status, - getFileErrorMessage, -}: AddFilesActionParams): Action => ({ - type: StorageManagerActionTypes.ADD_FILES, - files, - status, - getFileErrorMessage, -}); - -export const clearFilesAction = (): Action => ({ - type: StorageManagerActionTypes.CLEAR_FILES, -}); - -export const queueFilesAction = (): Action => ({ - type: StorageManagerActionTypes.QUEUE_FILES, -}); - -export const setProcessedKeyAction = (input: { - id: string; - processedKey: string; -}): Action => ({ - ...input, - type: StorageManagerActionTypes.SET_PROCESSED_FILE_KEY, -}); - -export const setUploadingFileAction = ({ - id, - uploadTask, -}: TaskEvent): Action => ({ - type: StorageManagerActionTypes.SET_STATUS_UPLOADING, - id, - uploadTask, -}); - -export const setUploadProgressAction = ({ - id, - progress, -}: { - id: string; - progress: number; -}): Action => ({ - type: StorageManagerActionTypes.SET_UPLOAD_PROGRESS, - id, - progress, -}); - -export const setUploadStatusAction = ({ - id, - status, -}: { - id: string; - status: FileStatus; -}): Action => ({ - type: StorageManagerActionTypes.SET_STATUS, - id, - status, -}); - -export const removeUploadAction = ({ id }: { id: string }): Action => ({ - type: StorageManagerActionTypes.REMOVE_UPLOAD, - id, -}); diff --git a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/index.ts b/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/index.ts deleted file mode 100644 index 884dfcff107..00000000000 --- a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { useStorageManager, UseStorageManager } from './useStorageManager'; diff --git a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/reducer.ts b/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/reducer.ts deleted file mode 100644 index 39fd44fc6a8..00000000000 --- a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/reducer.ts +++ /dev/null @@ -1,114 +0,0 @@ -import { FileStatus, StorageFile, StorageFiles } from '../../types'; -import { - Action, - StorageManagerActionTypes, - UseStorageManagerState, -} from './types'; - -const updateFiles = ( - files: StorageFiles, - nextFileData: Pick & Partial -) => - files.reduce((files, currentFile) => { - if (currentFile.id === nextFileData.id) { - return [...files, { ...currentFile, ...nextFileData }]; - } - return [...files, currentFile]; - }, []); - -export function storageManagerStateReducer( - state: UseStorageManagerState, - action: Action -): UseStorageManagerState { - switch (action.type) { - case StorageManagerActionTypes.ADD_FILES: { - const { files, status } = action; - - const newUploads: StorageFiles = files.map((file) => { - const errorText = action.getFileErrorMessage(file); - - return { - // make sure id is unique, - // we only use it internally and don't send it to Storage - id: `${Date.now()}-${file.name}`, - file, - error: errorText, - key: file.name, - status: errorText ? FileStatus.ERROR : status, - isImage: file.type.startsWith('image/'), - progress: -1, - }; - }); - - const newFiles: StorageFiles = [...state.files, ...newUploads]; - - return { ...state, files: newFiles }; - } - case StorageManagerActionTypes.CLEAR_FILES: { - return { ...state, files: [] }; - } - case StorageManagerActionTypes.QUEUE_FILES: { - const { files } = state; - - const newFiles = files.reduce((files, currentFile) => { - return [ - ...files, - { - ...currentFile, - ...(currentFile.status === FileStatus.ADDED - ? { status: FileStatus.QUEUED } - : {}), - }, - ]; - }, []); - return { - ...state, - files: newFiles, - }; - } - case StorageManagerActionTypes.SET_STATUS_UPLOADING: { - const { id, uploadTask } = action; - const status = FileStatus.UPLOADING; - const progress = 0; - const nextFileData = { status, progress, id, uploadTask }; - - const files = updateFiles(state.files, nextFileData); - - return { ...state, files }; - } - case StorageManagerActionTypes.SET_PROCESSED_FILE_KEY: { - const { processedKey, id } = action; - const files = updateFiles(state.files, { processedKey, id }); - - return { files }; - } - case StorageManagerActionTypes.SET_UPLOAD_PROGRESS: { - const { id, progress } = action; - const files = updateFiles(state.files, { id, progress }); - - return { ...state, files }; - } - case StorageManagerActionTypes.SET_STATUS: { - const { id, status } = action; - const files = updateFiles(state.files, { id, status }); - - return { ...state, files }; - } - case StorageManagerActionTypes.REMOVE_UPLOAD: { - const { id } = action; - const { files } = state; - - const newFiles = files.reduce((files, currentFile) => { - if (currentFile.id === id) { - // remove by not returning currentFile - return [...files]; - } - return [...files, currentFile]; - }, []); - return { - ...state, - files: newFiles, - }; - } - } -} diff --git a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/types.ts b/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/types.ts deleted file mode 100644 index c526a81453a..00000000000 --- a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/types.ts +++ /dev/null @@ -1,63 +0,0 @@ -import { FileStatus, StorageFiles } from '../../types'; -import { UploadTask } from '../../utils'; - -export interface UseStorageManagerState { - files: StorageFiles; -} - -export enum StorageManagerActionTypes { - ADD_FILES = 'ADD_FILES', - CLEAR_FILES = 'CLEAR_FILES', - QUEUE_FILES = 'QUEUE_FILES', - SET_STATUS = 'SET_STATUS', - SET_PROCESSED_FILE_KEY = 'SET_PROCESSED_FILE_KEY', - SET_STATUS_UPLOADING = 'SET_STATUS_UPLOADING', - SET_UPLOAD_PROGRESS = 'SET_UPLOAD_PROGRESS', - REMOVE_UPLOAD = 'REMOVE_UPLOAD', -} - -export type GetFileErrorMessage = (file: File) => string; - -export type Action = - | { - type: StorageManagerActionTypes.ADD_FILES; - files: File[]; - status: FileStatus; - getFileErrorMessage: GetFileErrorMessage; - } - | { - type: StorageManagerActionTypes.CLEAR_FILES; - } - | { - type: StorageManagerActionTypes.SET_STATUS; - id: string; - status: FileStatus; - } - | { - type: StorageManagerActionTypes.QUEUE_FILES; - } - | { - type: StorageManagerActionTypes.SET_STATUS_UPLOADING; - id: string; - uploadTask?: UploadTask; - } - | { - type: StorageManagerActionTypes.SET_UPLOAD_PROGRESS; - id: string; - progress: number; - } - | { - type: StorageManagerActionTypes.SET_PROCESSED_FILE_KEY; - id: string; - processedKey: string; - } - | { - type: StorageManagerActionTypes.REMOVE_UPLOAD; - id: string; - }; - -export interface AddFilesActionParams { - files: File[]; - status: FileStatus; - getFileErrorMessage: GetFileErrorMessage; -} diff --git a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/useStorageManager.ts b/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/useStorageManager.ts deleted file mode 100644 index 4514e37c457..00000000000 --- a/packages/react-storage/src/components/StorageManager/hooks/useStorageManager/useStorageManager.ts +++ /dev/null @@ -1,123 +0,0 @@ -import React from 'react'; - -import { isObject } from '@aws-amplify/ui'; - -import { StorageFiles, FileStatus, DefaultFile } from '../../types'; -import { Action, GetFileErrorMessage, UseStorageManagerState } from './types'; -import { storageManagerStateReducer } from './reducer'; -import { - addFilesAction, - clearFilesAction, - queueFilesAction, - removeUploadAction, - setProcessedKeyAction, - setUploadingFileAction, - setUploadProgressAction, - setUploadStatusAction, -} from './actions'; -import { TaskHandler } from '../../utils'; - -export interface UseStorageManager { - addFiles: (params: { - files: File[]; - status: FileStatus; - getFileErrorMessage: GetFileErrorMessage; - }) => void; - clearFiles: () => void; - queueFiles: () => void; - setUploadingFile: TaskHandler; - setProcessedKey: (params: { id: string; processedKey: string }) => void; - setUploadProgress: (params: { id: string; progress: number }) => void; - setUploadSuccess: (params: { id: string }) => void; - setUploadResumed: (params: { id: string }) => void; - setUploadPaused: (params: { id: string }) => void; - removeUpload: (params: { id: string }) => void; - files: StorageFiles; -} - -const isDefaultFile = (file: unknown): file is DefaultFile => - !!(isObject(file) && (file as DefaultFile).key); - -const createFileFromDefault = (file: DefaultFile) => - isDefaultFile(file) - ? { ...file, id: file.key, status: FileStatus.UPLOADED } - : undefined; - -export function useStorageManager( - defaultFiles: Array = [] -): UseStorageManager { - const [{ files }, dispatch] = React.useReducer< - ( - prevState: UseStorageManagerState, - action: Action - ) => UseStorageManagerState - >(storageManagerStateReducer, { - files: (Array.isArray(defaultFiles) - ? defaultFiles.map(createFileFromDefault).filter((file) => !!file) - : []) as StorageFiles, - }); - - const addFiles: UseStorageManager['addFiles'] = ({ - files, - status, - getFileErrorMessage, - }) => { - dispatch(addFilesAction({ files, status, getFileErrorMessage })); - }; - - const clearFiles: UseStorageManager['clearFiles'] = () => { - dispatch(clearFilesAction()); - }; - - const queueFiles: UseStorageManager['queueFiles'] = () => { - dispatch(queueFilesAction()); - }; - - const setUploadingFile: UseStorageManager['setUploadingFile'] = ({ - uploadTask, - id, - }) => { - dispatch(setUploadingFileAction({ id, uploadTask })); - }; - - const setProcessedKey: UseStorageManager['setProcessedKey'] = (input) => { - dispatch(setProcessedKeyAction(input)); - }; - - const setUploadProgress: UseStorageManager['setUploadProgress'] = ({ - progress, - id, - }) => { - dispatch(setUploadProgressAction({ id, progress })); - }; - - const setUploadSuccess: UseStorageManager['setUploadSuccess'] = ({ id }) => { - dispatch(setUploadStatusAction({ id, status: FileStatus.UPLOADED })); - }; - - const setUploadPaused: UseStorageManager['setUploadPaused'] = ({ id }) => { - dispatch(setUploadStatusAction({ id, status: FileStatus.PAUSED })); - }; - - const setUploadResumed: UseStorageManager['setUploadPaused'] = ({ id }) => { - dispatch(setUploadStatusAction({ id, status: FileStatus.UPLOADING })); - }; - - const removeUpload: UseStorageManager['removeUpload'] = ({ id }) => { - dispatch(removeUploadAction({ id })); - }; - - return { - removeUpload, - setProcessedKey, - setUploadPaused, - setUploadProgress, - setUploadResumed, - setUploadSuccess, - setUploadingFile, - queueFiles, - addFiles, - clearFiles, - files, - }; -} diff --git a/packages/react-storage/src/components/StorageManager/hooks/useUploadFiles/__tests__/useUploadFiles.spec.ts b/packages/react-storage/src/components/StorageManager/hooks/useUploadFiles/__tests__/useUploadFiles.spec.ts deleted file mode 100644 index bf081c9cb13..00000000000 --- a/packages/react-storage/src/components/StorageManager/hooks/useUploadFiles/__tests__/useUploadFiles.spec.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { renderHook } from '@testing-library/react-hooks'; -import { waitFor } from '@testing-library/react'; - -import * as Storage from 'aws-amplify/storage'; - -import { FileStatus, StorageFile, StorageManagerProps } from '../../../types'; -import { useUploadFiles, UseUploadFilesProps } from '../useUploadFiles'; - -const uploadDataSpy = jest - .spyOn(Storage, 'uploadData') - .mockImplementation((input) => { - return { - cancel: jest.fn(), - pause: jest.fn(), - resume: jest.fn(), - state: 'SUCCESS', - result: Promise.resolve({ key: input.key, data: input.data }), - }; - }); - -const mockUploadingFile: StorageFile = { - id: 'uploading', - status: FileStatus.UPLOADING, - progress: 0, - error: '', - isImage: false, - key: '', -}; - -const imageFile = new File(['hello'], 'hello.png', { type: 'image/png' }); - -const mockQueuedFile: StorageFile = { - id: 'queued', - status: FileStatus.QUEUED, - progress: 0, - error: '', - isImage: false, - key: 'key', - file: imageFile, -}; - -const mockOnProcessFileSuccess = jest.fn(); -const mockOnUploadError = jest.fn(); -const mockOnUploadStart = jest.fn(); -const mockSetUploadingFile = jest.fn(); -const mockSetUploadProgress = jest.fn(); -const mockSetUploadSuccess = jest.fn(); -const props: Omit = { - accessLevel: 'guest', - maxFileCount: 2, - onProcessFileSuccess: mockOnProcessFileSuccess, - onUploadError: mockOnUploadError, - onUploadStart: mockOnUploadStart, - setUploadingFile: mockSetUploadingFile, - setUploadProgress: mockSetUploadProgress, - setUploadSuccess: mockSetUploadSuccess, -}; - -describe('useUploadFiles', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('should upload all queued files', async () => { - const { waitForNextUpdate } = renderHook(() => - useUploadFiles({ ...props, files: [mockUploadingFile, mockQueuedFile] }) - ); - - waitForNextUpdate(); - - await waitFor(() => { - expect(mockSetUploadingFile).toHaveBeenCalledTimes(1); - expect(mockSetUploadingFile).toHaveBeenCalledWith({ - id: mockQueuedFile.id, - uploadTask: expect.any(Object), - }); - expect(mockSetUploadingFile).not.toHaveBeenCalledWith({ - id: mockUploadingFile.id, - }); - expect(mockSetUploadSuccess).toHaveBeenCalledTimes(1); - expect(mockSetUploadSuccess).toHaveBeenCalledWith({ - id: mockQueuedFile.id, - }); - expect(mockSetUploadSuccess).not.toHaveBeenCalledWith({ - id: mockUploadingFile.id, - }); - expect(mockOnUploadError).not.toHaveBeenCalled(); - }); - }); - - it('should upload all resumable queued files', async () => { - const { waitForNextUpdate } = renderHook(() => - useUploadFiles({ - ...props, - isResumable: true, - files: [mockUploadingFile, mockQueuedFile], - }) - ); - - waitForNextUpdate(); - - await waitFor(() => { - expect(mockSetUploadingFile).toHaveBeenCalledTimes(1); - expect(mockSetUploadingFile).toHaveBeenCalledWith({ - id: mockQueuedFile.id, - uploadTask: expect.any(Object), - }); - expect(mockSetUploadingFile).not.toHaveBeenCalledWith({ - id: mockUploadingFile.id, - }); - }); - }); - - it('should do nothing if number of queued files exceeds max number of files', async () => { - const { waitForNextUpdate } = renderHook(() => - useUploadFiles({ ...props, maxFileCount: 0, files: [mockQueuedFile] }) - ); - - waitForNextUpdate(); - - expect(mockSetUploadingFile).not.toHaveBeenCalled(); - - await waitFor(() => { - expect(mockSetUploadSuccess).not.toHaveBeenCalled(); - }); - }); - - it('should call onUploadError when upload fails', async () => { - const errorMessage = new Error('Error'); - uploadDataSpy.mockImplementationOnce(() => { - return { - cancel: jest.fn(), - pause: jest.fn(), - resume: jest.fn(), - state: 'ERROR', - result: Promise.reject(errorMessage), - }; - }); - const { waitForNextUpdate } = renderHook(() => - useUploadFiles({ ...props, files: [mockQueuedFile] }) - ); - - waitForNextUpdate(); - - await waitFor(() => { - expect(mockOnUploadError).toHaveBeenCalledTimes(1); - expect(mockOnUploadError).toHaveBeenCalledWith('Error', { key: 'key' }); - }); - }); - - it('should start upload after processFile', async () => { - const processFile: StorageManagerProps['processFile'] = ({ file }) => ({ - file, - key: 'test.png', - }); - - const { waitForNextUpdate } = renderHook(() => - useUploadFiles({ - ...props, - isResumable: true, - processFile, - files: [mockQueuedFile], - }) - ); - - waitForNextUpdate(); - - await waitFor(() => { - expect(mockOnUploadStart).toHaveBeenCalledWith({ key: 'test.png' }); - }); - }); - - it('should start upload after processFile promise resolves', async () => { - const processFile: StorageManagerProps['processFile'] = ({ file }) => - new Promise((resolve) => resolve({ file, key: 'test.png' })); - - const { waitForNextUpdate } = renderHook(() => - useUploadFiles({ - ...props, - isResumable: true, - processFile, - files: [mockQueuedFile], - }) - ); - - waitForNextUpdate(); - - await waitFor(() => { - expect(mockOnUploadStart).toHaveBeenCalledWith({ - key: 'test.png', - }); - }); - }); - - it('prepends valid provided `path` to `processedKey`', async () => { - const path = 'test-path/'; - const { waitForNextUpdate } = renderHook(() => - useUploadFiles({ - ...props, - isResumable: true, - files: [mockQueuedFile], - path, - }) - ); - const expected = { key: `${path}${mockQueuedFile.key}` }; - - waitForNextUpdate(); - - await waitFor(() => { - expect(mockOnUploadStart).toHaveBeenCalledWith(expected); - expect(uploadDataSpy).toHaveBeenCalledTimes(1); - expect(uploadDataSpy).toHaveBeenCalledWith( - expect.objectContaining(expected) - ); - }); - }); -}); diff --git a/packages/react-storage/src/components/StorageManager/hooks/useUploadFiles/index.ts b/packages/react-storage/src/components/StorageManager/hooks/useUploadFiles/index.ts deleted file mode 100644 index 1b272e6ad93..00000000000 --- a/packages/react-storage/src/components/StorageManager/hooks/useUploadFiles/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { useUploadFiles, UseUploadFilesProps } from './useUploadFiles'; diff --git a/packages/react-storage/src/components/StorageManager/hooks/useUploadFiles/useUploadFiles.ts b/packages/react-storage/src/components/StorageManager/hooks/useUploadFiles/useUploadFiles.ts deleted file mode 100644 index 5dfd3f1f600..00000000000 --- a/packages/react-storage/src/components/StorageManager/hooks/useUploadFiles/useUploadFiles.ts +++ /dev/null @@ -1,127 +0,0 @@ -import * as React from 'react'; - -import { TransferProgressEvent } from 'aws-amplify/storage'; -import { isFunction } from '@aws-amplify/ui'; - -import { PathCallback, uploadFile } from '../../utils'; -import { getInput } from '../../utils'; -import { FileStatus } from '../../types'; -import { StorageManagerProps } from '../../types'; -import { UseStorageManager } from '../useStorageManager'; - -export interface UseUploadFilesProps - extends Pick< - StorageManagerProps, - | 'isResumable' - | 'onUploadSuccess' - | 'onUploadError' - | 'onUploadStart' - | 'maxFileCount' - | 'processFile' - | 'useAccelerateEndpoint' - >, - Pick< - UseStorageManager, - 'setUploadingFile' | 'setUploadProgress' | 'setUploadSuccess' | 'files' - > { - accessLevel?: StorageManagerProps['accessLevel']; - onProcessFileSuccess: (input: { id: string; processedKey: string }) => void; - path?: string | PathCallback; -} - -export function useUploadFiles({ - accessLevel, - files, - isResumable, - maxFileCount, - onProcessFileSuccess, - onUploadError, - onUploadStart, - onUploadSuccess, - path, - processFile, - setUploadingFile, - setUploadProgress, - setUploadSuccess, - useAccelerateEndpoint, -}: UseUploadFilesProps): void { - React.useEffect(() => { - const filesReadyToUpload = files.filter( - (file) => file.status === FileStatus.QUEUED - ); - - if (filesReadyToUpload.length > maxFileCount) { - return; - } - - for (const { file, key, id } of filesReadyToUpload) { - const onProgress = (event: TransferProgressEvent): void => { - /** - * When a file is zero bytes, the progress.total will equal zero. - * Therefore, this will prevent a divide by zero error. - */ - const progress = - event.totalBytes === undefined || event.totalBytes === 0 - ? 100 - : Math.floor((event.transferredBytes / event.totalBytes) * 100); - setUploadProgress({ id, progress }); - }; - - if (file) { - const handleProcessFileSuccess = (input: { processedKey: string }) => - onProcessFileSuccess({ id, ...input }); - - const input = getInput({ - accessLevel, - file, - key, - onProcessFileSuccess: handleProcessFileSuccess, - onProgress, - path, - processFile, - useAccelerateEndpoint, - }); - - uploadFile({ - input, - onComplete: (event) => { - if (isFunction(onUploadSuccess)) { - onUploadSuccess({ - key: - (event as { key: string }).key ?? - (event as { path: string }).path, - }); - } - setUploadSuccess({ id }); - }, - onError: ({ key, error }) => { - if (isFunction(onUploadError)) { - onUploadError(error.message, { key }); - } - }, - onStart: ({ key, uploadTask }) => { - if (isFunction(onUploadStart)) { - onUploadStart({ key }); - } - setUploadingFile({ id, uploadTask }); - }, - }); - } - } - }, [ - files, - accessLevel, - isResumable, - setUploadProgress, - setUploadingFile, - onUploadError, - onProcessFileSuccess, - onUploadSuccess, - onUploadStart, - maxFileCount, - setUploadSuccess, - processFile, - path, - useAccelerateEndpoint, - ]); -} diff --git a/packages/react-storage/src/components/StorageManager/types.ts b/packages/react-storage/src/components/StorageManager/types.ts index 24078912cf2..76b1f058725 100644 --- a/packages/react-storage/src/components/StorageManager/types.ts +++ b/packages/react-storage/src/components/StorageManager/types.ts @@ -2,6 +2,13 @@ import * as React from 'react'; import type { StorageAccessLevel } from '@aws-amplify/core'; +import { FileStatus } from '../FileUploader/types'; +import { + FileUploaderDisplayText as StorageManagerDisplayText, + PathCallback, + UploadTask, +} from '../FileUploader/utils'; + import { ContainerProps, DropZoneProps, @@ -10,16 +17,6 @@ import { FileListProps, FilePickerProps, } from './ui'; -import { StorageManagerDisplayText, PathCallback, UploadTask } from './utils'; - -export enum FileStatus { - ADDED = 'added', - QUEUED = 'queued', - UPLOADING = 'uploading', - PAUSED = 'paused', - ERROR = 'error', - UPLOADED = 'uploaded', -} export interface StorageFile { id: string; diff --git a/packages/react-storage/src/components/StorageManager/ui/DropZone/__tests__/DropZone.test.tsx b/packages/react-storage/src/components/StorageManager/ui/DropZone/__tests__/DropZone.test.tsx index 2680784277b..d169dc0cabb 100644 --- a/packages/react-storage/src/components/StorageManager/ui/DropZone/__tests__/DropZone.test.tsx +++ b/packages/react-storage/src/components/StorageManager/ui/DropZone/__tests__/DropZone.test.tsx @@ -5,7 +5,7 @@ import { ComponentClassName } from '@aws-amplify/ui'; import { IconsProvider, View } from '@aws-amplify/ui-react'; import { classNameModifier } from '@aws-amplify/ui'; -import { defaultStorageManagerDisplayText } from '../../../utils/displayText'; +import { defaultFileUploaderDisplayText as defaultStorageManagerDisplayText } from '../../../../FileUploader/utils/displayText'; import { DropZone } from '../DropZone'; describe('DropZone', () => { diff --git a/packages/react-storage/src/components/StorageManager/ui/DropZone/types.ts b/packages/react-storage/src/components/StorageManager/ui/DropZone/types.ts index aea292497f9..1668e6e2a9b 100644 --- a/packages/react-storage/src/components/StorageManager/ui/DropZone/types.ts +++ b/packages/react-storage/src/components/StorageManager/ui/DropZone/types.ts @@ -1,4 +1,4 @@ -import { StorageManagerDisplayText } from '../../utils/displayText'; +import { FileUploaderDisplayText as StorageManagerDisplayText } from '../../../FileUploader/utils/displayText'; export interface DropZoneProps { children?: React.ReactNode; diff --git a/packages/react-storage/src/components/StorageManager/ui/FileList/FileControl.tsx b/packages/react-storage/src/components/StorageManager/ui/FileList/FileControl.tsx index b524cbbd834..83fa59fa38a 100644 --- a/packages/react-storage/src/components/StorageManager/ui/FileList/FileControl.tsx +++ b/packages/react-storage/src/components/StorageManager/ui/FileList/FileControl.tsx @@ -3,7 +3,8 @@ import React from 'react'; import { ComponentClassName } from '@aws-amplify/ui'; import { View, Loader, Button } from '@aws-amplify/ui-react'; -import { FileStatus } from '../../types'; +import { FileStatus } from '../../../FileUploader/types'; + import { FileStatusMessage } from './FileStatusMessage'; import { FileRemoveButton } from './FileRemoveButton'; import { UploadDetails } from './FileDetails'; diff --git a/packages/react-storage/src/components/StorageManager/ui/FileList/FileList.tsx b/packages/react-storage/src/components/StorageManager/ui/FileList/FileList.tsx index d1fe33699c1..649d7c2df66 100644 --- a/packages/react-storage/src/components/StorageManager/ui/FileList/FileList.tsx +++ b/packages/react-storage/src/components/StorageManager/ui/FileList/FileList.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { ComponentClassName } from '@aws-amplify/ui'; import { Alert, View } from '@aws-amplify/ui-react'; -import { FileStatus } from '../../types'; +import { FileStatus } from '../../../FileUploader/types'; import { FileControl } from './FileControl'; import { FileListProps } from './types'; diff --git a/packages/react-storage/src/components/StorageManager/ui/FileList/FileStatusMessage.tsx b/packages/react-storage/src/components/StorageManager/ui/FileList/FileStatusMessage.tsx index 2bb22ca0896..15c75bf8b99 100644 --- a/packages/react-storage/src/components/StorageManager/ui/FileList/FileStatusMessage.tsx +++ b/packages/react-storage/src/components/StorageManager/ui/FileList/FileStatusMessage.tsx @@ -5,7 +5,7 @@ import { ComponentClassName } from '@aws-amplify/ui'; import { Text, View } from '@aws-amplify/ui-react'; import { IconCheck, IconError, useIcons } from '@aws-amplify/ui-react/internal'; import { classNameModifier } from '@aws-amplify/ui'; -import { FileStatus } from '../../types'; +import { FileStatus } from '../../../FileUploader/types'; import { FileStatusMessageProps } from './types'; export const FileStatusMessage = ({ diff --git a/packages/react-storage/src/components/StorageManager/ui/FileList/__tests__/FileControl.test.tsx b/packages/react-storage/src/components/StorageManager/ui/FileList/__tests__/FileControl.test.tsx index ececed8160b..884c50e768d 100644 --- a/packages/react-storage/src/components/StorageManager/ui/FileList/__tests__/FileControl.test.tsx +++ b/packages/react-storage/src/components/StorageManager/ui/FileList/__tests__/FileControl.test.tsx @@ -3,10 +3,11 @@ import { render } from '@testing-library/react'; import { ComponentClassName } from '@aws-amplify/ui'; +import { FileStatus } from '../../../../FileUploader/types'; +import { defaultFileUploaderDisplayText as defaultStorageManagerDisplayText } from '../../../../FileUploader/utils/displayText'; + import { FileControlProps } from '../types'; import { FileControl } from '../FileControl'; -import { FileStatus } from '../../../types'; -import { defaultStorageManagerDisplayText } from '../../../utils/displayText'; const fileControlProps: FileControlProps = { displayText: defaultStorageManagerDisplayText, diff --git a/packages/react-storage/src/components/StorageManager/ui/FileList/__tests__/FileList.test.tsx b/packages/react-storage/src/components/StorageManager/ui/FileList/__tests__/FileList.test.tsx index d2c4c9c9f77..8651b40f8ba 100644 --- a/packages/react-storage/src/components/StorageManager/ui/FileList/__tests__/FileList.test.tsx +++ b/packages/react-storage/src/components/StorageManager/ui/FileList/__tests__/FileList.test.tsx @@ -6,8 +6,8 @@ import { ComponentClassName } from '@aws-amplify/ui'; import { FileList } from '../FileList'; import { FileListProps } from '../types'; -import { FileStatus, StorageFile } from '../../../types'; -import { defaultStorageManagerDisplayText } from '../../../utils'; +import { FileStatus, StorageFile } from '../../../../FileUploader/types'; +import { defaultFileUploaderDisplayText as defaultStorageManagerDisplayText } from '../../../../FileUploader/utils'; const mockFile: StorageFile = { id: 'test', diff --git a/packages/react-storage/src/components/StorageManager/ui/FileList/__tests__/FileStatusMessage.test.tsx b/packages/react-storage/src/components/StorageManager/ui/FileList/__tests__/FileStatusMessage.test.tsx index 8021aaf33c0..b90660460c5 100644 --- a/packages/react-storage/src/components/StorageManager/ui/FileList/__tests__/FileStatusMessage.test.tsx +++ b/packages/react-storage/src/components/StorageManager/ui/FileList/__tests__/FileStatusMessage.test.tsx @@ -6,7 +6,7 @@ import { IconsProvider, View } from '@aws-amplify/ui-react'; import { FileStatusMessage } from '../FileStatusMessage'; import { FileStatusMessageProps } from '../types'; -import { FileStatus } from '../../../types'; +import { FileStatus } from '../../../../FileUploader/types'; const uploadingText = 'Uploading...'; const uploadingPausedText = 'Uploading paused...'; diff --git a/packages/react-storage/src/components/StorageManager/ui/FileList/types.ts b/packages/react-storage/src/components/StorageManager/ui/FileList/types.ts index 73ba250e75a..265317f32d5 100644 --- a/packages/react-storage/src/components/StorageManager/ui/FileList/types.ts +++ b/packages/react-storage/src/components/StorageManager/ui/FileList/types.ts @@ -1,5 +1,8 @@ -import { StorageManagerDisplayTextDefault, TaskHandler } from '../../utils'; -import { FileStatus, StorageFile } from '../../types'; +import { + FileUploaderDisplayTextDefault as StorageManagerDisplayTextDefault, + TaskHandler, +} from '../../../FileUploader/utils'; +import { FileStatus, StorageFile } from '../../../FileUploader/types'; export interface FileListProps { displayText: StorageManagerDisplayTextDefault; diff --git a/packages/react-storage/src/components/StorageManager/ui/FileListFooter/FileListFooter.tsx b/packages/react-storage/src/components/StorageManager/ui/FileListFooter/FileListFooter.tsx index a19e3d14230..48d94870220 100644 --- a/packages/react-storage/src/components/StorageManager/ui/FileListFooter/FileListFooter.tsx +++ b/packages/react-storage/src/components/StorageManager/ui/FileListFooter/FileListFooter.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { ComponentClassName } from '@aws-amplify/ui'; import { View, Button } from '@aws-amplify/ui-react'; -import { StorageManagerDisplayTextDefault } from '../../utils'; +import { FileUploaderDisplayTextDefault as StorageManagerDisplayTextDefault } from '../../../FileUploader/utils'; export interface FileListFooterProps { remainingFilesCount: number; diff --git a/packages/react-storage/src/components/StorageManager/ui/FileListHeader/FileListHeader.tsx b/packages/react-storage/src/components/StorageManager/ui/FileListHeader/FileListHeader.tsx index a01c6387e91..156dde0aa4d 100644 --- a/packages/react-storage/src/components/StorageManager/ui/FileListHeader/FileListHeader.tsx +++ b/packages/react-storage/src/components/StorageManager/ui/FileListHeader/FileListHeader.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import { StorageManagerDisplayTextDefault } from '../../utils'; +import { FileUploaderDisplayTextDefault as StorageManagerDisplayTextDefault } from '../../../FileUploader/utils'; + import { ComponentClassName } from '@aws-amplify/ui'; import { Text } from '@aws-amplify/ui-react'; diff --git a/packages/react-storage/src/components/StorageManager/ui/FileListHeader/__tests__/FileListHeader.test.tsx b/packages/react-storage/src/components/StorageManager/ui/FileListHeader/__tests__/FileListHeader.test.tsx index 35a0efe622f..551f31efd51 100644 --- a/packages/react-storage/src/components/StorageManager/ui/FileListHeader/__tests__/FileListHeader.test.tsx +++ b/packages/react-storage/src/components/StorageManager/ui/FileListHeader/__tests__/FileListHeader.test.tsx @@ -4,7 +4,7 @@ import { render } from '@testing-library/react'; import { ComponentClassName } from '@aws-amplify/ui'; import { FileListHeader, FileListHeaderProps } from '../FileListHeader'; -import { defaultStorageManagerDisplayText } from '../../../utils/displayText'; +import { defaultFileUploaderDisplayText as defaultStorageManagerDisplayText } from '../../../../FileUploader/utils/displayText'; const headerProps: FileListHeaderProps = { fileCount: 2, diff --git a/packages/react-storage/src/components/StorageManager/utils/__tests__/checkMaxFileSize.test.ts b/packages/react-storage/src/components/StorageManager/utils/__tests__/checkMaxFileSize.test.ts deleted file mode 100644 index 891cfb3a96b..00000000000 --- a/packages/react-storage/src/components/StorageManager/utils/__tests__/checkMaxFileSize.test.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { checkMaxFileSize } from '../checkMaxFileSize'; - -const imageFile = new File(['hello'], 'hello.png', { type: 'image/png' }); - -const getFileSizeErrorText = (sizeText: string) => `Error over ${sizeText}`; - -describe('checkMaxFileSize', () => { - it('returns empty string if maxFileSize is undefined', () => { - const message = checkMaxFileSize({ - file: imageFile, - maxFileSize: undefined, - getFileSizeErrorText, - }); - - expect(message).toBe(''); - }); - - it('returns empty string if file size is under maxFileSize', () => { - const message = checkMaxFileSize({ - file: imageFile, - maxFileSize: 6, - getFileSizeErrorText, - }); - - expect(message).toBe(''); - }); - - it('returns correct max error string if file size is over maxFileSize', () => { - const maxFileSize = 4; - const message = checkMaxFileSize({ - file: imageFile, - maxFileSize, - getFileSizeErrorText, - }); - - expect(message).toBe(`Error over ${maxFileSize} B`); - }); -}); diff --git a/packages/react-storage/src/components/StorageManager/utils/__tests__/filterAllowedFiles.test.ts b/packages/react-storage/src/components/StorageManager/utils/__tests__/filterAllowedFiles.test.ts deleted file mode 100644 index e3edb59ba04..00000000000 --- a/packages/react-storage/src/components/StorageManager/utils/__tests__/filterAllowedFiles.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { filterAllowedFiles } from '../filterAllowedFiles'; -const imageFile = new File(['hello'], 'hello.png', { type: 'image/png' }); -const docFile = new File(['goodbye'], 'goodbye.doc', { - type: 'application/msword', -}); - -describe('filterAllowedFiles', () => { - it('returns only image files with mimetype image/*', () => { - const acceptedFileTypes = ['image/*']; - const acceptedFiles = filterAllowedFiles( - [imageFile, docFile], - acceptedFileTypes - ); - expect(acceptedFiles).toEqual([imageFile]); - }); - it('returns only doc files', () => { - const acceptedFileTypes = ['.doc']; - const acceptedFiles = filterAllowedFiles( - [imageFile, docFile], - acceptedFileTypes - ); - expect(acceptedFiles).toEqual([docFile]); - }); - it('returns no acceptable files', () => { - const acceptedFileTypes = ['.xls']; - const acceptedFiles = filterAllowedFiles( - [imageFile, docFile], - acceptedFileTypes - ); - expect(acceptedFiles).toEqual([]); - }); - it('returns all files with undefined filter', () => { - const acceptedFileTypes = undefined; - const acceptedFiles = filterAllowedFiles( - [imageFile, docFile], - acceptedFileTypes - ); - expect(acceptedFiles).toEqual([imageFile, docFile]); - }); - it('returns all files with empty array', () => { - const acceptedFileTypes: string[] = []; - const acceptedFiles = filterAllowedFiles( - [imageFile, docFile], - acceptedFileTypes - ); - expect(acceptedFiles).toEqual([imageFile, docFile]); - }); - it('returns all files with star value in array', () => { - const acceptedFileTypes = ['*']; - const acceptedFiles = filterAllowedFiles( - [imageFile, docFile], - acceptedFileTypes - ); - expect(acceptedFiles).toEqual([imageFile, docFile]); - }); - it('returns all files with star value anywhere in array', () => { - const acceptedFileTypes = ['.doc', '*', '.xls']; - const acceptedFiles = filterAllowedFiles( - [imageFile, docFile], - acceptedFileTypes - ); - expect(acceptedFiles).toEqual([imageFile, docFile]); - }); - it('returns only image with star file specifier', () => { - const acceptedFileTypes = ['image/*']; - const acceptedFiles = filterAllowedFiles( - [imageFile, docFile], - acceptedFileTypes - ); - expect(acceptedFiles).toEqual([imageFile]); - }); -}); diff --git a/packages/react-storage/src/components/StorageManager/utils/__tests__/getInput.spec.ts b/packages/react-storage/src/components/StorageManager/utils/__tests__/getInput.spec.ts deleted file mode 100644 index 901b261ee8b..00000000000 --- a/packages/react-storage/src/components/StorageManager/utils/__tests__/getInput.spec.ts +++ /dev/null @@ -1,284 +0,0 @@ -import * as AuthModule from 'aws-amplify/auth'; -import { UploadDataWithPathInput, UploadDataInput } from 'aws-amplify/storage'; -import { getInput, GetInputParams } from '../getInput'; - -const identityId = 'identity-id'; -const fetchAuthSpy = jest - .spyOn(AuthModule, 'fetchAuthSession') - .mockResolvedValue({ identityId }); - -const file = new File(['hello'], 'hello.png', { type: 'image/png' }); -const key = file.name; -const onProgress = jest.fn(); -const accessLevel = 'guest'; - -const processFilePrefix = 'my-prefix/'; -const processFile: GetInputParams['processFile'] = ({ key, ...rest }) => ({ - key: `${processFilePrefix}${key}`, - ...rest, -}); - -const stringPath = 'my-path/'; - -const onProcessFileSuccess = jest.fn(); -const inputBase: Omit = { - file, - key, - onProgress, - processFile: undefined, - onProcessFileSuccess, -}; -const pathStringInput: GetInputParams = { - ...inputBase, - accessLevel: undefined, - path: stringPath, -}; -const pathCallbackInput: GetInputParams = { - ...inputBase, - accessLevel: undefined, - path: ({ identityId }) => `${stringPath}${identityId}`, -}; - -const accessLevelWithoutPathInput: GetInputParams = { - ...inputBase, - accessLevel, - path: undefined, -}; - -const accessLevelWithPathInput: GetInputParams = { - ...inputBase, - accessLevel, - file, - key, - path: stringPath, -}; - -describe('getInput', () => { - beforeEach(() => { - onProcessFileSuccess.mockClear(); - fetchAuthSpy.mockClear(); - }); - - it('resolves an UploadDataWithPathInput with a string `path` as expected', async () => { - const expected: UploadDataWithPathInput = { - data: file, - options: { - contentType: file.type, - useAccelerateEndpoint: undefined, - onProgress, - }, - path: `${stringPath}${key}`, - }; - - const input = getInput(pathStringInput); - - const output = await input(); - - expect(output).toStrictEqual(expected); - }); - - it('resolves an UploadDataWithPathInput with a callback `path` as expected', async () => { - const expected: UploadDataWithPathInput = { - data: file, - options: { - contentType: file.type, - useAccelerateEndpoint: undefined, - onProgress, - }, - path: `${stringPath}${identityId}${key}`, - }; - - const input = getInput(pathCallbackInput); - - const output = await input(); - - expect(output).toStrictEqual(expected); - }); - - it('resolves an UploadDataInput without a `path` as expected', async () => { - const expected: UploadDataInput = { - data: file, - options: { - accessLevel, - contentType: file.type, - useAccelerateEndpoint: undefined, - onProgress, - }, - key, - }; - - const input = getInput(accessLevelWithoutPathInput); - - const output = await input(); - - expect(output).toStrictEqual(expected); - }); - - it('resolves an UploadDataInput with a `path` as expected', async () => { - const expected: UploadDataInput = { - data: file, - options: { - accessLevel, - contentType: file.type, - useAccelerateEndpoint: undefined, - onProgress, - }, - key: `${stringPath}${key}`, - }; - - const input = getInput(accessLevelWithPathInput); - - const output = await input(); - - expect(output).toStrictEqual(expected); - }); - - it('handles a `processFile` param expected', async () => { - const expected: UploadDataWithPathInput = { - data: file, - options: { - contentType: file.type, - useAccelerateEndpoint: undefined, - onProgress, - }, - path: `${stringPath}${identityId}${processFilePrefix}${key}`, - }; - - const input = getInput({ ...pathCallbackInput, processFile }); - - const output = await input(); - - expect(output).toStrictEqual(expected); - }); - - it('calls `onProcessFileSuccess` when `processFile` is provided', async () => { - const processedKey = `processedKey`; - - const input = getInput({ - ...pathStringInput, - processFile: ({ key: _, ...rest }) => ({ - key: processedKey, - ...rest, - }), - }); - - await input(); - - expect(onProcessFileSuccess).toHaveBeenCalledTimes(1); - expect(onProcessFileSuccess).toHaveBeenCalledWith({ - processedKey, - }); - }); - - it('does not call `onProcessFileSuccess` when `processFile` is not provided', async () => { - const input = getInput(pathStringInput); - - await input(); - - expect(onProcessFileSuccess).not.toHaveBeenCalled(); - }); - - it('includes additional values returned from `processFile` in `options`', async () => { - const contentDisposition = 'attachment'; - const metadata = { key }; - - const expected: UploadDataWithPathInput = { - data: file, - options: { - contentDisposition, - contentType: file.type, - metadata, - onProgress, - useAccelerateEndpoint: undefined, - }, - path: `${stringPath}${processFilePrefix}${key}`, - }; - - const input = getInput({ - ...pathStringInput, - processFile: ({ key, ...rest }) => ({ - key: `${processFilePrefix}${key}`, - metadata, - contentDisposition, - ...rest, - }), - }); - - const output = await input(); - - expect(output).toStrictEqual(expected); - expect(output.options?.metadata).toStrictEqual(metadata); - expect(output.options?.contentDisposition).toStrictEqual( - contentDisposition - ); - }); - - it('calls `onProcessFileSuccess` after fetchAuthSession', async () => { - const processedKey = `processedKey`; - - const input = getInput({ - ...pathCallbackInput, - processFile: ({ key: _, ...rest }) => ({ - key: processedKey, - ...rest, - }), - }); - - await input(); - - const fetchAuthSessionCallOrder = fetchAuthSpy.mock.invocationCallOrder[0]; - const onProcessFileSuccessCallORder = - onProcessFileSuccess.mock.invocationCallOrder[0]; - expect(fetchAuthSessionCallOrder).toBeLessThan( - onProcessFileSuccessCallORder - ); - - expect(fetchAuthSpy).toHaveBeenCalledTimes(1); - expect(onProcessFileSuccess).toHaveBeenCalledTimes(1); - expect(onProcessFileSuccess).toHaveBeenCalledWith({ - processedKey, - }); - }); - - it('defaults `options.contentType` to "binary/octet-stream" when no file type is provided', async () => { - const data = new File(['hello'], 'hello.png'); - const expected: UploadDataWithPathInput = { - data, - options: { - contentType: 'binary/octet-stream', - useAccelerateEndpoint: undefined, - onProgress, - }, - path: `${stringPath}${key}`, - }; - - const input = getInput({ ...pathStringInput, file: data }); - - const output = await input(); - - expect(output).toStrictEqual(expected); - }); - - it('accepts useAccelerateEndpoint', async () => { - const data = new File(['hello'], 'hello.png'); - const expected: UploadDataWithPathInput = { - data, - options: { - contentType: 'binary/octet-stream', - useAccelerateEndpoint: true, - onProgress, - }, - path: `${stringPath}${key}`, - }; - - const input = getInput({ - ...pathStringInput, - file: data, - useAccelerateEndpoint: true, - }); - - const output = await input(); - - expect(output).toStrictEqual(expected); - }); -}); diff --git a/packages/react-storage/src/components/StorageManager/utils/__tests__/uploadFile.test.ts b/packages/react-storage/src/components/StorageManager/utils/__tests__/uploadFile.test.ts deleted file mode 100644 index eae7186f3cc..00000000000 --- a/packages/react-storage/src/components/StorageManager/utils/__tests__/uploadFile.test.ts +++ /dev/null @@ -1,161 +0,0 @@ -import * as Storage from 'aws-amplify/storage'; - -import { UploadFileProps, uploadFile } from '../uploadFile'; - -const imageFile = new File(['hello'], 'hello.png', { type: 'image/png' }); -const key = imageFile.name; -const data = imageFile; - -const onError = jest.fn(); -const onComplete = jest.fn(); -const onProgress = jest.fn(); - -const uploadDataSpy = jest.spyOn(Storage, 'uploadData'); - -describe('uploadFile', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); - - it('behaves as expected with an accessLevel provided in the input', async () => { - const uploadDataOutput: Storage.UploadDataOutput = { - cancel: jest.fn(), - pause: jest.fn(), - resume: jest.fn(), - state: 'SUCCESS', - result: Promise.resolve({ key, data }), - }; - - uploadDataSpy.mockReturnValueOnce(uploadDataOutput); - const input: UploadFileProps['input'] = () => - Promise.resolve({ - data, - key, - options: { - accessLevel: 'guest', - contentType: 'image/png', - onProgress, - }, - }); - const { result } = await uploadFile({ input, onComplete }); - - await result; - - expect(uploadDataSpy).toHaveBeenCalledWith({ - data, - key, - options: { - accessLevel: 'guest', - contentType: imageFile.type, - onProgress: expect.any(Function), - }, - }); - - expect(onComplete).toHaveBeenCalledWith({ key, data }); - expect(onError).not.toHaveBeenCalled(); - }); - - it('behaves as expected without an accessLevel provided in the input', async () => { - const path = `my-path/${key}`; - const uploadDataPathOutput: Storage.UploadDataWithPathOutput = { - cancel: jest.fn(), - pause: jest.fn(), - resume: jest.fn(), - state: 'SUCCESS', - result: Promise.resolve({ path, data }), - }; - // @ts-expect-error amplify storage doesn't expose the base overload of `uploadData` - uploadDataSpy.mockReturnValueOnce(uploadDataPathOutput); - const input: UploadFileProps['input'] = () => - Promise.resolve({ - data, - path, - options: { contentType: 'image/png', onProgress }, - }); - const { result } = await uploadFile({ - input, - onComplete, - }); - - await result; - - expect(uploadDataSpy).toHaveBeenCalledWith({ - data, - options: { - contentType: imageFile.type, - onProgress: expect.any(Function), - }, - path, - }); - - expect(onComplete).toHaveBeenCalledWith({ path, data }); - expect(onError).not.toHaveBeenCalled(); - }); - - it('calls onStart as expected', async () => { - const onStart = jest.fn(); - const uploadDataOutput: Storage.UploadDataOutput = { - cancel: jest.fn(), - pause: jest.fn(), - resume: jest.fn(), - state: 'SUCCESS', - result: Promise.resolve({ key, data }), - }; - - uploadDataSpy.mockReturnValueOnce(uploadDataOutput); - const input: UploadFileProps['input'] = () => - Promise.resolve({ - data, - key, - options: { - accessLevel: 'guest', - contentType: 'image/png', - onProgress, - }, - }); - const { result } = await uploadFile({ input, onComplete, onStart }); - - await result; - - expect(uploadDataSpy).toHaveBeenCalledWith({ - data, - key, - options: { - accessLevel: 'guest', - contentType: imageFile.type, - onProgress: expect.any(Function), - }, - }); - - expect(onStart).toHaveBeenCalledWith({ key, uploadTask: uploadDataOutput }); - }); - - it('calls errorCallback on upload error', async () => { - const error = new Error('Error'); - uploadDataSpy.mockReturnValueOnce({ - cancel: jest.fn(), - pause: jest.fn(), - resume: jest.fn(), - result: Promise.reject(error), - state: 'ERROR', - }); - - const input: UploadFileProps['input'] = () => - Promise.resolve({ - data, - key, - options: { - accessLevel: 'guest', - contentType: 'image/png', - onProgress, - }, - }); - const { result } = await uploadFile({ input, onComplete, onError }); - - await expect(result).rejects.toThrow(); - expect(onProgress).not.toHaveBeenCalled(); - expect(onError).toHaveBeenCalledTimes(1); - expect(onError).toHaveBeenCalledWith({ error, key }); - expect(onComplete).not.toHaveBeenCalled(); - }); -}); diff --git a/packages/react-storage/src/components/StorageManager/utils/checkMaxFileSize.ts b/packages/react-storage/src/components/StorageManager/utils/checkMaxFileSize.ts deleted file mode 100644 index 261c0398e2f..00000000000 --- a/packages/react-storage/src/components/StorageManager/utils/checkMaxFileSize.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { humanFileSize } from '@aws-amplify/ui'; - -export const checkMaxFileSize = ({ - file, - getFileSizeErrorText, - maxFileSize, -}: { - file: File; - getFileSizeErrorText: (sizeText: string) => string; - maxFileSize?: number; -}): string => { - if (maxFileSize === undefined) return ''; - if (file.size > maxFileSize) { - return getFileSizeErrorText(humanFileSize(maxFileSize, true)); - } - return ''; -}; diff --git a/packages/react-storage/src/components/StorageManager/utils/displayText.ts b/packages/react-storage/src/components/StorageManager/utils/displayText.ts deleted file mode 100644 index 57b2adc7332..00000000000 --- a/packages/react-storage/src/components/StorageManager/utils/displayText.ts +++ /dev/null @@ -1,65 +0,0 @@ -import { DisplayTextTemplate } from '@aws-amplify/ui'; - -export type StorageManagerDisplayText = DisplayTextTemplate<{ - getFilesUploadedText?: (count: number) => string; - getFileSizeErrorText?: (sizeText: string) => string; - getRemainingFilesText?: (count: number) => string; - getSelectedFilesText?: (count: number) => string; - getUploadingText?: (percentage: number) => string; - getUploadButtonText?: (count: number) => string; - getMaxFilesErrorText?: (count: number) => string; - getErrorText?: (message: string) => string; - doneButtonText?: string; - clearAllButtonText?: string; - extensionNotAllowedText?: string; - browseFilesText?: string; - dropFilesText?: string; - pauseButtonText?: string; - resumeButtonText?: string; - uploadSuccessfulText?: string; - getPausedText?: (percentage: number) => string; -}>; - -export type StorageManagerDisplayTextDefault = - Required; - -export const defaultStorageManagerDisplayText: StorageManagerDisplayTextDefault = - { - getFilesUploadedText(count: number): string { - return `${count} ${count === 1 ? 'file uploaded' : 'files uploaded'}`; - }, - getFileSizeErrorText(sizeText: string): string { - return `File size must be below ${sizeText}`; - }, - getRemainingFilesText(count: number): string { - return `${count} ${count === 1 ? 'file' : 'files'} uploading`; - }, - getSelectedFilesText(count: number): string { - return `${count} ${count === 1 ? 'file' : 'files'} selected`; - }, - getUploadingText(percentage: number): string { - return `Uploading${percentage > 0 ? `: ${percentage}%` : ''}`; - }, - getUploadButtonText(count: number): string { - return `Upload ${count} ${count === 1 ? 'file' : 'files'}`; - }, - getMaxFilesErrorText(count: number): string { - return `Cannot choose more than ${count} ${ - count === 1 ? 'file' : 'files' - }. Remove files before updating`; - }, - getErrorText(message: string): string { - return message; - }, - doneButtonText: 'Done', - clearAllButtonText: 'Clear all', - extensionNotAllowedText: 'Extension not allowed', - browseFilesText: 'Browse files', - dropFilesText: 'Drop files here or', - pauseButtonText: 'Pause', - resumeButtonText: 'Resume', - uploadSuccessfulText: 'Uploaded', - getPausedText(percentage: number): string { - return `Paused: ${percentage}%`; - }, - }; diff --git a/packages/react-storage/src/components/StorageManager/utils/filterAllowedFiles.ts b/packages/react-storage/src/components/StorageManager/utils/filterAllowedFiles.ts deleted file mode 100644 index 323203d28dd..00000000000 --- a/packages/react-storage/src/components/StorageManager/utils/filterAllowedFiles.ts +++ /dev/null @@ -1,30 +0,0 @@ -export const filterAllowedFiles = ( - files: File[], - acceptedFileTypes?: string[] -): File[] => { - // Allow any files if acceptedFileTypes is undefined, empty array, or contains '*' - if ( - !acceptedFileTypes || - acceptedFileTypes.length === 0 || - acceptedFileTypes.includes('*') - ) { - return files; - } - - // Remove any files that are not in the accepted file list - return files.filter((file) => { - const fileName = file.name || ''; - const mimeType = (file.type || '').toLowerCase(); - const baseMimeType = mimeType.replace(/\/.*$/, ''); - return acceptedFileTypes.some((type) => { - const validType = type.trim().toLowerCase(); - if (validType.charAt(0) === '.') { - return fileName.toLowerCase().endsWith(validType); - } else if (validType.endsWith('/*')) { - // This is something like a image/* mime type - return baseMimeType === validType.replace(/\/.*$/, ''); - } - return mimeType === validType; - }); - }); -}; diff --git a/packages/react-storage/src/components/StorageManager/utils/getInput.ts b/packages/react-storage/src/components/StorageManager/utils/getInput.ts deleted file mode 100644 index 9f810d1d1b1..00000000000 --- a/packages/react-storage/src/components/StorageManager/utils/getInput.ts +++ /dev/null @@ -1,77 +0,0 @@ -import { fetchAuthSession } from 'aws-amplify/auth'; -import { StorageAccessLevel } from '@aws-amplify/core'; -import { UploadDataWithPathInput, UploadDataInput } from 'aws-amplify/storage'; - -import { isString, isTypedFunction } from '@aws-amplify/ui'; - -import { ProcessFile } from '../types'; -import { resolveFile } from './resolveFile'; -import { PathCallback, PathInput } from './uploadFile'; - -export interface GetInputParams { - accessLevel: StorageAccessLevel | undefined; - file: File; - key: string; - onProcessFileSuccess: (input: { processedKey: string }) => void; - onProgress: NonNullable['onProgress']; - path: string | PathCallback | undefined; - processFile: ProcessFile | undefined; - useAccelerateEndpoint?: boolean; -} - -export const getInput = ({ - accessLevel, - file, - key, - onProcessFileSuccess, - onProgress, - path, - processFile, - useAccelerateEndpoint, -}: GetInputParams) => { - return async (): Promise => { - const hasCallbackPath = isTypedFunction(path); - const hasStringPath = isString(path); - - const hasKeyInput = !!accessLevel && !hasCallbackPath; - - const { - file: data, - key: processedKey, - ...rest - } = await resolveFile({ file, key, processFile }); - - const contentType = file.type || 'binary/octet-stream'; - - // IMPORTANT: always pass `...rest` here for backwards compatibility - const options = { contentType, onProgress, useAccelerateEndpoint, ...rest }; - - let inputResult: PathInput | UploadDataInput; - if (hasKeyInput) { - // legacy handling of `path` is to prefix to `fileKey` - const resolvedKey = hasStringPath - ? `${path}${processedKey}` - : processedKey; - - inputResult = { - data, - key: resolvedKey, - options: { ...options, accessLevel }, - }; - } else { - const { identityId } = await fetchAuthSession(); - const resolvedPath = `${ - hasCallbackPath ? path({ identityId }) : path - }${processedKey}`; - - inputResult = { data: file, path: resolvedPath, options }; - } - - if (processFile) { - // provide post-processing value of target `key` - onProcessFileSuccess({ processedKey }); - } - - return inputResult; - }; -}; diff --git a/packages/react-storage/src/components/StorageManager/utils/index.ts b/packages/react-storage/src/components/StorageManager/utils/index.ts deleted file mode 100644 index 6f88cad89ae..00000000000 --- a/packages/react-storage/src/components/StorageManager/utils/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -export { checkMaxFileSize } from './checkMaxFileSize'; -export { - defaultStorageManagerDisplayText, - StorageManagerDisplayText, - StorageManagerDisplayTextDefault, -} from './displayText'; -export { filterAllowedFiles } from './filterAllowedFiles'; -export { getInput } from './getInput'; - -export { - PathCallback, - TaskEvent, - TaskHandler, - uploadFile, - UploadTask, -} from './uploadFile'; diff --git a/packages/react-storage/src/components/StorageManager/utils/resolveFile.ts b/packages/react-storage/src/components/StorageManager/utils/resolveFile.ts deleted file mode 100644 index faac8b90d1d..00000000000 --- a/packages/react-storage/src/components/StorageManager/utils/resolveFile.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { isFunction } from '@aws-amplify/ui'; -import { ProcessFile, ProcessFileParams } from '../types'; - -/** - * Utility function that takes the processFile prop, along with a file a key - * and returns a Promise that resolves to { file, key, ..rest } - * regardless if processFile is defined and if it is sync or async - */ -export const resolveFile = ({ - processFile, - ...input -}: ProcessFileParams & { - processFile?: ProcessFile; -}): Promise => { - return new Promise((resolve, reject) => { - const result = isFunction(processFile) ? processFile(input) : input; - if (result instanceof Promise) { - result.then(resolve).catch(reject); - } else { - resolve(result); - } - }); -}; diff --git a/packages/react-storage/src/components/StorageManager/utils/uploadFile.ts b/packages/react-storage/src/components/StorageManager/utils/uploadFile.ts deleted file mode 100644 index 762dbdca2ee..00000000000 --- a/packages/react-storage/src/components/StorageManager/utils/uploadFile.ts +++ /dev/null @@ -1,76 +0,0 @@ -import { - uploadData, - UploadDataInput, - UploadDataWithPathOutput, - UploadDataWithPathInput, - UploadDataOutput, -} from 'aws-amplify/storage'; -import { isFunction } from '@aws-amplify/ui'; - -/** - * Callback provided an input containing the current `identityId` - * - * @param {{identityId: string | undefined}} input - Input parameters - * @returns target S3 bucket key - */ -export type PathCallback = (input: { - identityId: string | undefined; -}) => string; - -export type UploadTask = UploadDataOutput | UploadDataWithPathOutput; -export interface TaskEvent { - id: string; - uploadTask: UploadTask; -} - -// omit `path` callback, `path` must always be a string to support resolving -// `path` callback with `fileKey` and `identityId` -export type PathInput = Omit & { - path: string; -}; - -export type TaskHandler = (event: TaskEvent) => void; -export interface UploadFileProps { - input: () => Promise; - onComplete?: ( - result: Awaited<(UploadDataWithPathOutput | UploadDataOutput)['result']> - ) => void; - onError?: (event: { key: string; error: Error }) => void; - onStart?: (event: { key: string; uploadTask: UploadTask }) => void; -} - -type UploadData = ( - input: PathInput | UploadDataInput -) => UploadDataWithPathOutput | UploadDataOutput; - -export async function uploadFile({ - input, - onError, - onStart, - onComplete, -}: UploadFileProps): Promise { - const resolvedInput = await input(); - - const uploadTask = (uploadData as UploadData)(resolvedInput); - - const key = - (resolvedInput as { key: string })?.key ?? - (resolvedInput as { path: string })?.path; - - if (isFunction(onStart)) { - onStart({ key, uploadTask }); - } - - uploadTask.result - .then((result) => { - if (isFunction(onComplete) && uploadTask.state === 'SUCCESS') { - onComplete(result); - } - }) - .catch((error: Error) => { - if (isFunction(onError)) { - onError({ key, error }); - } - }); - return uploadTask; -} From 7e6d495955d36f2264db5ca6f11fea385eaa4a6c Mon Sep 17 00:00:00 2001 From: Caleb Pollman Date: Fri, 20 Sep 2024 16:44:20 -0700 Subject: [PATCH 4/5] lower coverage thresholds due to removal of duplicated code --- packages/react-storage/jest.config.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/react-storage/jest.config.ts b/packages/react-storage/jest.config.ts index 09e06db1612..09a528f0a3f 100644 --- a/packages/react-storage/jest.config.ts +++ b/packages/react-storage/jest.config.ts @@ -12,9 +12,9 @@ const config: Config = { coverageThreshold: { global: { branches: 87, - functions: 90, - lines: 94, - statements: 95, + functions: 86.5, + lines: 93.5, + statements: 94, }, }, moduleNameMapper: { '^uuid$': '/../../node_modules/uuid' }, From 472642dbe368733cf408db4eebd1ad8a30b34922 Mon Sep 17 00:00:00 2001 From: Caleb Pollman Date: Fri, 20 Sep 2024 16:46:04 -0700 Subject: [PATCH 5/5] Create tough-moons-march.md --- .changeset/tough-moons-march.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/tough-moons-march.md diff --git a/.changeset/tough-moons-march.md b/.changeset/tough-moons-march.md new file mode 100644 index 00000000000..695b2d95f99 --- /dev/null +++ b/.changeset/tough-moons-march.md @@ -0,0 +1,5 @@ +--- +"@aws-amplify/ui-react-storage": patch +--- + +fix(file-uploader): fix duplicate upload requests