From 5d77abc7c7ce35898ab0976918b768924b9b1e13 Mon Sep 17 00:00:00 2001 From: Kukhyeon Heo Date: Fri, 11 Feb 2022 00:49:20 +0900 Subject: [PATCH] fix: `cy.type(' ')` fires click event on button-like elements. (#20067) --- ...pe-enter.html => click-event-by-type.html} | 16 ++-- .../integration/commands/actions/type_spec.js | 86 ++++++++++++++++++- .../driver/src/cy/commands/actions/type.ts | 33 +++++-- 3 files changed, 121 insertions(+), 14 deletions(-) rename packages/driver/cypress/fixtures/{type-enter.html => click-event-by-type.html} (73%) diff --git a/packages/driver/cypress/fixtures/type-enter.html b/packages/driver/cypress/fixtures/click-event-by-type.html similarity index 73% rename from packages/driver/cypress/fixtures/type-enter.html rename to packages/driver/cypress/fixtures/click-event-by-type.html index 421e2683d19d..5999d152f00a 100644 --- a/packages/driver/cypress/fixtures/type-enter.html +++ b/packages/driver/cypress/fixtures/click-event-by-type.html @@ -1,19 +1,19 @@ - type('{enter}') + Click Event by cy.type() - - - - - - - + + + + + + +
diff --git a/packages/driver/cypress/integration/commands/actions/type_spec.js b/packages/driver/cypress/integration/commands/actions/type_spec.js index 28ef1155efe2..c63a0e954bb7 100644 --- a/packages/driver/cypress/integration/commands/actions/type_spec.js +++ b/packages/driver/cypress/integration/commands/actions/type_spec.js @@ -560,7 +560,7 @@ describe('src/cy/commands/actions/type - #type', () => { // https://github.com/cypress-io/cypress/issues/19541 describe(`type('{enter}') and click event on button-like elements`, () => { beforeEach(() => { - cy.visit('fixtures/type-enter.html') + cy.visit('fixtures/click-event-by-type.html') }) describe('triggers', () => { @@ -604,6 +604,90 @@ describe('src/cy/commands/actions/type - #type', () => { }) }) + describe(`type(' ') fires click event on button-like elements`, () => { + beforeEach(() => { + cy.visit('fixtures/click-event-by-type.html') + }) + + const targets = [ + 'button-tag', + 'input-button', + 'input-image', + 'input-reset', + 'input-submit', + ] + + describe(`triggers with single space`, () => { + targets.forEach((targetId) => { + it(targetId, () => { + cy.get(`#target-${targetId}`).focus().type(' ') + + cy.get('li').eq(0).should('have.text', 'keydown') + cy.get('li').eq(1).should('have.text', 'keypress') + cy.get('li').eq(2).should('have.text', 'keyup') + cy.get('li').eq(3).should('have.text', 'click') + }) + }) + }) + + describe('triggers after other characters', () => { + targets.forEach((targetId) => { + it(targetId, () => { + cy.get(`#target-${targetId}`).focus().type('asd ') + + cy.get('li').eq(12).should('have.text', 'click') + }) + }) + }) + + describe('checkbox', () => { + it('checkbox is checked/unchecked', () => { + cy.get(`#target-input-checkbox`).focus().type(' ') + + cy.get('li').eq(0).should('have.text', 'keydown') + cy.get('li').eq(1).should('have.text', 'keypress') + cy.get('li').eq(2).should('have.text', 'keyup') + cy.get('li').eq(3).should('have.text', 'click') + + cy.get('#target-input-checkbox').should('be.checked') + + cy.get(`#target-input-checkbox`).type(' ') + + cy.get('li').eq(4).should('have.text', 'keydown') + cy.get('li').eq(5).should('have.text', 'keypress') + cy.get('li').eq(6).should('have.text', 'keyup') + cy.get('li').eq(7).should('have.text', 'click') + + cy.get('#target-input-checkbox').should('not.be.checked') + }) + }) + + describe('radio', () => { + it('radio fires click event when it is not checked', () => { + cy.get(`#target-input-radio`).focus().type(' ') + + cy.get('li').eq(0).should('have.text', 'keydown') + cy.get('li').eq(1).should('have.text', 'keypress') + cy.get('li').eq(2).should('have.text', 'keyup') + cy.get('li').eq(3).should('have.text', 'click') + + cy.get('#target-input-radio').should('be.checked') + }) + + it('radio does not fire click event when it is checked', () => { + // We're clicking here first to make the radio element checked. + cy.get(`#target-input-radio`).click().type(' ') + + // item 0 is click event. It's fired because we want to make sure our radio button is checked. + cy.get('li').eq(1).should('have.text', 'keydown') + cy.get('li').eq(2).should('have.text', 'keypress') + cy.get('li').eq(3).should('have.text', 'keyup') + + cy.get('#target-input-radio').should('be.checked') + }) + }) + }) + describe('tabindex', () => { beforeEach(function () { this.$div = cy.$$('#tabindex') diff --git a/packages/driver/src/cy/commands/actions/type.ts b/packages/driver/src/cy/commands/actions/type.ts index 4ef46395b1c3..7d255a65d668 100644 --- a/packages/driver/src/cy/commands/actions/type.ts +++ b/packages/driver/src/cy/commands/actions/type.ts @@ -280,6 +280,13 @@ export default function (Commands, Cypress, cy, state, config) { const type = (type) => $elements.isInputType(options.$el.get(0), type) const sendClickEvent = type('button') || type('image') || type('submit') || type('reset') + const fireClickEvent = (el) => { + const ctor = $dom.getDocumentFromElement(el).defaultView!.PointerEvent + const event = new ctor('click') + + el.dispatchEvent(event) + } + return keyboard.type({ $el: options.$el, chars, @@ -320,7 +327,26 @@ export default function (Commands, Cypress, cy, state, config) { } }, - onEvent: updateTable || _.noop, + onEvent (id, key, event, value) { + if (updateTable) { + updateTable(id, key, event, value) + } + + if ( + // Firefox sends a click event when the Space key is pressed. + // We don't want send it twice. + !Cypress.isBrowser('firefox') && + // Click event is sent after keyup event with space key. + event.type === 'keyup' && event.code === 'Space' && + // Click events should be only sent to button-like elements. + // event.target is null when used with shadow DOM. + (event.target && $elements.isButtonLike(event.target)) && + // When a space key is pressed for input radio elements, the click event is only fired when it's not checked. + !(event.target.tagName === 'INPUT' && event.target.type === 'radio' && event.target.checked === true) + ) { + fireClickEvent(event.target) + } + }, // fires only when the 'value' // of input/text/contenteditable @@ -368,10 +394,7 @@ export default function (Commands, Cypress, cy, state, config) { if (sendClickEvent) { // Firefox sends a click event automatically. if (!Cypress.isBrowser('firefox')) { - const ctor = $dom.getDocumentFromElement(el).defaultView?.PointerEvent - const event = new ctor!('click') - - el.dispatchEvent(event) + fireClickEvent(el) } }