Skip to content
Merged
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
21 changes: 16 additions & 5 deletions src/elements/content-uploader/ContentUploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import CancelAllUploadsModal from './CancelAllUploadsModal';
import LargeFileWarningModal from './LargeFileWarningModal';
import DroppableContent from './DroppableContent';
import Footer from './Footer';
import ModernizedUploadsManagerDropZone from './ModernizedUploadsManagerDropZone';
import UploadsManager from './UploadsManager';
import { getUploadItemKey, mapToModernizedUploadItems } from './utils/mapToModernizedUploadItem';
import './ModernizedUploadsManagerPanel.scss';
Expand Down Expand Up @@ -76,6 +77,7 @@ export interface ContentUploaderProps {
dataTransferItems: Array<DataTransferItem | UploadDataTransferItemWithAPIOptions>;
fileLimit: number;
files?: Array<UploadFileWithAPIOptions | File>;
canDropOnUploadsManager?: boolean;
isDraggingItemsToUploadsManager?: boolean;
isFolderUploadEnabled: boolean;
isLarge: boolean;
Expand Down Expand Up @@ -172,6 +174,7 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
clientName: CLIENT_NAME_CONTENT_UPLOADER,
dataTransferItems: [],
files: [],
canDropOnUploadsManager: false,
fileLimit: FILE_LIMIT_DEFAULT,
isDraggingItemsToUploadsManager: false,
isFolderUploadEnabled: false,
Expand Down Expand Up @@ -652,7 +655,8 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
files: Array<UploadFileWithAPIOptions | File>,
itemUpdateCallback: Function,
) => {
const { rootFolderId } = this.props;
const { enableModernizedUploads, rootFolderId, useUploadsManager } = this.props;
const shouldAddRootFolderIdToUploadOptions = enableModernizedUploads && useUploadsManager;

// Convert files from the file API to upload items
const newItems = files.map(file => {
Expand All @@ -678,7 +682,9 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
};

if (uploadAPIOptions) {
Comment thread
bkepka-box marked this conversation as resolved.
uploadItem.options = uploadAPIOptions;
uploadItem.options = shouldAddRootFolderIdToUploadOptions
? { folderId: rootFolderId, ...uploadAPIOptions }
: uploadAPIOptions;
}

this.itemIdsRef.current[getFileId(uploadItem, rootFolderId)] = true;
Expand Down Expand Up @@ -1741,6 +1747,7 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
render() {
const {
className,
canDropOnUploadsManager,
enableModernizedUploads,
fileLimit,
isDraggingItemsToUploadsManager = false,
Expand Down Expand Up @@ -1794,8 +1801,12 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
return (
<div ref={measureRef} className={styleClassName} id={this.id}>
<ThemingStyles selector={`#${this.id}`} theme={theme} />
{/* eslint-disable-next-line jsx-a11y/no-static-element-interactions */}
<div
<ModernizedUploadsManagerDropZone
addDataTransferItemsToUploadQueue={droppedItems =>
this.addDroppedItemsToUploadQueue(droppedItems, this.upload)
}
allowedTypes={['Files']}
isDropEnabled={canDropOnUploadsManager}
className={classNames('bcu-modernized-panel', {
visible: modernizedPanelState === 'shown',
dismissing: modernizedPanelState === 'dismissing',
Expand Down Expand Up @@ -1833,7 +1844,7 @@ class ContentUploader extends Component<ContentUploaderProps, State> {
onUpgradeCTAClick={onUpgradeCTAClick}
oversizeFiles={oversizeFiles}
/>
</div>
</ModernizedUploadsManagerDropZone>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import * as React from 'react';
import omit from 'lodash/omit';

import makeDroppable from '../common/droppable';
import type { DOMStringList } from '../../common/types/core';

export interface ModernizedUploadsManagerDropZoneProps extends React.HTMLAttributes<HTMLDivElement> {
addDataTransferItemsToUploadQueue: (droppedItems: DataTransfer) => void;
allowedTypes: Array<string>;
children: React.ReactNode;
className: string;
isDropEnabled?: boolean;
}

interface DroppableStateProps {
canDrop?: boolean;
isDragging?: boolean;
isOver?: boolean;
}

const dropDefinition = {
dropValidator: (
{ allowedTypes, isDropEnabled = true }: ModernizedUploadsManagerDropZoneProps,
Comment thread
bkepka-box marked this conversation as resolved.
{ types }: { types: Array<string> | DOMStringList },
) => {
if (!isDropEnabled) {
return false;
}

if (types instanceof Array) {
return Array.from(types).some(type => allowedTypes.indexOf(type) > -1);
}

const allowedList = allowedTypes.filter(allowed => types.contains(allowed));
return allowedList.length > 0;
},

onDrop: (event: DragEvent, { addDataTransferItemsToUploadQueue }: ModernizedUploadsManagerDropZoneProps) => {
const { dataTransfer } = event;

if (!dataTransfer) {
return;
}

addDataTransferItemsToUploadQueue(dataTransfer);
},
} as const;

const ModernizedUploadsManagerDropZoneComponent = React.forwardRef<
HTMLDivElement,
ModernizedUploadsManagerDropZoneProps & DroppableStateProps
>((props, ref) => {
const { children } = props;
const htmlProps: React.HTMLAttributes<HTMLDivElement> = omit(props, [
'addDataTransferItemsToUploadQueue',
'allowedTypes',
'canDrop',
'children',
'isDragging',
'isDropEnabled',
'isOver',
]);

return (
<div ref={ref} {...htmlProps}>
{children}
</div>
);
});

const ModernizedUploadsManagerDropZone = makeDroppable(dropDefinition)(ModernizedUploadsManagerDropZoneComponent);

export default ModernizedUploadsManagerDropZone;
157 changes: 157 additions & 0 deletions src/elements/content-uploader/__tests__/ContentUploader.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import { shallow } from 'enzyme';
import { UploadsManager as UploadsManagerBP } from '@box/uploads-manager';
import * as UploaderUtils from '../../../utils/uploads';
import Browser from '../../../utils/Browser';
import { fireEvent, render, screen } from '../../../test-utils/testing-library';
import { ContentUploaderComponent, CHUNKED_UPLOAD_MIN_SIZE_BYTES } from '../ContentUploader';
import Footer from '../Footer';
import UploadsManager from '../UploadsManager';
import DroppableContent from '../DroppableContent';
import ModernizedUploadsManagerDropZone from '../ModernizedUploadsManagerDropZone';
import {
ERROR_CODE_ITEM_NAME_IN_USE,
STATUS_PENDING,
Expand Down Expand Up @@ -159,6 +161,33 @@ describe('elements/content-uploader/ContentUploader', () => {
expect(instance.itemIdsRef.current).toEqual(expected);
});

test('should add rootFolderId to raw file upload options for the modernized uploads manager', () => {
const files = [{ name: 'yoyo', size: 1000 }];
const wrapper = getWrapper({
enableModernizedUploads: true,
rootFolderId: '12345',
useUploadsManager: true,
});
wrapper.instance().upload = jest.fn();

wrapper.setProps({ files });

expect(wrapper.state().items[0].options.folderId).toBe('12345');
});

test('should not add rootFolderId to raw file upload options outside the modernized uploads manager', () => {
const files = [{ name: 'yoyo', size: 1000 }];
const wrapper = getWrapper({
rootFolderId: '12345',
useUploadsManager: true,
});
wrapper.instance().upload = jest.fn();

wrapper.setProps({ files });

expect(wrapper.state().items[0].options.folderId).toBeUndefined();
});

test('should handle accepting package "files" separate from folders', () => {
const mockFile = { name: 'hi' };
Browser.isSafari = jest.fn(() => true);
Expand Down Expand Up @@ -964,6 +993,127 @@ describe('elements/content-uploader/ContentUploader', () => {
});
});

describe('ModernizedUploadsManagerDropZone', () => {
const renderModernizedUploadsManagerDropZone = props =>
render(
<ModernizedUploadsManagerDropZone
addDataTransferItemsToUploadQueue={jest.fn()}
allowedTypes={['Files']}
className="bcu-modernized-panel"
data-testid="bcu-modernized-panel"
{...props}
>
<div />
</ModernizedUploadsManagerDropZone>,
);

test('prevents browser navigation and queues files dropped on the modernized panel', () => {
const addDataTransferItemsToUploadQueue = jest.fn();
const dataTransfer = {
items: ['file-item'],
types: ['Files'],
};
const dragEnterEvent = {
dataTransfer,
preventDefault: jest.fn(),
};
const dropEvent = {
dataTransfer,
preventDefault: jest.fn(),
};
const wrapper = shallow(
<ModernizedUploadsManagerDropZone
addDataTransferItemsToUploadQueue={addDataTransferItemsToUploadQueue}
allowedTypes={['Files']}
className="bcu-modernized-panel"
>
<div />
</ModernizedUploadsManagerDropZone>,
);

wrapper.instance().handleDragEnter(dragEnterEvent);
wrapper.instance().handleDrop(dropEvent);

expect(dragEnterEvent.preventDefault).toHaveBeenCalled();
expect(dropEvent.preventDefault).toHaveBeenCalled();
expect(addDataTransferItemsToUploadQueue).toHaveBeenCalledWith(dataTransfer);
});

test('prevents browser navigation without queueing when isDropEnabled is false', () => {
const addDataTransferItemsToUploadQueue = jest.fn();
const dataTransfer = {
items: ['file-item'],
types: ['Files'],
};
const dragEnterEvent = {
dataTransfer,
preventDefault: jest.fn(),
};
const dropEvent = {
dataTransfer,
preventDefault: jest.fn(),
};
const wrapper = shallow(
<ModernizedUploadsManagerDropZone
addDataTransferItemsToUploadQueue={addDataTransferItemsToUploadQueue}
allowedTypes={['Files']}
isDropEnabled={false}
className="bcu-modernized-panel"
>
<div />
</ModernizedUploadsManagerDropZone>,
);

wrapper.instance().handleDragEnter(dragEnterEvent);
wrapper.instance().handleDrop(dropEvent);

expect(dragEnterEvent.preventDefault).toHaveBeenCalled();
expect(dropEvent.preventDefault).toHaveBeenCalled();
expect(addDataTransferItemsToUploadQueue).not.toHaveBeenCalled();
});

test('does not forward droppable state props to the modernized panel DOM element', () => {
const consoleError = jest.spyOn(console, 'error').mockImplementation(() => {});
const dataTransfer = {
items: ['file-item'],
types: ['Files'],
};
let consoleErrors = '';

try {
renderModernizedUploadsManagerDropZone();

fireEvent.dragEnter(screen.getByTestId('bcu-modernized-panel'), { dataTransfer });
Comment thread
bkepka-box marked this conversation as resolved.

consoleErrors = consoleError.mock.calls.flat().join('\n');
} finally {
consoleError.mockRestore();
}

expect(consoleErrors).not.toContain('isDragging');
expect(consoleErrors).not.toContain('isOver');
});

test('does not queue files dropped on the modernized panel when isDropEnabled is false', () => {
const addDataTransferItemsToUploadQueue = jest.fn();
const dataTransfer = {
items: ['file-item'],
types: ['Files'],
};

renderModernizedUploadsManagerDropZone({
addDataTransferItemsToUploadQueue,
isDropEnabled: false,
});

const modernizedPanel = screen.getByTestId('bcu-modernized-panel');
fireEvent.dragEnter(modernizedPanel, { dataTransfer });
fireEvent.drop(modernizedPanel, { dataTransfer });

expect(addDataTransferItemsToUploadQueue).not.toHaveBeenCalled();
});
});

describe('render()', () => {
describe('enableModernizedUploads', () => {
test('should render legacy UploadsManager when enableModernizedUploads is false and useUploadsManager is true', () => {
Expand All @@ -985,6 +1135,13 @@ describe('elements/content-uploader/ContentUploader', () => {
expect(wrapper.find(UploadsManagerBP)).toHaveLength(1);
expect(wrapper.find(UploadsManager)).toHaveLength(0);
expect(wrapper.find(DroppableContent)).toHaveLength(0);
expect(wrapper.find(ModernizedUploadsManagerDropZone)).toHaveLength(1);
});

test('should disable dropping on the modernized panel when modernized drop props are omitted', () => {
const wrapper = getWrapper({ enableModernizedUploads: true });

expect(wrapper.find(ModernizedUploadsManagerDropZone).prop('isDropEnabled')).toBe(false);
});

test('should render modernized UploadsManagerBP even when useUploadsManager is true', () => {
Expand Down
Loading