Comparing version 2.0.6 to 3.0.0
@@ -9,4 +9,2 @@ /** | ||
/** | ||
@@ -22,44 +20,19 @@ * Basic type definitions. | ||
// * | ||
'*': { | ||
is: function(v) { | ||
return !_.isUndefined(v); | ||
}, | ||
to: function(v) { | ||
if(_.isUndefined(v)) { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
return v; | ||
}, | ||
getBase: function (){ | ||
return undefined; | ||
} | ||
}, | ||
// Boolean | ||
boolean: { | ||
is: _.isBoolean, | ||
to: function(v) { | ||
if(_.isBoolean(v)) return v; | ||
if (_.isNumber(v)){ | ||
if(v === 1) return true; | ||
if(v === 0) return false; | ||
} | ||
if (_.isString(v)) { | ||
if(v === 'true') return true; | ||
if(v === 'false') return false; | ||
if(v === '1') return true; | ||
if(v === '0') return false; | ||
} | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
}, | ||
getBase: function (){ | ||
return false; | ||
} | ||
}, | ||
// String | ||
// $$\ $$\ | ||
// $$ | \__| | ||
// $$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$$$$$$\ $$$$$$\ | ||
// $$ _____|\_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$\ | ||
// \$$$$$$\ $$ | $$ | \__|$$ |$$ | $$ |$$ / $$ | | ||
// \____$$\ $$ |$$\ $$ | $$ |$$ | $$ |$$ | $$ | | ||
// $$$$$$$ | \$$$$ |$$ | $$ |$$ | $$ |\$$$$$$$ | | ||
// \_______/ \____/ \__| \__|\__| \__| \____$$ | | ||
// $$\ $$ | | ||
// \$$$$$$ | | ||
// \______/ | ||
string: { | ||
@@ -115,56 +88,17 @@ is: _.isString, | ||
// Dictionary | ||
dictionary: { | ||
is: function(v) { | ||
if (!_.isObject(v)) return false; | ||
if (type.arr.is(v)) return false; | ||
if (!_.isPlainObject(v)) return false; | ||
return true; | ||
}, | ||
to: function(v) { | ||
if (_.isArray(v)) { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
if (!_.isObject(v)){ | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
if (_.isPlainObject(v)){ | ||
return v; | ||
} | ||
// Don't tolerate regexps or dates. | ||
if (_.isDate(v) || _.isRegExp(v)) { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
// But otherwise, if this is NOT a "plain object", determine whether | ||
// it's JSON-compatible and if so, coerce it to that. | ||
try { | ||
return JSON.parse(JSON.stringify(v)); | ||
} | ||
catch (e) {} | ||
// If that doesn't work, give up | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
}, | ||
getBase: function (){ | ||
return {}; | ||
} | ||
}, | ||
// Array | ||
array: { | ||
is: _.isArray, | ||
to: function(v) { | ||
if (!_.isArray(v)) { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
return v; | ||
}, | ||
getBase: function (){ | ||
return []; | ||
} | ||
}, | ||
// Number | ||
// $$\ | ||
// $$ | | ||
// $$$$$$$\ $$\ $$\ $$$$$$\$$$$\ $$$$$$$\ $$$$$$\ $$$$$$\ | ||
// $$ __$$\ $$ | $$ |$$ _$$ _$$\ $$ __$$\ $$ __$$\ $$ __$$\ | ||
// $$ | $$ |$$ | $$ |$$ / $$ / $$ |$$ | $$ |$$$$$$$$ |$$ | \__| | ||
// $$ | $$ |$$ | $$ |$$ | $$ | $$ |$$ | $$ |$$ ____|$$ | | ||
// $$ | $$ |\$$$$$$ |$$ | $$ | $$ |$$$$$$$ |\$$$$$$$\ $$ | | ||
// \__| \__| \______/ \__| \__| \__|\_______/ \_______|\__| | ||
// | ||
// | ||
// | ||
number: { | ||
@@ -225,2 +159,152 @@ is: function(v) { | ||
// $$\ $$\ | ||
// $$ | $$ | | ||
// $$$$$$$\ $$$$$$\ $$$$$$\ $$ | $$$$$$\ $$$$$$\ $$$$$$$\ | ||
// $$ __$$\ $$ __$$\ $$ __$$\ $$ |$$ __$$\ \____$$\ $$ __$$\ | ||
// $$ | $$ |$$ / $$ |$$ / $$ |$$ |$$$$$$$$ | $$$$$$$ |$$ | $$ | | ||
// $$ | $$ |$$ | $$ |$$ | $$ |$$ |$$ ____|$$ __$$ |$$ | $$ | | ||
// $$$$$$$ |\$$$$$$ |\$$$$$$ |$$ |\$$$$$$$\ \$$$$$$$ |$$ | $$ | | ||
// \_______/ \______/ \______/ \__| \_______| \_______|\__| \__| | ||
// | ||
// | ||
// | ||
boolean: { | ||
is: _.isBoolean, | ||
to: function(v) { | ||
if(_.isBoolean(v)) return v; | ||
if (_.isNumber(v)){ | ||
if(v === 1) return true; | ||
if(v === 0) return false; | ||
} | ||
if (_.isString(v)) { | ||
if(v === 'true') return true; | ||
if(v === 'false') return false; | ||
if(v === '1') return true; | ||
if(v === '0') return false; | ||
} | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
}, | ||
getBase: function (){ | ||
return false; | ||
} | ||
}, | ||
// $$\ $$\ $$\ $$\ | ||
// $$ |\__| $$ | \__| | ||
// $$$$$$$ |$$\ $$$$$$$\ $$$$$$\ $$\ $$$$$$\ $$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ | ||
// $$ __$$ |$$ |$$ _____|\_$$ _| $$ |$$ __$$\ $$ __$$\ \____$$\ $$ __$$\ $$ | $$ | | ||
// $$ / $$ |$$ |$$ / $$ | $$ |$$ / $$ |$$ | $$ | $$$$$$$ |$$ | \__|$$ | $$ | | ||
// $$ | $$ |$$ |$$ | $$ |$$\ $$ |$$ | $$ |$$ | $$ |$$ __$$ |$$ | $$ | $$ | | ||
// \$$$$$$$ |$$ |\$$$$$$$\ \$$$$ |$$ |\$$$$$$ |$$ | $$ |\$$$$$$$ |$$ | \$$$$$$$ | | ||
// \_______|\__| \_______| \____/ \__| \______/ \__| \__| \_______|\__| \____$$ | | ||
// $$\ $$ | | ||
// \$$$$$$ | | ||
// \______/ | ||
dictionary: { | ||
is: function(v) { | ||
if (!_.isObject(v)) return false; | ||
if (type.arr.is(v)) return false; | ||
if (!_.isPlainObject(v)) return false; | ||
return true; | ||
}, | ||
to: function(v) { | ||
// Don't tolerate non-objects, or arrays, or regexps, or dates. | ||
if (!_.isObject(v) || _.isArray(v) || _.isDate(v) || _.isRegExp(v)) { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
// Plain objects are ok, but we'll clone them. | ||
// (TODO: this should be configurable) | ||
if (_.isPlainObject(v)){ | ||
return _.clone(v); | ||
} | ||
// If this is NOT a "plain object" (i.e. some kind of prototypal thing) | ||
// determine whether it's JSON-compatible and if so, coerce it to that. | ||
try { | ||
return JSON.parse(JSON.stringify(v)); | ||
} | ||
catch (e) {} | ||
// If that doesn't work, give up | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
}, | ||
getBase: function (){ | ||
return {}; | ||
} | ||
}, | ||
// | ||
// | ||
// $$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$\ $$\ | ||
// \____$$\ $$ __$$\ $$ __$$\ \____$$\ $$ | $$ | | ||
// $$$$$$$ |$$ | \__|$$ | \__|$$$$$$$ |$$ | $$ | | ||
// $$ __$$ |$$ | $$ | $$ __$$ |$$ | $$ | | ||
// \$$$$$$$ |$$ | $$ | \$$$$$$$ |\$$$$$$$ | | ||
// \_______|\__| \__| \_______| \____$$ | | ||
// $$\ $$ | | ||
// \$$$$$$ | | ||
// \______/ | ||
array: { | ||
is: _.isArray, | ||
to: function(v) { | ||
if (!_.isArray(v)) { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
return v; | ||
}, | ||
getBase: function (){ | ||
return []; | ||
} | ||
}, | ||
// $$$\ $$\ $$\ $$\ $$$\ | ||
// $$\$$\ $$ _| $$ | $$ | \__| \$$\ | ||
// \$$$ | $$ / $$$$$$\ $$$$$$$\ $$\ $$\ $$$$$$\ $$$$$$$\ $$\ $$$$$$$\ $$$$$$\ \$$\ | ||
// $$$$$$$\ $$ | \____$$\ $$ __$$\ $$ | $$ |\_$$ _| $$ __$$\ $$ |$$ __$$\ $$ __$$\ $$ | | ||
// \_$$$ __| $$ | $$$$$$$ |$$ | $$ |$$ | $$ | $$ | $$ | $$ |$$ |$$ | $$ |$$ / $$ | $$ | | ||
// $$ $$\ \$$\ $$ __$$ |$$ | $$ |$$ | $$ | $$ |$$\ $$ | $$ |$$ |$$ | $$ |$$ | $$ | $$ | | ||
// \__\__| \$$$\\$$$$$$$ |$$ | $$ |\$$$$$$$ | \$$$$ |$$ | $$ |$$ |$$ | $$ |\$$$$$$$ |$$$ / | ||
// \___|\_______|\__| \__| \____$$ | \____/ \__| \__|\__|\__| \__| \____$$ |\___/ | ||
// $$\ $$ | $$\ $$ | | ||
// \$$$$$$ | \$$$$$$ | | ||
// \______/ \______/ | ||
'*': { | ||
is: function(v) { | ||
return !_.isUndefined(v); | ||
}, | ||
to: function(v) { | ||
if(_.isUndefined(v)) { | ||
throw new Error('E_runtimeInputTypeCoercionError'); | ||
} | ||
return v; | ||
}, | ||
getBase: function (){ | ||
return undefined; | ||
} | ||
}, | ||
}; | ||
@@ -230,2 +314,5 @@ | ||
// Aliases | ||
@@ -232,0 +319,0 @@ type.str = type.email = type.url = type.string; |
@@ -45,11 +45,3 @@ /** | ||
// // "*" (allow anything) | ||
// if (expected === '*'){ | ||
// // Except if this is `undefined`, it's a E_INVALID_TYPE error | ||
// // (i.e. won't matter if coercing, but will cause failure when validating) | ||
// if (_.isUndefined(actual)){} | ||
// return actual; | ||
// } | ||
// Flag '*' (allow anything that's not undefined) | ||
@@ -59,5 +51,5 @@ if (expected === '*') { | ||
} | ||
// Normalize: [] or ['*'] or "array" (allow any array) | ||
if (expected === 'array' || (_.isArray(expected) && (expected.length === 0 || expected[0] === '*'))) { | ||
expected = ['*']; | ||
// Normalize: [] or "array" (allow any array) | ||
if (expected === 'array' || (_.isArray(expected) && expected.length === 0)) { | ||
expected = []; | ||
allowAnyArray = true; | ||
@@ -109,2 +101,3 @@ } | ||
// Build an E_INVALID_TYPE error | ||
@@ -172,14 +165,23 @@ var newErr = (function (){ | ||
} | ||
// else { | ||
// console.log('actual:',actual,' IS EXPECTED TYPE.'); | ||
// } | ||
// Build partial result | ||
// (taking recursive step if necessary) | ||
// ...expecting ANYTHING ('*') | ||
if (isExpectingAnything) { | ||
coercedValue = sanitizeGenericDictionary(coercedValue); | ||
coercedValue = sanitizeGenericArray(coercedValue); | ||
return coercedValue; | ||
} | ||
if (isExpectingArray) { | ||
// ...expecting ANY array ([]) | ||
if (allowAnyArray) { | ||
return sanitizeGenericArray(coercedValue); | ||
return recursivelyCloneAndStripUndefinedKeysFromDictionaries(coercedValue); | ||
} | ||
// ...expecting a specific array example | ||
var arrayItemTpl = expected[0]; | ||
@@ -191,6 +193,10 @@ return _.reduce(coercedValue, function (memo, coercedVal){ | ||
} | ||
if (isExpectingDictionary) { | ||
// ...expecting ANY dictionary ({}) | ||
if (allowAnyDictionary){ | ||
return sanitizeGenericDictionary(coercedValue); | ||
return recursivelyCloneAndStripUndefinedKeysFromDictionaries(coercedValue); | ||
} | ||
// ...expecting a specific dictionary example | ||
return _.reduce(expected, function (memo, expectedVal, expectedKey) { | ||
@@ -202,2 +208,4 @@ var keyName = (meta && meta.keyName ? (meta.keyName + '.') : '') + expectedKey; | ||
} | ||
// ...expecting a primitive | ||
return coercedValue; | ||
@@ -209,31 +217,120 @@ }; | ||
/** | ||
* sanitize a dictionary used in examples `['*']`, `{}`, or `'*'` | ||
* | ||
*/ | ||
function sanitizeGenericDictionary(val){ | ||
function recursivelyCloneAndStripUndefinedKeysFromDictionaries(val) { | ||
// TODO: also consider doing all the things in the `dictionary` | ||
// type `.to()` function here... | ||
if (!_.isPlainObject(val)) return val; | ||
// (note that the recursive validation will not penetrate deeper into this | ||
// object, so we don't have to worry about this function running more than once | ||
// and doing unnecessary extra deep copies at each successive level) | ||
// Strip out keys w/ undefined values | ||
return _.reduce(_.keys(val), function (memo, key){ | ||
if (!_.isUndefined(val[key])) { | ||
memo[key] = val[key]; | ||
// If dictionary: | ||
// ============================================================================== | ||
// Sanitize a dictionary provided for a wildcard dictionary example (`example: {}`) | ||
// The main recursive validation function will not descend into this dictionary because | ||
// it's already met the minimum requirement of being an object. So we need to deep clone | ||
// the provided value for safety; and in the process ensure that it meets the basic minimum | ||
// quality requirements (i.e. no dictionaries within have any undefined keys) | ||
// | ||
// At this point, we can be sure that the provided value is a plain object | ||
// (otherwise it would have been caught by the coercion step, since in order to | ||
// be considered a "dictionary", a value has to pass a _.isPlainObject() check) | ||
// If array: | ||
// ============================================================================== | ||
// Sanitize an array provided for a wildcard array example (`example: []`) | ||
// The main recursive validation function will not descend into this array because | ||
// it's already met the minimum requirement of being `_.isArray()`. So we need to | ||
// deep clone the provided value for safety; and in the process ensure that it meets | ||
// the basic minimum quality requirements (i.e. no dictionaries within have any undefined | ||
// keys) | ||
// | ||
// (NOTE: `example: ['*']` won't make it here because it will be picked up | ||
// by the recursive validation. And so it's different-- it will contain | ||
// the original items, and therefore may contain dictionaries w/ undefined keys) | ||
// | ||
// At this point, we can be sure that the provided value is an array | ||
// (otherwise it would have been caught by the coercion step, since in order to | ||
// be considered an "array", a value has to pass a _.isArray() check) | ||
function _recursivelyCloneAndStripUndefinedKeys (val) { | ||
if (_.isArray(val)) { | ||
return _.reduce(val,function (memo, item, i) { | ||
memo.push(_recursivelyCloneAndStripUndefinedKeys(item)); | ||
return memo; | ||
}, []); | ||
} | ||
return memo; | ||
}, {}); | ||
else if (_.isObject(val)) { | ||
return _.reduce(val,function (memo, subVal, key) { | ||
if (!_.isUndefined(subVal)) { | ||
memo[key] = _recursivelyCloneAndStripUndefinedKeys(subVal); | ||
} | ||
return memo; | ||
}, {}); | ||
} | ||
else { | ||
return val; | ||
} | ||
} | ||
// First, prevent against endless circular recursion: | ||
// (this should never throw, but if it does, it needs to be handled | ||
// by the caller of `recursivelyCloneAndStripUndefinedKeysFromDictionaries`) | ||
val = JSON.parse(stringifySafe(val)); | ||
// TODO: | ||
// could consolidate this cloning + stripping undefined keys + prevention against | ||
// ∞-recursion into a single tree traversal, which would double the efficiency. | ||
// Then build a deep copy | ||
// (in the process, remove keys with undefined values from nested dictionaries recursively) | ||
return _recursivelyCloneAndStripUndefinedKeys(val); | ||
} | ||
/** | ||
* sanitize an array used in examples `['*']`, `{}`, or `'*'` | ||
* This was modified by @mikermcneil from @isaacs' json-stringify-safe | ||
* (see https://github.com/isaacs/json-stringify-safe/commit/02cfafd45f06d076ac4bf0dd28be6738a07a72f9#diff-c3fcfbed30e93682746088e2ce1a4a24) | ||
* @param {*} val [description] | ||
* @return {String} [description] | ||
*/ | ||
function sanitizeGenericArray(val){ | ||
if (!_.isArray(val)) return val; | ||
function stringifySafe(val) { | ||
return JSON.stringify(val, serializer()); | ||
} | ||
return _.reduce(val, function (memo, item){ | ||
item = sanitizeGenericDictionary(item); | ||
memo.push(item); | ||
return memo; | ||
}, []); | ||
function serializer(replacer, cycleReplacer) { | ||
var stack = []; | ||
var keys = []; | ||
if (!cycleReplacer) { | ||
cycleReplacer = function(key, value) { | ||
if (stack[0] === value) return '[Circular ~]'; | ||
return '[Circular ~.' + keys.slice(0, stack.indexOf(value)).join('.') + ']'; | ||
}; | ||
} | ||
return function(key, value) { | ||
if (stack.length > 0) { | ||
var thisPos = stack.indexOf(this); | ||
~thisPos ? stack.splice(thisPos + 1) : stack.push(this); | ||
~thisPos ? keys.splice(thisPos, Infinity, key) : keys.push(key); | ||
if (~stack.indexOf(value)) value = cycleReplacer.call(this, key, value); | ||
} | ||
else stack.push(value); | ||
if (!replacer) { | ||
return value; | ||
} | ||
return replacer.call(this, key, value); | ||
}; | ||
} |
{ | ||
"name": "rttc", | ||
"version": "2.0.6", | ||
"version": "3.0.0", | ||
"description": "Runtime type-checking for JavaScript.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -56,5 +56,5 @@ # rttc | ||
+ For a generic "array" (`[]`), base value is `[]`, with a single archetypal item matching the expectation (recursive) | ||
+ For "*", base value is `"undefined"`. | ||
<!-- | ||
@@ -68,3 +68,3 @@ TODO: | ||
+ `undefined` will always be coerced to the base value of the expected type. | ||
+ `undefined` is never valid. | ||
+ `null` is never valid. | ||
@@ -71,0 +71,0 @@ + `NaN` is never valid. |
@@ -188,2 +188,4 @@ // Export the array of tests below. | ||
// ARRAYS | ||
// (all of the tests below pass w/ either [] or ['*'] | ||
// however note they do have subtle differences re: strictEq) | ||
//////////////////////////////////////////// | ||
@@ -219,3 +221,2 @@ | ||
{ example: [], actual: new (require('stream').Readable)(), result: [] }, // TODO: consider enhancing this behavior to concat the stream contents? Needs community discussion. | ||
// Skip Buffer tests for now since the enumerable properties vary between Node.js versions. | ||
@@ -227,4 +228,5 @@ // TODO: bring back support for this by explicitly filtering properties of buffers in `.exec()` | ||
//////////////////////////////////////////// | ||
// MISC | ||
// example: * (aka undefined) | ||
//////////////////////////////////////////// | ||
@@ -273,7 +275,27 @@ | ||
{ example: undefined, actual: /some regexp/, result: /some regexp/ }, | ||
{ example: undefined, actual: new Date('November 5, 1605 GMT'), result: new Date('November 5, 1605 GMT') }, | ||
//////////////////////////////////////////// | ||
// RECURSIVE OBJECTS | ||
//////////////////////////////////////////// | ||
// $$\ | ||
// \__| | ||
// $$$$$$\ $$$$$$\ $$$$$$$\ $$\ $$\ $$$$$$\ $$$$$$$\ $$\ $$\ $$\ $$$$$$\ | ||
// $$ __$$\ $$ __$$\ $$ _____|$$ | $$ |$$ __$$\ $$ _____|$$ |\$$\ $$ |$$ __$$\ | ||
// $$ | \__|$$$$$$$$ |$$ / $$ | $$ |$$ | \__|\$$$$$$\ $$ | \$$\$$ / $$$$$$$$ | | ||
// $$ | $$ ____|$$ | $$ | $$ |$$ | \____$$\ $$ | \$$$ / $$ ____| | ||
// $$ | \$$$$$$$\ \$$$$$$$\ \$$$$$$ |$$ | $$$$$$$ |$$ | \$ / \$$$$$$$\ | ||
// \__| \_______| \_______| \______/ \__| \_______/ \__| \_/ \_______| | ||
// | ||
// | ||
// | ||
// Some basic deep objects | ||
@@ -290,2 +312,28 @@ { example: {a:1, b:'hi', c: false}, actual: {a: 23}, result: {a: 23, b: '', c: false} }, | ||
// Omit extra keys when coercing to `example: {...}` | ||
{ | ||
example: { a:23 }, | ||
actual: {a: 23, d: true}, | ||
result: {a: 23} | ||
}, | ||
// Coerce missing/undefined required keys to base value | ||
{ example: {b: 235}, actual: {b: undefined}, result: {b: 0} }, | ||
{ example: {b: 235}, actual: {}, result: {b: 0} }, | ||
// Strip keys with `undefined` values (`{...}` case) | ||
{ example: {b: 235}, actual: {a: undefined, b: 3}, result: {b: 3} }, | ||
// Strip nested keys with `undefined` values (`{...}` case) | ||
{ example: {a: {}, b: 235}, actual: {a: {x: undefined}, b: 3}, result: {a: {}, b: 3} }, | ||
// Strip keys with `undefined` values (`{}` case) | ||
{ example: {}, actual: {a: undefined, b: 3}, result: {b: 3} }, | ||
// Strip nested keys with `undefined` values (`{}` case) | ||
{ example: {}, actual: {a: {x: undefined}, b: 3}, result: {a: {}, b: 3} }, | ||
// Don't strip keys or nested keys with `undefined` values (`*` and nested `*` cases) | ||
{ example: '*', actual: {a: undefined, b: 3, c: {x: undefined}}, result: {a: undefined, b: 3, c: {x: undefined}} }, | ||
{ example: {c:'*'}, actual: {a: undefined, b: 3, c: {x: undefined}}, result: { c: {x: undefined}} }, | ||
// Ensure that this allows arbitary arrays when coercing to `example: []` | ||
@@ -298,5 +346,81 @@ { | ||
// Ensure that nested dictionaries inside of an array passed | ||
// through `example: []` are stripped of keys with undefined values | ||
{ | ||
example: [], | ||
actual: [{a:3, b: undefined}, {a: undefined}], | ||
result: [{a: 3},{}] | ||
}, | ||
{ | ||
example: [], | ||
actual: [{a:3,someStuff: [{x: undefined, y: 'foo'}, {x: 'bar', y: undefined}]},{a: 5, b: undefined}], | ||
result: [{a: 3, someStuff: [{y:'foo'}, {x:'bar'}]}, {a: 5}] | ||
}, | ||
// Complex multi-item array test | ||
// Ensure that nested dictionaries inside of an array passed | ||
// through `example: ['*']` are NOT stripped of keys with undefined values-- | ||
// and are left utterly alone | ||
{ | ||
example: ['*'], | ||
actual: [{a:3, b: undefined}, {a: undefined}], | ||
result: [{a: 3, b: undefined},{a:undefined}] | ||
}, | ||
{ | ||
example: ['*'], | ||
actual: [{a:3,someStuff: [{x: undefined, y: 'foo'}, {x: 'bar', y: undefined}]},{a: 5, b: undefined}], | ||
result: [{a:3,someStuff: [{x: undefined, y: 'foo'}, {x: 'bar', y: undefined}]},{a: 5, b: undefined}] | ||
}, | ||
// Ensure the recursive cloning / undefined-key-stripping doesn't get | ||
// stumped by circular dictionaries/arrays. | ||
// • dict keys whose values point to a past reference should be deleted | ||
// • array items that point to past references should be pruned | ||
(function (){ | ||
var someDict = {}; | ||
var someArray = []; | ||
someDict.x = {z: someDict, foo: undefined}; | ||
someDict.y = someArray; | ||
someArray.push(someDict); | ||
someArray.push(someDict.x); | ||
var test = { | ||
example: {}, | ||
actual: { | ||
someDict: someDict, | ||
someArray: someArray | ||
}, | ||
result: { | ||
someDict: { | ||
x: { | ||
z: '[Circular ~.someDict]' | ||
}, | ||
y: [ | ||
'[Circular ~.someDict]', | ||
{ | ||
z: '[Circular ~.someDict]' | ||
} | ||
] | ||
}, | ||
someArray: [ | ||
{ | ||
x: { | ||
z: '[Circular ~.someArray.0]' | ||
}, | ||
y: '[Circular ~.someArray]' | ||
}, | ||
{ | ||
z: { | ||
x: '[Circular ~.someArray.1]', | ||
y: '[Circular ~.someArray]' | ||
} | ||
} | ||
] | ||
} | ||
}; | ||
return test; | ||
})(), | ||
// Wholistic, complex multi-item array test | ||
{ | ||
example: [{ | ||
@@ -471,2 +595,146 @@ id: 123, | ||
// $$\ $$\ $$\ $$$\ $$$\ | ||
// $$ | \__| $$ | $$ _| \$$\ | ||
// $$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$ /$$$$\ $$$$\ $$$$\ \$$\ | ||
// $$ _____|\_$$ _| $$ __$$\ $$ |$$ _____|\_$$ _| $$ __$$\ $$ __$$\ $$ | \____|\____|\____| $$ | | ||
// \$$$$$$\ $$ | $$ | \__|$$ |$$ / $$ | $$$$$$$$ |$$ / $$ | $$ | $$$$\ $$$$\ $$$$\ $$ | | ||
// \____$$\ $$ |$$\ $$ | $$ |$$ | $$ |$$\ $$ ____|$$ | $$ | \$$\ \____|\____|\____|$$ | | ||
// $$$$$$$ | \$$$$ |$$ | $$ |\$$$$$$$\ \$$$$ | \$$$$$$$\ \$$$$$$$ | \$$$\ $$$ / | ||
// \_______/ \____/ \__| \__| \_______| \____/ \_______| \____$$ | \___| \___/ | ||
// $$ | | ||
// $$ | | ||
// \__| | ||
// | ||
// (strictEq / isNew checks to assert for and | ||
// against passing-by-reference in different | ||
// situations) | ||
//////////////////////////////////////////////// | ||
//////////////////////////////////////////////// | ||
// example: '*' | ||
// result value should always be strictly equal (===) | ||
//////////////////////////////////////////////// | ||
{ example: undefined, actual: {}, strictEq: true }, | ||
{ example: undefined, actual: {a:23,b:'asdg',c:true,d: {x:32,y:'sagd',z: [{a:2,b:'gsda',c:false}]}, e: [2]}, strictEq: true }, | ||
{ example: undefined, actual: [], strictEq: true }, | ||
{ example: undefined, actual: [{a:23,b:'asdg',c:true,d: {x:32,y:'sagd',z: [{a:2,b:'gsda',c:false}]}, e: [2]}], strictEq: true }, | ||
{ example: undefined, actual: /some regexp/, strictEq: true }, | ||
{ example: undefined, actual: function (){}, strictEq: true }, | ||
{ example: undefined, actual: new Date('November 5, 1605 GMT'), strictEq: true }, | ||
{ example: undefined, actual: new (require('stream').Readable)(), strictEq: true }, | ||
{ example: undefined, actual: new Buffer('asdf'), strictEq: true }, | ||
{ example: undefined, actual: new Error('asdf'), strictEq: true }, | ||
//////////////////////////////////////////////////////////////////////////////////////////////// | ||
// example: nested '*' in dictionaries/arrays | ||
// TODO: needs to be tested some other way, since we'd be checking reference passing within another nested obj. | ||
// also check against strict equality between sub-values (`!==` between nested things...) | ||
// This is prbly eaiest if we just pull it out into a separate test; ie. don't make the test declarative. | ||
//////////////////////////////////////////////////////////////////////////////////////////////// | ||
//////////////////////////////////////////////// | ||
// example: {} should perform a deep copy on things | ||
//////////////////////////////////////////////// | ||
{ example: {}, actual: {}, isNew: true }, | ||
{ example: {}, actual: {a:23,b:'asdg',c:true,d: {x:32,y:'sagd',z: [{a:2,b:'gsda',c:false}]}, e: [2]}, isNew: true }, | ||
//////////////////////////////////////////////// | ||
// example: {...} should perform a shallow copy | ||
// Assert pass-by-reference behavior for more specific `example` | ||
//////////////////////////////////////////////// | ||
{ | ||
example: { id: 123, title: 'Scott', body: 'Scott', votes: 33, resolved: true }, | ||
actual: {}, | ||
result: { id: 0, title: '', body: '', votes: 0, resolved: false }, | ||
isNew: true | ||
}, | ||
{ | ||
example: { id: 123, title: 'Scott', body: 'Scott', votes: 33, resolved: true }, | ||
actual: {a:23,b:'asdg',c:true,d: {x:32,y:'sagd',z: [{a:2,b:'gsda',c:false}]}, e: [2]}, | ||
result: { id: 0, title: '', body: '', votes: 0, resolved: false }, | ||
isNew: true | ||
}, | ||
{ | ||
example: { id: 123, title: 'Scott', body: 'Scott', votes: 33, resolved: true, something: '*' }, | ||
actual: {}, | ||
result: { id: 0, title: '', body: '', votes: 0, resolved: false, something: undefined }, | ||
isNew: true | ||
}, | ||
{ | ||
example: { id: 123, title: 'Scott', body: 'Scott', votes: 33, resolved: true, something: '*' }, | ||
actual: {a:23,b:'asdg',c:true,d: {x:32,y:'sagd',z: [{a:2,b:'gsda',c:false}]}, e: [2]}, | ||
result: { id: 0, title: '', body: '', votes: 0, resolved: false, something: undefined }, | ||
isNew: true | ||
}, | ||
{ | ||
example: { id: 123, title: 'Scott', body: 'Scott', votes: 33, resolved: true, something: '*' }, | ||
actual: { something: new Date('November 5, 1605 GMT')}, | ||
result: { id: 0, title: '', body: '', votes: 0, resolved: false, something: new Date('November 5, 1605 GMT') }, | ||
isNew: true | ||
}, | ||
{ | ||
example: { id: 123, title: 'Scott', body: 'Scott', votes: 33, resolved: true, something: '*' }, | ||
actual: { something: new Date('November 5, 1605 GMT'), a:23,b:'asdg',c:true,d: {x:32,y:'sagd',z: [{a:2,b:'gsda',c:false}]}, e: [2]}, | ||
result: { id: 0, title: '', body: '', votes: 0, resolved: false, something: new Date('November 5, 1605 GMT') }, | ||
isNew: true | ||
}, | ||
//////////////////////////////////////////////// | ||
// example: [] should copy things | ||
//////////////////////////////////////////////// | ||
{ example: [], actual: [], isNew: true }, | ||
{ example: [], actual: [{a:23,b:'asdg',c:true,d: {x:32,y:'sagd',z: [{a:2,b:'gsda',c:false}]}, e: [2]}], isNew: true }, | ||
//////////////////////////////////////////////// | ||
// example: {...} should perform a shallow copy | ||
// Assert pass-by-reference behavior for more specific `example`s | ||
//////////////////////////////////////////////// | ||
{ | ||
example: [{ id: 123, title: 'Scott', body: 'Scott', votes: 0, resolved: true }], | ||
actual: [], | ||
result: [], | ||
isNew: true | ||
}, | ||
{ | ||
example: [{ id: 123, title: 'Scott', body: 'Scott', votes: 0, resolved: true }], | ||
actual: [{a:23,b:'asdg',c:true,d: {x:32,y:'sagd',z: [{a:2,b:'gsda',c:false}]}, e: [2]}], | ||
result: [{ id: 0, title: '', body: '', votes: 0, resolved: false }], | ||
isNew: true | ||
}, | ||
{ | ||
example: [{ id: 123, title: 'Scott', body: 'Scott', votes: 33, resolved: true, something: '*' }], | ||
actual: [], | ||
result: [], | ||
isNew: true | ||
}, | ||
{ | ||
example: [{ id: 123, title: 'Scott', body: 'Scott', votes: 33, resolved: true, something: '*' }], | ||
actual: [{a:23,b:'asdg',c:true,d: {x:32,y:'sagd',z: [{a:2,b:'gsda',c:false}]}, e: [2]}], | ||
result: [{ id: 0, title: '', body: '', votes: 0, resolved: false, something: undefined }], | ||
isNew: true | ||
}, | ||
{ | ||
example: [{ id: 123, title: 'Scott', body: 'Scott', votes: 33, resolved: true, something: '*' }], | ||
actual: [{ something: new Date('November 5, 1605 GMT')}], | ||
result: [{ id: 0, title: '', body: '', votes: 0, resolved: false, something: new Date('November 5, 1605 GMT') }], | ||
isNew: true | ||
}, | ||
{ | ||
example: [{ id: 123, title: 'Scott', body: 'Scott', votes: 33, resolved: true, something: '*' }], | ||
actual: [{ something: new Date('November 5, 1605 GMT'), a:23,b:'asdg',c:true,d: {x:32,y:'sagd',z: [{a:2,b:'gsda',c:false}]}, e: [2]}], | ||
result: [{ id: 0, title: '', body: '', votes: 0, resolved: false, something: new Date('November 5, 1605 GMT') }], | ||
isNew: true | ||
}, | ||
]; |
@@ -20,7 +20,16 @@ /** | ||
if (_.isUndefined(test.example)) { | ||
starTests.push({ | ||
var newTest = { | ||
example: '*', | ||
actual: _.cloneDeep(test.actual), | ||
result: _.cloneDeep(test.result) | ||
}); | ||
actual: test.actual | ||
}; | ||
if (test.hasOwnProperty('result')) { | ||
newTest.result = test.result; | ||
} | ||
if (test.hasOwnProperty('strictEq')) { | ||
newTest.strictEq = _.cloneDeep(test.strictEq); | ||
} | ||
if (test.hasOwnProperty('isNew')) { | ||
newTest.isNew = _.cloneDeep(test.isNew); | ||
} | ||
starTests.push(newTest); | ||
} | ||
@@ -40,4 +49,5 @@ }); | ||
// • tests that expect a result===`undefined` | ||
// • tests that verify `strictEq` or `isNew` | ||
// (nested behavior is different in these cases^) | ||
if (!_.isUndefined(test.example) && !test.error && !_.isUndefined(test.result)) { | ||
if (!test.error && !_.isUndefined(test.result) && !test.hasOwnProperty('strictEq') && !test.hasOwnProperty('isNew')) { | ||
@@ -44,0 +54,0 @@ // test one level of additional array nesting |
@@ -26,2 +26,6 @@ /** | ||
if (test.strictEq && test.isNew) { | ||
throw new Error('INVALID TEST: `isNew` and `strictEq` are mutually exclusive opposites- cannot use them together. For reference, this is test:\n'+util.inspect(test, false, null)); | ||
} | ||
var actualDisplayName = (_.isObject(test.actual)&&test.actual.constructor && test.actual.constructor.name !== 'Object' && test.actual.constructor.name !== 'Array')?test.actual.constructor.name:util.inspect(test.actual, false, null); | ||
@@ -38,3 +42,2 @@ | ||
} | ||
if (!_.isUndefined(test.example)) { | ||
@@ -49,3 +52,2 @@ msg += 'with a '+getDisplayType(test.example)+' example ('+util.inspect(test.example,false, null)+')'; | ||
} | ||
return msg; | ||
@@ -60,3 +62,18 @@ })(), function suite (){ | ||
it(util.format('should coerce %s', actualDisplayName, 'into '+util.inspect(test.result, false, null)+''), function (done){ | ||
var itMsg = 'should '; | ||
if (test.strictEq) { | ||
itMsg+='maintain strict equality (===) when ' + actualDisplayName + ' is provided'; | ||
} | ||
else if (test.isNew) { | ||
if (test.hasOwnProperty('result')) { | ||
itMsg+='convert ' + actualDisplayName + ' into a new (!== original) value '+util.inspect(test.result, false, null); | ||
} | ||
else { | ||
itMsg+='take ' + actualDisplayName + ' and yield a copy (which !== original)'; | ||
} | ||
} | ||
else { | ||
itMsg+='convert ' + actualDisplayName + ' into '+util.inspect(test.result, false, null); | ||
} | ||
it(itMsg, function (done){ | ||
runTestFn(test, done); | ||
@@ -63,0 +80,0 @@ }); |
@@ -196,2 +196,4 @@ // Export the array of tests below | ||
// ARRAYS | ||
// (all of the tests below pass w/ either [] or ['*'] | ||
// however note they do have subtle differences re: strictEq) | ||
//////////////////////////////////////////// | ||
@@ -242,11 +244,16 @@ | ||
{ example: {a:1}, actual: {a: undefined}, error: true }, | ||
{ example: {a:1}, actual: {}, error: true }, | ||
// Missing keys (`*` case) | ||
{ example: {a:'*'}, actual: {a: undefined}, error: true }, | ||
{ example: {a:'*'}, actual: {}, error: true }, | ||
// Keys with `undefined` values (`{}` case) | ||
// Strip keys with `undefined` values (`{}` case) | ||
{ example: {}, actual: {a: undefined, b: 3}, result: {b: 3} }, | ||
// Keys with `undefined` values (`*` case) | ||
{ example: '*', actual: {a: undefined, b: 3}, result: {b: 3} }, | ||
{ example: [{}], actual: [{a: undefined, b: 3}], result: [{b: 3}] }, | ||
{ example: {x:{}}, actual: {x:{a: undefined, b: 3}}, result: {x:{b: 3}} }, | ||
// Don't strip keys with `undefined` values (`*` case) | ||
{ example: '*', actual: {a: undefined, b: 3}, result: {a: undefined, b: 3} }, | ||
// Extra keys: | ||
@@ -301,23 +308,179 @@ { example: {a:1, b:'hi'}, actual: {a: 23, b: 'stuff', d: true}, result: {a: 23, b: 'stuff'} }, | ||
// $$\ | ||
// \__| | ||
// $$$$$$\ $$$$$$\ $$$$$$$\ $$\ $$\ $$$$$$\ $$$$$$$\ $$\ $$\ $$\ $$$$$$\ | ||
// $$ __$$\ $$ __$$\ $$ _____|$$ | $$ |$$ __$$\ $$ _____|$$ |\$$\ $$ |$$ __$$\ | ||
// $$ | \__|$$$$$$$$ |$$ / $$ | $$ |$$ | \__|\$$$$$$\ $$ | \$$\$$ / $$$$$$$$ | | ||
// $$ | $$ ____|$$ | $$ | $$ |$$ | \____$$\ $$ | \$$$ / $$ ____| | ||
// $$ | \$$$$$$$\ \$$$$$$$\ \$$$$$$ |$$ | $$$$$$$ |$$ | \$ / \$$$$$$$\ | ||
// \__| \_______| \_______| \______/ \__| \_______/ \__| \_/ \_______| | ||
// | ||
// | ||
// | ||
// Some basic deep dicts and array | ||
{ | ||
example: {a:1, b:'hi', c: false}, | ||
actual: {a: 23}, | ||
error: true | ||
}, | ||
{ | ||
example: {a:1, b:'hi', c: false}, | ||
actual: {a: 23, d: true}, | ||
error: true | ||
}, | ||
// Ensure that this allows extra keys when coercing to `example: {}` | ||
{ | ||
example: {}, | ||
actual: {a: 23, d: true}, | ||
result: {a: 23, d: true} | ||
}, | ||
// Omit extra keys when coercing to `example: {...}` | ||
{ | ||
example: { a:23 }, | ||
actual: {a: 23, d: true}, | ||
result: {a: 23} | ||
}, | ||
// Reject when there are missing or undefined required keys | ||
{ example: {b: 235}, actual: {b: undefined}, error: true }, | ||
{ example: {b: 235}, actual: {}, error: true }, | ||
// Strip extra keys with `undefined` values (`{...}` case) | ||
{ example: {b: 235}, actual: {a: undefined, b: 3}, result: {b: 3} }, | ||
// Strip extra nested keys with `undefined` values (`{...}` case) | ||
{ example: {a: {}, b: 235}, actual: {a: {x: undefined}, b: 3}, result: {a: {}, b: 3} }, | ||
// Strip keys with `undefined` values (`{}` case) | ||
{ example: {}, actual: {a: undefined, b: 3}, result: {b: 3} }, | ||
// Strip nested keys with `undefined` values (`{}` case) | ||
{ example: {}, actual: {a: {x: undefined}, b: 3}, result: {a: {}, b: 3} }, | ||
// Don't strip keys or nested keys with `undefined` values (`*` and nested `*` cases) | ||
{ example: '*', actual: {a: undefined, b: 3, c: {x: undefined}}, result: {a: undefined, b: 3, c: {x: undefined}} }, | ||
{ example: {c:'*'}, actual: {a: undefined, b: 3, c: {x: undefined}}, result: { c: {x: undefined}} }, | ||
// Ensure that this allows arbitary arrays when coercing to `example: []` | ||
{ | ||
example: [], | ||
actual: [{a: 23, d: true}], | ||
result: [{a: 23, d: true}] | ||
}, | ||
// Ensure that nested dictionaries inside of an array passed | ||
// through `example: []` are stripped of keys with undefined values | ||
{ | ||
example: [], | ||
actual: [{a:3, b: undefined}, {a: undefined}], | ||
result: [{a: 3},{}] | ||
}, | ||
{ | ||
example: [], | ||
actual: [{a:3,someStuff: [{x: undefined, y: 'foo'}, {x: 'bar', y: undefined}]},{a: 5, b: undefined}], | ||
result: [{a: 3, someStuff: [{y:'foo'}, {x:'bar'}]}, {a: 5}] | ||
}, | ||
// Ensure that nested dictionaries inside of an array passed | ||
// through `example: ['*']` are NOT stripped of keys with undefined values-- | ||
// and are left utterly alone | ||
{ | ||
example: ['*'], | ||
actual: [{a:3, b: undefined}, {a: undefined}], | ||
result: [{a: 3, b: undefined},{a:undefined}] | ||
}, | ||
{ | ||
example: ['*'], | ||
actual: [{a:3,someStuff: [{x: undefined, y: 'foo'}, {x: 'bar', y: undefined}]},{a: 5, b: undefined}], | ||
result: [{a:3,someStuff: [{x: undefined, y: 'foo'}, {x: 'bar', y: undefined}]},{a: 5, b: undefined}] | ||
}, | ||
// Ensure the recursive cloning / undefined-key-stripping doesn't get | ||
// stumped by circular dictionaries/arrays. | ||
// • dict keys whose values point to a past reference should be deleted | ||
// • array items that point to past references should be pruned | ||
(function (){ | ||
var regexp = /some regexp/; | ||
return { example: '*', actual: regexp, result: regexp, }; | ||
var someDict = {}; | ||
var someArray = []; | ||
someDict.x = {z: someDict, foo: undefined}; | ||
someDict.y = someArray; | ||
someArray.push(someDict); | ||
someArray.push(someDict.x); | ||
var test = { | ||
example: {}, | ||
actual: { | ||
someDict: someDict, | ||
someArray: someArray | ||
}, | ||
result: { | ||
someDict: { | ||
x: { | ||
z: '[Circular ~.someDict]' | ||
}, | ||
y: [ | ||
'[Circular ~.someDict]', | ||
{ | ||
z: '[Circular ~.someDict]' | ||
} | ||
] | ||
}, | ||
someArray: [ | ||
{ | ||
x: { | ||
z: '[Circular ~.someArray.0]' | ||
}, | ||
y: '[Circular ~.someArray]' | ||
}, | ||
{ | ||
z: { | ||
x: '[Circular ~.someArray.1]', | ||
y: '[Circular ~.someArray]' | ||
} | ||
} | ||
] | ||
} | ||
}; | ||
return test; | ||
})(), | ||
(function (){ | ||
var fn = function (){}; | ||
return { example: '*', actual: fn, result: fn, }; | ||
})(), | ||
{ example: '*', actual: new Date('November 5, 1605 GMT'), result: new Date('November 5, 1605 GMT'), }, | ||
{ example: '*', actual: new (require('stream').Readable)(), result: new (require('stream').Readable)(), }, | ||
(function (){ | ||
var buffer = new Buffer('asdf'); | ||
return { example: '*', actual: buffer, result: buffer }; | ||
})(), | ||
(function (){ | ||
var err = new Error('asdf'); | ||
return { example: '*', actual: err, result: err, }; | ||
})() | ||
// $$\ $$\ $$\ $$$\ $$$\ | ||
// $$ | \__| $$ | $$ _| \$$\ | ||
// $$$$$$$\ $$$$$$\ $$$$$$\ $$\ $$$$$$$\ $$$$$$\ $$$$$$\ $$$$$$\ $$ /$$$$\ $$$$\ $$$$\ \$$\ | ||
// $$ _____|\_$$ _| $$ __$$\ $$ |$$ _____|\_$$ _| $$ __$$\ $$ __$$\ $$ | \____|\____|\____| $$ | | ||
// \$$$$$$\ $$ | $$ | \__|$$ |$$ / $$ | $$$$$$$$ |$$ / $$ | $$ | $$$$\ $$$$\ $$$$\ $$ | | ||
// \____$$\ $$ |$$\ $$ | $$ |$$ | $$ |$$\ $$ ____|$$ | $$ | \$$\ \____|\____|\____|$$ | | ||
// $$$$$$$ | \$$$$ |$$ | $$ |\$$$$$$$\ \$$$$ | \$$$$$$$\ \$$$$$$$ | \$$$\ $$$ / | ||
// \_______/ \____/ \__| \__| \_______| \____/ \_______| \____$$ | \___| \___/ | ||
// $$ | | ||
// $$ | | ||
// \__| | ||
// | ||
// (strictEq / isNew checks to assert for and | ||
// against passing-by-reference in different | ||
// situations) | ||
//////////////////////////////////////////////// | ||
{ example: '*', actual: /some regexp/, strictEq: true }, | ||
{ example: '*', actual: function (){}, strictEq: true }, | ||
{ example: '*', actual: new Date('November 5, 1605 GMT'), strictEq: true }, | ||
{ example: '*', actual: new (require('stream').Readable)(), strictEq: true }, | ||
{ example: '*', actual: new Buffer('asdf'), strictEq: true }, | ||
{ example: '*', actual: new Error('asdf'), strictEq: true }, | ||
]; | ||
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
89702
1944