diff --git a/src/file-upload/__tests__/file-upload-dropzone.test.tsx b/src/file-upload/__tests__/file-upload-dropzone.test.tsx index d47abf3c96..8a98763c80 100644 --- a/src/file-upload/__tests__/file-upload-dropzone.test.tsx +++ b/src/file-upload/__tests__/file-upload-dropzone.test.tsx @@ -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', @@ -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
{isDropzoneVisible ? 'visible' : 'hidden'}
; } +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(); 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(); + 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(); + 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(); fireEvent(document, createDragEvent('dragover')); @@ -50,7 +82,7 @@ describe('File upload dropzone', () => { }); test('Dropzone hides after a delay once global drop event is received', async () => { - render(); + render(); fireEvent(document, createDragEvent('dragover')); @@ -63,6 +95,12 @@ describe('File upload dropzone', () => { }); }); + test('dropzone is rendered in component', () => { + render(); + fireEvent(document, createDragEvent('dragover')); + expect(screen.getByText('Drag files to upload')).toBeDefined(); + }); + test('dropzone renders provided children', () => { render(Drop files here); expect(screen.findByText('Drop files here')).toBeDefined(); diff --git a/src/file-upload/dropzone/index.tsx b/src/file-upload/dropzone/index.tsx index c5919b8fa5..b49ada808a 100644 --- a/src/file-upload/dropzone/index.tsx +++ b/src/file-upload/dropzone/index.tsx @@ -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. @@ -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); } @@ -53,7 +59,7 @@ export function useDropzoneVisible() { document.removeEventListener('dragleave', onDocumentDragLeave); document.removeEventListener('drop', onDocumentDrop); }; - }, []); + }, [multiple]); return isDropzoneVisible; } @@ -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'; } }; diff --git a/src/file-upload/internal.tsx b/src/file-upload/internal.tsx index 5009bd742c..627b160412 100644 --- a/src/file-upload/internal.tsx +++ b/src/file-upload/internal.tsx @@ -80,7 +80,7 @@ function InternalFileUpload( } }; - const isDropzoneVisible = useDropzoneVisible(); + const isDropzoneVisible = useDropzoneVisible(multiple); const formFieldContext = useFormFieldContext(restProps); const ariaDescribedBy = joinStrings(