UNPKG

should

Version:

test framework agnostic BDD-style assertions

1,845 lines (1,546 loc) 122 kB
/*! * 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 =