const { assertLogLength } = require('../../support/utils')
const { _, $ } = Cypress

describe('src/cy/commands/window', () => {
  context('#window', () => {
    it('returns the remote window', () => {
      cy.window().then((win) => {
        expect(win).to.eq(cy.state('$autIframe').prop('contentWindow'))
      })
    })

    describe('assertion verification', {
      defaultCommandTimeout: 100,
    }, () => {
      beforeEach(function () {
        this.remoteWindow = cy.state('window')

        delete this.remoteWindow.foo

        this.logs = []

        cy.on('log:added', (attrs, log) => {
          this.lastLog = log
          this.logs.push(log)
        })

        return null
      })

      it('eventually passes the assertion', function () {
        cy.on('command:retry', _.after(2, () => {
          this.remoteWindow.foo = 'bar'
        }))

        cy.window().should('have.property', 'foo', 'bar').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
        })
      })

      it('eventually fails the assertion', function (done) {
        cy.on('command:retry', _.after(2, () => {
          this.remoteWindow.foo = 'foo'
        }))

        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.window().should('have.property', 'foo', 'bar')
      })

      it('can still fail on window', function (done) {
        const win = cy.state('window')

        cy.state('window', undefined)

        cy.on('fail', (err) => {
          cy.state('window', win)

          const { lastLog } = this

          assertLogLength(this.logs, 1)
          expect(err.message).to.include(lastLog.get('error').message)
          expect(lastLog.get('name')).to.eq('window')
          expect(lastLog.get('state')).to.eq('failed')

          done()
        })

        cy.window()
      })

      it('does not log an additional log on failure', function (done) {
        this.remoteWindow.foo = 'foo'

        cy.on('fail', () => {
          assertLogLength(this.logs, 2)

          done()
        })

        cy.window().should('have.property', 'foo', 'bar')
      })
    })

    describe('.log', () => {
      beforeEach(function () {
        this.logs = []

        cy.on('log:added', (attrs, log) => {
          this.lastLog = log
          this.logs.push(log)
        })

        return null
      })

      it('can turn off logging when protocol is disabled', function () {
        cy.state('isProtocolEnabled', false)
        cy.on('_log:added', (attrs, log) => {
          this.hiddenLog = log
        })

        cy.window({ log: false }).then(function () {
          const { lastLog, hiddenLog } = this

          expect(lastLog).to.be.undefined
          expect(hiddenLog).to.be.undefined
        })
      })

      it('can send hidden log when protocol is enabled', function () {
        cy.state('isProtocolEnabled', true)
        cy.on('_log:added', (attrs, log) => {
          this.hiddenLog = log
        })

        cy.window({ log: false }).then(function () {
          const { lastLog, hiddenLog } = this

          expect(lastLog).to.be.undefined
          expect(hiddenLog.get('name'), 'log name').to.eq('window')
          expect(hiddenLog.get('hidden'), 'log hidden').to.be.true
          expect(hiddenLog.get('snapshots').length, 'log snapshot length').to.eq(1)
        })
      })

      it('logs immediately before resolving', (done) => {
        cy.on('log:added', (attrs, log) => {
          if (attrs.name === 'window') {
            expect(log.get('state')).to.eq('pending')
            expect(log.get('snapshot')).not.to.be.ok

            done()
          }
        })

        cy.window()
      })

      it('snapshots after resolving', () => {
        cy.window().then(function () {
          const { lastLog } = this

          expect(lastLog.get('snapshots').length).to.eq(1)
          expect(lastLog.get('snapshots')[0]).to.be.an('object')
        })
      })

      it('can be aliased', () => {
        return cy
        .window().as('win')
        .get('body')
        .get('@win').then(function (win) {
        // window + get + get
          assertLogLength(this.logs, 3)

          expect(win).to.eq(this.win)

          expect(this.logs[0].get('alias')).to.eq('@win')
          expect(this.logs[0].get('aliasType')).to.eq('primitive')

          expect(this.logs[2].get('aliasType')).to.eq('primitive')
          expect(this.logs[2].get('referencesAlias').name).to.eq('win')
        })
      })

      it('logs obj', () => {
        cy.window().then(function () {
          const obj = {
            name: 'window',
            message: '',
          }

          const { lastLog } = this

          _.each(obj, (value, key) => {
            expect(lastLog.get(key)).to.deep.eq(value)
          })
        })
      })

      it('#consoleProps', () => {
        cy.window().then(function (win) {
          expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
            name: 'window',
            type: 'command',
            props: {
              Yielded: win,
            },
          })
        })
      })
    })
  })

  context('#document', () => {
    it('returns the remote document as a jquery object', () => {
      cy.document().then(($doc) => {
        expect($doc).to.eq(cy.state('$autIframe').prop('contentDocument'))
      })
    })

    describe('assertion verification', {
      defaultCommandTimeout: 100,
    }, () => {
      beforeEach(function () {
        this.remoteDocument = cy.state('window').document

        delete this.remoteDocument.foo

        this.logs = []

        cy.on('log:added', (attrs, log) => {
          this.lastLog = log
          this.logs.push(log)
        })

        return null
      })

      it('eventually passes the assertion', function () {
        cy.on('command:retry', _.after(2, () => {
          this.remoteDocument.foo = 'bar'
        }))

        cy.document().should('have.property', 'foo', 'bar').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
        })
      })

      it('eventually fails the assertion', function (done) {
        cy.on('command:retry', _.after(2, () => {
          this.remoteDocument.foo = 'foo'
        }))

        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.document().should('have.property', 'foo', 'bar')
      })

      it('can still fail on document', function (done) {
        const win = cy.state('window')

        cy.state('window', undefined)

        cy.on('fail', (err) => {
          cy.state('window', win)

          const { lastLog } = this

          assertLogLength(this.logs, 1)
          expect(err.message).to.include(lastLog.get('error').message)
          expect(lastLog.get('name')).to.eq('document')
          expect(lastLog.get('state')).to.eq('failed')

          done()
        })

        cy.document()
      })

      it('does not log an additional log on failure', function (done) {
        this.remoteDocument.foo = 'foo'

        cy.on('fail', () => {
          assertLogLength(this.logs, 2)

          done()
        })

        cy.document().should('have.property', 'foo', 'bar')
      })
    })

    describe('.log', () => {
      beforeEach(function () {
        this.logs = []

        cy.on('log:added', (attrs, log) => {
          this.lastLog = log
          this.logs.push(log)
        })

        return null
      })

      it('can turn off logging when protocol is disabled', function () {
        cy.state('isProtocolEnabled', false)
        cy.on('_log:added', (attrs, log) => {
          this.hiddenLog = log
        })

        cy.document({ log: false }).then(function () {
          const { lastLog, hiddenLog } = this

          expect(lastLog).to.be.undefined
          expect(hiddenLog).to.be.undefined
        })
      })

      it('can send hidden log when protocol is enabled', function () {
        cy.state('isProtocolEnabled', true)
        cy.on('_log:added', (attrs, log) => {
          this.hiddenLog = log
        })

        cy.document({ log: false }).then(function () {
          const { lastLog, hiddenLog } = this

          expect(lastLog).to.be.undefined

          expect(hiddenLog.get('name')).to.eq('document')
          expect(hiddenLog.get('hidden')).to.be.true
          expect(hiddenLog.get('snapshots').length, 'log snapshot length').to.eq(1)
        })
      })

      it('logs immediately before resolving', (done) => {
        cy.on('log:added', (attrs, log) => {
          if (attrs.name === 'document') {
            expect(log.get('state')).to.eq('pending')
            expect(log.get('snapshots')).not.to.be.ok

            done()
          }
        })

        cy.document()
      })

      it('snapshots after resolving', () => {
        cy.document().then(function () {
          const { lastLog } = this

          expect(lastLog.get('snapshots').length).to.eq(1)
          expect(lastLog.get('snapshots')[0]).to.be.an('object')
        })
      })

      it('can be aliased', function () {
        cy
        .document().as('doc')
        .get('body')
        .get('@doc').then(function (doc) {
          // docdow + get + get
          assertLogLength(this.logs, 3)

          expect(doc).to.eq(this.doc)
          expect(this.logs[0].get('alias')).to.eq('@doc')
          expect(this.logs[0].get('aliasType')).to.eq('primitive')

          expect(this.logs[2].get('aliasType')).to.eq('primitive')
          expect(this.logs[2].get('referencesAlias').name).to.eq('doc')
        })
      })

      it('logs obj', () => {
        cy.document().then(function () {
          const obj = {
            name: 'document',
            message: '',
          }

          const { lastLog } = this

          _.each(obj, (value, key) => {
            expect(lastLog.get(key)).to.deep.eq(value)
          })
        })
      })

      it('#consoleProps', () => {
        cy.document().then(function (win) {
          expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
            name: 'document',
            type: 'command',
            props: {
              Yielded: win,
            },
          })
        })
      })
    })
  })

  context('#title', () => {
    beforeEach(() => {
      cy
      .visit('/fixtures/generic.html')
      .then(function (win) {
        const h = $(win.document.head)

        h.find('script').remove()
      })
    })

    it('returns the pages title as a string', () => {
      const title = cy.$$('title').text()

      cy.title().then((text) => {
        expect(text).to.eq(title)
      })
    })

    it('retries finding the title', () => {
      cy.$$('title').remove()

      const retry = _.after(2, () => {
        cy.$$('head').append($('<title>waiting on title</title>'))
      })

      cy.on('command:retry', retry)

      cy.title().should('eq', 'waiting on title')
    })

    it('eventually resolves', () => {
      _.delay(() => {
        cy.$$('title').text('about page')
      }, 100)

      cy.title().should('eq', 'about page').and('match', /about/)
    })

    it('uses the first title element', () => {
      cy.$$('head').prepend('<title>some title</title>')
      cy.$$('head').prepend('<title>another title</title>')

      cy.title().then(($title) => {
        expect($title).to.eq('another title')
      })
    })

    it('uses document.title setter over <title>', () => {
      const title = cy.$$('title')

      // make sure we have a title element
      expect(title.length).to.eq(1)
      expect(title.text()).not.to.eq('foo')

      cy.state('document').title = 'foo'

      cy.title().then((title) => {
        expect(title).to.eq('foo')
      })
    })

    it('is empty string when no <title>', () => {
      cy.$$('title').remove()

      cy.title().then(($title) => {
        expect($title).to.eq('')
      })
    })

    describe('errors', {
      defaultCommandTimeout: 200,
    }, () => {
      beforeEach(function () {
        this.logs = []

        cy.on('log:added', (attrs, log) => {
          this.lastLog = log
          this.logs.push(log)
        })

        return null
      })

      it('throws after timing out', {
        defaultCommandTimeout: 100,
      }, (done) => {
        cy.$$('title').remove()

        cy.on('fail', (err) => {
          expect(err.message).to.include('expected \'\' to equal \'asdf\'')

          done()
        })

        cy.title().should('eq', 'asdf')
      })

      it('only logs once', function (done) {
        cy.$$('title').remove()

        cy.on('fail', (err) => {
          const { lastLog } = this

          assertLogLength(this.logs, 2)
          expect(err.message).to.include(lastLog.get('error').message)

          done()
        })

        cy.title().should('eq', 'asdf')
      })
    })

    describe('.log', () => {
      beforeEach(function () {
        cy.on('log:added', (attrs, log) => {
          this.lastLog = log

          if (log.get('name') === 'get') {
            throw new Error('`cy.get()` should not have logged out.')
          }
        })

        return null
      })

      it('can turn off logging when protocol is disabled', function () {
        cy.state('isProtocolEnabled', false)
        cy.on('_log:added', (attrs, log) => {
          this.hiddenLog = log
        })

        cy.title({ log: false }).then(function () {
          const { lastLog, hiddenLog } = this

          expect(lastLog).to.be.undefined
          expect(hiddenLog).to.be.undefined
        })
      })

      it('can send hidden log when protocol is enabled', function () {
        cy.state('isProtocolEnabled', true)
        cy.on('_log:added', (attrs, log) => {
          this.hiddenLog = log
        })

        cy.title({ log: false }).then(function () {
          const { lastLog, hiddenLog } = this

          expect(lastLog).to.be.undefined
          expect(hiddenLog.get('name')).to.eq('title')
          expect(hiddenLog.get('hidden')).to.be.true
          expect(hiddenLog.get('snapshots').length, 'log snapshot length').to.eq(1)
        })
      })

      it('logs immediately before resolving', (done) => {
        cy.on('log:added', (attrs, log) => {
          if (log.get('name') === 'title') {
            expect(log.get('state')).to.eq('pending')

            done()
          }
        })

        cy.title()
      })

      it('snapshots after clicking', () => {
        cy.title().then(function () {
          const { lastLog } = this

          expect(lastLog.get('snapshots').length).to.eq(1)
          expect(lastLog.get('snapshots')[0]).to.be.an('object')
        })
      })

      it('logs obj', () => {
        cy.title().then(function () {
          const obj = {
            name: 'title',
          }

          const { lastLog } = this

          _.each(obj, (value, key) => {
            expect(lastLog.get(key)).to.deep.eq(value)
          })
        })
      })

      it('#consoleProps', () => {
        cy.title().then(function () {
          expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
            name: 'title',
            type: 'command',
            props: {
              Yielded: 'Generic HTML Fixture',
            },
          })
        })
      })
    })
  })

  context('#viewport', () => {
    it('triggers \'viewport:changed\' event with dimensions object', () => {
      let expected = false

      cy.on('viewport:changed', (viewport, fn) => {
        expected = true
        expect(viewport).to.deep.eq({ viewportWidth: 800, viewportHeight: 600 })
        expect(fn).to.be.a('function')
      })

      cy.viewport(800, 600).then(() => {
        expect(expected).to.be.true
      })
    })

    it('does not trigger \'viewport:changed\' when changing to the default', () => {
      const fn = function () {
        throw new Error('Should not trigger \'viewport:changed\'')
      }

      Cypress.prependListener('viewport:changed', fn)

      cy.viewport(1000, 660).then(() => {
        return Cypress.removeListener('viewport:changed', fn)
      })
    })

    it('does not trigger \'viewport:changed\' when changing to the same viewport', () => {
      let triggeredOnce = false
      const fn = function () {
        if (triggeredOnce) {
          throw new Error('Should not trigger \'viewport:changed\'')
        }

        triggeredOnce = true
      }

      Cypress.prependListener('viewport:changed', fn)

      cy.viewport(800, 600)
      cy.viewport(800, 600).then(() => {
        return Cypress.removeListener('viewport:changed', fn)
      })
    })

    it('triggers \'viewport:changed\' if width changes', (done) => {
      let finished = false

      setTimeout(() => {
        if (!finished) {
          return done('Timed out before \'viewport:changed\'')
        }
      }, 1000)

      let triggeredOnce = false

      cy.on('viewport:changed', (viewport) => {
        if (triggeredOnce) {
          expect(viewport).to.eql({ viewportWidth: 900, viewportHeight: 600 })
          finished = true
          done()
        }

        triggeredOnce = true
      })

      cy.viewport(800, 600)
      cy.viewport(900, 600)
    })

    it('triggers \'viewport:changed\' if height changes', (done) => {
      let finished = false

      setTimeout(() => {
        if (!finished) {
          return done('Timed out before \'viewport:changed\'')
        }
      }, 1000)

      let triggeredOnce = false

      cy.on('viewport:changed', (viewport) => {
        if (triggeredOnce) {
          expect(viewport).to.eql({ viewportWidth: 800, viewportHeight: 700 })
          finished = true
          done()
        }

        triggeredOnce = true
      })

      cy.viewport(800, 600)
      cy.viewport(800, 700)
    })

    it('sets subject to null', () => {
      cy.viewport('ipad-2').then((subject) => {
        expect(subject).to.be.null
      })
    })

    it('does not modify viewportWidth and viewportHeight in config', () => {
      let expected = false

      cy.on('viewport:changed', () => {
        expected = true
        expect(Cypress.config('viewportWidth')).not.to.eq(800)
        expect(Cypress.config('viewportHeight')).not.to.eq(600)
      })

      cy.viewport(800, 600).then(() => {
        expect(expected).to.be.true
      })
    })

    context('changing viewport', () => {
      it('changes viewport and then resets back to the original', () => {
        const { viewportHeight, viewportWidth } = Cypress.config()

        cy.viewport(500, 400).then(async () => {
          await Cypress.action('runner:test:before:run:async', {
            id: 'r1',
            currentRetry: 0,
          }, Cypress.state('runnable'))
          .then(() => {
            expect(Cypress.config('viewportWidth')).to.eq(viewportWidth)
            expect(Cypress.config('viewportHeight')).to.eq(viewportHeight)
          })
        })
      })
    })

    context('presets', () => {
      it('ipad-2', (done) => {
        cy.on('viewport:changed', (viewport) => {
          expect(viewport).to.deep.eq({ viewportWidth: 768, viewportHeight: 1024 })

          done()
        })

        cy.viewport('ipad-2')
      })

      it('ipad-mini', (done) => {
        cy.on('viewport:changed', (viewport) => {
          expect(viewport).to.deep.eq({ viewportWidth: 768, viewportHeight: 1024 })

          done()
        })

        cy.viewport('ipad-mini')
      })

      it('iphone-xr', (done) => {
        cy.on('viewport:changed', (viewport) => {
          expect(viewport).to.deep.eq({ viewportWidth: 414, viewportHeight: 896 })

          done()
        })

        cy.viewport('iphone-xr')
      })

      it('iphone-x', (done) => {
        cy.on('viewport:changed', (viewport) => {
          expect(viewport).to.deep.eq({ viewportWidth: 375, viewportHeight: 812 })

          done()
        })

        cy.viewport('iphone-x')
      })

      it('iphone-6+', (done) => {
        cy.on('viewport:changed', (viewport) => {
          expect(viewport).to.deep.eq({ viewportWidth: 414, viewportHeight: 736 })

          done()
        })

        cy.viewport('iphone-6+')
      })

      it('iphone-se2', (done) => {
        cy.on('viewport:changed', (viewport) => {
          expect(viewport).to.deep.eq({ viewportWidth: 375, viewportHeight: 667 })

          done()
        })

        cy.viewport('iphone-se2')
      })

      it('iphone-8', (done) => {
        cy.on('viewport:changed', (viewport) => {
          expect(viewport).to.deep.eq({ viewportWidth: 375, viewportHeight: 667 })

          done()
        })

        cy.viewport('iphone-8')
      })

      it('iphone-7', (done) => {
        cy.on('viewport:changed', (viewport) => {
          expect(viewport).to.deep.eq({ viewportWidth: 375, viewportHeight: 667 })

          done()
        })

        cy.viewport('iphone-7')
      })

      it('iphone-6', (done) => {
        cy.on('viewport:changed', (viewport) => {
          expect(viewport).to.deep.eq({ viewportWidth: 375, viewportHeight: 667 })

          done()
        })

        cy.viewport('iphone-6')
      })

      it('iphone-5', (done) => {
        cy.on('viewport:changed', (viewport) => {
          expect(viewport).to.deep.eq({ viewportWidth: 320, viewportHeight: 568 })

          done()
        })

        cy.viewport('iphone-5')
      })

      it('can change the orientation to landspace', (done) => {
        cy.on('viewport:changed', (viewport) => {
          expect(viewport).to.deep.eq({ viewportWidth: 568, viewportHeight: 320 })

          done()
        })

        cy.viewport('iphone-5', 'landscape')
      })

      it('can change the orientation to portrait', (done) => {
        cy.on('viewport:changed', (viewport) => {
          expect(viewport).to.deep.eq({ viewportWidth: 320, viewportHeight: 568 })

          done()
        })

        cy.viewport('iphone-5', 'portrait')
      })

      it('samsung-s10', (done) => {
        cy.on('viewport:changed', (viewport) => {
          expect(viewport).to.deep.eq({ viewportWidth: 360, viewportHeight: 760 })

          done()
        })

        cy.viewport('samsung-s10')
      })

      it('samsung-note9', (done) => {
        cy.on('viewport:changed', (viewport) => {
          expect(viewport).to.deep.eq({ viewportWidth: 414, viewportHeight: 846 })

          done()
        })

        cy.viewport('samsung-note9')
      })

      it('macbook-16', (done) => {
        cy.on('viewport:changed', (viewport) => {
          expect(viewport).to.deep.eq({ viewportWidth: 1536, viewportHeight: 960 })

          done()
        })

        cy.viewport('macbook-16')
      })
    })

    context('errors', {
      defaultCommandTimeout: 50,
    }, () => {
      beforeEach(function () {
        this.logs = []

        cy.on('log:added', (attrs, log) => {
          this.lastLog = log
          this.logs.push(log)
        })

        return null
      })

      it('throws when passed invalid preset', function (done) {
        cy.on('fail', (err) => {
          assertLogLength(this.logs, 1)
          expect(err.message).to.match(/^`cy.viewport\(\)` could not find a preset for: `foo`. Available presets are: /)
          expect(err.docsUrl).to.eq('https://on.cypress.io/viewport')

          done()
        })

        cy.viewport('foo')
      })

      it('throws when passed a string as height', function (done) {
        cy.on('fail', (err) => {
          assertLogLength(this.logs, 1)
          expect(err.message).to.eq('`cy.viewport()` can only accept a string preset or a `width` and `height` as numbers.')
          expect(err.docsUrl).to.eq('https://on.cypress.io/viewport')

          done()
        })

        cy.viewport(800, '600')
      })

      it('throws when passed negative numbers', function (done) {
        cy.on('fail', (err) => {
          assertLogLength(this.logs, 1)
          expect(err.message).to.eq('`cy.viewport()` `width` and `height` must be at least 0px.')
          expect(err.docsUrl).to.eq('https://on.cypress.io/viewport')

          done()
        })

        cy.viewport(800, -600)
      })

      it('does not throw when passed width equal to 0', () => {
        cy.viewport(0, 600)
      })

      it('does not throw when passed width equal to 1000000', () => {
        cy.viewport(200, 1000000)
      })

      it('throws when passed an empty string as width', function (done) {
        cy.on('fail', (err) => {
          assertLogLength(this.logs, 1)
          expect(err.message).to.eq('`cy.viewport()` cannot be passed an empty string.')
          expect(err.docsUrl).to.eq('https://on.cypress.io/viewport')

          done()
        })

        cy.viewport('')
      })

      it('throws when passed an invalid orientation on a preset', function (done) {
        cy.on('fail', (err) => {
          assertLogLength(this.logs, 1)
          expect(err.message).to.eq('`cy.viewport()` can only accept `landscape` or `portrait` as valid orientations. Your orientation was: `foobar`')
          expect(err.docsUrl).to.eq('https://on.cypress.io/viewport')

          done()
        })

        cy.viewport('iphone-4', 'foobar')
      })

      _.each([{}, [], NaN, Infinity, null, undefined], (val) => {
        it(`throws when passed the invalid: '${val}' as width`, function (done) {
          const logs = []

          cy.on('log:added', (attrs, log) => {
            logs.push(log)
          })

          cy.on('fail', (err) => {
            assertLogLength(this.logs, 1)
            expect(err.message).to.eq('`cy.viewport()` can only accept a string preset or a `width` and `height` as numbers.')
            expect(err.docsUrl).to.eq('https://on.cypress.io/viewport')

            done()
          })

          cy.viewport(val)
        })
      })
    })

    context('.log', () => {
      beforeEach(function () {
        this.logs = []

        cy.on('log:added', (attrs, log) => {
          this.lastLog = log
          this.logs.push(log)
        })

        return null
      })

      it('logs viewport', () => {
        cy.viewport(800, 600).then(function () {
          const { lastLog } = this

          expect(lastLog.get('name')).to.eq('viewport')
        })
      })

      it('logs viewport with width, height', () => {
        cy.viewport(800, 600).then(function () {
          const { lastLog } = this

          expect(lastLog.get('message')).to.eq('800, 600')
        })
      })

      it('logs viewport with preset', () => {
        cy.viewport('ipad-2').then(function () {
          const { lastLog } = this

          expect(lastLog.get('message')).to.eq('ipad-2')
        })
      })

      it('sets state to success immediately', () => {
        cy.viewport(800, 600).then(function () {
          const { lastLog } = this

          expect(lastLog.get('state')).to.eq('passed')
        })
      })

      it('snapshots immediately', () => {
        cy.viewport(800, 600).then(function () {
          const { lastLog } = this

          expect(lastLog.get('snapshots').length).to.eq(1)
          expect(lastLog.get('snapshots')[0]).to.be.an('object')
        })
      })

      it('can turn off logging viewport command', function () {
        cy.state('isProtocolEnabled', false)
        cy.on('_log:added', (attrs, log) => {
          this.hiddenLog = log
        })

        cy.viewport(800, 600, { log: false }).then(function () {
          const { lastLog, hiddenLog } = this

          expect(lastLog).to.be.undefined
          expect(hiddenLog).to.be.undefined
        })
      })

      it('can turn off logging viewport when using preset', function () {
        cy.state('isProtocolEnabled', false)
        cy.on('_log:added', (attrs, log) => {
          this.hiddenLog = log
        })

        cy.viewport('macbook-15', { log: false }).then(function () {
          const { lastLog, hiddenLog } = this

          expect(lastLog).to.be.undefined
          expect(hiddenLog).to.be.undefined
        })
      })

      it('can send hidden log when protocol is enabled', function () {
        cy.state('isProtocolEnabled', true)
        cy.on('_log:added', (attrs, log) => {
          this.hiddenLog = log
        })

        cy.viewport(800, 600, { log: false }).then(function () {
          const { lastLog, hiddenLog } = this

          expect(lastLog).to.be.undefined
          expect(hiddenLog.get('name')).to.eq('viewport')
          expect(hiddenLog.get('hidden')).to.be.true
          expect(hiddenLog.get('snapshots').length, 'log snapshot length').to.eq(1)
        })
      })

      it('sets viewportWidth and viewportHeight directly', () => {
        cy.viewport(800, 600).then(function () {
          const { lastLog } = this

          expect(lastLog.get('viewportWidth')).to.eq(800)
          expect(lastLog.get('viewportHeight')).to.eq(600)
        })
      })

      it('.consoleProps with preset', () => {
        cy.viewport('ipad-mini').then(function () {
          expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
            name: 'viewport',
            type: 'command',
            props: {
              Preset: 'ipad-mini',
              Width: 768,
              Height: 1024,
            },
          })
        })
      })

      it('.consoleProps without preset', () => {
        cy.viewport(1024, 768).then(function () {
          expect(this.lastLog.invoke('consoleProps')).to.deep.eq({
            name: 'viewport',
            type: 'command',
            props: {
              Width: 1024,
              Height: 768,
            },
          })
        })
      })
    })
  })
})
