core-functions
Advanced tools
Comparing version 2.0.14 to 3.0.0
@@ -35,6 +35,5 @@ 'use strict'; | ||
function isTrueOrFalse(value) { | ||
//TODO redundant (effectively an alias for isBoolean, since end results are same), but maybe do a micro-benchmark on the two to decide which to keep | ||
//return value === true || value === false || (value instanceof Boolean && isTrueOrFalse(value.valueOf())); | ||
//Redundant - effectively an alias for isBoolean, since end results are same, but maybe do a micro-benchmark on the two to decide which to keep | ||
return value === true || value === false || value instanceof Boolean; | ||
} | ||
345
numbers.js
'use strict'; | ||
/** The smallest positive normal value of type number, 2-1022. It is equal to the hexadecimal floating-point literal 0x1.0p-1022. */ | ||
// const MIN_NORMAL = 2.2250738585072014E-308; | ||
// const MAX_FIXED_DECIMAL_PLACES = 20; | ||
const integerRegex = /^[+-]?\d+(\.0*)?$/; | ||
const numberRegex = /^[+-]?(\d+\.?|\d*\.\d+)([eE][+-]?\d+)?$/; | ||
const zeroRegex = /^[+-]?0+(\.0*)?([eE][+-]?\d+)?$/; | ||
const leadingZeroesRegex = /^([+-]?)0+([1-9].*|0(\..*)?$)/; | ||
const trailingZeroesRegex = /^([^.]*\.(?:\d+?))0+(([eE][+-]?\d+)?)$/; | ||
/** | ||
@@ -16,3 +28,29 @@ * Module containing utilities for working with numbers. | ||
/** Returns true if the number is NaN or if it is an instance of Number and its value is NaN; false otherwise. */ | ||
isNaN: isNaN | ||
isNaN: isNaN, | ||
isInteger: isInteger, | ||
isSafeInteger: isSafeInteger, | ||
integerRegex: integerRegex, | ||
numberRegex: numberRegex, | ||
isNumberLike: isNumberLike, | ||
isIntegerLike: isIntegerLike, | ||
isZeroLike: isZeroLike, | ||
toNumberLike: toNumberLike, | ||
toDecimalLike: toDecimalLike, | ||
toDecimalLikeOrNaN: toDecimalLikeOrNaN, | ||
toIntegerLike: toIntegerLike, | ||
toIntegerLikeOrNaN: toIntegerLikeOrNaN, | ||
toNumberOrIntegerLike: toNumberOrIntegerLike, | ||
removeLeadingZeroes: removeLeadingZeroes, | ||
removeTrailingZeroes: removeTrailingZeroes, | ||
zeroPadLeft: zeroPadLeft, | ||
removeSignIfZero: removeSignIfZeroLike, | ||
nearlyEqual: nearlyEqual | ||
}; | ||
@@ -50,4 +88,4 @@ | ||
function isSpecialNumber(value) { | ||
return value === +Infinity || value === -Infinity || (typeof value === 'number' && Number.isNaN(value)) || | ||
(value instanceof Number && isSpecialNumber(value.valueOf())); | ||
if (value instanceof Number) value = value.valueOf(); | ||
return value === +Infinity || value === -Infinity || (typeof value === 'number' && Number.isNaN(value)); | ||
} | ||
@@ -62,4 +100,303 @@ | ||
function isNaN(value) { | ||
return (typeof value === 'number' && Number.isNaN(value)) || (value instanceof Number && isNaN(value.valueOf())); | ||
return (typeof value === 'number' && Number.isNaN(value)) || | ||
(value instanceof Number && Number.isNaN(value.valueOf())); | ||
} | ||
/** | ||
* Returns true if the given value is an integer. | ||
* @see Number.isInteger | ||
* @param {number|Number} value - the value to test | ||
* @returns {boolean} true if the given value is an integer; false otherwise | ||
*/ | ||
function isInteger(value) { | ||
return (typeof value === 'number' && Number.isInteger(value)) || | ||
(value instanceof Number && Number.isInteger(value.valueOf())); | ||
} | ||
/** | ||
* Returns true if the given value is a safe integer. | ||
* @see Number.isSafeInteger | ||
* @param {number|Number} value - the value to test | ||
* @returns {boolean} true if the given value is a safe integer; false otherwise | ||
*/ | ||
function isSafeInteger(value) { | ||
return (typeof value === 'number' && Number.isSafeInteger(value)) || | ||
(value instanceof Number && Number.isSafeInteger(value.valueOf())); | ||
} | ||
/** | ||
* Returns true if the given value is a number-like string containing a number of any precision. | ||
* @param {string} value - the value to test | ||
* @returns {boolean} true if the given value is a number string; false otherwise | ||
*/ | ||
function isNumberLike(value) { | ||
return typeof value === 'string' && numberRegex.test(value); | ||
} | ||
/** | ||
* Returns true if the given value is a integer-like string containing an integer of any precision. | ||
* @param {string} value - the value to test | ||
* @returns {boolean} true if the given value is an integer string; false otherwise | ||
*/ | ||
function isIntegerLike(value) { | ||
return typeof value === 'string' && (integerRegex.test(value) || | ||
(numberRegex.test(value) && integerRegex.test(toDecimalLike(value)))); | ||
} | ||
/** | ||
* Returns true if the given value is a zero number-like string; false otherwise. | ||
* @param {string} value - the value to test | ||
* @returns {boolean} true if the given value is a zero number-like string; false otherwise | ||
*/ | ||
function isZeroLike(value) { | ||
return typeof value === 'string' && zeroRegex.test(value); | ||
} | ||
/** | ||
* Converts the given number into a number-like string. | ||
* @param {number|Number} number - the number to convert | ||
* @returns {string} a number-like string | ||
*/ | ||
function toNumberLike(number) { | ||
// Unbox if Number object | ||
if (number instanceof Number) { | ||
number = number.valueOf() | ||
} | ||
return Number.isInteger(number) ? number.toFixed(0) : number.toPrecision(); //removeTrailingZeroes(number.toFixed(MAX_FIXED_DECIMAL_PLACES)); | ||
} | ||
/** | ||
* Converts the given number-like into a decimal-like string by replacing any exponent part with its decimal equivalent. | ||
* Precondition: number or isNumberLike(numberLike) | ||
* @param {string|number|Number} numberLike - a number-like string or number | ||
* @returns {string|undefined} a decimal-like string (if number-like); otherwise undefined | ||
*/ | ||
function toDecimalLike(numberLike) { | ||
if (typeof numberLike === 'number' || numberLike instanceof Number) { | ||
numberLike = toNumberLike(numberLike); // Convert any number into a number-like | ||
} else if (numberLike instanceof String) { | ||
numberLike = numberLike.valueOf(); // unbox String object | ||
} | ||
// Ensure that the given numberLike is actually number-like | ||
if (!isNumberLike(numberLike)) return undefined; | ||
// Convert number-like string into decimal-like string | ||
const signed = numberLike.length > 0 && (numberLike[0] === '-' || numberLike[0] === '+'); | ||
const sign = signed ? numberLike.substring(0, 1) : ''; | ||
let n = signed ? numberLike.substring(1) : numberLike; | ||
const decPos = n.indexOf('.'); | ||
const hasDecPoint = decPos !== -1; | ||
const ePos = n.indexOf('e'); | ||
const hasExponent = ePos !== -1; | ||
const intPart = removeLeadingZeroes(hasDecPoint || hasExponent ? n.substring(0, hasDecPoint ? decPos : ePos) : n); | ||
const fractionalPart = hasDecPoint ? n.substring(decPos + 1, hasExponent ? ePos : n.length) : ''; | ||
const decPlaces = fractionalPart.length; | ||
if (hasExponent) { | ||
// Convert exponent into decimal format | ||
let e = Number.parseInt(n.substring(ePos + 1)); | ||
if (e >= 0) { | ||
if (hasDecPoint) { | ||
if (e < decPlaces) { | ||
n = `${intPart}${fractionalPart.substring(0, e)}.${fractionalPart.substring(e)}`; | ||
} else if (e === decPlaces) { | ||
n = `${intPart}${fractionalPart}`; | ||
} else { // e > decPlaces | ||
n = `${intPart}${fractionalPart}${'0'.repeat(e - decPlaces)}`; | ||
} | ||
n = removeLeadingZeroes(n); | ||
} else { | ||
// has no decimal point | ||
n = `${e > 0 ? removeLeadingZeroes(`${intPart}${'0'.repeat(e)}`) : intPart}`; | ||
} | ||
} else { | ||
// e < 0 | ||
const intLen = intPart.length; | ||
e = -e; | ||
if (e < intLen) { | ||
n = `${intPart.substring(0, e)}.${intPart.substring(e)}${fractionalPart}`; | ||
} else if (e === intLen) { | ||
n = `0.${intPart}${fractionalPart}`; | ||
} else { // e > intLen | ||
n = `0.${'0'.repeat(e - intLen)}${intPart}${fractionalPart}`; | ||
} | ||
} | ||
} else { | ||
// has no exponent | ||
n = `${intPart}${decPlaces > 0 ? `.${fractionalPart}` : ''}`; | ||
} | ||
return !isZeroLike(n) ? `${sign}${n}` : n; | ||
} | ||
/** | ||
* A convenience version of {@linkcode toDecimalLike} function that returns NaN instead of undefined. | ||
* Precondition: number or isNumberLike(numberLike) | ||
* @param {number|string|Number|String} numberLike - the number or number-like string to convert | ||
* @returns {string|NaN} a decimal-like string (if number-like); otherwise NaN | ||
*/ | ||
function toDecimalLikeOrNaN(numberLike) { | ||
const decimalLike = toDecimalLike(numberLike); | ||
return decimalLike ? decimalLike : NaN; | ||
} | ||
/** | ||
* Converts the given number or number-like string into an integer-like string by first converting it into a decimal- | ||
* like string and then removing any fractional part. | ||
* Precondition: number or isNumberLike(numberLike) (or IDEALLY isIntegerLike(numberLike)) | ||
* @param {number|string|Number|String} numberLike - the number or number-like string to convert | ||
* @returns {string|undefined} an integer-like string (if number-like); otherwise undefined | ||
*/ | ||
function toIntegerLike(numberLike) { | ||
// First convert number or number-like string into a decimal-like string | ||
const decimalLike = toDecimalLike(numberLike); | ||
if (!decimalLike) return undefined; | ||
// and then remove any fractional part by truncating the number at the decimal point | ||
const decPos = decimalLike.indexOf('.'); | ||
return decPos !== -1 ? removeSignIfZeroLike(decimalLike.substring(0, decPos)) : decimalLike; | ||
} | ||
/** | ||
* A convenience version of {@linkcode toIntegerLike} function that returns NaN instead of undefined. | ||
* Precondition: number or isNumberLike(numberLike) (or IDEALLY isIntegerLike(numberLike)) | ||
* @param {number|string|Number|String} numberLike - the number or number-like string to convert | ||
* @returns {string|NaN} an integer-like string (if number-like); otherwise NaN | ||
*/ | ||
function toIntegerLikeOrNaN(numberLike) { | ||
const integerLike = toIntegerLike(numberLike); | ||
return integerLike ? integerLike : NaN; | ||
} | ||
/** | ||
* Converts the given value into a number (if its a number or a Number or a safe integer-like string or a number-like | ||
* string); or returns the given value (if its a big integer-like string that cannot be safely converted to an integer | ||
* without losing precision); otherwise returns NaN. | ||
* @param {string|String|number|Number} value - the value to convert | ||
* @returns {number|string|NaN} a number or big integer-like string or NaN | ||
*/ | ||
function toNumberOrIntegerLike(value) { | ||
// Unbox the given value if it is a Number or String object | ||
if (value instanceof Number || value instanceof String) { | ||
value = value.valueOf(); | ||
} | ||
const type = typeof value; | ||
if (type === 'number') { | ||
return value; | ||
} else if (type === 'string') { | ||
// Check if the string contains an integer | ||
if (isIntegerLike(value)) { | ||
// Integer-like string, so attempt to parse to an integer | ||
const n = Number.parseInt(value); | ||
// Check if have enough precision to hold the given integer value ... otherwise return the given value excluding | ||
// its fractional part (if any) | ||
return Number.isSafeInteger(n) ? n : Number.isNaN(n) ? NaN : toIntegerLikeOrNaN(value); | ||
} | ||
// Check if the string contains a number | ||
else if (isNumberLike(value)) { | ||
// Number-like string, so attempt to parse to a number | ||
return Number.parseFloat(value); | ||
} | ||
} | ||
return NaN; | ||
} | ||
/** | ||
* Returns the given number string without superfluous leading zeroes. | ||
* @param {string} numberString - the number string | ||
* @returns {string} the number string without superfluous leading zeroes | ||
*/ | ||
function removeLeadingZeroes(numberString) { | ||
return typeof numberString === 'string' ? numberString.replace(leadingZeroesRegex, '$1$2') : | ||
!numberString ? numberString : undefined; | ||
} | ||
/** | ||
* Returns the given number string without superfluous trailing zeroes. | ||
* @param {string} numberString - the number string | ||
* @returns {string} the number string without superfluous trailing zeroes | ||
*/ | ||
function removeTrailingZeroes(numberString) { | ||
return typeof numberString === 'string' ? numberString.replace(trailingZeroesRegex, '$1$2') : | ||
!numberString ? numberString : undefined; | ||
} | ||
/** | ||
* Pads the given number string with leading zeroes to create a number string with a number of digits equal to the given | ||
* number of digits (excluding any +/- sign). NB: If the given number string is signed then it must have a leading plus | ||
* or minus sign. | ||
* @param {string} numberString - the number string to pad | ||
* @param {number|undefined} [digits] - the optional number of digits up to which to pad the given number with leading | ||
* zeroes if necessary (if digits > 0); otherwise (if digits <= 0 or undefined) then returns the given number (without | ||
* any zero-padding) | ||
* @returns {string} the zero-padded number string | ||
* @throws an Error if the given number has more digits than the given number of digits | ||
*/ | ||
function zeroPadLeft(numberString, digits) { | ||
if (digits && digits > 0) { | ||
const signed = numberString.length > 0 && (numberString[0] === '-' || numberString[0] === '+'); | ||
const unsignedNumber = signed ? numberString.substring(1) : numberString; | ||
const len = unsignedNumber.length; | ||
if (len < digits) { | ||
return (signed ? numberString[0] : '') + '0'.repeat(digits - len) + unsignedNumber; | ||
} else if (len > digits) { | ||
throw new Error(`${numberString} has more than ${digits} digits`); | ||
} | ||
} | ||
return numberString; | ||
} | ||
/** | ||
* Returns the given number-like string either with its sign (if non-zero) or without its sign (if zero). | ||
* @param {string} numberLike - a number-like string | ||
* @returns {*} the given number-like string either with its sign (if non-zero) or without its sign (if zero) | ||
*/ | ||
function removeSignIfZeroLike(numberLike) { | ||
if (isZeroLike(numberLike)) { | ||
const signed = numberLike.length > 0 && (numberLike[0] === '-' || numberLike[0] === '+'); | ||
return signed ? numberLike.substring(1) : numberLike; | ||
} | ||
return numberLike; | ||
} | ||
/** | ||
* Compares two given floating-point numbers for approximate equality. | ||
* Algorithm below was inspired by http://floating-point-gui.de/errors/comparison/ | ||
* with an extra case personally added for comparing safe integers | ||
* @param {number} a - the first number to compare | ||
* @param {number} b - the second number to compare | ||
* @returns {boolean} true if x is approximately equal to y | ||
*/ | ||
function nearlyEqual(a, b) { | ||
if (a === b) { // shortcut that also handles case if both Infinity, MAX_VALUE or MIN_VALUE | ||
return true; | ||
} | ||
// Use an absolute epsilon check for safe integer comparisons | ||
if (Number.isSafeInteger(a) && Number.isSafeInteger(b)) { | ||
return Math.abs(a - b) < Number.EPSILON; | ||
} | ||
// Use a relative epsilon check for floating-point comparisons | ||
//let m = Math.abs(a) + Math.abs(b); | ||
//let m = Math.max(Math.abs(a), Math.abs(b)); | ||
let m = Math.abs(a)/2 + Math.abs(b)/2; // average magnitude, seems to work better than sum | ||
// Constrain m to be within the range MIN_VALUE to MAX_VALUE to avoid divide/times by zero & +/- infinities | ||
m = Math.min(Math.max(m, Number.MIN_VALUE), Number.MAX_VALUE); | ||
//return Math.abs((a - b) / m) < Number.EPSILON; | ||
return Math.abs(a - b) < Number.EPSILON * m; | ||
} | ||
// // Algorithm translated from http://floating-point-gui.de/errors/comparison/ | ||
// function nearlyEqual_0(a, b) { | ||
// const absA = Math.abs(a); | ||
// const absB = Math.abs(b); | ||
// const diff = Math.abs(a - b); | ||
// | ||
// if (a === b) { // shortcut, handles infinities | ||
// return true; | ||
// } else if (a === 0 || b === 0 || diff < MIN_NORMAL) { | ||
// // a or b is zero or both are extremely close to it | ||
// // relative error is less meaningful here | ||
// return diff < Number.EPSILON * MIN_NORMAL; | ||
// } else { // use relative error | ||
// return diff / Math.min(absA + absB, Number.MAX_VALUE) < Number.EPSILON; | ||
// } | ||
// } |
@@ -18,3 +18,4 @@ 'use strict'; | ||
getPropertyValue: getPropertyValue, | ||
copyNamedProperties: copyNamedProperties | ||
copyNamedProperties: copyNamedProperties, | ||
toKeyValuePairs: toKeyValuePairs | ||
}; | ||
@@ -33,11 +34,25 @@ | ||
/** | ||
* Merges the enumerable properties of the given 'from' object into the given 'to' object, only replacing same named | ||
* properties in the 'to' object if the given replace flag is true. Executes a deep merge if the given deep flag is true, | ||
* otherwise only does a shallow merge. Returns the updated 'to' object. | ||
* Merges the properties of the given 'from' object into the given 'to' object, only replacing same named properties in | ||
* the 'to' object if opts.replace is true. Executes a deep merge if opts.deep is true, otherwise only does a shallow | ||
* merge. Returns the updated 'to' object. | ||
* | ||
* Legacy parameters were (from, to, replace, deep), so for backward compatibility, convert any boolean opts or | ||
* (undefined opts and boolean 4th argument) into an appropriate object opts. | ||
* | ||
* @param {Object} from - the 'from' object from which to get enumerable properties to be merged into the 'to' object | ||
* @param {Object} to - the 'to' object to which to add or deep merge (or optionally replace) properties from the 'from' object | ||
* @param {boolean|undefined} [replace] - whether to replace properties in the 'to' object with same named properties in the from object or not | ||
* @param {boolean|undefined} [deep] - Executes a deep merge if the given deep flag is true, otherwise only does a shallow merge | ||
* @param {Object|undefined} [opts] - optional opts to use | ||
* @param {boolean|undefined} [opts.replace] - whether to replace properties in the 'to' object with same named properties in the from object or not (defaults to not) | ||
* @param {boolean|undefined} [opts.deep] - Executes a deep merge if true, otherwise only does a shallow merge (defaults to shallow) | ||
*/ | ||
function merge(from, to, replace, deep) { | ||
function merge(from, to, opts) { | ||
// Legacy parameters were (from, to, replace, deep), so for backward compatibility, convert any boolean opts or | ||
// (undefined opts and boolean 4th argument) into an appropriate object opts | ||
if (typeof opts === "boolean" || (!opts && typeof arguments[3] === "boolean")) { | ||
opts = {replace: !!opts, deep: !!arguments[3]}; | ||
} | ||
// Resolve the options from opts | ||
const replace = !!opts && opts.replace === true; | ||
const deep = !!opts && opts.deep === true; | ||
if (!from || typeof from !== 'object') throw new TypeError(`from must be a non-null object`); | ||
@@ -69,3 +84,3 @@ if (!to || typeof to !== 'object') throw new TypeError(`to must be a non-null object`); | ||
} else if (replace) { | ||
dest[i] = srcElementIsObject ? copy(srcElement, true) : srcElement; | ||
dest[i] = srcElementIsObject ? copy(srcElement, {deep: true}) : srcElement; | ||
} | ||
@@ -77,3 +92,3 @@ } | ||
const srcElementIsObject = srcElement && typeof srcElement === 'object'; | ||
dest.push(srcElementIsObject ? copy(srcElement, true) : srcElement); | ||
dest.push(srcElementIsObject ? copy(srcElement, {deep: true}) : srcElement); | ||
} | ||
@@ -102,6 +117,6 @@ } | ||
} else if (replace) { | ||
dest[name] = srcPropertyIsObject ? copy(srcProperty, true) : srcProperty; | ||
dest[name] = srcPropertyIsObject ? copy(srcProperty, {deep: true}) : srcProperty; | ||
} | ||
} else { | ||
dest[name] = srcPropertyIsObject ? copy(srcProperty, true) : srcProperty; | ||
dest[name] = srcPropertyIsObject ? copy(srcProperty, {deep: true}) : srcProperty; | ||
} | ||
@@ -116,12 +131,20 @@ } | ||
/** | ||
* Copies the enumerable properties of the given object into a new object. Executes a deep copy if the given deep flag | ||
* is true, otherwise only does a shallow copy. Returns the new copy of the original object or the original object if it | ||
* was NOT a non-null instance of Object. | ||
* Copies the properties of the given object into a new object. Executes a deep copy if opts.deep is true, otherwise | ||
* only does a shallow copy. Returns the new copy of the original object or the original "object" if it was NOT a non- | ||
* null instance of Object. | ||
* | ||
* Legacy parameters were (object, deep), so for backward compatibility, use any true opts as if it was true opts.deep | ||
* | ||
* @param {Object} object - the object from which to copy enumerable properties into a new object | ||
* @param {boolean|undefined} [deep] - Executes a deep copy if the given deep flag is true, otherwise only does a shallow copy | ||
* @param {Object|boolean|undefined} [opts] - optional opts to use (if opts is true, handles it as if opts.deep were true & does a deep copy) | ||
* @param {boolean|undefined} [opts.deep] - Executes a deep copy if opts.deep is true, otherwise only does a shallow copy (defaults to shallow) | ||
*/ | ||
function copy(object, deep) { | ||
function copy(object, opts) { | ||
if (!object || typeof object !== 'object') { | ||
return object; | ||
} | ||
// Resolve the options from opts | ||
// Legacy parameters were (object, deep), so for backward compatibility, use any true opts as if it was true opts.deep | ||
const deep = (!!opts && opts.deep === true) || opts === true; | ||
const history = new WeakMap(); | ||
@@ -191,15 +214,31 @@ | ||
* property name if compact is true. | ||
* | ||
* Legacy parameters were (src, propertyNames, compact, deep, omitPropertyIfUndefined), so for backward compatibility, | ||
* convert any boolean opts or (no opts, but boolean 4th or 5th arg) into an appropriate object opts. | ||
* | ||
* @param {Object} src - the source object from which to copy the named properties | ||
* @param {string[]} propertyNames - the list of named properties to be copied | ||
* @param {boolean|undefined} [compact] - whether to create a flatter, more-compact destination object, which will use | ||
* @param {Object|undefined} [opts] - optional opts to use | ||
* @param {boolean|undefined} [opts.compact] - whether to create a flatter, more-compact destination object, which will use | ||
* any compound property names as is and eliminate any unnecessary intermediate objects or rather create a more- | ||
* conventional, less-compact destination object, which will only have simple property names and all necessary intermediate objects | ||
* @param {boolean|undefined} [deep] - executes a deep copy of each property value if the given deep flag is true, otherwise only does a shallow copy | ||
* @param {boolean|undefined} [omitPropertyIfUndefined] - whether or not to omit any named property that has an undefined value from the destination object | ||
* @param {boolean|undefined} [opts.deep] - executes a deep copy of each property value if the given deep flag is true, otherwise only does a shallow copy | ||
* @param {boolean|undefined} [opts.omitIfUndefined] - whether or not to omit any named property that has an undefined value from the destination object | ||
* @returns {Object} a new object containing copies of only the named properties from the source object | ||
*/ | ||
function copyNamedProperties(src, propertyNames, compact, deep, omitPropertyIfUndefined) { | ||
function copyNamedProperties(src, propertyNames, opts) { | ||
if (!src || typeof src !== 'object') { | ||
return src === undefined ? undefined : src === null ? null : {}; | ||
} | ||
// Legacy parameters were (src, propertyNames, compact, deep, omitPropertyIfUndefined), so for backward compatibility, | ||
// convert any boolean opts or (no opts, but boolean 4th or 5th arg) into an appropriate object opts | ||
if (typeof opts === "boolean" || (!opts && (typeof arguments[3] === "boolean" || typeof arguments[4] === "boolean"))) { | ||
opts = {compact: !!opts, deep: !!arguments[3], omitIfUndefined: !!arguments[4]}; | ||
} | ||
// Resolve the options from opts | ||
const compact = !!opts && opts.compact === true; | ||
const deep = !!opts && opts.deep === true; | ||
const deepOpts = {deep: deep}; | ||
const omitIfUndefined = !!opts && opts.omitIfUndefined === true; | ||
const dest = {}; | ||
@@ -209,5 +248,5 @@ for (let i = 0; i < propertyNames.length; ++i) { | ||
const propertyValue = getPropertyValue(src, propertyName); | ||
if (!omitPropertyIfUndefined || propertyValue !== undefined) { | ||
if (!omitIfUndefined || propertyValue !== undefined) { | ||
if (compact) { | ||
dest[propertyName] = copy(propertyValue, deep); | ||
dest[propertyName] = copy(propertyValue, deepOpts); | ||
} else { | ||
@@ -224,3 +263,3 @@ const names = propertyName.split(".").map(n => trim(n)).filter(name => isNotBlank(name)); | ||
} | ||
d[names[names.length - 1]] = copy(propertyValue, deep); | ||
d[names[names.length - 1]] = copy(propertyValue, deepOpts); | ||
} | ||
@@ -231,2 +270,12 @@ } | ||
return dest; | ||
} | ||
/** | ||
* Extracts an array of key value pairs from the given object. Each key value pair is represented as an array containing | ||
* a key property name followed by its associated value. | ||
* @param {Object} object - an object | ||
* @returns {KeyValuePair[]} an array of key value pairs | ||
*/ | ||
function toKeyValuePairs(object) { | ||
return object && typeof object === 'object' ? Object.getOwnPropertyNames(object).map(key => [key, object[key]]) : []; | ||
} |
{ | ||
"name": "core-functions", | ||
"version": "2.0.14", | ||
"version": "3.0.0", | ||
"description": "Core functions, utilities and classes for working with Node/JavaScript primitives and built-in objects, including strings, booleans, Promises, base 64, Arrays, Objects, standard AppErrors, etc.", | ||
@@ -5,0 +5,0 @@ "author": "Byron du Preez", |
311
promises.js
'use strict'; | ||
const tries = require('./tries'); | ||
//const Try = tries.Try; | ||
const Success = tries.Success; | ||
const Failure = tries.Failure; | ||
/** | ||
* Module containing native Promise utility functions, which are installed directly onto the native {@linkcode Promise} | ||
* class. | ||
* An Error subclass thrown to cancel/short-circuit a promise that is waiting for a list of promises to resolve. | ||
* @property (Success|Failure)[]} resolvedOutcomes - a list of resolved outcomes | ||
* @property {Promise[]} unresolvedPromises - a list of unresolved promises | ||
* @property {boolean} completed - whether all of the promises resolved prior to cancellation or not | ||
*/ | ||
class CancelledError extends Error { | ||
/** | ||
* Constructs a new CancelledError. | ||
* @param {(Success|Failure)[]} resolvedOutcomes - the list of resolved outcomes | ||
* @param {Promise[]} unresolvedPromises - the list of unresolved promises | ||
*/ | ||
constructor(resolvedOutcomes, unresolvedPromises) { | ||
const doneCount = resolvedOutcomes ? resolvedOutcomes.length : 0; | ||
const totalCount = doneCount + (unresolvedPromises ? unresolvedPromises.length : 0); | ||
const message = 'Cancelled' + (totalCount > 0 ? ` after resolving ${doneCount} outcome${doneCount !== 1 ? 's' : ''} of ${totalCount} promise${totalCount !== 1 ? 's' : ''}` : ''); | ||
super(message); | ||
Object.defineProperty(this, 'message', {writable: false, enumerable: true, configurable: false}); | ||
Object.defineProperty(this, 'name', {value: this.constructor.name}); | ||
Object.defineProperty(this, 'resolvedOutcomes', {value: resolvedOutcomes, enumerable: false}); | ||
Object.defineProperty(this, 'unresolvedPromises', {value: unresolvedPromises, enumerable: false}); | ||
Object.defineProperty(this, 'completed', {value: doneCount === totalCount, enumerable: false}); | ||
} | ||
} | ||
/** | ||
* Module containing Promise utility functions. | ||
* @module core-functions/promises | ||
@@ -10,4 +39,6 @@ * @author Byron du Preez | ||
module.exports = { | ||
/** Returns true if the given value is a Promise or at least a "then-able"; otherwise false. */ | ||
/** Returns true if the given value is a native Promise; otherwise false */ | ||
isPromise: isPromise, | ||
/** Returns true if the given value is a native Promise or a promise-like ("then-able") object; otherwise false */ | ||
isPromiseLike: isPromiseLike, | ||
/** Returns a function that will wrap and convert a node-style function into a Promise-returning function */ | ||
@@ -18,26 +49,65 @@ wrap: wrap, | ||
/** Triggers execution of the given (typically synchronous) no-arg function, which may throw an error, within a new promise and returns the new promise */ | ||
try: tryFn, | ||
try: attempt, | ||
/** Starts a simple timeout Promise, which will resolve after the specified delay in milliseconds */ | ||
delay: delay, | ||
/** Transforms a result into a single Promise by using Promise.all (if result is an array of promises), the promise-result or Promise.resolve */ | ||
/** Transforms the given result into a single Promise by applying Promise.all to the given result (if it's an Array); otherwise by applying Promise.resolve to the given non-Array result */ | ||
allOrOne: allOrOne, | ||
/** Utility function to check if a result is an array of promises */ | ||
isArrayOfPromises: isArrayOfPromises | ||
/** Returns a promise that will return a list of Success or Failure outcomes, each of which contains either a resolved value or a rejected error, for the given promises */ | ||
every: every, | ||
/** Maps the given promise to a native Promise of a Success or Failure outcome */ | ||
one: one, | ||
/** Transforms the given promise-like object (or non-promise value) into a native Promise */ | ||
toPromise: toPromise, | ||
/** An Error subclass thrown to cancel/short-circuit a promise that is waiting for a list of promises to resolve. */ | ||
CancelledError: CancelledError | ||
}; | ||
const timers = require('./timers'); | ||
/** | ||
* Returns true if the given value is a Promise or at least a "then-able"; otherwise false. | ||
* Returns true if the given value is a native Promise; otherwise false. | ||
* @param {*} value - the value to check | ||
* @returns {boolean|*} true if its a promise (or a "then-able"); false otherwise | ||
* @returns {boolean|*} true if its a native Promise; false otherwise | ||
*/ | ||
function isPromise(value) { | ||
return value instanceof Promise || (value && value.then && typeof value.then === 'function'); | ||
return value instanceof Promise; | ||
} | ||
if (!Promise.isPromise) { // polyfill-safe guard check | ||
Promise.isPromise = isPromise; | ||
/** | ||
* Returns true if the given value is a native Promise or a promise-like ("then-able") object; otherwise false. | ||
* @param {*} value - the value to check | ||
* @returns {boolean|*} true if its a promise or a promise-like ("then-able") object; false otherwise | ||
*/ | ||
function isPromiseLike(value) { | ||
return value instanceof Promise || (!!value && !!value.then && typeof value.then === 'function'); | ||
} | ||
/** | ||
* Transforms the given promise-like object (or non-promise value) into a native Promise using the following process: | ||
* 1. If the given promiseLike is already a native Promise, then just returns it; | ||
* 2. If the given promiseLike is a "then-able", promise-like object (i.e. it has a "then" method)***, then wraps a call | ||
* to its "then" method in a new native Promise (in order to transfer its resolved result or rejected error to the | ||
* new Promise) and then returns the new Promise; otherwise | ||
* 3. If the given promiseLike is anything else, returns Promise.resolve(promiseLike). | ||
* | ||
* ***NB: Any given "then-able" promiseLike MUST accept the same arguments as Promise.then (i.e. a resolve function and | ||
* a reject function) - if this is NOT the case, then do NOT use this function on it! | ||
* | ||
* @param {Promise|PromiseLike|*} promiseLike - the promise-like object to convert into a native promise | ||
* @returns {Promise} a native Promise of the given promise-like object's resolved result or rejected error | ||
*/ | ||
function toPromise(promiseLike) { | ||
return promiseLike instanceof Promise ? promiseLike : isPromiseLike(promiseLike) ? | ||
new Promise((resolve, reject) => { | ||
try { | ||
// Assumption: "then-able" accepts the same arguments as Promise.then | ||
promiseLike.then( | ||
result => resolve(result), | ||
error => reject(error) | ||
); | ||
} catch (err) { | ||
reject(err) | ||
} | ||
}) : Promise.resolve(promiseLike); | ||
} | ||
/** | ||
* Wraps and converts the given node-style function into a Promise-returning function, which when invoked must be passed | ||
@@ -53,2 +123,4 @@ * all of the wrapped function's arguments other than its last callback argument. | ||
* Example: | ||
* // Assuming an import as follows | ||
* const Promises = require('core-functions/promises'); | ||
* | ||
@@ -67,3 +139,3 @@ * // crude example of a node-style function | ||
* | ||
* Promise.wrap(nodeStyleFunction)(arg1, arg2, ..., argN) | ||
* Promises.wrap(nodeStyleFunction)(arg1, arg2, ..., argN) | ||
* .then(result => ...) | ||
@@ -74,7 +146,7 @@ * .catch(err => ...); | ||
* Example: | ||
* Promise.wrap(obj.nodeStyleMethod.bind(obj))(arg1, arg2, ..., argN) | ||
* Promises.wrap(obj.nodeStyleMethod.bind(obj))(arg1, arg2, ..., argN) | ||
* .then(result => ...) | ||
* .catch(err => ...); | ||
* | ||
* OR instead use the Promise.wrapMethod function below. | ||
* OR instead use the Promises.wrapMethod function below. | ||
* | ||
@@ -91,3 +163,3 @@ * @param {Function} fn - a Node-callback style function to promisify | ||
const args = new Array(len + 1); | ||
for (let i = 0; i < len; i++) { | ||
for (let i = 0; i < len; ++i) { | ||
args[i] = arguments[i]; | ||
@@ -105,5 +177,2 @@ } | ||
} | ||
if (!Promise.wrap) { // polyfill-safe guard check | ||
Promise.wrap = wrap; | ||
} | ||
@@ -117,5 +186,6 @@ /** | ||
* Example: | ||
* const Promises = require('core-functions/promises'); | ||
* | ||
* // crude example of a node-style method | ||
* var object = { | ||
* const object = { | ||
* nodeStyleMethod: function nodeStyleMethod(arg1, arg2, ..., argN, callback) { | ||
@@ -133,3 +203,3 @@ * // example synchronous invocation of callback with error | ||
* | ||
* Promise.wrapMethod(obj, obj.nodeStyleMethod)(arg1, arg2, ..., argN) | ||
* Promises.wrapMethod(obj, obj.nodeStyleMethod)(arg1, arg2, ..., argN) | ||
* .then(result => ...) | ||
@@ -149,3 +219,3 @@ * .catch(err => ...); | ||
const args = new Array(len + 1); | ||
for (let i = 0; i < len; i++) { | ||
for (let i = 0; i < len; ++i) { | ||
args[i] = arguments[i]; | ||
@@ -163,9 +233,7 @@ } | ||
} | ||
if (!Promise.wrapMethod) { // polyfill-safe guard check | ||
Promise.wrapMethod = wrapMethod; | ||
} | ||
/** | ||
* Triggers execution of the given (typically synchronous) no-arg function, which may throw an error, within a new | ||
* promise and returns this new promise. | ||
* Triggers execution of the given (typically synchronous) no-arg function, which may throw an error, and returns a | ||
* promise of its result or failure. | ||
* | ||
* Use this function to safely start a new promise chain and ensure that any errors that may be thrown (when the given | ||
@@ -175,7 +243,10 @@ * function's body is executed) cause the promise to be properly rejected. | ||
* | ||
* Preamble to examples: | ||
* const Promises = require('core-functions/promises'); | ||
* | ||
* Example 1: | ||
* Promise.try(() => functionThatMayThrow(arg1, arg2, ..., argN)); | ||
* Promises.try(() => functionThatMayThrow(arg1, arg2, ..., argN)); | ||
* | ||
* Example 2: | ||
* Promise.try(() => { | ||
* Promises.try(() => { | ||
* // code that may throw an error | ||
@@ -192,14 +263,10 @@ * let result = functionThatMayThrow(arg1, arg2, ..., argN) | ||
* | ||
* Promise.try(functionToTry); | ||
* Promises.try(functionToTry); | ||
* | ||
* @param {Function} fn - the function to execute within a promise | ||
* @returns {Promise} the promise to execute the given function | ||
* @returns {Promise} a promise of the executed function's result or failure | ||
*/ | ||
function tryFn(fn) { | ||
//return new Promise((resolve, reject) => resolve(fn())); | ||
function attempt(fn) { | ||
try { | ||
const promiseOrResult = fn(); | ||
// If the executed fn returned a promise, just return that; otherwise wrap its non-promise result in a promise | ||
//return isPromise(promiseOrResult) ? promiseOrResult : Promise.resolve(promiseOrResult); | ||
return allOrOne(promiseOrResult); | ||
return Promise.resolve(fn()); | ||
} catch (err) { | ||
@@ -209,16 +276,12 @@ return Promise.reject(err); | ||
} | ||
if (!Promise.try) { // polyfill-safe guard check | ||
Promise.try = tryFn; | ||
} | ||
/** | ||
* Starts a simple timeout Promise, which will resolve after the specified delay in milliseconds. If any object is | ||
* passed into this function as the cancellable argument, then this function will install a cancelTimeout method on it, | ||
* which accepts a single optional mustResolve argument and which if subsequently invoked will cancel the timeout and | ||
* either resolve the promise (if mustResolve) or reject the promise (default), but ONLY if the timeout | ||
* has not triggered yet. | ||
* Starts a simple timeout Promise, which will resolve after the specified delay in milliseconds. If any non-null object | ||
* is passed into this function as the `cancellable` argument, then this function will also install a `cancelTimeout` method | ||
* on it, which accepts a single optional mustResolve argument and which if subsequently invoked will cancel the timeout | ||
* and either resolve the promise (if mustResolve) or reject the promise (default), but ONLY if the timeout has not | ||
* triggered yet. | ||
* | ||
* @param {number} ms - the number of milliseconds to delay | ||
* @param {Object|undefined|null} [cancellable] - an arbitrary object onto which a cancelTimeout method will be installed | ||
* @returns {Function|undefined} [cancellable.cancelTimeout] - installs a cancelTimeout method on the given cancellable | ||
* @param {Object|DelayCancellable|*} [cancellable] - an arbitrary object onto which a `cancelTimeout` method will be installed | ||
* @returns {Promise} the timeout Promise | ||
@@ -255,70 +318,118 @@ */ | ||
} | ||
if (!Promise.delay) { // polyfill-safe guard check | ||
Promise.delay = delay; | ||
} | ||
/** | ||
* Transforms the given result into a single Promise by doing the following: applies Promise.all to the given result and | ||
* returns its Promise (if result is an array of promises); or returns the given promise result (if it's already a | ||
* Promise); otherwise wraps the given non-promise result in a Promise.resolve. | ||
* Transforms the given result into a single Promise by applying Promise.all to the given result (if it's an Array); | ||
* otherwise by applying Promise.resolve to the given non-Array result. Note: Promise.resolve only wraps non-promises, | ||
* so there is no need to do any extra isPromise check for a single promise. | ||
* | ||
* @param {Promise|Promise[]|*} result - a promise or an array of promises or a non-promise result | ||
* @returns {Promise} a single promise containing the given result or containing the result's results if the result was | ||
* an array of promises | ||
* @param {Promise[]|*[]|Promise|*} result - an Array of promises (and/or non-promise values); OR a single promise (or | ||
* non-promise value) | ||
* @returns {Promise} a single promise of the given non-Array result's result or of all of the given Array result's results | ||
*/ | ||
function allOrOne(result) { | ||
return Array.isArray(result) && result.every(r => isPromise(r)) ? Promise.all(result) : | ||
isPromise(result) ? result : Promise.resolve(result); | ||
return Array.isArray(result) ? Promise.all(result) : Promise.resolve(result); | ||
} | ||
if (!Promise.allOrOne) { // polyfill-safe guard check | ||
Promise.allOrOne = allOrOne; | ||
} | ||
/** | ||
* Returns true if the given result is an array of promises; false otherwise. | ||
* @param {*} result - the result to check | ||
* @returns {boolean} true if array of promises; false otherwise | ||
* Returns a promise that will return a list of Success or Failure outcomes, each of which contains either a resolved | ||
* value or a rejected error, for the given promises in the same sequence as the given promises, when every one of the | ||
* given promises has resolved. The given array of `promises` can contain zero or more promises and/or non-promise | ||
* values. If any non-null object is passed into this function as the `cancellable` argument, then this function will | ||
* also install a `cancel` method on it, which expects no arguments and returns true if all of the promises have already | ||
* resolved; or false otherwise. If this `cancel` method is subsequently invoked, it will attempt to short-circuit the | ||
* `every` promise that is waiting for any remaining promises to complete by instead throwing a `CancelledError`, which | ||
* will contain a list of any resolved outcomes and a list of any unresolved promises. | ||
* | ||
* Note: The returned promise should NEVER reject (UNLESS it is cancelled via the `cancellable`), since it only resolves | ||
* with Success or Failure outcomes, which indicate whether their corresponding given promises resolved or failed. | ||
* | ||
* @param {Promise[]|*[]} promises - a list of promises from which to collect their outcomes | ||
* @param {Object|Cancellable|*} [cancellable] - an arbitrary object onto which a `cancel` method will be installed | ||
* @returns {Promise.<(Success|Failure)[]|CancelledError>} a promise that will resolve with a list of Success or Failure | ||
* outcomes for the given promises (unless cancelled); otherwise reject with a `CancelledError` (if cancelled) | ||
* @throws {Error} an error if the given `promises` is not an array | ||
*/ | ||
function isArrayOfPromises(result) { | ||
return Array.isArray(result) && result.every(r => isPromise(r)) | ||
} | ||
if (!Promise.isArrayOfPromises) { // polyfill-safe guard check | ||
Promise.isArrayOfPromises = isArrayOfPromises; | ||
} | ||
/** | ||
* @typedef {{result:*}|{error:Error}} ResultOrError - An object containing either a result or an error. | ||
*/ | ||
/** | ||
* Returns a promise that returns a list of either a result or an error for each of the given promises in the same | ||
* sequence as the given promises when every one of the given promises has resolved. | ||
* @param {Promise[]|Promise...} promises - a list of promises from which to collect their resolved results or their rejected errors | ||
* @returns {Promise.<ResultOrError[]>} promises - a promise of a list of either a result or an error for each of the | ||
* given promises in the same sequence as the given promises | ||
*/ | ||
function every(promises) { | ||
const ps = Array.isArray(promises) ? promises : isPromise(promises) ? arguments : []; | ||
const n = ps.length; | ||
function every(promises, cancellable) { | ||
if (!Array.isArray(promises)) { | ||
throw new Error('The `every` function only accepts an array of promises and/or non-promises'); | ||
} | ||
const n = promises.length; | ||
if (n <= 0) { | ||
return Promise.resolve([]); | ||
} | ||
const results = new Array(n); | ||
const last = n - 1; | ||
const outcomes = new Array(n); | ||
let completed = false; | ||
let cancelled = false; | ||
// Set up a cancel method on the given cancellable object (if any) | ||
if (cancellable && typeof cancellable === 'object') { | ||
cancellable.cancel = () => { | ||
cancelled = true; | ||
return completed; | ||
} | ||
} | ||
function next(i) { | ||
return ps[i].then( | ||
result => | ||
results[i] = {result: result}, | ||
err => | ||
results[i] = {error: err}) | ||
.then(() => | ||
// If remaining list (after i) contains at least one more promise, then continue; otherwise done | ||
i < n - 1 ? next(i + 1) : results | ||
); | ||
let p = promises[i]; | ||
if (i > 0) { | ||
// If we are at any element other than the first and it's NOT a promise then simply collect its value and recurse | ||
if (!isPromiseLike(p)) { | ||
outcomes[i] = new Success(p); | ||
if (i < last) { | ||
// Short-circuit if cancelled by throwing a cancelled error with the outcomes collected so far | ||
if (cancelled) throw new CancelledError(outcomes.slice(0, i + 1), promises.slice(i + 1)); | ||
return next(i + 1); | ||
} | ||
completed = true; | ||
return outcomes; | ||
} | ||
} else { | ||
// Ensure that we have a native promise at the first element to start the chain | ||
p = toPromise(p); | ||
} | ||
// The current element is now definitely a promise, so continue by calling its then method | ||
return p.then( | ||
value => { | ||
outcomes[i] = new Success(value); | ||
// If still not at the last element, then continue with a recursive call; otherwise return outcomes, since done | ||
if (i < last) { | ||
// Short-circuit if cancelled by throwing a cancelled error with the outcomes collected so far | ||
if (cancelled) throw new CancelledError(outcomes.slice(0, i + 1), promises.slice(i + 1)); | ||
return next(i + 1); | ||
} | ||
completed = true; | ||
return outcomes; | ||
}, | ||
err => { | ||
outcomes[i] = new Failure(err); | ||
// If still not at the last element, then continue with a recursive call; otherwise return results, since done | ||
if (i < last) { | ||
// Short-circuit if cancelled by throwing a cancelled error with the outcomes collected so far | ||
if (cancelled) throw new CancelledError(outcomes.slice(0, i + 1), promises.slice(i + 1)); | ||
return next(i + 1); | ||
} | ||
completed = true; | ||
return outcomes; | ||
} | ||
); | ||
} | ||
// Start the process at the first element | ||
return next(0); | ||
} | ||
if (!Promise.every) { // polyfill-safe guard check | ||
Promise.every = every; | ||
} | ||
/** | ||
* Maps the given single promise (or promise-like object or non-promise value) to a native Promise of a Success or | ||
* Failure resolution. This function is similar to the `every` function, except that it only handles one promise. | ||
* Note: The returned promise should NEVER reject, since it only resolves to either a Success with the resolved value or | ||
* a Failure with the rejected error. | ||
* @param {Promise.<*>|PromiseLike|*} promise - the promise to be mapped | ||
* @returns {Promise.<(Success|Failure)>} a native Promise of a Success or Failure | ||
*/ | ||
function one(promise) { | ||
return toPromise(promise).then( | ||
value => new Success(value), | ||
err => new Failure(err) | ||
); | ||
} |
145
README.md
@@ -1,5 +0,5 @@ | ||
# core-functions v2.0.14 | ||
# core-functions v3.0.0 | ||
Core functions, utilities and classes for working with Node/JavaScript primitives and built-in objects, including | ||
strings, booleans, Promises, base 64, Arrays, Objects, standard AppErrors, etc. | ||
strings, numbers, booleans, Dates, Promises, base 64, Arrays, Objects, standard AppErrors, sorting utilities, etc. | ||
@@ -11,7 +11,10 @@ Currently includes: | ||
- booleans.js - boolean utilities | ||
- dates.js - Date utilities | ||
- numbers.js - number utilities | ||
- objects.js - Object utilities | ||
- promises.js - native Promise utilities | ||
- sorting.js - sorting utilities | ||
- strings.js - string utilities | ||
- timers.js - Timer/timeout utilities | ||
- tries.js - Try, Success and Failure classes representing the outcome of a function execution | ||
@@ -49,11 +52,17 @@ This module is exported as a [Node.js](https://nodejs.org/) module. | ||
To use the Promise utilities (as static methods on the native `Promise` class) | ||
To use the Date utilities | ||
```js | ||
require('core-functions/promises'); | ||
const Dates = require('core-functions/dates'); | ||
``` | ||
To use the Promise utilities (as exported functions) | ||
To use the sorting utilities | ||
```js | ||
const promises = require('core-functions/promises'); | ||
const sorting = require('core-functions/sorting'); | ||
``` | ||
To use the Promise utilities | ||
```js | ||
const Promises = require('core-functions/promises'); | ||
``` | ||
To use the Object utilities | ||
@@ -74,2 +83,44 @@ ```js | ||
To use the `Try`, `Success` and `Failure` classes | ||
```js | ||
const tries = require('./tries'); | ||
const Try = tries.Try; | ||
const Success = tries.Success; | ||
const Failure = tries.Failure; | ||
// Simulate getting a Success outcome from successful execution of a function, which returns a value | ||
const outcome = Try.try(() => 'Abc'); | ||
// outcome = new Success('Abc') | ||
assert(outcome.isSuccess()); | ||
assert(outcome.value === 'Abc'); | ||
// using map function to convert a Success('Abc') outcome's value into a Success('AbcXyz') | ||
const outcome1 = outcome.map(v => v + 'Xyz'); | ||
assert(outcome1.isSuccess()); | ||
assert(outcome1.value === 'AbcXyz'); | ||
// Simulate getting a Failure outcome from unsuccessful execution of a function, which throws an error | ||
const testErr = new Error("Err"); // an arbitrary error for the example | ||
const outcome2 = Try.try(() => {throw testErr}); | ||
// outcome2 is equivalent to new Failed(new Error("Err")) | ||
assert(outcome2.isFailure()); | ||
assert(outcome2.error === testErr); | ||
// using recover function to convert a Failed outcome's error into a Success(123) | ||
const outcome3 = outcome2.recover(err => 123); | ||
assert(outcome3.isSuccess()); | ||
assert(outcome3.value === 123); | ||
// ... or using map function to handle both successes & failures cases at the same time (similar to Promise.then) | ||
const outcome4 = outcome.map( | ||
value => { | ||
return value * 42; | ||
}, | ||
err => { | ||
console.log(err.stack); | ||
return -1; | ||
} | ||
); | ||
``` | ||
To use the standard application errors | ||
@@ -109,2 +160,84 @@ ```js | ||
### 3.0.0 | ||
- Non-backward compatible changes & fixes to `promises.js` module: | ||
- Removed all poly-filling & injection of `promises` module functions onto native `Promise` class | ||
- Changed behaviour of the `try` function to NOT use `Promise.all` when the given function returns an array of promises, | ||
so that it instead preserves ALL of the executed function's returned promises, "wrapped" in a `Promise.resolve` | ||
- Changed behaviour of the `allOrOne` and `every` functions to both accept any mixture of promise and/or non-promise | ||
values, in order to bring their behaviour in line with that of the standard `Promise.all` method | ||
- Changed the `every` function to ONLY accept an array of `promises` (i.e. it no longer supports var args containing | ||
promises) and added a new `cancellable` parameter to enable the wait for every promise to complete to be short- | ||
circuited at the earliest possible opportunity and the `every` function will then instead return a rejected | ||
`CancelledError` from which the `resolvedOutcomes` and `unresolvedPromises` can be retrieved | ||
- Changed the `every` function's returned resolutions from literal objects containing `result` or `error` properties | ||
to use the new `tries` modules's `Success`, `Failure` and `Try` classes instead (NB: Success has a `value` property, | ||
and not a `result` property, so this change is not backward-compatible) | ||
- Fixed defect that was causing the `every` function to return an empty array when the first argument was not a promise | ||
- Renamed existing `isPromise` function to `isPromiseLike` & added new `isPromise` function that ONLY returns true for native promises | ||
- Removed the `isArrayOfPromises` function, which was no longer useful & would have had to change after `isPromise` changed | ||
- Added a new `one` function to convert a single promise into a native Promise that resolves to a `Success` or `Failure` outcome | ||
- Added a new `toPromise` function to attempt to convert a promise-like into a native promise | ||
- Used it in the new `one` function | ||
- Changed the `every` function to use it to ensure that the first promise in the chain becomes a native Promise | ||
- Added new `tries.js` module: | ||
- Added `Try` superclass and `Success` and `Failure` subclasses modelled after their same-named Scala counterparts | ||
- Added new `dates.js` module: | ||
- Added `simpleISODateTimeRegex` & `simpleISODateRegex` regular expressions | ||
- Added `extendedISODateTimeRegex` & `extendedISODateRegex` regular expressions | ||
- Added `isSimpleISODateTimeLike` & `isSimpleISODateLike` functions | ||
- Added `isSimpleISODateTime` & `isSimpleISODate` functions | ||
- Added `isExtendedISODateTimeLike` & `isExtendedISODateLike` functions | ||
- Added `isExtendedISODateTime` & `isExtendedISODate` functions | ||
- Added `toSimpleISODateTime` & `toSimpleISODate` functions | ||
- Added `toDateTime` & `toExtendedISODate` functions | ||
- Added `isValidDate` function | ||
- Added new `sorting.js` module: | ||
- Added `SortType` "enum" object to defined the types of sorting supported | ||
- Added `compareNumbers`, `compareStrings`, `compareBooleans`, `compareDates`, `compareIntegerLikes` & | ||
`compareUndefinedOrNull` compare functions to be used with Array `sort` method | ||
- Added `toSortable` function, which resolves the appropriate sort type and compare function to use for a given array | ||
of values intended to be sorted and also maps the values into an array of consistent, sortable types of values | ||
- Added `sortSortable` function (primarily for testing), which simply sorts a "Sortable" object's `sortableValues` | ||
using its `compare` function | ||
- Changes to `numbers.js` module: | ||
- Added `integerRegex`, `numberRegex`, `zeroRegex`, `leadingZeroesRegex` & `trailingZeroesRegex` regular expressions | ||
- Added `isInteger` & `isSafeInteger` functions | ||
- Added `isNumberLike`, `isIntegerLike` & `isZeroLike` functions | ||
- Added `toNumberLike`, `toDecimalLike`, `toDecimalLikeOrNaN`, `toIntegerLike`, `toIntegerLikeOrNaN` & | ||
`toNumberOrIntegerLike` functions | ||
- Added `removeLeadingZeroes`, `removeTrailingZeroes`, `zeroPadLeft` & `removeSignIfZero` functions | ||
- Added `nearlyEqual` function for testing numbers for approximate equality | ||
- Changes to `strings.js` module: | ||
- Added `stringifyKeyValuePairs` function | ||
- Added null-safe `toLowerCase` function | ||
- Changes to `stringify` function: | ||
- Added special cases to better support Dates, Promises, Maps & WeakMaps | ||
- Improved conversion of Errors - changed default behaviour to use a variation of Error toString() instead of always | ||
treating Errors as Objects (the latter behaviour is now available by passing opts.avoidErrorToString as true) | ||
- Replaced `useToStringForErrors`, `avoidToJSONMethods` & `quoteStrings` parameters with a single, optional `opts` | ||
parameter with optional `avoidErrorToString` (NB: renamed and changed default behaviour to use toString() for | ||
Errors), `avoidToJSONMethods` and `quoteStrings` properties. | ||
- Added support for also handling any legacy arguments passed instead of a new `opts` object, which means this | ||
API change is still backward-compatible | ||
- Added new `useJSONStringify`, `replacer` & `space` opts properties to enable `stringify`'s behaviour to be | ||
switched to simply use `JSON.stringify` instead via its new `opts` argument | ||
- Changes to `objects.js` module: | ||
- Added `toKeyValuePairs` function | ||
- Changes to `merge` function: | ||
- Replaced `replace` & `deep` parameters with a single, optional `opts` parameter with optional `replace` & `deep` | ||
properties | ||
- Added support for also handling any legacy arguments passed instead of a new `opts` object, which means this | ||
API change is still backward-compatible | ||
- Changes to `copy` function: | ||
- Replaced `deep` parameter with a single, optional `opts` parameter with an optional `deep` property | ||
- Added support for also handling any legacy `deep` boolean argument passed instead of a new `opts` object, which | ||
means this API change is still backward-compatible | ||
- Changes to `copyNamedProperties` function: | ||
- Replaced `compact`, `deep` & `omitPropertyIfUndefined` parameters with a single, optional `opts` parameter with | ||
optional `replace`, `deep` & `omitIfUndefined` properties | ||
- Added support for also handling any legacy arguments passed instead of a new `opts` object, which means this | ||
API change is still backward-compatible | ||
- Added new `type-defs` "module" to gather the various type definitions into one place | ||
- Removed `test/testing.js` | ||
### 2.0.14 | ||
@@ -111,0 +244,0 @@ - Added `copyNamedProperties` function to `objects.js` module |
179
strings.js
@@ -26,8 +26,23 @@ 'use strict'; | ||
stringify: stringify, | ||
nthIndexOf: nthIndexOf | ||
nthIndexOf: nthIndexOf, | ||
toLowerCase: toLowerCase, | ||
stringifyKeyValuePairs: stringifyKeyValuePairs | ||
}; | ||
const inspectOpts = {depth: null, breakLength: Infinity}; // unfortunately breakLength is only available in later Node versions than 4.3.2 | ||
const breakRegex = /\s*[\n\r]+\s*/g; | ||
const promiseInspectRegex = /^(Promise \{)([\s\n\r]+)(.*)([\s\n\r]+)(})$/; | ||
// Attempts to get Node's util.js inspect function (if available) | ||
const inspect = (() => { | ||
try { | ||
return require('util').inspect; | ||
} catch (_) { | ||
return undefined; | ||
} | ||
})(); | ||
/** | ||
* Returns true if the given value is a string; false otherwise. | ||
* @param {*} value the value to check | ||
* @param {*} value - the value to check | ||
* @return {boolean} true if its a string; false otherwise | ||
@@ -41,3 +56,3 @@ */ | ||
* Returns true if the given string is blank (i.e. undefined, null, empty or contains only whitespace); false otherwise. | ||
* @param {string|String} s the string to check | ||
* @param {string|String} s - the string to check | ||
* @return {boolean} true if blank; false otherwise | ||
@@ -52,3 +67,3 @@ */ | ||
* otherwise. | ||
* @param {string|String} s the string to check | ||
* @param {string|String} s - the string to check | ||
* @return {boolean} true if NOT blank; false otherwise | ||
@@ -62,3 +77,3 @@ */ | ||
* Trims the given value if it is a string; otherwise returns a non-string value as is. | ||
* @param {*} value the value to trim | ||
* @param {*} value - the value to trim | ||
* @returns {string|*} the trimmed string or the original non-string value | ||
@@ -73,3 +88,3 @@ */ | ||
* the non-undefined, non-null, non-string value as is. | ||
* @param {*} value the value to trim | ||
* @param {*} value - the value to trim | ||
* @returns {string|*} the trimmed string; an empty string; or the original non-undefined, non-null, non-string value | ||
@@ -84,9 +99,14 @@ */ | ||
* Returns the given value as a string with special case handling for undefined, null, strings, numbers, booleans, | ||
* Strings, Numbers, Booleans, Errors, Functions, Arrays, Objects and special numbers (i.e. Infinity, -Infinity and NaN) | ||
* and also handles circular dependencies. Similar to {@linkcode JSON#stringify}, but shows more about the given value | ||
* than JSON.stringify does, for example: | ||
* - JSON.stringify(undefined) returns undefined, but this returns 'undefined' | ||
* Strings, Numbers, Booleans, Dates, Promises, Errors, Functions, Arrays, Maps, WeakMaps, Objects and special numbers | ||
* (i.e. Infinity, -Infinity and NaN) and also handles circular dependencies. However, if opts.useJSONStringify is true, | ||
* then does NONE of this and instead just returns JSON.stringify(value, opts.replacer, opts.space) ignoring other opts. | ||
* | ||
* Similar to {@linkcode JSON#stringify}, but shows more about the given value than JSON.stringify does, for example: | ||
* - JSON.stringify(undefined) returns undefined literally, but this returns 'undefined' | ||
* - JSON.stringify({a:undefined}) returns {}, but this returns '{"a":undefined}' | ||
* - JSON.stringify(new Error("Boom")) returns '{}', but this returns either '{"message":"Boom","name":"Error"}' (if | ||
* useToStringForErrors is false) or '[Error: Boom]' (if useToStringForErrors is true) | ||
* - JSON.stringify(new Error("Boom")) returns '{}', but this returns EITHER: | ||
* - '[Error: Boom]' (or '[Error: Boom {<extras>}]') WHEN opts.avoidErrorToString is false (default); OR | ||
* - '{"name": "Error", "message": "Boom"}' (or '{"name": "Error", "message": "Boom", <extras>}') WHEN | ||
* opts.avoidErrorToString is true, | ||
* where <extras> above is any and all extra properties on the Error object OTHER than name, message and stack | ||
* - JSON.stringify(func) with function func() {} returns undefined, but this returns '[Function: func]' | ||
@@ -107,8 +127,23 @@ * - JSON.stringify({fn: func}) with function func() {} returns '{}', but this returns '{"fn": [Function: func]}' | ||
* @param {*} value the value to stringify | ||
* @param {boolean|undefined} [useToStringForErrors] - whether to stringify errors using toString or as normal objects (default) | ||
* @param {boolean|undefined} [avoidToJSONMethods] - whether to avoid using objects' toJSON methods or not (default) | ||
* @param {boolean|undefined} [quoteStrings] - whether to surround simple string values with double-quotes or not (default) | ||
* @param {StringifyOpts|undefined} [opts] - optional options to control how the value gets stringified | ||
* @returns {string} the value as a string | ||
*/ | ||
function stringify(value, useToStringForErrors, avoidToJSONMethods, quoteStrings) { | ||
function stringify(value, opts) { | ||
// Legacy parameters were (value, useToStringForErrors, avoidToJSONMethods, quoteStrings), so for backward compatibility, | ||
// convert any boolean opts or (no opts, but boolean 4th or 5th arg) into an appropriate object opts | ||
if (typeof opts === "boolean" || (!opts && (typeof arguments[3] === "boolean" || typeof arguments[4] === "boolean"))) { | ||
opts = {avoidErrorToString: !opts, avoidToJSONMethods: !!arguments[3], quoteStrings: !!arguments[4]}; | ||
} | ||
// Resolve any options requested | ||
const useJSONStringify = !!opts && opts.useJSONStringify == true; | ||
if (useJSONStringify) { | ||
const replacer = opts && opts.replacer ? opts.replacer : undefined; | ||
const space = opts && opts.space ? opts.space : undefined; | ||
return JSON.stringify(value, replacer, space); | ||
} | ||
const avoidErrorToString = !!opts && opts.avoidErrorToString == true; | ||
const avoidToJSONMethods = !!opts && opts.avoidToJSONMethods == true; | ||
const quoteStrings = !!opts && opts.quoteStrings == true; | ||
const history = new WeakMap(); | ||
@@ -134,6 +169,2 @@ | ||
// Special case for Errors - use toString() if directed, since stringify on most errors, just returns "{}" | ||
const valueIsError = value instanceof Error; | ||
if (valueIsError && useToStringForErrors) return `[${value}]`; | ||
// Special case for Functions - show them as [Function: {function name}] | ||
@@ -143,6 +174,2 @@ if (typeOfValue === 'function') return isNotBlank(value.name) ? `[Function: ${value.name}]` : '[Function: anonymous]'; | ||
if (typeOfValue === 'object') { | ||
// Special case for objects that have toJSON methods | ||
if (!avoidToJSONMethods && typeof value.toJSON === 'function') { | ||
return JSON.stringify(value.toJSON()); | ||
} | ||
// Check if already seen this same object before | ||
@@ -161,2 +188,39 @@ if (history.has(value)) { | ||
// Special case for console, which is otherwise very verbose | ||
if (value instanceof console.Console) { | ||
return value === console ? '[Console]' : '[Console {}]'; | ||
} | ||
// Special case for Dates - show them as ISO strings rather than default toString() | ||
if (value instanceof Date) { | ||
const date = value.toJSON(); | ||
return date ? date : '[Invalid Date]'; | ||
} | ||
// Special case for Promises | ||
if (value instanceof Promise) { | ||
// Attempts to use Node's util.js inspect function (if available), which returns more-useful strings like: | ||
// 'Promise { <pending> }'; 'Promise { "Abc" }' | ||
return inspect ? `[${cleanInspectedPromise(inspect(value, inspectOpts))}]` : '[Promise]'; | ||
} | ||
// Special case for WeakMaps, which cannot be enumerated | ||
if (value instanceof WeakMap) { | ||
return `[WeakMap]` | ||
} | ||
// Special case for Maps, which currently do not have a useful toString() and are also not handled well by JSON.stringify | ||
if (value instanceof Map) { | ||
let result = '[Map {'; | ||
let first = true; | ||
let i = 0; | ||
for (let kv of value.entries()) { | ||
const k = kv[0], v = kv[1]; | ||
result += `${!first ? ', ' : ''}${stringifyWithHistory(k, `${name}[${i}].KEY`, true)} => ${stringifyWithHistory(v, `${name}[${i}].VAL`, true)}`; | ||
first = false; | ||
++i; | ||
} | ||
result += '}]'; | ||
return result; | ||
} | ||
// Special case for Array objects, stringify each of its elements | ||
@@ -167,15 +231,34 @@ if (Array.isArray(value)) { | ||
// Special case for objects that have toJSON methods | ||
if (!avoidToJSONMethods && typeof value.toJSON === 'function') { | ||
return JSON.stringify(value.toJSON()); | ||
} | ||
// Stringify the object | ||
let names = Object.getOwnPropertyNames(value); | ||
let prefix = '{'; | ||
let suffix = '}'; | ||
if (valueIsError) { | ||
// Special case for Error objects - include message and name (if any), but exclude stack, which are all normally hidden with JSON.stringify | ||
// First exclude name, message and stack | ||
// Special cases for Errors | ||
if (value instanceof Error) { | ||
// First exclude name, message and stack from the list of names | ||
names = names.filter(n => n !== 'stack' && n !== 'name' && n !== 'message'); | ||
// Second re-add name and message to the front of the list | ||
if (value.message) names.unshift('message'); | ||
if (value.name) names.unshift('name'); | ||
if (avoidErrorToString) { | ||
// Special case for Errors when opts.avoidErrorToString is true - treat Error as if it was just an object, but | ||
// include name and message (if any) at the front of the list of names (but still exclude stack) | ||
if (value.message) names.unshift('message'); | ||
if (value.name) names.unshift('name'); | ||
} else { | ||
// Special case for Errors when opts.avoidErrorToString is true- use the Error's toString(), but also include | ||
// any extra properties OTHER than message, name and stack in the output | ||
if (names.length <= 0) { | ||
return `[${value}]`; // If no extra properties then just use Error's toString() | ||
} | ||
// Change the prefix to include the Error's toString() info and continue as normal | ||
prefix = `[${value} {`; | ||
suffix = '}]'; | ||
} | ||
} | ||
let result = '{'; | ||
let result = prefix; | ||
for (let i = 0; i < names.length; ++i) { | ||
@@ -189,3 +272,3 @@ const propertyName = names[i]; | ||
} | ||
result += '}'; | ||
result += suffix; | ||
return result; | ||
@@ -235,2 +318,34 @@ } | ||
return index; | ||
} | ||
/** | ||
* Converts the given value to lower-case if it is a string; otherwise returns a non-string value as is. | ||
* @param {*} value - the value to convert to lower-case | ||
* @returns {string|*} the lower-case string or the original non-string value | ||
*/ | ||
function toLowerCase(value) { | ||
return typeof value === 'string' || value instanceof String ? value.toLowerCase() : value; | ||
} | ||
/** | ||
* Creates a string version of the given key value pairs array, with its keys and values separated by the given | ||
* keyValueSeparator and its pairs separated by the given pairSeparator. | ||
* @param {KeyValuePair[]} keyValuePairs - an array of key value pairs | ||
* @param {StringifyKeyValuePairsOpts|undefined} [opts] - optional options to control how the array of key value pairs get stringified | ||
* @returns {string} a string version of the given key value pairs array | ||
*/ | ||
function stringifyKeyValuePairs(keyValuePairs, opts) { | ||
const keyValueSeparator = opts && typeof opts.keyValueSeparator === 'string' ? opts.keyValueSeparator : ':'; | ||
const pairSeparator = opts && typeof opts.pairSeparator === 'string' ? opts.pairSeparator : ','; | ||
return keyValuePairs && keyValuePairs.length > 0 ? | ||
keyValuePairs.map(kv => `${kv[0]}${keyValueSeparator}${stringify(kv[1], opts)}`).join(pairSeparator) : ''; | ||
} | ||
/** | ||
* Workaround to avoid default behaviour that inserts new line(s) when length too long (>60?) by removing any carriage | ||
* returns and line feeds. | ||
* @param {string} inspectedPromise - util.inspect result for a Promise instance | ||
*/ | ||
function cleanInspectedPromise(inspectedPromise) { | ||
return inspectedPromise.replace(breakRegex, ' ').replace(promiseInspectRegex, '$1 $3 $5'); //.replace(/"/g, '\\"').replace(/\\'/g, '"') | ||
} |
@@ -42,19 +42,22 @@ 'use strict'; | ||
const testing = require('./testing'); | ||
// const okNotOk = testing.okNotOk; | ||
// const checkOkNotOk = testing.checkOkNotOk; | ||
// const checkMethodOkNotOk = testing.checkMethodOkNotOk; | ||
const equal = testing.equal; | ||
// const checkEqual = testing.checkEqual; | ||
// const checkMethodEqual = testing.checkMethodEqual; | ||
const immutable = testing.immutable; | ||
function immutable(t, obj, propertyName, prefix) { | ||
const now = new Date().toISOString(); | ||
const opts = {quoteStrings: true}; | ||
try { | ||
obj[propertyName] = now; | ||
t.fail(`${prefix ? prefix : ''}${stringify(obj, opts)} ${propertyName} is supposed to be immutable`); | ||
} catch (err) { | ||
// Expect an error on attempted mutation of immutable property | ||
t.pass(`${prefix ? prefix : ''}${stringify(obj, opts)} ${propertyName} is immutable`); | ||
//console.log(`Expected error ${err}`); | ||
} | ||
} | ||
test('AppError must be initialized ', t => { | ||
function check(appError, message, code, httpStatus, cause, causeStatus) { | ||
equal(t, appError.message, message, `${appError} message`); | ||
equal(t, appError.code, code, `${appError} code`); | ||
equal(t, appError.httpStatus, httpStatus, `${appError} httpStatus`); | ||
equal(t, appError.cause, cause, `${appError} cause`); | ||
equal(t, appError.causeStatus, causeStatus, `${appError} causeStatus`); | ||
t.equal(appError.message, message, `${appError} message must be ${message}`); | ||
t.equal(appError.code, code, `${appError} code must be ${appError}`); | ||
t.equal(appError.httpStatus, httpStatus, `${appError} httpStatus must be ${httpStatus}`); | ||
t.equal(appError.cause, cause, `${appError} cause must be ${cause}`); | ||
t.equal(appError.causeStatus, causeStatus, `${appError} causeStatus must be ${causeStatus}`); | ||
} | ||
@@ -103,7 +106,7 @@ | ||
function check(appError, message, code, httpStatus, cause, causeStatus) { | ||
equal(t, appError.message, message, `${appError} message`); | ||
equal(t, appError.code, code, `${appError} code`); | ||
equal(t, appError.httpStatus, httpStatus, `${appError} httpStatus`); | ||
equal(t, appError.cause, cause, `${appError} cause`); | ||
equal(t, appError.causeStatus, causeStatus, `${appError} causeStatus`); | ||
t.equal(appError.message, message, `${appError} message must be ${message}`); | ||
t.equal(appError.code, code, `${appError} code must be ${code}`); | ||
t.equal(appError.httpStatus, httpStatus, `${appError} httpStatus must be ${httpStatus}`); | ||
t.equal(appError.cause, cause, `${appError} cause must be ${cause}`); | ||
t.equal(appError.causeStatus, causeStatus, `${appError} causeStatus must be ${causeStatus}`); | ||
} | ||
@@ -183,8 +186,8 @@ | ||
t.ok(appError instanceof type, `toAppError(${stringify(error)}) -> (${appError}) must be instanceof ${type.name}`); | ||
equal(t, appError.name, type.name, `toAppError(${stringify(error)}) -> (${appError}) name`); | ||
t.equal(appError.name, type.name, `toAppError(${stringify(error)}) -> (${appError}) name must be ${type.name}`); | ||
if (isBlank(message)) { | ||
equal(t, appError.message, trimOrEmpty(error.message), `toAppError(${stringify(error)}, ${message}, ${code}) -> (${appError}) message`); | ||
t.equal(appError.message, trimOrEmpty(error.message), `toAppError(${stringify(error)}, ${message}, ${code}) -> (${appError}) message must be ${trimOrEmpty(error.message)}`); | ||
} else if (isNotBlank(message)) { | ||
equal(t, appError.message, stringify(trimOrEmpty(message)), `toAppError(${stringify(error)}, ${message}, ${code}) -> (${appError}) message`); | ||
t.equal(appError.message, stringify(trimOrEmpty(message)), `toAppError(${stringify(error)}, ${message}, ${code}) -> (${appError}) message must be ${stringify(trimOrEmpty(message))}`); | ||
} | ||
@@ -202,5 +205,5 @@ | ||
error.name ? error.name : appError.name; | ||
equal(t, appError.code, expectedCode, `toAppError(${stringify(error)}, ${message}, ${code}) -> (${appError}) code`); | ||
t.equal(appError.code, expectedCode, `toAppError(${stringify(error)}, ${message}, ${code}) -> (${appError}) code must be ${expectedCode}`); | ||
} else if (isNotBlank(code)) { | ||
equal(t, appError.code, stringify(trimOrEmpty(code)), `toAppError(${stringify(error)}, ${message}, ${code}) -> (${appError}) code`); | ||
t.equal(appError.code, stringify(trimOrEmpty(code)), `toAppError(${stringify(error)}, ${message}, ${code}) -> (${appError}) code must be ${stringify(trimOrEmpty(code))}`); | ||
} | ||
@@ -297,8 +300,8 @@ | ||
t.ok(appError instanceof type, `toAppErrorForApiGateway(${stringify(error)}) -> (${appError}) must be instanceof ${type.name}`); | ||
equal(t, appError.name, type.name, `toAppErrorForApiGateway(${stringify(error)}) -> (${appError}) name`); | ||
t.equal(appError.name, type.name, `toAppErrorForApiGateway(${stringify(error)}) -> (${appError}) name must be ${type.name}`); | ||
if (isBlank(message)) { | ||
equal(t, appError.message, trimOrEmpty(error.message), `toAppErrorForApiGateway(${stringify(error)}, ${message}, ${code}) -> (${appError}) message`); | ||
t.equal(appError.message, trimOrEmpty(error.message), `toAppErrorForApiGateway(${stringify(error)}, ${message}, ${code}) -> (${appError}) message must be ${trimOrEmpty(error.message)}`); | ||
} else if (isNotBlank(message)) { | ||
equal(t, appError.message, stringify(trimOrEmpty(message)), `toAppErrorForApiGateway(${stringify(error)}, ${message}, ${code}) -> (${appError}) message`); | ||
t.equal(appError.message, stringify(trimOrEmpty(message)), `toAppErrorForApiGateway(${stringify(error)}, ${message}, ${code}) -> (${appError}) message must be ${stringify(trimOrEmpty(message))}`); | ||
} | ||
@@ -316,5 +319,5 @@ | ||
appError0.name; | ||
equal(t, appError.code, trimOrEmpty(expectedCode), `toAppErrorForApiGateway(${stringify(error)}, ${message}, ${code}) -> (${appError}) code`); | ||
t.equal(appError.code, trimOrEmpty(expectedCode), `toAppErrorForApiGateway(${stringify(error)}, ${message}, ${code}) -> (${appError}) code must be ${trimOrEmpty(expectedCode)}`); | ||
} else if (isNotBlank(code)) { | ||
equal(t, appError.code, stringify(trimOrEmpty(code)), `toAppErrorForApiGateway(${stringify(error)}, ${message}, ${code}) -> (${appError}) code`); | ||
t.equal(appError.code, stringify(trimOrEmpty(code)), `toAppErrorForApiGateway(${stringify(error)}, ${message}, ${code}) -> (${appError}) code must be ${stringify(trimOrEmpty(code))}`); | ||
} | ||
@@ -321,0 +324,0 @@ |
@@ -18,15 +18,10 @@ 'use strict'; | ||
const testing = require('./testing'); | ||
// const okNotOk = testing.okNotOk; | ||
const checkOkNotOk = testing.checkOkNotOk; | ||
// const checkMethodOkNotOk = testing.checkMethodOkNotOk; | ||
// const equal = testing.equal; | ||
const checkEqual = testing.checkEqual; | ||
// const checkMethodEqual = testing.checkMethodEqual; | ||
function wrap(value) { | ||
switch (typeof value) { | ||
case 'string': return new String(value); | ||
case 'number': return new Number(value); | ||
case 'boolean': return new Boolean(value); | ||
case 'string': //noinspection JSPrimitiveTypeWrapperUsage | ||
return new String(value); | ||
case 'number': //noinspection JSPrimitiveTypeWrapperUsage | ||
return new Number(value); | ||
case 'boolean': //noinspection JSPrimitiveTypeWrapperUsage | ||
return new Boolean(value); | ||
default: return value; | ||
@@ -38,3 +33,3 @@ } | ||
function check(array, expected) { | ||
checkOkNotOk(t, Arrays.isDistinct, [array], expected, 'must be distinct', 'must NOT be distinct'); | ||
t.equal(Arrays.isDistinct(array), expected, `Arrays.isDistinct(${stringify(array)}) must ${expected ? '' : 'NOT '}be distinct`); | ||
} | ||
@@ -73,3 +68,3 @@ // empty array | ||
function check(array, expected) { | ||
checkEqual(t, Arrays.distinct, [array], expected); | ||
t.deepEqual(Arrays.distinct(array), expected, `Arrays.distinct(array) must be ${stringify(expected)}`); | ||
} | ||
@@ -111,4 +106,3 @@ | ||
function check(array, type, expected) { | ||
checkOkNotOk(t, Arrays.isArrayOfType, [array, type, strict], expected, `must be an array of ${stringify(type)}`, | ||
`must NOT be an array of ${stringify(type)}`); | ||
t.equal(Arrays.isArrayOfType(array, type, strict), expected, `Arrays.isDistinct(${stringify(array)}, ${type}, ${strict}) must ${expected ? '' : 'NOT '}be an array of ${stringify(type)}`); | ||
} | ||
@@ -115,0 +109,0 @@ // empty array |
@@ -12,13 +12,6 @@ 'use strict'; | ||
const Strings = require('../strings'); | ||
// const Strings = require('../strings'); | ||
const testing = require('./testing'); | ||
// const okNotOk = testing.okNotOk; | ||
const checkOkNotOk = testing.checkOkNotOk; | ||
// const checkMethodOkNotOk = testing.checkMethodOkNotOk; | ||
// const equal = testing.equal; | ||
const checkEqual = testing.checkEqual; | ||
// const checkMethodEqual = testing.checkMethodEqual; | ||
function wrap(value, wrapInBoolean) { | ||
//noinspection JSPrimitiveTypeWrapperUsage | ||
return wrapInBoolean && !(value instanceof Boolean) ? new Boolean(value) : value; | ||
@@ -34,4 +27,3 @@ } | ||
function check(value, expected) { | ||
return checkOkNotOk(t, Booleans.isBoolean, [wrap(value, wrapInBoolean)], expected, ' is a boolean', | ||
' is NOT a boolean', toPrefix(value, wrapInBoolean)); | ||
return t.equal(Booleans.isBoolean(wrap(value, wrapInBoolean)), expected, `Booleans.isBoolean(${toPrefix(value, wrapInBoolean)}) is ${expected ? '' : 'NOT '}a boolean`); // : | ||
} | ||
@@ -101,4 +93,3 @@ // undefined | ||
function check(value, expected) { | ||
return checkOkNotOk(t, Booleans.isTrueOrFalse, [wrap(value, wrapInBoolean)], expected, ' is true or false', | ||
' is NOT true or false', toPrefix(value, wrapInBoolean)); | ||
return t.equal(Booleans.isTrueOrFalse(wrap(value, wrapInBoolean)), expected, `Booleans.isTrueOrFalse(${toPrefix(value, wrapInBoolean)}) is ${expected ? '' : 'NOT '}true or false`); // : | ||
} | ||
@@ -105,0 +96,0 @@ // undefined |
@@ -12,2 +12,3 @@ 'use strict'; | ||
const valueOf = Objects.valueOf; | ||
const toKeyValuePairs = Objects.toKeyValuePairs; | ||
@@ -17,13 +18,5 @@ const strings = require('../strings'); | ||
const testing = require('./testing'); | ||
// const okNotOk = testing.okNotOk; | ||
// const checkOkNotOk = testing.checkOkNotOk; | ||
// const checkMethodOkNotOk = testing.checkMethodOkNotOk; | ||
// const equal = testing.equal; | ||
const checkEqual = testing.checkEqual; | ||
// const checkMethodEqual = testing.checkMethodEqual; | ||
test('valueOf', t => { | ||
function check(value, expected) { | ||
return checkEqual(t, Objects.valueOf, [value], expected, false); | ||
return t.deepEqual(Objects.valueOf(value), expected, `Objects.valueOf(${stringify(value)}) must be ${stringify(expected)}`); | ||
} | ||
@@ -81,3 +74,3 @@ | ||
// shallow merge with replace (all same properties) | ||
const merge1 = Objects.merge(from, to1, true); | ||
const merge1 = Objects.merge(from, to1, {replace: true}); | ||
t.notDeepEqual(merge1, {a: 2, b: '3', c: {d: 4, e: '5', f: 6}}, 'shallow merge with replace must not be original to'); | ||
@@ -92,3 +85,3 @@ t.deepEqual(merge1, from, 'shallow merge with replace must have all of from'); | ||
// shallow merge with replace (with to properties not in from) | ||
const merge2 = Objects.merge(from, to2, true); | ||
const merge2 = Objects.merge(from, to2, {replace: true}); | ||
t.notDeepEqual(merge2, to2Orig, 'shallow merge with replace must not be original to2'); | ||
@@ -105,3 +98,3 @@ t.notDeepEqual(merge2, from, 'shallow merge with replace must not be from'); | ||
const to3 = {a: 2, b: '3', c: {d: 4, e: '5', f: 6}, z: 'ZZZ'}; | ||
t.deepEqual(Objects.merge(from, to3, true, true), { | ||
t.deepEqual(Objects.merge(from, to3, {replace: true, deep: true}), { | ||
a: 1, | ||
@@ -116,3 +109,3 @@ b: '2', | ||
const to4Orig = {a: 2, b: '2', c: {d: 3, e: '5', f: 6, y: 'Y'}, x: 'X', z: 'ZZZ'}; | ||
t.deepEqual(Objects.merge(from, to4, false, true), to4Orig, 'deep merge without replace must have all of to4 and only extras of from'); | ||
t.deepEqual(Objects.merge(from, to4, {replace: false, deep: true}), to4Orig, 'deep merge without replace must have all of to4 and only extras of from'); | ||
@@ -135,3 +128,3 @@ // check that functions get merged in too | ||
const expected5 = {a: a1, b: b, c: c, z: 'Z1'}; | ||
t.deepEqual(Objects.merge(from5, to5, false, false), expected5, 'deep merge without replace must have all functions of to5 and only extra functions of from5'); | ||
t.deepEqual(Objects.merge(from5, to5, {replace: false, deep: false}), expected5, 'deep merge without replace must have all functions of to5 and only extra functions of from5'); | ||
t.equal(to5.a, a1, 'to5.a must be function a1'); | ||
@@ -147,3 +140,3 @@ t.equal(to5.b, b, 'to5.b must be function b'); | ||
const expected6 = {a: a2, b: b, c: c, x: x, y: 'y1', z: 'Z2'}; | ||
t.deepEqual(Objects.merge(from5, to6, true, false), expected6, 'deep merge with replace must have all functions of from5 and only extra functions of to6'); | ||
t.deepEqual(Objects.merge(from5, to6, {replace: true, deep: false}), expected6, 'deep merge with replace must have all functions of from5 and only extra functions of to6'); | ||
t.equal(to6.a, a2, 'to6.a must be function a2'); | ||
@@ -156,3 +149,3 @@ t.equal(to6.x, x, 'to6.x must be function x'); | ||
const expected7 = {a: a2, b: b, c: c, x: x, y: 'y1', z: 'Z2'}; | ||
t.deepEqual(Objects.merge(from7, to7, true, true), expected7, 'deep merge with replace must have all functions of from7 and only extra functions of to7'); | ||
t.deepEqual(Objects.merge(from7, to7, {replace: true, deep: true}), expected7, 'deep merge with replace must have all functions of from7 and only extra functions of to7'); | ||
@@ -165,3 +158,3 @@ function d() { | ||
const expected8 = {o: {a: a1, b: b, x: x, y: 'y1'}, c: c, d: d, z: 'Z1'}; | ||
t.deepEqual(Objects.merge(from8, to8, false, true), expected8, 'deep merge without replace must have all functions of to8 and only extra functions of from8'); | ||
t.deepEqual(Objects.merge(from8, to8, {replace: false, deep: true}), expected8, 'deep merge without replace must have all functions of to8 and only extra functions of from8'); | ||
@@ -171,3 +164,3 @@ const from9 = {o: {a: a2, b: b}, c: c, z: 'Z2'}; | ||
const expected9 = {o: {a: a2, b: b, x: x, y: 'y1'}, c: c, d: d, z: 'Z2'}; | ||
t.deepEqual(Objects.merge(from9, to9, true, true), expected9, 'deep merge with replace must have all functions of from9 and only extra functions of to9'); | ||
t.deepEqual(Objects.merge(from9, to9, {replace: true, deep: true}), expected9, 'deep merge with replace must have all functions of from9 and only extra functions of to9'); | ||
@@ -177,3 +170,3 @@ const from10 = {o: {a: a2, b: b}, c: c, z: 'Z2'}; | ||
const expected10 = {o: {a: a2, b: b}, c: c, d: d, z: 'Z2'}; | ||
t.deepEqual(Objects.merge(from10, to10, true, false), expected10, 'shallow merge with replace must have all of from10 and only extra top-level properties of to10'); | ||
t.deepEqual(Objects.merge(from10, to10, {replace: true, deep: false}), expected10, 'shallow merge with replace must have all of from10 and only extra top-level properties of to10'); | ||
@@ -183,3 +176,3 @@ const from11 = {o: {a: a2, b: b}, c: c, z: 'Z2'}; | ||
const expected11 = {o: {a: a1, x: x, y: 'y1'}, c: c, d: d, z: 'Z1'}; | ||
t.deepEqual(Objects.merge(from11, to11, false, false), expected11, 'shallow merge with replace must have all of to11 and only extra top-level properties of from11'); | ||
t.deepEqual(Objects.merge(from11, to11, {replace: false, deep: false}), expected11, 'shallow merge with replace must have all of to11 and only extra top-level properties of from11'); | ||
@@ -195,3 +188,3 @@ // Create infinite loops (non-DAGs) | ||
Objects.merge(o3, c3, false, true); | ||
Objects.merge(o3, c3, {replace: false, deep: true}); | ||
//EEK t.deepEqual fails with "Maximum call stack size exceeded" | ||
@@ -213,33 +206,33 @@ //t.deepEqual(c3, o3, 'deep copy must be deep equal to o3'); | ||
// Non-objects | ||
t.deepEqual(Objects.copy(undefined, false), undefined, 'shallow copy of undefined is undefined'); | ||
t.deepEqual(Objects.copy(undefined, true), undefined, 'deep copy of undefined is undefined'); | ||
t.deepEqual(Objects.copy(undefined, {deep: false}), undefined, 'shallow copy of undefined is undefined'); | ||
t.deepEqual(Objects.copy(undefined, {deep: true}), undefined, 'deep copy of undefined is undefined'); | ||
t.deepEqual(Objects.copy(null, false), null, 'shallow copy of null is null'); | ||
t.deepEqual(Objects.copy(null, true), null, 'deep copy of null is null'); | ||
t.deepEqual(Objects.copy(null, {deep: false}), null, 'shallow copy of null is null'); | ||
t.deepEqual(Objects.copy(null, {deep: true}), null, 'deep copy of null is null'); | ||
t.deepEqual(Objects.copy('', false), '', `shallow copy of '' is ''`); | ||
t.deepEqual(Objects.copy('', true), '', `deep copy of '' is ''`); | ||
t.deepEqual(Objects.copy('', {deep: false}), '', `shallow copy of '' is ''`); | ||
t.deepEqual(Objects.copy('', {deep: true}), '', `deep copy of '' is ''`); | ||
t.deepEqual(Objects.copy('abc', false), 'abc', `shallow copy of 'abc' is 'abc'`); | ||
t.deepEqual(Objects.copy('abc', true), 'abc', `deep copy of 'abc' is 'abc'`); | ||
t.deepEqual(Objects.copy('abc', {deep: false}), 'abc', `shallow copy of 'abc' is 'abc'`); | ||
t.deepEqual(Objects.copy('abc', {deep: true}), 'abc', `deep copy of 'abc' is 'abc'`); | ||
t.deepEqual(Objects.copy(123, false), 123, 'shallow copy of 123 is 123'); | ||
t.deepEqual(Objects.copy(123, true), 123, 'deep copy of 123 is 123'); | ||
t.deepEqual(Objects.copy(123, {deep: false}), 123, 'shallow copy of 123 is 123'); | ||
t.deepEqual(Objects.copy(123, {deep: true}), 123, 'deep copy of 123 is 123'); | ||
t.deepEqual(Objects.copy(123.456, false), 123.456, 'shallow copy of 123.456 is 123.456'); | ||
t.deepEqual(Objects.copy(123.456, true), 123.456, 'deep copy of 123.456 is 123.456'); | ||
t.deepEqual(Objects.copy(123.456, {deep: false}), 123.456, 'shallow copy of 123.456 is 123.456'); | ||
t.deepEqual(Objects.copy(123.456, {deep: true}), 123.456, 'deep copy of 123.456 is 123.456'); | ||
t.deepEqual(Objects.copy(true, false), true, 'shallow copy of true is true'); | ||
t.deepEqual(Objects.copy(true, true), true, 'deep copy of true is true'); | ||
t.deepEqual(Objects.copy(true, {deep: false}), true, 'shallow copy of true is true'); | ||
t.deepEqual(Objects.copy(true, {deep: true}), true, 'deep copy of true is true'); | ||
t.deepEqual(Objects.copy(false, false), false, 'shallow copy of false is false'); | ||
t.deepEqual(Objects.copy(false, false), false, 'deep copy of false is false'); | ||
t.deepEqual(Objects.copy(false, {deep: false}), false, 'shallow copy of false is false'); | ||
t.deepEqual(Objects.copy(false, {deep: false}), false, 'deep copy of false is false'); | ||
// Empty object | ||
t.deepEqual(Objects.copy({}, false), {}, 'shallow copy of {} is {}'); | ||
t.deepEqual(Objects.copy({}, true), {}, 'deep copy of {} is {}'); | ||
t.deepEqual(Objects.copy({}, {deep: false}), {}, 'shallow copy of {} is {}'); | ||
t.deepEqual(Objects.copy({}, {deep: true}), {}, 'deep copy of {} is {}'); | ||
// Empty array | ||
t.deepEqual(Objects.copy([], false), [], 'shallow copy of [] is []'); | ||
t.deepEqual(Objects.copy([], true), [], 'deep copy of [] is []'); | ||
t.deepEqual(Objects.copy([], {deep: false}), [], 'shallow copy of [] is []'); | ||
t.deepEqual(Objects.copy([], {deep: true}), [], 'deep copy of [] is []'); | ||
@@ -249,2 +242,39 @@ t.end(); | ||
test('copy with non-objects & empty object & empty array', t => { | ||
// Non-objects | ||
t.deepEqual(Objects.copy(undefined, {deep: false}), undefined, 'shallow copy of undefined is undefined'); | ||
t.deepEqual(Objects.copy(undefined, {deep: true}), undefined, 'deep copy of undefined is undefined'); | ||
t.deepEqual(Objects.copy(null, {deep: false}), null, 'shallow copy of null is null'); | ||
t.deepEqual(Objects.copy(null, {deep: true}), null, 'deep copy of null is null'); | ||
t.deepEqual(Objects.copy('', {deep: false}), '', `shallow copy of '' is ''`); | ||
t.deepEqual(Objects.copy('', {deep: true}), '', `deep copy of '' is ''`); | ||
t.deepEqual(Objects.copy('abc', {deep: false}), 'abc', `shallow copy of 'abc' is 'abc'`); | ||
t.deepEqual(Objects.copy('abc', {deep: true}), 'abc', `deep copy of 'abc' is 'abc'`); | ||
t.deepEqual(Objects.copy(123, {deep: false}), 123, 'shallow copy of 123 is 123'); | ||
t.deepEqual(Objects.copy(123, {deep: true}), 123, 'deep copy of 123 is 123'); | ||
t.deepEqual(Objects.copy(123.456, {deep: false}), 123.456, 'shallow copy of 123.456 is 123.456'); | ||
t.deepEqual(Objects.copy(123.456, {deep: true}), 123.456, 'deep copy of 123.456 is 123.456'); | ||
t.deepEqual(Objects.copy(true, {deep: false}), true, 'shallow copy of true is true'); | ||
t.deepEqual(Objects.copy(true, {deep: true}), true, 'deep copy of true is true'); | ||
t.deepEqual(Objects.copy(false, {deep: false}), false, 'shallow copy of false is false'); | ||
t.deepEqual(Objects.copy(false, {deep: false}), false, 'deep copy of false is false'); | ||
// Empty object | ||
t.deepEqual(Objects.copy({}, {deep: false}), {}, 'shallow copy of {} is {}'); | ||
t.deepEqual(Objects.copy({}, {deep: true}), {}, 'deep copy of {} is {}'); | ||
// Empty array | ||
t.deepEqual(Objects.copy([], {deep: false}), [], 'shallow copy of [] is []'); | ||
t.deepEqual(Objects.copy([], {deep: true}), [], 'deep copy of [] is []'); | ||
t.end(); | ||
}); | ||
test('copy', t => { | ||
@@ -263,3 +293,3 @@ function a() { | ||
const o1 = {o: {a: a, x: 'X', p: {b: b, y: 'Y'}}, c: c, z: 'Z'}; | ||
const c1 = Objects.copy(o1, false); | ||
const c1 = Objects.copy(o1, {deep: false}); | ||
t.deepEqual(c1, o1, 'shallow copy circular - c1 must be deep equal to o1'); | ||
@@ -271,3 +301,3 @@ t.notEqual(c1, o1, 'shallow copy circular - c1 must not be o1'); | ||
const o2 = {o: {a: a, x: 'X', p: {b: b, y: 'Y'}}, c: c, z: 'Z'}; | ||
const c2 = Objects.copy(o2, true); | ||
const c2 = Objects.copy(o2, {deep: true}); | ||
t.deepEqual(c2, o2, 'deep copy circular - c2 must be deep equal to o2'); | ||
@@ -282,3 +312,3 @@ t.notEqual(c2, o2, 'deep copy circular - c2 must not be o2'); | ||
o3.o.p.o3Again = o3; | ||
const c3 = Objects.copy(o3, true); | ||
const c3 = Objects.copy(o3, {deep: true}); | ||
//EEK t.deepEqual fails with "Maximum call stack size exceeded" | ||
@@ -305,3 +335,3 @@ //t.deepEqual(c3, o3, 'deep copy must be deep equal to o3'); | ||
const a0 = []; | ||
const c0 = Objects.copy(a0, false); | ||
const c0 = Objects.copy(a0, {deep: false}); | ||
t.ok(Array.isArray(c0), `shallow copy ${stringify(c0)} must be an array`); | ||
@@ -313,3 +343,3 @@ t.notEqual(c0, a0, `shallow copy ${stringify(c0)} must not be same instance`); | ||
const a1 = []; | ||
const c1 = Objects.copy(a1, true); | ||
const c1 = Objects.copy(a1, {deep: true}); | ||
t.ok(Array.isArray(c1), `deep copy ${stringify(c1)} must be an array`); | ||
@@ -321,3 +351,3 @@ t.notEqual(c1, a1, `deep copy ${stringify(c1)} must NOT be same instance`); | ||
const a2 = [1, 2, "3", undefined, null, {a: 1}, [4, 5, "6", null, undefined, {b: 2, c: [7]}]]; | ||
const c2 = Objects.copy(a2, false); | ||
const c2 = Objects.copy(a2, {deep: false}); | ||
@@ -335,3 +365,3 @@ t.ok(Array.isArray(c2), `shallow copy ${stringify(c2)} must be an array`); | ||
const a3 = [1, 2, "3", undefined, null, {a: 1}, [4, 5, "6", null, undefined, {b: 2, c: [7]}]]; | ||
const c3 = Objects.copy(a3, true); | ||
const c3 = Objects.copy(a3, {deep: true}); | ||
@@ -361,3 +391,3 @@ t.ok(Array.isArray(c3), `deep copy ${stringify(c3)} must be an array`); | ||
const a0 = {a: []}; | ||
const c0 = Objects.copy(a0, false); | ||
const c0 = Objects.copy(a0, {deep: false}); | ||
t.ok(!Array.isArray(c0), `shallow copy ${stringify(c0)} must NOT be an array`); | ||
@@ -369,3 +399,3 @@ t.notEqual(c0, a0, `shallow copy ${stringify(c0)} must not be same instance`); | ||
const a1 = {a: []}; | ||
const c1 = Objects.copy(a1, true); | ||
const c1 = Objects.copy(a1, {deep: true}); | ||
t.ok(!Array.isArray(c1), `deep copy ${stringify(c1)} must NOT be an array`); | ||
@@ -377,3 +407,3 @@ t.notEqual(c1, a1, `deep copy ${stringify(c1)} must NOT be same instance`); | ||
const a2 = {a: [1, 2, "3", undefined, null, {a: 1}, [4, 5, "6", null, undefined, {b: 2, c: [7]}]]}; | ||
const c2 = Objects.copy(a2, false); | ||
const c2 = Objects.copy(a2, {deep: false}); | ||
@@ -390,3 +420,3 @@ t.ok(!Array.isArray(c2), `shallow copy ${stringify(c2)} must NOT be an array`); | ||
const a3 = {a: [1, 2, "3", undefined, null, {a: 1}, [4, 5, "6", null, undefined, {b: 2, c: [7]}]]}; | ||
const c3 = Objects.copy(a3, true); | ||
const c3 = Objects.copy(a3, {deep: true}); | ||
@@ -415,3 +445,3 @@ t.ok(!Array.isArray(c3), `deep copy ${stringify(c3)} must NOT be an array`); | ||
const b0 = []; | ||
const c0 = Objects.merge(a0, b0, false, false); | ||
const c0 = Objects.merge(a0, b0, {replace: false, deep: false}); | ||
t.ok(Array.isArray(c0), `shallow merge ${stringify(c0)} must be an array`); | ||
@@ -424,3 +454,3 @@ t.notEqual(c0, a0, `shallow merge ${stringify(c0)} must not be same instance`); | ||
const b1 = []; | ||
const c1 = Objects.merge(a1, b1, false, true); | ||
const c1 = Objects.merge(a1, b1, {replace: false, deep: true}); | ||
t.ok(Array.isArray(c1), `deep merge ${stringify(c1)} must be an array`); | ||
@@ -437,3 +467,3 @@ t.notEqual(c1, a1, `deep merge ${stringify(c1)} must NOT be same instance`); | ||
const b0 = []; | ||
const c0 = Objects.merge(a0, b0, false, false); | ||
const c0 = Objects.merge(a0, b0, {replace: false, deep: false}); | ||
t.ok(Array.isArray(c0), `shallow merge ${stringify(c0)} must be an array`); | ||
@@ -446,3 +476,3 @@ t.notEqual(c0, a0, `shallow merge ${stringify(c0)} must not be same instance`); | ||
const b1 = []; | ||
const c1 = Objects.merge(a1, b1, false, true); | ||
const c1 = Objects.merge(a1, b1, {replace: false, deep: true}); | ||
t.ok(Array.isArray(c1), `deep merge ${stringify(c1)} must be an array`); | ||
@@ -460,3 +490,3 @@ t.notEqual(c1, a1, `deep merge ${stringify(c1)} must NOT be same instance`); | ||
const x0 = [1, 2, 3]; | ||
Objects.merge(a0, b0, false, false); | ||
Objects.merge(a0, b0, {replace: false, deep: false}); | ||
t.ok(Array.isArray(b0), `shallow merge ${stringify(b0)} must be an array`); | ||
@@ -470,3 +500,3 @@ t.notEqual(b0, a0, `shallow merge ${stringify(b0)} must not be same instance`); | ||
const x1 = ["1", 2, "3"]; | ||
const c1 = Objects.merge(a1, b1, false, true); | ||
const c1 = Objects.merge(a1, b1, {replace: false, deep: true}); | ||
t.ok(Array.isArray(c1), `deep merge ${stringify(c1)} must be an array`); | ||
@@ -484,3 +514,3 @@ t.notEqual(c1, a1, `deep merge ${stringify(c1)} must NOT be same instance`); | ||
const x0 = [9, "8", "3", 4.0]; | ||
Objects.merge(a0, b0, false, false); | ||
Objects.merge(a0, b0, {replace: false, deep: false}); | ||
t.ok(Array.isArray(b0), `shallow merge ${stringify(b0)} must be an array`); | ||
@@ -494,3 +524,3 @@ t.notEqual(b0, a0, `shallow merge ${stringify(b0)} must not be same instance`); | ||
const x1 = ["1", 2, "3", 4.0]; | ||
Objects.merge(a1, b1, true, false); | ||
Objects.merge(a1, b1, {replace: true, deep: false}); | ||
t.ok(Array.isArray(b1), `deep merge ${stringify(b1)} must be an array`); | ||
@@ -504,3 +534,3 @@ t.notEqual(b1, a1, `deep merge ${stringify(b1)} must NOT be same instance`); | ||
const x2 = ["9", 8, 3, 4.0]; | ||
Objects.merge(a2, b2, false, true); | ||
Objects.merge(a2, b2, {replace: false, deep: true}); | ||
t.ok(Array.isArray(b2), `deep merge ${stringify(b2)} must be an array`); | ||
@@ -514,3 +544,3 @@ t.notEqual(b2, a2, `deep merge ${stringify(b2)} must not be same instance`); | ||
const x4 = ["1", 2, "3", 4.0]; | ||
Objects.merge(a4, b4, true, true); | ||
Objects.merge(a4, b4, {replace: true, deep: true}); | ||
t.ok(Array.isArray(b4), `deep merge ${stringify(b4)} must be an array`); | ||
@@ -528,3 +558,3 @@ t.notEqual(b4, a4, `deep merge ${stringify(b4)} must NOT be same instance`); | ||
const x0 = ["1", 2, "3", 4.0, [5]]; | ||
Objects.merge(a0, b0, false, false); | ||
Objects.merge(a0, b0, {replace: false, deep: false}); | ||
t.ok(Array.isArray(b0), `shallow merge ${stringify(b0)} must be an array`); | ||
@@ -538,3 +568,3 @@ t.notEqual(b0, a0, `shallow merge ${stringify(b0)} must not be same instance`); | ||
const x1 = [9, "8", "3", 4.0, [5]]; | ||
Objects.merge(a1, b1, true, false); | ||
Objects.merge(a1, b1, {replace: true, deep: false}); | ||
t.ok(Array.isArray(b1), `deep merge ${stringify(b1)} must be an array`); | ||
@@ -548,3 +578,3 @@ t.notEqual(b1, a1, `deep merge ${stringify(b1)} must NOT be same instance`); | ||
const x2 = [1, 2, 3, 4.0, [5]]; | ||
Objects.merge(a2, b2, false, true); | ||
Objects.merge(a2, b2, {replace: false, deep: true}); | ||
t.ok(Array.isArray(b2), `deep merge ${stringify(b2)} must be an array`); | ||
@@ -558,3 +588,3 @@ t.notEqual(b2, a2, `deep merge ${stringify(b2)} must not be same instance`); | ||
const x4 = [9, 8, "3", 4.0, [5]]; | ||
Objects.merge(a4, b4, true, true); | ||
Objects.merge(a4, b4, {replace: true, deep: true}); | ||
t.ok(Array.isArray(b4), `deep merge ${stringify(b4)} must be an array`); | ||
@@ -574,3 +604,3 @@ t.notEqual(b4, a4, `deep merge ${stringify(b4)} must NOT be same instance`); | ||
Objects.merge(a1, b1, false, false); | ||
Objects.merge(a1, b1, {replace: false, deep: false}); | ||
@@ -592,3 +622,3 @@ t.ok(Array.isArray(b1), `shallow merge ${stringify(b1)} must be an array`); | ||
Objects.merge(a2, b2, true, false); | ||
Objects.merge(a2, b2, {replace: true, deep: false}); | ||
@@ -621,3 +651,3 @@ t.ok(Array.isArray(b2), `shallow merge ${stringify(b2)} must be an array`); | ||
}]]; | ||
Objects.merge(a3, b3, false, true); | ||
Objects.merge(a3, b3, {replace: false, deep: true}); | ||
@@ -639,3 +669,3 @@ t.ok(Array.isArray(b3), `deep merge ${stringify(b3)} must be an array`); | ||
}]]; | ||
Objects.merge(a4, b4, true, true); | ||
Objects.merge(a4, b4, {replace: true, deep: true}); | ||
@@ -660,3 +690,3 @@ t.ok(Array.isArray(b4), `deep merge ${stringify(b4)} must be an array`); | ||
const x0 = {a: [1, 2], b: {c: [6]}}; | ||
Objects.merge(a0, b0, false, false); | ||
Objects.merge(a0, b0, {replace: false, deep: false}); | ||
t.ok(!Array.isArray(b0), `shallow merge ${stringify(b0)} must NOT be an array`); | ||
@@ -671,3 +701,3 @@ t.ok(Array.isArray(b0.a), `shallow merge ${stringify(b0.a)} must be an array`); | ||
const x1 = {a: [3], b: {c: [4, 5]}}; | ||
Objects.merge(a1, b1, true, false); | ||
Objects.merge(a1, b1, {replace: true, deep: false}); | ||
t.ok(!Array.isArray(b1), `shallow merge ${stringify(b1)} must NOT be an array`); | ||
@@ -682,3 +712,3 @@ t.ok(Array.isArray(b1.a), `shallow merge ${stringify(b1.a)} must be an array`); | ||
const x2 = {a: [1, 2], b: {c: [6, 5]}}; | ||
Objects.merge(a2, b2, false, true); | ||
Objects.merge(a2, b2, {replace: false, deep: true}); | ||
t.ok(!Array.isArray(b2), `deep merge ${stringify(b2)} must NOT be an array`); | ||
@@ -693,3 +723,3 @@ t.ok(Array.isArray(b2.a), `deep merge ${stringify(b2.a)} must be an array`); | ||
const x3 = {a: [3, 2], b: {c: [4, 5]}}; | ||
Objects.merge(a3, b3, true, true); | ||
Objects.merge(a3, b3, {replace: true, deep: true}); | ||
t.ok(!Array.isArray(b3), `deep merge ${stringify(b3)} must NOT be an array`); | ||
@@ -713,3 +743,3 @@ t.ok(Array.isArray(b3.a), `deep merge ${stringify(b3.a)} must be an array`); | ||
Objects.merge(a0, b0, false, false); | ||
Objects.merge(a0, b0, {replace: false, deep: false}); | ||
@@ -726,3 +756,3 @@ t.ok(Array.isArray(b0), `shallow merge ${stringify(b0)} must be an array`); | ||
Objects.merge(a1, b1, true, false); | ||
Objects.merge(a1, b1, {replace: true, deep: false}); | ||
@@ -739,3 +769,3 @@ t.ok(Array.isArray(b1), `shallow merge ${stringify(b1)} must be an array`); | ||
Objects.merge(a2, b2, false, true); | ||
Objects.merge(a2, b2, {replace: false, deep: true}); | ||
@@ -751,3 +781,3 @@ t.ok(Array.isArray(b2), `deep merge ${stringify(b2)} must be an array`); | ||
const x3 = [9,7]; x3.a = [3,2]; x3.b = {c: [4,5]}; | ||
Objects.merge(a3, b3, true, true); | ||
Objects.merge(a3, b3, {replace: true, deep: true}); | ||
t.ok(Array.isArray(b3), `deep merge ${stringify(b3)} must be an array`); | ||
@@ -771,3 +801,3 @@ t.deepEqual(b3, x3, `deep merge ${stringify(b3)} must be deep equal`); | ||
Objects.merge(a0, b0, false, false); | ||
Objects.merge(a0, b0, {replace: false, deep: false}); | ||
@@ -784,3 +814,3 @@ t.ok(!Array.isArray(b0), `shallow merge ${stringify(b0)} must not be an array`); | ||
Objects.merge(a1, b1, true, false); | ||
Objects.merge(a1, b1, {replace: true, deep: false}); | ||
@@ -797,3 +827,3 @@ t.ok(!Array.isArray(b1), `shallow merge ${stringify(b1)} must not be an array`); | ||
Objects.merge(a2, b2, false, true); | ||
Objects.merge(a2, b2, {replace: false, deep: true}); | ||
@@ -810,3 +840,3 @@ t.ok(!Array.isArray(b2), `deep merge ${stringify(b2)} must not be an array`); | ||
Objects.merge(a3, b3, true, true); | ||
Objects.merge(a3, b3, {replace: true, deep: true}); | ||
@@ -841,31 +871,31 @@ t.ok(!Array.isArray(b3), `deep merge ${stringify(b3)} must not be an array`); | ||
const omit = true; | ||
t.deepEqual(Objects.copyNamedProperties(undefined, ['a.b.c'], compact, deep, omit), undefined, `(undefined, ['a.b.c'], compact, deep, omit) must be undefined`); | ||
t.deepEqual(Objects.copyNamedProperties(null, ['a.b.c'], compact, deep, omit), null, `(null, ['a.b.c'], compact, deep, omit) must be null`); | ||
t.deepEqual(Objects.copyNamedProperties({}, ['a.b.c'], compact, deep, omit), {}, `({}, ['a.b.c'], compact, deep, omit) must be {}`); | ||
t.deepEqual(Objects.copyNamedProperties({}, ['a.b.c'], compact, deep, !omit), {'a.b.c': undefined}, `({}, ['a.b.c'], compact, deep, !omit) must be {'a.b.c': undefined}`); | ||
t.deepEqual(Objects.copyNamedProperties([], ['a.b.c'], compact, deep, omit), {}, `([] ['a.b.c'], compact, deep, omit) must be {}`); | ||
t.deepEqual(Objects.copyNamedProperties([], ['a.b.c'], compact, deep, !omit), {'a.b.c': undefined}, `([], ['a.b.c'], compact, deep, !omit) must be {'a.b.c': undefined}`); | ||
t.deepEqual(Objects.copyNamedProperties(undefined, ['a.b.c'], {compact: compact, deep: deep, omitIfUndefined: omit}), undefined, `(undefined, ['a.b.c'], {compact: compact, deep: deep, omitIfUndefined: omit}) must be undefined`); | ||
t.deepEqual(Objects.copyNamedProperties(null, ['a.b.c'], {compact: compact, deep: deep, omitIfUndefined: omit}), null, `(null, ['a.b.c'], {compact: compact, deep: deep, omitIfUndefined: omit}) must be null`); | ||
t.deepEqual(Objects.copyNamedProperties({}, ['a.b.c'], {compact: compact, deep: deep, omitIfUndefined: omit}), {}, `({}, ['a.b.c'], {compact: compact, deep: deep, omitIfUndefined: omit}) must be {}`); | ||
t.deepEqual(Objects.copyNamedProperties({}, ['a.b.c'], {compact: compact, deep: deep, omitIfUndefined: !omit}), {'a.b.c': undefined}, `({}, ['a.b.c'], {compact: compact, deep: deep, omitIfUndefined: !omit}) must be {'a.b.c': undefined}`); | ||
t.deepEqual(Objects.copyNamedProperties([], ['a.b.c'], {compact: compact, deep: deep, omitIfUndefined: omit}), {}, `([] ['a.b.c'], {compact: compact, deep: deep, omitIfUndefined: omit}) must be {}`); | ||
t.deepEqual(Objects.copyNamedProperties([], ['a.b.c'], {compact: compact, deep: deep, omitIfUndefined: !omit}), {'a.b.c': undefined}, `([], ['a.b.c'], {compact: compact, deep: deep, omitIfUndefined: !omit}) must be {'a.b.c': undefined}`); | ||
const o = {a: 1, b: {c: 'c', d: {e: 'e'}}}; | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a'], compact, deep, !omit), {a: 1}, `(o, [a], compact, deep, !omit) must be {a: 1}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b'], compact, deep, !omit), {b: {c: 'c', d: {e: 'e'}}}, `(o, [b], compact, deep, !omit) must be {b: {c: 'c', d: {e: 'e'}}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a'], {compact: compact, deep: deep, omitIfUndefined: !omit}), {a: 1}, `(o, [a], {compact: compact, deep: deep, omitIfUndefined: !omit}) must be {a: 1}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b'], {compact: compact, deep: deep, omitIfUndefined: !omit}), {b: {c: 'c', d: {e: 'e'}}}, `(o, [b], {compact: compact, deep: deep, omitIfUndefined: !omit}) must be {b: {c: 'c', d: {e: 'e'}}}`); | ||
t.notEqual(Objects.copyNamedProperties(o, ['b'], compact, deep, !omit).b, o.b, `(o, [b], compact, deep, !omit).b must NOT be o.b`); | ||
t.notEqual(Objects.copyNamedProperties(o, ['b'], !deep, !omit).b, o.b, `(o, [b], !deep, !omit).b must NOT be o.b`); | ||
t.notEqual(Objects.copyNamedProperties(o, ['b'], {compact: compact, deep: deep, omitIfUndefined: !omit}).b, o.b, `(o, [b], {compact: compact, deep: deep, omitIfUndefined: !omit}).b must NOT be o.b`); | ||
t.notEqual(Objects.copyNamedProperties(o, ['b'], {deep: !deep, omitIfUndefined: !omit}).b, o.b, `(o, [b], {deep: !deep, omitIfUndefined: !omit}).b must NOT be o.b`); | ||
t.equal(Objects.copyNamedProperties(o, ['b'], compact, deep, !omit).b.c, o.b.c, `(o, [b], compact, deep, !omit).b.c must equal o.b.c`); | ||
t.equal(Objects.copyNamedProperties(o, ['b'], !deep, !omit).b.c, o.b.c, `(o, [b], !deep, !omit).b.c must equal o.b.c`); | ||
t.equal(Objects.copyNamedProperties(o, ['b'], {compact: compact, deep: deep, omitIfUndefined: !omit}).b.c, o.b.c, `(o, [b], {compact: compact, deep: deep, omitIfUndefined: !omit}).b.c must equal o.b.c`); | ||
t.equal(Objects.copyNamedProperties(o, ['b'], {deep: !deep, omitIfUndefined: !omit}).b.c, o.b.c, `(o, [b], {deep: !deep, omitIfUndefined: !omit}).b.c must equal o.b.c`); | ||
t.notEqual(Objects.copyNamedProperties(o, ['b'], compact, deep, !omit).b.d, o.b.d, `(o, [b], compact, deep, !omit).b.d must NOT be o.b.d`); | ||
t.equal(Objects.copyNamedProperties(o, ['b'], !deep, !omit).b.d, o.b.d, `(o, [b], !deep, !omit).b.d must be o.b.d`); | ||
t.notEqual(Objects.copyNamedProperties(o, ['b'], {compact: compact, deep: deep, omitIfUndefined: !omit}).b.d, o.b.d, `(o, [b], {compact: compact, deep: deep, omitIfUndefined: !omit}).b.d must NOT be o.b.d`); | ||
t.equal(Objects.copyNamedProperties(o, ['b'], {deep: !deep, omitIfUndefined: !omit}).b.d, o.b.d, `(o, [b], {deep: !deep, omitIfUndefined: !omit}).b.d must be o.b.d`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b.c'], compact, deep, !omit), {'b.c': 'c'}, `(o, [b.c], compact, deep, !omit) must be {'b.c': 'c'}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b.d'], compact, deep, !omit), {'b.d': {e: 'e'}}, `(o, [b.d], compact, deep, !omit) must be {'b.d': {e: 'e'}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b.d.e'], compact, deep, !omit), {'b.d.e': 'e'}, `(o, [b.d.e], compact, deep, !omit) must be {'b.d.e': 'e'}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['x.y.z'], compact, deep, !omit), {'x.y.z': undefined}, `(o, [x.y.z], compact, deep, !omit) must be {'x.y.z': undefined}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['x.y.z'], compact, deep, omit), {}, `(o, [x.y.z], compact, deep, omit) must be {}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b.c'], {compact: compact, deep: deep, omitIfUndefined: !omit}), {'b.c': 'c'}, `(o, [b.c], {compact: compact, deep: deep, omitIfUndefined: !omit}) must be {'b.c': 'c'}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b.d'], {compact: compact, deep: deep, omitIfUndefined: !omit}), {'b.d': {e: 'e'}}, `(o, [b.d], {compact: compact, deep: deep, omitIfUndefined: !omit}) must be {'b.d': {e: 'e'}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b.d.e'], {compact: compact, deep: deep, omitIfUndefined: !omit}), {'b.d.e': 'e'}, `(o, [b.d.e], {compact: compact, deep: deep, omitIfUndefined: !omit}) must be {'b.d.e': 'e'}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['x.y.z'], {compact: compact, deep: deep, omitIfUndefined: !omit}), {'x.y.z': undefined}, `(o, [x.y.z], {compact: compact, deep: deep, omitIfUndefined: !omit}) must be {'x.y.z': undefined}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['x.y.z'], {compact: compact, deep: deep, omitIfUndefined: omit}), {}, `(o, [x.y.z], {compact: compact, deep: deep, omitIfUndefined: omit}) must be {}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a', 'b'], compact, deep, !omit), o, `(o, [a,b], compact, deep, !omit) must equal o`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a', 'b.c', 'b.d'], compact, deep, !omit), {a: 1, 'b.c': 'c', 'b.d': {e: 'e'}}, `(o, [a,b], compact, deep, !omit) must equal {a: 1, 'b.c': 'c', 'b.d': {e: 'e'}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a', 'b.c', 'b.d.e'], compact, deep, !omit), {a: 1, 'b.c': 'c', 'b.d.e': 'e'}, `(o, [a,b], compact, deep, !omit) must equal {a: 1, 'b.c': 'c', 'b.d.e': 'e'}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a', 'b'], {compact: compact, deep: deep, omitIfUndefined: !omit}), o, `(o, [a,b], {compact: compact, deep: deep, omitIfUndefined: !omit}) must equal o`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a', 'b.c', 'b.d'], {compact: compact, deep: deep, omitIfUndefined: !omit}), {a: 1, 'b.c': 'c', 'b.d': {e: 'e'}}, `(o, [a,b], {compact: compact, deep: deep, omitIfUndefined: !omit}) must equal {a: 1, 'b.c': 'c', 'b.d': {e: 'e'}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a', 'b.c', 'b.d.e'], {compact: compact, deep: deep, omitIfUndefined: !omit}), {a: 1, 'b.c': 'c', 'b.d.e': 'e'}, `(o, [a,b], {compact: compact, deep: deep, omitIfUndefined: !omit}) must equal {a: 1, 'b.c': 'c', 'b.d.e': 'e'}`); | ||
@@ -879,33 +909,62 @@ t.end(); | ||
const omit = true; | ||
t.deepEqual(Objects.copyNamedProperties(undefined, ['a.b.c'], !compact, deep, omit), undefined, `(undefined, ['a.b.c'], !compact, deep, omit) must be undefined`); | ||
t.deepEqual(Objects.copyNamedProperties(null, ['a.b.c'], !compact, deep, omit), null, `(null, ['a.b.c'], !compact, deep, omit) must be null`); | ||
t.deepEqual(Objects.copyNamedProperties({}, ['a.b.c'], !compact, deep, omit), {}, `({}, ['a.b.c'], !compact, deep, omit) must be {}`); | ||
t.deepEqual(Objects.copyNamedProperties({}, ['a.b.c'], !compact, deep, !omit), {a: {b: {c: undefined}}}, `({}, ['a.b.c'], !compact, deep, !omit) must be {a: {b: {c: undefined}}}`); | ||
t.deepEqual(Objects.copyNamedProperties([], ['a.b.c'], !compact, deep, omit), {}, `([] ['a.b.c'], !compact, deep, omit) must be {}`); | ||
t.deepEqual(Objects.copyNamedProperties([], ['a.b.c'], !compact, deep, !omit), {a: {b: {c: undefined}}}, `([], ['a.b.c'], !compact, deep, !omit) must be {a: {b: {c: undefined}}}`); | ||
t.deepEqual(Objects.copyNamedProperties(undefined, ['a.b.c'], {compact: !compact, deep: deep, omitIfUndefined: omit}), undefined, `(undefined, ['a.b.c'], {compact: !compact, deep: deep, omitIfUndefined: omit}) must be undefined`); | ||
t.deepEqual(Objects.copyNamedProperties(null, ['a.b.c'], {compact: !compact, deep: deep, omitIfUndefined: omit}), null, `(null, ['a.b.c'], {compact: !compact, deep: deep, omitIfUndefined: omit}) must be null`); | ||
t.deepEqual(Objects.copyNamedProperties({}, ['a.b.c'], {compact: !compact, deep: deep, omitIfUndefined: omit}), {}, `({}, ['a.b.c'], {compact: !compact, deep: deep, omitIfUndefined: omit}) must be {}`); | ||
t.deepEqual(Objects.copyNamedProperties({}, ['a.b.c'], {compact: !compact, deep: deep, omitIfUndefined: !omit}), {a: {b: {c: undefined}}}, `({}, ['a.b.c'], {compact: !compact, deep: deep, omitIfUndefined: !omit}) must be {a: {b: {c: undefined}}}`); | ||
t.deepEqual(Objects.copyNamedProperties([], ['a.b.c'], {compact: !compact, deep: deep, omitIfUndefined: omit}), {}, `([] ['a.b.c'], {compact: !compact, deep: deep, omitIfUndefined: omit}) must be {}`); | ||
t.deepEqual(Objects.copyNamedProperties([], ['a.b.c'], {compact: !compact, deep: deep, omitIfUndefined: !omit}), {a: {b: {c: undefined}}}, `([], ['a.b.c'], {compact: !compact, deep: deep, omitIfUndefined: !omit}) must be {a: {b: {c: undefined}}}`); | ||
const o = {a: 1, b: {c: 'c', d: {e: 'e'}}}; | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a'], !compact, deep, !omit), {a: 1}, `(o, [a], !compact, deep, !omit) must be {a: 1}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b'], !compact, deep, !omit), {b: {c: 'c', d: {e: 'e'}}}, `(o, [b], !compact, deep, !omit) must be {b: {c: 'c', d: {e: 'e'}}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a'], {compact: !compact, deep: deep, omitIfUndefined: !omit}), {a: 1}, `(o, [a], {compact: !compact, deep: deep, omitIfUndefined: !omit}) must be {a: 1}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b'], {compact: !compact, deep: deep, omitIfUndefined: !omit}), {b: {c: 'c', d: {e: 'e'}}}, `(o, [b], {compact: !compact, deep: deep, omitIfUndefined: !omit}) must be {b: {c: 'c', d: {e: 'e'}}}`); | ||
t.notEqual(Objects.copyNamedProperties(o, ['b'], !compact, deep, !omit).b, o.b, `(o, [b], !compact, deep, !omit).b must NOT be o.b`); | ||
t.notEqual(Objects.copyNamedProperties(o, ['b'], !deep, !omit).b, o.b, `(o, [b], !deep, !omit).b must NOT be o.b`); | ||
t.notEqual(Objects.copyNamedProperties(o, ['b'], {compact: !compact, deep: deep, omitIfUndefined: !omit}).b, o.b, `(o, [b], {compact: !compact, deep: deep, omitIfUndefined: !omit}).b must NOT be o.b`); | ||
t.notEqual(Objects.copyNamedProperties(o, ['b'], {deep: !deep, omitIfUndefined: !omit}).b, o.b, `(o, [b], {deep: !deep, omitIfUndefined: !omit}).b must NOT be o.b`); | ||
t.equal(Objects.copyNamedProperties(o, ['b'], !compact, deep, !omit).b.c, o.b.c, `(o, [b], !compact, deep, !omit).b.c must equal o.b.c`); | ||
t.equal(Objects.copyNamedProperties(o, ['b'], !deep, !omit).b.c, o.b.c, `(o, [b], !deep, !omit).b.c must equal o.b.c`); | ||
t.equal(Objects.copyNamedProperties(o, ['b'], {compact: !compact, deep: deep, omitIfUndefined: !omit}).b.c, o.b.c, `(o, [b], {compact: !compact, deep: deep, omitIfUndefined: !omit}).b.c must equal o.b.c`); | ||
t.equal(Objects.copyNamedProperties(o, ['b'], {deep: !deep, omitIfUndefined: !omit}).b.c, o.b.c, `(o, [b], {deep: !deep, omitIfUndefined: !omit}).b.c must equal o.b.c`); | ||
t.notEqual(Objects.copyNamedProperties(o, ['b'], !compact, deep, !omit).b.d, o.b.d, `(o, [b], !compact, deep, !omit).b.d must NOT be o.b.d`); | ||
t.equal(Objects.copyNamedProperties(o, ['b'], !deep, !omit).b.d, o.b.d, `(o, [b], !deep, !omit).b.d must be o.b.d`); | ||
t.notEqual(Objects.copyNamedProperties(o, ['b'], {compact: !compact, deep: deep, omitIfUndefined: !omit}).b.d, o.b.d, `(o, [b], {compact: !compact, deep: deep, omitIfUndefined: !omit}).b.d must NOT be o.b.d`); | ||
t.equal(Objects.copyNamedProperties(o, ['b'], {deep: !deep, omitIfUndefined: !omit}).b.d, o.b.d, `(o, [b], {deep: !deep, omitIfUndefined: !omit}).b.d must be o.b.d`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b.c'], !compact, deep, !omit), {b: {c: 'c'}}, `(o, [b.c], !compact, deep, !omit) must be {b: {c: 'c'}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b.d'], !compact, deep, !omit), {b: {d: {e: 'e'}}}, `(o, [b.d], !compact, deep, !omit) must be {b: {d: {e: 'e'}}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b.d.e'], !compact, deep, !omit), {b: {d: {e: 'e'}}}, `(o, [b.d.e], !compact, deep, !omit) must be {b: {d: {e: 'e'}}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['x.y.z'], !compact, deep, !omit), {x: {y: {z: undefined}}}, `(o, [x.y.z], !compact, deep, !omit) must be {x: {y: {z: undefined}}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['x.y.z'], !compact, deep, omit), {}, `(o, [x.y.z], !compact, deep, omit) must be {}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b.c'], {compact: !compact, deep: deep, omitIfUndefined: !omit}), {b: {c: 'c'}}, `(o, [b.c], {compact: !compact, deep: deep, omitIfUndefined: !omit}) must be {b: {c: 'c'}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b.d'], {compact: !compact, deep: deep, omitIfUndefined: !omit}), {b: {d: {e: 'e'}}}, `(o, [b.d], {compact: !compact, deep: deep, omitIfUndefined: !omit}) must be {b: {d: {e: 'e'}}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['b.d.e'], {compact: !compact, deep: deep, omitIfUndefined: !omit}), {b: {d: {e: 'e'}}}, `(o, [b.d.e], {compact: !compact, deep: deep, omitIfUndefined: !omit}) must be {b: {d: {e: 'e'}}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['x.y.z'], {compact: !compact, deep: deep, omitIfUndefined: !omit}), {x: {y: {z: undefined}}}, `(o, [x.y.z], {compact: !compact, deep: deep, omitIfUndefined: !omit}) must be {x: {y: {z: undefined}}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['x.y.z'], {compact: !compact, deep: deep, omitIfUndefined: omit}), {}, `(o, [x.y.z], {compact: !compact, deep: deep, omitIfUndefined: omit}) must be {}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a', 'b'], !compact, deep, !omit), o, `(o, [a,b], !compact, deep, !omit) must equal o`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a', 'b.c', 'b.d'], !compact, deep, !omit), {a: 1, b: {c: 'c', d: {e: 'e'}}}, `(o, [a,b], !compact, deep, !omit) must equal {a: 1, b: {c: 'c', d: {e: 'e'}}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a', 'b.c', 'b.d.e'], !compact, deep, !omit), {a: 1, b: {c: 'c', d: {e: 'e'}}}, `(o, [a,b], !compact, deep, !omit) must equal {a: 1, b: {c: 'c', d: {e: 'e'}}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a', 'b'], {compact: !compact, deep: deep, omitIfUndefined: !omit}), o, `(o, [a,b], {compact: !compact, deep: deep, omitIfUndefined: !omit}) must equal o`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a', 'b.c', 'b.d'], {compact: !compact, deep: deep, omitIfUndefined: !omit}), {a: 1, b: {c: 'c', d: {e: 'e'}}}, `(o, [a,b], {compact: !compact, deep: deep, omitIfUndefined: !omit}) must equal {a: 1, b: {c: 'c', d: {e: 'e'}}}`); | ||
t.deepEqual(Objects.copyNamedProperties(o, ['a', 'b.c', 'b.d.e'], {compact: !compact, deep: deep, omitIfUndefined: !omit}), {a: 1, b: {c: 'c', d: {e: 'e'}}}, `(o, [a,b], {compact: !compact, deep: deep, omitIfUndefined: !omit}) must equal {a: 1, b: {c: 'c', d: {e: 'e'}}}`); | ||
t.end(); | ||
}); | ||
test('toKeyValuePairs', t => { | ||
// Applied to non-objects | ||
let expected = []; | ||
t.deepEqual(toKeyValuePairs(undefined), expected, `toKeyValuePairs(undefined) must be ${stringify(expected)}`); | ||
t.deepEqual(toKeyValuePairs(null), expected, `toKeyValuePairs(null) must be ${stringify(expected)}`); | ||
t.deepEqual(toKeyValuePairs('abc'), expected, `toKeyValuePairs('abc') must be ${stringify(expected)}`); | ||
t.deepEqual(toKeyValuePairs(123), expected, `toKeyValuePairs(123) must be ${stringify(expected)}`); | ||
// Applied to objects | ||
expected = []; | ||
t.deepEqual(toKeyValuePairs({}), expected, `toKeyValuePairs({}) must be ${stringify(expected)}`); | ||
expected = [['a', 1], ['b', {c:2}], ['d', '3']]; | ||
t.deepEqual(toKeyValuePairs({a:1, b:{c:2}, d:'3'}), expected, `toKeyValuePairs({a:1, b:{c:2}, d:'3'}) must be ${stringify(expected)}`); | ||
expected = [['d', '3'], ['b', {c:2}], ['a', 1]]; | ||
t.deepEqual(toKeyValuePairs({d:'3', b:{c:2}, a:1}), expected, `toKeyValuePairs({d:'3', b:{c:2}, a:1}) must be ${stringify(expected)}`); | ||
// Not meant to be applied to arrays, but if so ... | ||
expected = [['length', 0]]; | ||
t.deepEqual(toKeyValuePairs([]), expected, `toKeyValuePairs([]) must be ${stringify(expected)}`); | ||
expected = [['0', '1'], ['1', 2], ['2', '3'], ['length', 3]]; | ||
t.deepEqual(toKeyValuePairs(['1', 2, '3']), expected, `toKeyValuePairs(['1', 2, '3']) must be ${stringify(expected)}`); | ||
expected = [['0', '3'], ['1', 2], ['2', '1'], ['length', 3]]; | ||
t.deepEqual(toKeyValuePairs(['3',2,'1']), expected, `toKeyValuePairs(['3',2,'1']) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); |
{ | ||
"name": "core-functions-tests", | ||
"version": "2.0.14", | ||
"version": "3.0.0", | ||
"author": "Byron du Preez", | ||
"license": "Apache-2.0", | ||
"private": true, | ||
"engines": { | ||
@@ -7,0 +8,0 @@ "node": ">=4.3.2" |
@@ -10,16 +10,13 @@ 'use strict'; | ||
const tries = require('../tries'); | ||
// const Try = tries.Try; | ||
const Success = tries.Success; | ||
const Failure = tries.Failure; | ||
const Promises = require('../promises'); | ||
const CancelledError = Promises.CancelledError; | ||
const Strings = require('../strings'); | ||
const stringify = Strings.stringify; | ||
//const testing = require('./testing'); | ||
// const okNotOk = testing.okNotOk; | ||
// const checkOkNotOk = testing.checkOkNotOk; | ||
// const checkMethodOkNotOk = testing.checkMethodOkNotOk; | ||
// const equal = testing.equal; | ||
// const checkEqual = testing.checkEqual; | ||
// const checkMethodEqual = testing.checkMethodEqual; | ||
const error = new Error('Fail'); | ||
@@ -67,13 +64,79 @@ | ||
// Arbitrary Promises | ||
const p1 = Promise.resolve('p1'); | ||
const p2Error = new Error('p2 error'); | ||
const p2 = Promise.reject(p2Error); | ||
const p3 = Promise.resolve('p3'); | ||
const p4Error = new Error('p4 error'); | ||
const p4 = Promise.reject(p4Error); | ||
const t1 = genThenable(null, 't1', false, 1); | ||
const t2Error = new Error('t2 error'); | ||
const t2 = genThenable(t2Error, null, false, 1); | ||
const t3Error = new Error('t3 error'); | ||
const t3 = genThenable(t3Error, null, true, 1); | ||
const d2Error = new Error('d2 error'); | ||
const d4Error = new Error('d4 error'); | ||
function genDelayedPromise(err, name, ms, delayCancellable, cancellable) { | ||
const startTime = Date.now(); | ||
return Promises.delay(ms, delayCancellable).then(() => { | ||
if (cancellable) { | ||
if (cancellable.cancel) { | ||
const completed = cancellable.cancel(); | ||
console.log(`Delayed promise ${name} ${completed ? '"cancelled" completed' : 'cancelled incomplete'} cancellable`); | ||
} else { | ||
console.log(`Delayed promise ${name} could NOT cancel given cancellable, since no cancel was installed yet!`); | ||
} | ||
} | ||
const msElapsed = Date.now() - startTime; | ||
if (msElapsed >= ms) { | ||
console.log(`Delayed promise ${name} completed at ${msElapsed} ms (original delay was ${ms} ms)`); | ||
} else { | ||
console.log(`Delayed promise ${name} ended prematurely at ${msElapsed} ms out of ${ms} ms delay`); | ||
} | ||
if (err) throw err; | ||
return name; | ||
}); | ||
} | ||
function genThenable(err, data, failSync, ms) { | ||
return { | ||
then: (res, rej) => { | ||
setTimeout(() => { | ||
if (err) { | ||
if (!failSync) { | ||
console.log(`"then-able".then rejecting with error ${stringify(err)}`); | ||
rej(err); | ||
} | ||
} else { | ||
console.log(`"then-able".then resolving with result ${stringify(data)}`); | ||
res(data); | ||
} | ||
}, ms); | ||
if (err && failSync) { | ||
console.log(`"then-able".then throwing error ${stringify(err)}`); | ||
throw err; | ||
} | ||
} | ||
}; | ||
} | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promise.isPromise | ||
// Promises.isPromise | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promise.isPromise', t => { | ||
t.notOk(Promise.isPromise(undefined), 'undefined is not a promise'); | ||
t.notOk(Promise.isPromise(null), 'null is not a promise'); | ||
t.notOk(Promise.isPromise(new Error("Err")), 'An error is not a promise'); | ||
t.notOk(Promise.isPromise({}), 'An empty object is not a promise'); | ||
t.ok(Promise.isPromise(new Promise((resolve, reject) => resolve)), 'A Promise is a promise'); | ||
t.ok(Promise.isPromise({then: () => console.log('Then-able')}), 'A then-able (i.e. an object with a then method) "is" a promise'); | ||
test('Promises.isPromise', t => { | ||
t.notOk(Promises.isPromise(undefined), 'undefined is not a promise'); | ||
t.notOk(Promises.isPromise(null), 'null is not a promise'); | ||
t.notOk(Promises.isPromise(new Error("Err")), 'An error is not a promise'); | ||
t.notOk(Promises.isPromise({}), 'An empty object is not a promise'); | ||
t.ok(Promises.isPromise(new Promise((resolve, reject) => resolve)), 'A Promise is a promise'); | ||
t.notOk(Promises.isPromise({then: () => console.log('Then-able')}), 'A then-able (i.e. an object with a then method) is NOT a promise'); | ||
@@ -84,14 +147,29 @@ t.end(); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promise.wrap | ||
// Promises.isPromiseLike | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promise.wrap with node-style function that calls back with an error', t => { | ||
const promiseReturningFn = Promise.wrap(nodeStyleFn); | ||
test('Promises.isPromiseLike', t => { | ||
t.notOk(Promises.isPromiseLike(undefined), 'undefined is not a promise-like'); | ||
t.notOk(Promises.isPromiseLike(null), 'null is not a promise-like'); | ||
t.notOk(Promises.isPromiseLike(new Error("Err")), 'An error is not a promise-like'); | ||
t.notOk(Promises.isPromiseLike({}), 'An empty object is not a promise-like'); | ||
t.ok(Promises.isPromiseLike(new Promise((resolve, reject) => resolve)), 'A Promise is a promise-like'); | ||
t.ok(Promises.isPromiseLike({then: () => console.log('Then-able')}), 'A then-able (i.e. an object with a then method) "is" a promise-like'); | ||
t.end(); | ||
}); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promises.wrap | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promises.wrap with node-style function that calls back with an error', t => { | ||
const promiseReturningFn = Promises.wrap(nodeStyleFn); | ||
promiseReturningFn(true) | ||
.then(result => { | ||
t.fail(`Promise.wrap(nodeStyleFn)(true).then should NOT have got result (${result})`); | ||
t.fail(`Promises.wrap(nodeStyleFn)(true).then should NOT have got result (${result})`); | ||
t.end(err); | ||
}) | ||
.catch(err => { | ||
t.pass(`Promise.wrap(nodeStyleFn)(true).catch should have got error (${err})`); | ||
t.pass(`Promises.wrap(nodeStyleFn)(true).catch should have got error (${err})`); | ||
t.end(); | ||
@@ -101,11 +179,11 @@ }); | ||
test('Promise.wrap with node-style function that calls back with a successful result', t => { | ||
const promiseReturningFn = Promise.wrap(nodeStyleFn); | ||
test('Promises.wrap with node-style function that calls back with a successful result', t => { | ||
const promiseReturningFn = Promises.wrap(nodeStyleFn); | ||
promiseReturningFn(false) | ||
.then(result => { | ||
t.pass(`Promise.wrap(nodeStyleFn)(false).then should have got result (${result})`); | ||
t.pass(`Promises.wrap(nodeStyleFn)(false).then should have got result (${result})`); | ||
t.end(); | ||
}) | ||
.catch(err => { | ||
t.fail(`Promise.wrap(nodeStyleFn)(false).catch should NOT have got error (${err})`); | ||
t.fail(`Promises.wrap(nodeStyleFn)(false).catch should NOT have got error (${err})`); | ||
t.end(err); | ||
@@ -116,14 +194,14 @@ }); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promise.wrapMethod | ||
// Promises.wrapMethod | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promise.wrapMethod with node-style method that calls back with an error', t => { | ||
const promiseReturningMethod = Promise.wrapMethod(objWithNodeStyleMethod, objWithNodeStyleMethod.nodeStyleMethod); | ||
test('Promises.wrapMethod with node-style method that calls back with an error', t => { | ||
const promiseReturningMethod = Promises.wrapMethod(objWithNodeStyleMethod, objWithNodeStyleMethod.nodeStyleMethod); | ||
promiseReturningMethod(true) | ||
.then(result => { | ||
t.fail(`Promise.wrapMethod(...)(true).then should NOT have got result (${result})`); | ||
t.fail(`Promises.wrapMethod(...)(true).then should NOT have got result (${result})`); | ||
t.end(err); | ||
}) | ||
.catch(err => { | ||
t.pass(`Promise.wrapMethod(...)(true).catch should have got error (${err})`); | ||
t.pass(`Promises.wrapMethod(...)(true).catch should have got error (${err})`); | ||
t.end(); | ||
@@ -133,11 +211,11 @@ }); | ||
test('Promise.wrapMethod with node-style method that calls back with a successful result', t => { | ||
const promiseReturningMethod = Promise.wrapMethod(objWithNodeStyleMethod, objWithNodeStyleMethod.nodeStyleMethod); | ||
test('Promises.wrapMethod with node-style method that calls back with a successful result', t => { | ||
const promiseReturningMethod = Promises.wrapMethod(objWithNodeStyleMethod, objWithNodeStyleMethod.nodeStyleMethod); | ||
promiseReturningMethod(false) | ||
.then(result => { | ||
t.pass(`Promise.wrapMethod(...)(false).then should have got result (${result})`); | ||
t.pass(`Promises.wrapMethod(...)(false).then should have got result (${result})`); | ||
t.end(); | ||
}) | ||
.catch(err => { | ||
t.fail(`Promise.wrapMethod(...)(false).catch should NOT have got error (${err})`); | ||
t.fail(`Promises.wrapMethod(...)(false).catch should NOT have got error (${err})`); | ||
t.end(err); | ||
@@ -148,6 +226,6 @@ }); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Using standard Promise.resolve with a synchronous function that throws an error (reason for Promise.try) | ||
// Using standard Promise.resolve with a synchronous function that throws an error (reason for Promises.try) | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Standard Promise.resolve with a synchronous function that throws an error (reason for Promise.try)', t => { | ||
test('Standard Promise.resolve with a synchronous function that throws an error (reason for Promises.try)', t => { | ||
const mustFail = true; | ||
@@ -193,6 +271,6 @@ const prefix = `Promise.resolve(fallible(${mustFail}))`; | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Using standard Promise.reject with a synchronous function that throws an error (another reason for Promise.try) | ||
// Using standard Promise.reject with a synchronous function that throws an error (another reason for Promises.try) | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Standard Promise.reject with a synchronous function that throws an error (another reason for Promise.try)', t => { | ||
test('Standard Promise.reject with a synchronous function that throws an error (another reason for Promises.try)', t => { | ||
const mustFail = true; | ||
@@ -300,3 +378,3 @@ const prefix = `Promise.reject(fallible(${mustFail}))`; | ||
// Unexpected behaviour, Promise.reject does NOT unravel a promise value | ||
t.ok(Promise.isPromise(err), `${prefix}.catch error is a promise!`); | ||
t.ok(Promises.isPromise(err), `${prefix}.catch error is a promise!`); | ||
err | ||
@@ -330,3 +408,3 @@ .then(result2 => { | ||
// Unexpected behaviour, Promise.reject does NOT unravel a promise value | ||
t.ok(Promise.isPromise(err), `${prefix}.catch error is a promise!`); | ||
t.ok(Promises.isPromise(err), `${prefix}.catch error is a promise!`); | ||
err | ||
@@ -349,10 +427,10 @@ .then(result2 => { | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promise.try with a synchronous function | ||
// Promises.try with a synchronous function | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promise.try with a synchronous function that throws exception', t => { | ||
test('Promises.try with a synchronous function that throws exception', t => { | ||
const mustFail = true; | ||
const prefix = `Promise.try(() => fallible(${mustFail}))`; | ||
const prefix = `Promises.try(() => fallible(${mustFail}))`; | ||
try { | ||
Promise.try(() => fallible(mustFail)) | ||
Promises.try(() => fallible(mustFail)) | ||
.then(result => { | ||
@@ -373,7 +451,7 @@ t.fail(`${prefix}.then should NOT have got result (${result})`); | ||
test('Promise.try with a synchronous function that does not throw exception', t => { | ||
test('Promises.try with a synchronous function that does not throw exception', t => { | ||
const mustFail = false; | ||
const prefix = `Promise.try(() => fallible(${mustFail}))`; | ||
const prefix = `Promises.try(() => fallible(${mustFail}))`; | ||
try { | ||
Promise.try(() => fallible(mustFail)) | ||
Promises.try(() => fallible(mustFail)) | ||
.then(result => { | ||
@@ -395,10 +473,10 @@ t.pass(`${prefix}.then should have got result (${result})`); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promise.try with an asynchronous function that returns a promise | ||
// Promises.try with an asynchronous function that returns a promise | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promise.try with an asynchronous function that returns a rejected promise', t => { | ||
test('Promises.try with an asynchronous function that returns a rejected promise', t => { | ||
const mustFail = true; | ||
const prefix = `Promise.try(() => fallibleAsync(${mustFail}))`; | ||
const prefix = `Promises.try(() => fallibleAsync(${mustFail}))`; | ||
try { | ||
Promise.try(() => fallibleAsync(mustFail)) | ||
Promises.try(() => fallibleAsync(mustFail)) | ||
.then(result => { | ||
@@ -419,7 +497,7 @@ t.fail(`${prefix}.then should NOT have got result (${result})`); | ||
test('Promise.try with an asynchronous function that returns a resolved promise', t => { | ||
test('Promises.try with an asynchronous function that returns a resolved promise', t => { | ||
const mustFail = false; | ||
const prefix = `Promise.try(() => fallibleAsync(${mustFail}))`; | ||
const prefix = `Promises.try(() => fallibleAsync(${mustFail}))`; | ||
try { | ||
Promise.try(() => fallibleAsync(mustFail)) | ||
Promises.try(() => fallibleAsync(mustFail)) | ||
.then(result => { | ||
@@ -441,15 +519,15 @@ t.pass(`${prefix}.then should have got result (${result})`); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promise.delay | ||
// Promises.delay | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promise.delay with no cancellable', t => { | ||
Promise.delay(10) | ||
test('Promises.delay with no cancellable', t => { | ||
Promises.delay(10) | ||
.then( | ||
triggered => { | ||
t.pass('Promise.delay should have resolved and its timeout should have triggered'); | ||
t.equal(triggered, true, 'Promise.delay should have triggered'); | ||
t.pass('Promises.delay should have resolved and its timeout should have triggered'); | ||
t.equal(triggered, true, 'Promises.delay should have triggered'); | ||
t.end(); | ||
}, | ||
failed => { | ||
t.fail('Promise.delay should have resolved and its timeout should have triggered'); | ||
() => { | ||
t.fail('Promises.delay should have resolved and its timeout should have triggered'); | ||
t.end(); | ||
@@ -460,13 +538,13 @@ } | ||
test('Promise.delay without cancellation of delay', t => { | ||
test('Promises.delay without cancellation of delay', t => { | ||
const cancellable = {}; | ||
Promise.delay(10, cancellable) | ||
Promises.delay(10, cancellable) | ||
.then( | ||
triggered => { | ||
t.pass('Promise.delay should have resolved and its timeout should have triggered'); | ||
t.equal(triggered, true, 'Promise.delay should have triggered'); | ||
t.pass('Promises.delay should have resolved and its timeout should have triggered'); | ||
t.equal(triggered, true, 'Promises.delay should have triggered'); | ||
t.end(); | ||
}, | ||
failed => { | ||
t.fail('Promise.delay should have resolved and its timeout should have triggered'); | ||
() => { | ||
t.fail('Promises.delay should have resolved and its timeout should have triggered'); | ||
t.end(); | ||
@@ -477,13 +555,13 @@ } | ||
test('Promise.delay with cancellation of delay (mustResolve undefined)', t => { | ||
test('Promises.delay with cancellation of delay (mustResolve undefined)', t => { | ||
const cancellable = {}; | ||
Promise.delay(50, cancellable) | ||
Promises.delay(50, cancellable) | ||
.then( | ||
failed => { | ||
t.fail('Promise.delay should have rejected and its timeout should have cancelled'); | ||
() => { | ||
t.fail('Promises.delay should have rejected and its timeout should have cancelled'); | ||
t.end(); | ||
}, | ||
triggered => { | ||
t.pass('Promise.delay should have rejected and its timeout should been cancelled'); | ||
t.equal(triggered, false, 'Promise.delay should NOT have triggered yet'); | ||
t.pass('Promises.delay should have rejected and its timeout should been cancelled'); | ||
t.equal(triggered, false, 'Promises.delay should NOT have triggered yet'); | ||
t.end(); | ||
@@ -496,13 +574,13 @@ } | ||
test('Promise.delay with cancellation of delay (mustResolve explicit false)', t => { | ||
test('Promises.delay with cancellation of delay (mustResolve explicit false)', t => { | ||
const cancellable = {}; | ||
Promise.delay(50, cancellable) | ||
Promises.delay(50, cancellable) | ||
.then( | ||
failed => { | ||
t.fail('Promise.delay should have rejected and its timeout should have cancelled'); | ||
() => { | ||
t.fail('Promises.delay should have rejected and its timeout should have cancelled'); | ||
t.end(); | ||
}, | ||
triggered => { | ||
t.pass('Promise.delay should have rejected and its timeout should have cancelled'); | ||
t.equal(triggered, false, 'Promise.delay should NOT have triggered yet'); | ||
t.pass('Promises.delay should have rejected and its timeout should have cancelled'); | ||
t.equal(triggered, false, 'Promises.delay should NOT have triggered yet'); | ||
t.end(); | ||
@@ -516,13 +594,13 @@ } | ||
test('Promise.delay with cancellation of delay (mustResolve true)', t => { | ||
test('Promises.delay with cancellation of delay (mustResolve true)', t => { | ||
const cancellable = {}; | ||
Promise.delay(50, cancellable) | ||
Promises.delay(50, cancellable) | ||
.then( | ||
triggered => { | ||
t.pass('Promise.delay should have resolved, but its timeout should NOT have triggered'); | ||
t.equal(triggered, false, 'Promise.delay should NOT have triggered yet'); | ||
t.pass('Promises.delay should have resolved, but its timeout should NOT have triggered'); | ||
t.equal(triggered, false, 'Promises.delay should NOT have triggered yet'); | ||
t.end(); | ||
}, | ||
failed => { | ||
t.fail('Promise.delay should NOT have rejected'); | ||
() => { | ||
t.fail('Promises.delay should NOT have rejected'); | ||
t.end(); | ||
@@ -537,50 +615,28 @@ } | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promise.isArrayOfPromises | ||
// Promises.allOrOne | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promise.isArrayOfPromises', t => { | ||
t.notOk(Promise.isArrayOfPromises(undefined), 'undefined is not a Promise[]'); | ||
t.notOk(Promise.isArrayOfPromises(null), 'null is not a Promise[]'); | ||
t.notOk(Promise.isArrayOfPromises([null]), '[null] is not a Promise[]'); | ||
t.notOk(Promise.isArrayOfPromises(new Error("Err")), 'An error is not a Promise[]'); | ||
t.notOk(Promise.isArrayOfPromises({}), 'An empty object is not a Promise[]'); | ||
t.notOk(Promise.isArrayOfPromises(new Promise((resolve, reject) => resolve)), 'A Promise is not a Promise[]'); | ||
t.notOk(Promise.isArrayOfPromises({then: () => console.log('Then-able')}), 'A then-able is not a Promise[]'); | ||
test('Promises.allOrOne', t => { | ||
t.ok(Promises.isPromise(Promises.allOrOne(new Error("Err"))), 'allOrOne(error) gives a promise'); | ||
t.ok(Promises.isPromise(Promises.allOrOne(undefined)), 'allOrOne(undefined) gives a promise'); | ||
t.ok(Promises.isPromise(Promises.allOrOne(null)), 'allOrOne(null) gives a promise'); | ||
t.ok(Promise.isArrayOfPromises([]), 'An empty array is a Promise[]'); | ||
t.ok(Promise.isArrayOfPromises([new Promise((resolve, reject) => resolve)]), 'A array with a Promise is a Promise[]'); | ||
t.ok(Promise.isArrayOfPromises([new Promise((resolve, reject) => resolve), new Promise((resolve, reject) => resolve)]), 'A array with 2 Promises is a Promise[]'); | ||
t.ok(Promise.isArrayOfPromises([{then: () => console.log('[Then-able]')}]), 'An array with a then-able "is" a Promise[]'); | ||
t.ok(Promise.isArrayOfPromises([{then: () => console.log('[Then-able]')}, {then: () => console.log('[Then-able2]')}]), 'An array with 2 then-ables "is" a Promise[]'); | ||
t.ok(Promise.isArrayOfPromises([{then: () => console.log('[Then-able]')}, new Promise((resolve, reject) => resolve)]), 'An array with a then-able & a promise "is" a Promise[]'); | ||
t.end(); | ||
}); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promise.allOrOne | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promise.allOrOne', t => { | ||
t.ok(Promise.isPromise(Promise.allOrOne(new Error("Err"))), 'allOrOne(error) gives a promise'); | ||
t.ok(Promise.isPromise(Promise.allOrOne(undefined)), 'allOrOne(undefined) gives a promise'); | ||
t.ok(Promise.isPromise(Promise.allOrOne(null)), 'allOrOne(null) gives a promise'); | ||
const thenable = {then: () => 'Then-able'}; | ||
t.ok(Promise.isPromise(Promise.allOrOne(thenable)), 'allOrOne(then-able) is a promise'); | ||
t.equal(Promise.allOrOne(thenable), thenable, 'allOrOne(then-able) gives same then-able'); | ||
t.ok(Promise.isPromise(Promise.allOrOne([thenable])), 'allOrOne([then-able]) is a promise'); | ||
t.ok(Promise.isPromise(Promise.allOrOne([thenable, thenable])), 'allOrOne([then-able,then-able]) is a promise'); | ||
t.ok(Promises.isPromise(Promises.allOrOne(thenable)), 'allOrOne(then-able) gives a promise'); | ||
//t.equal(Promises.allOrOne(thenable), thenable, 'allOrOne(then-able) gives same then-able'); | ||
t.notEqual(Promises.allOrOne(thenable), thenable, 'allOrOne(then-able) is NOT same then-able'); | ||
t.ok(Promises.isPromise(Promises.allOrOne([thenable])), 'allOrOne([then-able]) is a promise'); | ||
t.ok(Promises.isPromise(Promises.allOrOne([thenable, thenable])), 'allOrOne([then-able,then-able]) is a promise'); | ||
const promise = new Promise((resolve, reject) => resolve('Bob')); | ||
t.ok(Promise.isPromise(Promise.allOrOne(promise)), 'allOrOne(promise) gives a promise'); | ||
t.equal(Promise.allOrOne(promise), promise, 'allOrOne(promise) gives same promise'); | ||
t.ok(Promises.isPromise(Promises.allOrOne(promise)), 'allOrOne(promise) gives a promise'); | ||
t.equal(Promises.allOrOne(promise), promise, 'allOrOne(promise) gives same promise'); | ||
t.ok(Promise.isPromise(Promise.allOrOne([promise])), 'allOrOne([promise]) gives a promise'); | ||
t.notEqual(Promise.allOrOne([promise]), promise, 'allOrOne([promise]) does not give same promise'); | ||
t.ok(Promises.isPromise(Promises.allOrOne([promise])), 'allOrOne([promise]) gives a promise'); | ||
t.notEqual(Promises.allOrOne([promise]), promise, 'allOrOne([promise]) does not give same promise'); | ||
t.ok(Promise.isPromise(Promise.allOrOne([promise, promise])), 'allOrOne([promise, promise]) gives a promise'); | ||
t.ok(Promises.isPromise(Promises.allOrOne([promise, promise])), 'allOrOne([promise, promise]) gives a promise'); | ||
const promiseArray = [promise]; | ||
Promise.allOrOne(promiseArray) | ||
Promises.allOrOne(promiseArray) | ||
.then(ps => { | ||
@@ -596,82 +652,751 @@ promiseArray[0].then(p => { | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promise.every | ||
// Promises.every | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test('Promise.every', t => { | ||
t.plan(11); | ||
test("Promises.every()", t => { | ||
t.throws(() => Promises.every(), Error, `Promises.every() must throw a 'not an array' Error`); | ||
t.throws(() => Promises.every(undefined), Error, `Promises.every(undefined) must throw a 'not an array' Error`); | ||
t.throws(() => Promises.every(null), Error, `Promises.every(null) must throw a 'not an array' Error`); | ||
t.throws(() => Promises.every(123), Error, `Promises.every(123) must throw a 'not an array' Error`); | ||
t.throws(() => Promises.every(Promise.resolve(1)), Error, `Promises.every(Promise.resolve(1)) must throw a 'not an array' Error`); | ||
t.end(); | ||
}); | ||
// Empty array or undefined | ||
Promise.every([]) | ||
test('Promises.every([])', t => { | ||
Promises.every([]).then(results => { | ||
const expected = []; | ||
t.deepEqual(results, expected, `Promises.every([]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
}); | ||
test('Promises.every([undefined])', t => { | ||
Promises.every([undefined]).then(results => { | ||
const expected = [new Success(undefined)]; | ||
t.deepEqual(results, expected, `Promises.every([undefined]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
}); | ||
test('Promises.every([null])', t => { | ||
Promises.every([null]).then(results => { | ||
const expected = [new Success(null)]; | ||
t.deepEqual(results, expected, `Promises.every([null]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
}); | ||
test('Promises.every([Promise.resolve(null), undefined])', t => { | ||
Promises.every([Promise.resolve(null), undefined]).then(results => { | ||
const expected = [new Success(null), new Success(undefined)]; | ||
t.deepEqual(results, expected, `Promises.every([Promise.resolve(null), undefined]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.every([undefined, Promise.resolve(null)])", t => { | ||
Promises.every([undefined, Promise.resolve(null)]).then(results => { | ||
const expected = [new Success(undefined), new Success(null)]; | ||
t.deepEqual(results, expected, `Promises.every([undefined, Promise.resolve(null)]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.every([Promise.resolve(null), null])", t => { | ||
Promises.every([Promise.resolve(null), null]).then(results => { | ||
const expected = [new Success(null), new Success(null)]; | ||
t.deepEqual(results, expected, `Promises.every([Promise.resolve(null), null]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.every([undefined, null, Promise.resolve(null), 123, 'ABCDEF'])", t => { | ||
Promises.every([undefined, null, Promise.resolve(null), 123, 'ABCDEF']).then(results => { | ||
const expected = [new Success(undefined), new Success(null), new Success(null), new Success(123), new Success('ABCDEF')]; | ||
t.deepEqual(results, expected, `Promises.every([undefined, null, Promise.resolve(null), 123]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.every([p1])", t => { | ||
Promises.every([p1]).then(results => { | ||
const expected = [new Success('p1')]; | ||
t.deepEqual(results, expected, `Promises.every([p1]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.every([p1, p2])", t => { | ||
Promises.every([p1, p2]) | ||
.then(results => { | ||
t.deepEqual(results, [], 'Promise.every([]) must be []'); | ||
const expected = [new Success('p1'), new Failure(p2Error)]; | ||
t.deepEqual(results, expected, `Promises.every([p1,p2]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
Promise.every(undefined) | ||
}); | ||
test("Promises.every([p1, p2, p3])", t => { | ||
Promises.every([p1, p2, p3]) | ||
.then(results => { | ||
t.deepEqual(results, [], 'Promise.every(undefined) must be []'); | ||
const expected = [new Success('p1'), new Failure(p2Error), new Success('p3')]; | ||
t.deepEqual(results, expected, `Promises.every([p1,p2,p3]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
Promise.every(null) | ||
}); | ||
test("Promises.every([p1, p2, p3, p4])", t => { | ||
Promises.every([p1, p2, p3, p4]) | ||
.then(results => { | ||
t.deepEqual(results, [], 'Promise.every(null) must be []'); | ||
const expected = [new Success('p1'), new Failure(p2Error), new Success('p3'), new Failure(p4Error),]; | ||
t.deepEqual(results, expected, `Promises.every([p1,p2,p3,p4]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
}); | ||
// Single Promise | ||
const p1 = Promise.resolve('p1'); | ||
const expected = [{result: 'p1'}]; | ||
Promise.every(p1) | ||
test("Promises.every with [Infinity, p4, 4.5, p3, '3.5', p2, null, p1, undefined, p2, {a:1}]", t => { | ||
// Reversed with duplicate and with simple values | ||
Promises.every([Infinity, p4, 4.5, p3, '3.5', p2, null, p1, undefined, p2, {a: 1}]) | ||
.then(results => { | ||
t.deepEqual(results, expected, `Promise.every(p1) must be ${stringify(expected)}`); | ||
const expected = [new Success(Infinity), new Failure(p4Error), new Success(4.5), new Success('p3'), new Success('3.5'), new Failure(p2Error), | ||
new Success(null), new Success('p1'), new Success(undefined), new Failure(p2Error), new Success({a: 1})]; | ||
t.deepEqual(results, expected, `Promises.every([Infinity, p4, 4.5, p3, '3.5', p2, null, p1, undefined, p2, {a:1}]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
}); | ||
Promise.every([p1]) | ||
.then(results => { | ||
t.deepEqual(results, expected, `Promise.every([p1]) must be ${stringify(expected)}`); | ||
test('Promises.every([1, 2, 3])', t => { | ||
Promises.every([1, 2, 3]).then(results => { | ||
const expected = [new Success(1), new Success(2), new Success(3)]; | ||
t.deepEqual(results, expected, `Promises.every([1, 2, 3]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.every([1, '2', 3])", t => { | ||
Promises.every([1, '2', 3]).then(results => { | ||
const expected = [new Success(1), new Success("2"), new Success(3)]; | ||
t.deepEqual(results, expected, `Promises.every([1, '2', 3]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
}); | ||
// ===================================================================================================================== | ||
// Promises.every with cancellations | ||
// ===================================================================================================================== | ||
test("Promises.every([d1,d2,d3,d4]) cancelled immediately (i.e. before d1, d2, d3 & d4 resolve) will resolve only d1", t => { | ||
const cancellable = {}; | ||
const d1Cancellable = {}; | ||
const d1 = genDelayedPromise(null, 'd1', 10, d1Cancellable); | ||
t.ok(typeof d1Cancellable.cancelTimeout === "function", `d1Cancellable.cancelTimeout must be installed`); | ||
const d2Cancellable = {}; | ||
const d2 = genDelayedPromise(d2Error, 'd2', 2000, d2Cancellable); | ||
t.ok(typeof d2Cancellable.cancelTimeout === "function", `d2Cancellable.cancelTimeout must be installed`); | ||
const d3Cancellable = {}; | ||
const d3 = genDelayedPromise(null, 'd3', 3000, d3Cancellable); | ||
t.ok(typeof d3Cancellable.cancelTimeout === "function", `d3Cancellable.cancelTimeout must be installed`); | ||
const d4Cancellable = {}; | ||
const d4 = genDelayedPromise(d4Error, 'd4', 4000, d4Cancellable); | ||
t.ok(typeof d4Cancellable.cancelTimeout === "function", `d4Cancellable.cancelTimeout must be installed`); | ||
Promises.every([d1, d2, d3, d4], cancellable).then( | ||
results => { | ||
t.end(`Promises.every([d1,d2,d3,d4]) when cancelled, must NOT complete successfully with results: ${stringify(results)}`); | ||
}, | ||
err => { | ||
t.pass(`Promises.every([d1,d2,d3,d4]) when cancelled must reject with an error`); | ||
// Cancel the d2, d3 & d4 delays too (just for clean-up) | ||
t.ok(d1Cancellable.cancelTimeout(true), `d1Cancellable.cancelTimeout() should have timed-out`); | ||
t.notOk(d2Cancellable.cancelTimeout(true), `d2Cancellable.cancelTimeout() should NOT have timed-out yet`); | ||
t.notOk(d3Cancellable.cancelTimeout(true), `d3Cancellable.cancelTimeout() should NOT have timed-out yet`); | ||
t.notOk(d4Cancellable.cancelTimeout(true), `d4Cancellable.cancelTimeout() should NOT have timed-out yet`); | ||
t.ok(err instanceof CancelledError, `Promises.every([d1,d2,d3,d4]) rejected error ${stringify(err)} must be instanceof CancelledError`); | ||
t.notOk(err.completed, `CancelledError.completed must be false`); | ||
const expectedResolvedOutcomes = [new Success('d1')]; | ||
t.deepEqual(err.resolvedOutcomes, expectedResolvedOutcomes, `Promises.every([d1,d2,d3,d4]) resolvedOutcomes must be ${stringify(expectedResolvedOutcomes)}`); | ||
const expectedUnresolvedPromises = [d2, d3, d4]; | ||
t.deepEqual(err.unresolvedPromises, expectedUnresolvedPromises, `Promises.every([d1,d2,d3,d4]) unresolvedPromises must be ${stringify(expectedUnresolvedPromises)}`); | ||
t.end(); | ||
} | ||
); | ||
t.ok(typeof cancellable.cancel === "function", `cancellable.cancel must be installed`); | ||
const completed = cancellable.cancel(); | ||
t.notOk(completed, `Promises.every([d1,d2,d3,d4]) must not be completed yet`); | ||
}); | ||
test("Promises.every([d1,d2,d3,d4]) cancelled during d1 (i.e. before d2, d3 & d4 resolve) will resolve only d1", t => { | ||
const cancellable = {}; | ||
const d1Cancellable = {}; | ||
const d1 = genDelayedPromise(null, 'd1', 10, d1Cancellable, cancellable); | ||
t.ok(typeof d1Cancellable.cancelTimeout === "function", `d1Cancellable.cancelTimeout must be installed`); | ||
const d2Cancellable = {}; | ||
const d2 = genDelayedPromise(d2Error, 'd2', 2000, d2Cancellable); | ||
t.ok(typeof d2Cancellable.cancelTimeout === "function", `d2Cancellable.cancelTimeout must be installed`); | ||
const d3Cancellable = {}; | ||
const d3 = genDelayedPromise(null, 'd3', 3000, d3Cancellable); | ||
t.ok(typeof d3Cancellable.cancelTimeout === "function", `d3Cancellable.cancelTimeout must be installed`); | ||
const d4Cancellable = {}; | ||
const d4 = genDelayedPromise(d4Error, 'd4', 4000, d4Cancellable); | ||
t.ok(typeof d4Cancellable.cancelTimeout === "function", `d4Cancellable.cancelTimeout must be installed`); | ||
Promises.every([d1, d2, d3, d4], cancellable).then( | ||
results => { | ||
t.end(`Promises.every([d1,d2,d3,d4]) when cancelled, must NOT complete successfully with results: ${stringify(results)}`); | ||
}, | ||
err => { | ||
t.pass(`Promises.every([d1,d2,d3,d4]) when cancelled must reject with an error`); | ||
// Cancel the d2, d3 & d4 delays too (just for clean-up) | ||
t.ok(d1Cancellable.cancelTimeout(true), `d1Cancellable.cancelTimeout() should have timed-out`); | ||
t.notOk(d2Cancellable.cancelTimeout(true), `d2Cancellable.cancelTimeout() should NOT have timed-out yet`); | ||
t.notOk(d3Cancellable.cancelTimeout(true), `d3Cancellable.cancelTimeout() should NOT have timed-out yet`); | ||
t.notOk(d4Cancellable.cancelTimeout(true), `d4Cancellable.cancelTimeout() should NOT have timed-out yet`); | ||
t.ok(err instanceof CancelledError, `Promises.every([d1,d2,d3,d4]) rejected error ${stringify(err)} must be instanceof CancelledError`); | ||
t.notOk(err.completed, `CancelledError.completed must be false`); | ||
const expectedResolvedOutcomes = [new Success('d1')]; | ||
t.deepEqual(err.resolvedOutcomes, expectedResolvedOutcomes, `Promises.every([d1,d2,d3,d4]) resolvedOutcomes must be ${stringify(expectedResolvedOutcomes)}`); | ||
const expectedUnresolvedPromises = [d2, d3, d4]; | ||
t.deepEqual(err.unresolvedPromises, expectedUnresolvedPromises, `Promises.every([d1,d2,d3,d4]) unresolvedPromises must be ${stringify(expectedUnresolvedPromises)}`); | ||
t.end(); | ||
} | ||
); | ||
t.ok(typeof cancellable.cancel === "function", `cancellable.cancel must be installed`); | ||
}); | ||
test("Promises.every([d1,d2,d3,d4]) cancelled during d2 (i.e. before d3 & d4 resolve) will resolve only d1 & d2", t => { | ||
const cancellable = {}; | ||
const d1Cancellable = {}; | ||
const d1 = genDelayedPromise(null, 'd1', 10, d1Cancellable); | ||
t.ok(typeof d1Cancellable.cancelTimeout === "function", `d1Cancellable.cancelTimeout must be installed`); | ||
const d2Cancellable = {}; | ||
const d2 = genDelayedPromise(d2Error, 'd2', 20, d2Cancellable, cancellable); | ||
t.ok(typeof d2Cancellable.cancelTimeout === "function", `d2Cancellable.cancelTimeout must be installed`); | ||
const d3Cancellable = {}; | ||
const d3 = genDelayedPromise(null, 'd3', 3000, d3Cancellable); | ||
t.ok(typeof d3Cancellable.cancelTimeout === "function", `d3Cancellable.cancelTimeout must be installed`); | ||
const d4Cancellable = {}; | ||
const d4 = genDelayedPromise(d4Error, 'd4', 4000, d4Cancellable); | ||
t.ok(typeof d4Cancellable.cancelTimeout === "function", `d4Cancellable.cancelTimeout must be installed`); | ||
Promises.every([d1, d2, d3, d4], cancellable).then( | ||
results => { | ||
t.end(`Promises.every([d1,d2,d3,d4]) when cancelled, must NOT complete successfully with results: ${stringify(results)}`); | ||
}, | ||
err => { | ||
t.pass(`Promises.every([d1,d2,d3,d4]) when cancelled must reject with an error`); | ||
// Cancel the d2, d3 & d4 delays too (just for clean-up) | ||
t.ok(d1Cancellable.cancelTimeout(true), `d1Cancellable.cancelTimeout() should have timed-out`); | ||
t.ok(d2Cancellable.cancelTimeout(true), `d2Cancellable.cancelTimeout() should have timed-out`); | ||
t.notOk(d3Cancellable.cancelTimeout(true), `d3Cancellable.cancelTimeout() should NOT have timed-out yet`); | ||
t.notOk(d4Cancellable.cancelTimeout(true), `d4Cancellable.cancelTimeout() should NOT have timed-out yet`); | ||
t.ok(err instanceof CancelledError, `Promises.every([d1,d2,d3,d4]) rejected error ${stringify(err)} must be instanceof CancelledError`); | ||
t.notOk(err.completed, `CancelledError.completed must be false`); | ||
const expectedResolvedOutcomes = [new Success('d1'), new Failure(d2Error)]; | ||
t.deepEqual(err.resolvedOutcomes, expectedResolvedOutcomes, `Promises.every([d1,d2,d3,d4]) resolvedOutcomes must be ${stringify(expectedResolvedOutcomes)}`); | ||
const expectedUnresolvedPromises = [d3, d4]; | ||
t.deepEqual(err.unresolvedPromises, expectedUnresolvedPromises, `Promises.every([d1,d2,d3,d4]) unresolvedPromises must be ${stringify(expectedUnresolvedPromises)}`); | ||
t.end(); | ||
} | ||
); | ||
t.ok(typeof cancellable.cancel === "function", `cancellable.cancel must be installed`); | ||
}); | ||
test("Promises.every([d1,d2,d3,d4]) cancelled during d3 (i.e. before d4 completes) will resolve d1, d2 & d3", t => { | ||
const cancellable = {}; | ||
const d1Cancellable = {}; | ||
const d1 = genDelayedPromise(null, 'd1', 10, d1Cancellable); | ||
t.ok(typeof d1Cancellable.cancelTimeout === "function", `d1Cancellable.cancelTimeout must be installed`); | ||
const d2Cancellable = {}; | ||
const d2 = genDelayedPromise(d2Error, 'd2', 20, d2Cancellable); | ||
t.ok(typeof d2Cancellable.cancelTimeout === "function", `d2Cancellable.cancelTimeout must be installed`); | ||
const d3Cancellable = {}; | ||
const d3 = genDelayedPromise(null, 'd3', 30, d3Cancellable, cancellable); | ||
t.ok(typeof d3Cancellable.cancelTimeout === "function", `d3Cancellable.cancelTimeout must be installed`); | ||
const d4Cancellable = {}; | ||
const d4 = genDelayedPromise(d4Error, 'd4', 4000, d4Cancellable); | ||
t.ok(typeof d4Cancellable.cancelTimeout === "function", `d4Cancellable.cancelTimeout must be installed`); | ||
Promises.every([d1, d2, d3, d4], cancellable).then( | ||
results => { | ||
t.end(`Promises.every([d1,d2,d3,d4]) when cancelled, must NOT complete successfully with results: ${stringify(results)}`); | ||
}, | ||
err => { | ||
t.pass(`Promises.every([d1,d2,d3,d4]) when cancelled must reject with an error`); | ||
// Cancel the d2, d3 & d4 delays too (just for clean-up) | ||
t.ok(d1Cancellable.cancelTimeout(true), `d1Cancellable.cancelTimeout() should have timed-out`); | ||
t.ok(d2Cancellable.cancelTimeout(true), `d2Cancellable.cancelTimeout() should have timed-out`); | ||
t.ok(d3Cancellable.cancelTimeout(true), `d3Cancellable.cancelTimeout() should have timed-out`); | ||
t.notOk(d4Cancellable.cancelTimeout(true), `d4Cancellable.cancelTimeout() should NOT have timed-out yet`); | ||
t.ok(err instanceof CancelledError, `Promises.every([d1,d2,d3,d4]) rejected error ${stringify(err)} must be instanceof CancelledError`); | ||
t.notOk(err.completed, `CancelledError.completed must be false`); | ||
const expectedResolvedOutcomes = [new Success('d1'), new Failure(d2Error), new Success('d3')]; | ||
t.deepEqual(err.resolvedOutcomes, expectedResolvedOutcomes, `Promises.every([d1,d2,d3,d4]) resolvedOutcomes must be ${stringify(expectedResolvedOutcomes)}`); | ||
const expectedUnresolvedPromises = [d4]; | ||
t.deepEqual(err.unresolvedPromises, expectedUnresolvedPromises, `Promises.every([d1,d2,d3,d4]) unresolvedPromises must be ${stringify(expectedUnresolvedPromises)}`); | ||
t.end(); | ||
} | ||
); | ||
t.ok(typeof cancellable.cancel === "function", `cancellable.cancel must be installed`); | ||
}); | ||
test("Promises.every([d1,d2,d3,d4]) cancelled during d4 will resolve d1, d2, d3 & d4", t => { | ||
const cancellable = {}; | ||
const d1Cancellable = {}; | ||
const d1 = genDelayedPromise(null, 'd1', 10, d1Cancellable); | ||
t.ok(typeof d1Cancellable.cancelTimeout === "function", `d1Cancellable.cancelTimeout must be installed`); | ||
const d2Cancellable = {}; | ||
const d2 = genDelayedPromise(d2Error, 'd2', 20, d2Cancellable); | ||
t.ok(typeof d2Cancellable.cancelTimeout === "function", `d2Cancellable.cancelTimeout must be installed`); | ||
const d3Cancellable = {}; | ||
const d3 = genDelayedPromise(null, 'd3', 30, d3Cancellable); | ||
t.ok(typeof d3Cancellable.cancelTimeout === "function", `d3Cancellable.cancelTimeout must be installed`); | ||
const d4Cancellable = {}; | ||
const d4 = genDelayedPromise(d4Error, 'd4', 40, d4Cancellable, cancellable); | ||
t.ok(typeof d4Cancellable.cancelTimeout === "function", `d4Cancellable.cancelTimeout must be installed`); | ||
Promises.every([d1, d2, d3, d4], cancellable).then( | ||
outcomes => { | ||
t.pass(`Promises.every([d1,d2,d3,d4]) when cancelled too late must resolve with outcomes`); | ||
// Cancel the d2, d3 & d4 delays too (just for clean-up) | ||
t.ok(d1Cancellable.cancelTimeout(true), `d1Cancellable.cancelTimeout() should have timed-out`); | ||
t.ok(d2Cancellable.cancelTimeout(true), `d2Cancellable.cancelTimeout() should have timed-out`); | ||
t.ok(d3Cancellable.cancelTimeout(true), `d3Cancellable.cancelTimeout() should have timed-out`); | ||
t.ok(d4Cancellable.cancelTimeout(true), `d4Cancellable.cancelTimeout() should have timed-out`); | ||
const expectedOutcomes = [new Success('d1'), new Failure(d2Error), new Success('d3'), new Failure(d4Error)]; | ||
t.deepEqual(outcomes, expectedOutcomes, `Promises.every([d1,d2,d3,d4]) outcomes must be ${stringify(expectedOutcomes)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.end(`Promises.every([d1,d2,d3,d4]) when cancelled too late, must NOT reject with error`, err.stack); | ||
} | ||
); | ||
t.ok(typeof cancellable.cancel === "function", `cancellable.cancel must be installed`); | ||
}); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promises.one | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test("Promises.one(undefined)", t => { | ||
const p = Promises.one(undefined); | ||
t.ok(p instanceof Promise, `Promises.one(undefined) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = new Success(undefined); | ||
t.deepEqual(results, expected, `Promises.one(undefined) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.one(undefined) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
// Multiple Promises | ||
const p2Error = new Error('p2 error'); | ||
const p2 = Promise.reject(p2Error); | ||
const p3 = Promise.resolve('p3'); | ||
test("Promises.one(null)", t => { | ||
const p = Promises.one(null); | ||
t.ok(p instanceof Promise, `Promises.one(null) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = new Success(null); | ||
t.deepEqual(results, expected, `Promises.one(null) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.one(null) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
const p4Error = new Error('p4 error'); | ||
const p4 = Promise.reject(p4Error); | ||
test("Promises.one(1)", t => { | ||
const p = Promises.one(1); | ||
t.ok(p instanceof Promise, `Promises.one(1) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = new Success(1); | ||
t.deepEqual(results, expected, `Promises.one(1) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.one(1) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
// Promises as arguments | ||
Promise.every(p1, p2) | ||
.then(results => { | ||
const expected = [{result: 'p1'}, {error: p2Error}]; | ||
t.deepEqual(results, expected, `Promise.every(p1,p2) must be ${stringify(expected)}`); | ||
test("Promises.one({})", t => { | ||
const p = Promises.one({}); | ||
t.ok(p instanceof Promise, `Promises.one({}) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = new Success({}); | ||
t.deepEqual(results, expected, `Promises.one({}) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.one({}) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
Promise.every(p1, p2, p3) | ||
.then(results => { | ||
const expected = [{result: 'p1'}, {error: p2Error}, {result: 'p3'}]; | ||
t.deepEqual(results, expected, `Promise.every(p1,p2,p3) must be ${stringify(expected)}`); | ||
test("Promises.one([])", t => { | ||
const p = Promises.one([]); | ||
t.ok(p instanceof Promise, `Promises.one([]) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = new Success([]); | ||
t.deepEqual(results, expected, `Promises.one([]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.one([]) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
Promise.every(p1, p2, p3, p4) | ||
.then(results => { | ||
const expected = [{result: 'p1'}, {error: p2Error}, {result: 'p3'}, {error: p4Error},]; | ||
t.deepEqual(results, expected, `Promise.every(p1,p2,p3,p4) must be ${stringify(expected)}`); | ||
test("Promises.one({a:1})", t => { | ||
const p = Promises.one({a: 1}); | ||
t.ok(p instanceof Promise, `Promises.one({a:1}) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = new Success({a: 1}); | ||
t.deepEqual(results, expected, `Promises.one({a:1}) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.one({a:1}) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
// Promises in arrays | ||
Promise.every([p1, p2]) | ||
.then(results => { | ||
const expected = [{result: 'p1'}, {error: p2Error}]; | ||
t.deepEqual(results, expected, `Promise.every([p1,p2]) must be ${stringify(expected)}`); | ||
test("Promises.one([1,'2',3])", t => { | ||
const p = Promises.one([1, '2', 3]); | ||
t.ok(p instanceof Promise, `Promises.one([1,'2',3]) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = new Success([1, '2', 3]); | ||
t.deepEqual(results, expected, `Promises.one([1,'2',3]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.one([1,'2',3]) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
Promise.every([p1, p2, p3]) | ||
.then(results => { | ||
const expected = [{result: 'p1'}, {error: p2Error}, {result: 'p3'}]; | ||
t.deepEqual(results, expected, `Promise.every([p1,p2,p3]) must be ${stringify(expected)}`); | ||
test("Promises.one([p1])", t => { | ||
const p = Promises.one([p1]); | ||
t.ok(p instanceof Promise, `Promises.one([p1]) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = new Success([p1]); | ||
t.deepEqual(results, expected, `Promises.one([p1]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.one(p1) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
Promise.every([p1, p2, p3, p4]) | ||
.then(results => { | ||
const expected = [{result: 'p1'}, {error: p2Error}, {result: 'p3'}, {error: p4Error},]; | ||
t.deepEqual(results, expected, `Promise.every([p1,p2,p3,p4]) must be ${stringify(expected)}`); | ||
test("Promises.one(p1)", t => { | ||
const p = Promises.one(p1); | ||
t.ok(p instanceof Promise, `Promises.one(p1) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = new Success('p1'); | ||
t.deepEqual(results, expected, `Promises.one(p1) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.one(p1) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.one(p2)", t => { | ||
const p = Promises.one(p2); | ||
t.ok(p instanceof Promise, `Promises.one(p2) must be a Promise`); | ||
p.then( | ||
resolution => { | ||
const expected = new Failure(p2Error); | ||
t.deepEqual(resolution, expected, `Promises.one(p2) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.one(p2) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.one(t1) with 'then-able' t1 that returns a result", t => { | ||
const p = Promises.one(t1); | ||
t.ok(p instanceof Promise, `Promises.one(t1) must be a Promise`); | ||
t.notEqual(p, t1, `Promises.one(t1) must NOT be t1`); | ||
p.then( | ||
resolution => { | ||
const expected = new Success('t1'); | ||
t.deepEqual(resolution, expected, `Promises.one(t1) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.one(t1) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.one(t2) with 'then-able' t2 that rejects with an error", t => { | ||
const p = Promises.one(t2); | ||
t.notEqual(p, t2, `Promises.one(t2) must NOT be t2`); | ||
t.ok(p instanceof Promise, `Promises.one(t2) must be a Promise`); | ||
p.then( | ||
resolution => { | ||
const expected = new Failure(t2Error); | ||
t.deepEqual(resolution, expected, `Promises.one(t2) must reject with ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.one(t2) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.one(t3) with 'then-able' t3 that throws an error synchronously", t => { | ||
const p = Promises.one(t3); | ||
t.ok(p instanceof Promise, `Promises.one(t3) must be a Promise`); | ||
t.notEqual(p, t3, `Promises.one(t3) must NOT be t3`); | ||
p.then( | ||
resolution => { | ||
const expected = new Failure(t3Error); | ||
t.deepEqual(resolution, expected, `Promises.one(t3) must reject with ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.one(t3) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
// Promises.toPromise | ||
// --------------------------------------------------------------------------------------------------------------------- | ||
test("Promises.toPromise(undefined)", t => { | ||
const p = Promises.toPromise(undefined); | ||
t.ok(p instanceof Promise, `Promises.toPromise(undefined) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = undefined; | ||
t.deepEqual(results, expected, `Promises.toPromise(undefined) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.toPromise(undefined) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.toPromise(null)", t => { | ||
const p = Promises.toPromise(null); | ||
t.ok(p instanceof Promise, `Promises.toPromise(null) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = null; | ||
t.deepEqual(results, expected, `Promises.toPromise(null) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.toPromise(null) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.toPromise(1)", t => { | ||
const p = Promises.toPromise(1); | ||
t.ok(p instanceof Promise, `Promises.toPromise(1) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = 1; | ||
t.deepEqual(results, expected, `Promises.toPromise(1) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.toPromise(1) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.toPromise({})", t => { | ||
const p = Promises.toPromise({}); | ||
t.ok(p instanceof Promise, `Promises.toPromise({}) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = {}; | ||
t.deepEqual(results, expected, `Promises.toPromise({}) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.toPromise({}) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.toPromise([])", t => { | ||
const p = Promises.toPromise([]); | ||
t.ok(p instanceof Promise, `Promises.toPromise([]) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = []; | ||
t.deepEqual(results, expected, `Promises.toPromise([]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.toPromise([]) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.toPromise({a:1})", t => { | ||
const p = Promises.toPromise({a: 1}); | ||
t.ok(p instanceof Promise, `Promises.toPromise({a: 1}) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = {a: 1}; | ||
t.deepEqual(results, expected, `Promises.toPromise({a:1}) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.toPromise({a:1}) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.toPromise([1,'2',3])", t => { | ||
const p = Promises.toPromise([1, '2', 3]); | ||
t.ok(p instanceof Promise, `Promises.toPromise([1,'2',3]) must be a Promise`); | ||
p.then( | ||
results => { | ||
const expected = [1, '2', 3]; | ||
t.deepEqual(results, expected, `Promises.toPromise([1,'2',3]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.toPromise([1,'2',3]) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.toPromise([p1])", t => { | ||
const p = Promises.toPromise([p1]); | ||
t.ok(p instanceof Promise, `Promises.toPromise([p1]) must be a Promise`); | ||
t.notEqual(p, p1, `Promises.toPromise([p1]) must NOT be p1`); | ||
p.then( | ||
results => { | ||
const expected = [p1]; | ||
t.deepEqual(results, expected, `Promises.toPromise([p1]) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.toPromise([p1]) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.toPromise(p1)", t => { | ||
const p = Promises.toPromise(p1); | ||
t.ok(p instanceof Promise, `Promises.toPromise(p1) must be a Promise`); | ||
t.equal(p, p1, `Promises.toPromise(p1) must be p1`); | ||
p.then( | ||
results => { | ||
const expected = 'p1'; | ||
t.deepEqual(results, expected, `Promises.toPromise(p1) must be ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.toPromise(p1) must NOT fail`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.toPromise(p2)", t => { | ||
const p = Promises.toPromise(p2); | ||
t.ok(p instanceof Promise, `Promises.toPromise(p2) must be a Promise`); | ||
t.equal(p, p2, `Promises.toPromise(p2) must be p2`); | ||
p.then( | ||
result => { | ||
t.fail(`Promises.toPromise(p2) must NOT pass with result (${stringify(result)})`); | ||
t.end(); | ||
}, | ||
err => { | ||
const expected = p2Error; | ||
t.deepEqual(err, expected, `Promises.toPromise(p2) must be ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.toPromise(t1) with 'then-able' t1 that resolves with a result", t => { | ||
const p = Promises.toPromise(t1); | ||
t.ok(p instanceof Promise, `Promises.toPromise(t1) must be a Promise`); | ||
t.notEqual(p, t1, `Promises.toPromise(t1) must NOT be t1`); | ||
p.then( | ||
results => { | ||
const expected = 't1'; | ||
t.deepEqual(results, expected, `Promises.toPromise(t1) must resolve with ${stringify(expected)}`); | ||
t.end(); | ||
}, | ||
err => { | ||
t.fail(`Promises.toPromise(t1) must NOT reject with ${stringify(err)}`, err.stack); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.toPromise(t2) with 'then-able' t2 that rejects with an error", t => { | ||
const p = Promises.toPromise(t2); | ||
t.ok(p instanceof Promise, `Promises.toPromise(t2) must be a Promise`); | ||
t.notEqual(p, t2, `Promises.toPromise(t2) must NOT be t2`); | ||
p.then( | ||
result => { | ||
t.fail(`Promises.toPromise(t2) must NOT resolve with result (${stringify(result)})`); | ||
t.end(); | ||
}, | ||
err => { | ||
const expected = t2Error; | ||
t.equal(err, expected, `Promises.toPromise(t2) must reject with ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
}); | ||
test("Promises.toPromise(t3) with 'then-able' t3 that throws an error synchronously", t => { | ||
const p = Promises.toPromise(t3); | ||
t.ok(p instanceof Promise, `Promises.toPromise(t3) must be a Promise`); | ||
t.notEqual(p, t3, `Promises.toPromise(t3) must NOT be t3`); | ||
p.then( | ||
result => { | ||
t.fail(`Promises.toPromise(t3) must NOT resolve with result (${stringify(result)})`); | ||
t.end(); | ||
}, | ||
err => { | ||
const expected = t3Error; | ||
t.equal(err, expected, `Promises.toPromise(t3) must reject with ${stringify(expected)}`); | ||
t.end(); | ||
}); | ||
}); | ||
@@ -12,11 +12,4 @@ 'use strict'; | ||
const testing = require('./testing'); | ||
// const okNotOk = testing.okNotOk; | ||
const checkOkNotOk = testing.checkOkNotOk; | ||
// const checkMethodOkNotOk = testing.checkMethodOkNotOk; | ||
// const equal = testing.equal; | ||
const checkEqual = testing.checkEqual; | ||
// const checkMethodEqual = testing.checkMethodEqual; | ||
function wrap(value, wrapInString) { | ||
//noinspection JSPrimitiveTypeWrapperUsage | ||
return wrapInString && !(value instanceof String) ? new String(value) : value; | ||
@@ -27,3 +20,4 @@ } | ||
const wrapped = wrap(value, wrapInString); | ||
return wrapInString || value instanceof String ? `String(${Strings.stringify(value, true, false, true)}) = [${Strings.stringify(wrapped ? wrapped.valueOf() : value, true, false, true)}] ` : ''; | ||
const opts = {quoteStrings: true}; | ||
return wrapInString || value instanceof String ? `String(${Strings.stringify(value, opts)}) = [${Strings.stringify(wrapped ? wrapped.valueOf() : value, opts)}] ` : ''; | ||
} | ||
@@ -33,4 +27,3 @@ | ||
function check(value, expected) { | ||
return checkOkNotOk(t, Strings.isString, [wrap(value, wrapInString)], expected, ' is a string', ' is NOT a string', | ||
toPrefix(value, wrapInString)); | ||
return t.equal(!!Strings.isString(wrap(value, wrapInString)), !!expected, `Strings.isString(${toPrefix(value, wrapInString)}) is ${expected ? '' : 'NOT '}a string`); | ||
} | ||
@@ -98,4 +91,3 @@ | ||
function check(value, expected) { | ||
return checkOkNotOk(t, Strings.isBlank, [wrap(value, wrapInString)], expected, ' is blank', ' is NOT blank', | ||
toPrefix(value, wrapInString)); | ||
return t.equal(!!Strings.isBlank(wrap(value, wrapInString)), !!expected, `Strings.isBlank(${toPrefix(value, wrapInString)}) is ${expected ? '' : 'NOT '}blank`); | ||
} | ||
@@ -174,4 +166,3 @@ | ||
function check(value, expected) { | ||
return checkOkNotOk(t, Strings.isNotBlank, [wrap(value, wrapInString)], expected, ' is not blank', ' is blank', | ||
toPrefix(value, wrapInString)); | ||
return t.equal(!!Strings.isNotBlank(wrap(value, wrapInString)), !!expected, `Strings.isNotBlank(${toPrefix(value, wrapInString)}) is ${expected ? 'NOT ' : ''}blank`); | ||
} | ||
@@ -249,85 +240,93 @@ | ||
function checkStringify(t, wrapInString) { | ||
function check(value, expected) { | ||
return checkEqual(t, Strings.stringify, [wrap(value, wrapInString)], expected, toPrefix(value, wrapInString)); | ||
function checkNoOpts(value, expected) { | ||
return t.equal(Strings.stringify(wrap(value, wrapInString)), expected, `Strings.stringify(${toPrefix(value, wrapInString)} must be ${expected}`); | ||
} | ||
function checkWithArgs(value, useToStringForErrors, avoidToJSONMethods, quoteStrings, expected) { | ||
return checkEqual(t, Strings.stringify, [wrap(value, wrapInString), useToStringForErrors, avoidToJSONMethods, quoteStrings], expected, toPrefix(value, wrapInString)); | ||
function checkWithOpts(value, opts, expected) { | ||
return t.equal(Strings.stringify(wrap(value, wrapInString), opts), expected, `Strings.stringify(${toPrefix(value, wrapInString)} must be ${expected}`); | ||
} | ||
// undefined | ||
check(undefined, 'undefined'); | ||
checkNoOpts(undefined, 'undefined'); | ||
// null | ||
check(null, 'null'); | ||
checkNoOpts(null, 'null'); | ||
// objects | ||
check({}, wrapInString ? '[object Object]' : '{}'); | ||
check({a: 1, b: 2}, wrapInString ? '[object Object]' : JSON.stringify({a: 1, b: 2})); | ||
check({a: 1, b: 2, o: {c: 'C'}}, wrapInString ? '[object Object]' : JSON.stringify({a: 1, b: 2, o: {c: 'C'}})); | ||
checkNoOpts({}, wrapInString ? '[object Object]' : '{}'); | ||
checkNoOpts({a: 1, b: 2}, wrapInString ? '[object Object]' : JSON.stringify({a: 1, b: 2})); | ||
checkNoOpts({a: 1, b: 2, o: {c: 'C'}}, wrapInString ? '[object Object]' : JSON.stringify({a: 1, b: 2, o: {c: 'C'}})); | ||
// booleans | ||
check(true, 'true'); | ||
check(false, 'false'); | ||
checkNoOpts(true, 'true'); | ||
checkNoOpts(false, 'false'); | ||
// arrays | ||
check([], wrapInString ? '' : '[]'); | ||
check([1, 2, 3], wrapInString ? '1,2,3' : '[1, 2, 3]'); | ||
checkNoOpts([], wrapInString ? '' : '[]'); | ||
checkNoOpts([1, 2, 3], wrapInString ? '1,2,3' : '[1, 2, 3]'); | ||
// special case numbers | ||
check(Number.POSITIVE_INFINITY, `${Number.POSITIVE_INFINITY}`); | ||
check(Number.NEGATIVE_INFINITY, `${Number.NEGATIVE_INFINITY}`); | ||
check(NaN, 'NaN'); | ||
checkNoOpts(Number.POSITIVE_INFINITY, `${Number.POSITIVE_INFINITY}`); | ||
checkNoOpts(Number.NEGATIVE_INFINITY, `${Number.NEGATIVE_INFINITY}`); | ||
checkNoOpts(NaN, 'NaN'); | ||
// negative numbers | ||
check(Number.MIN_VALUE, `${Number.MIN_VALUE}`); | ||
check(Number.MIN_SAFE_INTEGER, `${Number.MIN_SAFE_INTEGER}`); | ||
check(-123.456, '-123.456'); | ||
check(-1, '-1'); | ||
checkNoOpts(Number.MIN_VALUE, `${Number.MIN_VALUE}`); | ||
checkNoOpts(Number.MIN_SAFE_INTEGER, `${Number.MIN_SAFE_INTEGER}`); | ||
checkNoOpts(-123.456, '-123.456'); | ||
checkNoOpts(-1, '-1'); | ||
// zero | ||
check(0, '0'); // not sure if this should return blank for 0 | ||
checkNoOpts(0, '0'); // not sure if this should return blank for 0 | ||
// positive numbers | ||
check(1, '1'); | ||
check(123.456, '123.456'); | ||
check(Number.MAX_SAFE_INTEGER, `${Number.MAX_SAFE_INTEGER}`); | ||
check(Number.MAX_VALUE, `${Number.MAX_VALUE}`); | ||
checkNoOpts(1, '1'); | ||
checkNoOpts(123.456, '123.456'); | ||
checkNoOpts(Number.MAX_SAFE_INTEGER, `${Number.MAX_SAFE_INTEGER}`); | ||
checkNoOpts(Number.MAX_VALUE, `${Number.MAX_VALUE}`); | ||
// strings containing numbers | ||
check(`${Number.MIN_VALUE}`, `${Number.MIN_VALUE}`); | ||
check(`${Number.MIN_SAFE_INTEGER}`, `${Number.MIN_SAFE_INTEGER}`); | ||
check('-123.456', '-123.456'); | ||
check('-1', '-1'); | ||
checkNoOpts(`${Number.MIN_VALUE}`, `${Number.MIN_VALUE}`); | ||
checkNoOpts(`${Number.MIN_SAFE_INTEGER}`, `${Number.MIN_SAFE_INTEGER}`); | ||
checkNoOpts('-123.456', '-123.456'); | ||
checkNoOpts('-1', '-1'); | ||
check('0', '0'); | ||
checkNoOpts('0', '0'); | ||
check('1', '1'); | ||
check('123.456', '123.456'); | ||
check(`${Number.MAX_SAFE_INTEGER}`, `${Number.MAX_SAFE_INTEGER}`); | ||
check(`${Number.MAX_VALUE}`, `${Number.MAX_VALUE}`); | ||
checkNoOpts('1', '1'); | ||
checkNoOpts('123.456', '123.456'); | ||
checkNoOpts(`${Number.MAX_SAFE_INTEGER}`, `${Number.MAX_SAFE_INTEGER}`); | ||
checkNoOpts(`${Number.MAX_VALUE}`, `${Number.MAX_VALUE}`); | ||
// strings containing only whitespace | ||
check('', ''); | ||
check(' ', ' '); | ||
check('\n', '\n'); | ||
check('\r', '\r'); | ||
check('\t', '\t'); | ||
check(' ', ' '); | ||
check(' \n ', ' \n '); | ||
check(' \r ', ' \r '); | ||
check(' \t ', ' \t '); | ||
check(' \n \r \t \n \r \t ', ' \n \r \t \n \r \t '); | ||
checkNoOpts('', ''); | ||
checkNoOpts(' ', ' '); | ||
checkNoOpts('\n', '\n'); | ||
checkNoOpts('\r', '\r'); | ||
checkNoOpts('\t', '\t'); | ||
checkNoOpts(' ', ' '); | ||
checkNoOpts(' \n ', ' \n '); | ||
checkNoOpts(' \r ', ' \r '); | ||
checkNoOpts(' \t ', ' \t '); | ||
checkNoOpts(' \n \r \t \n \r \t ', ' \n \r \t \n \r \t '); | ||
// strings not containing numbers | ||
check('a', 'a'); | ||
check('abc', 'abc'); | ||
check('ABC', 'ABC'); | ||
checkWithArgs('', false, false, true, '""'); | ||
checkWithArgs('ABC', false, false, true, '"ABC"'); | ||
checkNoOpts('a', 'a'); | ||
checkNoOpts('abc', 'abc'); | ||
checkNoOpts('ABC', 'ABC'); | ||
checkWithOpts('', {quoteStrings: true}, '""'); | ||
checkWithOpts('ABC', {quoteStrings: true}, '"ABC"'); | ||
// errors | ||
check(new Error('Planned error'), wrapInString ? 'Error: Planned error' : '{"name":"Error","message":"Planned error"}'); | ||
checkWithArgs(new Error('Planned error'), true, false, false, wrapInString ? 'Error: Planned error' : '[Error: Planned error]'); | ||
checkNoOpts(new Error('Planned error'), wrapInString ? 'Error: Planned error' : '[Error: Planned error]'); | ||
checkWithOpts(new Error('Planned error'), {avoidErrorToString: false}, wrapInString ? 'Error: Planned error' : '[Error: Planned error]'); | ||
checkWithOpts(new Error('Planned error'), {avoidErrorToString: true}, wrapInString ? 'Error: Planned error' : '{"name":"Error","message":"Planned error"}'); | ||
const error = new TypeError('Planned error'); | ||
error.extra1 = 'Extra 1'; | ||
error.extra2 = 'Extra 2'; | ||
checkNoOpts(error, wrapInString ? 'TypeError: Planned error' : '[TypeError: Planned error {"extra1":"Extra 1","extra2":"Extra 2"}]'); | ||
checkWithOpts(error, {avoidErrorToString: false}, wrapInString ? 'TypeError: Planned error' : '[TypeError: Planned error {"extra1":"Extra 1","extra2":"Extra 2"}]'); | ||
checkWithOpts(error, {avoidErrorToString: true}, wrapInString ? 'TypeError: Planned error' : '{"name":"TypeError","message":"Planned error","extra1":"Extra 1","extra2":"Extra 2"}'); | ||
// circular objects | ||
@@ -337,3 +336,3 @@ const circular0 = {a: 1, o: {b: 2}}; | ||
circular0.o.oAgain = circular0.o; | ||
check(circular0, wrapInString ? '[object Object]' : '{"a":1,"o":{"b":2,"oAgain":[Circular: this.o]},"circular":[Circular: this]}'); | ||
checkNoOpts(circular0, wrapInString ? '[object Object]' : '{"a":1,"o":{"b":2,"oAgain":[Circular: this.o]},"circular":[Circular: this]}'); | ||
@@ -352,3 +351,3 @@ const circular1 = {a: 1, b: 2, o: {c: 'C', p: {d: 'D'}}}; | ||
circular1.o.p.pAgain = circular1.o.p; | ||
check(circular1, wrapInString ? '[object Object]' : '{"a":1,"b":2,"o":{"c":"C","p":{"d":"D","thisAgain":[Circular: this],"oAgain":[Circular: this.o],"pAgain":[Circular: this.o.p]},"thisAgain":[Circular: this],"oAgain":[Circular: this.o],"pAgain":[Reference: this.o.p]},"thisAgain":[Circular: this],"oAgain":[Reference: this.o],"pAgain":[Reference: this.o.p]}'); | ||
checkNoOpts(circular1, wrapInString ? '[object Object]' : '{"a":1,"b":2,"o":{"c":"C","p":{"d":"D","thisAgain":[Circular: this],"oAgain":[Circular: this.o],"pAgain":[Circular: this.o.p]},"thisAgain":[Circular: this],"oAgain":[Circular: this.o],"pAgain":[Reference: this.o.p]},"thisAgain":[Circular: this],"oAgain":[Reference: this.o],"pAgain":[Reference: this.o.p]}'); | ||
@@ -362,3 +361,3 @@ // circular arrays with circular objects | ||
check(array2, wrapInString ? 'a,[object Object],123,' : '["a", {"thisAgain":[Circular: this],"this1Again":[Circular: this[1]]}, 123, [Circular: this]]'); | ||
checkNoOpts(array2, wrapInString ? 'a,[object Object],123,' : '["a", {"thisAgain":[Circular: this],"this1Again":[Circular: this[1]]}, 123, [Circular: this]]'); | ||
@@ -371,3 +370,3 @@ const array3 = ['x', {y: 'Y'}, 123]; | ||
check(circular3, wrapInString ? '[object Object]' : '{"y":"Y","thisAgain":[Circular: this],"arrayAgain":["x", [Circular: this], 123, [Circular: this.arrayAgain]]}'); | ||
checkNoOpts(circular3, wrapInString ? '[object Object]' : '{"y":"Y","thisAgain":[Circular: this],"arrayAgain":["x", [Circular: this], 123, [Circular: this.arrayAgain]]}'); | ||
@@ -385,3 +384,3 @@ // circular objects with circular arrays | ||
check(circular4, wrapInString ? '[object Object]' : '{"a":"A","array":["b", {"z":"Z","thisAgain":[Circular: this],"arrayAgain":[Circular: this.array]}, 456, [Circular: this.array]],"thisAgain":[Circular: this],"arrayAgain":[Reference: this.array]}'); | ||
checkNoOpts(circular4, wrapInString ? '[object Object]' : '{"a":"A","array":["b", {"z":"Z","thisAgain":[Circular: this],"arrayAgain":[Circular: this.array]}, 456, [Circular: this.array]],"thisAgain":[Circular: this],"arrayAgain":[Reference: this.array]}'); | ||
@@ -401,6 +400,6 @@ const array5 = ['c', {x: "X"}, 789]; | ||
check(array5, wrapInString ? 'c,[object Object],789,' : '["c", {"x":"X","thisAgain":[Circular: this],"this1Again":[Circular: this[1]],"circular5":{"a":"A","array":[Circular: this],"thisAgain":[Circular: this],"this1Again":[Circular: this[1]],"this1Circular5Again":[Circular: this[1].circular5],"this1Circular5ArrayAgain":[Circular: this]}}, 789, [Circular: this]]'); | ||
checkNoOpts(array5, wrapInString ? 'c,[object Object],789,' : '["c", {"x":"X","thisAgain":[Circular: this],"this1Again":[Circular: this[1]],"circular5":{"a":"A","array":[Circular: this],"thisAgain":[Circular: this],"this1Again":[Circular: this[1]],"this1Circular5Again":[Circular: this[1].circular5],"this1Circular5ArrayAgain":[Circular: this]}}, 789, [Circular: this]]'); | ||
// reference-only objects | ||
const array6 = [{a:1}, {b:"B"}]; | ||
const array6 = [{a: 1}, {b: "B"}]; | ||
const references6 = { | ||
@@ -413,3 +412,3 @@ array6: array6, | ||
}; | ||
check(references6, wrapInString ? '[object Object]' : '{"array6":[{"a":1}, {"b":"B"}],"array6Again":[Reference: this.array6],"array6aOnly":[[Reference: this.array6[0]]],"array6bOnly":[[Reference: this.array6[1]]],"diffArrayWithSameElems":[[Reference: this.array6[0]], [Reference: this.array6[1]]]}'); | ||
checkNoOpts(references6, wrapInString ? '[object Object]' : '{"array6":[{"a":1}, {"b":"B"}],"array6Again":[Reference: this.array6],"array6aOnly":[[Reference: this.array6[0]]],"array6bOnly":[[Reference: this.array6[1]]],"diffArrayWithSameElems":[[Reference: this.array6[0]], [Reference: this.array6[1]]]}'); | ||
@@ -419,2 +418,3 @@ // Functions | ||
} | ||
//check(func, wrapInString ? 'function func() {}' : '[Function: func]'); // this test breaks if function func() {} is reformatted to multi-line | ||
@@ -424,8 +424,8 @@ if (wrapInString) { | ||
} else { | ||
check(func, '[Function: func]'); | ||
checkNoOpts(func, '[Function: func]'); | ||
} | ||
check({fn: func}, wrapInString ? '[object Object]' : '{"fn":[Function: func]}'); | ||
checkNoOpts({fn: func}, wrapInString ? '[object Object]' : '{"fn":[Function: func]}'); | ||
// undefined object properties | ||
check({a: undefined}, wrapInString ? '[object Object]' : '{"a":undefined}'); | ||
checkNoOpts({a: undefined}, wrapInString ? '[object Object]' : '{"a":undefined}'); | ||
@@ -438,3 +438,4 @@ // objects with toJSON methods | ||
executable: true, | ||
execute: () => { }, | ||
execute: () => { | ||
}, | ||
subTaskDefs: [], | ||
@@ -444,4 +445,5 @@ parent: undefined | ||
executable: true, | ||
execute: () => { }, | ||
_subTasks: [], | ||
execute: () => { | ||
}, | ||
_subTasks: [], | ||
_subTasksByName: {}, | ||
@@ -474,9 +476,9 @@ parent: undefined, | ||
// default behaviour must use toJSON method | ||
check(task, wrapInString ? '[object Object]' : '{"name":"Task1","executable":true,"state":{"code":"Unstarted","completed":false,"rejected":false},"attempts":1,"lastExecutedAt":"2016-12-01T05:09:09.119Z","subTasks":[]}'); | ||
checkNoOpts(task, wrapInString ? '[object Object]' : '{"name":"Task1","executable":true,"state":{"code":"Unstarted","completed":false,"rejected":false},"attempts":1,"lastExecutedAt":"2016-12-01T05:09:09.119Z","subTasks":[]}'); | ||
// explicit !avoidToJSONMethods must use toJSON method | ||
checkWithArgs(task, false, false, false, wrapInString ? '[object Object]' : '{"name":"Task1","executable":true,"state":{"code":"Unstarted","completed":false,"rejected":false},"attempts":1,"lastExecutedAt":"2016-12-01T05:09:09.119Z","subTasks":[]}'); | ||
checkWithOpts(task, {avoidToJSONMethods: false}, wrapInString ? '[object Object]' : '{"name":"Task1","executable":true,"state":{"code":"Unstarted","completed":false,"rejected":false},"attempts":1,"lastExecutedAt":"2016-12-01T05:09:09.119Z","subTasks":[]}'); | ||
// explicit avoidToJSONMethods must NOT use toJSON method | ||
checkWithArgs(task, false, true, false, wrapInString ? '[object Object]' : '{"name":"Task1","definition":{"name":"Task1","executable":true,"execute":[Function: anonymous],"subTaskDefs":[],"parent":undefined},"executable":true,"execute":[Function: anonymous],"_subTasks":[],"_subTasksByName":{},"parent":undefined,"_state":{"code":"Unstarted","completed":false,"error":undefined,"rejected":false,"reason":undefined},"_attempts":1,"_lastExecutedAt":"2016-12-01T05:09:09.119Z","_result":undefined,"_error":undefined,"_slaveTasks":[],"_frozen":true,"toJSON":[Function: toJSON]}'); | ||
checkWithOpts(task, {avoidToJSONMethods: true}, wrapInString ? '[object Object]' : '{"name":"Task1","definition":{"name":"Task1","executable":true,"execute":[Function: anonymous],"subTaskDefs":[],"parent":undefined},"executable":true,"execute":[Function: anonymous],"_subTasks":[],"_subTasksByName":{},"parent":undefined,"_state":{"code":"Unstarted","completed":false,"error":undefined,"rejected":false,"reason":undefined},"_attempts":1,"_lastExecutedAt":"2016-12-01T05:09:09.119Z","_result":undefined,"_error":undefined,"_slaveTasks":[],"_frozen":true,"toJSON":[Function: toJSON]}'); | ||
} | ||
@@ -486,3 +488,6 @@ | ||
function check(value, expected) { | ||
return checkEqual(t, Strings.trim, [wrap(value, wrapInString)], expected, toPrefix(value, wrapInString)); | ||
if (Number.isNaN(Strings.trim(wrap(value, wrapInString)) && Number.isNaN(expected))) { | ||
return t.pass(`Strings.trim(${toPrefix(value, wrapInString)} must be ${expected}`); | ||
} | ||
return t.deepEqual(Strings.trim(wrap(value, wrapInString)), expected, `Strings.trim(${toPrefix(value, wrapInString)} must be ${expected}`); | ||
} | ||
@@ -563,3 +568,6 @@ | ||
function check(value, expected) { | ||
return checkEqual(t, Strings.trimOrEmpty, [wrap(value, wrapInString)], expected, toPrefix(value, wrapInString)); | ||
if (Number.isNaN(Strings.trimOrEmpty(wrap(value, wrapInString)) && Number.isNaN(expected))) { | ||
return t.pass(`Strings.trimOrEmpty(${toPrefix(value, wrapInString)} must be ${expected}`); | ||
} | ||
return t.deepEqual(Strings.trimOrEmpty(wrap(value, wrapInString)), expected, `Strings.trimOrEmpty(${toPrefix(value, wrapInString)} must be ${expected}`); | ||
} | ||
@@ -730,2 +738,109 @@ | ||
t.end(); | ||
}); | ||
}); | ||
test('stringify on Maps', t => { | ||
let entries = []; | ||
let expected = `[Map {}]`; | ||
t.equal(Strings.stringify(new Map(entries)), expected, `stringify on Map(${Strings.stringify(entries)}) must be ${expected}`); | ||
entries = [['a', 123]]; | ||
expected = `[Map {"a" => 123}]`; | ||
t.equal(Strings.stringify(new Map(entries)), expected, `stringify on Map(${Strings.stringify(entries)}) must be ${expected}`); | ||
entries = [['b', 789], [{o: 1}, 'Xyz']]; | ||
expected = `[Map {"b" => 789, {"o":1} => "Xyz"}]`; | ||
t.equal(Strings.stringify(new Map(entries)), expected, `stringify on Map(${Strings.stringify(entries)}) must be ${expected}`); | ||
// Map containing a Map | ||
let map = new Map([['a', 3.14]]); | ||
entries = [['b', 789], [{o: 1}, 'Xyz'], ['m', map], [map, map]]; | ||
expected = `[Map {"b" => 789, {"o":1} => "Xyz", "m" => [Map {"a" => 3.14}], [Reference: this[2].VAL] => [Reference: this[2].VAL]}]`; | ||
t.equal(Strings.stringify(new Map(entries)), expected, `stringify on Map(${Strings.stringify(entries)}) must be ${expected}`); | ||
t.end(); | ||
}); | ||
test('stringify on WeakMaps', t => { | ||
let entries = []; | ||
let expected = `[WeakMap]`; | ||
t.equal(Strings.stringify(new WeakMap(entries)), expected, `stringify on WeakMap(${Strings.stringify(entries)}) must be ${expected}`); | ||
entries = [[{'a': 1}, 123]]; | ||
expected = `[WeakMap]`; | ||
t.equal(Strings.stringify(new WeakMap(entries)), expected, `stringify on WeakMap(${Strings.stringify(entries)}) must be ${expected}`); | ||
entries = [[{'b': 2}, 789], [{o: 1}, 'Xyz']]; | ||
expected = `[WeakMap]`; | ||
t.equal(Strings.stringify(new WeakMap(entries)), expected, `stringify on WeakMap(${Strings.stringify(entries)}) must be ${expected}`); | ||
// Map containing a WeakMap | ||
let map = new WeakMap([[{'a': 1}, 3.14]]); | ||
entries = [['b', 789], [{o: 1}, 'Xyz'], ['m', map], [map, map]]; | ||
expected = `[Map {"b" => 789, {"o":1} => "Xyz", "m" => [WeakMap], [Reference: this[2].VAL] => [Reference: this[2].VAL]}]`; | ||
t.equal(Strings.stringify(new Map(entries)), expected, `stringify on Map(${Strings.stringify(entries)}) must be ${expected}`); | ||
t.end(); | ||
}); | ||
test('stringify on Promises', t => { | ||
// Attempts to get Node's util.js inspect function (if available) | ||
const inspect = (() => { | ||
try { | ||
return require('util').inspect; | ||
} catch (_) { | ||
return undefined; | ||
} | ||
})(); | ||
let p = Promise.resolve('Hi'); | ||
let expected = inspect ? `[Promise { 'Hi' }]` : `[Promise]`; | ||
t.equal(Strings.stringify(p), expected, `stringify(Promise.resolve('Hi')) -> ${Strings.stringify(p)} must be ${expected}`); | ||
p = Promise.resolve('Hi "Bob"'); | ||
expected = inspect ? `[Promise { 'Hi "Bob"' }]` : `[Promise]`; | ||
t.equal(Strings.stringify(p), expected, `stringify(Promise.resolve('Hi "Bob"')) -> ${Strings.stringify(p)} must be ${expected}`); | ||
// Long string that "breaks" to next line | ||
const promised1 = 'shardId-000000000000:49545115243490985018280067714973144582180062593244200961'; | ||
p = Promise.resolve(promised1); | ||
expected = inspect ? `[Promise { '${promised1}' }]` : `[Promise]`; | ||
t.equal(Strings.stringify(p), expected, `stringify(Promise.resolve(${promised1})) -> ${Strings.stringify(p)} must be ${expected}`); | ||
const promised2 = 'shardId-111111111111:49545115243490985018280067714973144582180062593244200962'; | ||
let promised = [promised1, promised2]; | ||
p = Promise.resolve(promised); | ||
expected = inspect ? `[Promise { [ '${promised1}', '${promised2}' ] }]` : `[Promise]`; | ||
t.equal(Strings.stringify(p), expected, `stringify(Promise.resolve(${promised})) -> ${Strings.stringify(p)} must be ${expected}`); | ||
promised = {a: 1, p: Promise.resolve('Another promise')}; | ||
p = Promise.resolve(Promise.resolve(promised)); | ||
expected = inspect ? `[Promise { { a: 1, p: Promise { 'Another promise' } } }]` : `[Promise]`; | ||
t.equal(Strings.stringify(p), expected, `stringify(Promise.resolve(Promise.resolve(${Strings.stringify(promised)}))) -> ${Strings.stringify(p)} must be ${expected}`); | ||
promised = {a: 1, p: Promise.resolve(promised2)}; | ||
p = Promise.resolve(Promise.resolve(promised)); | ||
// if (inspect) { | ||
// const inspectOpts = {depth: null, breakLength: Infinity}; | ||
// console.log(`####################### inspect(p}) = ${inspect(p)}`); | ||
// console.log(`####################### inspect(p, ${Strings.stringify(inspectOpts)}) = ${inspect(p, inspectOpts)}`); | ||
// } | ||
expected = inspect ? `[Promise { { a: 1, p: Promise { '${promised2}' } } }]` : `[Promise]`; | ||
t.equal(Strings.stringify(p), expected, `stringify(Promise.resolve(Promise.resolve(${Strings.stringify(promised)}))) -> ${Strings.stringify(p)} must be ${expected}`); | ||
p = Promise.resolve({a: 1}).then(r => r); | ||
expected = inspect ? `[Promise { <pending> }]` : `[Promise]`; | ||
t.equal(Strings.stringify(p), expected, `stringify(Promise.resolve({a:1}).then(r => r)) -> ${Strings.stringify(p)} must be ${expected}`); | ||
t.end(); | ||
}); | ||
test('stringify on console & Console', t => { | ||
console.log(`console = ${Strings.stringify(console)}`); | ||
t.equal(Strings.stringify(console), '[Console]', `stringify(console) must be '[Console]'`); | ||
const customConsole = new console.Console(process.stdout, process.stderr); | ||
t.notEqual(customConsole, console, `customConsole must not be console`); | ||
t.deepEqual(Strings.stringify(customConsole), '[Console {}]', `stringify(customConsole) must be '[Console {}]'`); | ||
t.end(); | ||
}); | ||
@@ -29,11 +29,14 @@ 'use strict'; | ||
function toFnArgsPrefix(fn, args) { | ||
return prefixFnArgs ? `${fn.name ? fn.name : '' }${stringify(args, true, false, true)} -> ` : ''; | ||
const opts = {quoteStrings: true}; | ||
return prefixFnArgs ? `${fn.name ? fn.name : '' }${stringify(args, opts)} -> ` : ''; | ||
} | ||
function toMethodArgsPrefix(obj, method, args) { | ||
return prefixFnArgs ? `${obj.name ? obj.name : stringify(obj, true, false, true)}.${method.name ? method.name : '<anon>' }${stringify(args, true, false, true)} -> ` : ''; | ||
const opts = {quoteStrings: true}; | ||
return prefixFnArgs ? `${obj.name ? obj.name : stringify(obj, opts)}.${method.name ? method.name : '<anon>' }${stringify(args, opts)} -> ` : ''; | ||
} | ||
function equal(t, actual, expected, prefix) { | ||
const msg = `${toPrefix(prefix)}${stringify(actual, true, false, true)} must be ${stringify(expected, true, false, true)}`; | ||
const opts = {quoteStrings: true}; | ||
const msg = `${toPrefix(prefix)}${stringify(actual, opts)} must be ${stringify(expected, opts)}`; | ||
@@ -91,10 +94,11 @@ if (Numbers.isNaN(actual)) { | ||
const now = new Date().toISOString(); | ||
const opts = {quoteStrings: true}; | ||
try { | ||
obj[propertyName] = now; | ||
t.fail(`${prefix ? prefix : ''}${stringify(obj, true, false, true)} ${propertyName} is supposed to be immutable`); | ||
t.fail(`${prefix ? prefix : ''}${stringify(obj, opts)} ${propertyName} is supposed to be immutable`); | ||
} catch (err) { | ||
// Expect an error on attempted mutation of immutable property | ||
t.pass(`${prefix ? prefix : ''}${stringify(obj, true, false, true)} ${propertyName} is immutable`); | ||
t.pass(`${prefix ? prefix : ''}${stringify(obj, opts)} ${propertyName} is immutable`); | ||
//console.log(`Expected error ${err}`); | ||
} | ||
} |
@@ -12,12 +12,4 @@ 'use strict'; | ||
const Strings = require('../strings'); | ||
// const Strings = require('../strings'); | ||
const testing = require('./testing'); | ||
// const okNotOk = testing.okNotOk; | ||
const checkOkNotOk = testing.checkOkNotOk; | ||
// const checkMethodOkNotOk = testing.checkMethodOkNotOk; | ||
// const equal = testing.equal; | ||
const checkEqual = testing.checkEqual; | ||
// const checkMethodEqual = testing.checkMethodEqual; | ||
test('Cancel normal timeout with cancelTimeout', t => { | ||
@@ -24,0 +16,0 @@ const timeout = setTimeout(() => { |
@@ -16,11 +16,2 @@ 'use strict'; | ||
/** | ||
* @typedef {Object} Timeout - A timeout object created by {@linkcode setTimeout} or {@linkcode setInterval}, which can | ||
* be cancelled by using the {@linkcode clearTimeout} or {@linkcode clearInterval} functions respectively. | ||
* | ||
* @property {boolean} _called - a "private" flag that indicates if the Timeout has been triggered already or not | ||
* @property {Function|null} _repeat - a "private" function that appears to be present on interval timeouts and null | ||
* on normal timeouts | ||
*/ | ||
/** | ||
* Attempts to cancel the given normal timeout, which was created by a call to {@linkcode setTimeout} using the | ||
@@ -27,0 +18,0 @@ * {@linkcode clearTimeout} function as instructed (and if the timeout looks like an interval timeout also using the |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
608627
34
9612
336