UNPKG

chai-iterator

Version:
621 lines (458 loc) 19.8 kB
# chai-iterator: Assertions for iterable objects [![Version][version-badge]][npm] [![Build][build-badge]][travis] [![Coverage][coverage-badge]][coveralls] [![Dependencies][dependencies-badge]][greenkeeper] ## Contents - [Overview](#overview) - [Compatibility](#compatibility) - [Installation](#installation) - [Setup](#setup) - [Expect/Should API](#expectshould-api) - [Assert API](#assert-api) - [License](#license) ## Overview **chai-iterator** extends the [Chai][chai] assertion library with methods for testing [iterable][iterable] objects. Introduced in the [ES2015 specification][ecma-iterable], iterable objects have an [`@@iterator`][iterator-method] method, which allows us to iterate over them with a [`for...of`][for-of] loop. A number of [built-in][built-in-iterable] types are iterable by default, while [custom iterable objects][custom-iterable] may also be defined. chai-iterator makes it easy to test all such objects. ### You may not need chai-iterator In many cases the [array spread operator][array-spread] is the best way to test iterables. chai-iterator is however very useful for testing part of a very long (or infinite) iterable. ### Basic usage Here is a fairly exhaustive sample of the assertions we can make using Chai Iterator. While we could just as easily use `expect` or `assert`, we'll use Chai's `should()` [assertion style][assertion-style], just to be different. ```js [2, 3, 5].should.be.iterable; [2, 3, 5].should.iterate.over([2, 3, 5]); [2, 3, 5].should.iterate.from([2, 3]); [2, 3, 5].should.iterate.until([3, 5]); [2, 3, 5].should.iterate.for.lengthOf(3); [2, 3, 5].should.iterate.for.length.above(2); [2, 3, 5].should.iterate.for.length.below(4); [2, 3, 5].should.iterate.for.length.of.at.least(3); [2, 3, 5].should.iterate.for.length.of.at.most(3); [2, 3, 5].should.iterate.for.length.within(2, 4); [2, 3, 5].should.not.iterate.over([1, 2, 3]); [{n: 2}, {n: 3}].should.deep.iterate.from([{n: 2}]); ``` Let's not limit ourselves to Arrays; we can test any iterable object. ```js 'abcde'.should.iterate.until(['c', 'd', 'e']); ``` And we can pass any iterable as our expected values too. ```js 'abcde'.should.iterate.until('cde'); ``` ### User-defined iterable objects chai-iterator is best used to test [user-defined iterable objects][custom-iterable], like the one constructed by the following [class][class]. ```js class Count { constructor(start=0, step=1) { this.start = start; this.step = step; } *[Symbol.iterator]() { for (let n = this.start; true; n += this.step) { yield n; } } } ``` The sequence generated by `Count.prototype[@@iterator]()` is infinite; it continues to yield values indefinitely. Still, we can safely use the [`from()`](#iteratefromexpected) assertion with it, since it will terminate as soon as our expected iterable is done. ```js let tens = new Count(10, 10); tens.should.be.iterable; tens.should.iterate.from([10, 20, 30]); tens.should.iterate.from([10, 20, 30, 40, 50]); ``` Just don't go trying to use [`over()`](#iterateoverexpected) or [`until()`](#iterateuntilexpected) on infinite sequences. The former will always fail and the latter will never stop. ### Generators and iterators Let's generate the [fibonacci sequence][fibonacci-sequence]. A [generator function][generator-function] is just a function that returns a [`Generator`][generator] object &mdash; an [iterator][iterator] that is also [iterable][iterable]. We can test a `Generator` just as we would any other iterable. ```js function* fibonacci() { for (let [x, y] = [1, 1]; true; [x, y] = [y, x + y]) { yield x; } } fibonacci().should.iterate.from([1, 1, 2, 3, 5]); ``` Be careful though. Iterators can't go back in time. Once a value has been yielded, it is lost forever. And so the following assertions pass. ```js let fiborator = fibonacci(); fiborator.should.iterate.from([1, 1, 2, 3, 5]); fiborator.should.iterate.from([8, 13, 21, 34]); ``` It usually makes more sense to construct a new `Generator` for each assertion. ```js fibonacci().should.iterate.from([1, 1, 2, 3, 5]); fibonacci().should.iterate.from([1, 1, 2, 3, 5, 8, 13]); ``` ## Compatibility chai-iterator requires that [`Symbol.iterator`][iterator-method] be available in the environment. In [Node][node], this means the version must be v4.0 or greater. While the latest versions of [most browsers][browser-list] are compatible, web-facing projects should almost certainly use a polyfill. The [Babel polyfill][babel-polyfill] is one option for environments that do not natively support `Symbol.iterator`. More minimally, we can get away with just two sub-modules from the [core-js][core-js] library, like so. ```js require('core-js/es6/symbol'); require('core-js/fn/symbol/iterator'); ``` ## Installation Install chai-iterator using [npm][npm]. And be sure, of course, to install [Chai][chai-npm]. ```sh npm install --save chai chai-iterator ``` ## Setup chai-iterator can be imported as a [Node][node] module, an [AMD][amd] module, or included in an HTML [`<script>`][script-tag] tag. For [TypeScript][typescript] users, declarations are installed with the package. ### Node To set up chai-iterator for [Node][node], make sure the version is v4.0 or higher, as prior versions lack support for the [`@@iterator`][iterator-method] method. ```js const chai = require('chai'); const chaiIterator = require('chai-iterator'); chai.use(chaiIterator); ``` #### AMD chai-iterator can be set up inside of an [AMD][amd] module like so. ```js define((require, exports, module) => { let chai = require('chai'); let chaiIterator = require('chai-iterator'); chai.use(chaiIterator); }); ``` #### HTML script tag chai-iterator can be included via a [`<script>`][script-tag] tag. If it is loaded after `chai.js`, Chai will use it automatically. ```html <script src="chai.js"></script> <script src="chai-iterator.js"></script> ``` #### TypeScript [TypeScript][typescript] declarations are included in the package. To use them, ensure chai-iterator is installed with [npm][npm], then install the declarations and their dependencies via [typings][typings]. And be sure to install the declarations for [chai][chai-typings]. ```sh typings install --save-dev npm~chai npm:chai-iterator ``` In the [compiler options][compiler-options], set `"target"` to `"es6"`, or at least include a reference to [`lib.es6.d.ts`][es6-lib]. Now the following will just work. ```ts import chai = require("chai"); import chaiIterator = require("chai-iterator"); chai.use(chaiIterator); [2, 3, 5].should.iterate.over([2, 3, 5]); ``` ## Expect/Should API ### Assertions - [`iterable`](#iterable) - [`iterate.over()`](#iterateoverexpected) - [`iterate.from()`](#iteratefromexpected) - [`iterate.until()`](#iterateuntilexpected) - [`iterate.for.lengthOf()`](#iterateforlengthofn) - [`iterate.for.length.above()`](#iterateforlengthaboven) - [`iterate.for.length.below()`](#iterateforlengthbelown) - [`iterate.for.length.of.at.least()`](#iterateforlengthofatleastn) - [`iterate.for.length.of.at.most()`](#iterateforlengthofatmostn) - [`iterate.for.length.within()`](#iterateforlengthwithinmin-max) #### `iterable` Asserts that the target is an iterable object, i.e., that it has an [`@@iterator`][iterator-method] method. ```js expect([2, 3, 5]).to.be.iterable; expect('abcdefg').to.be.iterable; expect(12345).not.to.be.iterable; ``` #### `iterate.over(expected)` Asserts that the target iterates over a given sequence of values. Set the [`deep`][deep] flag to use deep equality to compare values. | Param | Type | Description | | :-------- | :------- | :------------------ | | expected | `object` | An iterable object. | ```js expect([2, 3, 5]).to.iterate.over([2, 3, 5]); expect('abcdefg').to.itetate.over('abcdefg'); expect([2, 3, 5]).not.to.iterate.over([2, 3]); expect([{n: 2}, {n: 3}]).to.deep.iterate.over([{n: 2}, {n: 3}]); ``` #### `iterate.from(expected)` Asserts that the target begins iterating over a given sequence of values. Set the [`deep`][deep] flag to use deep equality to compare values. | Param | Type | Description | | :-------- | :------- | :------------------ | | expected | `object` | An iterable object. | ```js expect([2, 3, 5]).to.iterate.from([2, 3]); expect('abcdefg').to.iterate.from('abc'); expect([2, 3, 5]).not.to.iterate.from([3, 5]); expect([{n: 2}, {n: 3}]).to.deep.iterate.from([{n: 2}]); ``` #### `iterate.until(expected)` Asserts that the target ends iteration with a given sequence of values. Set the [`deep`][deep] flag to use deep equality to compare values. | Param | Type | Description | | :-------- | :------- | :------------------ | | expected | `object` | An iterable object. | ```js expect([2, 3, 5]).to.iterate.until([3, 5]); expect('abcdefg').to.iterate.until('efg'); expect([2, 3, 5]).not.to.iterate.until([2, 3]); expect([{n: 2}, {n: 3}]).to.deep.iterate.until([{n: 3}]); ``` #### `iterate.for.lengthOf(n)` Asserts that the target yields exactly *n* values. | Param | Type | Description | | :----- | :------- | :------------------ | | n | `number` | A positive integer | ```js expect([2, 3, 5]).to.iterate.for.lengthOf(3); expect('abcdefg').to.iterate.for.lengthOf(7); expect([2, 3, 5]).not.to.iterate.for.lengthOf(7); ``` #### `iterate.for.length.above(n)` Asserts that the target yields more than *n* values. | Param | Type | Description | | :----- | :------- | :------------------ | | n | `number` | A positive integer | ```js expect([2, 3, 5]).to.iterate.for.length.above(2); expect('abcdefg').to.iterate.for.length.above(5); expect([2, 3, 5]).not.to.iterate.for.length.above(3); ``` #### `iterate.for.length.below(n)` Asserts that the target yields fewer than *n* values. | Param | Type | Description | | :----- | :------- | :------------------ | | n | `number` | A positive integer | ```js expect([2, 3, 5]).to.iterate.for.length.below(4); expect('abcdefg').to.iterate.for.length.below(10); expect([2, 3, 5]).not.to.iterate.for.length.below(3); ``` #### `iterate.for.length.of.at.least(n)` Asserts that the target yields at least *n* values. | Param | Type | Description | | :----- | :------- | :------------------ | | n | `number` | A positive integer | ```js expect([2, 3, 5]).to.iterate.for.length.of.at.least(2); expect([2, 3, 5]).to.iterate.for.length.of.at.least(3); expect([2, 3, 5]).not.to.iterate.for.length.of.at.least(4); ``` #### `iterate.for.length.of.at.most(n)` Asserts that the target yields at most *n* values. | Param | Type | Description | | :----- | :------- | :------------------ | | n | `number` | A positive integer | ```js expect([2, 3, 5]).to.iterate.for.length.of.at.most(4); expect([2, 3, 5]).to.iterate.for.length.of.at.most(3); expect([2, 3, 5]).not.to.iterate.for.length.of.at.most(2); ``` #### `iterate.for.length.within(min, max)` Asserts that the target yields between *min* and *max* values, inclusive. | Param | Type | Description | | :----- | :------- | :------------------- | | min | `number` | A positive integer | | max | `number` | A positive integer | ```js expect([2, 3, 5]).to.iterate.for.length.within(2, 4); expect([2, 3, 5]).to.iterate.for.length.within(3, 3); expect([2, 3, 5]).not.to.iterate.for.length.within(4, 7); ``` ## Assert API ### Assertions - [`isIterable()`](#isiterablevalue-message) - [`isNotIterable()`](#isnotiterablevalue-message) - [`iteratesOver()`](#iteratesovervalue-expected-message) - [`doesNotIterateOver()`](#doesnotiterateovervalue-expected-message) - [`deepIteratesOver()`](#deepiteratesovervalue-expected-message) - [`doesNotDeepIterateOver()`](#doesnotdeepiterateovervalue-expected-message) - [`iteratesFrom()`](#iteratesfromvalue-expected-message) - [`doesNotIterateFrom()`](#doesnotiteratefromvalue-expected-message) - [`deepIteratesFrom()`](#deepiteratesfromvalue-expected-message) - [`doesNotDeepIterateFrom()`](#doesnotdeepiteratefromvalue-expected-message) - [`iteratesUntil()`](#iteratesuntilvalue-expected-message) - [`doesNotIterateUntil()`](#doesnotiterateuntilvalue-expected-message) - [`deepIteratesUntil()`](#deepiteratesuntilvalue-expected-message) - [`doesNotDeepIterateUntil()`](#doesnotdeepiterateuntilvalue-expected-message) - [`lengthOf()`](#lengthofvalue-n-message) #### Parameters The parameters for the assert methods are as follows. | Param | Type | Description | | :------- | :------- | :--------------------------------------- | | value | `any` | Any value. | | expected | `object` | An iterable object. | | n | `number` | A positive integer. | | message? | `string` | An optional message to display on error. | #### `isIterable(value, [message])` Asserts that a value is an iterable object, i.e., that it is an object with an [`@@iterator`][iterator-method] method. ```js assert.isIterable([2, 3, 5]); assert.isIterable('abcdefg'); ``` #### `isNotIterable(value, [message])` Asserts that a value is not an iterable object, i.e., that it lacks an [`@@iterator`][iterator-method] method. ```js assert.isNotIterable(235); assert.isNotIterable(true); ``` #### `iteratesOver(value, expected, [message])` Asserts that a value iterates exactly over a given sequence of values. ```js assert.iteratesOver([2, 3, 5], [2, 3, 5]); assert.iteratesOver('abcdefg', 'abcdefg'); ``` #### `doesNotIterateOver(value, expected, [message])` Asserts that a value does not iterate exactly over a given sequence of values. ```js assert.doesNotIterateOver([2, 3, 5], [1, 2, 3]); assert.doesNotIterateOver('abcdefg', 'abc'); ``` #### `deepIteratesOver(value, expected, [message])` Asserts that a value iterates exactly over a given sequence of values, using deep equality. ```js assert.deepIteratesOver([{n: 2}, {n: 3}], [{n: 2}, {n: 3}]); assert.deepIteratesOver([[0, 2], [1, 3]], [[0, 2], [1, 3]]); ``` #### `doesNotDeepIterateOver(value, expected, [message])` Asserts that a value does not iterate exactly over a given sequence of values, using deep equality. ```js assert.doesNotDeepIterateOver([{n: 2}, {n: 3}], [{n: 5}, {n: 7}]); assert.doesNotDeepIterateOver([[0, 2], [1, 3]], [[1, 3], [0, 2]]); ``` #### `iteratesFrom(value, expected, [message])` Asserts that a value begins iteration with a given sequence of values. ```js assert.iteratesFrom([2, 3, 5], [2, 3, 5]); assert.iteratesFrom([2, 3, 5], [2, 3]); assert.iteratesFrom('abcdefg', 'abc'); assert.iteratesFrom('abcdefg', ''); ``` #### `doesNotIterateFrom(value, expected, [message])` Asserts that a value does not begin iteration with a given sequence of values. ```js assert.doesNotIterateFrom([2, 3, 5], [3, 5]); assert.doesNotIterateFrom('abcdefg', 'cdef'); ``` #### `deepIteratesFrom(value, expected, [message])` Asserts that a value begins iteration with a given sequence of values, using deep equality. ```js assert.deepIteratesFrom([{n: 2}, {n: 3}], [{n: 2}]); assert.deepIteratesFrom([[0, 2], [1, 3]], [[0, 2]]); ``` #### `doesNotDeepIterateFrom(value, expected, [message])` Asserts that a value does not begin iteration with a given sequence of values, using deep equality. ```js assert.doesNotDeepIterateFrom([{n: 2}, {n: 3}], [{n: 5}]); assert.doesNotDeepIterateFrom([[0, 2], [1, 3]], [[1, 3]]); ``` #### `iteratesUntil(value, expected, [message])` Asserts that a value ends iteration with a given sequence of values. ```js assert.iteratesUntil([2, 3, 5], [2, 3, 5]); assert.iteratesUntil([2, 3, 5], [3, 5]); assert.iteratesUntil('abcdefg', 'efg'); assert.iteratesUntil('abcdefg', ''); ``` #### `doesNotIterateUntil(value, expected, [message])` Asserts that a value does not end iteration with a given sequence of values. ```js assert.doesNotIterateUntil([2, 3, 5], [2, 3]); assert.doesNotIterateUntil('abcdefg', 'cdef'); ``` #### `deepIteratesUntil(value, expected, [message])` Asserts that a value ends iteration with a given sequence of values, using deep equality. ```js assert.deepIteratesUntil([{n: 2}, {n: 3}], [{n: 3}]); assert.deepIteratesUntil([[0, 2], [1, 3]], [[1, 3]]); ``` #### `doesNotDeepIterateUntil(value, expected, [message])` Asserts that a value does not end iteration with a given sequence of values, using deep equality. ```js assert.doesNotDeepIterateUntil([{n: 2}, {n: 3}], [{n: 5}]); assert.doesNotDeepIterateUntil([[0, 2], [1, 3]], [[0, 2]]); ``` #### `lengthOf(value, n, [message])` Asserts that an iterable yields a given number of values. If *value* is not an iterable object, or if it has a `'length'` property, Chai's built-in [`assert.lengthOf()`][chai-assert-lengthof] will be used. ```js function* range(min=0, max=Infinity, step=1) { for (let n = min; n < max; n += step) { yield n; } } assert.lengthOf(range(0, 10), 10); assert.lengthOf(range(6, 42), 36); ``` ## License Copyright &copy; 2016&ndash;2017 Akim McMath. Licensed under the [MIT License][license]. [version-badge]: https://badge.fury.io/js/chai-iterator.svg [build-badge]: https://travis-ci.com/harrysarson/chai-iterator.svg?branch=master [coverage-badge]: https://coveralls.io/repos/github/harrysarson/chai-iterator/badge.svg?branch=master [dependencies-badge]: https://badges.greenkeeper.io/harrysarson/chai-iterator.svg [npm]: https://www.npmjs.com/package/chai-iterator [travis]: https://travis-ci.com/harrysarson/chai-iterator [coveralls]: https://coveralls.io/github/harrysarson/chai-iterator?branch=master [greenkeeper]: https://greenkeeper.io [chai]: http://chaijs.com/ [iterable]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols#iterable [ecma-iterable]: http://www.ecma-international.org/ecma-262/6.0/#sec-iterable-interface [iterator-method]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol/iterator [for-of]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of [array-spread]: (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax) [built-in-iterable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#Builtin_iterables [custom-iterable]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#User-defined_iterables [assertion-style]: http://chaijs.com/guide/styles/ [class]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Classes [fibonacci-sequence]: https://en.wikipedia.org/wiki/Fibonacci_number [generator-function]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function* [generator]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Generator [iterator]: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols#iterator [node]: https://nodejs.org/ [browser-list]: https://kangax.github.io/compat-table/es6/#test-well-known_symbols_Symbol.iterator,_existence [babel-polyfill]: https://babeljs.io/docs/usage/polyfill/ [core-js]: https://github.com/zloirock/core-js [amd]: https://github.com/amdjs/amdjs-api/wiki/AMD [script-tag]: https://developer.mozilla.org/en/docs/Web/HTML/Element/script [typescript]: http://www.typescriptlang.org/ [chai-typings]: https://github.com/typed-typings/npm-chai [typings]: https://github.com/typings/typings [compiler-options]: https://www.typescriptlang.org/docs/handbook/compiler-options.html [es6-lib]: https://github.com/Microsoft/TypeScript/blob/master/lib/lib.es6.d.ts [deep]: http://chaijs.com/api/bdd/#method_deep [chai-npm]: https://www.npmjs.com/package/chai [chai-assert-lengthof]: http://chaijs.com/api/assert/#method_lengthof