iframe:
diff --git a/packages/driver/test/cypress/fixtures/issue-2956.html b/packages/driver/test/cypress/fixtures/issue-2956.html
new file mode 100644
index 000000000000..4536d323874b
--- /dev/null
+++ b/packages/driver/test/cypress/fixtures/issue-2956.html
@@ -0,0 +1,134 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/driver/test/cypress/integration/commands/actions/click_spec.js b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
index 31a4b692d438..028494e1dc80 100644
--- a/packages/driver/test/cypress/integration/commands/actions/click_spec.js
+++ b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
@@ -1,11 +1,31 @@
const $ = Cypress.$.bind(Cypress)
const { _ } = Cypress
const { Promise } = Cypress
+const chaiSubset = require('chai-subset')
+
+chai.use(chaiSubset)
const fail = function (str) {
throw new Error(str)
}
+const mouseClickEvents = ['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click']
+const mouseHoverEvents = [
+ 'pointerout',
+ 'pointerleave',
+ 'pointerover',
+ 'pointerenter',
+ 'mouseout',
+ 'mouseleave',
+ 'mouseover',
+ 'mouseenter',
+ 'pointermove',
+ 'mousemove',
+]
+const focusEvents = ['focus', 'focusin']
+
+const allMouseEvents = [...mouseClickEvents, ...mouseHoverEvents, ...focusEvents]
+
describe('src/cy/commands/actions/click', () => {
before(() => {
cy
@@ -18,7 +38,10 @@ describe('src/cy/commands/actions/click', () => {
beforeEach(function () {
const doc = cy.state('document')
- return $(doc.body).empty().html(this.body)
+ $(doc.body).empty().html(this.body)
+ // scroll back to top of page before every test
+ // since this is a side-effect
+ doc.documentElement.scrollTop = 0
})
context('#click', () => {
@@ -144,8 +167,23 @@ describe('src/cy/commands/actions/click', () => {
const $btn = cy.$$('#button')
_.each('mousedown mouseup click'.split(' '), (event) => {
- return $btn.get(0).addEventListener(event, () => {
- return events.push(event)
+ $btn.get(0).addEventListener(event, () => {
+ events.push(event)
+ })
+ })
+
+ cy.get('#button').click().then(() => {
+ expect(events).to.deep.eq(['mousedown', 'mouseup', 'click'])
+ })
+ })
+
+ it('sends pointer and mouse events in order', () => {
+ const events = []
+ const $btn = cy.$$('#button')
+
+ _.each('pointerdown mousedown pointerup mouseup click'.split(' '), (event) => {
+ $btn.get(0).addEventListener(event, () => {
+ events.push(event)
})
})
@@ -229,6 +267,7 @@ describe('src/cy/commands/actions/click', () => {
.then(() => {
expect(onError).calledOnce
})
+
})
})
@@ -267,25 +306,37 @@ describe('src/cy/commands/actions/click', () => {
})
it('will send all events even mousedown is defaultPrevented', () => {
- const events = []
const $btn = cy.$$('#button')
$btn.get(0).addEventListener('mousedown', (e) => {
e.preventDefault()
-
expect(e.defaultPrevented).to.be.true
})
- _.each('mouseup click'.split(' '), (event) => {
- return $btn.get(0).addEventListener(event, () => {
- return events.push(event)
- })
- })
+ attachMouseClickListeners({ $btn })
- cy.get('#button').click().then(() => {
- expect(events).to.deep.eq(['mouseup', 'click'])
+ cy.get('#button').click().should('not.have.focus')
+
+ cy.getAll('$btn', 'pointerdown mousedown pointerup mouseup click').each(shouldBeCalled)
+ })
+
+ it('will not send mouseEvents/focus if pointerdown is defaultPrevented', () => {
+ const $btn = cy.$$('#button')
+
+ $btn.get(0).addEventListener('pointerdown', (e) => {
+ e.preventDefault()
+
+ expect(e.defaultPrevented).to.be.true
})
+
+ attachMouseClickListeners({ $btn })
+
+ cy.get('#button').click().should('not.have.focus')
+
+ cy.getAll('$btn', 'pointerdown pointerup click').each(shouldBeCalledOnce)
+ cy.getAll('$btn', 'mousedown mouseup').each(shouldNotBeCalled)
+
})
it('sends a click event', (done) => {
@@ -304,14 +355,15 @@ describe('src/cy/commands/actions/click', () => {
})
})
- it('causes focusable elements to receive focus', (done) => {
- const $text = cy.$$(':text:first')
+ it('causes focusable elements to receive focus', () => {
- $text.focus(() => {
- done()
- })
+ const el = cy.$$(':text:first')
+
+ attachFocusListeners({ el })
- cy.get(':text:first').click()
+ cy.get(':text:first').click().should('have.focus')
+
+ cy.getAll('el', 'focus focusin').each(shouldBeCalledOnce)
})
it('does not fire a focus, mouseup, or click event when element has been removed on mousedown', () => {
@@ -319,26 +371,69 @@ describe('src/cy/commands/actions/click', () => {
$btn.on('mousedown', function () {
// synchronously remove this button
- return $(this).remove()
+ $(this).remove()
})
$btn.on('focus', () => {
- return fail('should not have gotten focus')
+ fail('should not have gotten focus')
})
$btn.on('focusin', () => {
- return fail('should not have gotten focusin')
+ fail('should not have gotten focusin')
})
$btn.on('mouseup', () => {
- return fail('should not have gotten mouseup')
+ fail('should not have gotten mouseup')
})
$btn.on('click', () => {
- return fail('should not have gotten click')
+ fail('should not have gotten click')
+ })
+
+ cy.contains('button').click()
+ })
+
+ it('events when element removed on pointerdown', () => {
+ const btn = cy.$$('button:first')
+ const div = cy.$$('div#tabindex')
+
+ attachFocusListeners({ btn })
+ attachMouseClickListeners({ btn, div })
+ attachMouseHoverListeners({ div })
+
+ btn.on('pointerdown', () => {
+ // synchronously remove this button
+
+ btn.remove()
+ })
+
+ // return
+ cy.contains('button').click()
+
+ cy.getAll('btn', 'pointerdown').each(shouldBeCalled)
+ cy.getAll('btn', 'mousedown mouseup').each(shouldNotBeCalled)
+ cy.getAll('div', 'pointerover pointerenter mouseover mouseenter pointerup mouseup').each(shouldBeCalled)
+ })
+
+ it('events when element removed on pointerover', () => {
+ const btn = cy.$$('button:first')
+ const div = cy.$$('div#tabindex')
+
+ // attachFocusListeners({ btn })
+ attachMouseClickListeners({ btn, div })
+ attachMouseHoverListeners({ btn, div })
+
+ btn.on('pointerover', () => {
+ // synchronously remove this button
+
+ btn.remove()
})
cy.contains('button').click()
+
+ cy.getAll('btn', 'pointerover pointerenter').each(shouldBeCalled)
+ cy.getAll('btn', 'pointerdown mousedown mouseover mouseenter').each(shouldNotBeCalled)
+ cy.getAll('div', 'pointerover pointerenter pointerdown mousedown pointerup mouseup click').each(shouldBeCalled)
})
it('does not fire a click when element has been removed on mouseup', () => {
@@ -346,19 +441,54 @@ describe('src/cy/commands/actions/click', () => {
$btn.on('mouseup', function () {
// synchronously remove this button
- return $(this).remove()
+ $(this).remove()
})
$btn.on('click', () => {
- return fail('should not have gotten click')
+ fail('should not have gotten click')
})
cy.contains('button').click()
})
- it('silences errors on unfocusable elements', () => {
- cy.$$('div:first')
+ it('does not fire a click or mouseup when element has been removed on pointerup', () => {
+ const $btn = cy.$$('button:first')
+
+ $btn.on('pointerup', function () {
+ // synchronously remove this button
+ $(this).remove()
+ })
+
+ ;['mouseup', 'click'].forEach((eventName) => {
+ $btn.on(eventName, () => {
+ fail(`should not have gotten ${eventName}`)
+ })
+ })
+
+ cy.contains('button').click()
+ })
+
+ it('sends modifiers', () => {
+
+ const btn = cy.$$('button:first')
+
+ attachMouseClickListeners({ btn })
+
+ cy.get('input:first').type('{ctrl}{shift}', { release: false })
+ cy.get('button:first').click()
+
+ cy.getAll('btn', 'pointerdown mousedown pointerup mouseup click').each((stub) => {
+ expect(stub).to.be.calledWithMatch({
+ shiftKey: true,
+ ctrlKey: true,
+ metaKey: false,
+ altKey: false,
+ })
+
+ })
+ })
+ it('silences errors on unfocusable elements', () => {
cy.get('div:first').click({ force: true })
})
@@ -366,7 +496,7 @@ describe('src/cy/commands/actions/click', () => {
let blurred = false
cy.$$('input:first').blur(() => {
- return blurred = true
+ blurred = true
})
cy
@@ -423,7 +553,7 @@ describe('src/cy/commands/actions/click', () => {
})
const clicked = cy.spy(() => {
- return stop()
+ stop()
})
const $anchors = cy.$$('#sequential-clicks a')
@@ -439,7 +569,7 @@ describe('src/cy/commands/actions/click', () => {
// is called
const timeout = cy.spy(cy.timeout)
- return _.delay(() => {
+ _.delay(() => {
// and we should have stopped clicking after 3
expect(clicked.callCount).to.eq(3)
@@ -454,24 +584,23 @@ describe('src/cy/commands/actions/click', () => {
})
it('serially clicks a collection', () => {
- let clicks = 0
+ const throttled = cy.stub().as('clickcount')
// create a throttled click function
// which proves we are clicking serially
- const throttled = _.throttle(() => {
- return clicks += 1
- }
- , 5, { leading: false })
+ const handleClick = cy.stub()
+ .callsFake(_.throttle(throttled, 0, { leading: false }))
+ .as('handleClick')
- const anchors = cy.$$('#sequential-clicks a')
+ const $anchors = cy.$$('#sequential-clicks a')
- anchors.click(throttled)
+ $anchors.on('click', handleClick)
- // make sure we're clicking multiple anchors
- expect(anchors.length).to.be.gt(1)
+ // make sure we're clicking multiple $anchors
+ expect($anchors.length).to.be.gt(1)
- cy.get('#sequential-clicks a').click({ multiple: true }).then(($anchors) => {
- expect($anchors.length).to.eq(clicks)
+ cy.get('#sequential-clicks a').click({ multiple: true }).then(($els) => {
+ expect($els).to.have.length(throttled.callCount)
})
})
@@ -622,6 +751,28 @@ describe('src/cy/commands/actions/click', () => {
})
})
+ it('can click inside an iframe', () => {
+ cy.get('iframe')
+ .should(($iframe) => {
+ // wait for iframe to load
+ expect($iframe.contents().find('body').html()).ok
+ })
+ .then(($iframe) => {
+ // cypress does not wrap this as a DOM element (does not wrap in jquery)
+ // return cy.wrap($iframe[0].contentDocument.body)
+ return cy.wrap($iframe.contents().find('body'))
+ })
+
+ .within(() => {
+ cy.get('a#hashchange')
+ // .should($el => $el[0].click())
+ .click()
+ })
+ .then(($body) => {
+ expect($body[0].ownerDocument.defaultView.location.hash).eq('#hashchange')
+ })
+ })
+
describe('actionability', () => {
it('can click on inline elements that wrap lines', () => {
@@ -636,7 +787,7 @@ describe('src/cy/commands/actions/click', () => {
const scrolled = []
cy.on('scrolled', ($el, type) => {
- return scrolled.push(type)
+ scrolled.push(type)
})
cy
@@ -650,17 +801,14 @@ describe('src/cy/commands/actions/click', () => {
const scrolled = []
cy.on('scrolled', ($el, type) => {
- return scrolled.push(type)
+ scrolled.push(type)
})
cy.viewport(1000, 660)
const $body = cy.$$('body')
- $body.css({
- padding: 0,
- margin: 0,
- }).children().remove()
+ $body.children().remove()
const $wrap = $('
')
.attr('id', 'flex-wrap')
@@ -764,15 +912,15 @@ describe('src/cy/commands/actions/click', () => {
let clicked = false
cy.on('scrolled', ($el, type) => {
- return scrolled.push(type)
+ scrolled.push(type)
})
cy.on('command:retry', () => {
- return retried = true
+ retried = true
})
$btn.on('click', () => {
- return clicked = true
+ clicked = true
})
cy.get('#button-covered-in-span').click({ force: true }).then(() => {
@@ -801,7 +949,7 @@ describe('src/cy/commands/actions/click', () => {
let retried = false
cy.on('scrolled', ($el, type) => {
- return scrolled.push(type)
+ scrolled.push(type)
})
cy.on('command:retry', _.after(3, () => {
@@ -822,9 +970,12 @@ describe('src/cy/commands/actions/click', () => {
})
it('scrolls the window past a fixed position element when being covered', () => {
+ const spy = cy.spy().as('mousedown')
+
$('
')
.attr('id', 'button-covered-in-nav')
.appendTo(cy.$$('#fixed-nav-test'))
+ .mousedown(spy)
$('
').css({
position: 'fixed',
@@ -838,14 +989,17 @@ describe('src/cy/commands/actions/click', () => {
const scrolled = []
cy.on('scrolled', ($el, type) => {
- return scrolled.push(type)
+ scrolled.push(type)
})
// - element scrollIntoView
// - element scrollIntoView (retry animation coords)
// - window
- cy.get('#button-covered-in-nav').click().then(() => {
+ cy.get('#button-covered-in-nav').click()
+ .then(() => {
expect(scrolled).to.deep.eq(['element', 'element', 'window'])
+ expect(spy.args[0][0]).property('clientX').closeTo(60, 2)
+ expect(spy.args[0][0]).property('clientY').eq(68)
})
})
@@ -875,7 +1029,7 @@ describe('src/cy/commands/actions/click', () => {
const scrolled = []
cy.on('scrolled', ($el, type) => {
- return scrolled.push(type)
+ scrolled.push(type)
})
// - element scrollIntoView
@@ -932,7 +1086,7 @@ describe('src/cy/commands/actions/click', () => {
const scrolled = []
cy.on('scrolled', ($el, type) => {
- return scrolled.push(type)
+ scrolled.push(type)
})
// - element scrollIntoView
@@ -966,7 +1120,7 @@ describe('src/cy/commands/actions/click', () => {
let clicks = 0
$btn.on('click', () => {
- return clicks += 1
+ clicks += 1
})
cy.on('command:retry', _.after(3, () => {
@@ -985,7 +1139,7 @@ describe('src/cy/commands/actions/click', () => {
let retries = 0
cy.on('command:retry', () => {
- return retries += 1
+ retries += 1
})
cy.stub(cy, 'ensureElementIsNotAnimating')
@@ -1062,13 +1216,13 @@ describe('src/cy/commands/actions/click', () => {
}
})
- return null
+ null
})
it('eventually passes the assertion', () => {
cy.$$('button:first').click(function () {
_.delay(() => {
- return $(this).addClass('clicked')
+ $(this).addClass('clicked')
}
, 50)
@@ -1088,7 +1242,7 @@ describe('src/cy/commands/actions/click', () => {
it('eventually passes the assertion on multiple buttons', () => {
cy.$$('button').click(function () {
_.delay(() => {
- return $(this).addClass('clicked')
+ $(this).addClass('clicked')
}
, 50)
@@ -1248,8 +1402,6 @@ describe('src/cy/commands/actions/click', () => {
it('can pass options along with position', (done) => {
const $btn = $('
').attr('id', 'button-covered-in-span').css({ height: 100, width: 100 }).prependTo(cy.$$('body'))
- $('
span').css({ position: 'absolute', left: $btn.offset().left + 80, top: $btn.offset().top + 80, padding: 5, display: 'inline-block', backgroundColor: 'yellow' }).appendTo(cy.$$('body'))
-
$btn.on('click', () => {
done()
})
@@ -1282,8 +1434,6 @@ describe('src/cy/commands/actions/click', () => {
it('can pass options along with x, y', (done) => {
const $btn = $('
').attr('id', 'button-covered-in-span').css({ height: 100, width: 100 }).prependTo(cy.$$('body'))
- $('
span').css({ position: 'absolute', left: $btn.offset().left + 50, top: $btn.offset().top + 65, padding: 5, display: 'inline-block', backgroundColor: 'yellow' }).appendTo(cy.$$('body'))
-
$btn.on('click', () => {
done()
})
@@ -1348,8 +1498,8 @@ describe('src/cy/commands/actions/click', () => {
const input = cy.$$('input:first')
_.each('focus focusin mousedown mouseup click'.split(' '), (event) => {
- return input.get(0).addEventListener(event, () => {
- return events.push(event)
+ input.get(0).addEventListener(event, () => {
+ events.push(event)
})
})
@@ -1403,27 +1553,17 @@ describe('src/cy/commands/actions/click', () => {
expect(onFocus).not.to.be.called
})
})
- })
-
- // it "events", ->
- // $btn = cy.$$("button")
- // win = $(cy.state("window"))
-
- // _.each {"btn": btn, "win": win}, (type, key) ->
- // _.each "focus mousedown mouseup click".split(" "), (event) ->
- // # _.each "focus focusin focusout mousedown mouseup click".split(" "), (event) ->
- // type.get(0).addEventListener event, (e) ->
- // if key is "btn"
- // # e.preventDefault()
- // e.stopPropagation()
-
- // console.log "#{key} #{event}", e
- // $btn.on "mousedown", (e) ->
- // console.log("btn mousedown")
- // e.preventDefault()
-
- // win.on "mousedown", -> console.log("win mousedown")
+ it('will fire pointerdown event', () => {
+ // cy.get('input').eq(1).click()
+ // cy.get('input').eq(2).click()
+ // cy.get('input').eq(4).click()
+ cy.get('textarea:first').click()
+ // cy.get('input').eq(3).click()
+ cy.get('input:first').click()
+ // cy.get('input').eq(1).click()
+ })
+ })
describe('errors', () => {
beforeEach(function () {
@@ -1434,10 +1574,10 @@ describe('src/cy/commands/actions/click', () => {
cy.on('log:added', (attrs, log) => {
this.lastLog = log
- return this.logs.push(log)
+ this.logs.push(log)
})
- return null
+ null
})
it('throws when not a dom subject', (done) => {
@@ -1449,15 +1589,14 @@ describe('src/cy/commands/actions/click', () => {
})
it('throws when attempting to click multiple elements', (done) => {
- const num = cy.$$('button').length
cy.on('fail', (err) => {
- expect(err.message).to.eq(`cy.click() can only be called on a single element. Your subject contained ${num} elements. Pass { multiple: true } if you want to serially click each element.`)
+ expect(err.message).to.eq('cy.click() can only be called on a single element. Your subject contained 4 elements. Pass { multiple: true } if you want to serially click each element.')
done()
})
- cy.get('button').click()
+ cy.get('.badge-multi').click()
})
it('throws when subject is not in the document', (done) => {
@@ -1492,16 +1631,23 @@ describe('src/cy/commands/actions/click', () => {
cy.click()
})
+ // Array(1).fill().map(()=>
it('throws when any member of the subject isnt visible', function (done) {
- cy.timeout(250)
+
+ // sometimes the command will timeout early with
+ // Error: coordsHistory must be at least 2 sets of coords
+ cy.timeout(300)
cy.$$('#three-buttons button').show().last().hide()
cy.on('fail', (err) => {
- const { lastLog } = this
+ const { lastLog, logs } = this
+ const logsArr = logs.map((log) => {
+ return log.get().consoleProps()
+ })
- expect(this.logs.length).to.eq(4)
+ expect(logsArr).to.have.length(4)
expect(lastLog.get('error')).to.eq(err)
expect(err.message).to.include('cy.click() failed because this element is not visible')
@@ -1676,7 +1822,7 @@ describe('src/cy/commands/actions/click', () => {
let clicks = 0
cy.$$('button:first').on('click', () => {
- return clicks += 1
+ clicks += 1
})
cy.on('fail', (err) => {
@@ -1697,7 +1843,7 @@ describe('src/cy/commands/actions/click', () => {
expect(err.message).not.to.include('undefined')
expect(lastLog.get('name')).to.eq('assert')
expect(lastLog.get('state')).to.eq('failed')
- expect(lastLog.get('error')).to.be.an.instanceof(chai.AssertionError)
+ expect(lastLog.get('error')).to.be.an.instanceof(window.chai.AssertionError)
done()
})
@@ -1723,10 +1869,10 @@ describe('src/cy/commands/actions/click', () => {
cy.on('log:added', (attrs, log) => {
this.lastLog = log
- return this.logs.push(log)
+ this.logs.push(log)
})
- return null
+ null
})
it('logs immediately before resolving', (done) => {
@@ -1781,7 +1927,7 @@ describe('src/cy/commands/actions/click', () => {
cy.on('log:added', (attrs, log) => {
if (log.get('name') === 'click') {
- return clicks.push(log)
+ clicks.push(log)
}
})
@@ -1798,7 +1944,7 @@ describe('src/cy/commands/actions/click', () => {
cy.on('log:added', (attrs, log) => {
if (log.get('name') === 'click') {
- return logs.push(log)
+ logs.push(log)
}
})
@@ -1823,12 +1969,12 @@ describe('src/cy/commands/actions/click', () => {
cy.on('log:added', (attrs, log) => {
if (log.get('name') === 'click') {
- return logs.push(log)
+ logs.push(log)
}
})
cy.get('#three-buttons button').click({ multiple: true }).then(() => {
- return _.each(logs, (log) => {
+ _.each(logs, (log) => {
expect(log.get('state')).to.eq('passed')
expect(log.get('ended')).to.be.true
@@ -1857,7 +2003,7 @@ describe('src/cy/commands/actions/click', () => {
expect(logCoords.x).to.be.closeTo(fromWindow.x, 1) // ensure we are within 1
expect(logCoords.y).to.be.closeTo(fromWindow.y, 1) // ensure we are within 1
expect(console.Command).to.eq('click')
- expect(console['Applied To']).to.eq(lastLog.get('$el').get(0))
+ expect(console['Applied To'], 'applied to').to.eq(lastLog.get('$el').get(0))
expect(console.Elements).to.eq(1)
expect(console.Coords.x).to.be.closeTo(fromWindow.x, 1) // ensure we are within 1
@@ -1886,29 +2032,75 @@ describe('src/cy/commands/actions/click', () => {
})
cy.get('input:first').click().then(function () {
- expect(this.lastLog.invoke('consoleProps').groups()).to.deep.eq([
- {
- name: 'MouseDown',
- items: {
- preventedDefault: true,
- stoppedPropagation: true,
+
+ const consoleProps = this.lastLog.invoke('consoleProps')
+
+ expect(consoleProps.table[1]()).to.containSubset({
+ 'name': 'Mouse Move Events',
+ 'data': [
+ {
+ 'Event Name': 'pointerover',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
},
- },
- {
- name: 'MouseUp',
- items: {
- preventedDefault: false,
- stoppedPropagation: false,
+ {
+ 'Event Name': 'mouseover',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
},
- },
- {
- name: 'Click',
- items: {
- preventedDefault: false,
- stoppedPropagation: false,
+ {
+ 'Event Name': 'pointermove',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
},
- },
- ])
+ {
+ 'Event Name': 'mousemove',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ ],
+ })
+
+ expect(consoleProps.table[2]()).to.containSubset({
+ name: 'Mouse Click Events',
+ data: [
+ {
+ 'Event Name': 'pointerdown',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ {
+ 'Event Name': 'mousedown',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': true,
+ 'Stopped Propagation?': true,
+ },
+ {
+ 'Event Name': 'pointerup',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ {
+ 'Event Name': 'mouseup',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ {
+ 'Event Name': 'click',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ ],
+ })
+
})
})
@@ -1918,27 +2110,36 @@ describe('src/cy/commands/actions/click', () => {
})
cy.get('input:first').click().then(function () {
- expect(this.lastLog.invoke('consoleProps').groups()).to.deep.eq([
+ expect(this.lastLog.invoke('consoleProps').table[2]().data).to.containSubset([
{
- name: 'MouseDown',
- items: {
- preventedDefault: false,
- stoppedPropagation: false,
- },
+ 'Event Name': 'pointerdown',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
},
{
- name: 'MouseUp',
- items: {
- preventedDefault: true,
- stoppedPropagation: true,
- },
+ 'Event Name': 'mousedown',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
},
{
- name: 'Click',
- items: {
- preventedDefault: false,
- stoppedPropagation: false,
- },
+ 'Event Name': 'pointerup',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ {
+ 'Event Name': 'mouseup',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': true,
+ 'Stopped Propagation?': true,
+ },
+ {
+ 'Event Name': 'click',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
},
])
})
@@ -1950,29 +2151,106 @@ describe('src/cy/commands/actions/click', () => {
})
cy.get('input:first').click().then(function () {
- expect(this.lastLog.invoke('consoleProps').groups()).to.deep.eq([
+ expect(this.lastLog.invoke('consoleProps').table[2]().data).to.containSubset([
{
- name: 'MouseDown',
- items: {
- preventedDefault: false,
- stoppedPropagation: false,
- },
+ 'Event Name': 'pointerdown',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
},
{
- name: 'MouseUp',
- items: {
- preventedDefault: false,
- stoppedPropagation: false,
- },
+ 'Event Name': 'mousedown',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
},
{
- name: 'Click',
- items: {
- preventedDefault: true,
- stoppedPropagation: true,
- },
+ 'Event Name': 'pointerup',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ {
+ 'Event Name': 'mouseup',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ {
+ 'Event Name': 'click',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': true,
+ 'Stopped Propagation?': true,
+ },
+ ])
+ })
+ })
+
+ it('#consoleProps groups skips mouse move events if no mouse move', () => {
+ const btn = cy.$$('span#not-hidden')
+
+ attachMouseClickListeners({ btn })
+ attachMouseHoverListeners({ btn })
+
+ cy.get('span#not-hidden').click().click()
+
+ cy.getAll('btn', 'mousemove mouseover').each(shouldBeCalledOnce)
+ cy.getAll('btn', 'pointerdown mousedown pointerup mouseup click').each(shouldBeCalledNth(2))
+ .then(function () {
+
+ const { logs } = this
+ const logsArr = logs.map((x) => x.invoke('consoleProps'))
+
+ const lastClickProps = _.filter(logsArr, { Command: 'click' })[1]
+ const consoleProps = lastClickProps
+
+ expect(_.map(consoleProps.table, (x) => x())).to.containSubset([
+ {
+ 'name': 'Mouse Move Events (skipped)',
+ 'data': [],
+ },
+ {
+ 'name': 'Mouse Click Events',
+ 'data': [
+ {
+ 'Event Name': 'pointerdown',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'mousedown',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'pointerup',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'mouseup',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'click',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ ],
},
])
+
})
})
@@ -1982,53 +2260,46 @@ describe('src/cy/commands/actions/click', () => {
})
cy.get('input:first').type('{ctrl}{shift}', { release: false }).click().then(function () {
- expect(this.lastLog.invoke('consoleProps').groups()).to.deep.eq([
+ expect(this.lastLog.invoke('consoleProps').table[2]().data).to.containSubset([
{
- name: 'MouseDown',
- items: {
- preventedDefault: false,
- stoppedPropagation: false,
- modifiers: 'ctrl, shift',
- },
+ 'Event Name': 'pointerdown',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': 'ctrl, shift',
+
},
{
- name: 'MouseUp',
- items: {
- preventedDefault: false,
- stoppedPropagation: false,
- modifiers: 'ctrl, shift',
- },
+ 'Event Name': 'mousedown',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': 'ctrl, shift',
+
},
{
- name: 'Click',
- items: {
- preventedDefault: true,
- stoppedPropagation: true,
- modifiers: 'ctrl, shift',
- },
- },
- ])
-
- cy.get('body').type('{ctrl}')
- })
- }) // clear modifiers
+ 'Event Name': 'pointerup',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': 'ctrl, shift',
- it('#consoleProps when no mouseup or click', () => {
- const $btn = cy.$$('button:first')
-
- $btn.on('mousedown', function () {
- // synchronously remove this button
- return $(this).remove()
- })
+ },
+ {
+ 'Event Name': 'mouseup',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': 'ctrl, shift',
- cy.contains('button').click().then(function () {
- expect(this.lastLog.invoke('consoleProps').groups()).to.deep.eq([
+ },
{
- name: 'MouseDown',
- items: {
- preventedDefault: false,
- stoppedPropagation: false,
- },
+ 'Event Name': 'click',
+ 'Target Element': { id: 'input' },
+ 'Prevented Default?': true,
+ 'Stopped Propagation?': true,
+ 'Modifiers': 'ctrl, shift',
+
},
])
})
@@ -2039,24 +2310,45 @@ describe('src/cy/commands/actions/click', () => {
$btn.on('mouseup', function () {
// synchronously remove this button
- return $(this).remove()
+ $(this).remove()
})
cy.contains('button').click().then(function () {
- expect(this.lastLog.invoke('consoleProps').groups()).to.deep.eq([
+ expect(this.lastLog.invoke('consoleProps').table[2]().data).to.containSubset([
{
- name: 'MouseDown',
- items: {
- preventedDefault: false,
- stoppedPropagation: false,
- },
+ 'Event Name': 'pointerdown',
+ 'Target Element': { id: 'button' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
},
{
- name: 'MouseUp',
- items: {
- preventedDefault: false,
- stoppedPropagation: false,
- },
+ 'Event Name': 'mousedown',
+ 'Target Element': { id: 'button' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'pointerup',
+ 'Target Element': { id: 'button' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'mouseup',
+ 'Target Element': { id: 'button' },
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'click',
+ 'Target Element': '⚠️ not fired (Element was detached)',
+ 'Prevented Default?': null,
+ 'Stopped Propagation?': null,
+ 'Modifiers': null,
},
])
})
@@ -2067,11 +2359,11 @@ describe('src/cy/commands/actions/click', () => {
$btn.on('mouseup', function () {
// synchronously remove this button
- return $(this).remove()
+ $(this).remove()
})
$btn.on('click', () => {
- return fail('should not have gotten click')
+ fail('should not have gotten click')
})
cy.contains('button').click()
@@ -2093,7 +2385,7 @@ describe('src/cy/commands/actions/click', () => {
context('#dblclick', () => {
it('sends a dblclick event', (done) => {
- cy.$$('#button').dblclick(() => {
+ cy.$$('#button').on('dblclick', () => {
done()
})
@@ -2108,25 +2400,19 @@ describe('src/cy/commands/actions/click', () => {
})
})
- it('causes focusable elements to receive focus', (done) => {
- const $text = cy.$$(':text:first')
-
- $text.focus(() => {
- done()
- })
-
- cy.get(':text:first').dblclick()
+ it('causes focusable elements to receive focus', () => {
+ cy.get(':text:first').dblclick().should('have.focus')
})
it('silences errors on unfocusable elements', () => {
- cy.get('div:first').dblclick()
+ cy.get('div:first').dblclick({ force: true })
})
it('causes first focused element to receive blur', () => {
let blurred = false
cy.$$('input:first').blur(() => {
- return blurred = true
+ blurred = true
})
cy
@@ -2165,12 +2451,12 @@ describe('src/cy/commands/actions/click', () => {
})
})
- // NOTE: fix this once we implement aborting / restoring / reset
+ // TODO: fix this once we implement aborting / restoring / reset
it.skip('can cancel multiple dblclicks', function (done) {
let dblclicks = 0
const spy = this.sandbox.spy(() => {
- return this.Cypress.abort()
+ this.Cypress.abort()
})
// abort after the 3rd dblclick
@@ -2181,7 +2467,7 @@ describe('src/cy/commands/actions/click', () => {
anchors.dblclick(() => {
dblclicks += 1
- return dblclicked()
+ dblclicked()
})
// make sure we have at least 5 anchor links
@@ -2193,7 +2479,7 @@ describe('src/cy/commands/actions/click', () => {
// is called
const timeout = this.sandbox.spy(cy, '_timeout')
- return _.delay(() => {
+ _.delay(() => {
// abort should only have been called once
expect(spy.callCount).to.eq(1)
@@ -2210,25 +2496,95 @@ describe('src/cy/commands/actions/click', () => {
cy.get('#sequential-clicks a').dblclick()
})
+ it('serially dblclicks a collection of anchors to the top of the page', () => {
+
+ const throttled = cy.stub().as('clickcount')
+
+ // create a throttled click function
+ // which proves we are clicking serially
+ const handleClick = cy.stub()
+ .callsFake(_.throttle(throttled, 5, { leading: false }))
+ .as('handleClick')
+
+ const $anchors = cy.$$('#sequential-clicks a')
+
+ $anchors.on('click', handleClick)
+ cy.$$('div#dom').on('click', cy.stub().as('topClick'))
+ .on('dblclick', cy.stub().as('topDblclick'))
+
+ // make sure we're clicking multiple $anchors
+ expect($anchors.length).to.be.gt(1)
+
+ cy.get('#sequential-clicks a').dblclick({ multiple: true }).then(($els) => {
+ expect($els).to.have.length(throttled.callCount)
+ cy.get('@topDblclick').should('have.property', 'callCount', $els.length)
+ })
+ })
+
it('serially dblclicks a collection', () => {
- let dblclicks = 0
- // create a throttled dblclick function
- // which proves we are dblclicking serially
- const throttled = _.throttle(() => {
- return dblclicks += 1
- }
- , 5, { leading: false })
+ const throttled = cy.stub().as('clickcount')
- const anchors = cy.$$('#sequential-clicks a')
+ // create a throttled click function
+ // which proves we are clicking serially
+ const handleClick = cy.stub()
+ .callsFake(_.throttle(throttled, 5, { leading: false }))
+ .as('handleClick')
+
+ const $anchors = cy.$$('#three-buttons button')
+
+ $anchors.on('dblclick', handleClick)
+
+ // make sure we're clicking multiple $anchors
+ expect($anchors.length).to.be.gt(1)
+
+ cy.get('#three-buttons button').dblclick({ multiple: true }).then(($els) => {
+ expect($els).to.have.length(throttled.callCount)
+ })
+ })
+
+ it('correctly sets the detail property on mouse events', () => {
+ const btn = cy.$$('button:first')
+
+ attachMouseClickListeners({ btn })
+ attachMouseDblclickListeners({ btn })
+ cy.get('button:first').dblclick()
+ cy.getAll('btn', 'mousedown mouseup click').each((spy) => {
+ expect(spy.firstCall).calledWithMatch({ detail: 1 })
+ })
+
+ cy.getAll('btn', 'mousedown mouseup click').each((spy) => {
+ expect(spy.lastCall).to.be.calledWithMatch({ detail: 2 })
+ })
+
+ cy.getAll('btn', 'dblclick').each((spy) => {
+ expect(spy).to.be.calledOnce
+ expect(spy.firstCall).to.be.calledWithMatch({ detail: 2 })
+ })
+
+ // pointer events do not set change detail prop
+ cy.getAll('btn', 'pointerdown pointerup').each((spy) => {
+ expect(spy).to.be.calledWithMatch({ detail: 0 })
+ })
+ })
+
+ it('sends modifiers', () => {
- anchors.dblclick(throttled)
+ const btn = cy.$$('button:first')
- // make sure we're dblclicking multiple anchors
- expect(anchors.length).to.be.gt(1)
+ attachMouseClickListeners({ btn })
+ attachMouseDblclickListeners({ btn })
- cy.get('#sequential-clicks a').dblclick().then(($anchors) => {
- expect($anchors.length).to.eq(dblclicks)
+ cy.get('input:first').type('{ctrl}{shift}', { release: false })
+ cy.get('button:first').dblclick()
+
+ cy.getAll('btn', 'pointerdown mousedown pointerup mouseup click dblclick').each((stub) => {
+ expect(stub).to.be.calledWithMatch({
+ shiftKey: true,
+ ctrlKey: true,
+ metaKey: false,
+ altKey: false,
+ })
})
})
@@ -2257,10 +2613,10 @@ describe('src/cy/commands/actions/click', () => {
cy.on('log:added', (attrs, log) => {
this.lastLog = log
- return this.logs.push(log)
+ this.logs.push(log)
})
- return null
+ null
})
it('throws when not a dom subject', (done) => {
@@ -2291,18 +2647,6 @@ describe('src/cy/commands/actions/click', () => {
cy.get('button:first').dblclick().dblclick()
})
- it('throws when any member of the subject isnt visible', (done) => {
- cy.$$('button').slice(0, 3).show().last().hide()
-
- cy.on('fail', (err) => {
- expect(err.message).to.include('cy.dblclick() failed because this element is not visible')
-
- done()
- })
-
- cy.get('button').invoke('slice', 0, 3).dblclick()
- })
-
it('logs once when not dom subject', function (done) {
cy.on('fail', (err) => {
const { lastLog } = this
@@ -2317,12 +2661,15 @@ describe('src/cy/commands/actions/click', () => {
})
it('throws when any member of the subject isnt visible', function (done) {
+ cy.timeout(600)
cy.$$('#three-buttons button').show().last().hide()
cy.on('fail', (err) => {
const { lastLog } = this
- expect(this.logs.length).to.eq(4)
+ const logs = _.cloneDeep(this.logs)
+
+ expect(logs).to.have.length(4)
expect(lastLog.get('error')).to.eq(err)
expect(err.message).to.include('cy.dblclick() failed because this element is not visible')
@@ -2340,10 +2687,10 @@ describe('src/cy/commands/actions/click', () => {
cy.on('log:added', (attrs, log) => {
this.lastLog = log
- return this.logs.push(log)
+ this.logs.push(log)
})
- return null
+ null
})
it('logs immediately before resolving', (done) => {
@@ -2365,9 +2712,9 @@ describe('src/cy/commands/actions/click', () => {
cy.get('button:first').dblclick().then(function () {
const { lastLog } = this
- expect(lastLog.get('snapshots').length).to.eq(1)
-
- expect(lastLog.get('snapshots')[0]).to.be.an('object')
+ expect(lastLog.get('snapshots')).to.have.length(2)
+ expect(lastLog.get('snapshots')[0]).to.containSubset({ name: 'before' })
+ expect(lastLog.get('snapshots')[1]).to.containSubset({ name: 'after' })
})
})
@@ -2383,7 +2730,7 @@ describe('src/cy/commands/actions/click', () => {
cy.on('log:added', (attrs, log) => {
if (log.get('name') === 'dblclick') {
- return dblclicks.push(log)
+ dblclicks.push(log)
}
})
@@ -2400,7 +2747,7 @@ describe('src/cy/commands/actions/click', () => {
cy.on('log:added', (attrs, log) => {
if (log.get('name') === 'dblclick') {
- return logs.push(log)
+ logs.push(log)
}
})
@@ -2418,13 +2765,1283 @@ describe('src/cy/commands/actions/click', () => {
cy.get('button').first().dblclick().then(function () {
const { lastLog } = this
- expect(lastLog.invoke('consoleProps')).to.deep.eq({
- Command: 'dblclick',
- 'Applied To': lastLog.get('$el').get(0),
- Elements: 1,
+ const consoleProps = lastLog.invoke('consoleProps')
+
+ expect(consoleProps).to.containSubset({
+ 'Command': 'dblclick',
+ 'Applied To': {},
+ 'Elements': 1,
+ 'Coords': {
+ 'x': 34,
+ 'y': 548,
+ },
+ 'Options': {
+ 'multiple': true,
+ },
+ 'table': {},
})
- })
- })
- })
- })
-})
+
+ const tables = _.map(consoleProps.table, ((x) => x()))
+
+ expect(tables).to.containSubset([
+ {
+ 'name': 'Mouse Move Events',
+ 'data': [
+ {
+ 'Event Name': 'pointerover',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ {
+ 'Event Name': 'mouseover',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ {
+ 'Event Name': 'pointermove',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ {
+ 'Event Name': 'mousemove',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ ],
+ },
+ {
+ 'name': 'Mouse Click Events',
+ 'data': [
+ {
+ 'Event Name': 'pointerdown',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'mousedown',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'pointerup',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'mouseup',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'click',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'pointerdown',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'mousedown',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'pointerup',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'mouseup',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'click',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ ],
+ },
+ {
+ 'name': 'Mouse Dblclick Event',
+ 'data': [
+ {
+ 'Event Name': 'dblclick',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ ],
+ },
+ ])
+ })
+ })
+ })
+ })
+
+ context('#rightclick', () => {
+
+ it('can rightclick', () => {
+ const el = cy.$$('button:first')
+
+ attachMouseClickListeners({ el })
+ attachContextmenuListeners({ el })
+
+ cy.get('button:first').rightclick().should('have.focus')
+
+ cy.getAll('el', 'pointerdown mousedown contextmenu pointerup mouseup').each(shouldBeCalled)
+ cy.getAll('el', 'click').each(shouldNotBeCalled)
+
+ cy.getAll('el', 'pointerdown mousedown pointerup mouseup').each((stub) => {
+ expect(stub.firstCall.args[0]).to.containSubset({
+ button: 2,
+ buttons: 2,
+ which: 3,
+ })
+ })
+
+ cy.getAll('el', 'contextmenu').each((stub) => {
+ expect(stub.firstCall.args[0]).to.containSubset({
+ altKey: false,
+ bubbles: true,
+ target: el.get(0),
+ button: 2,
+ buttons: 2,
+ cancelable: true,
+ data: undefined,
+ detail: 0,
+ eventPhase: 2,
+ handleObj: { type: 'contextmenu', origType: 'contextmenu', data: undefined },
+ relatedTarget: null,
+ shiftKey: false,
+ type: 'contextmenu',
+ view: cy.state('window'),
+ which: 3,
+ })
+ })
+ })
+
+ it('can rightclick disabled', () => {
+ const el = cy.$$('input:first')
+
+ el.get(0).disabled = true
+
+ attachMouseClickListeners({ el })
+ attachFocusListeners({ el })
+ attachContextmenuListeners({ el })
+
+ cy.get('input:first').rightclick({ force: true })
+
+ cy.getAll('el', 'mousedown contextmenu mouseup').each(shouldNotBeCalled)
+ cy.getAll('el', 'pointerdown pointerup').each(shouldBeCalled)
+ })
+
+ it('rightclick cancel contextmenu', () => {
+ const el = cy.$$('button:first')
+
+ // canceling contextmenu prevents the native contextmenu
+ // likely we want to call attention to this, since we cannot
+ // reproduce the native contextmenu
+ el.on('contextmenu', () => false)
+
+ attachMouseClickListeners({ el })
+ attachFocusListeners({ el })
+ attachContextmenuListeners({ el })
+
+ cy.get('button:first').rightclick().should('have.focus')
+
+ cy.getAll('el', 'pointerdown mousedown contextmenu pointerup mouseup').each(shouldBeCalled)
+ cy.getAll('el', 'click').each(shouldNotBeCalled)
+ })
+
+ it('rightclick cancel mousedown', () => {
+ const el = cy.$$('button:first')
+
+ el.on('mousedown', () => false)
+
+ attachMouseClickListeners({ el })
+ attachFocusListeners({ el })
+ attachContextmenuListeners({ el })
+
+ cy.get('button:first').rightclick().should('not.have.focus')
+
+ cy.getAll('el', 'pointerdown mousedown contextmenu pointerup mouseup').each(shouldBeCalled)
+ cy.getAll('el', 'focus click').each(shouldNotBeCalled)
+
+ })
+
+ it('rightclick cancel pointerdown', () => {
+ const el = cy.$$('button:first')
+
+ el.on('pointerdown', () => false)
+
+ attachMouseClickListeners({ el })
+ attachFocusListeners({ el })
+ attachContextmenuListeners({ el })
+
+ cy.get('button:first').rightclick()
+
+ cy.getAll('el', 'pointerdown pointerup contextmenu').each(shouldBeCalled)
+ cy.getAll('el', 'mousedown mouseup').each(shouldNotBeCalled)
+
+ })
+
+ it('rightclick remove el on pointerdown', () => {
+ const el = cy.$$('button:first')
+
+ el.on('pointerdown', () => el.get(0).remove())
+
+ attachMouseClickListeners({ el })
+ attachFocusListeners({ el })
+ attachContextmenuListeners({ el })
+
+ cy.get('button:first').rightclick().should('not.exist')
+
+ cy.getAll('el', 'pointerdown').each(shouldBeCalled)
+ cy.getAll('el', 'mousedown mouseup contextmenu pointerup').each(shouldNotBeCalled)
+
+ })
+
+ it('rightclick remove el on mouseover', () => {
+ const el = cy.$$('button:first')
+ const el2 = cy.$$('div#tabindex')
+
+ el.on('mouseover', () => el.get(0).remove())
+
+ attachMouseClickListeners({ el, el2 })
+ attachMouseHoverListeners({ el, el2 })
+ attachFocusListeners({ el, el2 })
+ attachContextmenuListeners({ el, el2 })
+
+ cy.get('button:first').rightclick().should('not.exist')
+ cy.get(el2.selector).should('have.focus')
+
+ cy.getAll('el', 'pointerover mouseover').each(shouldBeCalledOnce)
+ cy.getAll('el', 'pointerdown mousedown pointerup mouseup contextmenu').each(shouldNotBeCalled)
+ cy.getAll('el2', 'focus pointerdown pointerup contextmenu').each(shouldBeCalled)
+
+ })
+
+ describe('errors', () => {
+ beforeEach(function () {
+ Cypress.config('defaultCommandTimeout', 100)
+
+ this.logs = []
+
+ cy.on('log:added', (attrs, log) => {
+ this.lastLog = log
+
+ this.logs.push(log)
+ })
+
+ null
+ })
+
+ it('throws when not a dom subject', (done) => {
+ cy.on('fail', () => {
+ done()
+ })
+
+ cy.rightclick()
+ })
+
+ it('throws when subject is not in the document', (done) => {
+ let rightclicked = 0
+
+ const $button = cy.$$('button:first').on('contextmenu', () => {
+ rightclicked += 1
+ $button.remove()
+
+ return false
+ })
+
+ cy.on('fail', (err) => {
+ expect(rightclicked).to.eq(1)
+ expect(err.message).to.include('cy.rightclick() failed because this element')
+
+ done()
+ })
+
+ cy.get('button:first').rightclick().rightclick()
+ })
+
+ it('logs once when not dom subject', function (done) {
+ cy.on('fail', (err) => {
+ const { lastLog } = this
+
+ expect(this.logs.length).to.eq(1)
+ expect(lastLog.get('error')).to.eq(err)
+
+ done()
+ })
+
+ cy.rightclick()
+ })
+
+ it('throws when any member of the subject isnt visible', function (done) {
+ cy.timeout(300)
+ cy.$$('#three-buttons button').show().last().hide()
+
+ cy.on('fail', (err) => {
+ const { lastLog } = this
+
+ expect(this.logs.length).to.eq(4)
+ expect(lastLog.get('error')).to.eq(err)
+ expect(err.message).to.include('cy.rightclick() failed because this element is not visible')
+
+ done()
+ })
+
+ cy.get('#three-buttons button').rightclick({ multiple: true })
+ })
+ })
+
+ describe('.log', () => {
+ beforeEach(function () {
+ this.logs = []
+
+ cy.on('log:added', (attrs, log) => {
+ this.lastLog = log
+
+ this.logs.push(log)
+ })
+
+ null
+ })
+
+ it('logs immediately before resolving', (done) => {
+ const $button = cy.$$('button:first')
+
+ cy.on('log:added', (attrs, log) => {
+ if (log.get('name') === 'rightclick') {
+ expect(log.get('state')).to.eq('pending')
+ expect(log.get('$el').get(0)).to.eq($button.get(0))
+
+ done()
+ }
+ })
+
+ cy.get('button:first').rightclick()
+ })
+
+ it('snapshots after clicking', () => {
+ cy.get('button:first').rightclick().then(function () {
+ const { lastLog } = this
+
+ expect(lastLog.get('snapshots')).to.have.length(2)
+ expect(lastLog.get('snapshots')[0]).to.containSubset({ name: 'before' })
+ expect(lastLog.get('snapshots')[1]).to.containSubset({ name: 'after' })
+ })
+ })
+
+ it('returns only the $el for the element of the subject that was rightclicked', () => {
+ const rightclicks = []
+
+ // append two buttons
+ const $button = () => {
+ return $('
{
+ if (log.get('name') === 'rightclick') {
+ rightclicks.push(log)
+ }
+ })
+
+ cy.get('button.rightclicks').rightclick({ multiple: true }).then(($buttons) => {
+ expect($buttons.length).to.eq(2)
+ expect(rightclicks.length).to.eq(2)
+
+ expect(rightclicks[1].get('$el').get(0)).to.eq($buttons.last().get(0))
+ })
+ })
+
+ it('logs only 1 rightclick event', () => {
+ const logs = []
+
+ cy.on('log:added', (attrs, log) => {
+ if (log.get('name') === 'rightclick') {
+ logs.push(log)
+ }
+ })
+
+ cy.get('button:first').rightclick().then(() => {
+ expect(logs.length).to.eq(1)
+ })
+ })
+
+ it('#consoleProps', function () {
+ cy.on('log:added', (attrs, log) => {
+ this.log = log
+
+ })
+
+ cy.get('button').first().rightclick().then(function () {
+ const { lastLog } = this
+
+ const consoleProps = lastLog.invoke('consoleProps')
+
+ expect(consoleProps).to.containSubset({
+ 'Command': 'rightclick',
+ 'Applied To': {},
+ 'Elements': 1,
+ 'Coords': {
+ 'x': 34,
+ 'y': 548,
+ },
+ 'table': {},
+ })
+
+ const tables = _.map(consoleProps.table, ((x) => x()))
+
+ expect(tables).to.containSubset([
+ {
+ 'name': 'Mouse Move Events',
+ 'data': [
+ {
+ 'Event Name': 'pointerover',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ {
+ 'Event Name': 'mouseover',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ {
+ 'Event Name': 'pointermove',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ {
+ 'Event Name': 'mousemove',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ },
+ ],
+ },
+ {
+ 'name': 'Mouse Click Events',
+ 'data': [
+ {
+ 'Event Name': 'pointerdown',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'mousedown',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'pointerup',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ {
+ 'Event Name': 'mouseup',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ ],
+ },
+ {
+ 'name': 'Contextmenu Event',
+ 'data': [
+ {
+ 'Event Name': 'contextmenu',
+ 'Target Element': {},
+ 'Prevented Default?': false,
+ 'Stopped Propagation?': false,
+ 'Modifiers': null,
+ },
+ ],
+ },
+ ])
+ })
+ })
+ })
+
+ })
+})
+
+describe('mouse state', () => {
+
+ describe('mouse/pointer events', () => {
+ beforeEach(() => {
+ cy.visit('http://localhost:3500/fixtures/dom.html')
+ })
+
+ describe('resets mouse state', () => {
+
+ it('set state', () => {
+ cy.get('div.item:first').then(($el) => {
+ const mouseenter = cy.stub().as('mouseenter')
+
+ cy.get('body').then(($el) => {
+ $el[0].addEventListener('mouseenter', mouseenter)
+ }).then(() => {
+ const rect = _.pick($el[0].getBoundingClientRect(), 'left', 'top')
+ const coords = {
+ x: rect.left,
+ y: rect.top,
+ doc: cy.state('document'),
+ }
+
+ cy.internal.mouse.mouseMove(coords)
+ expect(mouseenter).to.be.calledOnce
+ expect(cy.state('mouseCoords')).ok
+ })
+ })
+ })
+
+ it('reset state', () => {
+ const mouseenter = cy.stub().callsFake(() => { }).as('mouseenter')
+
+ cy.get('body').then(($el) => {
+ $el[0].addEventListener('mouseenter', mouseenter)
+ }).then(() => {
+ expect(cy.state('mouseCoords')).to.eq(undefined)
+ expect(mouseenter).to.not.be.called
+ })
+ // expect(this.mousemove).to.have.been.called
+ })
+ })
+
+ describe('mouseout', () => {
+ it('can move mouse from a div to another div', () => {
+ const mouseout = cy.stub().callsFake((e) => {
+ expect(_.toPlainObject(e)).to.containSubset({
+ altKey: false,
+ bubbles: true,
+ button: 0,
+ buttons: 0,
+ cancelBubble: false,
+ cancelable: true,
+ clientX: 492,
+ clientY: 9,
+ composed: true,
+ ctrlKey: false,
+ currentTarget: cy.$$('div.item')[0],
+ defaultPrevented: false,
+ detail: 0,
+ eventPhase: 2,
+ // fromElement: cy.$$('div.item')[0],
+ isTrusted: false,
+ layerX: 492,
+ layerY: 215,
+ metaKey: false,
+ movementX: 0,
+ movementY: 0,
+ // offsetX: 484,
+ // offsetY: 27,
+ pageX: 492,
+ pageY: 215,
+ relatedTarget: cy.$$('div.item')[1],
+ returnValue: true,
+ screenX: 492,
+ screenY: 9,
+ shiftKey: false,
+ sourceCapabilities: null,
+ target: cy.$$('div.item')[0],
+ // not in firefox?
+ // toElement: cy.$$('div.item')[1],
+ type: 'mouseout',
+ view: cy.state('window'),
+ // which: 0,
+ x: 492,
+ y: 9,
+ })
+ }).as('mouseout')
+ const mouseleave = cy.stub().callsFake((e) => {
+ expect(_.toPlainObject(e)).to.containSubset({
+ altKey: false,
+ bubbles: false,
+ button: 0,
+ buttons: 0,
+ cancelBubble: false,
+ cancelable: false,
+ clientX: 492,
+ clientY: 9,
+ composed: true,
+ ctrlKey: false,
+ currentTarget: cy.$$('div.item')[0],
+ defaultPrevented: false,
+ detail: 0,
+ eventPhase: 2,
+ // fromElement: cy.$$('div.item')[0],
+ isTrusted: false,
+ layerX: 492,
+ layerY: 215,
+ metaKey: false,
+ movementX: 0,
+ movementY: 0,
+ // offsetX: 484,
+ // offsetY: 27,
+ pageX: 492,
+ pageY: 215,
+ relatedTarget: cy.$$('div.item')[1],
+ returnValue: true,
+ screenX: 492,
+ screenY: 9,
+ shiftKey: false,
+ sourceCapabilities: null,
+ target: cy.$$('div.item')[0],
+ // not in firefox?
+ // toElement: cy.$$('div.item')[1],
+ type: 'mouseleave',
+ view: cy.state('window'),
+ // which: 0,
+ x: 492,
+ y: 9,
+ })
+ }).as('mouseleave')
+ const pointerout = cy.stub().callsFake((e) => {
+ expect(_.toPlainObject(e)).to.containSubset({
+ altKey: false,
+ bubbles: true,
+ button: -1,
+ buttons: 0,
+ cancelBubble: false,
+ cancelable: true,
+ clientX: 492,
+ clientY: 9,
+ composed: true,
+ ctrlKey: false,
+ currentTarget: cy.$$('div.item')[0],
+ defaultPrevented: false,
+ detail: 0,
+ eventPhase: 2,
+ // fromElement: cy.$$('div.item')[0],
+ isTrusted: false,
+ layerX: 492,
+ layerY: 215,
+ metaKey: false,
+ movementX: 0,
+ movementY: 0,
+ // offsetX: 484,
+ // offsetY: 27,
+ pageX: 492,
+ pageY: 215,
+ relatedTarget: cy.$$('div.item')[1],
+ returnValue: true,
+ screenX: 492,
+ screenY: 9,
+ shiftKey: false,
+ sourceCapabilities: null,
+ target: cy.$$('div.item')[0],
+ // not in firefox?
+ // toElement: cy.$$('div.item')[1],
+ type: 'pointerout',
+ view: cy.state('window'),
+ // which: 0,
+ x: 492,
+ y: 9,
+ })
+ }).as('pointerout')
+ const pointerleave = cy.stub().callsFake((e) => {
+ expect(_.toPlainObject(e)).to.containSubset({
+ altKey: false,
+ bubbles: false,
+ button: -1,
+ buttons: 0,
+ cancelBubble: false,
+ cancelable: false,
+ clientX: 492,
+ clientY: 9,
+ composed: true,
+ ctrlKey: false,
+ currentTarget: cy.$$('div.item')[0],
+ defaultPrevented: false,
+ detail: 0,
+ eventPhase: 2,
+ // fromElement: cy.$$('div.item')[0],
+ isTrusted: false,
+ layerX: 492,
+ layerY: 215,
+ metaKey: false,
+ movementX: 0,
+ movementY: 0,
+ // offsetX: 484,
+ // offsetY: 27,
+ pageX: 492,
+ pageY: 215,
+ relatedTarget: cy.$$('div.item')[1],
+ returnValue: true,
+ screenX: 492,
+ screenY: 9,
+ shiftKey: false,
+ sourceCapabilities: null,
+ target: cy.$$('div.item')[0],
+ // not in firefox?
+ // toElement: cy.$$('div.item')[1],
+ type: 'pointerleave',
+ view: cy.state('window'),
+ // which: 0,
+ x: 492,
+ y: 9,
+ })
+ }).as('pointerleave')
+ const mouseover = cy.stub().callsFake((e) => {
+ expect(_.toPlainObject(e)).to.containSubset({
+ altKey: false,
+ bubbles: true,
+ button: 0,
+ buttons: 0,
+ cancelBubble: false,
+ cancelable: true,
+ clientX: 492,
+ clientY: 9,
+ composed: true,
+ ctrlKey: false,
+ currentTarget: cy.$$('div.item')[1],
+ defaultPrevented: false,
+ detail: 0,
+ eventPhase: 2,
+ // fromElement: cy.$$('div.item')[0],
+ isTrusted: false,
+ layerX: 492,
+ layerY: 215,
+ metaKey: false,
+ movementX: 0,
+ movementY: 0,
+ // offsetX: 484,
+ // offsetY: 27,
+ pageX: 492,
+ pageY: 215,
+ relatedTarget: cy.$$('div.item')[0],
+ returnValue: true,
+ screenX: 492,
+ screenY: 9,
+ shiftKey: false,
+ sourceCapabilities: null,
+ target: cy.$$('div.item')[1],
+ // not in Firefox
+ // toElement: cy.$$('div.item')[1],
+ type: 'mouseover',
+ view: cy.state('window'),
+ // which: 0,
+ x: 492,
+ y: 9,
+ })
+ }).as('mouseover')
+ const mouseenter = cy.stub().callsFake((e) => {
+ expect(_.toPlainObject(e)).to.containSubset({
+ altKey: false,
+ bubbles: false,
+ button: 0,
+ buttons: 0,
+ cancelBubble: false,
+ cancelable: false,
+ clientX: 492,
+ clientY: 9,
+ composed: true,
+ ctrlKey: false,
+ currentTarget: cy.$$('div.item')[1],
+ defaultPrevented: false,
+ detail: 0,
+ eventPhase: 2,
+ // fromElement: cy.$$('div.item')[0],
+ isTrusted: false,
+ layerX: 492,
+ layerY: 215,
+ metaKey: false,
+ movementX: 0,
+ movementY: 0,
+ // offsetX: 484,
+ // offsetY: 27,
+ pageX: 492,
+ pageY: 215,
+ relatedTarget: cy.$$('div.item')[0],
+ returnValue: true,
+ screenX: 492,
+ screenY: 9,
+ shiftKey: false,
+ sourceCapabilities: null,
+ target: cy.$$('div.item')[1],
+ // not in Firefox
+ // toElement: cy.$$('div.item')[1],
+ type: 'mouseenter',
+ view: cy.state('window'),
+ // which: 0,
+ x: 492,
+ y: 9,
+ })
+ }).as('mouseenter')
+ const pointerover = cy.stub().callsFake((e) => {
+ expect(_.toPlainObject(e)).to.containSubset({
+ altKey: false,
+ bubbles: true,
+ button: -1,
+ buttons: 0,
+ cancelBubble: false,
+ cancelable: true,
+ clientX: 492,
+ clientY: 9,
+ composed: true,
+ ctrlKey: false,
+ currentTarget: cy.$$('div.item')[1],
+ defaultPrevented: false,
+ detail: 0,
+ eventPhase: 2,
+ // fromElement: cy.$$('div.item')[0],
+ isTrusted: false,
+ layerX: 492,
+ layerY: 215,
+ metaKey: false,
+ movementX: 0,
+ movementY: 0,
+ // offsetX: 484,
+ // offsetY: 27,
+ pageX: 492,
+ pageY: 215,
+ relatedTarget: cy.$$('div.item')[0],
+ returnValue: true,
+ screenX: 492,
+ screenY: 9,
+ shiftKey: false,
+ sourceCapabilities: null,
+ target: cy.$$('div.item')[1],
+ // not in Firefox
+ // toElement: cy.$$('div.item')[1],
+ type: 'pointerover',
+ view: cy.state('window'),
+ // which: 0,
+ x: 492,
+ y: 9,
+ })
+ }).as('pointerover')
+ const pointerenter = cy.stub().callsFake((e) => {
+ expect(_.toPlainObject(e)).to.containSubset({
+ altKey: false,
+ bubbles: false,
+ button: -1,
+ buttons: 0,
+ cancelBubble: false,
+ cancelable: false,
+ clientX: 492,
+ clientY: 9,
+ composed: true,
+ ctrlKey: false,
+ currentTarget: cy.$$('div.item')[1],
+ defaultPrevented: false,
+ detail: 0,
+ eventPhase: 2,
+ // fromElement: cy.$$('div.item')[0],
+ isTrusted: false,
+ layerX: 492,
+ layerY: 215,
+ metaKey: false,
+ movementX: 0,
+ movementY: 0,
+ // offsetX: 484,
+ // offsetY: 27,
+ pageX: 492,
+ pageY: 215,
+ relatedTarget: cy.$$('div.item')[0],
+ returnValue: true,
+ screenX: 492,
+ screenY: 9,
+ shiftKey: false,
+ sourceCapabilities: null,
+ target: cy.$$('div.item')[1],
+ // not in Firefox
+ // toElement: cy.$$('div.item')[1],
+ type: 'pointerenter',
+ view: cy.state('window'),
+ // which: 0,
+ x: 492,
+ y: 9,
+ })
+ }).as('pointerenter')
+
+ cy.get('div.item').eq(0)
+ .should(($el) => {
+ $el[0].addEventListener('mouseout', mouseout)
+ $el[0].addEventListener('mouseleave', mouseleave)
+ $el[0].addEventListener('pointerout', pointerout)
+ $el[0].addEventListener('pointerleave', pointerleave)
+ })
+ .click()
+ .then(() => {
+ expect(cy.state('mouseCoords')).ok
+ })
+
+ cy.get('div.item').eq(1).should(($el) => {
+ $el[0].addEventListener('mouseover', mouseover)
+ $el[0].addEventListener('mouseenter', mouseenter)
+ $el[0].addEventListener('pointerover', pointerover)
+ $el[0].addEventListener('pointerenter', pointerenter)
+ })
+ .click()
+
+ Cypress.Promise.delay(5000)
+ .then(() => {
+ expect(mouseout).to.be.calledOnce
+ expect(mouseleave).to.be.calledOnce
+ expect(pointerout).to.be.calledOnce
+ expect(pointerleave).to.be.calledOnce
+ expect(mouseover).to.be.calledOnce
+ expect(mouseover).to.be.calledOnce
+ expect(mouseenter).to.be.calledOnce
+ expect(pointerover).to.be.calledOnce
+ expect(pointerenter).to.be.calledOnce
+ })
+ })
+ })
+ })
+
+ describe('more mouse state', () => {
+ beforeEach(() => {
+ cy.visit('http://localhost:3500/fixtures/issue-2956.html')
+ })
+
+ describe('mouseleave mouseenter animations', () => {
+ it('sends mouseenter/mouseleave event', () => {
+ cy.get('#outer').click()
+ cy.get('#inner').should('be.visible')
+ cy.get('body').click()
+ cy.get('#inner').should('not.be.visible')
+ })
+
+ it('will respect changes to dom in event handlers', () => {
+
+ allMouseEvents.forEach((evt) => {
+ cy.$$('#sq4').on(evt, cy.spy().as(`sq4:${evt}`))
+ cy.$$('#outer').on(evt, cy.spy().as(`outer:${evt}`))
+ cy.$$('input:first').on(evt, cy.spy().as(`input:${evt}`))
+ })
+
+ cy.get('#sq4').click()
+ cy.get('#outer').click({ timeout: 200 })
+
+ cy.getAll('@sq4:mouseover @sq4:mousedown @sq4:mouseup @sq4:click').each((spy) => {
+ expect(spy).to.be.calledTwice
+ })
+
+ cy.getAll('@sq4:mouseout').each((spy) => {
+ expect(spy).to.be.calledOnce
+ })
+
+ cy.getAll('@outer:mousedown @outer:mouseup @outer:click').each((spy) => {
+ expect(spy).to.not.be.called
+ })
+
+ cy.getAll('@outer:mouseover @outer:mouseout').each((spy) => {
+ expect(spy).to.be.calledOnce
+ })
+
+ cy.get('input:first').click().should('not.have.focus')
+
+ cy.getAll('@input:mouseover @input:mouseout').each((spy) => {
+ expect(spy).to.be.calledOnce
+ })
+
+ cy.getAll('@input:mousedown @input:mouseup @input:click').each((spy) => {
+ expect(spy).to.not.be.called
+ })
+
+ })
+ // it('will continue to send mouseleave events', function (done) {
+ // cy.once('fail', (err) => {
+ // expect(err.message).to.contain('is being covered')
+ // done()
+ // })
+
+ // cy.get('#sq4').click()
+ // cy.timeout(500)
+ // cy.get('#outer').click()
+ // cy.get('input:last').click()//.click({ timeout: 200 })
+ // })
+ })
+
+ it('handles disabled attr', () => {
+ const btn = cy.$$(/*html*/'
').appendTo(cy.$$('body'))
+
+ allMouseEvents.forEach((eventName) => {
+ btn.on(eventName, cy.stub().as(eventName))
+ })
+
+ btn.on('pointerover', () => {
+ btn.attr('disabled', true)
+ })
+
+ cy.get('#btn').click()
+
+ cy.getAll('@pointerover @pointerenter @pointerdown @pointerup').each((stub) => {
+ expect(stub).to.be.calledOnce
+ })
+
+ cy.getAll('@mouseover @mouseenter @mousedown @mouseup @click').each((stub) => {
+ expect(stub).to.not.be.called
+ })
+
+ })
+
+ it('handles disabled attr added on mousedown', () => {
+ const btn = cy.$$(/*html*/'
').appendTo(cy.$$('body'))
+
+ allMouseEvents.forEach((eventName) => {
+ btn.on(eventName, cy.stub().as(eventName))
+ })
+
+ btn.on('mousedown', () => {
+ btn.attr('disabled', true)
+ })
+
+ cy.get('#btn').click()
+
+ cy.getAll('@pointerdown @mousedown @pointerup').each((stub) => {
+ expect(stub).to.be.calledOnce
+ })
+
+ cy.getAll('@mouseup @click').each((stub) => {
+ expect(stub).to.not.be.calledOnce
+ })
+ })
+
+ it('can click new element after mousemove sequence', () => {
+ const btn = cy.$$(/*html*/'
').css({ display: 'block' }).appendTo(cy.$$('body'))
+ const cover = cy.$$(/*html*/'
').css({
+ backgroundColor: 'blue',
+ position: 'relative',
+ height: '50px',
+ width: '300px',
+ top: '-30px',
+ }).appendTo(btn.parent())
+
+ cover.on('mousemove', () => {
+ cover.hide()
+ })
+
+ allMouseEvents.forEach((eventName) => {
+ btn.on(eventName, cy.stub().as(eventName))
+ cover.on(eventName, cy.stub().as(`cover:${eventName}`))
+ })
+
+ cy.get('#cover').click()
+
+ cy.getAll('@cover:pointerdown @cover:mousedown @cover:pointerup @cover:mouseup @cover:click').each((stub) => {
+ expect(stub).to.not.be.called
+ })
+
+ // should we send mouseover to newly hovered els?
+ // cy.getAll('@mouseover').each((stub) => {
+ // expect(stub).to.be.calledOnce
+ // })
+
+ cy.getAll('@pointerdown @mousedown @mouseup @pointerup @click').each((stub) => {
+ expect(stub).to.be.calledOnce
+ })
+
+ })
+
+ it('can click new element after mousemove sequence [disabled]', () => {
+ const btn = cy.$$(/*html*/'
').css({ display: 'block' }).appendTo(cy.$$('body'))
+ const cover = cy.$$(/*html*/'
').css({
+ backgroundColor: 'blue',
+ position: 'relative',
+ height: '50px',
+ width: '300px',
+ top: '-30px',
+ }).appendTo(btn.parent())
+
+ btn.attr('disabled', true)
+
+ cover.on('mousemove', () => {
+ cover.hide()
+ })
+
+ allMouseEvents.forEach((eventName) => {
+ btn.on(eventName, cy.stub().as(eventName))
+ })
+
+ cy.get('#cover').click()
+
+ cy.getAll('@mousedown @mouseup @click').each((stub) => {
+ expect(stub).to.not.be.called
+ })
+
+ // on disabled inputs, pointer events are still fired
+ cy.getAll('@pointerdown @pointerup').each((stub) => {
+ expect(stub).to.be.called
+ })
+
+ })
+
+ it('can target new element after mousedown sequence', () => {
+ const btn = cy.$$(/*html*/'
').css({ display: 'block' }).appendTo(cy.$$('body'))
+ const cover = cy.$$(/*html*/'
').css({
+ backgroundColor: 'blue',
+ position: 'relative',
+ height: '50px',
+ width: '300px',
+ top: '-30px',
+ }).appendTo(btn.parent())
+
+ cover.on('mousedown', () => {
+ cover.hide()
+ })
+
+ allMouseEvents.forEach((eventName) => {
+ btn.on(eventName, cy.stub().as(eventName))
+ })
+
+ btn.on('mouseup', () => {
+ btn.attr('disabled', true)
+ })
+
+ cy.get('#cover').click()
+
+ cy.getAll('@mouseup @pointerup').each((stub) => {
+ expect(stub).to.be.calledOnce
+ })
+
+ })
+
+ it('can target new element after mouseup sequence', () => {
+ const btn = cy.$$(/*html*/'
').css({ display: 'block' }).appendTo(cy.$$('body'))
+ const cover = cy.$$(/*html*/'
').css({
+ backgroundColor: 'blue',
+ position: 'relative',
+ height: '50px',
+ width: '300px',
+ top: '-30px',
+ }).appendTo(btn.parent())
+
+ cover.on('mouseup', () => {
+ cover.hide()
+ })
+
+ allMouseEvents.forEach((eventName) => {
+ btn.on(eventName, cy.stub().as(eventName))
+ cover.on(eventName, cy.stub().as(`cover:${eventName}`))
+ })
+
+ btn.on('mouseup', () => {
+ btn.attr('disabled', true)
+ })
+
+ cy.get('#cover').click()
+
+ return cy.getAll('@cover:click').each((stub) => {
+ expect(stub).to.not.be.called
+ }).then(() => {
+ return cy.getAll('@cover:pointerdown @cover:mousedown @cover:mouseup').each((stub) => {
+ expect(stub).to.be.calledOnce
+ })
+ })
+
+ })
+
+ it('responds to changes in move handlers', () => {
+ const btn = cy.$$(/*html*/'
').css({ display: 'block' }).appendTo(cy.$$('body'))
+ const cover = cy.$$(/*html*/'
').css({
+ backgroundColor: 'blue',
+ position: 'relative',
+ height: '50px',
+ width: '300px',
+ top: '-30px',
+ }).appendTo(btn.parent())
+
+ cover.on('mouseover', () => {
+ cover.hide()
+ })
+
+ allMouseEvents.forEach((eventName) => {
+ btn.on(eventName, cy.stub().as(eventName))
+
+ cover.on(eventName, cy.stub().as(`cover:${eventName}`))
+ })
+
+ cy.get('#cover').click()
+
+ cy.getAll('@cover:mousedown').each((stub) => {
+ expect(stub).to.not.be.called
+ })
+
+ cy.getAll('@pointerdown @mousedown @mouseup @pointerup @click').each((stub) => {
+ expect(stub).to.be.calledOnce
+ })
+ })
+
+ })
+
+})
+
+const attachListeners = (listenerArr) => {
+ return (els) => {
+ _.each(els, (el, elName) => {
+ return listenerArr.forEach((evtName) => {
+ el.on(evtName, cy.stub().as(`${elName}:${evtName}`))
+ })
+ })
+ }
+}
+
+const attachFocusListeners = attachListeners(focusEvents)
+const attachMouseClickListeners = attachListeners(mouseClickEvents)
+const attachMouseHoverListeners = attachListeners(mouseHoverEvents)
+const attachMouseDblclickListeners = attachListeners(['dblclick'])
+const attachContextmenuListeners = attachListeners(['contextmenu'])
+
+const getAllFn = (...aliases) => {
+ if (aliases.length > 1) {
+ return getAllFn((_.isArray(aliases[1]) ? aliases[1] : aliases[1].split(' ')).map((alias) => `@${aliases[0]}:${alias}`).join(' '))
+ }
+
+ return Cypress.Promise.all(
+ aliases[0].split(' ').map((alias) => {
+ return cy.now('get', alias)
+ })
+ )
+}
+
+Cypress.Commands.add('getAll', getAllFn)
+
+const _wrapLogFalse = (obj) => cy.wrap(obj, { log: false })
+const shouldBeCalled = (stub) => _wrapLogFalse(stub).should('be.called')
+const shouldBeCalledOnce = (stub) => _wrapLogFalse(stub).should('be.calledOnce')
+const shouldBeCalledNth = (num) => (stub) => _wrapLogFalse(stub).should('have.callCount', num)
+const shouldNotBeCalled = (stub) => _wrapLogFalse(stub).should('not.be.called')
From 2adf0135434f3754d1d06fccd0b6fef654262352 Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Thu, 18 Jul 2019 11:42:17 -0400
Subject: [PATCH 05/36] temp 07/18/19 [skip ci] type_spec minor changes
---
.../integration/commands/actions/type_spec.js | 44 +++++--------------
1 file changed, 10 insertions(+), 34 deletions(-)
diff --git a/packages/driver/test/cypress/integration/commands/actions/type_spec.js b/packages/driver/test/cypress/integration/commands/actions/type_spec.js
index a3f4d05101c8..86faccda25b5 100644
--- a/packages/driver/test/cypress/integration/commands/actions/type_spec.js
+++ b/packages/driver/test/cypress/integration/commands/actions/type_spec.js
@@ -1,6 +1,6 @@
+/* eslint prefer-arrow-callback: "error", arrow-body-style: ["error", "always"] */
const $ = Cypress.$.bind(Cypress)
const { _ } = Cypress
-const { Keyboard } = Cypress
const { Promise } = Cypress
const $selection = require('../../../../../src/dom/selection')
@@ -4088,17 +4088,17 @@ describe('src/cy/commands/actions/type', () => {
})
it('has a table of keys', () => {
- cy.get(':text:first').type('{cmd}{option}foo{enter}b{leftarrow}{del}{enter}').then(function () {
- const table = this.lastLog.invoke('consoleProps').table()
+ cy.get(':text:first').type('{cmd}{option}foo{enter}b{leftarrow}{del}{enter}')
+ .then(function () {
+ const table = this.lastLog.invoke('consoleProps').table[3]()
// eslint-disable-next-line
console.table(table.data, table.columns)
-
expect(table.columns).to.deep.eq([
'typed', 'which', 'keydown', 'keypress', 'textInput', 'input', 'keyup', 'change', 'modifiers',
])
- expect(table.name).to.eq('Key Events Table')
+ expect(table.name).to.eq('Keyboard Events')
const expectedTable = {
1: { typed: '
', which: 91, keydown: true, modifiers: 'meta' },
2: { typed: '
', which: 18, keydown: true, modifiers: 'alt, meta' },
@@ -4112,9 +4112,7 @@ describe('src/cy/commands/actions/type', () => {
10: { typed: '{enter}', which: 13, keydown: true, keypress: true, keyup: true, modifiers: 'alt, meta' },
}
- return [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((i) => {
- expect(table.data[i]).to.deep.eq(expectedTable[i])
- })
+ expect(table.data).to.deep.eq(expectedTable)
})
})
@@ -4125,7 +4123,7 @@ describe('src/cy/commands/actions/type', () => {
it('has no modifiers when there are none activated', () => {
cy.get(':text:first').type('f').then(function () {
- const table = this.lastLog.invoke('consoleProps').table()
+ const table = this.lastLog.invoke('consoleProps').table[3]()
expect(table.data).to.deep.eq({
1: { typed: 'f', which: 70, keydown: true, keypress: true, textInput: true, input: true, keyup: true },
@@ -4139,7 +4137,7 @@ describe('src/cy/commands/actions/type', () => {
})
cy.get(':text:first').type('f').then(function () {
- const table = this.lastLog.invoke('consoleProps').table()
+ const table = this.lastLog.invoke('consoleProps').table[3]()
// eslint-disable-next-line
console.table(table.data, table.columns)
@@ -4316,7 +4314,7 @@ describe('src/cy/commands/actions/type', () => {
cy.on('fail', (err) => {
expect(this.logs.length).to.eq(2)
- const allChars = _.keys(Keyboard.specialChars).concat(_.keys(Keyboard.modifierChars)).join(', ')
+ const allChars = _.keys(cy.internal.keyboard.specialChars).concat(_.keys(cy.internal.keyboard.modifierChars)).join(', ')
expect(err.message).to.eq(`Special character sequence: '{bar}' is not recognized. Available sequences are: ${allChars}`)
@@ -5019,29 +5017,7 @@ describe('src/cy/commands/actions/type', () => {
const logs = []
cy.on('log:added', (attrs, log) => {
- if (log.get('name') === 'clear') {
- logs.push(log)
- }
- })
-
- cy.get('input').invoke('slice', 0, 2).clear().then(() => {
- _.each(logs, (log) => {
- expect(log.get('state')).to.eq('passed')
-
- expect(log.get('ended')).to.be.true
- })
- })
- })
-
- it('snapshots after clicking', () => {
- cy.get('input:first').clear().then(function ($input) {
- const { lastLog } = this
-
- expect(lastLog.get('snapshots').length).to.eq(1)
-
- expect(lastLog.get('snapshots')[0]).to.be.an('object')
- })
- })
+ ifa
it('logs deltaOptions', () => {
cy.get('input:first').clear({ force: true, timeout: 1000 }).then(function () {
From 57b0e0ee77edf7538f51d0d59925aa38a446b2ad Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Fri, 19 Jul 2019 13:15:05 -0400
Subject: [PATCH 06/36] temp 07/19/19 [skip ci]
---
.eslintrc | 7 +----
package.json | 2 +-
.../commands/actions/click_spec.js | 2 +-
.../integration/commands/actions/type_spec.js | 26 +++++++++++++++++--
4 files changed, 27 insertions(+), 10 deletions(-)
diff --git a/.eslintrc b/.eslintrc
index aabf1ee70b7c..22f00333db5e 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -5,10 +5,5 @@
"extends": [
"plugin:@cypress/dev/general"
],
- "rules": {
- "no-unused-vars": [
- "error",
- {}
- ]
- }
+ "rules": {}
}
diff --git a/package.json b/package.json
index 9b3b7614b483..014e6dc44333 100644
--- a/package.json
+++ b/package.json
@@ -66,7 +66,7 @@
"@cypress/bumpercar": "2.0.9",
"@cypress/commit-message-install": "2.7.0",
"@cypress/env-or-json-file": "2.0.0",
- "@cypress/eslint-plugin-dev": "3.5.0",
+ "@cypress/eslint-plugin-dev": "3.6.0",
"@cypress/eslint-plugin-json": "3.2.1",
"@cypress/github-commit-status-check": "1.5.0",
"@cypress/npm-run-all": "4.0.5",
diff --git a/packages/driver/test/cypress/integration/commands/actions/click_spec.js b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
index 028494e1dc80..f5bfa0ae41f8 100644
--- a/packages/driver/test/cypress/integration/commands/actions/click_spec.js
+++ b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
@@ -188,7 +188,7 @@ describe('src/cy/commands/actions/click', () => {
})
cy.get('#button').click().then(() => {
- expect(events).to.deep.eq(['mousedown', 'mouseup', 'click'])
+ expect(events).to.deep.eq(['pointerdown', 'mousedown', 'pointerup', 'mouseup', 'click'])
})
})
diff --git a/packages/driver/test/cypress/integration/commands/actions/type_spec.js b/packages/driver/test/cypress/integration/commands/actions/type_spec.js
index 86faccda25b5..99003fdaef30 100644
--- a/packages/driver/test/cypress/integration/commands/actions/type_spec.js
+++ b/packages/driver/test/cypress/integration/commands/actions/type_spec.js
@@ -3429,7 +3429,7 @@ describe('src/cy/commands/actions/type', () => {
it('enter and \\n should act the same for [contenteditable]', () => {
// non breaking white space
const cleanseText = (text) => {
- text.split('\u00a0').join(' ')
+ return text.split('\u00a0').join(' ')
}
const expectMatchInnerText = ($el, innerText) => {
@@ -5017,7 +5017,29 @@ describe('src/cy/commands/actions/type', () => {
const logs = []
cy.on('log:added', (attrs, log) => {
- ifa
+ if (log.get('name') === 'clear') {
+ return logs.push(log)
+ }
+ })
+
+ cy.get('input').invoke('slice', 0, 2).clear().then(() => {
+ return _.each(logs, (log) => {
+ expect(log.get('state')).to.eq('passed')
+
+ expect(log.get('ended')).to.be.true
+ })
+ })
+ })
+
+ it('snapshots after clicking', () => {
+ cy.get('input:first').clear().then(function () {
+ const { lastLog } = this
+
+ expect(lastLog.get('snapshots').length).to.eq(1)
+
+ expect(lastLog.get('snapshots')[0]).to.be.an('object')
+ })
+ })
it('logs deltaOptions', () => {
cy.get('input:first').clear({ force: true, timeout: 1000 }).then(function () {
From fa2fbcfe7cc648faccc3cd63d45c4a77bd39b78b Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Mon, 22 Jul 2019 17:55:32 -0400
Subject: [PATCH 07/36] temp 07/22/19 [skip ci]
---
.../test/cypress/integration/e2e/react-dnd.js | 261 ++++++++++++++++++
packages/web-config/webpack.config.base.ts | 2 +-
2 files changed, 262 insertions(+), 1 deletion(-)
create mode 100644 packages/driver/test/cypress/integration/e2e/react-dnd.js
diff --git a/packages/driver/test/cypress/integration/e2e/react-dnd.js b/packages/driver/test/cypress/integration/e2e/react-dnd.js
new file mode 100644
index 000000000000..71f2415b6b9d
--- /dev/null
+++ b/packages/driver/test/cypress/integration/e2e/react-dnd.js
@@ -0,0 +1,261 @@
+///
+const { _ } = Cypress
+
+describe('react-drag-and-drop', () => {
+ it('can dnd', () => {
+ cy.visit('http://react-dnd.github.io/react-dnd/examples/dustbin/single-target')
+
+ const onAlert = cy.stub()
+
+ cy.on('window:alert', onAlert)
+ cy.window().then((win) => {
+ win.addEventListener('selectend', () => {
+ debugger
+ })
+
+ mouseevents.forEach((evtName) => {
+ win.document.addEventListener(evtName, () => console.log(evtName))
+ })
+ })
+
+ cy.contains('Using Hooks').click()
+ // cy.contains('Using Decorators').click()
+
+ let toOpts
+
+ cy.contains('div', 'Drag a box here').then(($el) => {
+ const rect = $el[0].getBoundingClientRect()
+
+ toOpts = { clientX: rect.left, clientY: rect.top, log: true }
+
+ }).then(() => {
+
+ cy.contains('div', 'Paper')
+ .trigger('mouseover')
+ .trigger('mousedown')
+ .trigger('dragstart')
+ .trigger('selectstart')
+ .trigger('mousemove', toOpts)
+ .trigger('drag', toOpts)
+ .trigger('dragover', toOpts)
+ .trigger('mouseup', toOpts)
+ .trigger('dragend')
+ .trigger('selectend')
+ .trigger('dragEnd', toOpts)
+ .trigger('drop', toOpts)
+ })
+
+ cy.then(() => {
+
+ expect(onAlert).calledOnce
+ })
+
+ // cy.wait(1000)
+ // cy.window().then((win) => {
+ // // win.addEventListener('mousemove', () => {
+ // // debugger
+ // // })
+ // mouseevents.forEach((evtName) => {
+ // win.addEventListener(evtName, () => console.log(evtName))
+ // })
+ // })
+
+ // cy.contains('div', 'Drag a box here').then(($toEl) => {
+
+ // cy.contains('div', 'Paper').dragTo($toEl)
+ // })
+
+ })
+
+})
+
+const mouseevents = ['dragstart', 'dragend', 'selectstart', 'selectend', 'pointerdown', 'pointerover', 'pointerdown', 'pointerup', 'mousedown', 'mouseover', 'mouseup']
+
+ // const getCoords = ($el) => {
+ // const domRect = $el[0].getBoundingClientRect()
+ // const coords = { x: domRect.left + (domRect.width / 2 || 0), y: domRect.top + (domRect.height / 2 || 0) }
+
+ // return coords
+ // }
+
+ // const dragTo = (subject, to, opts) => {
+
+ // opts = Cypress._.defaults(opts, {
+ // // delay inbetween steps
+ // delay: 0,
+ // // interpolation between coords
+ // steps: 0,
+ // // >=10 steps
+ // smooth: false,
+ // })
+
+ // if (opts.smooth) {
+ // opts.steps = Math.max(opts.steps, 10)
+ // }
+
+ // const win = subject[0].ownerDocument.defaultView
+
+ // const elFromCoords = (coords) => win.document.elementFromPoint(coords.x, coords.y)
+ // const winMouseEvent = win.TouchEvent
+ // const winPointerEvent = win.PointerEvent
+
+ // const send = (type, coords, el, opts) => {
+
+ // el = el || elFromCoords(coords)
+
+ // opts = _.defaults({}, opts, {
+ // clientX: coords.x,
+ // clientY: coords.y,
+ // pageX: coords.x,
+ // pageY: coords.y,
+ // x: coords.x,
+ // y: coords.y,
+ // buttons: 1,
+ // button: 0,
+ // })
+
+ // if (type.startsWith('mouse')) {
+
+ // // console.log('mouseevent')
+ // el.dispatchEvent(
+ // new winMouseEvent(type, Object.assign({}, opts, { bubbles: true, cancelable: true }))
+ // )
+
+ // return
+ // }
+
+ // // console.log('pointerevent')
+ // el.dispatchEvent(
+ // new winPointerEvent(type, Object.assign({}, opts, { bubbles: true, cancelable: true }))
+ // )
+ // }
+
+ // const toSel = to
+
+ // function drag (from, to, steps = 1) {
+
+ // const fromEl = elFromCoords(from)
+
+ // const _log = Cypress.log({
+ // $el: fromEl,
+ // name: 'drag to',
+ // message: toSel,
+ // })
+
+ // _log.snapshot('before', { next: 'after', at: 0 })
+
+ // _log.set({ coords: to })
+
+ // send('mouseover', from, fromEl)
+ // send('mousemove', from, fromEl)
+ // send('mousedown', from, fromEl)
+
+ // cy.then(() => {
+ // return Cypress.Promise.try(() => {
+
+ // if (steps > 0) {
+
+ // const dx = (to.x - from.x) / steps
+ // const dy = (to.y - from.y) / steps
+
+ // return Cypress.Promise.map(Array(steps).fill(), (v, i) => {
+ // i = steps - 1 - i
+
+ // let _to = {
+ // x: from.x + dx * (i),
+ // y: from.y + dy * (i),
+ // }
+
+ // send('mousemove', _to, fromEl)
+
+ // return Cypress.Promise.delay(opts.delay)
+
+ // }, { concurrency: 1 })
+ // }
+ // })
+ // .then(() => {
+
+ // send('mousemove', to, fromEl)
+ // send('mouseover', to)
+ // send('mousemove', to)
+ // send('mouseup', to)
+ // _log.snapshot('after', { at: 1 }).end()
+
+ // })
+
+ // })
+
+ // }
+
+ // const $el = subject
+ // const fromCoords = getCoords($el)
+ // let toEl = to
+
+ // if (_.isString(to)) {
+ // toEl = cy.$$(to)
+ // }
+
+ // const toCoords = getCoords(toEl)
+
+ // drag(fromCoords, toCoords, opts.steps)
+ // }
+
+ // Cypress.Commands.addAll(
+ // { prevSubject: 'element' },
+ // {
+ // dragTo,
+ // }
+ // )
+
+;/*js*/`
+e: DragEvent
+altKey: false
+bubbles: true
+button: 0
+buttons: 1
+cancelBubble: false
+cancelable: true
+clientX: 655
+clientY: 611
+composed: true
+ctrlKey: false
+currentTarget: div
+dataTransfer: DataTransfer
+dropEffect: "none"
+effectAllowed: "uninitialized"
+files: FileList {length: 0}
+items: DataTransferItemList {length: 0}
+types: []
+__proto__: DataTransfer
+defaultPrevented: false
+detail: 0
+eventPhase: 2
+fromElement: null
+isTrusted: true
+layerX: 275
+layerY: 25
+metaKey: false
+movementX: 0
+movementY: 0
+offsetX: 36
+offsetY: 25
+pageX: 655
+pageY: 611
+path: (19) [div, div, div, div, div#react-tabs-37.react-tabs__tab-panel.react-tabs__tab-panel--selected, div.react-tabs, div, div.doc__HtmlContainer-sc-1s0952h-0.gswDSM, div.doc__Container-sc-1s0952h-1.jqhLew, div.layout__ChildrenContainer-sc-16juec5-1.iSfRoO, div.pagebody__SidebarContent-ivj5mq-2.hrlsNx, div.pagebody__Container-ivj5mq-0.hcXHj, div.layout__ContentContainer-sc-16juec5-2.lbwisx, div#gatsby-focus-wrapper, div#___gatsby, body, html, document, Window]
+relatedTarget: null
+returnValue: true
+screenX: 1367
+screenY: 444
+shiftKey: false
+sourceCapabilities: InputDeviceCapabilities {firesTouchEvents: false}
+srcElement: div
+target: div
+timeStamp: 4224.634999991395
+toElement: div
+type: "dragstart"
+view: Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
+which: 1
+x: 655
+y: 611
+__proto__: DragEvent
+`
diff --git a/packages/web-config/webpack.config.base.ts b/packages/web-config/webpack.config.base.ts
index 4b8d8c378ba6..21bb01d1bfda 100644
--- a/packages/web-config/webpack.config.base.ts
+++ b/packages/web-config/webpack.config.base.ts
@@ -177,7 +177,7 @@ const config: webpack.Configuration = {
})]
),
- ...(liveReloadEnabled ? [new LiveReloadPlugin({ appendScriptTag: 'true', port: 0 })] : []),
+ ...(liveReloadEnabled ? [new LiveReloadPlugin({ appendScriptTag: 'true', port: 0, hostname: 'localhost' })] : []),
],
cache: true,
From c8424ef87d4bd2cb5b125ef4b6e913d4041c0e8b Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Mon, 22 Jul 2019 17:57:19 -0400
Subject: [PATCH 08/36] temp 07/22/19 [skip ci]
---
packages/web-config/webpack.config.base.ts | 2 --
1 file changed, 2 deletions(-)
diff --git a/packages/web-config/webpack.config.base.ts b/packages/web-config/webpack.config.base.ts
index 21bb01d1bfda..b8af4480ee3d 100644
--- a/packages/web-config/webpack.config.base.ts
+++ b/packages/web-config/webpack.config.base.ts
@@ -179,9 +179,7 @@ const config: webpack.Configuration = {
...(liveReloadEnabled ? [new LiveReloadPlugin({ appendScriptTag: 'true', port: 0, hostname: 'localhost' })] : []),
],
-
cache: true,
-
}
export default config
From d7442354754882b6e0c31022d31cca601425c370 Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Fri, 26 Jul 2019 13:39:42 -0400
Subject: [PATCH 09/36] remove accidental spec, use beforeEach visit in
click_spec
---
.../commands/actions/click_spec.js | 14 +-
.../test/cypress/integration/e2e/react-dnd.js | 261 ------------------
2 files changed, 1 insertion(+), 274 deletions(-)
delete mode 100644 packages/driver/test/cypress/integration/e2e/react-dnd.js
diff --git a/packages/driver/test/cypress/integration/commands/actions/click_spec.js b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
index f5bfa0ae41f8..a1cce3256396 100644
--- a/packages/driver/test/cypress/integration/commands/actions/click_spec.js
+++ b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
@@ -27,21 +27,9 @@ const focusEvents = ['focus', 'focusin']
const allMouseEvents = [...mouseClickEvents, ...mouseHoverEvents, ...focusEvents]
describe('src/cy/commands/actions/click', () => {
- before(() => {
+ beforeEach(() => {
cy
.visit('/fixtures/dom.html')
- .then(function (win) {
- this.body = win.document.body.outerHTML
- })
- })
-
- beforeEach(function () {
- const doc = cy.state('document')
-
- $(doc.body).empty().html(this.body)
- // scroll back to top of page before every test
- // since this is a side-effect
- doc.documentElement.scrollTop = 0
})
context('#click', () => {
diff --git a/packages/driver/test/cypress/integration/e2e/react-dnd.js b/packages/driver/test/cypress/integration/e2e/react-dnd.js
deleted file mode 100644
index 71f2415b6b9d..000000000000
--- a/packages/driver/test/cypress/integration/e2e/react-dnd.js
+++ /dev/null
@@ -1,261 +0,0 @@
-///
-const { _ } = Cypress
-
-describe('react-drag-and-drop', () => {
- it('can dnd', () => {
- cy.visit('http://react-dnd.github.io/react-dnd/examples/dustbin/single-target')
-
- const onAlert = cy.stub()
-
- cy.on('window:alert', onAlert)
- cy.window().then((win) => {
- win.addEventListener('selectend', () => {
- debugger
- })
-
- mouseevents.forEach((evtName) => {
- win.document.addEventListener(evtName, () => console.log(evtName))
- })
- })
-
- cy.contains('Using Hooks').click()
- // cy.contains('Using Decorators').click()
-
- let toOpts
-
- cy.contains('div', 'Drag a box here').then(($el) => {
- const rect = $el[0].getBoundingClientRect()
-
- toOpts = { clientX: rect.left, clientY: rect.top, log: true }
-
- }).then(() => {
-
- cy.contains('div', 'Paper')
- .trigger('mouseover')
- .trigger('mousedown')
- .trigger('dragstart')
- .trigger('selectstart')
- .trigger('mousemove', toOpts)
- .trigger('drag', toOpts)
- .trigger('dragover', toOpts)
- .trigger('mouseup', toOpts)
- .trigger('dragend')
- .trigger('selectend')
- .trigger('dragEnd', toOpts)
- .trigger('drop', toOpts)
- })
-
- cy.then(() => {
-
- expect(onAlert).calledOnce
- })
-
- // cy.wait(1000)
- // cy.window().then((win) => {
- // // win.addEventListener('mousemove', () => {
- // // debugger
- // // })
- // mouseevents.forEach((evtName) => {
- // win.addEventListener(evtName, () => console.log(evtName))
- // })
- // })
-
- // cy.contains('div', 'Drag a box here').then(($toEl) => {
-
- // cy.contains('div', 'Paper').dragTo($toEl)
- // })
-
- })
-
-})
-
-const mouseevents = ['dragstart', 'dragend', 'selectstart', 'selectend', 'pointerdown', 'pointerover', 'pointerdown', 'pointerup', 'mousedown', 'mouseover', 'mouseup']
-
- // const getCoords = ($el) => {
- // const domRect = $el[0].getBoundingClientRect()
- // const coords = { x: domRect.left + (domRect.width / 2 || 0), y: domRect.top + (domRect.height / 2 || 0) }
-
- // return coords
- // }
-
- // const dragTo = (subject, to, opts) => {
-
- // opts = Cypress._.defaults(opts, {
- // // delay inbetween steps
- // delay: 0,
- // // interpolation between coords
- // steps: 0,
- // // >=10 steps
- // smooth: false,
- // })
-
- // if (opts.smooth) {
- // opts.steps = Math.max(opts.steps, 10)
- // }
-
- // const win = subject[0].ownerDocument.defaultView
-
- // const elFromCoords = (coords) => win.document.elementFromPoint(coords.x, coords.y)
- // const winMouseEvent = win.TouchEvent
- // const winPointerEvent = win.PointerEvent
-
- // const send = (type, coords, el, opts) => {
-
- // el = el || elFromCoords(coords)
-
- // opts = _.defaults({}, opts, {
- // clientX: coords.x,
- // clientY: coords.y,
- // pageX: coords.x,
- // pageY: coords.y,
- // x: coords.x,
- // y: coords.y,
- // buttons: 1,
- // button: 0,
- // })
-
- // if (type.startsWith('mouse')) {
-
- // // console.log('mouseevent')
- // el.dispatchEvent(
- // new winMouseEvent(type, Object.assign({}, opts, { bubbles: true, cancelable: true }))
- // )
-
- // return
- // }
-
- // // console.log('pointerevent')
- // el.dispatchEvent(
- // new winPointerEvent(type, Object.assign({}, opts, { bubbles: true, cancelable: true }))
- // )
- // }
-
- // const toSel = to
-
- // function drag (from, to, steps = 1) {
-
- // const fromEl = elFromCoords(from)
-
- // const _log = Cypress.log({
- // $el: fromEl,
- // name: 'drag to',
- // message: toSel,
- // })
-
- // _log.snapshot('before', { next: 'after', at: 0 })
-
- // _log.set({ coords: to })
-
- // send('mouseover', from, fromEl)
- // send('mousemove', from, fromEl)
- // send('mousedown', from, fromEl)
-
- // cy.then(() => {
- // return Cypress.Promise.try(() => {
-
- // if (steps > 0) {
-
- // const dx = (to.x - from.x) / steps
- // const dy = (to.y - from.y) / steps
-
- // return Cypress.Promise.map(Array(steps).fill(), (v, i) => {
- // i = steps - 1 - i
-
- // let _to = {
- // x: from.x + dx * (i),
- // y: from.y + dy * (i),
- // }
-
- // send('mousemove', _to, fromEl)
-
- // return Cypress.Promise.delay(opts.delay)
-
- // }, { concurrency: 1 })
- // }
- // })
- // .then(() => {
-
- // send('mousemove', to, fromEl)
- // send('mouseover', to)
- // send('mousemove', to)
- // send('mouseup', to)
- // _log.snapshot('after', { at: 1 }).end()
-
- // })
-
- // })
-
- // }
-
- // const $el = subject
- // const fromCoords = getCoords($el)
- // let toEl = to
-
- // if (_.isString(to)) {
- // toEl = cy.$$(to)
- // }
-
- // const toCoords = getCoords(toEl)
-
- // drag(fromCoords, toCoords, opts.steps)
- // }
-
- // Cypress.Commands.addAll(
- // { prevSubject: 'element' },
- // {
- // dragTo,
- // }
- // )
-
-;/*js*/`
-e: DragEvent
-altKey: false
-bubbles: true
-button: 0
-buttons: 1
-cancelBubble: false
-cancelable: true
-clientX: 655
-clientY: 611
-composed: true
-ctrlKey: false
-currentTarget: div
-dataTransfer: DataTransfer
-dropEffect: "none"
-effectAllowed: "uninitialized"
-files: FileList {length: 0}
-items: DataTransferItemList {length: 0}
-types: []
-__proto__: DataTransfer
-defaultPrevented: false
-detail: 0
-eventPhase: 2
-fromElement: null
-isTrusted: true
-layerX: 275
-layerY: 25
-metaKey: false
-movementX: 0
-movementY: 0
-offsetX: 36
-offsetY: 25
-pageX: 655
-pageY: 611
-path: (19) [div, div, div, div, div#react-tabs-37.react-tabs__tab-panel.react-tabs__tab-panel--selected, div.react-tabs, div, div.doc__HtmlContainer-sc-1s0952h-0.gswDSM, div.doc__Container-sc-1s0952h-1.jqhLew, div.layout__ChildrenContainer-sc-16juec5-1.iSfRoO, div.pagebody__SidebarContent-ivj5mq-2.hrlsNx, div.pagebody__Container-ivj5mq-0.hcXHj, div.layout__ContentContainer-sc-16juec5-2.lbwisx, div#gatsby-focus-wrapper, div#___gatsby, body, html, document, Window]
-relatedTarget: null
-returnValue: true
-screenX: 1367
-screenY: 444
-shiftKey: false
-sourceCapabilities: InputDeviceCapabilities {firesTouchEvents: false}
-srcElement: div
-target: div
-timeStamp: 4224.634999991395
-toElement: div
-type: "dragstart"
-view: Window {postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, parent: Window, …}
-which: 1
-x: 655
-y: 611
-__proto__: DragEvent
-`
From 994b74fc5f8c111a21ed6b60fabcaf41edd08933 Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Mon, 29 Jul 2019 13:23:34 -0400
Subject: [PATCH 10/36] add tests for cy.trigger changes
---
.../commands/actions/trigger_spec.coffee | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/packages/driver/test/cypress/integration/commands/actions/trigger_spec.coffee b/packages/driver/test/cypress/integration/commands/actions/trigger_spec.coffee
index 9ba929bcd7be..a1776ee79aaf 100644
--- a/packages/driver/test/cypress/integration/commands/actions/trigger_spec.coffee
+++ b/packages/driver/test/cypress/integration/commands/actions/trigger_spec.coffee
@@ -112,6 +112,20 @@ describe "src/cy/commands/actions/trigger", ->
cy.get("#scrolledBtn").trigger("mouseover")
+ it "records correct pageX and pageY el scrolled", (done) ->
+ $btn = $("").appendTo cy.$$("body")
+
+ win = cy.state("window")
+
+ $btn.on "mouseover", (e) =>
+ { fromViewport } = Cypress.dom.getElementCoordinatesByPosition($btn)
+
+ expect(e.pageX).to.be.closeTo(win.pageXOffset + e.clientX, 1)
+ expect(e.pageY).to.be.closeTo(win.pageYOffset + e.clientY, 1)
+ done()
+
+ cy.get("#scrolledBtn").trigger("mouseover")
+
it "does not change the subject", ->
$input = cy.$$("input:first")
@@ -788,3 +802,5 @@ describe "src/cy/commands/actions/trigger", ->
expect(eventOptions.clientY).to.be.be.a("number")
expect(eventOptions.pageX).to.be.be.a("number")
expect(eventOptions.pageY).to.be.be.a("number")
+ expect(eventOptions.screenX).to.be.be.a("number").and.eq(eventOptions.clientX)
+ expect(eventOptions.screenY).to.be.be.a("number").and.eq(eventOptions.clientY)
From 76f830729e34142fbe2edce62364d2f3ad07116b Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Mon, 29 Jul 2019 16:18:28 -0400
Subject: [PATCH 11/36] re-run build
From 5a123bf94dcf39b0bddf803a0b9e96e5867d5fde Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Thu, 8 Aug 2019 13:54:55 -0400
Subject: [PATCH 12/36] add typedefs
---
cli/types/index.d.ts | 9 ++++++++-
1 file changed, 8 insertions(+), 1 deletion(-)
diff --git a/cli/types/index.d.ts b/cli/types/index.d.ts
index 540db9629ace..dcf0f812cd94 100644
--- a/cli/types/index.d.ts
+++ b/cli/types/index.d.ts
@@ -616,7 +616,14 @@ declare namespace Cypress {
*
* @see https://on.cypress.io/dblclick
*/
- dblclick(options?: Partial): Chainable
+ dblclick(options?: Partial): Chainable
+
+ /**
+ * Right-click a DOM element.
+ *
+ * @see https://on.cypress.io/rightclick
+ */
+ rightclick(options?: Partial): Chainable
/**
* Set a debugger and log what the previous command yields.
From 53771dc53ed21c14e643ec212cdef07f97144708 Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Thu, 8 Aug 2019 13:59:54 -0400
Subject: [PATCH 13/36] temp 08/08/19 [skip ci]
---
cli/types/tests/chainer-examples.ts | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/cli/types/tests/chainer-examples.ts b/cli/types/tests/chainer-examples.ts
index 020b447e7e0f..4825f497c032 100644
--- a/cli/types/tests/chainer-examples.ts
+++ b/cli/types/tests/chainer-examples.ts
@@ -452,3 +452,7 @@ cy.writeFile('../file.path', '', {
flag: 'a+',
encoding: 'utf-8'
})
+
+cy.get('foo').click()
+cy.get('foo').rightclick()
+cy.get('foo').dblclick()
From 3db8363b8cc6d96237a5bd3e4d901cfff9cbc725 Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Thu, 8 Aug 2019 14:26:39 -0400
Subject: [PATCH 14/36] fix type_spec, click_spec
---
packages/driver/src/cy/commands/actions/type.js | 6 +++++-
.../test/cypress/integration/commands/actions/click_spec.js | 2 +-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/packages/driver/src/cy/commands/actions/type.js b/packages/driver/src/cy/commands/actions/type.js
index 0da258071548..732c6b02dd20 100644
--- a/packages/driver/src/cy/commands/actions/type.js
+++ b/packages/driver/src/cy/commands/actions/type.js
@@ -52,7 +52,11 @@ module.exports = function (Commands, Cypress, cy, state, config) {
let obj
table[id] = (obj = {})
- obj.modifiers = $Keyboard.modifiersToString(keyboard.getActiveModifiers())
+ const modifiers = $Keyboard.modifiersToString(keyboard.getActiveModifiers())
+
+ if (modifiers) {
+ obj.modifiers = modifiers
+ }
if (key) {
obj.typed = key
diff --git a/packages/driver/test/cypress/integration/commands/actions/click_spec.js b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
index 97b11ac2226e..d9e9dc9d7c5c 100644
--- a/packages/driver/test/cypress/integration/commands/actions/click_spec.js
+++ b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
@@ -3316,7 +3316,7 @@ describe('mouse state', () => {
doc: cy.state('document'),
}
- cy.internal.mouse.mouseMove(coords)
+ cy.devices.mouse.mouseMove(coords)
expect(mouseenter).to.be.calledOnce
expect(cy.state('mouseCoords')).ok
})
From 977a19e0811412a7b2082927d43a4015df4be469 Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Sun, 11 Aug 2019 12:54:29 -0400
Subject: [PATCH 15/36] fix console table event table logging
---
.../commands/actions/click_spec.js | 29 +++++++++++++++++++
.../integration/commands/actions/type_spec.js | 25 ++++++++++++++++
packages/driver/test/cypress/support/utils.js | 24 +++++++++++++++
packages/runner/src/lib/logger.js | 18 +++++++++---
4 files changed, 92 insertions(+), 4 deletions(-)
create mode 100644 packages/driver/test/cypress/support/utils.js
diff --git a/packages/driver/test/cypress/integration/commands/actions/click_spec.js b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
index d9e9dc9d7c5c..3128218911c3 100644
--- a/packages/driver/test/cypress/integration/commands/actions/click_spec.js
+++ b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
@@ -2,6 +2,7 @@ const $ = Cypress.$.bind(Cypress)
const { _ } = Cypress
const { Promise } = Cypress
const chaiSubset = require('chai-subset')
+const { getCommandLogWithText, findReactInstance, withMutableReporterState } = require('../../../support/utils')
chai.use(chaiSubset)
@@ -2341,6 +2342,34 @@ describe('src/cy/commands/actions/click', () => {
})
})
+ it('can print table of keys on click', () => {
+ const spyTableName = cy.spy(top.console, 'groupCollapsed')
+ const spyTableData = cy.spy(top.console, 'table')
+
+ cy.get('input:first').click()
+
+ cy.wrap(null)
+ .should(() => {
+ spyTableName.reset()
+ spyTableData.reset()
+
+ return withMutableReporterState(() => {
+
+ const commandLogEl = getCommandLogWithText('click')
+
+ const reactCommandInstance = findReactInstance(commandLogEl)
+
+ reactCommandInstance.props.appState.isRunning = false
+
+ $(commandLogEl).find('.command-wrapper').click()
+
+ expect(spyTableName).calledWith('Mouse Move Events')
+ expect(spyTableName).calledWith('Mouse Click Events')
+ expect(spyTableData).calledTwice
+ })
+ })
+ })
+
it('does not fire a click when element has been removed on mouseup', () => {
const $btn = cy.$$('button:first')
diff --git a/packages/driver/test/cypress/integration/commands/actions/type_spec.js b/packages/driver/test/cypress/integration/commands/actions/type_spec.js
index 6d8c2ede870b..bc43e5c046a9 100644
--- a/packages/driver/test/cypress/integration/commands/actions/type_spec.js
+++ b/packages/driver/test/cypress/integration/commands/actions/type_spec.js
@@ -2,6 +2,7 @@ const $ = Cypress.$.bind(Cypress)
const { _ } = Cypress
const { Promise } = Cypress
const $selection = require('../../../../../src/dom/selection')
+const { getCommandLogWithText, findReactInstance, withMutableReporterState } = require('../../../support/utils')
// trim new lines at the end of innerText
// due to changing browser versions implementing
@@ -4133,6 +4134,30 @@ describe('src/cy/commands/actions/type', () => {
})
})
+ it('can print table of keys on click', () => {
+ cy.get('input:first').type('foo')
+
+ .then(() => {
+ return withMutableReporterState(() => {
+ const spyTableName = cy.spy(top.console, 'groupCollapsed')
+ const spyTableData = cy.spy(top.console, 'table')
+
+ const commandLogEl = getCommandLogWithText('foo')
+
+ const reactCommandInstance = findReactInstance(commandLogEl)
+
+ reactCommandInstance.props.appState.isRunning = false
+
+ $(commandLogEl).find('.command-wrapper').click()
+
+ expect(spyTableName.firstCall).calledWith('Mouse Move Events')
+ expect(spyTableName.secondCall).calledWith('Mouse Click Events')
+ expect(spyTableName.thirdCall).calledWith('Keyboard Events')
+ expect(spyTableData).calledThrice
+ })
+ })
+ })
+
// table.data.forEach (item, i) ->
// expect(item).to.deep.eq(expectedTable[i])
diff --git a/packages/driver/test/cypress/support/utils.js b/packages/driver/test/cypress/support/utils.js
new file mode 100644
index 000000000000..c4b253cf3242
--- /dev/null
+++ b/packages/driver/test/cypress/support/utils.js
@@ -0,0 +1,24 @@
+export const getCommandLogWithText = (text) => cy.$$(`.command-wrapper:contains(${text}):visible`, top.document).parentsUntil('li').last().parent()[0]
+
+export const findReactInstance = function (dom) {
+ let key = Object.keys(dom).find((key) => key.startsWith('__reactInternalInstance$'))
+ let internalInstance = dom[key]
+
+ if (internalInstance == null) return null
+
+ return internalInstance._debugOwner
+ ? internalInstance._debugOwner.stateNode
+ : internalInstance.return.stateNode
+
+}
+
+export const withMutableReporterState = (fn) => {
+ top.Runner.configureMobx({ enforceActions: 'never' })
+
+ return Cypress.Promise.try(() => {
+ return fn()
+ }).then(() => {
+ top.Runner.configureMobx({ enforceActions: 'strict' })
+ })
+
+}
diff --git a/packages/runner/src/lib/logger.js b/packages/runner/src/lib/logger.js
index d39f0c711ccf..0cd7546691ad 100644
--- a/packages/runner/src/lib/logger.js
+++ b/packages/runner/src/lib/logger.js
@@ -76,8 +76,6 @@ export default {
if (!groups) return
- delete consoleProps.groups
-
return _.map(groups, (group) => {
group.items = this._formatted(group.items)
@@ -86,6 +84,18 @@ export default {
},
_logTable (consoleProps) {
+
+ if (isMultiEntryTable(consoleProps.table)) {
+ _.each(
+ _.sortBy(consoleProps.table, (val, key) => key),
+ (table) => {
+ return this._logTable({ table })
+ }
+ )
+
+ return
+ }
+
const table = this._getTable(consoleProps)
if (!table) return
@@ -104,8 +114,8 @@ export default {
if (!table) return
- delete consoleProps.table
-
return table
},
}
+
+const isMultiEntryTable = (table) => !_.isFunction(table) && !_.some(_.keys(table).map(isNaN).filter(Boolean), true)
From ef536c58ec96985cdd566236d08da8405e5cf2da Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Mon, 12 Aug 2019 11:47:16 -0400
Subject: [PATCH 16/36] fix spec utils
---
packages/driver/test/cypress/support/utils.js | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/packages/driver/test/cypress/support/utils.js b/packages/driver/test/cypress/support/utils.js
index c4b253cf3242..c6e7000e8c38 100644
--- a/packages/driver/test/cypress/support/utils.js
+++ b/packages/driver/test/cypress/support/utils.js
@@ -15,6 +15,10 @@ export const findReactInstance = function (dom) {
export const withMutableReporterState = (fn) => {
top.Runner.configureMobx({ enforceActions: 'never' })
+ const currentTestLog = findReactInstance(cy.$$('.runnable-active', top.document)[0])
+
+ currentTestLog.props.model.isOpen = true
+
return Cypress.Promise.try(() => {
return fn()
}).then(() => {
From 9202ff620cd2ef30798148d46f9b004569fbd9eb Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Wed, 14 Aug 2019 18:23:05 -0400
Subject: [PATCH 17/36] fix invalid clicking-into-iframe spec
---
.../test/cypress/integration/commands/actions/click_spec.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/packages/driver/test/cypress/integration/commands/actions/click_spec.js b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
index 3128218911c3..6901262a0f30 100644
--- a/packages/driver/test/cypress/integration/commands/actions/click_spec.js
+++ b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
@@ -743,12 +743,12 @@ describe('src/cy/commands/actions/click', () => {
cy.get('iframe')
.should(($iframe) => {
// wait for iframe to load
- expect($iframe.contents().find('body').html()).ok
+ expect($iframe.first().contents().find('body').html()).ok
})
.then(($iframe) => {
// cypress does not wrap this as a DOM element (does not wrap in jquery)
// return cy.wrap($iframe[0].contentDocument.body)
- return cy.wrap($iframe.contents().find('body'))
+ return cy.wrap($iframe.first().contents().find('body'))
})
.within(() => {
From 44eb7404241c16a3dd41197c25a97174cfb4ff05 Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Thu, 15 Aug 2019 13:10:51 -0400
Subject: [PATCH 18/36] address review, cleanup
---
packages/driver/src/cy/keyboard.js | 10 ++-----
packages/driver/src/cy/mouse.js | 43 +++++++++++++-----------------
2 files changed, 21 insertions(+), 32 deletions(-)
diff --git a/packages/driver/src/cy/keyboard.js b/packages/driver/src/cy/keyboard.js
index daef0d7b4c77..60c703916758 100644
--- a/packages/driver/src/cy/keyboard.js
+++ b/packages/driver/src/cy/keyboard.js
@@ -57,14 +57,7 @@ const fromModifierEventOptions = (eventOptions) => {
}, Boolean)
}
-const modifiersToString = (modifiers) => {
- return _.keys(
- _.pickBy(modifiers, (val) => {
- return val
- })
- )
- .join(', ')
-}
+const modifiersToString = (modifiers) => _.keys(_.pickBy(modifiers, Boolean)).join(', ')
const create = (state) => {
const kb = {
@@ -680,6 +673,7 @@ const create = (state) => {
return kb.specialChars[chars].call(this, el, options)
},
+
isModifier (chars) {
return _.includes(_.keys(kb.modifierChars), chars)
},
diff --git a/packages/driver/src/cy/mouse.js b/packages/driver/src/cy/mouse.js
index f65777dd43f9..13ede096088c 100644
--- a/packages/driver/src/cy/mouse.js
+++ b/packages/driver/src/cy/mouse.js
@@ -4,7 +4,7 @@ const $ = require('jquery')
const _ = require('lodash')
const $Keyboard = require('./keyboard')
const $selection = require('../dom/selection')
-const debug = require('debug')('driver:mouse')
+const debug = require('debug')('cypress:driver:mouse')
/**
* @typedef Coords
@@ -23,7 +23,16 @@ const getLastHoveredEl = (state) => {
}
return lastHoveredEl
+}
+const defaultPointerDownUpOptions = {
+ pointerType: 'mouse',
+ pointerId: 1,
+ isPrimary: true,
+ detail: 0,
+ // pressure 0.5 is default for mouse that doesn't support pressure
+ // https://developer.mozilla.org/en-US/docs/Web/API/PointerEvent/pressure
+ pressure: 0.5,
}
const getMouseCoords = (state) => {
@@ -188,7 +197,6 @@ const create = (state, keyboard, focused) => {
}
- // if (!Cypress.config('mousemoveBeforeMouseover') && el) {
pointermove = () => {
return sendPointermove(el, defaultPointerOptions)
}
@@ -213,7 +221,6 @@ const create = (state, keyboard, focused) => {
events.push({ mousemove: mousemove() })
return events
-
},
/**
@@ -229,10 +236,7 @@ const create = (state, keyboard, focused) => {
const el = doc.elementFromPoint(x, y)
- // mouse._mouseMoveEvents(el, { x, y })
-
return el
-
},
/**
@@ -264,14 +268,10 @@ const create = (state, keyboard, focused) => {
const defaultOptions = mouse._getDefaultMouseOptions(x, y, win)
const pointerEvtOptions = _.extend({}, defaultOptions, {
+ ...defaultPointerDownUpOptions,
button: 0,
which: 1,
buttons: 1,
- detail: 0,
- pressure: 0.5,
- pointerType: 'mouse',
- pointerId: 1,
- isPrimary: true,
relatedTarget: null,
}, pointerEvtOptionsExtend)
@@ -399,12 +399,8 @@ const create = (state, keyboard, focused) => {
let defaultOptions = mouse._getDefaultMouseOptions(fromViewport.x, fromViewport.y, win)
const pointerEvtOptions = _.extend({}, defaultOptions, {
+ ...defaultPointerDownUpOptions,
buttons: 0,
- pressure: 0.5,
- pointerType: 'mouse',
- pointerId: 1,
- isPrimary: true,
- detail: 0,
}, pointerEvtOptionsExtend)
let mouseEvtOptions = _.extend({}, defaultOptions, {
@@ -533,12 +529,12 @@ const create = (state, keyboard, focused) => {
const { stopPropagation } = window.MouseEvent.prototype
-const sendEvent = (evtName, el, evtOptions, bubbles = false, cancelable = false, constructor) => {
+const sendEvent = (evtName, el, evtOptions, bubbles = false, cancelable = false, Constructor) => {
evtOptions = _.extend({}, evtOptions, { bubbles, cancelable })
const _eventModifiers = $Keyboard.fromModifierEventOptions(evtOptions)
const modifiers = $Keyboard.modifiersToString(_eventModifiers)
- const evt = new constructor(evtName, _.extend({}, evtOptions, { bubbles, cancelable }))
+ const evt = new Constructor(evtName, _.extend({}, evtOptions, { bubbles, cancelable }))
if (bubbles) {
evt.stopPropagation = function (...args) {
@@ -560,16 +556,16 @@ const sendEvent = (evtName, el, evtOptions, bubbles = false, cancelable = false,
}
const sendPointerEvent = (el, evtOptions, evtName, bubbles = false, cancelable = false) => {
- const constructor = el.ownerDocument.defaultView.PointerEvent
+ const Constructor = el.ownerDocument.defaultView.PointerEvent
- return sendEvent(evtName, el, evtOptions, bubbles, cancelable, constructor)
+ return sendEvent(evtName, el, evtOptions, bubbles, cancelable, Constructor)
}
const sendMouseEvent = (el, evtOptions, evtName, bubbles = false, cancelable = false) => {
- // IE doesn't have event constructors, so you should use document.createEvent('mouseevent')
+ // TODO: IE doesn't have event constructors, so you should use document.createEvent('mouseevent')
// https://dom.spec.whatwg.org/#dom-document-createevent
- const constructor = el.ownerDocument.defaultView.MouseEvent
+ const Constructor = el.ownerDocument.defaultView.MouseEvent
- return sendEvent(evtName, el, evtOptions, bubbles, cancelable, constructor)
+ return sendEvent(evtName, el, evtOptions, bubbles, cancelable, Constructor)
}
const sendPointerup = (el, evtOptions) => {
@@ -630,7 +626,6 @@ const formatReasonNotFired = (reason) => {
}
const toCoordsEventOptions = (x, y, win) => {
-
// these are the coords from the document, ignoring scroll position
const fromDocCoords = $elements.getFromDocCoords(x, y, win)
From 109763853e3c11ce434c6aa84b94370d3e116445 Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Wed, 25 Sep 2019 15:57:01 -0400
Subject: [PATCH 19/36] temp 09/25/19 [skip ci]
---
packages/driver/src/cy/mouse.js | 3 ++
.../commands/actions/click_spec.js | 45 +++++++++----------
2 files changed, 23 insertions(+), 25 deletions(-)
diff --git a/packages/driver/src/cy/mouse.js b/packages/driver/src/cy/mouse.js
index 13ede096088c..4ccaca0d05d6 100644
--- a/packages/driver/src/cy/mouse.js
+++ b/packages/driver/src/cy/mouse.js
@@ -70,6 +70,7 @@ const create = (state, keyboard, focused) => {
// if coords are same AND we're already hovered on the element, don't send move events
if (_.isEqual({ x: coords.x, y: coords.y }, getMouseCoords(state)) && lastHoveredEl === targetEl) return { el: targetEl }
+ debug('mousemove events')
const events = mouse._mouseMoveEvents(targetEl, coords)
const resultEl = mouse.getElAtCoordsOrForce(coords, forceEl)
@@ -565,6 +566,8 @@ const sendMouseEvent = (el, evtOptions, evtName, bubbles = false, cancelable = f
// https://dom.spec.whatwg.org/#dom-document-createevent
const Constructor = el.ownerDocument.defaultView.MouseEvent
+ debug('send event:', evtName)
+
return sendEvent(evtName, el, evtOptions, bubbles, cancelable, Constructor)
}
diff --git a/packages/driver/test/cypress/integration/commands/actions/click_spec.js b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
index 7a149ef7a0de..84954c1b4319 100644
--- a/packages/driver/test/cypress/integration/commands/actions/click_spec.js
+++ b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
@@ -3769,42 +3769,37 @@ describe('mouse state', () => {
cy.get('#inner').should('not.be.visible')
})
- it('will respect changes to dom in event handlers', () => {
+ it.only('will respect changes to dom in event handlers', () => {
- allMouseEvents.forEach((evt) => {
- cy.$$('#sq4').on(evt, cy.spy().as(`sq4:${evt}`))
- cy.$$('#outer').on(evt, cy.spy().as(`outer:${evt}`))
- cy.$$('input:first').on(evt, cy.spy().as(`input:${evt}`))
- })
+ const els = {
+ sq4: cy.$$('#sq4'),
+ outer: cy.$$('#outer'),
+ input: cy.$$('input:first'),
+ }
+
+ attachListeners(['mouseenter', 'mouseexit'])
+
+ attachMouseClickListeners(els)
+ attachMouseHoverListeners(els)
+
+ return
cy.get('#sq4').click()
- cy.get('#outer').click({ timeout: 200 })
+ cy.get('#outer').click()
- cy.getAll('@sq4:mouseover @sq4:mousedown @sq4:mouseup @sq4:click').each((spy) => {
- expect(spy).to.be.calledTwice
- })
+ cy.getAll('@sq4:mouseover @sq4:mousedown @sq4:mouseup @sq4:click').each(shouldBeCalledNth(2))
- cy.getAll('@sq4:mouseout').each((spy) => {
- expect(spy).to.be.calledOnce
- })
+ cy.getAll('@sq4:mouseout').each(shouldBeCalledOnce)
- cy.getAll('@outer:mousedown @outer:mouseup @outer:click').each((spy) => {
- expect(spy).to.not.be.called
- })
+ cy.getAll('@outer:mousedown @outer:mouseup @outer:click').each(shouldNotBeCalled)
- cy.getAll('@outer:mouseover @outer:mouseout').each((spy) => {
- expect(spy).to.be.calledOnce
- })
+ cy.getAll('@outer:mouseover @outer:mouseout').each(shouldBeCalledOnce)
cy.get('input:first').click().should('not.have.focus')
- cy.getAll('@input:mouseover @input:mouseout').each((spy) => {
- expect(spy).to.be.calledOnce
- })
+ cy.getAll('@input:mouseover @input:mouseout').each(shouldBeCalledOnce)
- cy.getAll('@input:mousedown @input:mouseup @input:click').each((spy) => {
- expect(spy).to.not.be.called
- })
+ cy.getAll('@input:mousedown @input:mouseup @input:click').each(shouldNotBeCalled)
})
// it('will continue to send mouseleave events', function (done) {
From f7ab2ebd376fd0be8d7952a417476ea384acecb0 Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Thu, 26 Sep 2019 12:57:46 -0400
Subject: [PATCH 20/36] add test for clicking checkbox, cleanup click_spec
---
.../commands/actions/click_spec.js | 25 +++++++++++++++----
1 file changed, 20 insertions(+), 5 deletions(-)
diff --git a/packages/driver/test/cypress/integration/commands/actions/click_spec.js b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
index 84954c1b4319..e915178f9bfa 100644
--- a/packages/driver/test/cypress/integration/commands/actions/click_spec.js
+++ b/packages/driver/test/cypress/integration/commands/actions/click_spec.js
@@ -775,6 +775,24 @@ describe('src/cy/commands/actions/click', () => {
cy.get('#readonly-submit').click()
})
+ it('can click on checkbox inputs', () => {
+ cy.get(':checkbox:first').click()
+ .then(($el) => {
+ expect($el).to.be.checked
+ })
+ })
+
+ it('can force click on disabled checkbox inputs', () => {
+ cy.get(':checkbox:first')
+ .then(($el) => {
+ $el[0].disabled = true
+ })
+ .click({ force: true })
+ .then(($el) => {
+ expect($el).to.be.checked
+ })
+ })
+
it('can click elements which are hidden until scrolled within parent container', () => {
cy.get('#overflow-auto-container').contains('quux').click()
})
@@ -2961,7 +2979,6 @@ describe('src/cy/commands/actions/click', () => {
cancelable: true,
data: undefined,
detail: 0,
- eventPhase: 2,
handleObj: { type: 'contextmenu', origType: 'contextmenu', data: undefined },
relatedTarget: null,
shiftKey: false,
@@ -3065,7 +3082,7 @@ describe('src/cy/commands/actions/click', () => {
attachContextmenuListeners({ el, el2 })
cy.get('button:first').rightclick().should('not.exist')
- cy.get(el2.selector).should('have.focus')
+ cy.get('div#tabindex').should('have.focus')
cy.getAll('el', 'pointerover mouseover').each(shouldBeCalledOnce)
cy.getAll('el', 'pointerdown mousedown pointerup mouseup contextmenu').each(shouldNotBeCalled)
@@ -3769,7 +3786,7 @@ describe('mouse state', () => {
cy.get('#inner').should('not.be.visible')
})
- it.only('will respect changes to dom in event handlers', () => {
+ it('will respect changes to dom in event handlers', () => {
const els = {
sq4: cy.$$('#sq4'),
@@ -3782,8 +3799,6 @@ describe('mouse state', () => {
attachMouseClickListeners(els)
attachMouseHoverListeners(els)
- return
-
cy.get('#sq4').click()
cy.get('#outer').click()
From fbc43ebdaa2282d099d08acbfbce00449403a6bf Mon Sep 17 00:00:00 2001
From: Ben Kucera <14625260+Bkucera@users.noreply.github.com>
Date: Fri, 27 Sep 2019 15:42:08 -0400
Subject: [PATCH 21/36] document mouse click and mouse move algo, adjust mouse
move, add test for recursive movement
---
packages/driver/src/cy/mouse.js | 37 +++++++++++++++----
.../test/cypress/fixtures/issue-2956.html | 20 +++++++++-
.../commands/actions/click_spec.js | 33 +++++++++++------
3 files changed, 71 insertions(+), 19 deletions(-)
diff --git a/packages/driver/src/cy/mouse.js b/packages/driver/src/cy/mouse.js
index 4ccaca0d05d6..b2fd2e6f1ed4 100644
--- a/packages/driver/src/cy/mouse.js
+++ b/packages/driver/src/cy/mouse.js
@@ -70,21 +70,23 @@ const create = (state, keyboard, focused) => {
// if coords are same AND we're already hovered on the element, don't send move events
if (_.isEqual({ x: coords.x, y: coords.y }, getMouseCoords(state)) && lastHoveredEl === targetEl) return { el: targetEl }
- debug('mousemove events')
const events = mouse._mouseMoveEvents(targetEl, coords)
const resultEl = mouse.getElAtCoordsOrForce(coords, forceEl)
- if (resultEl !== targetEl) {
- mouse._mouseMoveEvents(resultEl, coords)
- }
-
return { el: resultEl, fromEl: lastHoveredEl, events }
},
/**
* @param {HTMLElement} el
* @param {Coords} coords
+ * Steps to perform mouse move:
+ * - send out events to elLastHovered (bubbles)
+ * - send leave events to all Elements until commonAncestor
+ * - send over events to elToHover (bubbles)
+ * - send enter events to all elements from commonAncestor
+ * - send move events to elToHover (bubbles)
+ * - elLastHovered = elToHover
*/
_mouseMoveEvents (el, coords) {
@@ -371,7 +373,28 @@ const create = (state, keyboard, focused) => {
return mouse._mouseUpEvents(fromViewport, forceEl, skipMouseEvent, pointerEvtOptionsExtend, mouseEvtOptionsExtend)
},
+ /**
+ *
+ * Steps to perform click:
+ *
+ * moveToCoordsOrNoop = (coords, el) => {
+ * elAtPoint = getElementFromPoint(coords)
+ * if (elAtPoint !== el)
+ * sendMouseMoveEvents(elAtPoint, el)
+ * return getElementFromPoint(coords)
+ * }
+ *
+ * coords = getCoords(elSubject)
+ * el1 = moveToCoordsOrNoop(coords, elLastHovered)
+ * mouseDown(el1)
+ * el2 = moveToCoordsOrNoop(coords, el1)
+ * mouseUp(el2)
+ * el3 = moveToCoordsOrNoop(coords, el2)
+ * if (notDetached(el1))
+ * sendClick(el3)
+ */
mouseClick (fromViewport, forceEl, pointerEvtOptionsExtend = {}, mouseEvtOptionsExtend = {}) {
+
debug('mouseClick', { fromViewport, forceEl })
const mouseDownEvents = mouse.mouseDown(fromViewport, forceEl, pointerEvtOptionsExtend, mouseEvtOptionsExtend)
@@ -545,6 +568,8 @@ const sendEvent = (evtName, el, evtOptions, bubbles = false, cancelable = false,
}
}
+ debug('event:', evtName, el)
+
const preventedDefault = !el.dispatchEvent(evt)
return {
@@ -566,8 +591,6 @@ const sendMouseEvent = (el, evtOptions, evtName, bubbles = false, cancelable = f
// https://dom.spec.whatwg.org/#dom-document-createevent
const Constructor = el.ownerDocument.defaultView.MouseEvent
- debug('send event:', evtName)
-
return sendEvent(evtName, el, evtOptions, bubbles, cancelable, Constructor)
}
diff --git a/packages/driver/test/cypress/fixtures/issue-2956.html b/packages/driver/test/cypress/fixtures/issue-2956.html
index 4536d323874b..cc2b6a1729ba 100644
--- a/packages/driver/test/cypress/fixtures/issue-2956.html
+++ b/packages/driver/test/cypress/fixtures/issue-2956.html
@@ -48,6 +48,10 @@
display: inherit;
}
+#sq6.hover {
+ margin-left: 100px;
+}
+
button {
display: inherit;
position: relative;
@@ -76,6 +80,16 @@
inner.classList.remove('active');
}
+function setHover() {
+ // console.log('hover')
+ this.classList.add('hover');
+}
+
+function unsetHover() {
+ // console.log('unhover')
+ this.classList.remove('hover');
+}
+
function color() {
if (outer.classList.contains('yellow')) {
outer.classList.remove('yellow');
@@ -106,6 +120,10 @@
e.preventDefault()
})
+const sq6 = document.getElementById('sq6')
+sq6.addEventListener("mouseenter", setHover);
+sq6.addEventListener("mouseleave", unsetHover);
+
}
@@ -128,7 +146,7 @@
-
+