diff --git a/README.md b/README.md
index c723777c..d6d552f9 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'
diff --git a/src/__tests__/upload.js b/src/__tests__/upload.js
index f09a6ae2..d1cbbba7 100644
--- a/src/__tests__/upload.js
+++ b/src/__tests__/upload.js
@@ -163,3 +163,39 @@ test('should call onChange/input bubbling up the event when a file is selected',
expect(onInputInput).toHaveBeenCalledTimes(1)
expect(onInputForm).toHaveBeenCalledTimes(1)
})
+
+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, undefined, {applyAccept})
+
+ expect(element.files).toHaveLength(expectedLength)
+ },
+)
+
+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 a252b438..db7eda9d 100644
--- a/src/upload.js
+++ b/src/upload.js
@@ -3,23 +3,27 @@ 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)
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 => !applyAccept || 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}
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,