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 authored Aug 10, 2023
1 parent d5ec667 commit e657a92
Show file tree
Hide file tree
Showing 3 changed files with 56 additions and 12 deletions.
52 changes: 45 additions & 7 deletions src/file-upload/__tests__/file-upload-dropzone.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import React from 'react';
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
import { Dropzone, useDropzoneVisible } from '../../../lib/components/file-upload/dropzone';
import selectors from '../../../lib/components/file-upload/dropzone/styles.selectors.js';
import FileUpload, { FileUploadProps } from '../../../lib/components/file-upload';

const file1 = new File([new Blob(['Test content 1'], { type: 'text/plain' })], 'test-file-1.txt', {
type: 'text/plain',
Expand All @@ -14,29 +15,60 @@ 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.map(() => ({ kind: 'file' })),
};
return event;
}

function TestDropzoneVisible() {
const isDropzoneVisible = useDropzoneVisible();
function TestDropzoneVisible({ multiple = false }) {
const isDropzoneVisible = useDropzoneVisible(multiple);
return <div>{isDropzoneVisible ? 'visible' : 'hidden'}</div>;
}

const i18nStrings: FileUploadProps.I18nStrings = {
uploadButtonText: multiple => (multiple ? 'Choose files' : 'Choose file'),
dropzoneText: multiple => (multiple ? 'Drag files to upload' : 'Drag file to upload'),
removeFileAriaLabel: fileIndex => `Remove file ${fileIndex + 1}`,
errorIconAriaLabel: 'Error',
limitShowFewer: 'Show fewer files',
limitShowMore: 'Show more files',
};

describe('File upload dropzone', () => {
test('Dropzone becomes visible once global dragover event is received', () => {
render(<TestDropzoneVisible />);
expect(screen.getByText('hidden')).toBeDefined();

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

expect(screen.getByText('visible')).toBeDefined();
});

test('Dropzone hides after a delay once global dragleave event is received', async () => {
test('Dropzone does not show if multiple files dragged into non-multiple zone', () => {
render(<TestDropzoneVisible />);
expect(screen.getByText('hidden')).toBeDefined();

fireEvent(document, createDragEvent('dragover', [file1, file2]));

expect(screen.getByText('hidden')).toBeDefined();
});

test('Dropzone shows if multiple files dragged into multiple zone', () => {
render(<TestDropzoneVisible multiple={true} />);
expect(screen.getByText('hidden')).toBeDefined();

fireEvent(document, createDragEvent('dragover', [file1, file2]));

expect(screen.getByText('visible')).toBeDefined();
});

test('Dropzone hides after a delay once global dragleave event is received', async () => {
render(<TestDropzoneVisible multiple={true} />);

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

Expand All @@ -50,7 +82,7 @@ describe('File upload dropzone', () => {
});

test('Dropzone hides after a delay once global drop event is received', async () => {
render(<TestDropzoneVisible />);
render(<TestDropzoneVisible multiple={true} />);

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

Expand All @@ -63,6 +95,12 @@ describe('File upload dropzone', () => {
});
});

test('dropzone is rendered in component', () => {
render(<FileUpload value={[]} i18nStrings={i18nStrings} multiple={true} />);
fireEvent(document, createDragEvent('dragover'));
expect(screen.getByText('Drag files to upload')).toBeDefined();
});

test('dropzone renders provided children', () => {
render(<Dropzone onChange={jest.fn()}>Drop files here</Dropzone>);
expect(screen.findByText('Drop files here')).toBeDefined();
Expand Down
14 changes: 10 additions & 4 deletions src/file-upload/dropzone/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface DropzoneProps {
children: React.ReactNode;
}

export function useDropzoneVisible() {
export function useDropzoneVisible(multiple: boolean) {
const [isDropzoneVisible, setDropzoneVisible] = useState(false);

// Registering global drag events listeners.
Expand All @@ -23,7 +23,13 @@ export function useDropzoneVisible() {
const onDocumentDragOver = (event: DragEvent) => {
event.preventDefault();

if (event.dataTransfer?.types?.indexOf('Files') !== -1) {
let files = 0;
for (let item = 0; item < (event.dataTransfer?.items.length || 0); item++) {
if (event.dataTransfer?.items[item].kind === 'file') {
files++;
}
}
if (files > 0 && (multiple || files === 1)) {
setDropzoneVisible(true);
dragTimer && clearTimeout(dragTimer);
}
Expand Down Expand Up @@ -53,7 +59,7 @@ export function useDropzoneVisible() {
document.removeEventListener('dragleave', onDocumentDragLeave);
document.removeEventListener('drop', onDocumentDrop);
};
}, []);
}, [multiple]);

return isDropzoneVisible;
}
Expand All @@ -63,9 +69,9 @@ export function Dropzone({ onChange, children }: DropzoneProps) {

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

if (event.dataTransfer) {
setDropzoneHovered(true);
event.dataTransfer.dropEffect = 'copy';
}
};
Expand Down
2 changes: 1 addition & 1 deletion src/file-upload/internal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function InternalFileUpload(
}
};

const isDropzoneVisible = useDropzoneVisible();
const isDropzoneVisible = useDropzoneVisible(multiple);

const formFieldContext = useFormFieldContext(restProps);
const ariaDescribedBy = joinStrings(
Expand Down

0 comments on commit e657a92

Please sign in to comment.