@revoloo/cypress6
Version:
Cypress.io end to end testing tool
467 lines (374 loc) • 13 kB
JavaScript
describe('src/cy/commands/clock', () => {
beforeEach(function () {
this.window = cy.state('window')
this.setTimeoutSpy = cy.spy(this.window, 'setTimeout')
this.setIntervalSpy = cy.spy(this.window, 'setInterval')
})
describe('#clock', () => {
it('sets clock as subject', () => {
cy.clock().then((clock) => {
expect(clock).to.exist
expect(clock.tick).to.be.a('function')
})
})
it('assigns clock to test context', () => {
cy.clock().then(function (clock) {
expect(clock).to.eq(this.clock)
})
})
it('proxies lolex clock, replacing window time methods', function (done) {
expect(this.setTimeoutSpy).not.to.be.called
cy.clock().then(function (clock) {
// lolex calls setTimeout once as part of its setup
// but it shouldn't be called again by the @window.setTimeout()
expect(this.setTimeoutSpy).to.be.calledOnce
this.window.setTimeout(() => {
expect(this.setTimeoutSpy).to.be.calledOnce
done()
})
clock.tick()
})
})
it('takes number now arg', () => {
const now = 1111111111111
cy.clock(now).then(function (clock) {
expect(new this.window.Date().getTime()).to.equal(now)
clock.tick(4321)
expect(new this.window.Date().getTime()).to.equal(now + 4321)
})
})
it('takes Date now arg', () => {
// April 15, 2017
const now = new Date(2017, 3, 15)
const nowTimestamp = now.getTime()
cy.clock(now).then(function (clock) {
expect(new this.window.Date().getTime()).to.equal(nowTimestamp)
clock.tick(4321)
expect(new this.window.Date().getTime()).to.equal(nowTimestamp + 4321)
})
})
it('restores window time methods when calling restore', (done) => {
cy.clock().then(function (clock) {
this.window.setTimeout(() => {
expect(this.setTimeoutSpy).to.be.calledOnce
clock.restore()
expect(this.window.setTimeout).to.equal(this.setTimeoutSpy)
this.window.setTimeout(() => {
expect(this.setTimeoutSpy).to.be.calledTwice
done()
})
})
clock.tick()
})
})
it('unsets clock after restore', () => {
cy.clock().then(function (clock) {
expect(cy.state('clock')).to.exist
clock.restore()
expect(cy.state('clock')).to.be.null
expect(this.clock).to.be.null
})
})
it('automatically restores clock on \'restore\' event', () => {
cy.clock().then((clock) => {
const r = cy.spy(clock, 'restore')
Cypress.emit('test:before:run', {})
expect(r).to.be.called
})
})
it('returns clock on subsequent calls, ignoring arguments', () => {
cy
.clock()
.clock(400)
.then((clock) => {
expect(clock.details().now).to.equal(0)
})
})
it('new Date() is an instance of Date', () => {
cy.clock()
cy.window().then((win) => {
expect(new win.Date()).to.be.an.instanceof(win.Date)
expect(new win.Date() instanceof win.Date).to.be.true
})
})
// this test was written to catch a bug in lolex (dep) 3 and is fixed by lolex 4 upgrade,
it('doesn\'t override window.performance members', () => {
cy.clock()
.then((clock) => {
cy.window().then((win) => {
expect(win.performance.getEntries()).to.deep.eq([])
clock.restore()
expect(win.performance.getEntries().length).to.be.at.least(1)
})
})
})
context('errors', () => {
it('throws if now is not a number (or options object)', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`cy.clock()` only accepts a number or an `options` object for its first argument. You passed: `"250"`')
expect(err.docsUrl).to.equal('https://on.cypress.io/clock')
done()
})
cy.clock('250')
})
it('throws if methods is not an array (or options object)', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`cy.clock()` only accepts an array of function names or an `options` object for its second argument. You passed: `"setTimeout"`')
expect(err.docsUrl).to.equal('https://on.cypress.io/clock')
done()
})
cy.clock(0, 'setTimeout')
})
it('throws if methods is not an array of strings (or options object)', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`cy.clock()` only accepts an array of function names or an `options` object for its second argument. You passed: `[42]`')
expect(err.docsUrl).to.equal('https://on.cypress.io/clock')
done()
})
cy.clock(0, [42])
})
})
context('arg for which functions to replace', () => {
it('replaces specified functions', (done) => {
cy.clock(null, ['setTimeout']).then(function (clock) {
this.window.setTimeout(() => {
expect(this.setTimeoutSpy).to.be.calledOnce
done()
})
clock.tick()
})
})
it('does not replace other functions', function (done) {
cy.clock(null, ['setTimeout']).then((clock) => {
const interval = this.window.setInterval(() => {
this.window.clearInterval(interval)
expect(this.setIntervalSpy).to.be.called
this.window.setTimeout(() => {
expect(this.setTimeoutSpy).to.be.calledOnce
done()
})
clock.tick()
}, 5)
})
})
})
context('options', () => {
beforeEach(function () {
this.logged = false
cy.on('log:added', (attrs, log) => {
if (log.get('name') === 'clock') {
this.logged = true
}
})
return null
})
it('can be first arg', function () {
cy.clock({ log: false }).then(() => {
expect(this.logged).to.be.false
})
})
it('can be second arg', function () {
cy.clock(new Date().getTime(), { log: false }).then(() => {
expect(this.logged).to.be.false
})
})
it('can be third arg', function () {
cy.clock(new Date().getTime(), ['setTimeout'], { log: false }).then(() => {
expect(this.logged).to.be.false
})
})
})
context('window changes', () => {
it('binds to default window before visit', () => {
cy.clock(null, ['setTimeout']).then((clock) => {
const onSetTimeout = cy.spy()
cy.state('window').setTimeout(onSetTimeout)
clock.tick()
expect(onSetTimeout).to.be.called
})
})
it('re-binds to new window when window changes', () => {
const newWindow = {
setTimeout () {},
clearTimeout () {},
Date: function Date () {},
XMLHttpRequest: {
prototype: {},
},
}
cy.clock(null, ['setTimeout']).then((clock) => {
Cypress.emit('window:before:load', newWindow)
const onSetTimeout = cy.spy()
newWindow.setTimeout(onSetTimeout)
clock.tick()
expect(onSetTimeout).to.be.called
})
})
it('binds to window if called before visit', () => {
cy.clock()
cy.visit('/fixtures/dom.html')// should not throw
})
})
context('logging', () => {
beforeEach(function () {
this.logs = []
cy.on('log:added', (attrs, log) => {
const name = log.get('name')
if (['clock', 'tick', 'restore'].includes(name)) {
return this.logs.push(log)
}
})
return null
})
it('logs when created', function () {
cy.clock().then(() => {
const log = this.logs[0]
expect(this.logs.length).to.equal(1)
expect(log.get('name')).to.eq('clock')
expect(log.get('message')).to.eq('')
expect(log.get('type')).to.eq('parent')
expect(log.get('state')).to.eq('passed')
expect(log.get('snapshots').length).to.eq(1)
expect(log.get('snapshots')[0]).to.be.an('object')
})
})
it('logs when restored', function () {
cy.clock().then((clock) => {
clock.restore()
const log = this.logs[1]
expect(this.logs.length).to.equal(2)
expect(log.get('name')).to.eq('restore')
expect(log.get('message')).to.eq('')
})
})
it('does not log when auto-restored', function (done) {
cy.clock().then(() => {
Cypress.emit('test:before:run', {})
expect(this.logs.length).to.equal(1)
done()
})
})
it('does not log when log: false', function () {
cy.clock({ log: false }).then((clock) => {
clock.tick()
clock.restore()
expect(this.logs.length).to.equal(0)
})
})
it('only logs the first call', function () {
cy
.clock()
.clock()
.clock()
.then(() => {
expect(this.logs.length).to.equal(1)
})
})
context('#consoleProps', () => {
beforeEach(() => {
cy.clock(100, ['setTimeout']).then(function (clock) {
this.clock = clock
this.clock.tick(100)
})
})
it('includes clock\'s now value', function () {
const consoleProps = this.logs[0].invoke('consoleProps')
expect(consoleProps['Now']).to.equal(100)
})
it('includes methods replaced by clock', function () {
const consoleProps = this.logs[0].invoke('consoleProps')
expect(consoleProps['Methods replaced']).to.eql(['setTimeout'])
})
it('logs ticked amount on tick', function () {
const createdConsoleProps = this.logs[0].invoke('consoleProps')
expect(createdConsoleProps['Ticked']).to.be.undefined
const tickedConsoleProps = this.logs[1].invoke('consoleProps')
expect(tickedConsoleProps['Ticked']).to.equal('100 milliseconds')
})
it('properties are unaffected by future actions', function () {
this.clock.tick(100)
this.clock.restore()
const consoleProps = this.logs[1].invoke('consoleProps')
expect(consoleProps['Now']).to.equal(200)
expect(consoleProps['Methods replaced']).to.eql(['setTimeout'])
})
})
})
})
describe('#tick', () => {
beforeEach(function () {
this.logs = []
cy.on('log:added', (attrs, log) => {
if (log.get('name') === 'tick') {
this.logs.push(log)
}
})
return null
})
it('moves time ahead and triggers callbacks', function (done) {
cy
.clock()
.then(() => {
return this.window.setTimeout(() => {
done()
}, 1000)
}).tick(1000)
})
it('returns the clock object', () => {
cy
.clock()
.tick(1000).then(function (clock) {
expect(clock).to.equal(this.clock)
})
})
it('defaults to 0ms', () => {
cy.clock()
.tick().then(function (clock) {
const consoleProps = this.logs[0].invoke('consoleProps')
expect(consoleProps['Ticked']).to.equal('0 milliseconds')
})
})
context('errors', () => {
it('throws if there is not a clock', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`cy.tick()` cannot be called without first calling `cy.clock()`')
expect(err.docsUrl).to.equal('https://on.cypress.io/tick')
done()
})
cy.tick()
})
it('throws if ms is not undefined or a number', (done) => {
cy.on('fail', (err) => {
expect(err.message).to.equal('`clock.tick()`/`cy.tick()` only accepts a number as their argument. You passed: `"100"`')
expect(err.docsUrl).to.equal('https://on.cypress.io/tick')
done()
})
cy.clock().tick('100')
})
})
context('logging', () => {
it('logs number of milliseconds', () => {
cy
.clock()
.tick(250)
.then(function () {
const log = this.logs[0]
expect(this.logs.length).to.equal(1)
expect(log.get('name')).to.eq('tick')
expect(log.get('message')).to.eq('250ms')
})
})
it('logs before and after snapshots', () => {
cy
.clock()
.tick(250)
.then(function () {
const log = this.logs[0]
expect(log.get('snapshots').length).to.eq(2)
expect(log.get('snapshots')[0].name).to.equal('before')
expect(log.get('snapshots')[1].name).to.equal('after')
})
})
})
})
})