Skip to content

Commit

Permalink
Fix issues with cy.type (#2016)
Browse files Browse the repository at this point in the history
this grew to a large PR fixing many cy.type issues.

fix #365
fix #420
fix #586 
fix #593 
fix #596 
fix #610 
fix #651
fix #940
fix #1002 
fix #1108
fix #1171
fix #1209 
fix #1234 
fix #1366
fix #1381 
fix #1684 
fix #1686
fix #1926 
fix #2056
fix #2096 
fix #2110 
fix #2173
fix #2187
  • Loading branch information
kuceb authored and brian-mann committed Jul 23, 2018
1 parent 5231e79 commit 9f28aea
Show file tree
Hide file tree
Showing 17 changed files with 1,626 additions and 1,140 deletions.
4 changes: 3 additions & 1 deletion packages/driver/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"scripts": {
"start": "../coffee/node_modules/.bin/coffee test/support/server.coffee",
"cypress:open": "node ../../cli/bin/cypress open --dev --project ./test",
"cypress:run": "node ../../scripts/run-cypress-tests.js --browser chrome --dir test",
"cypress:run": "node ../../scripts/run-cypress-tests.js --dir test",
"clean-deps": "rm -rf node_modules"
},
"files": [
Expand Down Expand Up @@ -53,9 +53,11 @@
"parse-domain": "2.0.0",
"setimmediate": "^1.0.2",
"sinon": "3.2.0",
"text-mask-addons": "^3.7.2",
"underscore": "^1.8.3",
"underscore.string": "3.3.4",
"url-parse": "^1.1.7",
"vanilla-text-mask": "^5.1.1",
"wait-on": "^2.0.2",
"zone.js": "^0.8.18"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/driver/src/cy/actionability.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ verify = (cy, $el, options, callbacks) ->
## then do not perform these additional ensures...
if (force isnt true) and (options.waitForAnimations isnt false)
## store the coords that were absolute
## from the window or from the viewport for sticky elements
## from the window or from the viewport for sticky elements
## (see https://github.com/cypress-io/cypress/pull/1478)

sticky = !!getStickyEl($el)
Expand Down
21 changes: 12 additions & 9 deletions packages/driver/src/cy/commands/actions/check.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Promise = require("bluebird")

$dom = require("../../../dom")
$utils = require("../../../cypress/utils")
$elements = require("../../../dom/elements")

checkOrUncheck = (type, subject, values = [], options = {}) ->
## we're not handling conversion of values to strings
Expand Down Expand Up @@ -46,13 +47,23 @@ checkOrUncheck = (type, subject, values = [], options = {}) ->
## in the values array?
## or values array is empty
elHasMatchingValue = ($el) ->
values.length is 0 or $el.val() in values
value = $elements.getNativeProp($el.get(0), "value")
values.length is 0 or value in values

## blow up if any member of the subject
## isnt a checkbox or radio
checkOrUncheckEl = (el, index) =>
$el = $(el)

if not isAcceptableElement($el)
node = $dom.stringify($el)
word = $utils.plural(options.$el, "contains", "is")
phrase = if type is "check" then " and :radio" else ""
$utils.throwErrByPath "check_uncheck.invalid_element", {
onFail: options._log
args: { node, word, phrase, cmd: type }
}

isElActionable = elHasMatchingValue($el)

if isElActionable
Expand All @@ -78,14 +89,6 @@ checkOrUncheck = (type, subject, values = [], options = {}) ->

options._log.snapshot("before", {next: "after"})

if not isAcceptableElement($el)
node = $dom.stringify($el)
word = $utils.plural(options.$el, "contains", "is")
phrase = if type is "check" then " and :radio" else ""
$utils.throwErrByPath "check_uncheck.invalid_element", {
onFail: options._log
args: { node, word, phrase, cmd: type }
}

## if the checkbox was already checked
## then notify the user of this note
Expand Down
11 changes: 9 additions & 2 deletions packages/driver/src/cy/commands/actions/click.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ $Mouse = require("../../../cypress/mouse")

$dom = require("../../../dom")
$utils = require("../../../cypress/utils")
$elements = require("../../../dom/elements")
$selection = require("../../../dom/selection")
$actionability = require("../../actionability")

module.exports = (Commands, Cypress, cy, state, config) ->
Expand Down Expand Up @@ -183,7 +185,9 @@ module.exports = (Commands, Cypress, cy, state, config) ->
onReady: ($elToClick, coords) ->
## TODO: get focused through a callback here
$focused = cy.getFocused()


el = $elToClick.get(0)

## record the previously focused element before
## issuing the mousedown because browsers may
## automatically shift the focus to the element
Expand All @@ -199,11 +203,14 @@ module.exports = (Commands, Cypress, cy, state, config) ->
if domEvents.mouseDown.preventedDefault or not $dom.isAttached($elToClick)
afterMouseDown($elToClick, coords)
else
if $elements.isInput(el) or $elements.isTextarea(el) or $elements.isContentEditable(el)
if !$elements.isNeedSingleValueChangeInputElement(el)
$selection.moveSelectionToEnd(el)

## retrieve the first focusable $el in our parent chain
$elToFocus = getFirstFocusableEl($elToClick)

$focused = cy.getFocused()

if shouldFireFocusEvent($focused, $elToFocus)
## if our mousedown went through and
## we are focusing a different element
Expand Down
6 changes: 4 additions & 2 deletions packages/driver/src/cy/commands/actions/select.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ Promise = require("bluebird")

$dom = require("../../../dom")
$utils = require("../../../cypress/utils")
$elements = require('../../../dom/elements')

newLineRe = /\n/g

Expand Down Expand Up @@ -70,7 +71,7 @@ module.exports = (Commands, Cypress, cy, state, config) ->
optionsObjects = options.$el.find("option").map((index, el) ->
## push the value in values array if its
## found within the valueOrText
value = el.value
value = $elements.getNativeProp(el, "value")
optEl = $(el)

if value in valueOrText
Expand Down Expand Up @@ -101,7 +102,8 @@ module.exports = (Commands, Cypress, cy, state, config) ->
_.each optionsObjects, (obj, index) ->
if obj.text in valueOrText
optionEls.push obj.$el
values.push(obj.value)
objValue = obj.value
values.push(objValue)

## if we didnt set multiple to true and
## we have more than 1 option to set then blow up
Expand Down
125 changes: 70 additions & 55 deletions packages/driver/src/cy/commands/actions/type.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Promise = require("bluebird")
moment = require("moment")

$dom = require("../../../dom")
$elements = require("../../../dom/elements")
$selection = require("../../../dom/selection")
$Keyboard = require("../../../cypress/keyboard")
$utils = require("../../../cypress/utils")
$actionability = require("../../actionability")
Expand All @@ -21,6 +23,7 @@ module.exports = (Commands, Cypress, cy, state, config) ->

Commands.addAll({ prevSubject: "element" }, {
type: (subject, chars, options = {}) ->
options = _.clone(options)
## allow the el we're typing into to be
## changed by options -- used by cy.clear()
_.defaults(options, {
Expand Down Expand Up @@ -61,10 +64,10 @@ module.exports = (Commands, Cypress, cy, state, config) ->
memo
, {}

options._log = Cypress.log
options._log = Cypress.log {
message: [chars, deltaOptions]
$el: options.$el
consoleProps: ->
consoleProps: -> {
"Typed": chars
"Applied To": $dom.getElements(options.$el)
"Options": deltaOptions
Expand All @@ -74,6 +77,8 @@ module.exports = (Commands, Cypress, cy, state, config) ->
data: getTableData()
columns: ["typed", "which", "keydown", "keypress", "textInput", "input", "keyup", "change", "modifiers"]
}
}
}

options._log.snapshot("before", {next: "after"})

Expand Down Expand Up @@ -210,45 +215,42 @@ module.exports = (Commands, Cypress, cy, state, config) ->
## consider changing type to a Promise and juggle logging
cy.now("submit", form, {log: false, $el: form})

dispatchChangeEvent = (id) ->
dispatchChangeEvent = (el, id) ->
change = document.createEvent("HTMLEvents")
change.initEvent("change", true, false)

dispatched = options.$el.get(0).dispatchEvent(change)
dispatched = el.dispatchEvent(change)

if id and updateTable
updateTable(id, null, "change", null, dispatched)

return dispatched

needSingleValueChange = ->
isDate or
isMonth or
isWeek or
isTime or
($dom.isType(options.$el, "number") and _.includes(options.chars, "."))
return $elements.isNeedSingleValueChangeInputElement(options.$el.get(0))

## see comment in updateValue below
typed = ""

isContentEditable = $elements.isContentEditable(options.$el.get(0))
isTextarea = $elements.isTextarea(options.$el.get(0))

$Keyboard.type({
$el: options.$el
chars: options.chars
delay: options.delay
release: options.release
window: win

updateValue: (rng, key) ->
updateValue: (el, key) ->
## in these cases, the value must only be set after all
## the characters are input because attemping to set
## a partial/invalid value results in the value being
## set to an empty string
if needSingleValueChange()
## in these cases, the value must only be set after all
## the characters are input because attemping to set
## a partial/invalid value results in the value being
## set to an empty string
typed += key
if typed is options.chars
options.$el.val(options.chars)
$elements.setNativeProp(el, "value", options.chars)
else
rng.text(key, "end")
$selection.replaceSelectionContents(el, key)

onBeforeType: (totalKeys) ->
## for the total number of keys we're about to
Expand Down Expand Up @@ -277,25 +279,35 @@ module.exports = (Commands, Cypress, cy, state, config) ->
## fires only when the 'value'
## of input/text/contenteditable
## changes
onTypeChange: ->
## never fire any change events for contenteditable
return if options.$el.is("[contenteditable]")
onValueChange: (originalText, el) ->
## contenteditable should never be called here.
## only input's and textareas can have change events
if changeEvent = state("changeEvent")
if !changeEvent(null, true)
state("changeEvent", null)
return

state "changeEvent", (id, readOnly) ->
changed = $elements.getNativeProp(el, 'value') isnt originalText

if !readOnly
if changed
dispatchChangeEvent(el, id)
state "changeEvent", null

state "changeEvent", ->
dispatchChangeEvent()
state "changeEvent", null
return changed

onEnterPressed: (changed, id) ->
onEnterPressed: (id) ->
## dont dispatch change events or handle
## submit event if we've pressed enter into
## a textarea or contenteditable
return if options.$el.is("textarea,[contenteditable]")
return if isTextarea || isContentEditable

## if our value has changed since our
## element was activated we need to
## fire a change event immediately
if changed
dispatchChangeEvent(id)
if changeEvent = state("changeEvent")
changeEvent(id)

## handle submit event handler here
simulateSubmitHandler()
Expand All @@ -315,32 +327,33 @@ module.exports = (Commands, Cypress, cy, state, config) ->
## if it's the body, don't need to worry about focus
return type() if isBody

cy.now("focused", {log: false, verify: false})
.then ($focused) ->
$actionability.verify(cy, options.$el, options, {
onScroll: ($el, type) ->
Cypress.action("cy:scrolled", $el, type)

onReady: ($elToClick) ->
## if we dont have a focused element
## or if we do and its not ourselves
## then issue the click
if not $focused or ($focused and $focused.get(0) isnt options.$el.get(0))
## click the element first to simulate focus
## and typical user behavior in case the window
## is out of focus
cy.now("click", $elToClick, {
$el: $elToClick
log: false
verify: false
_log: options._log
force: true ## force the click, avoid waiting
timeout: options.timeout
interval: options.interval
}).then(type)
else
## don't click, just type
$actionability.verify(cy, options.$el, options, {
onScroll: ($el, type) ->
Cypress.action("cy:scrolled", $el, type)

onReady: ($elToClick) ->
$focused = cy.getFocused()

## if we dont have a focused element
## or if we do and its not ourselves
## then issue the click
if not $focused or ($focused and $focused.get(0) isnt options.$el.get(0))
## click the element first to simulate focus
## and typical user behavior in case the window
## is out of focus
cy.now("click", $elToClick, {
$el: $elToClick
log: false
verify: false
_log: options._log
force: true ## force the click, avoid waiting
timeout: options.timeout
interval: options.interval
})
.then ->
type()
else
type()
})

handleFocused()
Expand Down Expand Up @@ -377,13 +390,15 @@ module.exports = (Commands, Cypress, cy, state, config) ->
## figure out the options which actually change the behavior of clicks
deltaOptions = $utils.filterOutOptions(options)

options._log = Cypress.log
options._log = Cypress.log {
message: deltaOptions
$el: $el
consoleProps: ->
consoleProps: () -> {
"Applied To": $dom.getElements($el)
"Elements": $el.length
"Options": deltaOptions
}
}

node = $dom.stringify($el)

Expand Down
2 changes: 0 additions & 2 deletions packages/driver/src/cypress.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ moment = require("moment")
Promise = require("bluebird")
sinon = require("sinon")
lolex = require("lolex")
bililiteRange = require("../vendor/bililiteRange")

$dom = require("./dom")
$errorMessages = require("./cypress/error_messages")
Expand Down Expand Up @@ -477,7 +476,6 @@ class $Cypress
minimatch: minimatch
sinon: sinon
lolex: lolex
bililiteRange: bililiteRange

_.extend $Cypress.prototype.$, _.pick($, "Event", "Deferred", "ajax", "get", "getJSON", "getScript", "post", "when")

Expand Down
Loading

0 comments on commit 9f28aea

Please sign in to comment.