Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/spy call order by parameter #75

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Expand Up @@ -175,6 +175,37 @@ expect(spy).to.have.been.called.always.with.exactly('foo');
spy.should.have.been.called.always.with.exactly('foo');
```

#### .nth(n).called.with

Asserts that the nth call of the spy has been made with the list of arguments provided. This assertion comes with other three flavors:

* .first.called.with
* .second.called.with
* .third.called.with

```js
spy('foo');
spy('bar');
spy('baz');
spy('foobar');
expect(spy).to.have.been.first.called.with('foo');
spy.should.have.been.first.called.with('foo');
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
number of times required, for example

```js
spy('foo');
spy('bar');
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.

#### .once

Assert that a spy has been called exactly once.
Expand Down
124 changes: 103 additions & 21 deletions chai-spies.js
Expand Up @@ -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
Expand Down Expand Up @@ -226,7 +226,7 @@ var spy = function (chai, _) {
s += " }";
return s;
};

proxy.__spy = {
calls: []
, called: false
Expand Down Expand Up @@ -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}'
Expand Down Expand Up @@ -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) {
Expand All @@ -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) {
Expand Down
124 changes: 103 additions & 21 deletions lib/spy.js
Expand Up @@ -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
Expand Down Expand Up @@ -220,7 +220,7 @@ module.exports = function (chai, _) {
s += " }";
return s;
};

proxy.__spy = {
calls: []
, called: false
Expand Down Expand Up @@ -433,41 +433,111 @@ module.exports = 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}'
Expand Down Expand Up @@ -506,6 +576,7 @@ module.exports = 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) {
Expand All @@ -521,6 +592,17 @@ module.exports = 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) {
Expand Down