chai-iterator
Version:
Chai assertions for iterable objects
335 lines (271 loc) • 8.26 kB
JavaScript
(function (chaiIterator) {
'use strict';
/* istanbul ignore else */
if (typeof module === 'object' && typeof exports === 'object') {
module.exports = chaiIterator;
} else if (typeof define === 'function' && define.amd) {
define(function () {
return chaiIterator;
});
} else {
chai.use(chaiIterator);
}
})(function (chai, utils) {
'use strict';
var Assertion = chai.Assertion;
var assert = chai.assert;
var noop = function () {};
var ELLIPSIS = unquote('...');
// Expect/should assertions =============================
Assertion.addProperty('iterable', function () {
this.assert(isIterable(this._obj),
'expected #{this} to be iterable',
'expected #{this} not to be iterable'
);
});
Assertion.addProperty('iterate', function () {
new Assertion(this._obj).iterable;
utils.flag(this, 'iterate', true);
});
Assertion.addMethod('over', iterateMethod('to iterate over', function (exp) {
var it = this._obj[Symbol.iterator]();
var actual = slice(it, exp.length + 2);
var suffix = it.next().done ? [] : [ELLIPSIS];
return actual.concat(suffix);
}));
Assertion.addMethod('from', iterateMethod('to begin iteration with', function (exp) {
return slice(this._obj[Symbol.iterator](), exp.length);
}));
Assertion.addMethod('until', iterateMethod('to end iteration with', function (exp) {
var it = this._obj[Symbol.iterator]();
var actual = slice(it, exp.length);
var step = it.next();
while (!step.done) {
actual.push(step.value);
actual.shift();
step = it.next();
}
return actual;
}));
Assertion.addProperty('for', noop);
Assertion.overwriteChainableMethod('lengthOf', function (_super) {
return function (exp) {
if (utils.flag(this, 'iterate')) {
var len = iterableLength(this._obj, exp);
this.assert(
len === exp,
'expected #{this} to iterate for length of #{exp}, but got #{act}',
'expected #{this} not to iterate for length of #{exp}',
exp,
len
);
} else {
_super.apply(this, arguments);
}
};
}, function (_super) {
return function () {
_super.apply(this);
};
});
Assertion.overwriteMethod('above', function (_super) {
return function (exp) {
if (utils.flag(this, 'iterate')) {
var len = iterableLength(this._obj, exp);
this.assert(
len > exp,
'expected #{this} to iterate for length above #{exp}, but got #{act}',
'expected #{this} not to iterate for length above #{exp}',
exp,
len
);
} else {
_super.apply(this, arguments);
}
};
});
Assertion.overwriteMethod('below', function (_super) {
return function (exp) {
if (utils.flag(this, 'iterate')) {
var max = exp + 100;
var len = iterableLength(this._obj, max);
var act = len === Infinity ? unquote('more than ' + max) : len;
this.assert(
len < exp,
'expected #{this} to iterate for length below #{exp}, but got #{act}',
'expected #{this} not to iterate for length below #{exp}',
exp,
act
);
} else {
_super.apply(this, arguments);
}
};
});
Assertion.overwriteMethod('least', function (_super) {
return function (exp) {
if (utils.flag(this, 'iterate')) {
var len = iterableLength(this._obj, exp);
this.assert(
len >= exp,
'expected #{this} to iterate for length of at least #{exp}, but got #{act}',
'expected #{this} not to iterate for length of at least #{exp}',
exp,
len
);
} else {
_super.apply(this, arguments);
}
};
});
Assertion.overwriteMethod('most', function (_super) {
return function (exp) {
if (utils.flag(this, 'iterate')) {
var max = exp + 100;
var len = iterableLength(this._obj, max);
var act = len === Infinity ? unquote('more than ' + max) : len;
this.assert(
len <= exp,
'expected #{this} to iterate for length of at most #{exp}, but got #{act}',
'expected #{this} not to iterate for length of at most #{exp}',
exp,
act
);
} else {
_super.apply(this, arguments);
}
};
});
Assertion.overwriteMethod('within', function (_super) {
return function (min, max) {
if (utils.flag(this, 'iterate')) {
var cutoff = max + 100;
var len = iterableLength(this._obj, cutoff);
var exp = unquote(min + 'and' + max);
var act = len === Infinity ? unquote('more than ' + cutoff) : len;
this.assert(
min <= len && len <= max,
'expected #{this} to iterate for length within #{exp}, but got #{act}',
'expected #{this} not to iterate for length within #{exp}',
exp,
act
);
} else {
_super.apply(this, arguments);
}
};
});
// Assert methods =======================================
assert.isIterable = function (value, msg) {
new Assertion(value, msg).iterable;
};
assert.isNotIterable = function (value, msg) {
new Assertion(value, msg).not.iterable;
};
assert.iteratesFrom = function (value, exp, msg) {
new Assertion(value, msg).iterate.from(exp);
};
assert.doesNotIterateFrom = function (value, exp, msg) {
new Assertion(value, msg).not.iterate.from(exp);
};
assert.deepIteratesFrom = function (value, exp, msg) {
new Assertion(value, msg).deep.iterate.from(exp);
};
assert.doesNotDeepIterateFrom = function (value, exp, msg) {
new Assertion(value, msg).not.deep.iterate.from(exp);
};
assert.iteratesOver = function (value, exp, msg) {
new Assertion(value, msg).iterate.over(exp);
};
assert.doesNotIterateOver = function (value, exp, msg) {
new Assertion(value, msg).not.iterate.over(exp);
};
assert.deepIteratesOver = function (value, exp, msg) {
new Assertion(value, msg).deep.iterate.over(exp);
};
assert.doesNotDeepIterateOver = function (value, exp, msg) {
new Assertion(value, msg).not.deep.iterate.over(exp);
};
assert.iteratesUntil = function (value, exp, msg) {
new Assertion(value, msg).iterate.until(exp);
};
assert.doesNotIterateUntil = function (value, exp, msg) {
new Assertion(value, msg).not.iterate.until(exp);
};
assert.deepIteratesUntil = function (value, exp, msg) {
new Assertion(value, msg).deep.iterate.until(exp);
};
assert.doesNotDeepIterateUntil = function (value, exp, msg) {
new Assertion(value, msg).not.deep.iterate.until(exp);
};
var _lengthOf = assert.lengthOf;
assert.lengthOf = function (value, exp, msg) {
if (isIterable(value) && typeof value.length !== 'number') {
new Assertion(value, msg).iterate.for.lengthOf(exp);
} else {
_lengthOf.apply(assert, arguments);
}
};
// Helpers ==============================================
function iterateMethod(predicate, getActual) {
return function (iterable) {
assert(utils.flag(this, 'iterate'), 'the iterate flag must be set');
new Assertion(iterable).iterable;
var exp = slice(iterable[Symbol.iterator]());
var act = getActual.call(this, exp);
var deep = utils.flag(this, 'deep') ? ' deep' : '';
this.assert(compareArrays(exp, act, deep),
'expected #{this} ' + predicate + deep + ' values #{exp}, but got #{act}',
'expected #{this} not ' + predicate + deep + ' values #{exp}',
exp,
act
);
};
}
// Utilities ============================================
function isIterable(value) {
return value !== undefined && value !== null && typeof value[Symbol.iterator] === 'function';
}
function iterableLength(iterable, max) {
max = max === undefined ? Infinity : max;
var it = iterable[Symbol.iterator]();
for (var i = 0; i <= max; i++) {
if (it.next().done) {
return i;
}
}
return Infinity;
}
function strictEqual(a, b) {
return a === b;
}
function compareArrays(exp, act, deep) {
var equalFn = deep ? utils.eql : strictEqual;
return exp.length === act.length && exp.every(function (value, i) {
return equalFn(value, act[i]);
});
}
function slice(it, stop) {
stop = stop === undefined ? Infinity : stop;
var result = [];
var max = stop - 1;
var step = it.next();
for (var i = 0; i <= max && !step.done; i++) {
result.push(step.value);
if (i < max) {
step = it.next();
}
}
return result;
}
function unquote(str) {
return {
inspect: function () {
return this.toString();
},
toString: function () {
return str;
}
};
}
});