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

Keys for maps and sets #633

Merged
merged 3 commits into from
Mar 27, 2016
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
73 changes: 47 additions & 26 deletions lib/chai/core/assertions.js
Original file line number Diff line number Diff line change
Expand Up @@ -1111,7 +1111,6 @@ module.exports = function (chai, _) {
);
});


/**
* ### .keys(key1, [key2], [...])
*
Expand All @@ -1133,6 +1132,10 @@ module.exports = function (chai, _) {
* match the number of keys passed in (in other words, a target object must
* have all and only all of the passed-in keys).
*
* You can also use this on Sets and Maps. Please notice that these two types
* can have objects as keys, so, in this case, you can pass multiple objects as arguments if
* you want to.
*
* 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');
Expand All @@ -1142,7 +1145,10 @@ module.exports = function (chai, _) {
* expect({ foo: 1, bar: 2 }).to.have.all.keys({'bar': 6, 'foo': 7});
* expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys(['bar', 'foo']);
* expect({ foo: 1, bar: 2, baz: 3 }).to.contain.all.keys({'bar': 6});
*
* expect(new Map([[{objKey: 'value'}, 'value'], [1, 2]])).to.contain.key({objKey: 'value'});
* expect(new Map([[{objKey: 'value'}, 'value'], [1, 2]])).to.contain.any.keys([{objKey: 'value'}, {anotherKey: 'anotherValue'}]);
* expect(new Map([['firstKey', 'firstValue'], [1, 2]])).to.contain.all.keys('firstKey', 1);
* expect(new Set([['foo', 'bar'], ['example', 1]])).to.have.any.keys('foo');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to have an example of passing multiple objects as keys here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would be good!
Totally forgot it, thanks for pointing this

*
* @name keys
* @alias key
Expand All @@ -1155,55 +1161,70 @@ module.exports = function (chai, _) {
var obj = flag(this, 'object')
, str
, ok = true
, mixedArgsMsg = 'keys must be given single argument of Array|Object|String, or multiple String arguments';

switch (_.type(keys)) {
case "array":
if (arguments.length > 1) throw (new Error(mixedArgsMsg));
break;
case "object":
if (arguments.length > 1) throw (new Error(mixedArgsMsg));
keys = Object.keys(keys);
break;
default:
, mixedArgsMsg = 'when testing keys against an object or an array you must give a single Array|Object|String argument or multiple String arguments';

if (_.type(obj) === 'map' || _.type(obj) === 'set') {
actual = Array.from(obj.keys());

if (_.type(keys) !== 'array') {
keys = Array.prototype.slice.call(arguments);
}

} else {
actual = Object.keys(obj);

switch (_.type(keys)) {
case 'array':
if (arguments.length > 1) throw new Error(mixedArgsMsg);
break;
case 'object':
if (arguments.length > 1) throw new Error(mixedArgsMsg);
keys = Object.keys(keys);
break;
default:
keys = Array.prototype.slice.call(arguments);
}

keys = keys.map(String);
}

if (!keys.length) throw new Error('keys required');

keys = keys.map(String);

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

if (!any && !all) {
all = true;
}

// Has any
if (any) {
var intersection = expected.filter(function(key) {
return ~actual.indexOf(key);
ok = expected.some(function(expectedKey) {
return actual.some(function(actualKey) {
return expectedKey === actualKey;
});
});
ok = intersection.length > 0;
}

// Has all
if (all) {
ok = keys.every(function(key){
return ~actual.indexOf(key);
ok = expected.every(function(expectedKey) {
return actual.some(function(actualKey) {
return expectedKey === actualKey;
});
});

if (!flag(this, 'negate') && !flag(this, 'contains')) {
ok = ok && keys.length == actual.length;
}
}

// Key string
if (len > 1) {
keys = keys.map(function(key){
keys = keys.map(function(key) {
return _.inspect(key);
});
var last = keys.pop();
Expand Down Expand Up @@ -1809,7 +1830,7 @@ module.exports = function (chai, _) {
var realDelta = flag(this, 'realDelta');

var expression;
if (behavior === 'change') {
if (behavior === 'change') {
expression = Math.abs(final - initial) === Math.abs(delta);
} else {
expression = realDelta === Math.abs(delta);
Expand Down
144 changes: 144 additions & 0 deletions lib/chai/interface/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -1086,6 +1086,150 @@ module.exports = function (chai, util) {
new Assertion(exp, msg).to.have.length(len);
};

/**
* ### .hasAnyKeys(object, [keys], [message])
*
* Asserts that `object` has at least one of the `keys` provided.
* You can also provide a single object instead of a `keys` array and its keys
* will be used as the expected set of keys.
*
* assert.hasAnyKey({foo: 1, bar: 2, baz: 3}, ['foo', 'iDontExist', 'baz']);
* assert.hasAnyKey({foo: 1, bar: 2, baz: 3}, {foo: 30, iDontExist: 99, baz: 1337]);
* assert.hasAnyKey(new Map([[{foo: 1}, 'bar'], ['key', 'value']]), [{foo: 1}, 'thisKeyDoesNotExist']);
* assert.hasAnyKey(new Set([{foo: 'bar'}, 'anotherKey'], [{foo: 'bar'}, 'thisKeyDoesNotExist']);
*
* @name hasAnyKeys
* @param {Mixed} object
* @param {Array|Object} keys
* @param {String} message
* @namespace Assert
* @api public
*/

