-
Notifications
You must be signed in to change notification settings - Fork 240
/
upload.ts
100 lines (85 loc) 路 2.48 KB
/
upload.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
import {fireEvent, createEvent} from '@testing-library/dom'
import {click} from './click'
import {blur} from './blur'
import {focus} from './focus'
import {isDisabled, isInstanceOfElement} from './utils'
interface uploadInit {
clickInit?: MouseEventInit
changeInit?: Event
}
interface uploadOptions {
applyAccept?: boolean
}
function upload(
element: HTMLInputElement | HTMLLabelElement,
fileOrFiles: File | File[],
init?: uploadInit,
{applyAccept = false}: uploadOptions = {},
) {
if (isDisabled(element)) return
click(element, init?.clickInit)
const input = isInstanceOfElement(element, 'HTMLLabelElement')
? ((element as HTMLLabelElement).control as HTMLInputElement)
: (element as HTMLInputElement)
const files = (Array.isArray(fileOrFiles) ? fileOrFiles : [fileOrFiles])
.filter(file => !applyAccept || isAcceptableFile(file, input.accept))
.slice(0, input.multiple ? undefined : 1)
// blur fires when the file selector pops up
blur(element)
// focus fires when they make their selection
focus(element)
// do not fire an input event if the file selection does not change
if (
files.length === input.files?.length &&
files.every((f, i) => f === input.files?.item(i))
) {
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...
const inputFiles: FileList & Iterable<File> = {
...files,
length: files.length,
item: (index: number) => files[index],
[Symbol.iterator]() {
let i = 0
return {
next: () => ({
done: i >= files.length,
value: files[i++],
}),
}
},
}
fireEvent(
input,
createEvent('input', input, {
target: {files: inputFiles},
bubbles: true,
cancelable: false,
composed: true,
...init,
}),
)
fireEvent.change(input, {
target: {files: inputFiles},
...init,
})
}
function isAcceptableFile(file: File, accept: string) {
if (!accept) {
return true
}
const wildcards = ['audio/*', 'image/*', 'video/*']
return accept.split(',').some(acceptToken => {
if (acceptToken.startsWith('.')) {
// 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}