From d5045735e094a0ea18055a5718f0fabd4f6065b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristj=C3=A1n=20Oddsson?= Date: Sun, 11 Feb 2024 11:40:05 +0100 Subject: [PATCH] Implement `iterable` assertion (#1592) * Implement `iterator` assertion * Consider `string` to be iterable * Revert changes to `isSubsetOf` * Move iterable check out of `an()` function --- lib/chai/core/assertions.js | 33 ++++++++++++++++++++++++++++++++- lib/chai/interface/assert.js | 27 +++++++++++++++++++++++++++ test/assert.js | 27 +++++++++++++++++++++++++++ test/expect.js | 27 +++++++++++++++++++++++++++ test/should.js | 19 +++++++++++++++++++ 5 files changed, 132 insertions(+), 1 deletion(-) diff --git a/lib/chai/core/assertions.js b/lib/chai/core/assertions.js index 0a55c58e..28f021cf 100644 --- a/lib/chai/core/assertions.js +++ b/lib/chai/core/assertions.js @@ -3133,7 +3133,6 @@ function isSubsetOf(subset, superset, cmp, contains, ordered) { * @namespace BDD * @api public */ - Assertion.addMethod('members', function (subset, msg) { if (msg) flag(this, 'message', msg); var obj = flag(this, 'object') @@ -3170,6 +3169,38 @@ Assertion.addMethod('members', function (subset, msg) { ); }); +/** + * ### .iterable + * + * Asserts that the target is an iterable, which means that it has a iterator. + * + * expect([1, 2]).to.be.iterable; + * + * Add `.not` earlier in the chain to negate `.iterable`. + * + * expect(1).to.not.be.iterable; + * expect(true).to.not.be.iterable; + * + * A custom error message can be given as the second argument to `expect`. + * + * expect(1, 'nooo why fail??').to.be.iterable; + * + * @name iterable + * @namespace BDD + * @api public + */ +Assertion.addProperty('iterable', function(msg) { + if (msg) flag(this, 'message', msg); + var obj = flag(this, 'object'); + + this.assert( + obj != undefined && obj[Symbol.iterator] + , 'expected #{this} to be an iterable' + , 'expected #{this} to not be an iterable' + , obj + ); +}); + /** * ### .oneOf(list[, msg]) * diff --git a/lib/chai/interface/assert.js b/lib/chai/interface/assert.js index 3c1eb2d3..a4488247 100644 --- a/lib/chai/interface/assert.js +++ b/lib/chai/interface/assert.js @@ -2376,6 +2376,33 @@ assert.oneOf = function (inList, list, msg) { new Assertion(inList, msg, assert.oneOf, true).to.be.oneOf(list); } +/** + * ### isIterable(obj, [message]) + * + * Asserts that the target is an iterable, which means that it has a iterator + * with the exception of `String.` + * + * assert.isIterable([1, 2]); + * + * @param {unknown} obj + * @param {string} [msg] + * @namespace Assert + * @api public + */ +assert.isIterable = function(obj, msg) { + if (obj == undefined || !obj[Symbol.iterator]) { + msg = msg ? + `${msg} expected ${inspect(obj)} to be an iterable` : + `expected ${inspect(obj)} to be an iterable`; + + throw new AssertionError( + msg, + undefined, + assert.isIterable + ); + } +} + /** * ### .changes(function, object, property, [message]) * diff --git a/test/assert.js b/test/assert.js index 1416f120..dbbfc39d 100644 --- a/test/assert.js +++ b/test/assert.js @@ -2315,6 +2315,33 @@ describe('assert', function () { }, 'blah: the argument to most must be a number'); }); + it('iterable', function() { + assert.isIterable([1, 2, 3]); + assert.isIterable(new Map([[1, 'one'], [2, 'two'], [3, 'three']])); + assert.isIterable(new Set([1, 2, 3])); + assert.isIterable('hello'); + + err(function() { + assert.isIterable(42); + }, 'expected 42 to be an iterable'); + + err(function() { + assert.isIterable(undefined); + }, 'expected undefined to be an iterable'); + + err(function() { + assert.isIterable(null); + }, 'expected null to be an iterable'); + + err(function() { + assert.isIterable(true); + }, 'expected true to be an iterable'); + + err(function() { + assert.isIterable({ key: 'value' }); + }, 'expected { key: \'value\' } to be an iterable'); + }); + it('change', function() { var obj = { value: 10, str: 'foo' }, heroes = ['spiderman', 'superman'], diff --git a/test/expect.js b/test/expect.js index ba03c8a2..fb0d0960 100644 --- a/test/expect.js +++ b/test/expect.js @@ -3569,6 +3569,33 @@ describe('expect', function () { }, 'expected [ { a: 1 }, { b: 2 }, { c: 3 } ] to not be an ordered superset of [ { a: 1 }, { b: 2 } ]'); }); + it('iterable', function() { + expect([1, 2, 3]).to.be.iterable; + expect(new Map([[1, 'one'], [2, 'two'], [3, 'three']])).to.be.iterable; + expect(new Set([1, 2, 3])).to.be.iterable; + expect('hello').to.be.iterable; + + err(function() { + expect(42).to.be.iterable; + }, 'expected 42 to be an iterable'); + + err(function() { + expect(undefined).to.be.iterable; + }, 'expected undefined to be an iterable'); + + err(function() { + expect(null).to.be.iterable; + }, 'expected null to be an iterable'); + + err(function() { + expect(true).to.be.iterable; + }, 'expected true to be an iterable'); + + err(function() { + expect({ key: 'value' }).to.be.iterable; + }, 'expected { key: \'value\' } to be an iterable'); + }) + it('change', function() { var obj = { value: 10, str: 'foo' }, heroes = ['spiderman', 'superman'], diff --git a/test/should.js b/test/should.js index f205b023..487d4a0a 100644 --- a/test/should.js +++ b/test/should.js @@ -2937,6 +2937,25 @@ describe('should', function() { }, 'expected [ { a: 1 }, { b: 2 }, { c: 3 } ] to not be an ordered superset of [ { a: 1 }, { b: 2 } ]'); }); + it ('iterable', function() { + ([1, 2, 3]).should.be.iterable; + (new Map([[1, 'one'], [2, 'two'], [3, 'three']])).should.be.iterable; + (new Set([1, 2, 3])).should.be.iterable; + ('hello').should.be.iterable; + + err(function() { + (42).should.be.iterable; + }, 'expected 42 to be an iterable'); + + err(function() { + (true).should.be.iterable; + }, 'expected true to be an iterable'); + + err(function() { + ({ key: 'value' }).should.be.iterable; + }, 'expected { key: \'value\' } to be an iterable'); + }) + it('change', function() { var obj = { value: 10, str: 'foo' }, heroes = ['spiderman', 'superman'],