Skip to content

Commit

Permalink
Merge pull request #313 from cjqed/254-expect-any-all
Browse files Browse the repository at this point in the history
Added the 'any' and 'all' flags for 'keys' assertion, with 'all' being the default behavior
  • Loading branch information
keithamus committed Dec 8, 2014
2 parents f06278f + e0e0608 commit 46aac80
Show file tree
Hide file tree
Showing 3 changed files with 143 additions and 15 deletions.
102 changes: 87 additions & 15 deletions lib/chai/core/assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,41 @@ module.exports = function (chai, _) {
flag(this, 'deep', true);
});

/**
* ### .any
*
* Sets the `any` flag, (opposite of the `all` flag)
* later used in the `keys` assertion.
*
* expect(foo).to.have.any.keys('bar', 'baz');
*
* @name any
* @api public
*/

Assertion.addProperty('any', function () {
flag(this, 'any', true);
flag(this, 'all', false)
});


/**
* ### .all
*
* Sets the `all` flag (opposite of the `any` flag)
* later used by the `keys` assertion.
*
* expect(foo).to.have.all.keys('bar', 'baz');
*
* @name all
* @api public
*/

Assertion.addProperty('all', function () {
flag(this, 'all', true);
flag(this, 'any', false);
});

/**
* ### .a(type)
*
Expand Down Expand Up @@ -930,12 +965,30 @@ module.exports = function (chai, _) {
/**
* ### .keys(key1, [key2], [...])
*
* Asserts that the target has exactly the given keys, or
* asserts the inclusion of some keys when using the
* `include` or `contain` modifiers.
*
* expect({ foo: 1, bar: 2 }).to.have.keys(['foo', 'bar']);
* expect({ foo: 1, bar: 2, baz: 3 }).to.contain.keys('foo', 'bar');
* Asserts that the target contains any or all of the passed-in keys.
* Use in combination with `any`, `all`, `contains`, or `have` will affect
* what will pass.
*
* When used in conjunction with `any`, at least one key that is passed
* in must exist in the target object. This is regardless whether or not
* the `have` or `contain` qualifiers are used. Note, either `any` or `all`
* should be used in the assertion. If neither are used, the assertion is
* defaulted to `all`.
*
* When both `all` and `contain` are used, the target object must have at
* least all of the passed-in keys but may have more keys not listed.
*
* When both `all` and `have` are used, the target object must both contain
* all of the passed-in keys AND the number of keys in the target object must
* match the number of keys passed in (in other words, a target object must
* have all and only all of the passed-in keys).
*
* expect({ foo: 1, bar: 2 }).to.have.any.keys('foo', 'baz');
* expect({ foo: 1, bar: 2 }).to.have.any.keys('foo');
* expect({ foo: 1, bar: 2 }).to.contain.any.keys('bar', 'baz');
* expect({ foo: 1, bar: 2 }).to.contain.any.keys(['foo']);
* expect({ foo: 1, bar: 2 }).to.have.all.keys(['bar', 'foo']);
* expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys(['bar', 'foo']);
*
* @name keys
* @alias key
Expand All @@ -956,16 +1009,30 @@ module.exports = function (chai, _) {

var actual = Object.keys(obj)
, expected = keys
, len = keys.length;
, len = keys.length
, any = flag(this, 'any')
, all = flag(this, 'all');

// Inclusion
ok = keys.every(function(key){
return ~actual.indexOf(key);
});
if (!any && !all) {
all = true;
}

// Strict
if (!flag(this, 'negate') && !flag(this, 'contains')) {
ok = ok && keys.length == actual.length;
// Has any
if (any) {
var intersection = expected.filter(function(key) {
return ~actual.indexOf(key);
});
ok = intersection.length > 0;
}

// Has all
if (all) {
ok = keys.every(function(key){
return ~actual.indexOf(key);
});
if (!flag(this, 'negate') && !flag(this, 'contains')) {
ok = ok && keys.length == actual.length;
}
}

// Key string
Expand All @@ -974,7 +1041,12 @@ module.exports = function (chai, _) {
return _.inspect(key);
});
var last = keys.pop();
str = keys.join(', ') + ', and ' + last;
if (all) {
str = keys.join(', ') + ', and ' + last;
}
if (any) {
str = keys.join(', ') + ', or ' + last;
}
} else {
str = _.inspect(keys[0]);
}
Expand Down
31 changes: 31 additions & 0 deletions test/expect.js
Original file line number Diff line number Diff line change
Expand Up @@ -581,13 +581,27 @@ describe('expect', function () {
expect({ foo: 1, bar: 2 }).to.contain.keys(['foo']);
expect({ foo: 1, bar: 2 }).to.contain.keys(['bar']);
expect({ foo: 1, bar: 2 }).to.contain.keys(['bar', 'foo']);
expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys(['bar', 'foo']);

expect({ foo: 1, bar: 2 }).to.not.have.keys('baz');
expect({ foo: 1, bar: 2 }).to.not.have.keys('foo', 'baz');
expect({ foo: 1, bar: 2 }).to.not.contain.keys('baz');
expect({ foo: 1, bar: 2 }).to.not.contain.keys('foo', 'baz');
expect({ foo: 1, bar: 2 }).to.not.contain.keys('baz', 'foo');

expect({ foo: 1, bar: 2 }).to.have.any.keys('foo', 'baz');
expect({ foo: 1, bar: 2 }).to.have.any.keys('foo');
expect({ foo: 1, bar: 2 }).to.contain.any.keys('bar', 'baz');
expect({ foo: 1, bar: 2 }).to.contain.any.keys(['foo']);
expect({ foo: 1, bar: 2 }).to.have.all.keys(['bar', 'foo']);
expect({ foo: 1, bar: 2 }).to.contain.all.keys(['bar', 'foo']);

expect({ foo: 1, bar: 2 }).to.not.have.any.keys('baz', 'abc', 'def');
expect({ foo: 1, bar: 2 }).to.not.have.any.keys('baz');
expect({ foo: 1, bar: 2 }).to.not.contain.any.keys('baz');
expect({ foo: 1, bar: 2 }).to.not.have.all.keys(['baz', 'foo']);
expect({ foo: 1, bar: 2 }).to.not.contain.all.keys(['baz', 'foo']);

err(function(){
expect({ foo: 1 }).to.have.keys();
}, "keys required");
Expand Down Expand Up @@ -627,6 +641,10 @@ describe('expect', function () {
err(function(){
expect({ foo: 1, bar: 2 }).to.not.have.keys(['foo', 'bar']);
}, "expected { foo: 1, bar: 2 } to not have keys 'foo', and 'bar'");

err(function(){
expect({ foo: 1, bar: 2 }).to.have.all.keys('foo');
}, "expected { foo: 1, bar: 2 } to have key 'foo'");

err(function(){
expect({ foo: 1 }).to.not.contain.keys(['foo']);
Expand All @@ -635,6 +653,19 @@ describe('expect', function () {
err(function(){
expect({ foo: 1 }).to.contain.keys('foo', 'bar');
}, "expected { foo: 1 } to contain keys 'foo', and 'bar'");

err(function() {
expect({ foo: 1 }).to.have.any.keys('baz');
}, "expected { foo: 1 } to have key 'baz'");

err(function(){
expect({ foo: 1, bar: 2 }).to.not.have.all.keys(['foo', 'bar']);
}, "expected { foo: 1, bar: 2 } to not have keys 'foo', and 'bar'");

err(function(){
expect({ foo: 1, bar: 2 }).to.not.have.any.keys(['foo', 'baz']);
}, "expected { foo: 1, bar: 2 } to not have keys 'foo', or 'baz'");

});

it('chaining', function(){
Expand Down
25 changes: 25 additions & 0 deletions test/should.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,19 @@ describe('should', function() {
({ foo: 1, bar: 2 }).should.not.contain.keys('foo', 'baz');
({ foo: 1, bar: 2 }).should.not.contain.keys('baz', 'foo');

({ foo: 1, bar: 2 }).should.have.any.keys('foo', 'baz');
({ foo: 1, bar: 2 }).should.have.any.keys('foo');
({ foo: 1, bar: 2 }).should.contain.any.keys('bar', 'baz');
({ foo: 1, bar: 2 }).should.contain.any.keys(['foo']);
({ foo: 1, bar: 2 }).should.have.all.keys(['bar', 'foo']);
({ foo: 1, bar: 2 }).should.contain.all.keys(['bar', 'foo']);

({ foo: 1, bar: 2 }).should.not.have.any.keys('baz', 'abc', 'def');
({ foo: 1, bar: 2 }).should.not.have.any.keys('baz');
({ foo: 1, bar: 2 }).should.not.contain.any.keys('baz');
({ foo: 1, bar: 2 }).should.not.have.all.keys(['baz', 'foo']);
({ foo: 1, bar: 2 }).should.not.contain.all.keys(['baz', 'foo']);

err(function(){
({ foo: 1 }).should.have.keys();
}, "keys required");
Expand Down Expand Up @@ -498,6 +511,18 @@ describe('should', function() {
err(function(){
({ foo: 1 }).should.contain.keys('foo', 'bar');
}, "expected { foo: 1 } to contain keys 'foo', and 'bar'");

err(function() {
({ foo: 1 }).should.have.any.keys('baz');
}, "expected { foo: 1 } to have key 'baz'");

err(function(){
({ foo: 1, bar: 2 }).should.not.have.all.keys(['foo', 'bar']);
}, "expected { foo: 1, bar: 2 } to not have keys 'foo', and 'bar'");

err(function(){
({ foo: 1, bar: 2 }).should.not.have.any.keys(['foo', 'baz']);
}, "expected { foo: 1, bar: 2 } to not have keys 'foo', or 'baz'");
});

it('throw', function () {
Expand Down

0 comments on commit 46aac80

Please sign in to comment.