should
Version:
test framework agnostic BDD-style assertions
1,845 lines (1,546 loc) • 122 kB
JavaScript
/*!
* should - test framework agnostic BDD-style assertions
* @version v13.2.3
* @author TJ Holowaychuk <tj@vision-media.ca>, Denis Bardadym <bardadymchik@gmail.com>
* @link https://github.com/shouldjs/should.js
* @license MIT
*/
(function () {
'use strict';
var types = {
NUMBER: 'number',
UNDEFINED: 'undefined',
STRING: 'string',
BOOLEAN: 'boolean',
OBJECT: 'object',
FUNCTION: 'function',
NULL: 'null',
ARRAY: 'array',
REGEXP: 'regexp',
DATE: 'date',
ERROR: 'error',
ARGUMENTS: 'arguments',
SYMBOL: 'symbol',
ARRAY_BUFFER: 'array-buffer',
TYPED_ARRAY: 'typed-array',
DATA_VIEW: 'data-view',
MAP: 'map',
SET: 'set',
WEAK_SET: 'weak-set',
WEAK_MAP: 'weak-map',
PROMISE: 'promise',
// node buffer
BUFFER: 'buffer',
// dom html element
HTML_ELEMENT: 'html-element',
HTML_ELEMENT_TEXT: 'html-element-text',
DOCUMENT: 'document',
WINDOW: 'window',
FILE: 'file',
FILE_LIST: 'file-list',
BLOB: 'blob',
HOST: 'host',
XHR: 'xhr',
// simd
SIMD: 'simd'
};
/*
* Simple data function to store type information
* @param {string} type Usually what is returned from typeof
* @param {string} cls Sanitized @Class via Object.prototype.toString
* @param {string} sub If type and cls the same, and need to specify somehow
* @private
* @example
*
* //for null
* new Type('null');
*
* //for Date
* new Type('object', 'date');
*
* //for Uint8Array
*
* new Type('object', 'typed-array', 'uint8');
*/
function Type(type, cls, sub) {
if (!type) {
throw new Error('Type class must be initialized at least with `type` information');
}
this.type = type;
this.cls = cls;
this.sub = sub;
}
Type.prototype = {
toString: function(sep) {
sep = sep || ';';
var str = [this.type];
if (this.cls) {
str.push(this.cls);
}
if (this.sub) {
str.push(this.sub);
}
return str.join(sep);
},
toTryTypes: function() {
var _types = [];
if (this.sub) {
_types.push(new Type(this.type, this.cls, this.sub));
}
if (this.cls) {
_types.push(new Type(this.type, this.cls));
}
_types.push(new Type(this.type));
return _types;
}
};
var toString = Object.prototype.toString;
/**
* Function to store type checks
* @private
*/
function TypeChecker() {
this.checks = [];
}
TypeChecker.prototype = {
add: function(func) {
this.checks.push(func);
return this;
},
addBeforeFirstMatch: function(obj, func) {
var match = this.getFirstMatch(obj);
if (match) {
this.checks.splice(match.index, 0, func);
} else {
this.add(func);
}
},
addTypeOf: function(type, res) {
return this.add(function(obj, tpeOf) {
if (tpeOf === type) {
return new Type(res);
}
});
},
addClass: function(cls, res, sub) {
return this.add(function(obj, tpeOf, objCls) {
if (objCls === cls) {
return new Type(types.OBJECT, res, sub);
}
});
},
getFirstMatch: function(obj) {
var typeOf = typeof obj;
var cls = toString.call(obj);
for (var i = 0, l = this.checks.length; i < l; i++) {
var res = this.checks[i].call(this, obj, typeOf, cls);
if (typeof res !== 'undefined') {
return { result: res, func: this.checks[i], index: i };
}
}
},
getType: function(obj) {
var match = this.getFirstMatch(obj);
return match && match.result;
}
};
var main = new TypeChecker();
//TODO add iterators
main
.addTypeOf(types.NUMBER, types.NUMBER)
.addTypeOf(types.UNDEFINED, types.UNDEFINED)
.addTypeOf(types.STRING, types.STRING)
.addTypeOf(types.BOOLEAN, types.BOOLEAN)
.addTypeOf(types.FUNCTION, types.FUNCTION)
.addTypeOf(types.SYMBOL, types.SYMBOL)
.add(function(obj) {
if (obj === null) {
return new Type(types.NULL);
}
})
.addClass('[object String]', types.STRING)
.addClass('[object Boolean]', types.BOOLEAN)
.addClass('[object Number]', types.NUMBER)
.addClass('[object Array]', types.ARRAY)
.addClass('[object RegExp]', types.REGEXP)
.addClass('[object Error]', types.ERROR)
.addClass('[object Date]', types.DATE)
.addClass('[object Arguments]', types.ARGUMENTS)
.addClass('[object ArrayBuffer]', types.ARRAY_BUFFER)
.addClass('[object Int8Array]', types.TYPED_ARRAY, 'int8')
.addClass('[object Uint8Array]', types.TYPED_ARRAY, 'uint8')
.addClass('[object Uint8ClampedArray]', types.TYPED_ARRAY, 'uint8clamped')
.addClass('[object Int16Array]', types.TYPED_ARRAY, 'int16')
.addClass('[object Uint16Array]', types.TYPED_ARRAY, 'uint16')
.addClass('[object Int32Array]', types.TYPED_ARRAY, 'int32')
.addClass('[object Uint32Array]', types.TYPED_ARRAY, 'uint32')
.addClass('[object Float32Array]', types.TYPED_ARRAY, 'float32')
.addClass('[object Float64Array]', types.TYPED_ARRAY, 'float64')
.addClass('[object Bool16x8]', types.SIMD, 'bool16x8')
.addClass('[object Bool32x4]', types.SIMD, 'bool32x4')
.addClass('[object Bool8x16]', types.SIMD, 'bool8x16')
.addClass('[object Float32x4]', types.SIMD, 'float32x4')
.addClass('[object Int16x8]', types.SIMD, 'int16x8')
.addClass('[object Int32x4]', types.SIMD, 'int32x4')
.addClass('[object Int8x16]', types.SIMD, 'int8x16')
.addClass('[object Uint16x8]', types.SIMD, 'uint16x8')
.addClass('[object Uint32x4]', types.SIMD, 'uint32x4')
.addClass('[object Uint8x16]', types.SIMD, 'uint8x16')
.addClass('[object DataView]', types.DATA_VIEW)
.addClass('[object Map]', types.MAP)
.addClass('[object WeakMap]', types.WEAK_MAP)
.addClass('[object Set]', types.SET)
.addClass('[object WeakSet]', types.WEAK_SET)
.addClass('[object Promise]', types.PROMISE)
.addClass('[object Blob]', types.BLOB)
.addClass('[object File]', types.FILE)
.addClass('[object FileList]', types.FILE_LIST)
.addClass('[object XMLHttpRequest]', types.XHR)
.add(function(obj) {
if ((typeof Promise === types.FUNCTION && obj instanceof Promise) ||
(typeof obj.then === types.FUNCTION)) {
return new Type(types.OBJECT, types.PROMISE);
}
})
.add(function(obj) {
if (typeof Buffer !== 'undefined' && obj instanceof Buffer) {// eslint-disable-line no-undef
return new Type(types.OBJECT, types.BUFFER);
}
})
.add(function(obj) {
if (typeof Node !== 'undefined' && obj instanceof Node) {
return new Type(types.OBJECT, types.HTML_ELEMENT, obj.nodeName);
}
})
.add(function(obj) {
// probably at the begginging should be enough these checks
if (obj.Boolean === Boolean && obj.Number === Number && obj.String === String && obj.Date === Date) {
return new Type(types.OBJECT, types.HOST);
}
})
.add(function() {
return new Type(types.OBJECT);
});
/**
* Get type information of anything
*
* @param {any} obj Anything that could require type information
* @return {Type} type info
* @private
*/
function getGlobalType(obj) {
return main.getType(obj);
}
getGlobalType.checker = main;
getGlobalType.TypeChecker = TypeChecker;
getGlobalType.Type = Type;
Object.keys(types).forEach(function(typeName) {
getGlobalType[typeName] = types[typeName];
});
function format(msg) {
var args = arguments;
for (var i = 1, l = args.length; i < l; i++) {
msg = msg.replace(/%s/, args[i]);
}
return msg;
}
var hasOwnProperty = Object.prototype.hasOwnProperty;
function EqualityFail(a, b, reason, path) {
this.a = a;
this.b = b;
this.reason = reason;
this.path = path;
}
function typeToString(tp) {
return tp.type + (tp.cls ? "(" + tp.cls + (tp.sub ? " " + tp.sub : "") + ")" : "");
}
var PLUS_0_AND_MINUS_0 = "+0 is not equal to -0";
var DIFFERENT_TYPES = "A has type %s and B has type %s";
var EQUALITY = "A is not equal to B";
var EQUALITY_PROTOTYPE = "A and B have different prototypes";
var WRAPPED_VALUE = "A wrapped value is not equal to B wrapped value";
var FUNCTION_SOURCES = "function A is not equal to B by source code value (via .toString call)";
var MISSING_KEY = "%s has no key %s";
var SET_MAP_MISSING_KEY = "Set/Map missing key %s";
var DEFAULT_OPTIONS = {
checkProtoEql: true,
checkSubType: true,
plusZeroAndMinusZeroEqual: true,
collectAllFails: false
};
function setBooleanDefault(property, obj, opts, defaults) {
obj[property] = typeof opts[property] !== "boolean" ? defaults[property] : opts[property];
}
var METHOD_PREFIX = "_check_";
function EQ(opts, a, b, path) {
opts = opts || {};
setBooleanDefault("checkProtoEql", this, opts, DEFAULT_OPTIONS);
setBooleanDefault("plusZeroAndMinusZeroEqual", this, opts, DEFAULT_OPTIONS);
setBooleanDefault("checkSubType", this, opts, DEFAULT_OPTIONS);
setBooleanDefault("collectAllFails", this, opts, DEFAULT_OPTIONS);
this.a = a;
this.b = b;
this._meet = opts._meet || [];
this.fails = opts.fails || [];
this.path = path || [];
}
function ShortcutError(fail) {
this.name = "ShortcutError";
this.message = "fail fast";
this.fail = fail;
}
ShortcutError.prototype = Object.create(Error.prototype);
EQ.checkStrictEquality = function(a, b) {
this.collectFail(a !== b, EQUALITY);
};
EQ.add = function add(type, cls, sub, f) {
var args = Array.prototype.slice.call(arguments);
f = args.pop();
EQ.prototype[METHOD_PREFIX + args.join("_")] = f;
};
EQ.prototype = {
check: function() {
try {
this.check0();
} catch (e) {
if (e instanceof ShortcutError) {
return [e.fail];
}
throw e;
}
return this.fails;
},
check0: function() {
var a = this.a;
var b = this.b;
// equal a and b exit early
if (a === b) {
// check for +0 !== -0;
return this.collectFail(a === 0 && 1 / a !== 1 / b && !this.plusZeroAndMinusZeroEqual, PLUS_0_AND_MINUS_0);
}
var typeA = getGlobalType(a);
var typeB = getGlobalType(b);
// if objects has different types they are not equal
if (typeA.type !== typeB.type || typeA.cls !== typeB.cls || typeA.sub !== typeB.sub) {
return this.collectFail(true, format(DIFFERENT_TYPES, typeToString(typeA), typeToString(typeB)));
}
// as types the same checks type specific things
var name1 = typeA.type,
name2 = typeA.type;
if (typeA.cls) {
name1 += "_" + typeA.cls;
name2 += "_" + typeA.cls;
}
if (typeA.sub) {
name2 += "_" + typeA.sub;
}
var f =
this[METHOD_PREFIX + name2] ||
this[METHOD_PREFIX + name1] ||
this[METHOD_PREFIX + typeA.type] ||
this.defaultCheck;
f.call(this, this.a, this.b);
},
collectFail: function(comparison, reason, showReason) {
if (comparison) {
var res = new EqualityFail(this.a, this.b, reason, this.path);
res.showReason = !!showReason;
this.fails.push(res);
if (!this.collectAllFails) {
throw new ShortcutError(res);
}
}
},
checkPlainObjectsEquality: function(a, b) {
// compare deep objects and arrays
// stacks contain references only
//
var meet = this._meet;
var m = this._meet.length;
while (m--) {
var st = meet[m];
if (st[0] === a && st[1] === b) {
return;
}
}
// add `a` and `b` to the stack of traversed objects
meet.push([a, b]);
// TODO maybe something else like getOwnPropertyNames
var key;
for (key in b) {
if (hasOwnProperty.call(b, key)) {
if (hasOwnProperty.call(a, key)) {
this.checkPropertyEquality(key);
} else {
this.collectFail(true, format(MISSING_KEY, "A", key));
}
}
}
// ensure both objects have the same number of properties
for (key in a) {
if (hasOwnProperty.call(a, key)) {
this.collectFail(!hasOwnProperty.call(b, key), format(MISSING_KEY, "B", key));
}
}
meet.pop();
if (this.checkProtoEql) {
//TODO should i check prototypes for === or use eq?
this.collectFail(Object.getPrototypeOf(a) !== Object.getPrototypeOf(b), EQUALITY_PROTOTYPE, true);
}
},
checkPropertyEquality: function(propertyName) {
var _eq = new EQ(this, this.a[propertyName], this.b[propertyName], this.path.concat([propertyName]));
_eq.check0();
},
defaultCheck: EQ.checkStrictEquality
};
EQ.add(getGlobalType.NUMBER, function(a, b) {
this.collectFail((a !== a && b === b) || (b !== b && a === a) || (a !== b && a === a && b === b), EQUALITY);
});
[getGlobalType.SYMBOL, getGlobalType.BOOLEAN, getGlobalType.STRING].forEach(function(tp) {
EQ.add(tp, EQ.checkStrictEquality);
});
EQ.add(getGlobalType.FUNCTION, function(a, b) {
// functions are compared by their source code
this.collectFail(a.toString() !== b.toString(), FUNCTION_SOURCES);
// check user properties
this.checkPlainObjectsEquality(a, b);
});
EQ.add(getGlobalType.OBJECT, getGlobalType.REGEXP, function(a, b) {
// check regexp flags
var flags = ["source", "global", "multiline", "lastIndex", "ignoreCase", "sticky", "unicode"];
while (flags.length) {
this.checkPropertyEquality(flags.shift());
}
// check user properties
this.checkPlainObjectsEquality(a, b);
});
EQ.add(getGlobalType.OBJECT, getGlobalType.DATE, function(a, b) {
//check by timestamp only (using .valueOf)
this.collectFail(+a !== +b, EQUALITY);
// check user properties
this.checkPlainObjectsEquality(a, b);
});
[getGlobalType.NUMBER, getGlobalType.BOOLEAN, getGlobalType.STRING].forEach(function(tp) {
EQ.add(getGlobalType.OBJECT, tp, function(a, b) {
//primitive type wrappers
this.collectFail(a.valueOf() !== b.valueOf(), WRAPPED_VALUE);
// check user properties
this.checkPlainObjectsEquality(a, b);
});
});
EQ.add(getGlobalType.OBJECT, function(a, b) {
this.checkPlainObjectsEquality(a, b);
});
[getGlobalType.ARRAY, getGlobalType.ARGUMENTS, getGlobalType.TYPED_ARRAY].forEach(function(tp) {
EQ.add(getGlobalType.OBJECT, tp, function(a, b) {
this.checkPropertyEquality("length");
this.checkPlainObjectsEquality(a, b);
});
});
EQ.add(getGlobalType.OBJECT, getGlobalType.ARRAY_BUFFER, function(a, b) {
this.checkPropertyEquality("byteLength");
this.checkPlainObjectsEquality(a, b);
});
EQ.add(getGlobalType.OBJECT, getGlobalType.ERROR, function(a, b) {
this.checkPropertyEquality("name");
this.checkPropertyEquality("message");
this.checkPlainObjectsEquality(a, b);
});
EQ.add(getGlobalType.OBJECT, getGlobalType.BUFFER, function(a) {
this.checkPropertyEquality("length");
var l = a.length;
while (l--) {
this.checkPropertyEquality(l);
}
//we do not check for user properties because
//node Buffer have some strange hidden properties
});
function checkMapByKeys(a, b) {
var iteratorA = a.keys();
for (var nextA = iteratorA.next(); !nextA.done; nextA = iteratorA.next()) {
var key = nextA.value;
var hasKey = b.has(key);
this.collectFail(!hasKey, format(SET_MAP_MISSING_KEY, key));
if (hasKey) {
var valueB = b.get(key);
var valueA = a.get(key);
eq(valueA, valueB, this);
}
}
}
function checkSetByKeys(a, b) {
var iteratorA = a.keys();
for (var nextA = iteratorA.next(); !nextA.done; nextA = iteratorA.next()) {
var key = nextA.value;
var hasKey = b.has(key);
this.collectFail(!hasKey, format(SET_MAP_MISSING_KEY, key));
}
}
EQ.add(getGlobalType.OBJECT, getGlobalType.MAP, function(a, b) {
this._meet.push([a, b]);
checkMapByKeys.call(this, a, b);
checkMapByKeys.call(this, b, a);
this._meet.pop();
this.checkPlainObjectsEquality(a, b);
});
EQ.add(getGlobalType.OBJECT, getGlobalType.SET, function(a, b) {
this._meet.push([a, b]);
checkSetByKeys.call(this, a, b);
checkSetByKeys.call(this, b, a);
this._meet.pop();
this.checkPlainObjectsEquality(a, b);
});
function eq(a, b, opts) {
return new EQ(opts, a, b).check();
}
eq.EQ = EQ;
var _hasOwnProperty = Object.prototype.hasOwnProperty;
var _propertyIsEnumerable = Object.prototype.propertyIsEnumerable;
function hasOwnProperty$1(obj, key) {
return _hasOwnProperty.call(obj, key);
}
function propertyIsEnumerable(obj, key) {
return _propertyIsEnumerable.call(obj, key);
}
function merge(a, b) {
if (a && b) {
for (var key in b) {
a[key] = b[key];
}
}
return a;
}
function isIterator(obj) {
if (!obj) {
return false;
}
if (obj.__shouldIterator__) {
return true;
}
return typeof obj.next === 'function' &&
typeof Symbol === 'function' &&
typeof Symbol.iterator === 'symbol' &&
typeof obj[Symbol.iterator] === 'function' &&
obj[Symbol.iterator]() === obj;
}
//TODO find better way
function isGeneratorFunction(f) {
return typeof f === 'function' && /^function\s*\*\s*/.test(f.toString());
}
// TODO in future add generators instead of forEach and iterator implementation
function ObjectIterator(obj) {
this._obj = obj;
}
ObjectIterator.prototype = {
__shouldIterator__: true, // special marker
next: function() {
if (this._done) {
throw new Error('Iterator already reached the end');
}
if (!this._keys) {
this._keys = Object.keys(this._obj);
this._index = 0;
}
var key = this._keys[this._index];
this._done = this._index === this._keys.length;
this._index += 1;
return {
value: this._done ? void 0: [key, this._obj[key]],
done: this._done
};
}
};
if (typeof Symbol === 'function' && typeof Symbol.iterator === 'symbol') {
ObjectIterator.prototype[Symbol.iterator] = function() {
return this;
};
}
function TypeAdaptorStorage() {
this._typeAdaptors = [];
this._iterableTypes = {};
}
TypeAdaptorStorage.prototype = {
add: function(type, cls, sub, adaptor) {
return this.addType(new getGlobalType.Type(type, cls, sub), adaptor);
},
addType: function(type, adaptor) {
this._typeAdaptors[type.toString()] = adaptor;
},
getAdaptor: function(tp, funcName) {
var tries = tp.toTryTypes();
while (tries.length) {
var toTry = tries.shift();
var ad = this._typeAdaptors[toTry];
if (ad && ad[funcName]) {
return ad[funcName];
}
}
},
requireAdaptor: function(tp, funcName) {
var a = this.getAdaptor(tp, funcName);
if (!a) {
throw new Error('There is no type adaptor `' + funcName + '` for ' + tp.toString());
}
return a;
},
addIterableType: function(tp) {
this._iterableTypes[tp.toString()] = true;
},
isIterableType: function(tp) {
return !!this._iterableTypes[tp.toString()];
}
};
var defaultTypeAdaptorStorage = new TypeAdaptorStorage();
var objectAdaptor = {
forEach: function(obj, f, context) {
for (var prop in obj) {
if (hasOwnProperty$1(obj, prop) && propertyIsEnumerable(obj, prop)) {
if (f.call(context, obj[prop], prop, obj) === false) {
return;
}
}
}
},
has: function(obj, prop) {
return hasOwnProperty$1(obj, prop);
},
get: function(obj, prop) {
return obj[prop];
},
iterator: function(obj) {
return new ObjectIterator(obj);
}
};
// default for objects
defaultTypeAdaptorStorage.addType(new getGlobalType.Type(getGlobalType.OBJECT), objectAdaptor);
defaultTypeAdaptorStorage.addType(new getGlobalType.Type(getGlobalType.FUNCTION), objectAdaptor);
var mapAdaptor = {
has: function(obj, key) {
return obj.has(key);
},
get: function(obj, key) {
return obj.get(key);
},
forEach: function(obj, f, context) {
var iter = obj.entries();
forEach(iter, function(value) {
return f.call(context, value[1], value[0], obj);
});
},
size: function(obj) {
return obj.size;
},
isEmpty: function(obj) {
return obj.size === 0;
},
iterator: function(obj) {
return obj.entries();
}
};
var setAdaptor = merge({}, mapAdaptor);
setAdaptor.get = function(obj, key) {
if (obj.has(key)) {
return key;
}
};
setAdaptor.iterator = function(obj) {
return obj.values();
};
defaultTypeAdaptorStorage.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.MAP), mapAdaptor);
defaultTypeAdaptorStorage.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.SET), setAdaptor);
defaultTypeAdaptorStorage.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.WEAK_SET), setAdaptor);
defaultTypeAdaptorStorage.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.WEAK_MAP), mapAdaptor);
defaultTypeAdaptorStorage.addType(new getGlobalType.Type(getGlobalType.STRING), {
isEmpty: function(obj) {
return obj === '';
},
size: function(obj) {
return obj.length;
}
});
defaultTypeAdaptorStorage.addIterableType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.ARRAY));
defaultTypeAdaptorStorage.addIterableType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.ARGUMENTS));
defaultTypeAdaptorStorage.addIterableType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.SET));
function forEach(obj, f, context) {
if (isGeneratorFunction(obj)) {
return forEach(obj(), f, context);
} else if (isIterator(obj)) {
var value = obj.next();
while (!value.done) {
if (f.call(context, value.value, 'value', obj) === false) {
return;
}
value = obj.next();
}
} else {
var type = getGlobalType(obj);
var func = defaultTypeAdaptorStorage.requireAdaptor(type, 'forEach');
func(obj, f, context);
}
}
function size(obj) {
var type = getGlobalType(obj);
var func = defaultTypeAdaptorStorage.getAdaptor(type, 'size');
if (func) {
return func(obj);
} else {
var len = 0;
forEach(obj, function() {
len += 1;
});
return len;
}
}
function isEmpty(obj) {
var type = getGlobalType(obj);
var func = defaultTypeAdaptorStorage.getAdaptor(type, 'isEmpty');
if (func) {
return func(obj);
} else {
var res = true;
forEach(obj, function() {
res = false;
return false;
});
return res;
}
}
// return boolean if obj has such 'key'
function has(obj, key) {
var type = getGlobalType(obj);
var func = defaultTypeAdaptorStorage.requireAdaptor(type, 'has');
return func(obj, key);
}
// return value for given key
function get(obj, key) {
var type = getGlobalType(obj);
var func = defaultTypeAdaptorStorage.requireAdaptor(type, 'get');
return func(obj, key);
}
function some(obj, f, context) {
var res = false;
forEach(obj, function(value, key) {
if (f.call(context, value, key, obj)) {
res = true;
return false;
}
}, context);
return res;
}
function isIterable(obj) {
return defaultTypeAdaptorStorage.isIterableType(getGlobalType(obj));
}
function iterator(obj) {
return defaultTypeAdaptorStorage.requireAdaptor(getGlobalType(obj), 'iterator')(obj);
}
function looksLikeANumber(n) {
return !!n.match(/\d+/);
}
function keyCompare(a, b) {
var aNum = looksLikeANumber(a);
var bNum = looksLikeANumber(b);
if (aNum && bNum) {
return 1*a - 1*b;
} else if (aNum && !bNum) {
return -1;
} else if (!aNum && bNum) {
return 1;
} else {
return a.localeCompare(b);
}
}
function genKeysFunc(f) {
return function(value) {
var k = f(value);
k.sort(keyCompare);
return k;
};
}
function Formatter(opts) {
opts = opts || {};
this.seen = [];
var keysFunc;
if (typeof opts.keysFunc === 'function') {
keysFunc = opts.keysFunc;
} else if (opts.keys === false) {
keysFunc = Object.getOwnPropertyNames;
} else {
keysFunc = Object.keys;
}
this.getKeys = genKeysFunc(keysFunc);
this.maxLineLength = typeof opts.maxLineLength === 'number' ? opts.maxLineLength : 60;
this.propSep = opts.propSep || ',';
this.isUTCdate = !!opts.isUTCdate;
}
Formatter.prototype = {
constructor: Formatter,
format: function(value) {
var tp = getGlobalType(value);
if (this.alreadySeen(value)) {
return '[Circular]';
}
var tries = tp.toTryTypes();
var f = this.defaultFormat;
while (tries.length) {
var toTry = tries.shift();
var name = Formatter.formatterFunctionName(toTry);
if (this[name]) {
f = this[name];
break;
}
}
return f.call(this, value).trim();
},
defaultFormat: function(obj) {
return String(obj);
},
alreadySeen: function(value) {
return this.seen.indexOf(value) >= 0;
}
};
Formatter.addType = function addType(tp, f) {
Formatter.prototype[Formatter.formatterFunctionName(tp)] = f;
};
Formatter.formatterFunctionName = function formatterFunctionName(tp) {
return '_format_' + tp.toString('_');
};
var EOL = '\n';
function indent(v, indentation) {
return v
.split(EOL)
.map(function(vv) {
return indentation + vv;
})
.join(EOL);
}
function pad(str, value, filler) {
str = String(str);
var isRight = false;
if (value < 0) {
isRight = true;
value = -value;
}
if (str.length < value) {
var padding = new Array(value - str.length + 1).join(filler);
return isRight ? str + padding : padding + str;
} else {
return str;
}
}
function pad0(str, value) {
return pad(str, value, '0');
}
var functionNameRE = /^\s*function\s*(\S*)\s*\(/;
function functionName(f) {
if (f.name) {
return f.name;
}
var matches = f.toString().match(functionNameRE);
if (matches === null) {
// `functionNameRE` doesn't match arrow functions.
return '';
}
var name = matches[1];
return name;
}
function constructorName(obj) {
while (obj) {
var descriptor = Object.getOwnPropertyDescriptor(obj, 'constructor');
if (descriptor !== undefined && typeof descriptor.value === 'function') {
var name = functionName(descriptor.value);
if (name !== '') {
return name;
}
}
obj = Object.getPrototypeOf(obj);
}
}
var INDENT = ' ';
function addSpaces(str) {
return indent(str, INDENT);
}
function typeAdaptorForEachFormat(obj, opts) {
opts = opts || {};
var filterKey = opts.filterKey || function() { return true; };
var formatKey = opts.formatKey || this.format;
var formatValue = opts.formatValue || this.format;
var keyValueSep = typeof opts.keyValueSep !== 'undefined' ? opts.keyValueSep : ': ';
this.seen.push(obj);
var formatLength = 0;
var pairs = [];
forEach(obj, function(value, key) {
if (!filterKey(key)) {
return;
}
var formattedKey = formatKey.call(this, key);
var formattedValue = formatValue.call(this, value, key);
var pair = formattedKey ? (formattedKey + keyValueSep + formattedValue) : formattedValue;
formatLength += pair.length;
pairs.push(pair);
}, this);
this.seen.pop();
(opts.additionalKeys || []).forEach(function(keyValue) {
var pair = keyValue[0] + keyValueSep + this.format(keyValue[1]);
formatLength += pair.length;
pairs.push(pair);
}, this);
var prefix = opts.prefix || constructorName(obj) || '';
if (prefix.length > 0) {
prefix += ' ';
}
var lbracket, rbracket;
if (Array.isArray(opts.brackets)) {
lbracket = opts.brackets[0];
rbracket = opts.brackets[1];
} else {
lbracket = '{';
rbracket = '}';
}
var rootValue = opts.value || '';
if (pairs.length === 0) {
return rootValue || (prefix + lbracket + rbracket);
}
if (formatLength <= this.maxLineLength) {
return prefix + lbracket + ' ' + (rootValue ? rootValue + ' ' : '') + pairs.join(this.propSep + ' ') + ' ' + rbracket;
} else {
return prefix + lbracket + '\n' + (rootValue ? ' ' + rootValue + '\n' : '') + pairs.map(addSpaces).join(this.propSep + '\n') + '\n' + rbracket;
}
}
function formatPlainObjectKey(key) {
return typeof key === 'string' && key.match(/^[a-zA-Z_$][a-zA-Z_$0-9]*$/) ? key : this.format(key);
}
function getPropertyDescriptor(obj, key) {
var desc;
try {
desc = Object.getOwnPropertyDescriptor(obj, key) || { value: obj[key] };
} catch (e) {
desc = { value: e };
}
return desc;
}
function formatPlainObjectValue(obj, key) {
var desc = getPropertyDescriptor(obj, key);
if (desc.get && desc.set) {
return '[Getter/Setter]';
}
if (desc.get) {
return '[Getter]';
}
if (desc.set) {
return '[Setter]';
}
return this.format(desc.value);
}
function formatPlainObject(obj, opts) {
opts = opts || {};
opts.keyValueSep = ': ';
opts.formatKey = opts.formatKey || formatPlainObjectKey;
opts.formatValue = opts.formatValue || function(value, key) {
return formatPlainObjectValue.call(this, obj, key);
};
return typeAdaptorForEachFormat.call(this, obj, opts);
}
function formatWrapper1(value) {
return formatPlainObject.call(this, value, {
additionalKeys: [['[[PrimitiveValue]]', value.valueOf()]]
});
}
function formatWrapper2(value) {
var realValue = value.valueOf();
return formatPlainObject.call(this, value, {
filterKey: function(key) {
//skip useless indexed properties
return !(key.match(/\d+/) && parseInt(key, 10) < realValue.length);
},
additionalKeys: [['[[PrimitiveValue]]', realValue]]
});
}
function formatRegExp(value) {
return formatPlainObject.call(this, value, {
value: String(value)
});
}
function formatFunction(value) {
return formatPlainObject.call(this, value, {
prefix: 'Function',
additionalKeys: [['name', functionName(value)]]
});
}
function formatArray(value) {
return formatPlainObject.call(this, value, {
formatKey: function(key) {
if (!key.match(/\d+/)) {
return formatPlainObjectKey.call(this, key);
}
},
brackets: ['[', ']']
});
}
function formatArguments(value) {
return formatPlainObject.call(this, value, {
formatKey: function(key) {
if (!key.match(/\d+/)) {
return formatPlainObjectKey.call(this, key);
}
},
brackets: ['[', ']'],
prefix: 'Arguments'
});
}
function _formatDate(value, isUTC) {
var prefix = isUTC ? 'UTC' : '';
var date = value['get' + prefix + 'FullYear']() +
'-' +
pad0(value['get' + prefix + 'Month']() + 1, 2) +
'-' +
pad0(value['get' + prefix + 'Date'](), 2);
var time = pad0(value['get' + prefix + 'Hours'](), 2) +
':' +
pad0(value['get' + prefix + 'Minutes'](), 2) +
':' +
pad0(value['get' + prefix + 'Seconds'](), 2) +
'.' +
pad0(value['get' + prefix + 'Milliseconds'](), 3);
var to = value.getTimezoneOffset();
var absTo = Math.abs(to);
var hours = Math.floor(absTo / 60);
var minutes = absTo - hours * 60;
var tzFormat = (to < 0 ? '+' : '-') + pad0(hours, 2) + pad0(minutes, 2);
return date + ' ' + time + (isUTC ? '' : ' ' + tzFormat);
}
function formatDate(value) {
return formatPlainObject.call(this, value, { value: _formatDate(value, this.isUTCdate) });
}
function formatError(value) {
return formatPlainObject.call(this, value, {
prefix: value.name,
additionalKeys: [['message', value.message]]
});
}
function generateFormatForNumberArray(lengthProp, name, padding) {
return function(value) {
var max = this.byteArrayMaxLength || 50;
var length = value[lengthProp];
var formattedValues = [];
var len = 0;
for (var i = 0; i < max && i < length; i++) {
var b = value[i] || 0;
var v = pad0(b.toString(16), padding);
len += v.length;
formattedValues.push(v);
}
var prefix = value.constructor.name || name || '';
if (prefix) {
prefix += ' ';
}
if (formattedValues.length === 0) {
return prefix + '[]';
}
if (len <= this.maxLineLength) {
return prefix + '[ ' + formattedValues.join(this.propSep + ' ') + ' ' + ']';
} else {
return prefix + '[\n' + formattedValues.map(addSpaces).join(this.propSep + '\n') + '\n' + ']';
}
};
}
function formatMap(obj) {
return typeAdaptorForEachFormat.call(this, obj, {
keyValueSep: ' => '
});
}
function formatSet(obj) {
return typeAdaptorForEachFormat.call(this, obj, {
keyValueSep: '',
formatKey: function() { return ''; }
});
}
function genSimdVectorFormat(constructorName, length) {
return function(value) {
var Constructor = value.constructor;
var extractLane = Constructor.extractLane;
var len = 0;
var props = [];
for (var i = 0; i < length; i ++) {
var key = this.format(extractLane(value, i));
len += key.length;
props.push(key);
}
if (len <= this.maxLineLength) {
return constructorName + ' [ ' + props.join(this.propSep + ' ') + ' ]';
} else {
return constructorName + ' [\n' + props.map(addSpaces).join(this.propSep + '\n') + '\n' + ']';
}
};
}
function defaultFormat(value, opts) {
return new Formatter(opts).format(value);
}
defaultFormat.Formatter = Formatter;
defaultFormat.addSpaces = addSpaces;
defaultFormat.pad0 = pad0;
defaultFormat.functionName = functionName;
defaultFormat.constructorName = constructorName;
defaultFormat.formatPlainObjectKey = formatPlainObjectKey;
defaultFormat.formatPlainObject = formatPlainObject;
defaultFormat.typeAdaptorForEachFormat = typeAdaptorForEachFormat;
// adding primitive types
Formatter.addType(new getGlobalType.Type(getGlobalType.UNDEFINED), function() {
return 'undefined';
});
Formatter.addType(new getGlobalType.Type(getGlobalType.NULL), function() {
return 'null';
});
Formatter.addType(new getGlobalType.Type(getGlobalType.BOOLEAN), function(value) {
return value ? 'true': 'false';
});
Formatter.addType(new getGlobalType.Type(getGlobalType.SYMBOL), function(value) {
return value.toString();
});
Formatter.addType(new getGlobalType.Type(getGlobalType.NUMBER), function(value) {
if (value === 0 && 1 / value < 0) {
return '-0';
}
return String(value);
});
Formatter.addType(new getGlobalType.Type(getGlobalType.STRING), function(value) {
return '\'' + JSON.stringify(value).replace(/^"|"$/g, '')
.replace(/'/g, "\\'")
.replace(/\\"/g, '"') + '\'';
});
Formatter.addType(new getGlobalType.Type(getGlobalType.FUNCTION), formatFunction);
// plain object
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT), formatPlainObject);
// type wrappers
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.NUMBER), formatWrapper1);
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.BOOLEAN), formatWrapper1);
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.STRING), formatWrapper2);
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.REGEXP), formatRegExp);
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.ARRAY), formatArray);
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.ARGUMENTS), formatArguments);
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.DATE), formatDate);
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.ERROR), formatError);
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.SET), formatSet);
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.MAP), formatMap);
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.WEAK_MAP), formatMap);
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.WEAK_SET), formatSet);
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.BUFFER), generateFormatForNumberArray('length', 'Buffer', 2));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.ARRAY_BUFFER), generateFormatForNumberArray('byteLength', 'ArrayBuffer', 2));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.TYPED_ARRAY, 'int8'), generateFormatForNumberArray('length', 'Int8Array', 2));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.TYPED_ARRAY, 'uint8'), generateFormatForNumberArray('length', 'Uint8Array', 2));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.TYPED_ARRAY, 'uint8clamped'), generateFormatForNumberArray('length', 'Uint8ClampedArray', 2));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.TYPED_ARRAY, 'int16'), generateFormatForNumberArray('length', 'Int16Array', 4));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.TYPED_ARRAY, 'uint16'), generateFormatForNumberArray('length', 'Uint16Array', 4));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.TYPED_ARRAY, 'int32'), generateFormatForNumberArray('length', 'Int32Array', 8));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.TYPED_ARRAY, 'uint32'), generateFormatForNumberArray('length', 'Uint32Array', 8));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.SIMD, 'bool16x8'), genSimdVectorFormat('Bool16x8', 8));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.SIMD, 'bool32x4'), genSimdVectorFormat('Bool32x4', 4));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.SIMD, 'bool8x16'), genSimdVectorFormat('Bool8x16', 16));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.SIMD, 'float32x4'), genSimdVectorFormat('Float32x4', 4));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.SIMD, 'int16x8'), genSimdVectorFormat('Int16x8', 8));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.SIMD, 'int32x4'), genSimdVectorFormat('Int32x4', 4));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.SIMD, 'int8x16'), genSimdVectorFormat('Int8x16', 16));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.SIMD, 'uint16x8'), genSimdVectorFormat('Uint16x8', 8));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.SIMD, 'uint32x4'), genSimdVectorFormat('Uint32x4', 4));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.SIMD, 'uint8x16'), genSimdVectorFormat('Uint8x16', 16));
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.PROMISE), function() {
return '[Promise]';//TODO it could be nice to inspect its state and value
});
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.XHR), function() {
return '[XMLHttpRequest]';//TODO it could be nice to inspect its state
});
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.HTML_ELEMENT), function(value) {
return value.outerHTML;
});
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.HTML_ELEMENT, '#text'), function(value) {
return value.nodeValue;
});
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.HTML_ELEMENT, '#document'), function(value) {
return value.documentElement.outerHTML;
});
Formatter.addType(new getGlobalType.Type(getGlobalType.OBJECT, getGlobalType.HOST), function() {
return '[Host]';
});
/*
* should.js - assertion library
* Copyright(c) 2010-2013 TJ Holowaychuk <tj@vision-media.ca>
* Copyright(c) 2013-2017 Denis Bardadym <bardadymchik@gmail.com>
* MIT Licensed
*/
function isWrapperType(obj) {
return obj instanceof Number || obj instanceof String || obj instanceof Boolean;
}
// XXX make it more strict: numbers, strings, symbols - and nothing else
function convertPropertyName(name) {
return typeof name === "symbol" ? name : String(name);
}
var functionName$1 = defaultFormat.functionName;
function isPlainObject(obj) {
if (typeof obj == "object" && obj !== null) {
var proto = Object.getPrototypeOf(obj);
return proto === Object.prototype || proto === null;
}
return false;
}
/*
* should.js - assertion library
* Copyright(c) 2010-2013 TJ Holowaychuk <tj@vision-media.ca>
* Copyright(c) 2013-2017 Denis Bardadym <bardadymchik@gmail.com>
* MIT Licensed
*/
var config = {
typeAdaptors: defaultTypeAdaptorStorage,
getFormatter: function(opts) {
return new defaultFormat.Formatter(opts || config);
}
};
function format$2(value, opts) {
return config.getFormatter(opts).format(value);
}
function formatProp(value) {
var formatter = config.getFormatter();
return defaultFormat.formatPlainObjectKey.call(formatter, value);
}
/*
* should.js - assertion library
* Copyright(c) 2010-2013 TJ Holowaychuk <tj@vision-media.ca>
* Copyright(c) 2013-2017 Denis Bardadym <bardadymchik@gmail.com>
* MIT Licensed
*/
/**
* should AssertionError
* @param {Object} options
* @constructor
* @memberOf should
* @static
*/
function AssertionError(options) {
merge(this, options);
if (!options.message) {
Object.defineProperty(this, "message", {
get: function() {
if (!this._message) {
this._message = this.generateMessage();
this.generatedMessage = true;
}
return this._message;
},
configurable: true,
enumerable: false
});
}
if (Error.captureStackTrace) {
Error.captureStackTrace(this, this.stackStartFunction);
} else {
// non v8 browsers so we can have a stacktrace
var err = new Error();
if (err.stack) {
var out = err.stack;
if (this.stackStartFunction) {
// try to strip useless frames
var fn_name = functionName$1(this.stackStartFunction);
var idx = out.indexOf("\n" + fn_name);
if (idx >= 0) {
// once we have located the function frame
// we need to strip out everything before it (and its line)
var next_line = out.indexOf("\n", idx + 1);
out = out.substring(next_line + 1);
}
}
this.stack = out;
}
}
}
var indent$1 = " ";
function prependIndent(line) {
return indent$1 + line;
}
function indentLines(text) {
return text
.split("\n")
.map(prependIndent)
.join("\n");
}
// assert.AssertionError instanceof Error
AssertionError.prototype = Object.create(Error.prototype, {
name: {
value: "AssertionError"
},
generateMessage: {
value: function() {
if (!this.operator && this.previous) {
return this.previous.message;
}
var actual = format$2(this.actual);
var expected = "expected" in this ? " " + format$2(this.expected) : "";
var details =
"details" in this && this.details ? " (" + this.details + ")" : "";
var previous = this.previous
? "\n" + indentLines(this.previous.message)
: "";
return (
"expected " +
actual +
(this.negate ? " not " : " ") +
this.operator +
expected +
details +
previous
);
}
}
});
/*
* should.js - assertion library
* Copyright(c) 2010-2013 TJ Holowaychuk <tj@vision-media.ca>
* Copyright(c) 2013-2017 Denis Bardadym <bardadymchik@gmail.com>
* MIT Licensed
*/
// a bit hacky way how to get error to do not have stack
function LightAssertionError(options) {
merge(this, options);
if (!options.message) {
Object.defineProperty(this, "message", {
get: function() {
if (!this._message) {
this._message = this.generateMessage();
this.generatedMessage = true;
}
return this._message;
}
});
}
}
LightAssertionError.prototype = {
generateMessage: AssertionError.prototype.generateMessage
};
/**
* should Assertion
* @param {*} obj Given object for assertion
* @constructor
* @memberOf should
* @static
*/
function Assertion(obj) {
this.obj = obj;
this.anyOne = false;
this.negate = false;
this.params = { actual: obj };
}
Assertion.prototype = {
constructor: Assertion,
/**
* Base method for assertions.
*
* Before calling this method need to fill Assertion#params object. This method usually called from other assertion methods.
* `Assertion#params` can contain such properties:
* * `operator` - required string containing description of this assertion
* * `obj` - optional replacement for this.obj, it is useful if you prepare more clear object then given
* * `message` - if this property filled with string any others will be ignored and this one used as assertion message
* * `expected` - any object used when you need to assert relation between given object and expected. Like given == expected (== is a relation)
* * `details` - additional string with details to generated message
*
* @memberOf Assertion
* @category assertion
* @param {*} expr Any expression that will be used as a condition for asserting.
* @example
*
* var a = new should.Assertion(42);
*
* a.params = {
* operator: 'to be magic number',
* }
*
* a.assert(false);
* //throws AssertionError: expected 42 to be magic number
*/
assert: function(expr) {
if (expr) {
return this;
}
var params = this.params;
if ("obj" in params && !("actual" in params)) {
params.actual = params.obj;
} else if (!("obj" in params) && !("actual" in params)) {
params.actual = this.obj;
}
params.stackStartFunction = params.stackStartFunction || this.assert;
params.negate = this.negate;
params.assertion = this;
if (this.light) {
throw new LightAssertionError(params);
} else {
throw new AssertionError(params);
}
},
/**
* Shortcut for `Assertion#assert(false)`.
*
* @memberOf Assertion
* @category assertion
* @example
*
* var a = new should.Assertion(42);
*
* a.params = {
* operator: 'to be magic number',
* }
*
* a.fail();
* //throws AssertionError: expected 42 to be magic number
*/
fail: function() {
return this.assert(false);
},
assertZeroArguments: function(args) {
if (args.length !== 0) {
throw new TypeError("This assertion does not expect any arguments. You may need to check your code");
}
}
};
/**
* Assertion used to delegate calls of Assertion methods inside of Promise.
* It has almost all methods of Assertion.prototype
*
* @param {Promise} obj
*/
function PromisedAssertion(/* obj */) {
Assertion.apply(this, arguments);
}
/**
* Make PromisedAssertion to look like promise. Delegate resolve and reject to given promise.
*
* @private
* @returns {Promise}
*/
PromisedAssertion.prototype.then = function(resolve, reject) {
return this.obj.then(resolve, reject);
};
/**
* Way to extend Assertion function. It uses some logic
* to define only positive assertions and itself rule with negative assertion.
*
* All actions happen in subcontext and this method take care about negation.
* Potentially we can add some more modifiers that does not depends from state of assertion.
*
* @memberOf Assertion
* @static
* @param {String} name Name of assertion. It will be used for defining method or getter on Assertion.prototype
* @param {Function} func Function that will be called on executing assertion
* @example
*
* Assertion.add('asset', function() {
* this.params = { operator: 'to be asset' }
*
* this.obj.should.have.property('id').which.is.a.Number()
* this.obj.should.have.property('path')
* })
*/
Assertion.add = function(name, func) {
Object.defineProperty(Assertion.prototype, name, {
enumerable: true,
configurable: true,
value: function() {
var context = new Assertion(this.obj, this, name);
context.anyOne = this.anyOne;
context.onlyThis = this.onlyThis;
// hack
context.light = true;
try {
func.apply(context, arguments);
} catch (e) {
// check for fail
if (e instanceof AssertionError || e instanceof LightAssertionError) {
// negative fail
if (this.negate) {
this.obj = context.obj;
this.negate = false;
return this;
}
if (context !== e.assertion) {
context.params.previous = e;
}
// positive fail
context.negate = false;
// hack
context.light = false;
context.fail();
}
// throw if it is another exception
throw e;
}
// negative pass
if (this.negate) {
context.negate = true; // because .fail will set negate
context.params.details = "false negative fail";
// hack
context.light = false;
context.fail();
}
// positive pass
if (!this.params.operator) {
this.params = context.params; // shortcut
}
this.obj = context.obj;
this.negate = false;
return this;
}
});
Object.defineProperty(PromisedAssertion.prototype, name, {
enumerable: true,
configurable: true,
value: function() {
var args = arguments;
this.obj = this.obj.then(function(a) {
return a[name].apply(a, args);
});
return this;
}
});
};
/**
* Add chaining getter to Assertion like .a, .which etc
*
* @memberOf Assertion
* @static
* @param {string} name name of getter
* @param {function} [onCall] optional function to call
*/
Assertion.addChain = function(name, onCall) {
onCall = onCall || function() {};
Object.defineProperty(Assertion.prototype, name, {
get: function() {
onCall.call(this);
return this;
},
enumerable: true
});
Object.defineProperty(PromisedAssertion.prototype, name, {
enumerable: true,
configurable: true,
get: function() {
this.obj = this.obj.then(function(a) {
return a[name];
});
return this;
}
});
};
/**
* Create alias for some `Assertion` property
*
* @memberOf Assertion
* @static
* @param {String} from Name of to map
* @param {String} to Name of alias
* @example
*
* Assertion.alias('true', 'True')
*/
Assertion.alias = function(from, to) {
var desc = Object.getOwnPropertyDescriptor(Assertion.prototype, from);
if (!desc) {
throw new Error("Alias " + from + " -> " + to + " could not be created as " + from + " not defined");
}
Object.defineProperty(Assertion.prototype, to, desc);
var desc2 =