Skip to content

Commit

Permalink
try 2: fix type when previous selection in input in some cases (cypre…
Browse files Browse the repository at this point in the history
…ss-io#5854)

* fix type when previous selection in input

* cleanup

* cleanup more

* more cleanup

* more more cleanup final

* fix not firing input event in all cases
  • Loading branch information
kuceb authored and santoshyadavdev committed Dec 4, 2019
1 parent 45fa76e commit dbde3bd
Show file tree
Hide file tree
Showing 5 changed files with 531 additions and 188 deletions.
66 changes: 47 additions & 19 deletions packages/driver/src/cy/keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import * as $dom from '../dom'
import * as $document from '../dom/document'
import * as $elements from '../dom/elements'
import * as $selection from '../dom/selection'
import { HTMLTextLikeElement, HTMLTextLikeInputElement } from '../dom/types'
import $window from '../dom/window'

const debug = Debug('cypress:driver:keyboard')
Expand Down Expand Up @@ -36,7 +35,7 @@ interface KeyDetailsPartial extends Partial<KeyDetails> {
}

type SimulatedDefault = (
el: HTMLTextLikeElement,
el: HTMLElement,
key: KeyDetails,
options: any
) => void
Expand All @@ -63,6 +62,7 @@ const monthRe = /^\d{4}-(0\d|1[0-2])/
const weekRe = /^\d{4}-W(0[1-9]|[1-4]\d|5[0-3])/
const timeRe = /^([0-1]\d|2[0-3]):[0-5]\d(:[0-5]\d)?(\.[0-9]{1,3})?/
const dateTimeRe = /^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}/
const numberRe = /^-?(0|[1-9]\d*)(\.\d+)?(e-?(0|[1-9]\d*))?$/i
const charsBetweenCurlyBracesRe = /({.+?})/

const INITIAL_MODIFIERS = {
Expand Down Expand Up @@ -235,7 +235,7 @@ const shouldIgnoreEvent = <
return options[eventName] === false
}

