From a496da0bb8c102cc196930ff93c7e41efc39abbc Mon Sep 17 00:00:00 2001 From: Laura Beatris <48022589+LauraBeatris@users.noreply.github.com> Date: Mon, 22 Feb 2021 04:44:09 -0300 Subject: [PATCH 1/4] fix(upload): apply accept attribute (#558) Co-authored-by: Philipp Fritsche --- src/__tests__/upload.js | 52 +++++++++++++++++++++++++++++++++++++++++ src/upload.js | 30 ++++++++++++++++++++---- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/src/__tests__/upload.js b/src/__tests__/upload.js index f09a6ae2..fb6a2b7d 100644 --- a/src/__tests__/upload.js +++ b/src/__tests__/upload.js @@ -163,3 +163,55 @@ test('should call onChange/input bubbling up the event when a file is selected', expect(onInputInput).toHaveBeenCalledTimes(1) expect(onInputForm).toHaveBeenCalledTimes(1) }) + +test('should upload file with accepted format', () => { + const file = new File(['hello'], 'hello.png', {type: 'image/png'}) + const {element} = setup('') + + userEvent.upload(element, file) + + expect(element.files).toHaveLength(1) +}) + +test('should upload multiple files with accepted format', () => { + const files = [ + new File(['hello'], 'hello.png', {type: 'image/png'}), + new File(['there'], 'there.jpg', {type: 'audio/mp3'}), + new File(['there'], 'there.csv', {type: 'text/csv'}), + new File(['there'], 'there.jpg', {type: 'video/mp4'}), + ] + const {element} = setup(` + + `) + + userEvent.upload(element, files) + + expect(element.files).toHaveLength(3) +}) + +test('should not upload file with unaccepted format', () => { + const file = new File(['hello'], 'hello.png', {type: 'image/png'}) + const {element} = setup('') + + userEvent.upload(element, file) + + expect(element.files).toHaveLength(0) +}) + +test('should not upload multiple files with unaccepted formats', () => { + const files = [ + new File(['hello'], 'hello.txt', {type: 'text/plain'}), + new File(['there'], 'there.pdf', {type: 'application/pdf'}), + new File(['there'], 'there.png', {type: 'image/png'}), + ] + const {element} = setup(` + + `) + + userEvent.upload(element, files) + + expect(element.files).toHaveLength(0) +}) diff --git a/src/upload.js b/src/upload.js index a252b438..7a3b990a 100644 --- a/src/upload.js +++ b/src/upload.js @@ -10,16 +10,20 @@ function upload(element, fileOrFiles, init) { const input = element.tagName === 'LABEL' ? element.control : element - const files = (Array.isArray(fileOrFiles) - ? fileOrFiles - : [fileOrFiles] - ).slice(0, input.multiple ? undefined : 1) + const files = (Array.isArray(fileOrFiles) ? fileOrFiles : [fileOrFiles]) + .filter(file => isAcceptableFile(file, element.accept)) + .slice(0, input.multiple ? undefined : 1) // blur fires when the file selector pops up blur(element, init) // focus fires when they make their selection focus(element, init) + // treat empty array as if the user just closed the file upload dialog + if (files.length === 0) { + return + } + // the event fired in the browser isn't actually an "input" or "change" event // but a new Event with a type set to "input" and "change" // Kinda odd... @@ -46,4 +50,22 @@ function upload(element, fileOrFiles, init) { }) } +function isAcceptableFile(file, accept) { + if (!accept) { + return true + } + + const wildcards = ['audio/*', 'image/*', 'video/*'] + + return accept.split(',').some(acceptToken => { + if (acceptToken[0] === '.') { + // tokens starting with a dot represent a file extension + return file.name.endsWith(acceptToken) + } else if (wildcards.includes(acceptToken)) { + return file.type.startsWith(acceptToken.substr(0, acceptToken.length - 1)) + } + return file.type === acceptToken + }) +} + export {upload} From 5ee1ece7a5852eac1b9c9b3a2630195be9995ea8 Mon Sep 17 00:00:00 2001 From: Philipp Fritsche Date: Tue, 23 Feb 2021 13:28:28 +0100 Subject: [PATCH 2/4] fix(upload): make accept filter opt-in --- src/__tests__/upload.js | 66 ++++++++++++++++------------------------- src/upload.js | 4 +-- 2 files changed, 27 insertions(+), 43 deletions(-) diff --git a/src/__tests__/upload.js b/src/__tests__/upload.js index fb6a2b7d..d1cbbba7 100644 --- a/src/__tests__/upload.js +++ b/src/__tests__/upload.js @@ -164,54 +164,38 @@ test('should call onChange/input bubbling up the event when a file is selected', expect(onInputForm).toHaveBeenCalledTimes(1) }) -test('should upload file with accepted format', () => { - const file = new File(['hello'], 'hello.png', {type: 'image/png'}) - const {element} = setup('') - - userEvent.upload(element, file) - - expect(element.files).toHaveLength(1) -}) - -test('should upload multiple files with accepted format', () => { - const files = [ - new File(['hello'], 'hello.png', {type: 'image/png'}), - new File(['there'], 'there.jpg', {type: 'audio/mp3'}), - new File(['there'], 'there.csv', {type: 'text/csv'}), - new File(['there'], 'there.jpg', {type: 'video/mp4'}), - ] - const {element} = setup(` +test.each([ + [true, 'video/*,audio/*', 2], + [true, '.png', 1], + [true, 'text/csv', 1], + [true, '', 4], + [false, 'video/*', 4], +])( + 'should filter according to accept attribute applyAccept=%s, acceptAttribute=%s', + (applyAccept, acceptAttribute, expectedLength) => { + const files = [ + new File(['hello'], 'hello.png', {type: 'image/png'}), + new File(['there'], 'there.jpg', {type: 'audio/mp3'}), + new File(['there'], 'there.csv', {type: 'text/csv'}), + new File(['there'], 'there.jpg', {type: 'video/mp4'}), + ] + const {element} = setup(` `) - userEvent.upload(element, files) + userEvent.upload(element, files, undefined, {applyAccept}) - expect(element.files).toHaveLength(3) -}) + expect(element.files).toHaveLength(expectedLength) + }, +) -test('should not upload file with unaccepted format', () => { - const file = new File(['hello'], 'hello.png', {type: 'image/png'}) - const {element} = setup('') - - userEvent.upload(element, file) - - expect(element.files).toHaveLength(0) -}) - -test('should not upload multiple files with unaccepted formats', () => { - const files = [ - new File(['hello'], 'hello.txt', {type: 'text/plain'}), - new File(['there'], 'there.pdf', {type: 'application/pdf'}), - new File(['there'], 'there.png', {type: 'image/png'}), - ] - const {element} = setup(` - - `) - - userEvent.upload(element, files) +test('should not trigger input event for empty list', () => { + const {element, eventWasFired} = setup('') + userEvent.upload(element, []) expect(element.files).toHaveLength(0) + expect(eventWasFired('input')).toBe(false) }) diff --git a/src/upload.js b/src/upload.js index 7a3b990a..db7eda9d 100644 --- a/src/upload.js +++ b/src/upload.js @@ -3,7 +3,7 @@ import {click} from './click' import {blur} from './blur' import {focus} from './focus' -function upload(element, fileOrFiles, init) { +function upload(element, fileOrFiles, init, {applyAccept = false} = {}) { if (element.disabled) return click(element, init) @@ -11,7 +11,7 @@ function upload(element, fileOrFiles, init) { const input = element.tagName === 'LABEL' ? element.control : element const files = (Array.isArray(fileOrFiles) ? fileOrFiles : [fileOrFiles]) - .filter(file => isAcceptableFile(file, element.accept)) + .filter(file => !applyAccept || isAcceptableFile(file, element.accept)) .slice(0, input.multiple ? undefined : 1) // blur fires when the file selector pops up From 407bcebe9f96d6c7f267a5985a36e748f7c440f0 Mon Sep 17 00:00:00 2001 From: Philipp Fritsche Date: Tue, 23 Feb 2021 15:46:03 +0100 Subject: [PATCH 3/4] docs(upload): add options.applyAccept --- README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a5bff79f..85c49c02 100644 --- a/README.md +++ b/README.md @@ -271,12 +271,15 @@ test('types into the input', () => { }) ``` -### `upload(element, file, [{ clickInit, changeInit }])` +### `upload(element, file, [{ clickInit, changeInit }], [options])` Uploads file to an ``. For uploading multiple files use `` with `multiple` attribute and the second `upload` argument must be array then. Also it's possible to initialize click or change event with using third argument. +If `options.applyAccept` is set to `true` and there is an `accept` attribute on +the element, files that don't match will be discarded. + ```jsx import React from 'react' import {render, screen} from '@testing-library/react' From 46c172913be493a97ef276148ca5dc3b9e8ba10d Mon Sep 17 00:00:00 2001 From: Philipp Fritsche Date: Tue, 23 Feb 2021 15:50:54 +0100 Subject: [PATCH 4/4] fix(typing): add options.applyAccept --- typings/index.d.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/typings/index.d.ts b/typings/index.d.ts index 56211bfc..a603a026 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -26,6 +26,10 @@ export interface IClickOptions { clickCount?: number } +export interface IUploadOptions { + applyAccept?: boolean +} + declare const userEvent: { clear: (element: TargetElement) => void click: ( @@ -52,6 +56,7 @@ declare const userEvent: { element: TargetElement, files: FilesArgument, init?: UploadInitArgument, + options?: IUploadOptions, ) => void type: ( element: TargetElement,