Skip to content

Commit

Permalink
fix: Disallow dropping multiple files onto non-multiple FileUpload
Browse files Browse the repository at this point in the history
  • Loading branch information
gethinwebster committed Aug 9, 2023
1 parent fc92170 commit baec9c5
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 8 deletions.
63 changes: 59 additions & 4 deletions src/file-upload/__tests__/file-upload-dropzone.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,13 @@ const file2 = new File([new Blob(['Test content 2'], { type: 'text/plain' })], '
lastModified: 1590962400000,
});

function createDragEvent(type: string) {
function createDragEvent(type: string, files = [file1, file2]) {
const event = new CustomEvent(type, { bubbles: true });
(event as any).dataTransfer = { types: ['Files'], files: [file1, file2] };
(event as any).dataTransfer = {
types: ['Files'],
files: type === 'drop' ? files : [],
items: files,
};
return event;
}

Expand Down Expand Up @@ -69,7 +73,11 @@ describe('File upload dropzone', () => {
});

test('dropzone is hovered on dragover and un-hovered on dragleave', () => {
const { container } = render(<Dropzone onChange={jest.fn()}>Drop files here</Dropzone>);
const { container } = render(
<Dropzone onChange={jest.fn()} multiple={true}>
Drop files here
</Dropzone>
);
const dropzone = container.querySelector(`.${selectors.dropzone}`)!;

expect(dropzone).not.toHaveClass(selectors['dropzone-hovered']);
Expand All @@ -85,11 +93,58 @@ describe('File upload dropzone', () => {

test('dropzone fires onChange on drop', () => {
const onChange = jest.fn();
const { container } = render(<Dropzone onChange={onChange}>Drop files here</Dropzone>);
const { container } = render(
<Dropzone onChange={onChange} multiple={true}>
Drop files here
</Dropzone>
);
const dropzone = container.querySelector(`.${selectors.dropzone}`)!;

fireEvent(dropzone, createDragEvent('drop'));

expect(onChange).toHaveBeenCalledWith([file1, file2]);
});

describe('dragging on non-multiple zone', () => {
test('dropzone is not hovered on dragover of multiple files', () => {
const { container } = render(<Dropzone onChange={jest.fn()}>Drop files here</Dropzone>);
const dropzone = container.querySelector(`.${selectors.dropzone}`)!;

fireEvent(dropzone, createDragEvent('dragover'));

expect(dropzone).not.toHaveClass(selectors['dropzone-hovered']);
});
test('dropzone is hovered on dragover of single file', () => {
const { container } = render(<Dropzone onChange={jest.fn()}>Drop files here</Dropzone>);
const dropzone = container.querySelector(`.${selectors.dropzone}`)!;

fireEvent(dropzone, createDragEvent('dragover', [file1]));

expect(dropzone).toHaveClass(selectors['dropzone-hovered']);
});

test('dropzone fires no onChange on drop of multiple files', () => {
const onChange = jest.fn();
const { container } = render(<Dropzone onChange={onChange}>Drop files here</Dropzone>);
const dropzone = container.querySelector(`.${selectors.dropzone}`)!;

fireEvent(dropzone, createDragEvent('drop'));

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

test('dropzone fires onChange on drop of single file', () => {
const onChange = jest.fn();
const { container } = render(
<Dropzone onChange={onChange} multiple={true}>
Drop files here
</Dropzone>
);
const dropzone = container.querySelector(`.${selectors.dropzone}`)!;

fireEvent(dropzone, createDragEvent('drop', [file1]));

expect(onChange).toHaveBeenCalledWith([file1]);
});
});
});
14 changes: 11 additions & 3 deletions src/file-upload/dropzone/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import InternalIcon from '../../icon/internal';

interface DropzoneProps {
onChange: (files: File[]) => void;
multiple?: boolean;
children: React.ReactNode;
}

Expand Down Expand Up @@ -58,15 +59,19 @@ export function useDropzoneVisible() {
return isDropzoneVisible;
}

export function Dropzone({ onChange, children }: DropzoneProps) {
export function Dropzone({ onChange, multiple, children }: DropzoneProps) {
const [isDropzoneHovered, setDropzoneHovered] = useState(false);

const onDragOver = (event: React.DragEvent) => {
event.preventDefault();
setDropzoneHovered(true);

if (event.dataTransfer) {
event.dataTransfer.dropEffect = 'copy';
if (event.dataTransfer.items.length > 1 && !multiple) {
event.dataTransfer.dropEffect = 'none';
} else {
setDropzoneHovered(true);
event.dataTransfer.dropEffect = 'copy';
}
}
};

Expand All @@ -83,6 +88,9 @@ export function Dropzone({ onChange, children }: DropzoneProps) {
event.preventDefault();
setDropzoneHovered(false);

if (event.dataTransfer.files.length > 1 && !multiple) {
return;
}
onChange(Array.from(event.dataTransfer.files));
};

Expand Down
4 changes: 3 additions & 1 deletion src/file-upload/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,9 @@ function InternalFileUpload(
>
<InternalBox>
{isDropzoneVisible ? (
<Dropzone onChange={handleFilesChange}>{i18nStrings.dropzoneText(multiple)}</Dropzone>
<Dropzone onChange={handleFilesChange} multiple={multiple}>

Check warning on line 104 in src/file-upload/internal.tsx

View check run for this annotation

Codecov / codecov/patch

src/file-upload/internal.tsx#L104

Added line #L104 was not covered by tests
{i18nStrings.dropzoneText(multiple)}
</Dropzone>
) : (
<FileInput
ref={ref}
Expand Down

0 comments on commit baec9c5

Please sign in to comment.