const shouldUpdateValue = (el: HTMLElement, key: KeyDetails) => {
const shouldUpdateValue = (el: HTMLElement, key: KeyDetails, options) => {
if (!key.text) return false

const bounds = $selection.getSelectionBounds(el)
Expand All @@ -246,6 +246,29 @@ const shouldUpdateValue = (el: HTMLElement, key: KeyDetails) => {
return false
}

const isNumberInputType = $elements.isInput(el) && $elements.isInputType(el, 'number')

if (isNumberInputType) {
const needsValue = options.prevVal || ''
const needsValueLength = (needsValue && needsValue.length) || 0
const curVal = $elements.getNativeProp(el, 'value')
const bounds = $selection.getSelectionBounds(el)

// We need to see if the number we're about to type is a valid number, since setting a number input
// to an invalid number will not set the value and possibly throw a warning in the console
const potentialValue = $selection.insertSubstring(curVal + needsValue, key.text, [bounds.start + needsValueLength, bounds.end + needsValueLength])

if (!(numberRe.test(potentialValue))) {
debug('skipping inserting value since number input would be invalid', key.text, potentialValue)
options.prevVal = needsValue + key.text

return
}

key.text = (options.prevVal || '') + key.text
options.prevVal = null
}

if (noneSelected) {
const ml = $elements.getNativeProp(el, 'maxLength')

Expand Down Expand Up @@ -308,13 +331,15 @@ const validateTyping = (
let isWeek = false
let isDateTime = false

// use 'type' attribute instead of prop since browsers without
// support for attribute input type will have type prop of 'text'
if ($elements.isInput(el)) {
isDate = $dom.isInputType(el, 'date')
isTime = $dom.isInputType(el, 'time')
isMonth = $dom.isInputType(el, 'month')
isWeek = $dom.isInputType(el, 'week')
isDate = $elements.isAttrType(el, 'date')
isTime = $elements.isAttrType(el, 'time')
isMonth = $elements.isAttrType(el, 'month')
isWeek = $elements.isAttrType(el, 'week')
isDateTime =
$dom.isInputType(el, 'datetime') || $dom.isInputType(el, 'datetime-local')
$elements.isAttrType(el, 'datetime') || $elements.isAttrType(el, 'datetime-local')
}

const isFocusable = $elements.isFocusable($el)
Expand Down Expand Up @@ -452,11 +477,7 @@ function _getEndIndex (str, substr) {
// Simulated default actions for select few keys.
const simulatedDefaultKeyMap: { [key: string]: SimulatedDefault } = {
Enter: (el, key, options) => {
if ($elements.isContentEditable(el) || $elements.isTextarea(el)) {
key.events.input = $selection.replaceSelectionContents(el, '\n')
} else {
key.events.input = false
}
$selection.replaceSelectionContents(el, '\n')

options.onEnterPressed()
},
Expand Down Expand Up @@ -694,7 +715,7 @@ export class Keyboard {
debug('setting element value', valToSet, activeEl)

return $elements.setNativeProp(
activeEl as HTMLTextLikeInputElement,
activeEl as $elements.HTMLTextLikeInputElement,
'value',
valToSet
)
Expand Down Expand Up @@ -954,9 +975,11 @@ export class Keyboard {
const key = this.getModifierKeyDetails(_key)

if (!key.text) {
key.events.input = false
key.events.keypress = false
key.events.textInput = false
if (key.key !== 'Backspace' && key.key !== 'Delete') {
key.events.input = false
}
}

let elToType
Expand All @@ -976,9 +999,12 @@ export class Keyboard {

if (key.key === 'Enter' && $elements.isInput(elToType)) {
key.events.textInput = false
key.events.input = false
}

if ($elements.isReadOnlyInputOrTextarea(elToType)) {
if ($elements.isContentEditable(elToType)) {
key.events.input = false
} else if ($elements.isReadOnlyInputOrTextarea(elToType)) {
key.events.textInput = false
}

Expand Down Expand Up @@ -1035,7 +1061,7 @@ export class Keyboard {
this.fireSimulatedEvent(el, 'keyup', key, options)
}

getSimulatedDefaultForKey (key: KeyDetails) {
getSimulatedDefaultForKey (key: KeyDetails, options) {
debug('getSimulatedDefaultForKey', key.key)
if (key.simulatedDefault) return key.simulatedDefault

Expand All @@ -1044,7 +1070,7 @@ export class Keyboard {
}

return (el: HTMLElement) => {
if (!shouldUpdateValue(el, key)) {
if (!shouldUpdateValue(el, key, options)) {
debug('skip typing key', false)
key.events.input = false

Expand Down Expand Up @@ -1072,7 +1098,7 @@ export class Keyboard {

performSimulatedDefault (el: HTMLElement, key: KeyDetails, options: any) {
debug('performSimulatedDefault', key.key)
const simulatedDefault = this.getSimulatedDefaultForKey(key)
const simulatedDefault = this.getSimulatedDefaultForKey(key, options)

if ($elements.isTextLike(el)) {
if ($elements.isInput(el) || $elements.isTextarea(el)) {
Expand All @@ -1087,6 +1113,8 @@ export class Keyboard {
simulatedDefault(el, key, options)
}

debug({ key })

shouldIgnoreEvent('input', key.events) ||
this.fireSimulatedEvent(el, 'input', key, options)

Expand Down
3 changes: 3 additions & 0 deletions packages/driver/src/cypress/cy.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,9 @@ create = (specWindow, Cypress, Cookies, state, config, log) ->
contentWindow.SVGElement.prototype.blur = ->
focused.interceptBlur(@)

contentWindow.HTMLInputElement.prototype.select = ->
$selection.interceptSelect.call(@)

contentWindow.document.hasFocus = ->
focused.documentHasFocus.call(@)

Expand Down
7 changes: 7 additions & 0 deletions packages/driver/src/dom/elements.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,12 @@ const isInputType = function (el: JQueryOrEl<HTMLElement>, type) {
return elType === type
}

const isAttrType = function (el: HTMLInputElement, type: string) {
const elType = (el.getAttribute('type') || '').toLowerCase()

return elType === type
}

const isScrollOrAuto = (prop) => {
return prop === 'scroll' || prop === 'auto'
}
Expand Down Expand Up @@ -1079,6 +1085,7 @@ export {
isIframe,
isTextarea,
isInputType,
isAttrType,
isFocused,
isFocusedOrInFocused,
isInputAllowingImplicitFormSubmission,
Expand Down
Loading

0 comments on commit dbde3bd

Please sign in to comment.