From 287657b6487397ed41c7b6c132194aabd3ff0964 Mon Sep 17 00:00:00 2001 From: Sergii Stotskyi Date: Tue, 5 Sep 2017 05:44:11 +0300 Subject: [PATCH] fix(spy.nth): uses proper call arguments Also makes messages to be properly formatted by chai --- README.md | 6 +-- chai-spies.js | 124 +++++++++++++++++++++++++++++++++++++++++--------- lib/spy.js | 21 +++++---- 3 files changed, 117 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 79be421..2288ab8 100644 --- a/README.md +++ b/README.md @@ -194,14 +194,14 @@ expect(spy).on.nth(5).be.called.with('foobar'); spy.should.on.nth(5).be.called.with('foobar'); ``` -These assertions requires the spy to be called at least the +These assertions requires the spy to be called at least the number of times required, for example ```js spy('foo'); spy('bar'); -expect(spy).to.have.been.first.called.with('baz'); -spy.should.have.been.first.called.with('baz'); +expect(spy).to.have.been.third.called.with('baz'); +spy.should.have.been.third.called.with('baz'); ``` Won't pass because the spy has not been called a third time. diff --git a/chai-spies.js b/chai-spies.js index 2fc89f3..61189c4 100644 --- a/chai-spies.js +++ b/chai-spies.js @@ -48,7 +48,7 @@ var spy = function (chai, _) { * const array = [] * const spy = chai.spy.sandbox(); * const [push, pop] = spy.on(array, ['push', 'pop']); - * + * * spy.on(array, 'push', returns => 1) * * @param {Object} object @@ -226,7 +226,7 @@ var spy = function (chai, _) { s += " }"; return s; }; - + proxy.__spy = { calls: [] , called: false @@ -439,41 +439,111 @@ var spy = function (chai, _) { }); /** - * ### .with + * # nth call (spy, n, arguments) + * + * Asserts that the nth call of the spy has been called with * */ - function assertWith () { - new Assertion(this._obj).to.be.spy; - var expArgs = [].slice.call(arguments, 0) - , calls = this._obj.__spy.calls - , always = _.flag(this, 'spy always') + function nthCallWith(spy, n, expArgs) { + if (spy.calls.length < n) return false; + + var actArgs = spy.calls[n].slice() , passed = 0; - calls.forEach(function (call) { - var actArgs = call.slice() - , found = 0; - - expArgs.forEach(function (expArg) { - for (var i = 0; i < actArgs.length; i++) { - if (_.eql(actArgs[i], expArg)) { - found++; - actArgs.splice(i, 1); - break; - } + expArgs.forEach(function (expArg) { + for (var i = 0; i < actArgs.length; i++) { + if (_.eql(actArgs[i], expArg)) { + passed++; + actArgs.splice(i, 1); + break; } - }); - if (found === expArgs.length) passed++; + } }); + return passed === expArgs.length + } + + function numberOfCallsWith(spy, expArgs) { + var found = 0 + , calls = spy.calls; + + for (var i = 0; i < calls.length; i++) { + if (nthCallWith(spy, i, expArgs)) { + found++; + } + } + + return found; + } + + Assertion.addProperty('first', function () { + if ('undefined' !== this._obj.__spy) { + _.flag(this, 'spy nth call with', 1); + } + }); + + Assertion.addProperty('second', function () { + if ('undefined' !== this._obj.__spy) { + _.flag(this, 'spy nth call with', 2); + } + }); + + Assertion.addProperty('third', function () { + if ('undefined' !== this._obj.__spy) { + _.flag(this, 'spy nth call with', 3); + } + }); + + Assertion.addProperty('on'); + + Assertion.addChainableMethod('nth', function (n) { + if ('undefined' !== this._obj.__spy) { + _.flag(this, 'spy nth call with', n); + } + }); + + function generateOrdinalNumber(n) { + if (n === 1) return 'first'; + if (n === 2) return 'second'; + if (n === 3) return 'third'; + return n + 'th'; + } + + /** + * ### .with + * + */ + + function assertWith() { + new Assertion(this._obj).to.be.spy; + var expArgs = [].slice.call(arguments, 0) + , spy = this._obj.__spy + , calls = spy.calls + , always = _.flag(this, 'spy always') + , nthCall = _.flag(this, 'spy nth call with'); + if (always) { + var passed = numberOfCallsWith(spy, expArgs); this.assert( passed === calls.length , 'expected ' + this._obj + ' to have been always called with #{exp} but got ' + passed + ' out of ' + calls.length , 'expected ' + this._obj + ' to have not always been called with #{exp}' , expArgs ); + } else if (nthCall) { + var ordinalNumber = generateOrdinalNumber(nthCall), + actArgs = calls[nthCall - 1]; + new Assertion(this._obj).to.be.have.been.called.min(nthCall); + this.assert( + nthCallWith(spy, nthCall - 1, expArgs) + , 'expected ' + this._obj + ' to have been called at the ' + ordinalNumber + ' time with #{exp} but got #{act}' + , 'expected ' + this._obj + ' to have not been called at the ' + ordinalNumber + ' time with #{exp}' + , expArgs + , actArgs + ); } else { + var passed = numberOfCallsWith(spy, expArgs); this.assert( passed > 0 , 'expected ' + this._obj + ' to have been called with #{exp}' @@ -512,6 +582,7 @@ var spy = function (chai, _) { , _with = _.flag(this, 'spy with') , args = [].slice.call(arguments, 0) , calls = this._obj.__spy.calls + , nthCall = _.flag(this, 'spy nth call with') , passed; if (always && _with) { @@ -527,6 +598,17 @@ var spy = function (chai, _) { , 'expected ' + this._obj + ' to have not always been called with exactly #{exp}' , args ); + } else if(_with && nthCall) { + var ordinalNumber = generateOrdinalNumber(nthCall), + actArgs = calls[nthCall - 1]; + new Assertion(this._obj).to.be.have.been.called.min(nthCall); + this.assert( + _.eql(actArgs, args) + , 'expected ' + this._obj + ' to have been called at the ' + ordinalNumber + ' time with exactly #{exp} but got #{act}' + , 'expected ' + this._obj + ' to have not been called at the ' + ordinalNumber + ' time with exactly #{exp}' + , args + , actArgs + ); } else if (_with) { passed = 0; calls.forEach(function (call) { diff --git a/lib/spy.js b/lib/spy.js index 5c63d5a..92d3b41 100644 --- a/lib/spy.js +++ b/lib/spy.js @@ -42,7 +42,7 @@ module.exports = function (chai, _) { * const array = [] * const spy = chai.spy.sandbox(); * const [push, pop] = spy.on(array, ['push', 'pop']); - * + * * spy.on(array, 'push', returns => 1) * * @param {Object} object @@ -220,7 +220,7 @@ module.exports = function (chai, _) { s += " }"; return s; }; - + proxy.__spy = { calls: [] , called: false @@ -461,7 +461,7 @@ module.exports = function (chai, _) { function numberOfCallsWith(spy, expArgs) { var found = 0 , calls = spy.calls; - + for (var i = 0; i < calls.length; i++) { if (nthCallWith(spy, i, expArgs)) { found++; @@ -509,14 +509,14 @@ module.exports = function (chai, _) { * */ - function assertWith () { + function assertWith() { new Assertion(this._obj).to.be.spy; var expArgs = [].slice.call(arguments, 0) , spy = this._obj.__spy , calls = spy.calls , always = _.flag(this, 'spy always') , nthCall = _.flag(this, 'spy nth call with'); - + if (always) { var passed = numberOfCallsWith(spy, expArgs); this.assert( @@ -526,15 +526,15 @@ module.exports = function (chai, _) { , expArgs ); } else if (nthCall) { - var passed = spy.calls[nthCall], - ordinalNumber = generateOrdinalNumber(nthCall), - actArgs = calls[nthCall]; + var ordinalNumber = generateOrdinalNumber(nthCall), + actArgs = calls[nthCall - 1]; new Assertion(this._obj).to.be.have.been.called.min(nthCall); this.assert( nthCallWith(spy, nthCall - 1, expArgs) - , 'expected ' + this._obj + ' to have been called at the ' + ordinalNumber + ' time with #{exp} but got ' + actArgs + , 'expected ' + this._obj + ' to have been called at the ' + ordinalNumber + ' time with #{exp} but got #{act}' , 'expected ' + this._obj + ' to have not been called at the ' + ordinalNumber + ' time with #{exp}' , expArgs + , actArgs ); } else { var passed = numberOfCallsWith(spy, expArgs); @@ -598,9 +598,10 @@ module.exports = function (chai, _) { new Assertion(this._obj).to.be.have.been.called.min(nthCall); this.assert( _.eql(actArgs, args) - , 'expected ' + this._obj + ' to have been called at the ' + ordinalNumber + ' time with exactly #{exp} but got ' + args + , 'expected ' + this._obj + ' to have been called at the ' + ordinalNumber + ' time with exactly #{exp} but got #{act}' , 'expected ' + this._obj + ' to have not been called at the ' + ordinalNumber + ' time with exactly #{exp}' , args + , actArgs ); } else if (_with) { passed = 0;