assert.hasAnyKeys = function (obj, keys, msg) {
if (keys === undefined) {
new Assertion(obj, msg).to.not.have.all.keys();
}

new Assertion(obj, msg).to.have.any.keys(keys);
}

/**
* ### .hasAllKeys(object, [keys], [message])
*
* Asserts that `object` has all and only all of the `keys` provided.
* You can also provide a single object instead of a `keys` array and its keys
* will be used as the expected set of keys.
*
* assert.hasAllKeys({foo: 1, bar: 2, baz: 3}, ['foo', 'bar', 'baz']);
* assert.hasAllKeys({foo: 1, bar: 2, baz: 3}, {foo: 30, bar: 99, baz: 1337]);
* assert.hasAllKeys(new Map([[{foo: 1}, 'bar'], ['key', 'value']]), [{foo: 1}, 'key']);
* assert.hasAllKeys(new Set([{foo: 'bar'}, 'anotherKey'], [{foo: 'bar'}, 'anotherKey']);
*
* @name hasAllKeys
* @param {Mixed} object
* @param {String[]} keys
* @param {String} message
* @namespace Assert
* @api public
*/

assert.hasAllKeys = function (obj, keys, msg) {
if (keys === undefined) {
new Assertion(obj, msg).to.not.have.all.keys();
}

new Assertion(obj, msg).to.have.all.keys(keys);
}

/**
* ### .containsAllKeys(object, [keys], [message])
*
* Asserts that `object` has all of the `keys` provided but may have more keys not listed.
* You can also provide a single object instead of a `keys` array and its keys
* will be used as the expected set of keys.
*
* assert.containsAllKeys({foo: 1, bar: 2, baz: 3}, ['foo', 'baz']);
* assert.containsAllKeys({foo: 1, bar: 2, baz: 3}, ['foo', 'bar', 'baz']);
* assert.containsAllKeys({foo: 1, bar: 2, baz: 3}, {foo: 30, baz: 1337});
* assert.containsAllKeys({foo: 1, bar: 2, baz: 3}, {foo: 30, bar: 99, baz: 1337});
* assert.containsAllKeys(new Map([[{foo: 1}, 'bar'], ['key', 'value']]), [{foo: 1}]);
* assert.containsAllKeys(new Map([[{foo: 1}, 'bar'], ['key', 'value']]), [{foo: 1}, 'key']);
* assert.containsAllKeys(new Set([{foo: 'bar'}, 'anotherKey'], [{foo: 'bar'}]);
* assert.containsAllKeys(new Set([{foo: 'bar'}, 'anotherKey'], [{foo: 'bar'}, 'anotherKey']);
*
* @name containsAllKeys
* @param {Mixed} object
* @param {String[]} keys
* @param {String} message
* @namespace Assert
* @api public
*/

assert.containsAllKeys = function (obj, keys, msg) {
if (keys === undefined) {
new Assertion(obj, msg).to.not.have.all.keys();
}

new Assertion(obj, msg).to.contain.all.keys(keys);
}

/**
* ### .doesNotHaveAnyKeys(object, [keys], [message])
*
* Asserts that `object` has none of the `keys` provided.
* You can also provide a single object instead of a `keys` array and its keys
* will be used as the expected set of keys.
*
* assert.doesNotHaveAnyKeys({foo: 1, bar: 2, baz: 3}, ['one', 'two', 'example']);
* assert.doesNotHaveAnyKeys({foo: 1, bar: 2, baz: 3}, {one: 1, two: 2, example: 'foo'});
* assert.doesNotHaveAnyKeys(new Map([[{foo: 1}, 'bar'], ['key', 'value']]), [{one: 'two'}, 'example']);
* assert.doesNotHaveAnyKeys(new Set([{foo: 'bar'}, 'anotherKey'], [{one: 'two'}, 'example']);
*
* @name doesNotHaveAnyKeys
* @param {Mixed} object
* @param {String[]} keys
* @param {String} message
* @namespace Assert
* @api public
*/

assert.doesNotHaveAnyKeys = function (obj, keys, msg) {
if (keys === undefined) {
new Assertion(obj, msg).to.not.have.all.keys();
}

new Assertion(obj, msg).to.not.have.any.keys(keys);
}

/**
* ### .doesNotHaveAllKeys(object, [keys], [message])
*
* Asserts that `object` does not have at least one of the `keys` provided.
* You can also provide a single object instead of a `keys` array and its keys
* will be used as the expected set of keys.
*
* assert.doesNotHaveAllKeys({foo: 1, bar: 2, baz: 3}, ['one', 'two', 'example']);
* assert.doesNotHaveAllKeys({foo: 1, bar: 2, baz: 3}, {one: 1, two: 2, example: 'foo'});
* assert.doesNotHaveAllKeys(new Map([[{foo: 1}, 'bar'], ['key', 'value']]), [{one: 'two'}, 'example']);
* assert.doesNotHaveAllKeys(new Set([{foo: 'bar'}, 'anotherKey'], [{one: 'two'}, 'example']);
*
* @name doesNotHaveAllKeys
* @param {Mixed} object
* @param {String[]} keys
* @param {String} message
* @namespace Assert
* @api public
*/

assert.doesNotHaveAllKeys = function (obj, keys, msg) {
if (keys === undefined) {
new Assertion(obj, msg).to.not.have.all.keys();
}

new Assertion(obj, msg).to.not.have.all.keys(keys);
}

/**
* ### .throws(function, [constructor/string/regexp], [string/regexp], [message])
*
Expand Down