-
Notifications
You must be signed in to change notification settings - Fork 459
/
events.js
141 lines (129 loc) 路 4.45 KB
/
events.js
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import {getConfig} from './config'
import {getWindowFromNode} from './helpers'
import {eventMap, eventAliasMap} from './event-map'
function fireEvent(element, event) {
return getConfig().eventWrapper(() => {
if (!event) {
throw new Error(
`Unable to fire an event - please provide an event object.`,
)
}
if (!element) {
throw new Error(
`Unable to fire a "${event.type}" event - please provide a DOM element.`,
)
}
return element.dispatchEvent(event)
})
}
function createEvent(
eventName,
node,
init,
{EventType = 'Event', defaultInit = {}} = {},
) {
if (!node) {
throw new Error(
`Unable to fire a "${eventName}" event - please provide a DOM element.`,
)
}
const eventInit = {...defaultInit, ...init}
const {target: {value, files, ...targetProperties} = {}} = eventInit
if (value !== undefined) {
setNativeValue(node, value)
}
if (files !== undefined) {
// input.files is a read-only property so this is not allowed:
// input.files = [file]
// so we have to use this workaround to set the property
Object.defineProperty(node, 'files', {
configurable: true,
enumerable: true,
writable: true,
value: files,
})
}
Object.assign(node, targetProperties)
const window = getWindowFromNode(node)
const EventConstructor = window[EventType] || window.Event
let event
/* istanbul ignore else */
if (typeof EventConstructor === 'function') {
event = new EventConstructor(eventName, eventInit)
} else {
// IE11 polyfill from https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent#Polyfill
event = window.document.createEvent(EventType)
const {bubbles, cancelable, detail, ...otherInit} = eventInit
event.initEvent(eventName, bubbles, cancelable, detail)
Object.keys(otherInit).forEach(eventKey => {
event[eventKey] = otherInit[eventKey]
})
}
// TransitionEvent is not supported in jsdom: https://github.com/jsdom/jsdom/issues/1781
if (EventType === 'TransitionEvent') {
const transitionEventProperties = [
'propertyName',
'elapsedTime',
'pseudoElement',
]
transitionEventProperties.forEach(property => {
const value = eventInit[property]
Object.defineProperty(event, property, {value})
})
}
// DataTransfer is not supported in jsdom: https://github.com/jsdom/jsdom/issues/1568
const dataTransferProperties = ['dataTransfer', 'clipboardData']
dataTransferProperties.forEach(dataTransferKey => {
const dataTransferValue = eventInit[dataTransferKey]
if (typeof dataTransferValue === 'object') {
/* istanbul ignore if */
if (typeof window.DataTransfer === 'function') {
Object.defineProperty(event, dataTransferKey, {
value: Object.getOwnPropertyNames(dataTransferValue).reduce(
(acc, propName) => {
Object.defineProperty(acc, propName, {
value: dataTransferValue[propName],
})
return acc
},
new window.DataTransfer(),
),
})
} else {
Object.defineProperty(event, dataTransferKey, {
value: dataTransferValue,
})
}
}
})
return event
}
Object.keys(eventMap).forEach(key => {
const {EventType, defaultInit} = eventMap[key]
const eventName = key.toLowerCase()
createEvent[key] = (node, init) =>
createEvent(eventName, node, init, {EventType, defaultInit})
fireEvent[key] = (node, init) => fireEvent(node, createEvent[key](node, init))
})
// function written after some investigation here:
// https://github.com/facebook/react/issues/10135#issuecomment-401496776
function setNativeValue(element, value) {
const {set: valueSetter} =
Object.getOwnPropertyDescriptor(element, 'value') || {}
const prototype = Object.getPrototypeOf(element)
const {set: prototypeValueSetter} =
Object.getOwnPropertyDescriptor(prototype, 'value') || {}
if (prototypeValueSetter && valueSetter !== prototypeValueSetter) {
prototypeValueSetter.call(element, value)
} /* istanbul ignore next (I don't want to bother) */ else if (valueSetter) {
valueSetter.call(element, value)
} else {
throw new Error('The given element does not have a value setter')
}
}
Object.keys(eventAliasMap).forEach(aliasKey => {
const key = eventAliasMap[aliasKey]
fireEvent[aliasKey] = (...args) => fireEvent[key](...args)
})
export {fireEvent, createEvent}
/* eslint complexity:["error", 9] */