From 7e3e51c9460d9a1476968256cb4eacf2b231afe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Mon, 27 Nov 2017 01:01:45 -0500 Subject: [PATCH 1/7] Relax ESLint for the `.keys` assertion --- .eslintrc.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.eslintrc.yml b/.eslintrc.yml index b1d1d00..d723346 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -150,7 +150,7 @@ rules: - error - tabWidth: 2 ignoreUrls: true - ignorePattern: \* expect\(.*\)\.to\.have\.(keys|nested\.property) + ignorePattern: \* expect\(.*\)\.to\.have\.[a-z.]*(keys|nested\.property) max-nested-callbacks: - error - max: 6 @@ -228,7 +228,6 @@ rules: - as-needed arrow-spacing: error generator-star-spacing: error - no-confusing-arrow: error no-duplicate-imports: error no-restricted-imports: error no-useless-computed-key: error From d3852dee08fab84ddfb82c108ce598be15e16c21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Mon, 27 Nov 2017 01:03:04 -0500 Subject: [PATCH 2/7] Sync `.keys` with the native Mocha one --- README.md | 110 ++++++++++++++++++++++++++--------- chai-immutable.js | 143 ++++++++++++++++++++++++++++++++++------------ 2 files changed, 192 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index 42cbd62..059b72d 100644 --- a/README.md +++ b/README.md @@ -129,42 +129,100 @@ expect(new List([1, 2, 3])).to.include(2); expect(new Map({ foo: 'bar', hello: 'world' })).to.include.keys('foo'); ``` -### .keys(key1[, key2, ...[, keyN]]) +### .keys(key1[, key2[, ...]]) - **@param** *{ String... | Array | Object | Collection }* key*N* -Asserts that the keyed collection contains any or all of the passed-in -keys. Use in combination with `any`, `all`, `contains`, or `have` will -affect what will pass. +Asserts that the target collection has the given keys. -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 the target is an object or array, keys can be provided as one or more +string arguments, a single array argument, a single object argument, or an +immutable collection. In the last 2 cases, only the keys in the given +object/collection matter; the values are ignored. -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. +```js +expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys('foo', 'bar'); +expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys(new List(['bar', 'foo'])); +expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys(new Set(['bar', 'foo'])); +expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys(new Stack(['bar', 'foo'])); +expect(new List(['x', 'y'])).to.have.all.keys(0, 1); + +expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys(['foo', 'bar']); +expect(new List(['x', 'y'])).to.have.all.keys([0, 1]); + +// Values in the passed object are ignored: +expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys({ bar: 6, foo: 7 }); +expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys(new Map({ bar: 6, foo: 7 })); +expect(new List(['x', 'y'])).to.have.all.keys({ 0: 4, 1: 5 }); +``` + +Note that `deep.property` behaves exactly like `property` in the context of +immutable data structures. -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). +By default, the target must have all of the given keys and no more. Add +`.any` earlier in the chain to only require that the target have at least +one of the given keys. Also, add `.not` earlier in the chain to negate +`.keys`. It's often best to add `.any` when negating `.keys`, and to use +`.all` when asserting `.keys` without negation. -`key` is an alias to `keys`. +When negating `.keys`, `.any` is preferred because `.not.any.keys` asserts +exactly what's expected of the output, whereas `.not.all.keys` creates +uncertain expectations. + +```js +// Recommended; asserts that target doesn't have any of the given keys +expect(new Map({a: 1, b: 2})).to.not.have.any.keys('c', 'd'); + +// Not recommended; asserts that target doesn't have all of the given +// keys but may or may not have some of them +expect(new Map({a: 1, b: 2})).to.not.have.all.keys('c', 'd'); +``` + +When asserting `.keys` without negation, `.all` is preferred because +`.all.keys` asserts exactly what's expected of the output, whereas +`.any.keys` creates uncertain expectations. + +```js +// Recommended; asserts that target has all the given keys +expect(new Map({a: 1, b: 2})).to.have.all.keys('a', 'b'); + +// Not recommended; asserts that target has at least one of the given +// keys but may or may not have more of them +expect(new Map({a: 1, b: 2})).to.have.any.keys('a', 'b'); +``` + +Note that `.all` is used by default when neither `.all` nor `.any` appear +earlier in the chain. However, it's often best to add `.all` anyway because +it improves readability. + +```js +// Both assertions are identical +expect(new Map({a: 1, b: 2})).to.have.all.keys('a', 'b'); // Recommended +expect(new Map({a: 1, b: 2})).to.have.keys('a', 'b'); // Not recommended +``` + +Add `.include` earlier in the chain to require that the target's keys be a +superset of the expected keys, rather than identical sets. + +```js +// Target object's keys are a superset of ['a', 'b'] but not identical +expect(new Map({a: 1, b: 2, c: 3})).to.include.all.keys('a', 'b'); +expect(new Map({a: 1, b: 2, c: 3})).to.not.have.all.keys('a', 'b'); +``` + +However, if `.any` and `.include` are combined, only the `.any` takes +effect. The `.include` is ignored in this case. + +```js +// Both assertions are identical +expect(new Map({a: 1})).to.have.any.keys('a', 'b'); +expect(new Map({a: 1})).to.include.any.keys('a', 'b'); +``` + +The alias `.key` can be used interchangeably with `.keys`. ```js expect(new Map({ foo: 1 })).to.have.key('foo'); -expect(new Map({ foo: 1, bar: 2 })).to.have.keys('foo', 'bar'); -expect(new Map({ foo: 1, bar: 2 })).to.have.keys(new List(['bar', 'foo'])); -expect(new Map({ foo: 1, bar: 2 })).to.have.keys(new Set(['bar', 'foo'])); -expect(new Map({ foo: 1, bar: 2 })).to.have.keys(new Stack(['bar', 'foo'])); -expect(new Map({ foo: 1, bar: 2 })).to.have.keys(['bar', 'foo']); -expect(new Map({ foo: 1, bar: 2 })).to.have.keys({ 'bar': 6, 'foo': 7 }); -expect(new Map({ foo: 1, bar: 2 })).to.have.keys(new Map({ 'bar': 6, 'foo': 7 })); -expect(new Map({ foo: 1, bar: 2 })).to.have.any.keys('foo', 'not-foo'); -expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys('foo', 'bar'); -expect(new Map({ foo: 1, bar: 2 })).to.contain.key('foo'); ``` ### .property(path[, val]) diff --git a/chai-immutable.js b/chai-immutable.js index 87e08b8..bd60a8a 100644 --- a/chai-immutable.js +++ b/chai-immutable.js @@ -169,45 +169,104 @@ }); /** - * ### .keys(key1[, key2, ...[, keyN]]) + * ### .keys(key1[, key2[, ...]]) * - * Asserts that the keyed collection contains any or all of the passed-in - * keys. Use in combination with `any`, `all`, `contains`, or `have` will - * affect what will pass. + * Asserts that the target collection has the given keys. * - * 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 the target is an object or array, keys can be provided as one or more + * string arguments, a single array argument, a single object argument, or an + * immutable collection. In the last 2 cases, only the keys in the given + * object/collection matter; the values are ignored. * - * 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. + * ```js + * expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys('foo', 'bar'); + * expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys(new List(['bar', 'foo'])); + * expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys(new Set(['bar', 'foo'])); + * expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys(new Stack(['bar', 'foo'])); + * expect(new List(['x', 'y'])).to.have.all.keys(0, 1); + * + * expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys(['foo', 'bar']); + * expect(new List(['x', 'y'])).to.have.all.keys([0, 1]); + * + * // Values in the passed object are ignored: + * expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys({ 'bar': 6, 'foo': 7 }); + * expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys(new Map({ 'bar': 6, 'foo': 7 })); + * expect(new List(['x', 'y'])).to.have.all.keys({0: 4, 1: 5}); + * ``` + * + * Note that `deep.property` behaves exactly like `property` in the context of + * immutable data structures. + * + * By default, the target must have all of the given keys and no more. Add + * `.any` earlier in the chain to only require that the target have at least + * one of the given keys. Also, add `.not` earlier in the chain to negate + * `.keys`. It's often best to add `.any` when negating `.keys`, and to use + * `.all` when asserting `.keys` without negation. + * + * When negating `.keys`, `.any` is preferred because `.not.any.keys` asserts + * exactly what's expected of the output, whereas `.not.all.keys` creates + * uncertain expectations. + * + * ```js + * // Recommended; asserts that target doesn't have any of the given keys + * expect(new Map({a: 1, b: 2})).to.not.have.any.keys('c', 'd'); + * + * // Not recommended; asserts that target doesn't have all of the given + * // keys but may or may not have some of them + * expect(new Map({a: 1, b: 2})).to.not.have.all.keys('c', 'd'); + * ``` + * + * When asserting `.keys` without negation, `.all` is preferred because + * `.all.keys` asserts exactly what's expected of the output, whereas + * `.any.keys` creates uncertain expectations. + * + * ```js + * // Recommended; asserts that target has all the given keys + * expect(new Map({a: 1, b: 2})).to.have.all.keys('a', 'b'); + * + * // Not recommended; asserts that target has at least one of the given + * // keys but may or may not have more of them + * expect(new Map({a: 1, b: 2})).to.have.any.keys('a', 'b'); + * ``` + * + * Note that `.all` is used by default when neither `.all` nor `.any` appear + * earlier in the chain. However, it's often best to add `.all` anyway because + * it improves readability. * - * 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). + * ```js + * // Both assertions are identical + * expect(new Map({a: 1, b: 2})).to.have.all.keys('a', 'b'); // Recommended + * expect(new Map({a: 1, b: 2})).to.have.keys('a', 'b'); // Not recommended + * ``` + * + * Add `.include` earlier in the chain to require that the target's keys be a + * superset of the expected keys, rather than identical sets. + * + * ```js + * // Target object's keys are a superset of ['a', 'b'] but not identical + * expect(new Map({a: 1, b: 2, c: 3})).to.include.all.keys('a', 'b'); + * expect(new Map({a: 1, b: 2, c: 3})).to.not.have.all.keys('a', 'b'); + * ``` + * + * However, if `.any` and `.include` are combined, only the `.any` takes + * effect. The `.include` is ignored in this case. + * + * ```js + * // Both assertions are identical + * expect(new Map({a: 1})).to.have.any.keys('a', 'b'); + * expect(new Map({a: 1})).to.include.any.keys('a', 'b'); + * ``` * - * `key` is an alias to `keys`. + * The alias `.key` can be used interchangeably with `.keys`. * * ```js * expect(new Map({ foo: 1 })).to.have.key('foo'); - * expect(new Map({ foo: 1, bar: 2 })).to.have.keys('foo', 'bar'); - * expect(new Map({ foo: 1, bar: 2 })).to.have.keys(new List(['bar', 'foo'])); - * expect(new Map({ foo: 1, bar: 2 })).to.have.keys(new Set(['bar', 'foo'])); - * expect(new Map({ foo: 1, bar: 2 })).to.have.keys(new Stack(['bar', 'foo'])); - * expect(new Map({ foo: 1, bar: 2 })).to.have.keys(['bar', 'foo']); - * expect(new Map({ foo: 1, bar: 2 })).to.have.keys({ 'bar': 6, 'foo': 7 }); - * expect(new Map({ foo: 1, bar: 2 })).to.have.keys(new Map({ 'bar': 6, 'foo': 7 })); - * expect(new Map({ foo: 1, bar: 2 })).to.have.any.keys('foo', 'not-foo'); - * expect(new Map({ foo: 1, bar: 2 })).to.have.all.keys('foo', 'bar'); - * expect(new Map({ foo: 1, bar: 2 })).to.contain.key('foo'); * ``` * * @name keys - * @param {String...|Array|Object|Collection} keyN * @alias key + * @alias deep.key + * @param {...String|Array|Object|Collection} keys * @namespace BDD * @api public */ @@ -217,6 +276,8 @@ const obj = this._obj; if (Immutable.Iterable.isKeyed(obj)) { + const ssfi = utils.flag(this, 'ssfi'); + switch (utils.type(keys)) { case 'Object': if (Immutable.Iterable.isIndexed(keys)) { @@ -229,10 +290,12 @@ // `keys` is now an array so this statement safely falls through case 'Array': if (arguments.length > 1) { - throw new Error( - 'keys must be given single argument of ' + - 'Array|Object|String|Collection, ' + - 'or multiple String arguments' + throw new chai.AssertionError( + 'when testing keys against an object or an array you must ' + + 'give a single Array|Object|String|Collection argument or ' + + 'multiple String arguments', + null, + ssfi ); } break; @@ -241,19 +304,28 @@ break; } + // Only stringify non-Symbols because Symbols would become "Symbol()" + keys = keys.map(val => typeof val === 'symbol' ? val : String(val)); + if (!keys.length) { - throw new Error('keys required'); + throw new chai.AssertionError('keys required', null, ssfi); } + let all = utils.flag(this, 'all'); const any = utils.flag(this, 'any'); const contains = utils.flag(this, 'contains'); let ok; - let str = `${contains ? 'contain' : 'have'} `; + let str = contains ? 'contain ' : 'have '; + + if (!any && !all) { + all = true; + } if (any) { ok = keys.some(key => obj.has(key)); } else { ok = keys.every(key => obj.has(key)); + if (!contains) { ok = ok && keys.length === obj.count(); } @@ -272,8 +344,9 @@ ok, `expected #{act} to ${str}`, `expected #{act} to not ${str}`, - keys, - obj.toString() + keys.slice(0).sort(utils.compareByInspect), + obj.toString(), + true ); } else { _super.apply(this, arguments); From 7e3323a129a7be1919bb08b84701f376d8d9e669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Mon, 27 Nov 2017 01:03:30 -0500 Subject: [PATCH 3/7] Allow `.keys` to match on iterable collections as well (such as `List`) --- chai-immutable.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/chai-immutable.js b/chai-immutable.js index bd60a8a..c8ec5f1 100644 --- a/chai-immutable.js +++ b/chai-immutable.js @@ -275,7 +275,7 @@ return function (keys) { const obj = this._obj; - if (Immutable.Iterable.isKeyed(obj)) { + if (Immutable.Iterable.isIterable(obj)) { const ssfi = utils.flag(this, 'ssfi'); switch (utils.type(keys)) { From 36772e6cd92d9c48fde6eba4dc9db13c48942d0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Mon, 27 Nov 2017 01:04:09 -0500 Subject: [PATCH 4/7] Add tests for `.deep.keys` --- test/test.js | 58 ++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 18 deletions(-) diff --git a/test/test.js b/test/test.js index a71e69e..d8b875c 100644 --- a/test/test.js +++ b/test/test.js @@ -257,138 +257,160 @@ describe('chai-immutable', function () { // eslint-disable-line prefer-arrow-cal it('should pass given an existing key', function () { // eslint-disable-line prefer-arrow-callback expect(new Map({ x: 1 })).to.have.key('x'); - expect({ x: 1 }).to.have.key('x'); + expect(new Map({ x: 1 })).to.have.deep.key('x'); }); it('should pass using `not` given an inexisting key', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.not.have.key('z'); - expect(obj).to.not.have.key('z'); + expect(map).to.not.have.deep.key('z'); }); it('should pass given multiple existing keys', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.have.keys('x', 'y'); - expect(obj).to.have.keys('x', 'y'); + expect(map).to.have.deep.keys('x', 'y'); }); it('should pass using `not` given multiple inexisting keys', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.not.have.keys('z1', 'z2'); - expect(obj).to.not.have.keys('z1', 'z2'); + expect(map).to.not.have.deep.keys('z1', 'z2'); }); it('should accept an Array of keys to check against', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.have.keys(['x', 'y']); - expect(obj).to.have.keys(['x', 'y']); + expect(map).to.have.deep.keys(['x', 'y']); }); it('should accept a List of keys to check against', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.have.keys(new List(['x', 'y'])); + expect(map).to.have.deep.keys(new List(['x', 'y'])); }); it('should accept a Set of keys to check against', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.have.keys(new Set(['x', 'y'])); + expect(map).to.have.deep.keys(new Set(['x', 'y'])); }); it('should accept a Stack of keys to check against', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.have.keys(new Stack(['x', 'y'])); + expect(map).to.have.deep.keys(new Stack(['x', 'y'])); }); it('should accept an Object to check against', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.have.keys({ x: 6, y: 7 }); - expect(obj).to.have.keys({ x: 6, y: 7 }); + expect(map).to.have.deep.keys({ x: 6, y: 7 }); }); it('should accept a Map to check against', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.have.keys(new Map({ x: 6, y: 7 })); + expect(map).to.have.deep.keys(new Map({ x: 6, y: 7 })); }); it('should pass using `any` given an existing key', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.have.any.keys('x', 'z'); - expect(obj).to.have.any.keys('x', 'z'); + expect(map).to.have.any.deep.keys('x', 'z'); }); it('should pass using `not` and `any` given inexisting keys', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.not.have.any.keys('z1', 'z2'); - expect(obj).to.not.have.any.keys('z1', 'z2'); + expect(map).to.not.have.any.deep.keys('z1', 'z2'); }); it('should pass using `all` given existing keys', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.have.all.keys('x', 'y'); - expect(obj).to.have.all.keys('x', 'y'); + expect(map).to.have.all.deep.keys('x', 'y'); }); it('should pass using `not` and `all` given inexisting keys', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.not.have.all.keys('z1', 'y'); - expect(obj).to.not.have.all.keys('z1', 'y'); + expect(map).to.not.have.all.deep.keys('z1', 'y'); }); it('should pass using `contain` given an existing key', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.contain.key('x'); - expect(obj).to.contain.key('x'); + expect(map).to.contain.deep.key('x'); }); it('should not affect the original assertions', function () { // eslint-disable-line prefer-arrow-callback expect({ x: 1, y: 2 }).to.have.any.keys('x', 'z'); + expect({ x: 1, y: 2 }).to.have.any.deep.keys('x', 'z'); expect({ x: 1, y: 2 }).to.have.any.keys('x'); + expect({ x: 1, y: 2 }).to.have.any.deep.keys('x'); expect({ x: 1, y: 2 }).to.contain.any.keys('y', 'z'); + expect({ x: 1, y: 2 }).to.contain.any.deep.keys('y', 'z'); expect({ x: 1, y: 2 }).to.contain.any.keys(['x']); + expect({ x: 1, y: 2 }).to.contain.any.deep.keys(['x']); expect({ x: 1, y: 2 }).to.contain.any.keys({ x: 6 }); + expect({ x: 1, y: 2 }).to.contain.any.deep.keys({ x: 6 }); expect({ x: 1, y: 2 }).to.have.all.keys(['x', 'y']); + expect({ x: 1, y: 2 }).to.have.all.deep.keys(['x', 'y']); expect({ x: 1, y: 2 }).to.have.all.keys({ x: 6, y: 7 }); + expect({ x: 1, y: 2 }).to.have.all.deep.keys({ x: 6, y: 7 }); expect({ x: 1, y: 2, z: 3 }).to.contain.all.keys(['x', 'y']); + expect({ x: 1, y: 2, z: 3 }).to.contain.all.deep.keys(['x', 'y']); expect({ x: 1, y: 2, z: 3 }).to.contain.all.keys({ x: 6 }); + expect({ x: 1, y: 2, z: 3 }).to.contain.all.deep.keys({ x: 6 }); }); // See https://github.com/astorije/chai-immutable/issues/7 it('should display a helpful failure output on big objects', function () { // eslint-disable-line prefer-arrow-callback const lengthyMap = new Map({ foo: 'foo foo foo foo foo foo foo foo ' }); + fail( () => expect(lengthyMap).to.have.keys('not-foo'), /(foo ){8}/ ); + + fail( + () => expect(lengthyMap).to.have.deep.keys('not-foo'), + /(foo ){8}/ + ); }); it('should fail given an inexisting key', function () { // eslint-disable-line prefer-arrow-callback fail(() => expect(new Map({ x: 1 })).to.have.key('z')); + fail(() => expect(new Map({ x: 1 })).to.have.deep.key('z')); fail(() => expect({ x: 1 }).to.have.key('z')); + fail(() => expect({ x: 1 }).to.have.deep.key('z')); }); it('should fail given multiple inexisting keys', function () { // eslint-disable-line prefer-arrow-callback fail(() => expect(map).to.have.keys('z1', 'z2')); - fail(() => expect(obj).to.have.keys('z1', 'z2')); + fail(() => expect(map).to.have.deep.keys('z1', 'z2')); }); it('should fail using `not` given multiple existing keys', function () { // eslint-disable-line prefer-arrow-callback fail(() => expect(map).to.not.have.keys('x', 'y')); - fail(() => expect(obj).to.not.have.keys('x', 'y')); + fail(() => expect(map).to.not.have.deep.keys('x', 'y')); }); it('should fail using `any` given inexisting keys', function () { // eslint-disable-line prefer-arrow-callback fail(() => expect(map).to.have.any.keys('z1', 'z2')); - fail(() => expect(obj).to.have.any.keys('z1', 'z2')); + fail(() => expect(map).to.have.any.deep.keys('z1', 'z2')); }); it('should fail using `not` and `any` given an existing key', function () { // eslint-disable-line prefer-arrow-callback fail(() => expect(map).to.not.have.any.keys('x', 'z')); - fail(() => expect(obj).to.not.have.any.keys('x', 'z')); + fail(() => expect(map).to.not.have.any.deep.keys('x', 'z')); }); it('should fail using `all` given an inexisting key', function () { // eslint-disable-line prefer-arrow-callback fail(() => expect(map).to.have.all.keys('z1', 'y')); - fail(() => expect(obj).to.have.all.keys('z1', 'y')); + fail(() => expect(map).to.have.all.deep.keys('z1', 'y')); }); it('should fail using `not` and `all` given existing keys', function () { // eslint-disable-line prefer-arrow-callback fail(() => expect(map).to.not.have.all.keys('x', 'y')); - fail(() => expect(obj).to.not.have.all.keys('x', 'y')); + fail(() => expect(map).to.not.have.all.deep.keys('x', 'y')); }); it('should fail using `contain` given an inexisting key', function () { // eslint-disable-line prefer-arrow-callback fail(() => expect(map).to.contain.key('z')); - fail(() => expect(obj).to.contain.key('z')); + fail(() => expect(map).to.contain.deep.key('z')); }); it('should work if using different copies of Immutable', function () { // eslint-disable-line prefer-arrow-callback expect(new clonedImmutable.Map({ x: 1 })).to.have.key('x'); + expect(new clonedImmutable.Map({ x: 1 })).to.have.deep.key('x'); }); }); From b41dfb34a50f52e2b1e4928210483a89226ca83a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Mon, 27 Nov 2017 01:07:19 -0500 Subject: [PATCH 5/7] Add a test for `.keys` against `List`s --- test/test.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/test.js b/test/test.js index d8b875c..8e82b94 100644 --- a/test/test.js +++ b/test/test.js @@ -298,6 +298,7 @@ describe('chai-immutable', function () { // eslint-disable-line prefer-arrow-cal it('should accept an Object to check against', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.have.keys({ x: 6, y: 7 }); expect(map).to.have.deep.keys({ x: 6, y: 7 }); + expect(new List(['x', 'y'])).to.have.all.keys({ 0: 4, 1: 5 }); }); it('should accept a Map to check against', function () { // eslint-disable-line prefer-arrow-callback @@ -366,6 +367,11 @@ describe('chai-immutable', function () { // eslint-disable-line prefer-arrow-cal ); }); + it('should pass against Lists', function () { // eslint-disable-line prefer-arrow-callback + expect(new List(['x', 'y'])).to.have.all.keys(0, 1); + expect(new List(['x', 'y'])).to.have.all.keys([0, 1]); + }); + it('should fail given an inexisting key', function () { // eslint-disable-line prefer-arrow-callback fail(() => expect(new Map({ x: 1 })).to.have.key('z')); fail(() => expect(new Map({ x: 1 })).to.have.deep.key('z')); From 8d99f1a9c68d87e65d9ef7097e36b0fb0026c7bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Mon, 27 Nov 2017 01:29:26 -0500 Subject: [PATCH 6/7] Ensure an error message is returned when giving wrong arguments to `.keys` --- chai-immutable.js | 2 +- test/test.js | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/chai-immutable.js b/chai-immutable.js index c8ec5f1..f857ac1 100644 --- a/chai-immutable.js +++ b/chai-immutable.js @@ -291,7 +291,7 @@ case 'Array': if (arguments.length > 1) { throw new chai.AssertionError( - 'when testing keys against an object or an array you must ' + + 'when testing keys against an immutable collection, you must ' + 'give a single Array|Object|String|Collection argument or ' + 'multiple String arguments', null, diff --git a/test/test.js b/test/test.js index 8e82b94..910a453 100644 --- a/test/test.js +++ b/test/test.js @@ -306,6 +306,19 @@ describe('chai-immutable', function () { // eslint-disable-line prefer-arrow-cal expect(map).to.have.deep.keys(new Map({ x: 6, y: 7 })); }); + it('should error when given multiple non-scalar arguments', function () { // eslint-disable-line prefer-arrow-callback + const msg = 'when testing keys against an immutable collection, ' + + 'you must give a single Array|Object|String|Collection argument or ' + + 'multiple String arguments'; + + fail(() => expect(map).to.have.all.keys(['x'], 'y'), msg); + fail(() => expect(map).to.have.all.keys(new List(['x']), 'y'), msg); + fail(() => expect(map).to.have.all.keys(new Set(['x']), 'y'), msg); + fail(() => expect(map).to.have.all.keys(new Stack(['x']), 'y'), msg); + fail(() => expect(map).to.have.all.keys({ x: 1 }, 'y'), msg); + fail(() => expect(map).to.have.all.keys(new Map({ x: 1 }), 'y'), msg); + }); + it('should pass using `any` given an existing key', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.have.any.keys('x', 'z'); expect(map).to.have.any.deep.keys('x', 'z'); From db3bf84166e9bf102b447b6204771d879c59d1a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9mie=20Astori?= Date: Mon, 27 Nov 2017 01:40:57 -0500 Subject: [PATCH 7/7] Add tests to ensure giving no arguments to `.keys` errors --- test/test.js | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/test.js b/test/test.js index 910a453..42c71e7 100644 --- a/test/test.js +++ b/test/test.js @@ -319,6 +319,17 @@ describe('chai-immutable', function () { // eslint-disable-line prefer-arrow-cal fail(() => expect(map).to.have.all.keys(new Map({ x: 1 }), 'y'), msg); }); + it('should error when given no arguments', function () { // eslint-disable-line prefer-arrow-callback + const msg = 'keys required'; + + fail(() => expect(map).to.have.all.keys([]), msg); + fail(() => expect(map).to.have.all.keys(new List()), msg); + fail(() => expect(map).to.have.all.keys(new Set()), msg); + fail(() => expect(map).to.have.all.keys(new Stack()), msg); + fail(() => expect(map).to.have.all.keys({}), msg); + fail(() => expect(map).to.have.all.keys(new Map()), msg); + }); + it('should pass using `any` given an existing key', function () { // eslint-disable-line prefer-arrow-callback expect(map).to.have.any.keys('x', 'z'); expect(map).to.have.any.deep.keys('x', 'z');