From 52d7316038604c4f22d18d2114402f7784c89813 Mon Sep 17 00:00:00 2001 From: KHeo Date: Tue, 15 Feb 2022 10:28:27 +0900 Subject: [PATCH 1/3] chore: move tests to its own file. --- .../commands/actions/type_events_spec.js | 770 ++++++++++++++++++ .../integration/commands/actions/type_spec.js | 761 ----------------- 2 files changed, 770 insertions(+), 761 deletions(-) create mode 100644 packages/driver/cypress/integration/commands/actions/type_events_spec.js diff --git a/packages/driver/cypress/integration/commands/actions/type_events_spec.js b/packages/driver/cypress/integration/commands/actions/type_events_spec.js new file mode 100644 index 000000000000..697c1dd670e7 --- /dev/null +++ b/packages/driver/cypress/integration/commands/actions/type_events_spec.js @@ -0,0 +1,770 @@ +const { _, $ } = Cypress + +describe('src/cy/commands/actions/type - #type events', () => { + beforeEach(() => { + cy.visit('fixtures/dom.html') + }) + + describe('events', () => { + it('receives keydown event', (done) => { + const $txt = cy.$$(':text:first') + + $txt.on('keydown', (e) => { + expect(_.toPlainObject(e.originalEvent)).to.include({ + altKey: false, + bubbles: true, + cancelable: true, + charCode: 0, // deprecated + ctrlKey: false, + detail: 0, + key: 'a', + // has code property https://github.com/cypress-io/cypress/issues/3722 + code: 'KeyA', + keyCode: 65, // deprecated but fired by chrome always uppercase in the ASCII table + location: 0, + metaKey: false, + repeat: false, + shiftKey: false, + type: 'keydown', + which: 65, // deprecated but fired by chrome + }) + + done() + }) + + cy.get(':text:first').type('a') + }) + + it('receives keypress event', (done) => { + const $txt = cy.$$(':text:first') + + $txt.on('keypress', (e) => { + expect(_.toPlainObject(e.originalEvent)).to.include({ + altKey: false, + bubbles: true, + cancelable: true, + charCode: 97, // deprecated + ctrlKey: false, + detail: 0, + key: 'a', + code: 'KeyA', + keyCode: 97, // deprecated + location: 0, + metaKey: false, + repeat: false, + shiftKey: false, + type: 'keypress', + which: 97, // deprecated + }) + + done() + }) + + cy.get(':text:first').type('a') + }) + + it('receives keyup event', (done) => { + const $txt = cy.$$(':text:first') + + $txt.on('keyup', (e) => { + expect(_.toPlainObject(e.originalEvent)).to.include({ + altKey: false, + bubbles: true, + cancelable: true, + charCode: 0, // deprecated + ctrlKey: false, + detail: 0, + key: 'a', + code: 'KeyA', + keyCode: 65, // deprecated but fired by chrome always uppercase in the ASCII table + location: 0, + metaKey: false, + repeat: false, + shiftKey: false, + type: 'keyup', + view: cy.state('window'), + which: 65, // deprecated but fired by chrome + }) + .not.have.property('inputType') + + done() + }) + + cy.get(':text:first').type('a') + }) + + it('receives textInput event', (done) => { + const $txt = cy.$$(':text:first') + + $txt[0].addEventListener('textInput', (e) => { + // FIXME: (firefox) firefox cannot access window objects else throw cross-origin error + expect(Object.prototype.toString.call(e.view)).eq('[object Window]') + e.view = null + expect(_.toPlainObject(e)).to.include({ + bubbles: true, + cancelable: true, + data: 'a', + detail: 0, + type: 'textInput', + // view: cy.state('window'), + which: 0, + }) + + done() + }) + + cy.get(':text:first').type('a') + }) + + it('receives input event', (done) => { + const $txt = cy.$$(':text:first') + + $txt.on('input', (e) => { + const obj = _.pick(e.originalEvent, 'bubbles', 'cancelable', 'type') + + expect(obj).to.deep.eq({ + bubbles: true, + cancelable: false, + type: 'input', + }) + + done() + }) + + cy.get(':text:first').type('a') + }) + + it('fires events in the correct order') + + it('fires events for each key stroke') + + it('does fire input event when value changes', () => { + const onInput = cy.stub() + + cy.$$(':text:first').on('input', onInput) + + cy.get(':text:first') + .invoke('val', 'bar') + .type('{selectAll}{rightarrow}{backspace}') + .then(() => { + expect(onInput).to.be.calledOnce + }) + .then(() => { + onInput.resetHistory() + }) + + cy.get(':text:first') + .invoke('val', 'bar') + .type('{selectAll}{leftarrow}{del}') + .then(() => { + expect(onInput).to.be.calledOnce + }) + .then(() => { + onInput.resetHistory() + }) + + cy.$$('[contenteditable]:first').on('input', onInput) + + cy.get('[contenteditable]:first') + .invoke('html', 'foobar') + .type('{selectAll}{rightarrow}{backspace}') + .then(() => { + expect(onInput).to.be.calledOnce + }) + .then(() => { + onInput.resetHistory() + }) + + cy.get('[contenteditable]:first') + .invoke('html', 'foobar') + .type('{selectAll}{leftarrow}{del}') + .then(() => { + expect(onInput).to.be.calledOnce + }) + }) + + it('does not fire input event when value does not change', () => { + let fired = false + + cy.$$(':text:first').on('input', () => { + fired = true + }) + + cy.get(':text:first') + .invoke('val', 'bar') + .type('{selectAll}{rightarrow}{del}') + .then(() => { + expect(fired).to.eq(false) + }) + + cy.get(':text:first') + .invoke('val', 'bar') + .type('{selectAll}{leftarrow}{backspace}') + .then(() => { + expect(fired).to.eq(false) + }) + + cy.$$('textarea:first').on('input', () => { + fired = true + }) + + cy.get('textarea:first') + .invoke('val', 'bar') + .type('{selectAll}{rightarrow}{del}') + .then(() => { + expect(fired).to.eq(false) + }) + + cy.get('textarea:first') + .invoke('val', 'bar') + .type('{selectAll}{leftarrow}{backspace}') + .then(() => { + expect(fired).to.eq(false) + }) + + cy.$$('[contenteditable]:first').on('input', () => { + fired = true + }) + + cy.get('[contenteditable]:first') + .invoke('html', 'foobar') + .type('{movetoend}') + .then(($el) => { + expect(fired).to.eq(false) + }) + + cy.get('[contenteditable]:first') + .invoke('html', 'foobar') + .type('{selectAll}{leftarrow}{backspace}') + .then(() => { + expect(fired).to.eq(false) + }) + }) + }) + + describe('click events', () => { + it('passes timeout and interval down to click', (done) => { + const input = $('').attr('id', 'input-covered-in-span').prependTo(cy.$$('body')) + + $('span on input') + .css({ + position: 'absolute', + left: input.offset().left, + top: input.offset().top, + padding: 5, + display: 'inline-block', + backgroundColor: 'yellow', + }) + .prependTo(cy.$$('body')) + + cy.on('command:retry', (options) => { + expect(options.timeout).to.eq(1000) + expect(options.interval).to.eq(60) + + done() + }) + + cy.get('#input-covered-in-span').type('foobar', { timeout: 1000, interval: 60 }) + }) + + it('does not issue another click event between type/type', () => { + const clicked = cy.stub() + + cy.$$(':text:first').click(clicked) + + cy.get(':text:first').type('f').type('o').then(() => { + expect(clicked).to.be.calledOnce + }) + }) + + it('does not issue another click event if element is already in focus from click', () => { + const clicked = cy.stub() + + cy.$$(':text:first').click(clicked) + + cy.get(':text:first').click().type('o').then(() => { + expect(clicked).to.be.calledOnce + }) + }) + }) + + describe('change events', () => { + it('fires when enter is pressed and value has changed', () => { + const changed = cy.stub() + + cy.$$(':text:first').change(changed) + + cy.get(':text:first').invoke('val', 'foo').type('bar{enter}').then(() => { + expect(changed).to.be.calledOnce + }) + }) + + it('fires twice when enter is pressed and then again after losing focus', () => { + const changed = cy.stub() + + cy.$$(':text:first').change(changed) + + cy.get(':text:first').invoke('val', 'foo').type('bar{enter}baz').blur().then(() => { + expect(changed).to.be.calledTwice + }) + }) + + it('fires when element loses focus due to another action (click)', () => { + const changed = cy.stub() + + cy.$$(':text:first').change(changed) + + cy + .get(':text:first').type('foo').then(() => { + expect(changed).not.to.be.called + }) + .get('button:first').click().then(() => { + expect(changed).to.be.calledOnce + }) + }) + + it('fires when element loses focus due to another action (type)', () => { + const changed = cy.stub() + + cy.$$(':text:first').change(changed) + + cy + .get(':text:first').type('foo').then(() => { + expect(changed).not.to.be.called + }) + .get('textarea:first').type('bar').then(() => { + expect(changed).to.be.calledOnce + }) + }) + + it('fires when element is directly blurred', () => { + const changed = cy.stub() + + cy.$$(':text:first').change(changed) + + cy + .get(':text:first').type('foo').blur().then(() => { + expect(changed).to.be.calledOnce + }) + }) + + it('fires when element is tabbed away from')//, -> + // changed = 0 + + // cy.$$(":text:first").change -> + // changed += 1 + + // cy.get(":text:first").invoke("val", "foo").type("b{tab}").then -> + // expect(changed).to.eq 1 + + it('does not fire twice if element is already in focus between type/type', () => { + const changed = cy.stub() + + cy.$$(':text:first').change(changed) + + cy.get(':text:first').invoke('val', 'foo').type('f').type('o{enter}').then(() => { + expect(changed).to.be.calledOnce + }) + }) + + it('does not fire twice if element is already in focus between clear/type', () => { + const changed = cy.stub() + + cy.$$(':text:first').change(changed) + + cy.get(':text:first').invoke('val', 'foo').clear().type('o{enter}').then(() => { + expect(changed).to.be.calledOnce + }) + }) + + it('does not fire twice if element is already in focus between click/type', () => { + const changed = cy.stub() + + cy.$$(':text:first').change(changed) + + cy.get(':text:first').invoke('val', 'foo').click().type('o{enter}').then(() => { + expect(changed).to.be.calledOnce + }) + }) + + it('does not fire twice if element is already in focus between type/click', () => { + const changed = cy.stub() + + cy.$$(':text:first').change(changed) + + cy.get(':text:first').invoke('val', 'foo').type('d{enter}').click().then(() => { + expect(changed).to.be.calledOnce + }) + }) + + it('does not fire at all between clear/type/click', () => { + const changed = cy.stub() + + cy.$$(':text:first').change(changed) + + cy.get(':text:first').invoke('val', 'foo').clear().type('o').click().then(($el) => { + expect(changed).not.to.be.called + + return $el + }).blur() + .then(() => { + expect(changed).to.be.calledOnce + }) + }) + + it('does not fire if {enter} is preventedDefault', () => { + const changed = cy.stub() + + cy.$$(':text:first').keypress((e) => { + if (e.which === 13) { + e.preventDefault() + } + }) + + cy.$$(':text:first').change(changed) + + cy.get(':text:first').invoke('val', 'foo').type('b{enter}').then(() => { + expect(changed).not.to.be.called + }) + }) + + it('does not fire when enter is pressed and value hasnt changed', () => { + const changed = cy.stub() + + cy.$$(':text:first').change(changed) + + cy.get(':text:first').invoke('val', 'foo').type('b{backspace}{enter}').then(() => { + expect(changed).not.to.be.called + }) + }) + + it('does not fire at the end of the type', () => { + const changed = cy.stub() + + cy.$$(':text:first').change(changed) + + cy + .get(':text:first').type('foo').then(() => { + expect(changed).not.to.be.called + }) + }) + + it('does not fire change event if value hasnt actually changed', () => { + const changed = cy.stub() + + cy.$$(':text:first').change(changed) + + cy + .get(':text:first').invoke('val', 'foo').type('{backspace}{backspace}oo{enter}').blur().then(() => { + expect(changed).not.to.be.called + }) + }) + + it('does not fire if mousedown is preventedDefault which prevents element from losing focus', () => { + const changed = cy.stub() + + cy.$$(':text:first').change(changed) + + cy.$$('textarea:first').mousedown(() => { + return false + }) + + cy + .get(':text:first').invoke('val', 'foo').type('bar') + .get('textarea:first').click().then(() => { + expect(changed).not.to.be.called + }) + }) + + it('does not fire hitting {enter} inside of a textarea', () => { + const changed = cy.stub() + + cy.$$('textarea:first').change(changed) + + cy + .get('textarea:first').type('foo{enter}bar').then(() => { + expect(changed).not.to.be.called + }) + }) + + it('does not fire hitting {enter} inside of [contenteditable]', () => { + const changed = cy.stub() + + cy.$$('[contenteditable]:first').change(changed) + + cy + .get('[contenteditable]:first').type('foo{enter}bar').then(() => { + expect(changed).not.to.be.called + }) + }) + + // [contenteditable] does not fire ANY change events ever. + it('does not fire at ALL for [contenteditable]', () => { + const changed = cy.stub() + + cy.$$('[contenteditable]:first').change(changed) + + cy + .get('[contenteditable]:first').type('foo') + .get('button:first').click().then(() => { + expect(changed).not.to.be.called + }) + }) + + it('does not fire on .clear() without blur', () => { + const changed = cy.stub() + + cy.$$('input:first').change(changed) + + cy.get('input:first').invoke('val', 'foo') + .clear() + .then(($el) => { + expect(changed).not.to.be.called + + return $el + }).type('foo') + .blur() + .then(() => { + expect(changed).not.to.be.called + }) + }) + + it('fires change for single value change inputs', () => { + const changed = cy.stub() + + cy.$$('input[type="date"]:first').change(changed) + + cy.get('input[type="date"]:first') + .type('1959-09-13') + .blur() + .then(() => { + expect(changed).to.be.calledOnce + }) + }) + + it('does not fire change for non-change single value input', () => { + const changed = cy.stub() + + cy.$$('input[type="date"]:first').change(changed) + + cy.get('input[type="date"]:first') + .invoke('val', '1959-09-13') + .type('1959-09-13') + .blur() + .then(() => { + expect(changed).not.to.be.called + }) + }) + + it('does not fire change for type\'d change that restores value', () => { + const changed = cy.stub() + + cy.$$('input:first').change(changed) + + cy.get('input:first') + .invoke('val', 'foo') + .type('{backspace}o') + .invoke('val', 'bar') + .type('{backspace}r') + .blur() + .then(() => { + expect(changed).not.to.be.called + }) + }) + }) + + // https://github.com/cypress-io/cypress/issues/19541 + describe(`type('{enter}') and click event on button-like elements`, () => { + beforeEach(() => { + cy.visit('fixtures/click-event-by-type.html') + }) + + describe('triggers', () => { + const targets = [ + '#target-button-tag', + '#target-input-button', + '#target-input-image', + '#target-input-reset', + '#target-input-submit', + ] + + targets.forEach((target) => { + it(target, () => { + cy.get(target).focus().type('{enter}') + + cy.get('li').should('have.length', 4) + 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', 'click') + cy.get('li').eq(3).should('have.text', 'keyup') + }) + }) + }) + + describe('does not trigger', () => { + const targets = [ + '#target-input-checkbox', + '#target-input-radio', + ] + + targets.forEach((target) => { + it(target, () => { + cy.get(target).focus().type('{enter}') + + cy.get('li').should('have.length', 3) + 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') + }) + }) + }) + }) + + describe(`type(' ') fires click event on button-like elements`, () => { + beforeEach(() => { + cy.visit('fixtures/click-event-by-type.html') + }) + + const targets = [ + '#target-button-tag', + '#target-input-button', + '#target-input-image', + '#target-input-reset', + '#target-input-submit', + ] + + describe(`triggers with single space`, () => { + targets.forEach((target) => { + it(target, () => { + const events = [] + + $(target).on('keydown keypress keyup click', (evt) => { + events.push(evt.type) + }) + + cy.get(target).focus().type(' ').then(() => { + expect(events).to.deep.eq([ + 'keydown', + 'keypress', + 'keyup', + 'click', + ]) + }) + + 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(`does not trigger if keyup prevented`, () => { + targets.forEach((target) => { + it(`${target} does not fire click event`, () => { + const events = [] + + $(target) + .on('keydown keypress keyup click', (evt) => { + events.push(evt.type) + }) + .on('keyup', (evt) => { + evt.preventDefault() + }) + + cy.get(target).focus().type(' ').then(() => { + expect(events).to.deep.eq([ + 'keydown', + 'keypress', + 'keyup', + ]) + }) + + cy.get('li').should('have.length', 3) + 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') + }) + }) + }) + + describe('triggers after other characters', () => { + targets.forEach((target) => { + it(target, () => { + const events = [] + + $(target).on('keydown keypress keyup click', (evt) => { + events.push(evt.type) + }) + + cy.get(target).focus().type('asd ').then(() => { + expect(events).to.deep.eq([ + 'keydown', + 'keypress', + 'keyup', + 'keydown', + 'keypress', + 'keyup', + 'keydown', + 'keypress', + 'keyup', + 'keydown', + 'keypress', + 'keyup', + 'click', + ]) + }) + + 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') + }) + }) + }) +}) diff --git a/packages/driver/cypress/integration/commands/actions/type_spec.js b/packages/driver/cypress/integration/commands/actions/type_spec.js index 90e10798bb7a..20874e019a3b 100644 --- a/packages/driver/cypress/integration/commands/actions/type_spec.js +++ b/packages/driver/cypress/integration/commands/actions/type_spec.js @@ -557,199 +557,6 @@ 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/click-event-by-type.html') - }) - - describe('triggers', () => { - const targets = [ - 'button-tag', - 'input-button', - 'input-image', - 'input-reset', - 'input-submit', - ] - - targets.forEach((targetId) => { - it(`${targetId}`, () => { - cy.get(`#target-${targetId}`).focus().type('{enter}') - - 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', 'click') - cy.get('li').eq(3).should('have.text', 'keyup') - }) - }) - }) - - describe('does not trigger', () => { - const targets = [ - 'input-checkbox', - 'input-radio', - ] - - targets.forEach((targetId) => { - it(`${targetId}`, () => { - cy.get(`#target-${targetId}`).focus().type('{enter}') - - 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') - }) - }) - }) - }) - - describe(`type(' ') fires click event on button-like elements`, () => { - beforeEach(() => { - cy.visit('fixtures/click-event-by-type.html') - }) - - const targets = [ - '#target-button-tag', - '#target-input-button', - '#target-input-image', - '#target-input-reset', - '#target-input-submit', - ] - - describe(`triggers with single space`, () => { - targets.forEach((target) => { - it(target, () => { - const events = [] - - $(target).on('keydown keypress keyup click', (evt) => { - events.push(evt.type) - }) - - cy.get(target).focus().type(' ').then(() => { - expect(events).to.deep.eq([ - 'keydown', - 'keypress', - 'keyup', - 'click', - ]) - }) - - 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(`does not trigger if keyup prevented`, () => { - targets.forEach((target) => { - it(`${target} does not fire click event`, () => { - const events = [] - - $(target) - .on('keydown keypress keyup click', (evt) => { - events.push(evt.type) - }) - .on('keyup', (evt) => { - evt.preventDefault() - }) - - cy.get(target).focus().type(' ').then(() => { - expect(events).to.deep.eq([ - 'keydown', - 'keypress', - 'keyup', - ]) - }) - - cy.get('li').should('have.length', 3) - 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') - }) - }) - }) - - describe('triggers after other characters', () => { - targets.forEach((target) => { - it(target, () => { - const events = [] - - $(target).on('keydown keypress keyup click', (evt) => { - events.push(evt.type) - }) - - cy.get(target).focus().type('asd ').then(() => { - expect(events).to.deep.eq([ - 'keydown', - 'keypress', - 'keyup', - 'keydown', - 'keypress', - 'keyup', - 'keydown', - 'keypress', - 'keyup', - 'keydown', - 'keypress', - 'keyup', - 'click', - ]) - }) - - 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') @@ -979,243 +786,6 @@ describe('src/cy/commands/actions/type - #type', () => { }) }) - describe('events', () => { - it('receives keydown event', (done) => { - const $txt = cy.$$(':text:first') - - $txt.on('keydown', (e) => { - expect(_.toPlainObject(e.originalEvent)).to.include({ - altKey: false, - bubbles: true, - cancelable: true, - charCode: 0, // deprecated - ctrlKey: false, - detail: 0, - key: 'a', - // has code property https://github.com/cypress-io/cypress/issues/3722 - code: 'KeyA', - keyCode: 65, // deprecated but fired by chrome always uppercase in the ASCII table - location: 0, - metaKey: false, - repeat: false, - shiftKey: false, - type: 'keydown', - which: 65, // deprecated but fired by chrome - }) - - done() - }) - - cy.get(':text:first').type('a') - }) - - it('receives keypress event', (done) => { - const $txt = cy.$$(':text:first') - - $txt.on('keypress', (e) => { - expect(_.toPlainObject(e.originalEvent)).to.include({ - altKey: false, - bubbles: true, - cancelable: true, - charCode: 97, // deprecated - ctrlKey: false, - detail: 0, - key: 'a', - code: 'KeyA', - keyCode: 97, // deprecated - location: 0, - metaKey: false, - repeat: false, - shiftKey: false, - type: 'keypress', - which: 97, // deprecated - }) - - done() - }) - - cy.get(':text:first').type('a') - }) - - it('receives keyup event', (done) => { - const $txt = cy.$$(':text:first') - - $txt.on('keyup', (e) => { - expect(_.toPlainObject(e.originalEvent)).to.include({ - altKey: false, - bubbles: true, - cancelable: true, - charCode: 0, // deprecated - ctrlKey: false, - detail: 0, - key: 'a', - code: 'KeyA', - keyCode: 65, // deprecated but fired by chrome always uppercase in the ASCII table - location: 0, - metaKey: false, - repeat: false, - shiftKey: false, - type: 'keyup', - view: cy.state('window'), - which: 65, // deprecated but fired by chrome - }) - .not.have.property('inputType') - - done() - }) - - cy.get(':text:first').type('a') - }) - - it('receives textInput event', (done) => { - const $txt = cy.$$(':text:first') - - $txt[0].addEventListener('textInput', (e) => { - // FIXME: (firefox) firefox cannot access window objects else throw cross-origin error - expect(Object.prototype.toString.call(e.view)).eq('[object Window]') - e.view = null - expect(_.toPlainObject(e)).to.include({ - bubbles: true, - cancelable: true, - data: 'a', - detail: 0, - type: 'textInput', - // view: cy.state('window'), - which: 0, - }) - - done() - }) - - cy.get(':text:first').type('a') - }) - - it('receives input event', (done) => { - const $txt = cy.$$(':text:first') - - $txt.on('input', (e) => { - const obj = _.pick(e.originalEvent, 'bubbles', 'cancelable', 'type') - - expect(obj).to.deep.eq({ - bubbles: true, - cancelable: false, - type: 'input', - }) - - done() - }) - - cy.get(':text:first').type('a') - }) - - it('fires events in the correct order') - - it('fires events for each key stroke') - - it('does fire input event when value changes', () => { - const onInput = cy.stub() - - cy.$$(':text:first').on('input', onInput) - - cy.get(':text:first') - .invoke('val', 'bar') - .type('{selectAll}{rightarrow}{backspace}') - .then(() => { - expect(onInput).to.be.calledOnce - }) - .then(() => { - onInput.resetHistory() - }) - - cy.get(':text:first') - .invoke('val', 'bar') - .type('{selectAll}{leftarrow}{del}') - .then(() => { - expect(onInput).to.be.calledOnce - }) - .then(() => { - onInput.resetHistory() - }) - - cy.$$('[contenteditable]:first').on('input', onInput) - - cy.get('[contenteditable]:first') - .invoke('html', 'foobar') - .type('{selectAll}{rightarrow}{backspace}') - .then(() => { - expect(onInput).to.be.calledOnce - }) - .then(() => { - onInput.resetHistory() - }) - - cy.get('[contenteditable]:first') - .invoke('html', 'foobar') - .type('{selectAll}{leftarrow}{del}') - .then(() => { - expect(onInput).to.be.calledOnce - }) - }) - - it('does not fire input event when value does not change', () => { - let fired = false - - cy.$$(':text:first').on('input', () => { - fired = true - }) - - cy.get(':text:first') - .invoke('val', 'bar') - .type('{selectAll}{rightarrow}{del}') - .then(() => { - expect(fired).to.eq(false) - }) - - cy.get(':text:first') - .invoke('val', 'bar') - .type('{selectAll}{leftarrow}{backspace}') - .then(() => { - expect(fired).to.eq(false) - }) - - cy.$$('textarea:first').on('input', () => { - fired = true - }) - - cy.get('textarea:first') - .invoke('val', 'bar') - .type('{selectAll}{rightarrow}{del}') - .then(() => { - expect(fired).to.eq(false) - }) - - cy.get('textarea:first') - .invoke('val', 'bar') - .type('{selectAll}{leftarrow}{backspace}') - .then(() => { - expect(fired).to.eq(false) - }) - - cy.$$('[contenteditable]:first').on('input', () => { - fired = true - }) - - cy.get('[contenteditable]:first') - .invoke('html', 'foobar') - .type('{movetoend}') - .then(($el) => { - expect(fired).to.eq(false) - }) - - cy.get('[contenteditable]:first') - .invoke('html', 'foobar') - .type('{selectAll}{leftarrow}{backspace}') - .then(() => { - expect(fired).to.eq(false) - }) - }) - }) - describe('maxlength', () => { it('limits text entered to the maxlength attribute of a text input', () => { const $input = cy.$$(':text:first') @@ -2876,337 +2446,6 @@ describe('src/cy/commands/actions/type - #type', () => { }) }) - describe('click events', () => { - it('passes timeout and interval down to click', (done) => { - const input = $('').attr('id', 'input-covered-in-span').prependTo(cy.$$('body')) - - $('span on input') - .css({ - position: 'absolute', - left: input.offset().left, - top: input.offset().top, - padding: 5, - display: 'inline-block', - backgroundColor: 'yellow', - }) - .prependTo(cy.$$('body')) - - cy.on('command:retry', (options) => { - expect(options.timeout).to.eq(1000) - expect(options.interval).to.eq(60) - - done() - }) - - cy.get('#input-covered-in-span').type('foobar', { timeout: 1000, interval: 60 }) - }) - - it('does not issue another click event between type/type', () => { - const clicked = cy.stub() - - cy.$$(':text:first').click(clicked) - - cy.get(':text:first').type('f').type('o').then(() => { - expect(clicked).to.be.calledOnce - }) - }) - - it('does not issue another click event if element is already in focus from click', () => { - const clicked = cy.stub() - - cy.$$(':text:first').click(clicked) - - cy.get(':text:first').click().type('o').then(() => { - expect(clicked).to.be.calledOnce - }) - }) - }) - - describe('change events', () => { - it('fires when enter is pressed and value has changed', () => { - const changed = cy.stub() - - cy.$$(':text:first').change(changed) - - cy.get(':text:first').invoke('val', 'foo').type('bar{enter}').then(() => { - expect(changed).to.be.calledOnce - }) - }) - - it('fires twice when enter is pressed and then again after losing focus', () => { - const changed = cy.stub() - - cy.$$(':text:first').change(changed) - - cy.get(':text:first').invoke('val', 'foo').type('bar{enter}baz').blur().then(() => { - expect(changed).to.be.calledTwice - }) - }) - - it('fires when element loses focus due to another action (click)', () => { - const changed = cy.stub() - - cy.$$(':text:first').change(changed) - - cy - .get(':text:first').type('foo').then(() => { - expect(changed).not.to.be.called - }) - .get('button:first').click().then(() => { - expect(changed).to.be.calledOnce - }) - }) - - it('fires when element loses focus due to another action (type)', () => { - const changed = cy.stub() - - cy.$$(':text:first').change(changed) - - cy - .get(':text:first').type('foo').then(() => { - expect(changed).not.to.be.called - }) - .get('textarea:first').type('bar').then(() => { - expect(changed).to.be.calledOnce - }) - }) - - it('fires when element is directly blurred', () => { - const changed = cy.stub() - - cy.$$(':text:first').change(changed) - - cy - .get(':text:first').type('foo').blur().then(() => { - expect(changed).to.be.calledOnce - }) - }) - - it('fires when element is tabbed away from')//, -> - // changed = 0 - - // cy.$$(":text:first").change -> - // changed += 1 - - // cy.get(":text:first").invoke("val", "foo").type("b{tab}").then -> - // expect(changed).to.eq 1 - - it('does not fire twice if element is already in focus between type/type', () => { - const changed = cy.stub() - - cy.$$(':text:first').change(changed) - - cy.get(':text:first').invoke('val', 'foo').type('f').type('o{enter}').then(() => { - expect(changed).to.be.calledOnce - }) - }) - - it('does not fire twice if element is already in focus between clear/type', () => { - const changed = cy.stub() - - cy.$$(':text:first').change(changed) - - cy.get(':text:first').invoke('val', 'foo').clear().type('o{enter}').then(() => { - expect(changed).to.be.calledOnce - }) - }) - - it('does not fire twice if element is already in focus between click/type', () => { - const changed = cy.stub() - - cy.$$(':text:first').change(changed) - - cy.get(':text:first').invoke('val', 'foo').click().type('o{enter}').then(() => { - expect(changed).to.be.calledOnce - }) - }) - - it('does not fire twice if element is already in focus between type/click', () => { - const changed = cy.stub() - - cy.$$(':text:first').change(changed) - - cy.get(':text:first').invoke('val', 'foo').type('d{enter}').click().then(() => { - expect(changed).to.be.calledOnce - }) - }) - - it('does not fire at all between clear/type/click', () => { - const changed = cy.stub() - - cy.$$(':text:first').change(changed) - - cy.get(':text:first').invoke('val', 'foo').clear().type('o').click().then(($el) => { - expect(changed).not.to.be.called - - return $el - }).blur() - .then(() => { - expect(changed).to.be.calledOnce - }) - }) - - it('does not fire if {enter} is preventedDefault', () => { - const changed = cy.stub() - - cy.$$(':text:first').keypress((e) => { - if (e.which === 13) { - e.preventDefault() - } - }) - - cy.$$(':text:first').change(changed) - - cy.get(':text:first').invoke('val', 'foo').type('b{enter}').then(() => { - expect(changed).not.to.be.called - }) - }) - - it('does not fire when enter is pressed and value hasnt changed', () => { - const changed = cy.stub() - - cy.$$(':text:first').change(changed) - - cy.get(':text:first').invoke('val', 'foo').type('b{backspace}{enter}').then(() => { - expect(changed).not.to.be.called - }) - }) - - it('does not fire at the end of the type', () => { - const changed = cy.stub() - - cy.$$(':text:first').change(changed) - - cy - .get(':text:first').type('foo').then(() => { - expect(changed).not.to.be.called - }) - }) - - it('does not fire change event if value hasnt actually changed', () => { - const changed = cy.stub() - - cy.$$(':text:first').change(changed) - - cy - .get(':text:first').invoke('val', 'foo').type('{backspace}{backspace}oo{enter}').blur().then(() => { - expect(changed).not.to.be.called - }) - }) - - it('does not fire if mousedown is preventedDefault which prevents element from losing focus', () => { - const changed = cy.stub() - - cy.$$(':text:first').change(changed) - - cy.$$('textarea:first').mousedown(() => { - return false - }) - - cy - .get(':text:first').invoke('val', 'foo').type('bar') - .get('textarea:first').click().then(() => { - expect(changed).not.to.be.called - }) - }) - - it('does not fire hitting {enter} inside of a textarea', () => { - const changed = cy.stub() - - cy.$$('textarea:first').change(changed) - - cy - .get('textarea:first').type('foo{enter}bar').then(() => { - expect(changed).not.to.be.called - }) - }) - - it('does not fire hitting {enter} inside of [contenteditable]', () => { - const changed = cy.stub() - - cy.$$('[contenteditable]:first').change(changed) - - cy - .get('[contenteditable]:first').type('foo{enter}bar').then(() => { - expect(changed).not.to.be.called - }) - }) - - // [contenteditable] does not fire ANY change events ever. - it('does not fire at ALL for [contenteditable]', () => { - const changed = cy.stub() - - cy.$$('[contenteditable]:first').change(changed) - - cy - .get('[contenteditable]:first').type('foo') - .get('button:first').click().then(() => { - expect(changed).not.to.be.called - }) - }) - - it('does not fire on .clear() without blur', () => { - const changed = cy.stub() - - cy.$$('input:first').change(changed) - - cy.get('input:first').invoke('val', 'foo') - .clear() - .then(($el) => { - expect(changed).not.to.be.called - - return $el - }).type('foo') - .blur() - .then(() => { - expect(changed).not.to.be.called - }) - }) - - it('fires change for single value change inputs', () => { - const changed = cy.stub() - - cy.$$('input[type="date"]:first').change(changed) - - cy.get('input[type="date"]:first') - .type('1959-09-13') - .blur() - .then(() => { - expect(changed).to.be.calledOnce - }) - }) - - it('does not fire change for non-change single value input', () => { - const changed = cy.stub() - - cy.$$('input[type="date"]:first').change(changed) - - cy.get('input[type="date"]:first') - .invoke('val', '1959-09-13') - .type('1959-09-13') - .blur() - .then(() => { - expect(changed).not.to.be.called - }) - }) - - it('does not fire change for type\'d change that restores value', () => { - const changed = cy.stub() - - cy.$$('input:first').change(changed) - - cy.get('input:first') - .invoke('val', 'foo') - .type('{backspace}o') - .invoke('val', 'bar') - .type('{backspace}r') - .blur() - .then(() => { - expect(changed).not.to.be.called - }) - }) - }) - describe('single value change inputs', () => { // https://github.com/cypress-io/cypress/issues/5476 it('fires all keyboard events', () => { From 66229039b87dd96fa8f3150904154f78ec5d8da3 Mon Sep 17 00:00:00 2001 From: KHeo Date: Wed, 16 Feb 2022 11:00:07 +0900 Subject: [PATCH 2/3] feedback --- .../cypress/integration/commands/actions/type_events_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/driver/cypress/integration/commands/actions/type_events_spec.js b/packages/driver/cypress/integration/commands/actions/type_events_spec.js index 697c1dd670e7..58e281b92b50 100644 --- a/packages/driver/cypress/integration/commands/actions/type_events_spec.js +++ b/packages/driver/cypress/integration/commands/actions/type_events_spec.js @@ -5,7 +5,7 @@ describe('src/cy/commands/actions/type - #type events', () => { cy.visit('fixtures/dom.html') }) - describe('events', () => { + describe('keyboard events', () => { it('receives keydown event', (done) => { const $txt = cy.$$(':text:first') From c944ac6e72b874a8a0d63c475236acd545831451 Mon Sep 17 00:00:00 2001 From: KHeo Date: Mon, 21 Feb 2022 09:34:32 +0900 Subject: [PATCH 3/3] Add TODO comments --- .../cypress/integration/commands/actions/type_events_spec.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/driver/cypress/integration/commands/actions/type_events_spec.js b/packages/driver/cypress/integration/commands/actions/type_events_spec.js index 58e281b92b50..240c687f7c86 100644 --- a/packages/driver/cypress/integration/commands/actions/type_events_spec.js +++ b/packages/driver/cypress/integration/commands/actions/type_events_spec.js @@ -134,6 +134,8 @@ describe('src/cy/commands/actions/type - #type events', () => { cy.get(':text:first').type('a') }) + // https://github.com/cypress-io/cypress/issues/20283 + // TODO: Implement tests below. it('fires events in the correct order') it('fires events for each key stroke') @@ -348,6 +350,8 @@ describe('src/cy/commands/actions/type - #type events', () => { }) }) + // https://github.com/cypress-io/cypress/issues/20283 + // TODO: implement this test it('fires when element is tabbed away from')//, -> // changed = 0