Skip to content
Merged
48 changes: 48 additions & 0 deletions cli/types/cypress.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2319,6 +2319,54 @@ declare namespace Cypress {
* @default false
*/
multiple: boolean
/**
* Activates the control key during click
*
* @default false
*/
ctrlKey: boolean
/**
* Activates the control key during click
*
* @default false
*/
controlKey: boolean
/**
* Activates the alt key (option key for Mac) during click
*
* @default false
*/
altKey: boolean
/**
* Activates the alt key (option key for Mac) during click
*
* @default false
*/
optionKey: boolean
/**
* Activates the shift key during click
*
* @default false
*/
shiftKey: boolean
/**
* Activates the meta key (Windows key or command key for Mac) during click
*
* @default false
*/
metaKey: boolean
/**
* Activates the meta key (Windows key or command key for Mac) during click
*
* @default false
*/
commandKey: boolean
/**
* Activates the meta key (Windows key or command key for Mac) during click
*
* @default false
*/
cmdKey: boolean
}

interface ResolvedConfigOptions {
Expand Down
3 changes: 3 additions & 0 deletions cli/types/tests/chainer-examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,9 @@ cy.writeFile('../file.path', '', {
})

cy.get('foo').click()
cy.get('foo').click({
ctrlKey: true,
})
cy.get('foo').rightclick()
cy.get('foo').dblclick()

Expand Down
33 changes: 33 additions & 0 deletions packages/driver/cypress/fixtures/issue-486.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<!DOCTYPE html>
<html>
<head>
<title>Issue 486</title>
</head>
<body>
<button id="button">modifier test</button>
<div id="result">Result</div>
<script type="text/javascript">
document.getElementById('button').addEventListener('click', (e) => {
const result = document.getElementById('result')

result.innerText = ''

if (e.ctrlKey) {
result.innerText += '{Ctrl}'
}

if (e.altKey) {
result.innerText += '{Alt}'
}

if (e.shiftKey) {
result.innerText += '{Shift}'
}

if (e.metaKey) {
result.innerText += '{Meta}'
}
})
</script>
</body>
</html>
99 changes: 99 additions & 0 deletions packages/driver/cypress/integration/commands/actions/click_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -882,6 +882,105 @@ describe('src/cy/commands/actions/click', () => {
})
})

describe('modifier options', () => {
beforeEach(() => {
cy.visit('/fixtures/issue-486.html')
})

it('ctrl', () => {
cy.get('#button').click({
ctrlKey: true,
})

cy.get('#result').should('contain', '{Ctrl}')

// ctrl should be released
cy.get('#button').click()
cy.get('#result').should('not.contain', '{Ctrl}')

cy.get('#button').click({
controlKey: true,
})

cy.get('#result').should('contain', '{Ctrl}')
})

it('alt', () => {
cy.get('#button').click({
altKey: true,
})

cy.get('#result').should('contain', '{Alt}')

// alt should be released
cy.get('#button').click()
cy.get('#result').should('not.contain', '{Alt}')

cy.get('#button').click({
optionKey: true,
})

cy.get('#result').should('contain', '{Alt}')
})

it('shift', () => {
cy.get('#button').click({
shiftKey: true,
})

cy.get('#result').should('contain', '{Shift}')

// shift should be released
cy.get('#button').click()
cy.get('#result').should('not.contain', '{Shift}')
})

it('meta', () => {
cy.get('#button').click({
metaKey: true,
})

cy.get('#result').should('contain', '{Meta}')

// shift should be released
cy.get('#button').click()
cy.get('#result').should('not.contain', '{Meta}')

cy.get('#button').click({
commandKey: true,
})

cy.get('#result').should('contain', '{Meta}')

cy.get('#button').click({
cmdKey: true,
})

cy.get('#result').should('contain', '{Meta}')
})

it('multiple', () => {
cy.get('#button').click({
ctrlKey: true,
altKey: true,
shiftKey: true,
metaKey: true,
})

cy.get('#result').should('contain', '{Ctrl}')
cy.get('#result').should('contain', '{Alt}')
cy.get('#result').should('contain', '{Shift}')
cy.get('#result').should('contain', '{Meta}')

// modifiers should be released
cy.get('#button').click()
cy.get('#result').should('not.contain', '{Ctrl}')
cy.get('#result').should('not.contain', '{Alt}')
cy.get('#result').should('not.contain', '{Shift}')
cy.get('#result').should('not.contain', '{Meta}')
})
})

describe('pointer-events:none', () => {
beforeEach(function () {
cy.$$('<div id="ptr" style="position:absolute;width:200px;height:200px;background-color:#08c18d;">behind #ptrNone</div>').appendTo(cy.$$('#dom'))
Expand Down
32 changes: 31 additions & 1 deletion packages/driver/src/cy/commands/actions/click.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const formatMouseEvents = (events) => {
}

module.exports = (Commands, Cypress, cy, state, config) => {
const { mouse } = cy.devices
const { mouse, keyboard } = cy.devices

const mouseAction = (eventName, { subject, positionOrX, y, userOptions, onReady, onTable, defaultOptions }) => {
let position
Expand All @@ -54,6 +54,14 @@ module.exports = (Commands, Cypress, cy, state, config) => {
errorOnSelect: true,
waitForAnimations: config('waitForAnimations'),
animationDistanceThreshold: config('animationDistanceThreshold'),
ctrlKey: false,
controlKey: false,
altKey: false,
optionKey: false,
shiftKey: false,
metaKey: false,
commandKey: false,
cmdKey: false,
...defaultOptions,
})

Expand All @@ -65,6 +73,24 @@ module.exports = (Commands, Cypress, cy, state, config) => {
})
}

const flagModifiers = (press) => {
if (options.ctrlKey || options.controlKey) {
keyboard.flagModifier({ key: 'Control' }, press)
}

if (options.altKey || options.optionKey) {
keyboard.flagModifier({ key: 'Alt' }, press)
}

if (options.shiftKey) {
keyboard.flagModifier({ key: 'Shift' }, press)
}

if (options.metaKey || options.commandKey || options.cmdKey) {
keyboard.flagModifier({ key: 'Meta' }, press)
}
}

const perform = (el) => {
let deltaOptions
const $el = $dom.wrap(el)
Expand Down Expand Up @@ -158,8 +184,12 @@ module.exports = (Commands, Cypress, cy, state, config) => {

const moveEvents = mouse.move(fromElViewport, forceEl)

flagModifiers(true)

const onReadyProps = onReady(fromElViewport, forceEl)

flagModifiers(false)

return createLog({
moveEvents,
...onReadyProps,
Expand Down