-
Notifications
You must be signed in to change notification settings - Fork 3.3k
/
Copy pathclick.js
312 lines (250 loc) · 9.93 KB
/
click.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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
const _ = require('lodash')
const Promise = require('bluebird')
const $Mouse = require('../../../cypress/mouse')
const $dom = require('../../../dom')
const $utils = require('../../../cypress/utils')
const $elements = require('../../../dom/elements')
const $selection = require('../../../dom/selection')
const $actionability = require('../../actionability')
module.exports = (Commands, Cypress, cy, state, config) => {
return Commands.addAll({ prevSubject: 'element' }, {
click (subject, positionOrX, y, options = {}) {
//# TODO handle pointer-events: none
//# http://caniuse.com/#feat=pointer-events
let position
let x
({ options, position, x, y } = $actionability.getPositionFromArguments(positionOrX, y, options))
_.defaults(options, {
$el: subject,
log: true,
verify: true,
force: false,
multiple: false,
position,
x,
y,
errorOnSelect: true,
waitForAnimations: config('waitForAnimations'),
animationDistanceThreshold: config('animationDistanceThreshold'),
})
//# throw if we're trying to click multiple elements
//# and we did not pass the multiple flag
if ((options.multiple === false) && (options.$el.length > 1)) {
$utils.throwErrByPath('click.multiple_elements', {
args: { num: options.$el.length },
})
}
const click = (el) => {
let deltaOptions
const $el = $dom.wrap(el)
const domEvents = {}
if (options.log) {
//# figure out the options which actually change the behavior of clicks
deltaOptions = $utils.filterOutOptions(options)
options._log = Cypress.log({
message: deltaOptions,
$el,
})
options._log.snapshot('before', { next: 'after' })
}
if (options.errorOnSelect && $el.is('select')) {
$utils.throwErrByPath('click.on_select_element', { onFail: options._log })
}
const afterMouseDown = function ($elToClick, coords) {
//# we need to use both of these
let consoleObj
const { fromWindow, fromViewport } = coords
//# handle mouse events removing DOM elements
//# https://www.w3.org/TR/uievents/#event-type-click (scroll up slightly)
if ($dom.isAttached($elToClick)) {
domEvents.mouseUp = $Mouse.mouseUp($elToClick, fromViewport)
}
if ($dom.isAttached($elToClick)) {
domEvents.click = $Mouse.click($elToClick, fromViewport)
}
if (options._log) {
consoleObj = options._log.invoke('consoleProps')
}
const consoleProps = function () {
consoleObj = _.defaults(consoleObj != null ? consoleObj : {}, {
'Applied To': $dom.getElements($el),
'Elements': $el.length,
'Coords': _.pick(fromWindow, 'x', 'y'), //# always absolute
'Options': deltaOptions,
})
if ($el.get(0) !== $elToClick.get(0)) {
//# only do this if $elToClick isnt $el
consoleObj['Actual Element Clicked'] = $dom.getElements($elToClick)
}
consoleObj.groups = function () {
const groups = [{
name: 'MouseDown',
items: _.pick(domEvents.mouseDown, 'preventedDefault', 'stoppedPropagation', 'modifiers'),
}]
if (domEvents.mouseUp) {
groups.push({
name: 'MouseUp',
items: _.pick(domEvents.mouseUp, 'preventedDefault', 'stoppedPropagation', 'modifiers'),
})
}
if (domEvents.click) {
groups.push({
name: 'Click',
items: _.pick(domEvents.click, 'preventedDefault', 'stoppedPropagation', 'modifiers'),
})
}
return groups
}
return consoleObj
}
return Promise
.delay($actionability.delay, 'click')
.then(() => {
//# display the red dot at these coords
if (options._log) {
//# because we snapshot and output a command per click
//# we need to manually snapshot + end them
options._log.set({ coords: fromWindow, consoleProps })
}
//# we need to split this up because we want the coordinates
//# to mutate our passed in options._log but we dont necessary
//# want to snapshot and end our command if we're a different
//# action like (cy.type) and we're borrowing the click action
if (options._log && options.log) {
return options._log.snapshot().end()
}
}).return(null)
}
//# we want to add this delay delta to our
//# runnables timeout so we prevent it from
//# timing out from multiple clicks
cy.timeout($actionability.delay, true, 'click')
//# must use callbacks here instead of .then()
//# because we're issuing the clicks synchonrously
//# once we establish the coordinates and the element
//# passes all of the internal checks
return $actionability.verify(cy, $el, options, {
onScroll ($el, type) {
return Cypress.action('cy:scrolled', $el, type)
},
onReady ($elToClick, coords) {
//# record the previously focused element before
//# issuing the mousedown because browsers may
//# automatically shift the focus to the element
//# without firing the focus event
const $previouslyFocused = cy.getFocused()
const ElNeedingForceFocus = cy.needsForceFocus()
if (ElNeedingForceFocus) {
cy.fireFocus(ElNeedingForceFocus)
}
el = $elToClick.get(0)
domEvents.mouseDown = $Mouse.mouseDown($elToClick, coords.fromViewport)
//# if mousedown was cancelled then or caused
//# our element to be removed from the DOM
//# just resolve after mouse down and dont
//# send a focus event
if (domEvents.mouseDown.preventedDefault || !$dom.isAttached($elToClick)) {
return afterMouseDown($elToClick, coords)
}
if ($elements.isInput(el) || $elements.isTextarea(el) || $elements.isContentEditable(el)) {
if (!$elements.isNeedSingleValueChangeInputElement(el)) {
$selection.moveSelectionToEnd(el)
}
}
//# retrieve the first focusable $el in our parent chain
const $elToFocus = $elements.getFirstFocusableEl($elToClick)
if (cy.needsFocus($elToFocus, $previouslyFocused)) {
cy.fireFocus($elToFocus.get(0))
//# if we are currently trying to focus
//# the body then calling body.focus()
//# is a noop, and it will not blur the
//# current element, which is all so wrong
if ($elToFocus.is('body')) {
const $focused = cy.getFocused()
//# if the current focused element hasn't changed
//# then blur manually
if ($elements.isSame($focused, $previouslyFocused)) {
cy.fireBlur($focused.get(0))
}
}
}
return afterMouseDown($elToClick, coords)
},
})
.catch((err) => {
//# snapshot only on click failure
err.onFail = function () {
if (options._log) {
return options._log.snapshot()
}
}
//# if we give up on waiting for actionability then
//# lets throw this error and log the command
return $utils.throwErr(err, { onFail: options._log })
})
}
return Promise
.each(options.$el.toArray(), click)
.then(() => {
let verifyAssertions
if (options.verify === false) {
return options.$el
}
return (verifyAssertions = () => {
return cy.verifyUpcomingAssertions(options.$el, options, {
onRetry: verifyAssertions,
})
})()
})
},
//# update dblclick to use the click
//# logic and just swap out the event details?
dblclick (subject, options = {}) {
_.defaults(options,
{ log: true })
const dblclicks = []
const dblclick = (el) => {
let log
const $el = $dom.wrap(el)
//# we want to add this delay delta to our
//# runnables timeout so we prevent it from
//# timing out from multiple clicks
cy.timeout($actionability.delay, true, 'dblclick')
if (options.log) {
log = Cypress.log({
$el,
consoleProps () {
return {
'Applied To': $dom.getElements($el),
'Elements': $el.length,
}
},
})
}
cy.ensureVisibility($el, log)
const p = cy.now('focus', $el, { $el, error: false, verify: false, log: false }).then(() => {
const event = new MouseEvent('dblclick', {
bubbles: true,
cancelable: true,
})
el.dispatchEvent(event)
// $el.cySimulate("dblclick")
// log.snapshot() if log
//# need to return null here to prevent
//# chaining thenable promises
return null
}).delay($actionability.delay, 'dblclick')
dblclicks.push(p)
return p
}
//# create a new promise and chain off of it using reduce to insert
//# the artificial delays. we have to set this as cancellable for it
//# to propogate since this is an "inner" promise
//# return our original subject when our promise resolves
return Promise
.resolve(subject.toArray())
.each(dblclick)
.return(subject)
},
})
}