Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/mcmath/chai-iterator

Chai assertions for iterable objects
https://github.com/mcmath/chai-iterator

chai chai-plugin generator iterable iterator javascript plugin test testing typescript

Last synced: 3 months ago
JSON representation

Chai assertions for iterable objects

Awesome Lists containing this project

README

        

# Chai Iterator: Assertions for iterable objects

[![Version][version-badge]][npm]
[![License][license-badge]][license]
[![Build][build-badge]][travis]
[![Coverage][coverage-badge]][coveralls]
[![Dependencies][dependencies-badge]][gemnasium]

## 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.

#### 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

That's enough fiddling around with built-in objects whose iteration
behavior is well-known. 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; 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 — 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-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">

```

#### 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 © 2016–2017 Akim McMath. Licensed under the [MIT License][license].

[version-badge]: https://img.shields.io/npm/v/chai-iterator.svg?style=flat-square
[license-badge]: https://img.shields.io/npm/l/chai-iterator.svg?style=flat-square
[build-badge]: https://img.shields.io/travis/mcmath/chai-iterator/master.svg?style=flat-square
[coverage-badge]: https://img.shields.io/coveralls/mcmath/chai-iterator/master.svg?style=flat-square&service=github
[dependencies-badge]: https://img.shields.io/gemnasium/mcmath/chai-iterator.svg?style=flat-square
[npm]: https://www.npmjs.com/package/chai-iterator
[license]: LICENSE
[travis]: https://travis-ci.org/mcmath/chai-iterator
[coveralls]: https://coveralls.io/github/mcmath/chai-iterator?branch=master
[gemnasium]: https://gemnasium.com/mcmath/chai-iterator
[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
[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