UNPKG

@revoloo/cypress6

Version:

Cypress.io end to end testing tool

896 lines (693 loc) 23.2 kB
const { _, $ } = Cypress const getActiveElement = () => { return cy.state('document').activeElement } describe('src/cy/commands/actions/focus', () => { before(() => { 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) }) context('#focus', () => { it('sends a focus event', () => { let focus = false cy.$$('#focus input').focus(() => { focus = true }) cy.get('#focus input').focus().then(($input) => { expect(focus).to.be.true expect(getActiveElement()).to.eq($input.get(0)) }) }) it('bubbles focusin event', () => { let focusin = false cy.$$('#focus').focusin(() => { focusin = true }) cy.get('#focus input').focus().then(() => { expect(focusin).to.be.true }) }) it('manually blurs focused subject as a fallback', () => { let blurred = false cy.$$('input:first').blur(() => { blurred = true }) cy .get('input:first').focus() .get('#focus input').focus() .then(() => { expect(blurred).to.be.true }) }) it('matches cy.focused()', () => { const button = cy.$$('#button') cy .get('#button').focus().focused() .then(($focused) => { expect($focused.get(0)).to.eq(button.get(0)) }) }) it('returns the original subject', () => { const button = cy.$$('#button') cy.get('#button').focus().then(($button) => { expect($button).to.match(button) }) }) it('causes first focused element to receive blur', () => { let blurred = false cy.$$('input:first').blur(() => { blurred = true }) cy .get('input:first').focus() .get('input:last').focus() .then(() => { expect(blurred).to.be.true }) }) // https://allyjs.io/data-tables/focusable.html#footnote-3 // body is focusable, but it will not steal focus away // from another activeElement or cause any focus or blur events // to fire it('can focus the body but does not fire focus or blur events', () => { let doc const { body } = (doc = cy.state('document')) let focused = false let blurred = false const onFocus = () => { focused = true } const onBlur = () => { blurred = true } cy .get('input:first').focus().then(($input) => { expect(doc.activeElement).to.eq($input.get(0)) $input.get(0).addEventListener('blur', onBlur) body.addEventListener('focus', onFocus) cy.get('body').focus().then(() => { // should not have changed actual activeElement expect(doc.activeElement).to.eq($input.get(0)) $input.get(0).removeEventListener('blur', onBlur) body.removeEventListener('focus', onFocus) expect(focused).to.be.false expect(blurred).to.be.false }) }) }) it('can focus the window but does not change activeElement or fire blur events', () => { const win = cy.state('window') const doc = cy.state('document') let focused = false let blurred = false const onFocus = () => { focused = true } const onBlur = () => { blurred = true } cy .get('input:first').focus().then(($input) => { expect(doc.activeElement).to.eq($input.get(0)) $input.get(0).addEventListener('blur', onBlur) win.addEventListener('focus', onFocus) cy.window().focus().then(() => { // should not have changed actual activeElement expect(doc.activeElement).to.eq($input.get(0)) $input.get(0).removeEventListener('blur', onBlur) win.removeEventListener('focus', onFocus) expect(focused).to.be.true expect(blurred).to.be.false }) }) }) it('can focus [contenteditable]', () => { const ce = cy.$$('[contenteditable]:first') cy .get('[contenteditable]:first').focus() .focused().then(($ce) => { expect($ce.get(0)).to.eq(ce.get(0)) }) }) it('can focus svg elements', () => { const onFocus = cy.stub() cy.$$('[data-cy=rect]').focus(onFocus) cy.get('[data-cy=rect]').focus().then(() => { expect(onFocus).to.be.calledOnce }) }) it('can focus on readonly inputs', () => { const onFocus = cy.stub() cy.$$('#readonly-attr').focus(onFocus) cy.get('#readonly-attr').focus().then(() => { expect(onFocus).to.be.calledOnce }) }) describe('assertion verification', () => { beforeEach(function () { cy.on('log:added', (attrs, log) => { if (log.get('name') === 'assert') { this.lastLog = log } }) return null }) it('eventually passes the assertion', () => { cy.$$(':text:first').focus(function () { _.delay(() => { $(this).addClass('focused') }, 100) }) cy.get(':text:first').focus().should('have.class', 'focused').then(function () { const { lastLog } = this expect(lastLog.get('name')).to.eq('assert') expect(lastLog.get('state')).to.eq('passed') expect(lastLog.get('ended')).to.be.true }) }) }) describe('.log', () => { beforeEach(function () { this.logs = [] cy.on('log:added', (attrs, log) => { if (attrs.name === 'focus') { this.lastLog = log this.logs.push(log) } }) return null }) it('logs immediately before resolving', () => { const $input = cy.$$(':text:first') let expected = false // we can't end early here because our focus() // command will still be in flight and the promise // chain will get canceled before it gets attached // (besides the code will continue to run and create // side effects) cy.on('log:added', (attrs, log) => { if (log.get('name') === 'focus') { expect(log.get('state')).to.eq('pending') expect(log.get('$el').get(0)).to.eq($input.get(0)) expected = true } }) cy.get(':text:first').focus().then(() => { expect(expected).to.be.true }) }) it('snapshots after clicking', () => { cy.get(':text:first').focus().then(function () { const { lastLog } = this expect(lastLog.get('snapshots').length).to.eq(1) expect(lastLog.get('snapshots')[0]).to.be.an('object') }) }) it('passes in $el', () => { cy.get('input:first').focus().then(function ($input) { const { lastLog } = this expect(lastLog.get('$el')).to.eq($input) }) }) it('logs 2 focus event', () => { cy .get('input:first').focus() .get('button:first').focus().then(function () { expect(this.logs.length).to.eq(2) }) }) it('#consoleProps', () => { cy.get('input:first').focus().then(function ($input) { expect(this.lastLog.invoke('consoleProps')).to.deep.eq({ Command: 'focus', 'Applied To': $input.get(0), }) }) }) }) describe('errors', { defaultCommandTimeout: 100, }, () => { beforeEach(function () { this.logs = [] cy.on('log:added', (attrs, log) => { this.lastLog = log this.logs.push(log) }) return null }) it('throws when not a dom subject', (done) => { cy.on('fail', () => { done() }) cy.noop({}).focus() }) it('throws when subject is not in the document', (done) => { let focused = 0 const $input = cy.$$('input:first').focus((e) => { focused += 1 $input.remove() return false }) cy.on('fail', (err) => { expect(focused).to.eq(1) expect(err.message).to.include('`cy.focus()` failed because this element') done() }) cy.get('input:first').focus().focus() }) it('throws when not a[href],link[href],button,input,select,textarea,[tabindex]', (done) => { cy.on('fail', (err) => { expect(err.message).to.include('`cy.focus()` can only be called on a valid focusable element. Your subject is a: `<form id="by-id">...</form>`') expect(err.docsUrl).to.eq('https://on.cypress.io/focus') done() }) cy.get('form').focus() }) it('throws when subject is a collection of elements', function (done) { cy .get('textarea,:text').then(function ($inputs) { this.num = $inputs.length return $inputs }).focus() cy.on('fail', (err) => { expect(err.message).to.include(`\`cy.focus()\` can only be called on a single element. Your subject contained ${this.num} elements.`) expect(err.docsUrl).to.eq('https://on.cypress.io/focus') done() }) }) 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.focus() }) // TODO: dont skip this it.skip('slurps up failed promises', (done) => { cy.timeout(1000) // we only want to test when the document // isnt in focus if (cy.state('document').hasFocus()) { done() } // Preserved for later use. // // now = cy.now // // nowFn = (cmd) -> // ## we stub cy.now so that when we're about to blur // ## we cause isInDom to return false when its given // ## the last input element // if cmd is "blur" // cy.stub(cy, "isInDom").returns(false) // // now.apply(@, arguments) // // cy.stub(cy, "now", nowFn) const $first = cy.$$('input:first') // Preserved for later use. // const $last = cy.$$('input:last') $first.on('focus', function () { return $(this).remove() }) cy.on('fail', (err) => { expect(err.message).to.include('`cy.blur()` failed because this element') done() }) // we remove the first element and then // focus on the 2nd. the 2nd focus causes // a blur on the 1st element, which should // cause an error because its no longer in the DOM return cy .get('input:first').focus() .get('input:last').focus() .then(() => { // sometimes hasFocus() returns false // even though its really in focus // in those cases, just pass // i cant come up with another way // to test this accurately done() }) }) it('eventually fails the assertion', function (done) { cy.on('fail', (err) => { const { lastLog } = this expect(err.message).to.include(lastLog.get('error').message) 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) done() }) cy.get(':text:first').focus().should('have.class', 'focused') }) it('does not log an additional log on failure', function (done) { cy.on('fail', () => { expect(this.logs.length).to.eq(3) done() }) cy.get(':text:first').focus().should('have.class', 'focused') }) }) }) context('#blur', () => { it('should blur the originally focused element', () => { let blurred = false cy.$$('#focus input').blur(() => { blurred = true }) cy.get('#focus').within(() => { cy .get('input').focus() .then(($input) => { expect(getActiveElement()).to.eq($input.get(0)) }).get('button').focus() .then(($btn) => { expect(blurred).to.be.true expect(getActiveElement()).to.eq($btn.get(0)) }) }) }) it('sends a focusout event', () => { let focusout = false cy.$$('#focus').focusout(() => { focusout = true }) cy.get('#focus input').focus().blur().then(() => { expect(focusout).to.be.true }) }) it('sends a blur event', () => { let blurred = false cy.$$('input:first').blur(() => { blurred = true }) cy.get('input:first').focus().blur().then(() => { expect(blurred).to.be.true }) }) it('returns the original subject', () => { const input = cy.$$('input:first') cy.get('input:first').focus().blur().then(($input) => { expect($input).to.match(input) }) }) it('can blur the body but does not change activeElement or fire blur events', () => { const doc = cy.state('document') const { body } = doc let blurred = false const onBlur = () => { blurred = true } body.addEventListener('blur', onBlur) cy .get('body').blur().then(() => { expect(blurred).to.be.false }).get('input:first').focus().then(($input) => { cy .get('body').blur({ force: true }) .then(() => { expect(doc.activeElement).to.eq($input.get(0)) body.removeEventListener('blur', onBlur) expect(blurred).to.be.false }) }) }) it('can blur the window but does not change activeElement', () => { const win = cy.state('window') const doc = cy.state('document') let blurred = false const onBlur = () => { blurred = true } win.addEventListener('blur', onBlur) cy .window().blur().then(() => { expect(blurred).to.be.true }).get('input:first').focus().then(($input) => { cy .window().blur({ force: true }) .then(() => { expect(doc.activeElement).to.eq($input.get(0)) win.removeEventListener('blur', onBlur) }) }) }) it('can blur [contenteditable]', () => { const ce = cy.$$('[contenteditable]:first') cy .get('[contenteditable]:first').focus().blur().then(($ce) => { expect($ce.get(0)).to.eq(ce.get(0)) }) }) it('can blur input[type=time]', () => { let blurred = false cy.$$('#time-without-value').blur(() => { blurred = true }) cy .get('#time-without-value').focus().invoke('val', '03:15:00').blur() .then(() => { expect(blurred).to.be.true }) }) it('can blur tabindex', () => { let blurred = false cy .$$('#comments').blur(() => { blurred = true }).get(0).focus() cy.get('#comments').blur().then(() => { expect(blurred).to.be.true }) }) it('can force blurring on a non-focused element', () => { let blurred = false cy.$$('input:first').blur(() => { blurred = true }) cy .get('input:last').focus() .get('input:first').blur({ force: true }) .then(() => { expect(blurred).to.be.true }) }) it('can force blurring when there is no focused element', () => { let blurred = false cy.$$('input:first').blur(() => { blurred = true }) cy .focused().should('not.exist') .get('input:first').blur({ force: true }) .then(() => { expect(blurred).to.be.true }) }) it('can focus svg elements', () => { const onBlur = cy.stub() cy.$$('[data-cy=rect]').blur(onBlur) cy.get('[data-cy=rect]').focus().blur().then(() => { expect(onBlur).to.be.calledOnce }) }) describe('assertion verification', () => { beforeEach(function () { cy.on('log:added', (attrs, log) => { if (log.get('name') === 'assert') { this.lastLog = log } }) return null }) it('eventually passes the assertion', () => { cy.$$(':text:first').blur(function () { _.delay(() => { $(this).addClass('blured') }, 100) }) cy.get(':text:first').focus().blur().should('have.class', 'blured').then(function () { const { lastLog } = this expect(lastLog.get('name')).to.eq('assert') expect(lastLog.get('state')).to.eq('passed') expect(lastLog.get('ended')).to.be.true }) }) }) describe('.log', () => { beforeEach(function () { this.logs = [] cy.on('log:added', (attrs, log) => { if (attrs.name === 'blur') { this.lastLog = log return this.logs.push(log) } }) return null }) it('logs immediately before resolving', () => { const input = cy.$$(':text:first') let expected = false cy.on('log:added', (attrs, log) => { if (log.get('name') === 'blur') { expect(log.get('state')).to.eq('pending') expect(log.get('$el').get(0)).to.eq(input.get(0)) expected = true } }) cy.get(':text:first').focus().blur().then(() => { expect(expected).to.be.true }) }) it('snapshots after clicking', () => { cy.get(':text:first').focus().blur().then(function () { const { lastLog } = this expect(lastLog.get('snapshots').length).to.eq(1) expect(lastLog.get('snapshots')[0]).to.be.an('object') }) }) it('passes in $el', () => { cy.get('input:first').focus().blur().then(function ($input) { const { lastLog } = this expect(lastLog.get('$el')).to.eq($input) }) }) it('logs 1 blur event', () => { cy.get('input:first').focus().blur().then(function () { expect(this.logs.length).to.eq(1) }) }) it('logs delta options for {force: true}', () => { cy.get('input:first').blur({ force: true }).then(function () { const { lastLog } = this expect(lastLog.get('message')).to.eq('{force: true}') }) }) it('#consoleProps', () => { cy.get('input:first').focus().blur().then(function ($input) { expect(this.lastLog.invoke('consoleProps')).to.deep.eq({ Command: 'blur', 'Applied To': $input.get(0), }) }) }) }) describe('errors', { defaultCommandTimeout: 100, }, () => { beforeEach(function () { this.logs = [] cy.on('log:added', (attrs, log) => { this.lastLog = log this.logs.push(log) }) return null }) it('throws when not a dom subject', (done) => { cy.on('fail', (err) => { expect(err.message).to.include('`cy.blur()` failed because it requires a DOM element') done() }) cy.noop({}).blur() }) it('throws when subject is not in the document', (done) => { let blurred = 0 const $input = cy.$$('input:first').blur(() => { blurred += 1 $input.focus(() => { $input.remove() return false }) return false }) cy.on('fail', (err) => { expect(blurred).to.eq(1) expect(err.message).to.include('`cy.blur()` failed because this element') expect(err.docsUrl).to.include('https://on.cypress.io/element-has-detached-from-dom') done() }) cy.get('input:first').focus().blur().focus().blur() }) it('throws when subject is a collection of elements', (done) => { const num = cy.$$('textarea,:text').length cy.on('fail', (err) => { expect(err.message).to.include(`\`cy.blur()\` can only be called on a single element. Your subject contained ${num} elements.`) expect(err.docsUrl).to.include('https://on.cypress.io/blur') done() }) cy.get('textarea,:text').blur() }) it('throws when there isnt an activeElement', (done) => { cy.on('fail', (err) => { expect(err.message).to.include('`cy.blur()` can only be called when there is a currently focused element.') expect(err.docsUrl).to.include('https://on.cypress.io/blur') done() }) cy.get('form:first').blur() }) it('throws when blur is called on a non-active element', (done) => { cy.on('fail', (err) => { expect(err.message).to.include('`cy.blur()` can only be called on the focused element. Currently the focused element is a: `<input id="input">`') expect(err.docsUrl).to.include('https://on.cypress.io/blur') done() }) cy.get('input:first').focus() cy.get('#button').blur() }) it('logs delta options on error', function (done) { cy.$$('button:first').click(function () { $(this).remove() }) cy.on('fail', () => { const { lastLog } = this expect(lastLog.get('message')).to.eq('{force: true}') done() }) cy.get('button:first').click().blur({ force: true }) }) 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.blur() }) it('eventually fails the assertion', function (done) { cy.on('fail', (err) => { const { lastLog } = this expect(err.message).to.include(lastLog.get('error').message) 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) done() }) cy.get(':text:first').focus().blur().should('have.class', 'blured') }) it('does not log an additional log on failure', function (done) { cy.on('fail', () => { expect(this.logs.length).to.eq(4) done() }) cy.get(':text:first').focus().blur().should('have.class', 'blured') }) it('can handle window w/length > 1 as a subject', () => { cy.window().should('have.length', 2).focus() }) }) }) })