box-ui-elements-mlh
Version:
583 lines (504 loc) • 20.9 kB
JavaScript
import React from 'react';
import { shallow } from 'enzyme';
import * as UploaderUtils from '../../../utils/uploads';
import Browser from '../../../utils/Browser';
import { ContentUploaderComponent, CHUNKED_UPLOAD_MIN_SIZE_BYTES } from '../ContentUploader';
import Footer from '../Footer';
import {
STATUS_PENDING,
STATUS_IN_PROGRESS,
STATUS_STAGED,
STATUS_COMPLETE,
STATUS_ERROR,
VIEW_ERROR,
VIEW_UPLOAD_EMPTY,
VIEW_UPLOAD_IN_PROGRESS,
VIEW_UPLOAD_SUCCESS,
} from '../../../constants';
const EXPAND_UPLOADS_MANAGER_ITEMS_NUM_THRESHOLD = 5;
jest.mock('../../../utils/Browser');
describe('elements/content-uploader/ContentUploader', () => {
const getWrapper = (props = {}) => shallow(<ContentUploaderComponent {...props} />);
const createMockFiles = length => {
const filesList = [];
for (let i = 0; i < length; i += 1) {
const file = new File(['contents'], `upload_file_${i}.txt`, {
type: 'text/plain',
});
filesList.push(file);
}
return filesList;
};
const mapToUploadItems = files => {
return files.map(file => {
return {
api: {},
extension: '',
file,
name: file.name,
progress: 0,
size: 1000,
status: STATUS_PENDING,
};
});
};
describe('onBeforeUpload()', () => {
test('should call onBeforeUpload', () => {
const onBeforeUpload = jest.fn();
const wrapper = getWrapper({
onBeforeUpload,
});
wrapper.instance().addFilesToUploadQueue([{ name: 'yoyo', size: 1000 }], jest.fn(), false);
expect(onBeforeUpload).toBeCalled();
});
});
describe('updateViewAndCollection()', () => {
test('should set itemIds to be an empty when method is called with an empty array', () => {
const onComplete = jest.fn();
const useUploadsManager = false;
const isResumableUploadsEnabled = false;
const wrapper = getWrapper({
onComplete,
useUploadsManager,
isResumableUploadsEnabled,
});
wrapper.instance().updateViewAndCollection([], null);
expect(wrapper.state().itemIds).toEqual({});
});
test.each([
['not', true, 'not', STATUS_PENDING, 0],
['', true, '', STATUS_COMPLETE, 1],
['', false, 'not', STATUS_STAGED, 1],
])(
'should %s call onComplete when isResumableUploadsEnabled is %s and %s all items are finished',
(a, isResumableUploadsEnabled, b, status, expected) => {
const onComplete = jest.fn();
const useUploadsManager = true;
const wrapper = getWrapper({
onComplete,
useUploadsManager,
isResumableUploadsEnabled,
});
const instance = wrapper.instance();
const items = [{ status }, { status: STATUS_COMPLETE }, { status: STATUS_ERROR }];
instance.updateViewAndCollection(items, null);
expect(onComplete).toHaveBeenCalledTimes(expected);
},
);
});
describe('addFilesToUploadQueue()', () => {
test('should overwrite itemIds if they already exist', () => {
const wrapper = getWrapper();
wrapper.setState({ itemIds: { yoyo: false } });
wrapper.instance().addFilesToUploadQueue([{ name: 'yoyo', size: 1000 }], jest.fn(), false);
const expected = { yoyo: true };
expect(wrapper.state().itemIds).toMatchObject(expected);
});
test('should add generated itemId', () => {
const wrapper = getWrapper({ rootFolderId: 0 });
global.Date.now = jest.fn(() => 10000);
wrapper.instance().addFilesToUploadQueue([{ name: 'yoyo', size: 1000 }], jest.fn(), false);
const expected = { yoyo: true, yoyo_0_10000: true };
expect(wrapper.state().itemIds).toEqual(expected);
});
test('should handle accepting package "files" separate from folders', () => {
const mockFile = { name: 'hi' };
Browser.isSafari = jest.fn(() => true);
const entry = {
isDirectory: true,
kind: 'file',
file: fn => {
fn(mockFile);
},
};
const wrapper = getWrapper({
rootFolderId: 0,
isFolderUploadEnabled: true,
hasUploads: true,
useUploadsManager: true,
});
global.Date.now = jest.fn(() => 10000);
wrapper.setProps({
files: [mockFile],
dataTransferItems: [
{
kind: 'file',
type: 'application/zip',
getAsFile: jest.fn(() => mockFile),
webkitGetAsEntry: () => entry,
name: 'hi',
},
],
});
const expected = { hi: true, hi_0_10000: true };
expect(wrapper.state().itemIds).toEqual(expected);
});
});
describe('removeFileFromUploadQueue()', () => {
const item = {
api: {
cancel: jest.fn(),
},
status: STATUS_IN_PROGRESS,
};
let wrapper;
let instance;
beforeEach(() => {
wrapper = getWrapper();
wrapper.setState({
items: [item],
});
instance = wrapper.instance();
});
test('should cancel and remove item from uploading queue', () => {
instance.removeFileFromUploadQueue(item);
expect(item.api.cancel).toBeCalled();
expect(wrapper.state().items.length).toBe(0);
});
test.each`
view | action
${VIEW_ERROR} | ${'should not'}
${VIEW_UPLOAD_EMPTY} | ${'should not'}
${VIEW_UPLOAD_IN_PROGRESS} | ${'should'}
${VIEW_UPLOAD_SUCCESS} | ${'should not'}
`('$action call upload if the view is $view', option => {
wrapper.setState({
view: option,
});
instance.upload = jest.fn();
instance.removeFileFromUploadQueue(item);
if (option === VIEW_UPLOAD_IN_PROGRESS) {
expect(instance.upload).toBeCalled();
} else {
expect(instance.upload).not.toBeCalled();
}
});
});
describe('resetFile()', () => {
test('should call getUploadAPI and updateViewAndCollection', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
const item = { api: { cancel: jest.fn() }, file: { size: 10 } };
instance.getUploadAPI = jest.fn();
instance.updateViewAndCollection = jest.fn();
instance.resetFile(item);
expect(instance.getUploadAPI).toBeCalled();
expect(instance.updateViewAndCollection).toBeCalled();
});
test('should reset progress, status, and existing item error', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
const item = {
api: { cancel: jest.fn() },
file: { size: 10 },
progress: 85,
status: STATUS_ERROR,
error: { name: 'testerror' },
};
instance.resetFile(item);
expect(item.progress).toBe(0);
expect(item.status).toBe(STATUS_PENDING);
expect(item.error).toBeUndefined();
});
});
describe('resumeFile()', () => {
test('should call resume from api and call updateViewAndCollection', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
const item = { api: {} };
item.api.resume = jest.fn();
instance.updateViewAndCollection = jest.fn();
instance.resumeFile(item);
expect(item.api.resume).toBeCalled();
expect(instance.updateViewAndCollection).toBeCalled();
});
});
describe('onClick()', () => {
test('should cancel folder upload in progress', () => {
const item = { api: {}, isFolder: true, status: STATUS_IN_PROGRESS };
const onClickCancel = jest.fn();
const wrapper = getWrapper({ onClickCancel });
const instance = wrapper.instance();
instance.removeFileFromUploadQueue = jest.fn();
instance.onClick(item);
expect(instance.removeFileFromUploadQueue).toBeCalledWith(item);
expect(onClickCancel.mock.calls.length).toBe(1);
});
test.each([
[
'should set bytesUploadedOnLastResume when status is error and item is resumable',
{
api: { sessionId: 123, totalUploadedBytes: 123456 },
status: STATUS_ERROR,
file: { size: CHUNKED_UPLOAD_MIN_SIZE_BYTES + 1 },
},
true,
true,
],
[
'should not set bytesUploadedOnLastResume when file size <= CHUNKED_UPLOAD_MIN_SIZE_BYTES',
{
api: { sessionId: 123, totalUploadedBytes: 123456 },
status: STATUS_ERROR,
file: { size: CHUNKED_UPLOAD_MIN_SIZE_BYTES },
},
true,
true,
],
[
'should not set bytesUploadedOnLastResume when resumable uploads is not enabled',
{
api: { sessionId: 123, totalUploadedBytes: 123456 },
status: STATUS_ERROR,
file: { size: CHUNKED_UPLOAD_MIN_SIZE_BYTES + 1 },
},
false,
true,
],
[
'should not set bytesUploadedOnLastResume when not chunked upload',
{
api: { sessionId: 123, totalUploadedBytes: 123456 },
status: STATUS_ERROR,
file: { size: CHUNKED_UPLOAD_MIN_SIZE_BYTES + 1 },
},
true,
false,
],
[
'should not set bytesUploadedOnLastResume when item api has no session id',
{
api: { sessionId: undefined, totalUploadedBytes: 123456 },
status: STATUS_ERROR,
file: { size: CHUNKED_UPLOAD_MIN_SIZE_BYTES + 1 },
},
true,
true,
],
[
'should not set bytesUploadedOnLastResume when status is not error',
{
api: { sessionId: 123, totalUploadedBytes: 123456 },
status: STATUS_COMPLETE,
file: { size: CHUNKED_UPLOAD_MIN_SIZE_BYTES + 1 },
},
true,
true,
],
])('%o', (test, item, isResumableUploadsEnabled, chunked) => {
jest.spyOn(UploaderUtils, 'isMultiputSupported').mockImplementation(() => true);
const isChunkedUpload = chunked && item.file.size > CHUNKED_UPLOAD_MIN_SIZE_BYTES;
const isResumable = isResumableUploadsEnabled && isChunkedUpload && item.api.sessionId;
const onClickCancel = jest.fn();
const onClickResume = jest.fn();
const onClickRetry = jest.fn();
const wrapper = getWrapper({
chunked,
isResumableUploadsEnabled,
onClickCancel,
onClickResume,
onClickRetry,
});
const instance = wrapper.instance();
instance.removeFileFromUploadQueue = jest.fn();
instance.resumeFile = jest.fn();
instance.resetFile = jest.fn();
instance.uploadFile = jest.fn();
instance.onClick(item);
if (item.status === STATUS_ERROR && isResumable) {
expect(item.bytesUploadedOnLastResume).toBe(item.api.totalUploadedBytes);
expect(onClickResume.mock.calls.length).toBe(1);
} else if (item.status === STATUS_ERROR) {
expect(onClickRetry.mock.calls.length).toBe(1);
} else {
expect(item.bytesUploadedOnLastResume).toBe(undefined);
expect(onClickCancel.mock.calls.length).toBe(1);
}
});
});
describe('clickAllWithStatus()', () => {
test('should call onClick for all items', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
const items = [{ status: STATUS_COMPLETE }, { status: STATUS_IN_PROGRESS }, { status: STATUS_ERROR }];
instance.state.items = items;
instance.onClick = jest.fn();
instance.clickAllWithStatus();
expect(instance.onClick).toBeCalledWith(items[0]);
expect(instance.onClick).toBeCalledWith(items[1]);
expect(instance.onClick).toBeCalledWith(items[2]);
});
test('should call onClick for only items with given status', () => {
const wrapper = getWrapper();
const instance = wrapper.instance();
const items = [
{ status: STATUS_COMPLETE },
{ status: STATUS_ERROR },
{ status: STATUS_IN_PROGRESS },
{ status: STATUS_ERROR },
];
instance.state.items = items;
instance.onClick = jest.fn();
instance.clickAllWithStatus(STATUS_ERROR);
expect(instance.onClick).toBeCalledWith(items[1]);
expect(instance.onClick).toBeCalledWith(items[3]);
});
});
describe('isDone', () => {
test('should be true if all items are complete or staged', () => {
const wrapper = getWrapper();
const files = createMockFiles(3);
const items = mapToUploadItems(files).map(item => {
return {
...item,
status: STATUS_COMPLETE,
};
});
items[2].status = STATUS_STAGED;
wrapper.setState({
items,
});
expect(wrapper.find(Footer).prop('isDone')).toEqual(true);
});
test('should be false if not all items are complete or staged', () => {
const wrapper = getWrapper();
const files = createMockFiles(3);
const items = mapToUploadItems(files).map(item => {
return {
...item,
status: STATUS_COMPLETE,
};
});
items[2].status = STATUS_PENDING;
wrapper.setState({
items,
});
expect(wrapper.find(Footer).prop('isDone')).toEqual(false);
});
});
describe('getUploadAPI()', () => {
let wrapper;
let instance;
let getPlainUploadAPI;
let getChunkedUploadAPI;
const file = {
size: CHUNKED_UPLOAD_MIN_SIZE_BYTES + 1,
};
beforeEach(() => {
jest.spyOn(global.console, 'warn').mockImplementation();
wrapper = getWrapper({ isResumableUploadsEnabled: false });
instance = wrapper.instance();
getPlainUploadAPI = jest.fn();
getChunkedUploadAPI = jest.fn();
instance.createAPIFactory = jest.fn().mockReturnValue({
getPlainUploadAPI,
getChunkedUploadAPI,
});
});
afterEach(() => {
global.console.warn.mockRestore();
UploaderUtils.isMultiputSupported.mockRestore();
});
test('should use the chunked upload api', () => {
jest.spyOn(UploaderUtils, 'isMultiputSupported').mockImplementation(() => true);
instance.getUploadAPI(file);
expect(instance.createAPIFactory).toBeCalled();
expect(getChunkedUploadAPI).toBeCalled();
});
test('should use the regular upload api if the file <= CHUNKED_UPLOAD_MIN_SIZE_BYTES', () => {
jest.spyOn(UploaderUtils, 'isMultiputSupported').mockImplementation(() => true);
instance.getUploadAPI({
...file,
size: CHUNKED_UPLOAD_MIN_SIZE_BYTES,
});
expect(getPlainUploadAPI).toBeCalled();
});
test('should use the regular upload api if multiput not supported', () => {
jest.spyOn(UploaderUtils, 'isMultiputSupported').mockImplementation(() => false);
instance.getUploadAPI({
...file,
size: CHUNKED_UPLOAD_MIN_SIZE_BYTES,
});
expect(getPlainUploadAPI).toBeCalled();
});
test('should use the regular upload api if chunked is false', () => {
wrapper.setProps({
chunked: false,
});
jest.spyOn(UploaderUtils, 'isMultiputSupported').mockImplementation(() => true);
instance.getUploadAPI(file);
expect(getPlainUploadAPI).toBeCalled();
});
});
describe('Expand and collapse when more than EXPAND_UPLOADS_MANAGER_ITEMS_NUM_THRESHOLD files uploaded', () => {
let wrapper;
let instance;
let getPlainUploadAPI;
let getChunkedUploadAPI;
const expectAutoExpandStateToBe = expectation => {
expect(instance.isAutoExpanded).toBe(expectation);
expect(wrapper.state().isUploadsManagerExpanded).toBe(expectation);
};
beforeEach(() => {
wrapper = getWrapper({
useUploadsManager: true,
});
instance = wrapper.instance();
// Stub out upload so actual upload doesn't happen
const mockAPI = {
upload: () => {},
};
getPlainUploadAPI = () => mockAPI;
getChunkedUploadAPI = () => mockAPI;
instance.createAPIFactory = jest.fn().mockReturnValue({
getPlainUploadAPI,
getChunkedUploadAPI,
});
});
test('expand manager on upload when more than EXPAND_UPLOADS_MANAGER_ITEMS_NUM_THRESHOLD files', () => {
const files = createMockFiles(EXPAND_UPLOADS_MANAGER_ITEMS_NUM_THRESHOLD + 1);
wrapper.setProps({
files,
});
expectAutoExpandStateToBe(true);
});
test('do not expand manager on upload when less than than EXPAND_UPLOADS_MANAGER_ITEMS_NUM_THRESHOLD files', () => {
const files = createMockFiles(EXPAND_UPLOADS_MANAGER_ITEMS_NUM_THRESHOLD - 1);
wrapper.setProps({
files,
});
expectAutoExpandStateToBe(false);
});
test('expand manager on upload when two uploads totaling more than EXPAND_UPLOADS_MANAGER_ITEMS_NUM_THRESHOLD files', () => {
const files = createMockFiles(EXPAND_UPLOADS_MANAGER_ITEMS_NUM_THRESHOLD + 1);
wrapper.setProps({
files: files.slice(0, 3),
});
expectAutoExpandStateToBe(false);
wrapper.setProps({
files: files.slice(3),
});
expectAutoExpandStateToBe(true);
});
test('close upload manager when uploads are done', () => {
const files = createMockFiles(EXPAND_UPLOADS_MANAGER_ITEMS_NUM_THRESHOLD + 1);
const items = mapToUploadItems(files).map(item => {
return {
...item,
api: { upload: () => {} },
};
});
wrapper.setState({
items,
isUploadsManagerExpanded: true,
});
instance.isAutoExpanded = true;
instance.handleUploadSuccess(items[0]);
// Verify expanded is true after one file upload succeeds
expectAutoExpandStateToBe(true);
items.slice(1).forEach(item => instance.handleUploadSuccess(item));
expect(wrapper.state().view).toBe(VIEW_UPLOAD_SUCCESS);
expectAutoExpandStateToBe(false);
});
});
});