Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ exports[`CopyView display text values should match snapshot values 1`] = `
"addFilesLabel": "Add files",
"addFolderLabel": "Add folder",
"getActionCompleteMessage": [Function],
"getFilesValidationMessage": [Function],
"overwriteToggleLabel": "Overwrite existing files",
"statusDisplayCanceledLabel": "Canceled",
"statusDisplayCompletedLabel": "Completed",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { DEFAULT_ACTION_VIEW_DISPLAY_TEXT } from './shared';
import { DefaultUploadViewDisplayText } from '../../types';
import { UPLOAD_FILE_SIZE_LIMIT } from '../../../views/LocationActionView/constants';

export const DEFAULT_UPLOAD_VIEW_DISPLAY_TEXT: DefaultUploadViewDisplayText = {
...DEFAULT_ACTION_VIEW_DISPLAY_TEXT,
Expand Down Expand Up @@ -79,6 +80,19 @@ export const DEFAULT_UPLOAD_VIEW_DISPLAY_TEXT: DefaultUploadViewDisplayText = {

return { content: 'All files uploaded.', type };
},
getFilesValidationMessage: ({ invalidFiles } = {}) => {
if (!invalidFiles?.length) {
return undefined;
}
const fileNames = invalidFiles
.filter(({ file }) => file.size > UPLOAD_FILE_SIZE_LIMIT)
.map(({ file }) => file.name)
.join(', ');
return {
content: `These files cannot be added to the upload queue due to they are larger than 160GB respectively: ${fileNames}`,
type: 'warning',
};
},
statusDisplayOverwritePreventedLabel: 'Overwrite prevented',
overwriteToggleLabel: 'Overwrite existing files',
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { StatusCounts, Tasks } from '../tasks';
import {
CopyHandlerData,
CreateFolderHandlerData,
DeleteHandlerData,
FolderData,
LocationData,
Expand All @@ -11,7 +12,7 @@ import {
} from '../actions';
import { LocationState } from '../providers/store/location';
import { MessageType } from '../composables/Message';
import { CreateFolderHandlerData } from '../actions';
import { FileItems } from '../providers';

/**
* Common list view display text values
Expand Down Expand Up @@ -136,6 +137,9 @@ export interface DefaultUploadViewDisplayText
addFolderLabel: string;
statusDisplayOverwritePreventedLabel: string;
overwriteToggleLabel: string;
getFilesValidationMessage: (data?: {
invalidFiles?: FileItems;
}) => { content?: string; type?: MessageType } | undefined;
}

export interface DefaultStorageBrowserDisplayText {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { act, renderHook } from '@testing-library/react';
import { FilesProvider, useFiles } from '../context';

import * as UIReactModule from '@aws-amplify/ui-react/internal';
import { UPLOAD_FILE_SIZE_LIMIT } from '../../../../views/LocationActionView/constants';

let uuid = 0;
Object.defineProperty(globalThis, 'crypto', {
Expand All @@ -20,7 +21,7 @@ describe('useFiles', () => {
const { result } = renderHook(() => useFiles(), { wrapper: FilesProvider });
const [state, handler] = result.current;

expect(state).toStrictEqual([]);
expect(state).toStrictEqual({ validFiles: [], invalidFiles: [] });
expect(typeof handler).toBe('function');
});

Expand All @@ -39,22 +40,30 @@ describe('useFiles', () => {
expect(handleFileSelect).toHaveBeenCalledTimes(1);
});

it('adds files as as expected', () => {
it('adds files as expected', () => {
const fileOne = new File([], 'file-one');
const fileTwo = new File([], 'file-two');
const invalidFileThree = {
...new File([], 'file-three-invalid'),
size: UPLOAD_FILE_SIZE_LIMIT + 1,
};

const { result } = renderHook(() => useFiles(), { wrapper: FilesProvider });

const [initState, handler] = result.current;

expect(initState).toStrictEqual([]);
expect(initState).toStrictEqual({ validFiles: [], invalidFiles: [] });

act(() => {
handler({ type: 'ADD_FILE_ITEMS', files: [fileOne, fileTwo] });
handler({
type: 'ADD_FILE_ITEMS',
files: [fileOne, fileTwo, invalidFileThree],
});
});

const [nextState] = result.current;

expect(nextState).toHaveLength(2);
expect(nextState?.validFiles).toHaveLength(2);
expect(nextState?.invalidFiles).toHaveLength(1);
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FileItems, FileItem } from '../types';
import { UPLOAD_FILE_SIZE_LIMIT } from '../../../../views/LocationActionView/constants';
import { FileItem } from '../types';
import { resolveFiles, filesReducer, parseFileSelectParams } from '../utils';

let uuid = 0;
Expand All @@ -14,6 +15,10 @@ Object.defineProperty(globalThis, 'crypto', {
const fileOne = new File([], 'file-one');
const fileTwo = new File([], 'file-two');
const fileThree = new File([], 'file-three');
const invalidFile = {
...new File([], 'file-invalid'),
size: UPLOAD_FILE_SIZE_LIMIT + 1,
};

const fileItemOne: FileItem = {
id: 'item-one',
Expand All @@ -27,39 +32,56 @@ const fileItemTwo: FileItem = {
key: fileTwo.name,
};

const invalidFileItem: FileItem = {
id: 'item-invalid',
file: invalidFile,
key: invalidFile.name,
};

describe('files context utils', () => {
describe('resolveFiles', () => {
it('returns the previous `items` when incoming `files` are `undefined`', () => {
const previous = [fileItemOne, fileItemTwo];
const previous = {
validFiles: [fileItemOne, fileItemTwo],
invalidFiles: [],
};
const output = resolveFiles(previous, undefined);

expect(output).toBe(previous);
});

it('returns the previous `items` when incoming `files` is an empty array', () => {
const previous = [fileItemOne, fileItemTwo];
const previous = {
validFiles: [fileItemOne, fileItemTwo],
invalidFiles: [],
};
const output = resolveFiles(previous, []);

expect(output).toBe(previous);
});

it('returns the previous `items` when incoming `files` are all duplicates', () => {
const incoming = [fileOne, fileTwo];
const previous = [fileItemOne, fileItemTwo];
const previous = {
validFiles: [fileItemOne, fileItemTwo],
invalidFiles: [],
};
const output = resolveFiles(previous, incoming);

expect(output).toBe(previous);
expect(output).toEqual(previous);
});

it('filters incoming `files` that exist in previous `items`', () => {
const incoming = [fileOne, fileTwo, fileThree];
const previous = [fileItemOne, fileItemTwo];
const previous = {
validFiles: [fileItemOne, fileItemTwo],
invalidFiles: [],
};
const output = resolveFiles(previous, incoming);

expect(output).not.toBe(previous);
expect(output).toHaveLength(3);
expect(output.validFiles).toHaveLength(3);

const newItem = output[1];
const newItem = output.validFiles[1];

expect(newItem.file).toBe(fileThree);
expect(newItem.key).toBe(fileThree.name);
Expand All @@ -68,24 +90,27 @@ describe('files context utils', () => {

it('returns the sorted next `items` when previous `items` are `undefined`', () => {
const incoming = [fileTwo, fileOne];
const previous: FileItems = [];
const previous = { validFiles: [], invalidFiles: [] };
const output = resolveFiles(previous, incoming);

expect(output).toHaveLength(2);
const [itemOne, itemTwo] = output;
expect(output.validFiles).toHaveLength(2);
const [itemOne, itemTwo] = output.validFiles;

expect(itemOne.file).toBe(fileOne);
expect(itemTwo.file).toBe(fileTwo);
});

it('merges, sorts and returns previous and next `items`', () => {
const incoming = [fileThree];
const previous = [fileItemOne, fileItemTwo];
const previous = {
validFiles: [fileItemOne, fileItemTwo],
invalidFiles: [],
};
const output = resolveFiles(previous, incoming);

expect(output).toHaveLength(3);
expect(output.validFiles).toHaveLength(3);

const [itemOne, itemTwo, itemThree] = output;
const [itemOne, itemTwo, itemThree] = output.validFiles;

// fileItemOne.key === 'item-one'
expect(itemOne.key).toBe(fileItemOne.key);
Expand All @@ -98,61 +123,108 @@ describe('files context utils', () => {
});

it('returns the webKitRelativePath as key when available', () => {
const incoming = [
{ ...fileThree, webkitRelativePath: 'test/file/file-three' },
];
const previous = [fileItemOne, fileItemTwo];
const output = resolveFiles(previous, incoming);
const newFile = new File([], 'new-file');
Object.assign(newFile, { webkitRelativePath: 'test/file/new-file' });
const incoming = [newFile];
const previous = {
validFiles: [fileItemOne, fileItemTwo],
invalidFiles: [],
};
const { validFiles: output } = resolveFiles(previous, incoming);

expect(output).toHaveLength(3);
expect(output[2].key).toBe('test/file/file-three');
expect(output[2].key).toBe('test/file/new-file');
});
});

describe('filesReducer', () => {
it('adds `fileItems` as expected', () => {
const incoming = [fileOne, fileTwo, fileThree];
const previous = [fileItemOne, fileItemTwo];
const previous = {
validFiles: [fileItemOne, fileItemTwo],
invalidFiles: [],
};
const output = filesReducer(previous, {
type: 'ADD_FILE_ITEMS',
files: incoming,
});

expect(output).toHaveLength(3);
expect(output.validFiles).toHaveLength(3);
expect(output.invalidFiles).toHaveLength(0);
});

it('adds `fileItems` that is invalid', () => {
const previous = {
validFiles: [fileItemOne, fileItemTwo],
invalidFiles: [],
};
const output = filesReducer(previous, {
type: 'ADD_FILE_ITEMS',
files: [invalidFile],
});

expect(output.validFiles).toHaveLength(2);
expect(output.invalidFiles).toHaveLength(1);
});

it('removes a `fileItem` as expected', () => {
const previous = [fileItemOne, fileItemTwo];
const previous = {
validFiles: [fileItemOne, fileItemTwo],
invalidFiles: [],
};
const targetId = fileItemOne.id;

const output = filesReducer(previous, {
const { validFiles } = filesReducer(previous, {
type: 'REMOVE_FILE_ITEM',
id: targetId,
});

expect(output).toHaveLength(1);
expect(output[0]).toBe(fileItemTwo);
expect(validFiles).toHaveLength(1);
expect(validFiles[0]).toBe(fileItemTwo);
});

it('returns the previous items on remove when previous and next items are the same length', () => {
const previous = [fileItemOne, fileItemTwo];
const previous = {
validFiles: [fileItemOne, fileItemTwo],
invalidFiles: [],
};
const targetId = 'not a real id lol';

const output = filesReducer(previous, {
const { validFiles: outputValidFiles } = filesReducer(previous, {
type: 'REMOVE_FILE_ITEM',
id: targetId,
});

expect(output).toHaveLength(2);
expect(output).toBe(previous);
expect(outputValidFiles).toHaveLength(2);
expect(outputValidFiles).toBe(previous.validFiles);
});

it('resets `fileItems` as expected', () => {
const previous = [fileItemOne, fileItemTwo];
const previous = {
validFiles: [fileItemOne, fileItemTwo],
invalidFiles: [invalidFileItem],
};

const output = filesReducer(previous, { type: 'RESET_FILE_ITEMS' });
const { validFiles, invalidFiles } = filesReducer(previous, {
type: 'RESET_FILE_ITEMS',
});

expect(validFiles).toHaveLength(0);
expect(invalidFiles).toHaveLength(0);
});

it('resets invalid `fileTimes` as expected', () => {
const previous = {
validFiles: [fileItemOne, fileItemTwo],
invalidFiles: [invalidFileItem],
};

const { validFiles, invalidFiles } = filesReducer(previous, {
type: 'RESET_INVALID_FILE_ITEMS',
});

expect(output).toHaveLength(0);
expect(validFiles).toBe(previous.validFiles);
expect(invalidFiles).toHaveLength(0);
});
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@ export const { FilesContext, useFiles } = createContextUtilities({
export function FilesProvider({
children,
}: FilesProviderProps): React.JSX.Element {
const [items, dispatch] = React.useReducer(filesReducer, []);
const [items, dispatch] = React.useReducer(filesReducer, {
validFiles: [],
invalidFiles: [],
});

const [fileInput, handleFileSelect] = useFileSelect((nextFiles) => {
dispatch({ type: 'ADD_FILE_ITEMS', files: nextFiles });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ export type FilesActionType =
| { type: 'ADD_FILE_ITEMS'; files?: File[] }
| { type: 'REMOVE_FILE_ITEM'; id: string }
| { type: 'SELECT_FILES'; selectionType?: SelectionType }
| { type: 'RESET_FILE_ITEMS' };
| { type: 'RESET_FILE_ITEMS' }
| { type: 'RESET_INVALID_FILE_ITEMS' };

export type HandleFilesAction = (input: FilesActionType) => void;

Expand All @@ -14,7 +15,12 @@ export interface FileItem extends TaskData {

export type FileItems = FileItem[];

export type FilesContextType = [FileItems | undefined, HandleFilesAction];
export interface FileItemsState {
validFiles: FileItems;
invalidFiles: FileItems;
}

export type FilesContextType = [FileItemsState | undefined, HandleFilesAction];

export interface FilesProviderProps {
children?: React.ReactNode;
Expand Down
Loading