Comparing version 6.0.0 to 7.0.0
@@ -5,2 +5,12 @@ /* eslint-disable prettier/prettier */ | ||
// Regexes. | ||
const R_UPPER = /^[A-Z][A-Z0-9]*$/; | ||
const R_LOWER = /^[a-z][a-z0-9]*$/; | ||
const R_CAMEL = /^[a-z][a-zA-Z0-9]*$/; | ||
const R_PASCAL = /^[A-Z][a-zA-Z0-9]*$/; | ||
const R_SNAKE = /^[a-z][a-z0-9]*(_[a-z0-9]+)*$/; | ||
const R_SCREAMING = /^[A-Z][A-Z0-9]*(_[A-Z0-9]+)*$/; | ||
const R_KEBAB = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/; | ||
const R_TRAIN = /^[A-Z][a-zA-Z0-9]*(-[A-Z0-9][a-zA-Z0-9]+)*$/; | ||
// Checkers. | ||
@@ -18,21 +28,23 @@ function isNull(v) { return v === null; } | ||
function isNaN(v) { return Number.isNaN(v); } | ||
function isFiniteNumber(v) { return typeof v === "number" && Number.isFinite(v); } | ||
function isPositiveNumber(v) { return typeof v === "number" && Number.isFinite(v) && v >= 0; } | ||
function isNegativeNumber(v) { return typeof v === "number" && Number.isFinite(v) && v <= 0; } | ||
function isInteger(v) { return typeof v === "number" && Number.isInteger(v) && v >= Number.MIN_SAFE_INTEGER && v <= Number.MAX_SAFE_INTEGER; } | ||
function isPositiveInteger(v) { return typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= Number.MAX_SAFE_INTEGER; } | ||
function isNegativeInteger(v) { return typeof v === "number" && Number.isInteger(v) && v >= Number.MIN_SAFE_INTEGER && v <= 0; } | ||
function isFiniteNumber(v) { return Number.isFinite(v); } | ||
function isPositiveNumber(v) { return Number.isFinite(v) && v >= 0; } | ||
function isNegativeNumber(v) { return Number.isFinite(v) && v <= 0; } | ||
function isInteger(v) { return Number.isInteger(v) && v >= Number.MIN_SAFE_INTEGER && v <= Number.MAX_SAFE_INTEGER; } | ||
function isPositiveInteger(v) { return Number.isInteger(v) && v >= 0 && v <= Number.MAX_SAFE_INTEGER; } | ||
function isNegativeInteger(v) { return Number.isInteger(v) && v >= Number.MIN_SAFE_INTEGER && v <= 0; } | ||
function isString(v) { return typeof v === "string"; } | ||
function isNonEmptyString(v) { return typeof v === "string" && v.length > 0; } | ||
function isLowerString(v) { return typeof v === "string" && v === v.toLowerCase(); } | ||
function isNonEmptyLowerString(v) { return typeof v === "string" && v.length > 0 && v === v.toLowerCase(); } | ||
function isUpperString(v) { return typeof v === "string" && v === v.toUpperCase(); } | ||
function isNonEmptyUpperString(v) { return typeof v === "string" && v.length > 0 && v === v.toUpperCase(); } | ||
function isLower(v) { return typeof v === "string" && R_LOWER.test(v); } | ||
function isUpper(v) { return typeof v === "string" && R_UPPER.test(v); } | ||
function isCamel(v) { return typeof v === "string" && R_CAMEL.test(v); } | ||
function isPascal(v) { return typeof v === "string" && R_PASCAL.test(v); } | ||
function isSnake(v) { return typeof v === "string" && R_SNAKE.test(v); } | ||
function isScreaming(v) { return typeof v === "string" && R_SCREAMING.test(v); } | ||
function isKebab(v) { return typeof v === "string" && R_KEBAB.test(v); } | ||
function isTrain(v) { return typeof v === "string" && R_TRAIN.test(v); } | ||
function isPrimitive(v) { return v === undefined || v === null || typeof v === "boolean" || typeof v === "string" || Number.isFinite(v); } | ||
function isFunction(v) { return typeof v === "function"; } | ||
function isObject(v) { return typeof v === "object" && v !== null; } | ||
function isPlainObject(v) { return v instanceof Object && v.constructor === Object; } | ||
function isNonEmptyPlainObject(v) { return v instanceof Object && v.constructor === Object && Object.keys(v).length > 0; } | ||
function isIterable(v) { return typeof v === "object" && typeof v[Symbol.iterator] === "function"; } | ||
function isPlainArray(v) { return v instanceof Array && v.constructor === Array; } | ||
function isNonEmptyPlainArray(v) { return v instanceof Array && v.constructor === Array && v.length > 0; } | ||
function isPlainObject(v) { return typeof v === "object" && v !== null && Object.getPrototypeOf(v).constructor === Object; } | ||
function isIterable(v) { return typeof v === "object" && v !== null && typeof v[Symbol.iterator] === "function"; } | ||
function isPlainArray(v) { return v instanceof Array && Object.getPrototypeOf(v).constructor === Array; } | ||
function isArraylike(v) { return typeof v === "object" && v !== null && v.hasOwnProperty("length") && typeof v.length === "number" && Number.isInteger(v.length) && v.length >= 0 && v.length <= Number.MAX_SAFE_INTEGER; } | ||
@@ -42,8 +54,6 @@ function isDate(v) { return v instanceof Date; } | ||
function isPastDate(v) { return v instanceof Date && v.getTime() < Date.now(); } | ||
function isMap(v) { return v instanceof Map && v.constructor === Map; } | ||
function isNonEmptyMap(v) { return v instanceof Map && v.constructor === Map && v.size > 0; } | ||
function isWeakMap(v) { return v instanceof WeakMap && v.constructor === WeakMap; } | ||
function isSet(v) { return v instanceof Set && v.constructor === Set; } | ||
function isNonEmptySet(v) { return v instanceof Set && v.constructor === Set && v.size > 0; } | ||
function isWeakSet(v) { return v instanceof WeakSet && v.constructor === WeakSet; } | ||
function isMap(v) { return v instanceof Map; } | ||
function isWeakMap(v) { return v instanceof WeakMap; } | ||
function isSet(v) { return v instanceof Set; } | ||
function isWeakSet(v) { return v instanceof WeakSet; } | ||
function isPromise(v) { return v instanceof Promise; } | ||
@@ -55,32 +65,33 @@ function isRegExp(v) { return v instanceof RegExp; } | ||
// Descriptions. | ||
isNull.desc = "null" | ||
isUndefined.desc = "undefined" | ||
isDefined.desc = "defined" | ||
isBoolean.desc = "true or false" | ||
isTrue.desc = "true" | ||
isFalse.desc = "false" | ||
isTruthy.desc = "truthy" | ||
isFalsy.desc = "falsy" | ||
isZero.desc = "zero" | ||
isOne.desc = "one" | ||
isNaN.desc = "NaN" | ||
isFiniteNumber.desc = "finite number" | ||
isPositiveNumber.desc = "positive finite number" | ||
isNegativeNumber.desc = "negative finite number" | ||
isInteger.desc = "integer" | ||
isPositiveInteger.desc = "positive integer" | ||
isNegativeInteger.desc = "negative integer" | ||
isString.desc = "string" | ||
isNonEmptyString.desc = "non-empty string" | ||
isLowerString.desc = "lowercase string" | ||
isNonEmptyLowerString.desc = "non-empty lowercase string" | ||
isUpperString.desc = "uppercase string" | ||
isNonEmptyUpperString.desc = "non-empty uppercase string" | ||
isNull.desc = "null"; | ||
isUndefined.desc = "undefined"; | ||
isDefined.desc = "defined"; | ||
isBoolean.desc = "true or false"; | ||
isTrue.desc = "true"; | ||
isFalse.desc = "false"; | ||
isTruthy.desc = "truthy"; | ||
isFalsy.desc = "falsy"; | ||
isZero.desc = "zero"; | ||
isOne.desc = "one"; | ||
isNaN.desc = "NaN"; | ||
isFiniteNumber.desc = "finite number"; | ||
isPositiveNumber.desc = "positive finite number"; | ||
isNegativeNumber.desc = "negative finite number"; | ||
isInteger.desc = "integer"; | ||
isPositiveInteger.desc = "positive integer"; | ||
isNegativeInteger.desc = "negative integer"; | ||
isString.desc = "string"; | ||
isLower.desc = "lowercase-only string"; | ||
isUpper.desc = "UPPERCASE-only string"; | ||
isCamel.desc = "camelCase string"; | ||
isPascal.desc = "PascalCase string"; | ||
isSnake.desc = "snake_case string"; | ||
isScreaming.desc = "SCREAMING_SNAKE_CASE string"; | ||
isKebab.desc = "kebab-case string"; | ||
isTrain.desc = "Camel-Kebab-Case string"; | ||
isFunction.desc = "function" | ||
isObject.desc = "object" | ||
isPlainObject.desc = "plain object" | ||
isNonEmptyPlainObject.desc = "plain object with one or more enumerable properties" | ||
isIterable.desc = "iterable object" | ||
isPlainArray.desc = "plain array" | ||
isNonEmptyPlainArray.desc = "plain non-empty array" | ||
isArraylike.desc = "arraylike object with a numeric length property" | ||
@@ -91,6 +102,4 @@ isDate.desc = "date" | ||
isMap.desc = "map" | ||
isNonEmptyMap.desc = "non-empty map" | ||
isWeakMap.desc = "weak map" | ||
isSet.desc = "set" | ||
isNonEmptySet.desc = "non-empty set" | ||
isWeakSet.desc = "weak set" | ||
@@ -104,6 +113,14 @@ isPromise.desc = "promise" | ||
const checkers = { | ||
// Primitives. | ||
"primitive": isPrimitive, | ||
"null": isNull, | ||
"undefined": isUndefined, "void": isUndefined, "undef": isUndefined, | ||
"defined": isDefined, "def": isDefined, | ||
"boolean": isBoolean, "bool": isBoolean, | ||
"undefined": isUndefined, | ||
"void": isUndefined, | ||
"undef": isUndefined, | ||
"defined": isDefined, | ||
"def": isDefined, | ||
// Booleans. | ||
"boolean": isBoolean, | ||
"bool": isBoolean, | ||
"true": isTrue, | ||
@@ -113,40 +130,63 @@ "false": isFalse, | ||
"falsy": isFalsy, | ||
// Numbers. | ||
"zero": isZero, | ||
"one": isOne, | ||
"nan": isNaN, | ||
"number": isFiniteNumber, "num": isFiniteNumber, | ||
"number+": isPositiveNumber, "num+": isPositiveNumber, | ||
"number-": isNegativeNumber, "num-": isNegativeNumber, | ||
"integer": isInteger, "int": isInteger, | ||
"integer+": isPositiveInteger, "int+": isPositiveInteger, | ||
"integer-": isNegativeInteger, "int-": isNegativeInteger, | ||
"string": isString, "str": isString, | ||
"string+": isNonEmptyString, "str+": isNonEmptyString, | ||
"lowercase": isLowerString, "lower": isLowerString, | ||
"lowercase+": isNonEmptyLowerString, "lower+": isNonEmptyLowerString, | ||
"uppercase": isUpperString, "upper": isUpperString, | ||
"uppercase+": isNonEmptyUpperString, "upper+": isNonEmptyUpperString, | ||
"function": isFunction, "func": isFunction, | ||
"number": isFiniteNumber, | ||
"num": isFiniteNumber, | ||
"+number": isPositiveNumber, | ||
"+num": isPositiveNumber, | ||
"-number": isNegativeNumber, | ||
"-num": isNegativeNumber, | ||
"integer": isInteger, | ||
"int": isInteger, | ||
"+integer": isPositiveInteger, | ||
"+int": isPositiveInteger, | ||
"-integer": isNegativeInteger, | ||
"-int": isNegativeInteger, | ||
// Strings. | ||
"string": isString, | ||
"str": isString, | ||
"lower": isLower, | ||
"upper": isUpper, | ||
"camel": isCamel, | ||
"pascal": isPascal, | ||
"snake": isSnake, | ||
"screaming": isScreaming, | ||
"kebab": isKebab, | ||
"slug": isKebab, | ||
"train": isTrain, | ||
// Objects. | ||
"function": isFunction, | ||
"func": isFunction, | ||
"objectlike": isObject, | ||
"object": isPlainObject, "obj": isPlainObject, | ||
"object+": isNonEmptyPlainObject, "obj+": isNonEmptyPlainObject, | ||
"object": isPlainObject, | ||
"obj": isPlainObject, | ||
"iterable": isIterable, | ||
"array": isPlainArray, "arr": isPlainArray, | ||
"array+": isNonEmptyPlainArray, "arr+": isNonEmptyPlainArray, | ||
"arraylike": isArraylike, "arguments": isArraylike, "args": isArraylike, | ||
"circular": isCircular, | ||
"array": isPlainArray, | ||
"arr": isPlainArray, | ||
"arraylike": isArraylike, | ||
"arguments": isArraylike, | ||
"args": isArraylike, | ||
"date": isDate, | ||
"date+": isFutureDate, "future": isFutureDate, | ||
"date-": isPastDate, "past": isPastDate, | ||
"future": isFutureDate, | ||
"past": isPastDate, | ||
"map": isMap, | ||
"map+": isNonEmptyMap, | ||
"weakmap": isWeakMap, | ||
"set": isSet, | ||
"set+": isNonEmptySet, | ||
"weakset": isWeakSet, | ||
"promise": isPromise, | ||
"regex": isRegExp, "regexp": isRegExp, | ||
"regex": isRegExp, | ||
"regexp": isRegExp, | ||
"symbol": isSymbol, | ||
"any": isAny, "mixed": isAny, | ||
"circular": isCircular, | ||
// Other. | ||
"any": isAny, | ||
"mixed": isAny, | ||
"json": isJSONable, | ||
"jsonable": isJSONable, | ||
}; | ||
@@ -153,0 +193,0 @@ |
@@ -26,3 +26,3 @@ /** | ||
// Stop if not plain Array. | ||
if (value.constructor !== Array) return false; | ||
if (Object.getPrototypeOf(value).constructor !== Array) return false; | ||
@@ -45,3 +45,3 @@ // Stop if circular reference. | ||
// Only plain Objects. | ||
if (value.constructor !== Object) return false; | ||
if (Object.getPrototypeOf(value).constructor !== Object) return false; | ||
@@ -48,0 +48,0 @@ // Stop if circular reference. |
@@ -5,2 +5,3 @@ // @flow | ||
const format = require("../functions/format"); | ||
const { CLASS, KEYS, VALUES } = require("../constants"); | ||
@@ -11,2 +12,3 @@ // Vars. | ||
const R_INVERT = /^!/; | ||
const R_NONEMPTY = /\+$/; | ||
const R_OPTIONAL = /\?+$/; | ||
@@ -82,3 +84,3 @@ | ||
// Not found. | ||
throwError(BlorkError, "Checker not found", type, "arguments[0]"); | ||
throwError(BlorkError, "Checker not found", type, "checker(): type"); | ||
} | ||
@@ -98,3 +100,3 @@ | ||
// Check args. | ||
runChecker(this._checkers.string, prefix, "check(): value", BlorkError); | ||
runChecker(this._checkers.string, prefix, "check(): prefix", BlorkError); | ||
@@ -119,10 +121,8 @@ // Check the value against the type. | ||
// Vars. | ||
const l = types.length; | ||
// Recurse into each type. | ||
for (let i = 0; i < l; i++) this._check(args[i], types[i], `arguments[${i}]`, this._error); | ||
types.forEach((t, i) => this._check(args[i], t, `arguments[${i}]`, this._error)); | ||
// No excess arguments. | ||
if (args.length > l) throwError(this._error, `Too many arguments (expected ${l})`, args.length, "arguments"); | ||
if (args.length > types.length) | ||
throwError(this._error, `Must have ${types.length} arguments`, args.length, "arguments"); | ||
} | ||
@@ -136,3 +136,3 @@ | ||
* | ||
* @param {string} name The type reference for the checker. | ||
* @param {string} name The type reference for the checker in kebab-case format. | ||
* @param {function} checker A checker function: Takes a single argument (value), tests it, and returns either true (success) or an error message string in the 'Must be X' format. | ||
@@ -145,3 +145,3 @@ * @param {string} description="" A description of the type of value that's valid for this checker, e.g. "positive number" or "unique string". Defaults to same value as name. | ||
// Check args. | ||
runChecker(this._checkers["lower+"], name, "add(): name", BlorkError); | ||
runChecker(this._checkers.kebab, name, "add(): name", BlorkError); | ||
runChecker(this._checkers.function, checker, "add(): checker", BlorkError); | ||
@@ -167,3 +167,3 @@ runChecker(this._checkers.string, description, "add(): description", BlorkError); | ||
// Check args. | ||
runChecker(this._checkers.function, error, "throws(): error", BlorkError, this); | ||
runChecker(this._checkers.function, error, "throws(): error", BlorkError); | ||
@@ -186,4 +186,4 @@ // Save err. | ||
// Check args. | ||
runChecker(this._checkers.objectlike, object, "props(): object", BlorkError, this); | ||
runChecker(this._checkers["object+"], props, "props(): props", BlorkError, this); | ||
runChecker(this._checkers.objectlike, object, "props(): object", BlorkError); | ||
runChecker(this._checkers.object, props, "props(): props", BlorkError); | ||
@@ -263,3 +263,2 @@ // Loop through every property in props. | ||
* @param {string} type A single stringy type reference (e.g. 'str'), functional shorthand type reference (e.g. `String`), or an object/array with list of types (e.g. `{name:'str'}` or `['str', 'num']`). | ||
* @param {Blorker} checkers An object listing checkers for a Blork isntance. | ||
* @returns {function|undefined} The checker that was lazily created, or undefined if a checker could not be created. | ||
@@ -271,3 +270,3 @@ */ | ||
// Split type and get corresponding checker for each. | ||
const andCheckers = type.split(R_AND).map(this._find, this); | ||
const ands = type.split(R_AND).map(this._find, this); | ||
@@ -277,3 +276,3 @@ // Check each checker. | ||
// Loop through and call each checker. | ||
for (const checker of andCheckers) if (!checker(value)) return false; // Fail. | ||
for (const checker of ands) if (!checker(value)) return false; // Fail. | ||
return true; // Otherwise pass. | ||
@@ -283,3 +282,3 @@ }; | ||
// Description message joins the descriptions for the checkers. | ||
andChecker.desc = andCheckers.map(checker => checker.desc).join(" and "); | ||
andChecker.desc = ands.map(checker => checker.desc).join(" and "); | ||
@@ -294,3 +293,3 @@ // Add the AND checker to the list of checkers now it has been created. | ||
// Split type and get corresponding checker for each. | ||
const orCheckers = type.split(R_OR).map(this._find, this); | ||
const ors = type.split(R_OR).map(this._find, this); | ||
@@ -300,3 +299,3 @@ // Check each checker. | ||
// Loop through and call each checker. | ||
for (const checker of orCheckers) if (checker(value)) return true; // Pass. | ||
for (const checker of ors) if (checker(value)) return true; // Pass. | ||
return false; // Otherwise fail. | ||
@@ -306,3 +305,3 @@ }; | ||
// Description message joins the descriptions for the checkers. | ||
orChecker.desc = orCheckers.map(checker => checker.desc).join(" or "); | ||
orChecker.desc = ors.map(checker => checker.desc).join(" or "); | ||
@@ -317,10 +316,10 @@ // Add the OR checker to the list of checkers now it has been created. | ||
// Find non optional checker (strip '!'). | ||
const normalChecker = this._find(type.replace(R_INVERT, "")); | ||
const checker = this._find(type.replace(R_INVERT, "")); | ||
// Create an optional checker for this optional type. | ||
// Returns 0 if undefined, or passes through to the normal checker. | ||
const invertedChecker = v => !normalChecker(v); | ||
const invertedChecker = v => !checker(v); | ||
// Description message joins the descriptions for the checkers. | ||
invertedChecker.desc = `not ${normalChecker.desc}`; | ||
invertedChecker.desc = `not ${checker.desc}`; | ||
@@ -332,13 +331,41 @@ // Add the invertedChecker to the list and return it. | ||
// Non-empty value (ends with '+'), e.g. "str+" | ||
if (R_NONEMPTY.test(type)) { | ||
// Find non optional checker (strip '+'). | ||
const checker = this._find(type.replace(R_NONEMPTY, "")); | ||
// Create a length checker for this optional type. | ||
// Returns true if checker passes and there's a numeric length or size property with a value of >0. | ||
const lengthChecker = v => { | ||
// Must pass the checker. | ||
if (!checker(v)) return false; | ||
// Map and Set use .size | ||
if (v instanceof Map || v instanceof Set) return v.size > 0; | ||
// String and Array use .length | ||
if (typeof v === "string" || v instanceof Array) return v.length > 0; | ||
// Objects use key length. | ||
if (typeof v === "object" && v !== null) return Object.keys(v).length > 0; | ||
// Everything else (numbers, booleans, null, undefined) do a falsy check. | ||
return !!v; | ||
}; | ||
// Description message joins the descriptions for the checkers. | ||
lengthChecker.desc = `non-empty ${checker.desc}`; | ||
// Add the lengthChecker to the list and return it. | ||
this._checkers[type] = lengthChecker; | ||
return lengthChecker; | ||
} | ||
// Optional value (ends with '?'), e.g. "num?" | ||
if (R_OPTIONAL.test(type)) { | ||
// Find non optional checker (strip '?'). | ||
const nonOptionalChecker = this._find(type.replace(R_OPTIONAL, "")); | ||
const checker = this._find(type.replace(R_OPTIONAL, "")); | ||
// Create an optional checker for this optional type. | ||
// Returns 0 if undefined, or passes through to the normal checker. | ||
const optionalChecker = v => (v === undefined ? true : nonOptionalChecker(v)); | ||
const optionalChecker = v => (v === undefined ? true : checker(v)); | ||
// Description message joins the descriptions for the checkers. | ||
optionalChecker.desc = `${nonOptionalChecker.desc} or empty`; | ||
optionalChecker.desc = `${checker.desc} or empty`; | ||
@@ -410,3 +437,3 @@ // Add the optionalChecker to the list and return it. | ||
if (stack.indexOf(type) >= 0) | ||
throwError(BlorkError, "Blork type must not contain circular references", type, prefix); | ||
throwError(BlorkError, "Blork type must not contain circular references", type); | ||
@@ -417,4 +444,3 @@ // Value can have circular references, but don't keep checking it over and over. | ||
// Push type and value into the stack. | ||
stack.push(type); | ||
stack.push(value); | ||
stack = [...stack, type, value]; | ||
} else { | ||
@@ -429,17 +455,11 @@ // First loop. Start a stack. | ||
// Loop through items and check they match type[0] | ||
const length = value.length; | ||
for (let i = 0; i < length; i++) this._check(value[i], type[0], `${prefix}[${i}]`, error, stack); | ||
value.forEach((v, i) => this._check(v, type[0], `${prefix}[${i}]`, error, stack)); | ||
} else { | ||
// Tuple array | ||
// Loop through types and match each with a value recursively. | ||
const length = type.length; | ||
for (let i = 0; i < length; i++) this._check(value[i], type[i], `${prefix}[${i}]`, error, stack); | ||
type.forEach((t, i) => this._check(value[i], t, `${prefix}[${i}]`, error, stack)); | ||
// No excess items in a tuple. | ||
if (value.length > length) throwError(error, `Must have ${length} items`, value.length, prefix); | ||
if (value.length > type.length) throwError(error, `Must have ${type.length} items`, value.length, prefix); | ||
} | ||
// Pass. | ||
stack.pop(); | ||
stack.pop(); | ||
} | ||
@@ -460,4 +480,11 @@ | ||
_checkObject(value, type, prefix, error, stack) { | ||
// Value itself must be an object (check using the object checker). | ||
runChecker(this._checkers.object, value, prefix, error); | ||
// CLASS type key. | ||
const objectType = type[CLASS]; | ||
if (objectType) { | ||
// Must be plain checker. | ||
this._check(value, objectType, prefix, error, stack); | ||
} else { | ||
// Must be plain checker. | ||
runChecker(this._checkers.object, value, prefix, error); | ||
} | ||
@@ -468,3 +495,3 @@ // Prevent infinite loops. | ||
if (stack.indexOf(type) >= 0) | ||
throwError(BlorkError, "Blork type must not contain circular references", type, prefix); | ||
throwError(BlorkError, "Blork type must not contain circular references", type); | ||
@@ -475,4 +502,3 @@ // Value can have circular references, but don't keep checking it over and over. | ||
// Push type and value into the stack. | ||
stack.push(type); | ||
stack.push(value); | ||
stack = [...stack, type, value]; | ||
} else { | ||
@@ -487,30 +513,30 @@ // First loop. Kick off a stack. | ||
// Loop through each item in the types object. | ||
for (let i = 0; i < typeKeys.length; i++) { | ||
const key = typeKeys[i]; | ||
// Ignore the ANY key. | ||
if (key !== "_any") this._check(value[key], type[key], `${prefix}.${key}`, error, stack); | ||
} | ||
typeKeys.forEach(key => { | ||
// Check that the value matches the specified key. | ||
this._check(value[key], type[key], `${prefix}.${key}`, error, stack); | ||
}); | ||
// Is there an ANY key? | ||
if (type.hasOwnProperty("_any")) { | ||
// Get the KEYS and VALUES types. | ||
// KEYS is used to check all keys against a checker, e.g. checking that keys are camelCase. | ||
// VALUES is used to check that excess values on the object match a specified type. | ||
const keysType = type[KEYS]; | ||
const valuesType = type[VALUES]; | ||
// Is there an KEYS or VALUES type? | ||
if (keysType || valuesType) { | ||
// Vars. | ||
const valueKeys = Object.keys(value); | ||
// Check that we actually need to do this loop by comparing the lengths. | ||
// -1 subtracts the _any key itself. | ||
if (valueKeys.length > typeKeys.length - 1) { | ||
// Make a list of the excess keys (that are in valueKeys but not in typeKeys). | ||
const excessKeys = valueKeys.filter(v => !~typeKeys.indexOf(v)); | ||
// Check that we ACTUALLY need to do this loop by comparing the lengths. | ||
if (valueKeys.length > typeKeys.length) { | ||
// Loop through excess keys (that are in valueKeys but not in typeKeys). | ||
valueKeys.filter(v => !~typeKeys.indexOf(v)).forEach(key => { | ||
// If there's a KEYS type, check the key against that. | ||
if (keysType) this._check(key, keysType, `${prefix}.${key}: Key`, error, stack); | ||
// Loop through all excess keys and check against the ANY key. | ||
for (let i = 0; i < excessKeys.length; i++) { | ||
const key = excessKeys[i]; | ||
this._check(value[key], type._any, `${prefix}.${key}`, error, stack); | ||
} | ||
// Check the value against the VALUES type. | ||
if (valuesType) this._check(value[key], valuesType, `${prefix}.${key}`, error, stack); | ||
}); | ||
} | ||
} | ||
// Pass. | ||
stack.pop(); | ||
stack.pop(); | ||
} | ||
@@ -517,0 +543,0 @@ } |
@@ -6,2 +6,3 @@ const ValueError = require("./errors/ValueError"); | ||
const format = require("./functions/format"); | ||
const { CLASS, KEYS, VALUES } = require("./constants"); | ||
@@ -23,1 +24,4 @@ // Make a default instance. | ||
exports.ValueError = ValueError; | ||
exports.CLASS = CLASS; | ||
exports.KEYS = KEYS; | ||
exports.VALUES = VALUES; |
{ | ||
"name": "blork", | ||
"description": "Blork! Mini runtime type checking in Javascript", | ||
"version": "6.0.0", | ||
"version": "7.0.0", | ||
"license": "0BSD", | ||
@@ -6,0 +6,0 @@ "author": "Dave Houlbrooke <dave@shax.com>", |
255
README.md
@@ -167,6 +167,6 @@ # Blork! Mini runtime type checking in Javascript | ||
1. `name` The name of the custom checker you'll use to reference it later | ||
1. `name` The name of the custom checker (only kebab-case strings allowed). | ||
2. `checker` A function that accepts a single argument, `value`, and returns `true` or `false`. | ||
3. `description=""` An description for the value the checker will accept, e.g. "lowercase string" or "unique username", that is shown in the error message. Defaults to the value of `name`. | ||
4. `error=undefined` A custom class that is thrown when this checker fails (can be _any_ class, not just classes extending `Error`). An error set with `add() takes precedence for this checker over the error set through `throws()`. | ||
4. `error=undefined` A custom class that is thrown when this checker fails (can be [VALUES]_ class, not just classes extending `Error`). An error set with `add() takes precedence for this checker over the error set through `throws()`. | ||
@@ -179,3 +179,3 @@ ```js | ||
// Name of checker. | ||
"catty", | ||
"catty", | ||
// Checker to validate a string containing "cat". | ||
@@ -224,3 +224,3 @@ (v) => typeof v === "string" && v.strToLower().indexOf("cat") >= 0, | ||
To change the error object Blork throws when a type doesn't match, use the `throws()` function. It accepts a single argument a custom class (can be _any_ class, not just classes extending `Error`). | ||
To change the error object Blork throws when a type doesn't match, use the `throws()` function. It accepts a single argument a custom class (can be [VALUES]_ class, not just classes extending `Error`). | ||
@@ -264,3 +264,3 @@ ```js | ||
The `props()` function can define an object properties (like `Object.defineProperties()`) that are readable and writable, BUT the value must always match the type it was initially defined with. | ||
The `props()` function can define an object properties (like `Object.defineProperties()`) that are readable and writable, BUT the value must always match the type it was initially defined with. | ||
@@ -307,58 +307,75 @@ This allows you to create objects with properties that have a guaranteed type. This makes your object more robust and removes the need to check the type of the property before using it. | ||
## Types | ||
## Type reference | ||
### String types | ||
This section lists all types that are available in Blork. A number of different formats can be used for types: | ||
Types are generally accessed via a string reference. This list shows all Blork built-in checkers: | ||
- **String types** (e.g. `"promise"` and `"integer"`) | ||
- **String modifiers** that modify those string types (e.g. `"?"` and `"!"`) | ||
- **Constant** and **constructor** shorthand types (e.g. `null` and `String`) | ||
- **Object** and **Array** literal types (e.g. `{}` and `[]`) | ||
| Type string reference | Description | ||
|--------------------------------------|------------------- | ||
| `null` | Value is **null** | ||
| `undefined`, `undef`, `void` | Value is **undefined** | ||
| `defined`, `def` | Value is **not undefined** | ||
| `boolean`, `bool` | Value is **true** or **false** | ||
| `true` | Value is **true** | ||
| `false` | Value is **false** | ||
| `truthy` | Any truthy values (i.e. **== true**) | ||
| `falsy` | Any falsy values (i.e. **== false**) | ||
| `zero` | Value is **0** | ||
| `one` | Value is **1** | ||
| `nan` | Value is **NaN** | ||
| `number`, `num` | Numbers excluding NaN/Infinity (using **typeof** and finite check) | ||
| `number+`, `num+`, | Numbers more than or equal to zero | ||
| `number-`, `num-` | Numbers less than or equal to zero | ||
| `integer`, `int` | Integers (using **Number.isInteger()**) | ||
| `integer+`, `int+` | Positive integers including zero | ||
| `integer-`, `int-` | Negative integers including zero | ||
| `string`, `str` | Strings (using **typeof**) | ||
| `string+`, `str+` | Non-empty strings (using **str.length**) | ||
| `lowercase`, `lower` | Strings with no uppercase characters | ||
| `lowercase+`, `lower+` | Non-empty strings with no uppercase characters | ||
| `uppercase`, `upper` | Strings with no lowercase characters | ||
| `uppercase+`, `upper+` | Non-empty strings with no lowercase characters | ||
| `function`, `func` | Functions (using **instanceof Function**) | ||
| `object`, `obj` | Plain objects (using **instanceof Object** and constructor check) | ||
| `object+`, `obj+` | Plain objects with one or more properties (using **Object.keys().length**) | ||
| `objectlike` | Any object-like object (using **instanceof Object**) | ||
| `iterable` | Objects with a **Symbol.iterator** method (that can be used with **for..of** loops) | ||
| `circular` | Objects with one or more _circular references_ (use `!circular` to disallow circular references) | ||
| `array`, `arr` | Plain instances of Array (using **instanceof Array** and constructor check) | ||
| `array+`, `arr+` | Plain instances of **Array** with one or more items | ||
| `arraylike` | Any object, not just arrays, with numeric **.length** property | ||
| `arguments`, `args` | Arguments objects (any object, not just arrays, with numeric **.length** property) | ||
| `map` | Instances of **Map** | ||
| `map+` | Instances of **Map** with one or more items | ||
| `weakmap` | Instances of **WeakMap** | ||
| `set` | Instances of **Set** | ||
| `set+` | Instances of **Set** with one or more items | ||
| `weakset` | Instances of **WeakSet** | ||
| `promise` | Instances of **Promise** | ||
| `date` | Instances of **Date** | ||
| `date+`, `future` | Instances of **Date** with a value in the future | ||
| `date-`, `past` | Instances of **Date** with a value in the past | ||
| `regex`, `regexp` | Instances of **RegExp** (regular expressions) | ||
| `symbol` | Value is **Symbol** (using **typeof**) | ||
| `any`, `mixed` | Allow any value (transparently passes through with no error) | ||
| `json`, `jsonable` | **JSON-friendly** values (null, true, false, finite numbers, strings, plain objects, plain arrays) | ||
### String types: primitives | ||
| `primitive` | Any **primitive** value (undefined, null, booleans, strings, finite numbers) | ||
| `null` | Value is **null** | ||
| `undefined`, `undef`, `void` | Value is **undefined** | ||
| `defined`, `def` | Value is **not undefined** | ||
### String types: booleans | ||
| `boolean`, `bool` | Value is **true** or **false** | ||
| `true` | Value is **true** | ||
| `false` | Value is **false** | ||
| `truthy` | Any truthy values (i.e. **== true**) | ||
| `falsy` | Any falsy values (i.e. **== false**) | ||
### String types: numbers | ||
| `zero` | Value is **0** | ||
| `one` | Value is **1** | ||
| `nan` | Value is **NaN** | ||
| `number`, `num` | Any numbers except NaN/Infinity (using **Number.isFinite()**) | ||
| `+number`, `+num`, | Numbers more than or equal to zero | ||
| `-number`, `-num` | Numbers less than or equal to zero | ||
| `integer`, `int` | Integers (using **Number.isInteger()**) | ||
| `+integer`, `+int` | Positive integers including zero | ||
| `-integer`, `-int` | Negative integers including zero | ||
### String types: strings | ||
| `string`, `str` | Any strings (using **typeof**) | ||
| `lower` | lowercase string (non-empty and alphanumeric only) | ||
| `upper` | UPPERCASE strings (non-empty and alphanumeric only) | ||
| `camel` | camelCase strings e.g. variable/function names (non-empty alphanumeric with lowercase first letter) | ||
| `pascal` | PascalCase strings e.g. class names (non-empty alphanumeric with uppercase first letter) | ||
| `snake` | snake_case strings (non-empty alphanumeric lowercase) | ||
| `screaming` | SCREAMING_SNAKE_CASE strings e.g. environment vars (non-empty uppercase alphanumeric) | ||
| `kebab`, `slug` | kebab-case strings e.g. URL slugs (non-empty alphanumeric lowercase) | ||
| `train` | Train-Case strings e.g. HTTP-Headers (non-empty with uppercase first letters) | ||
### String types: objects | ||
| `function`, `func` | Functions (using **instanceof Function**) | ||
| `object`, `obj` | Plain objects (using **typeof && !null** and constructor check) | ||
| `objectlike` | Any object-like object (using **typeof && !null**) | ||
| `iterable` | Objects with a **Symbol.iterator** method (that can be used with **for..of** loops) | ||
| `circular` | Objects with one or more _circular references_ (use `!circular` to disallow circular references) | ||
| `array`, `arr` | Plain arrays (using **instanceof Array** and constructor check) | ||
| `arraylike`, `arguments`, `args` | Array-like objects, e.g. **arguments** (any object with numeric **.length** property, not just arrays) | ||
| `map` | Instances of **Map** | ||
| `weakmap` | Instances of **WeakMap** | ||
| `set` | Instances of **Set** | ||
| `weakset` | Instances of **WeakSet** | ||
| `promise` | Instances of **Promise** | ||
| `date` | Instances of **Date** | ||
| `future` | Instances of **Date** with a value in the future | ||
| `past` | Instances of **Date** with a value in the past | ||
| `regex`, `regexp` | Instances of **RegExp** (regular expressions) | ||
| `symbol` | Value is **Symbol** (using **typeof**) | ||
### String types: other | ||
| `any`, `mixed` | Allow any value (transparently passes through with no error) | ||
| `json`, `jsonable` | **JSON-friendly** values (null, true, false, finite numbers, strings, plain objects, plain arrays) | ||
```js | ||
@@ -379,3 +396,3 @@ // Pass. | ||
### Optional string types | ||
### String modifiers: Optional types | ||
@@ -396,4 +413,20 @@ Any string type can be made optional by appending a `?` question mark to the type reference. This means the check will also accept `undefined` in addition to the specified type. | ||
### Inverted string types | ||
### String modifiers: Non-empty types | ||
Any type can be made non-empty by appending a `+` plus sign to the type reference. This means the check will only pass if the value is non-empty and has length. | ||
```js | ||
// Pass. | ||
check("abc", "str+"); // No error. | ||
check([1], "arr+"); // No error. | ||
check({ a: 1 }, "obj+"); // No error. | ||
// Fail. | ||
check(123, "str+"); // Throws ValueError "Must be non-empty string (received "")" | ||
check([], "arr+"); // Throws ValueError "Must be non-empty plain array (received [])" | ||
check({}, "obj+"); // Throws ValueError "Must be non-empty plain object (received {})" | ||
``` | ||
### String modifiers: Inverted types | ||
Any string type can be made optional by prepending a `!` question mark to the type reference. This means the check will only pass if the _inverse_ of its type is true. | ||
@@ -414,3 +447,3 @@ | ||
### Combined string types | ||
### String modifiers: Combined types | ||
@@ -434,3 +467,4 @@ You can use `&` and `|` to join string types together, to form AND and OR chains of allowed types. This allows you to compose together more complex types like `number | string` or `date | number | null` or `string && custom-checker` | ||
```js | ||
add("catty", v => v.toLowerCase().indexOf("cat")); // Checks that cat | ||
// Add a checker that confirms a string contains the word "cat" | ||
add("catty", v => v.toLowerCase().indexOf("cat") >= 0); | ||
@@ -466,3 +500,3 @@ // Pass. | ||
You can pass in _any_ class name, and Blork will check the value using `instanceof` and generate a corresponding error message if the type doesn't match. | ||
You can pass in [VALUES]_ class name, and Blork will check the value using `instanceof` and generate a corresponding error message if the type doesn't match. | ||
@@ -508,27 +542,93 @@ Using `Object` and `Array` constructors will work also and will allow any object that is `instanceof Object` or `instanceof Array`. _Note: this is not the same as e.g. the `'object'` and `'array'` string types, which only allow plain objects an arrays (but will reject objects of custom classes extending `Object` or `Array`)._ | ||
### Object literal type (with additional properties) | ||
### Object literal type: additional values | ||
To check that the type of **any** properties conform to a single type, use an `_any` key. This allows you to check objects that don't have known keys (e.g. from user generated data). This is similar to how indexer keys work in Flow or Typescript. | ||
To check that the type of **any** properties conform to a single type, use the `VALUES` symbol and create a `[VALUES]` key. This allows you to check objects that don't have known keys (e.g. from user generated data). This is similar to how indexer keys work in Flow or Typescript. | ||
```js | ||
import { check } from "blork"; | ||
import { check, VALUES } from "blork"; | ||
// Pass. | ||
check({ a: 1, b: 2, c: 3 }, { _any: "num" }); // No error. | ||
check({ name: "Dan", a: 1, b: 2, c: 3 }, { name: "str", _any: "num" }); // No error. | ||
check( | ||
{ a: 1, b: 2, c: 3 }, | ||
{ [VALUES]: "num" } | ||
); // No error. | ||
check( | ||
{ name: "Dan", a: 1, b: 2, c: 3 }, | ||
{ name: "str", [VALUES]: "num" } | ||
); // No error. | ||
// Fail. | ||
check({ a: 1, b: 2, c: "abc" }, { _any: "num" }); // Throws ValueError "c: Must be number (received "abc")" | ||
check( | ||
{ a: 1, b: 2, c: "abc" }, | ||
{ [VALUES]: "num" } | ||
); // Throws ValueError "c: Must be number..." | ||
check( | ||
{ name: "Dan", a: 1, b: 2, c: 3 }, | ||
{ name: "str", [VALUES]: "bool" } | ||
); // Throws ValueError "a: Must be boolean..." | ||
``` | ||
If you wish you can use this functionality with the `undefined` type to ensure objects **do not** contain additional properties (object literal types by default are allowed to contain additional properties). | ||
You can use this functionality with the `undefined` type to ensure objects **do not** contain additional properties (object literal types by default are allowed to contain additional properties). | ||
```js | ||
// Pass. | ||
check({ name: "Carl" }, { name: "str", _any: "undefined" }); // No error. | ||
check( | ||
{ name: "Carl" }, | ||
{ name: "str", [VALUES]: "undefined" } | ||
); // No error. | ||
// Fail. | ||
check({ name: "Jess", another: 28 }, { name: "str", _any: "undefined" }); // Throws ValueError "another: Must be undefined (received 28)" | ||
check( | ||
{ name: "Jess", another: 28 }, | ||
{ name: "str", [VALUES]: "undefined" } | ||
); // Throws ValueError "another: Must be undefined..." | ||
``` | ||
### Object literal type: additional keys | ||
To check that the keys of any additional properties conform to a single type, use the `KEYS` symbol and create a `[KEYS]` key. This allows you to ensure that keys conform to a specific string type, e.g. **camelCase**, **kebab-case** or **UPPERCASE** (see string types above). | ||
```js | ||
import { check, VALUES } from "blork"; | ||
// Pass. | ||
check({ MYVAL: 1 }, { [KEYS]: "upper" }); // UPPERCASE keys — no error. | ||
check({ myVal: 1 }, { [KEYS]: "camel" }); // camelCase keys — no error. | ||
check({ MyVal: 1 }, { [KEYS]: "pascal" }); // PascalCase keys — no error. | ||
check({ my-val: 1 }, { [KEYS]: "kebab" }); // kebab-case keys — no error. | ||
// Fail. | ||
check({ MYVAL: 1 }, { [KEYS]: "upper" }); // UPPERCASE keys — no error. | ||
check({ myVal: 1 }, { [KEYS]: "camel" }); // camelCase keys — no error. | ||
check({ MyVal: 1 }, { [KEYS]: "pascal" }); // PascalCase keys — no error. | ||
check({ my-val: 1 }, { [KEYS]: "kebab" }); // kebab-case keys — no error. | ||
``` | ||
### Object literal type: custom constructor | ||
Normally object literal types check that the object is a **plain object**. If you wish to allow the object to be a different object, use the `CLASS` symbol and create a `[CLASS]` key. | ||
```js | ||
import { check, CLASS } from "blork"; | ||
// Make a fancy new class. | ||
class MyClass { | ||
constructor () { | ||
this.num = 123; | ||
} | ||
} | ||
// Pass. | ||
check( | ||
new MyClass, | ||
{ num: 123, [CLASS]: MyClass } | ||
); // No error. | ||
// Fail. | ||
check( | ||
{ num: 123, }, | ||
{ num: 123, [CLASS]: MyClass } | ||
); // Throws ValueError "Must be instance of MyClass..." | ||
``` | ||
### Array literal type | ||
@@ -574,2 +674,13 @@ | ||
- 7.0.0 | ||
- Add `VALUES`, `KEYS`, and `CLASS` symbol constants | ||
- Remove `_any` key and use `VALUES` to provide the same functionality | ||
- Add `KEYS` functionality to check type or case of object keys, e.g. camelCase or kebab-case | ||
- Add `CLASS` functionality to check the class of an object | ||
- Add string case checkers for e.g. variable names (kebab-case, camelCase, snake_case etc) | ||
- `upper` and `lower` checkers work differently (all characters must be UPPERCASE/lowercase) | ||
- Rename `int+`, `int-` checkers to `+int` and `-int` | ||
- Add '+' modifier to check for non-empty values with any checker | ||
- Remove hardcoded '+' checkers like `lower+`, `object+` | ||
- Remove `uppercase` and `lowercase` checkers for consistency | ||
- 6.0.0 | ||
@@ -580,3 +691,3 @@ - Remove `prop()` function and add `props()` function instead (`prop()` was impossible to type with Flow) | ||
- 5.0.0 | ||
- Change from symbol `[ANY]` key to `_any` key for indexer property (for convenience and better Flow compatibility) | ||
- Change from symbol `[ANY]` key to `[VALUES]` key for indexer property (for convenience and better Flow compatibility) | ||
- 4.5.0 | ||
@@ -583,0 +694,0 @@ - Add `checker()` function to return the boolean checker function itself. |
@@ -11,189 +11,187 @@ const checkers = require("../lib/checkers"); | ||
test("Every checker passes correctly", () => { | ||
expect.assertions(Object.keys(checkers).length); | ||
// Mock check() so we can check we tested everything. | ||
const called = []; | ||
function mockCheck(v, k) { | ||
called.push(k); | ||
return check(v, k); | ||
} | ||
// Primatives. | ||
expect(check(null, "null")).toBe(undefined); | ||
expect(check(undefined, "undefined")).toBe(undefined); | ||
expect(check(undefined, "void")).toBe(undefined); | ||
expect(check(undefined, "undef")).toBe(undefined); | ||
expect(check(true, "defined")).toBe(undefined); | ||
expect(check(true, "def")).toBe(undefined); | ||
expect(check(true, "boolean")).toBe(undefined); | ||
expect(check(true, "bool")).toBe(undefined); | ||
expect(check(true, "true")).toBe(undefined); | ||
expect(check(false, "false")).toBe(undefined); | ||
expect(check(true, "truthy")).toBe(undefined); | ||
expect(check(false, "falsy")).toBe(undefined); | ||
// Primitives. | ||
expect(mockCheck(null, "primitive")).toBe(undefined); | ||
expect(mockCheck(null, "null")).toBe(undefined); | ||
expect(mockCheck(undefined, "undefined")).toBe(undefined); | ||
expect(mockCheck(undefined, "void")).toBe(undefined); | ||
expect(mockCheck(undefined, "undef")).toBe(undefined); | ||
expect(mockCheck(true, "defined")).toBe(undefined); | ||
expect(mockCheck(true, "def")).toBe(undefined); | ||
// Booleans. | ||
expect(mockCheck(true, "boolean")).toBe(undefined); | ||
expect(mockCheck(true, "bool")).toBe(undefined); | ||
expect(mockCheck(true, "true")).toBe(undefined); | ||
expect(mockCheck(false, "false")).toBe(undefined); | ||
expect(mockCheck(true, "truthy")).toBe(undefined); | ||
expect(mockCheck(false, "falsy")).toBe(undefined); | ||
// Numbers. | ||
expect(check(0, "zero")).toBe(undefined); | ||
expect(check(1, "one")).toBe(undefined); | ||
expect(check(NaN, "nan")).toBe(undefined); | ||
expect(check(1.5, "number")).toBe(undefined); | ||
expect(check(1.5, "num")).toBe(undefined); | ||
expect(check(1.5, "number+")).toBe(undefined); | ||
expect(check(0.5, "num+")).toBe(undefined); | ||
expect(check(-1.5, "number-")).toBe(undefined); | ||
expect(check(-1.5, "num-")).toBe(undefined); | ||
expect(check(1, "integer")).toBe(undefined); | ||
expect(check(1, "int")).toBe(undefined); | ||
expect(check(1, "integer+")).toBe(undefined); | ||
expect(check(1, "int+")).toBe(undefined); | ||
expect(check(-1, "integer-")).toBe(undefined); | ||
expect(check(-1, "int-")).toBe(undefined); | ||
expect(mockCheck(0, "zero")).toBe(undefined); | ||
expect(mockCheck(1, "one")).toBe(undefined); | ||
expect(mockCheck(NaN, "nan")).toBe(undefined); | ||
expect(mockCheck(1.5, "number")).toBe(undefined); | ||
expect(mockCheck(1.5, "num")).toBe(undefined); | ||
expect(mockCheck(1.5, "+number")).toBe(undefined); | ||
expect(mockCheck(0.5, "+num")).toBe(undefined); | ||
expect(mockCheck(-1.5, "-number")).toBe(undefined); | ||
expect(mockCheck(-1.5, "-num")).toBe(undefined); | ||
expect(mockCheck(1, "integer")).toBe(undefined); | ||
expect(mockCheck(1, "int")).toBe(undefined); | ||
expect(mockCheck(1, "+integer")).toBe(undefined); | ||
expect(mockCheck(1, "+int")).toBe(undefined); | ||
expect(mockCheck(-1, "-integer")).toBe(undefined); | ||
expect(mockCheck(-1, "-int")).toBe(undefined); | ||
// Strings. | ||
expect(check("a", "string")).toBe(undefined); | ||
expect(check("a", "str")).toBe(undefined); | ||
expect(check("a", "string+")).toBe(undefined); | ||
expect(check("a", "str+")).toBe(undefined); | ||
expect(check("a", "lowercase")).toBe(undefined); | ||
expect(check("a", "lower")).toBe(undefined); | ||
expect(check("a", "lowercase+")).toBe(undefined); | ||
expect(check("a", "lower+")).toBe(undefined); | ||
expect(check("A", "uppercase")).toBe(undefined); | ||
expect(check("A", "upper")).toBe(undefined); | ||
expect(check("A", "uppercase+")).toBe(undefined); | ||
expect(check("A", "upper+")).toBe(undefined); | ||
expect(mockCheck("a", "string")).toBe(undefined); | ||
expect(mockCheck("a", "str")).toBe(undefined); | ||
expect(mockCheck("myvar", "lower")).toBe(undefined); | ||
expect(mockCheck("MYVAR", "upper")).toBe(undefined); | ||
expect(mockCheck("myVar", "camel")).toBe(undefined); | ||
expect(mockCheck("MyVar", "pascal")).toBe(undefined); | ||
expect(mockCheck("my_var", "snake")).toBe(undefined); | ||
expect(mockCheck("MY_VAR", "screaming")).toBe(undefined); | ||
expect(mockCheck("my-var", "kebab")).toBe(undefined); | ||
expect(mockCheck("my-var", "slug")).toBe(undefined); | ||
expect(mockCheck("My-Var", "train")).toBe(undefined); | ||
// Functions. | ||
expect(check(function() {}, "function")).toBe(undefined); | ||
expect(check(function() {}, "func")).toBe(undefined); | ||
// Objects. | ||
expect(check({}, "object")).toBe(undefined); | ||
expect(check({ a: 1 }, "obj")).toBe(undefined); | ||
expect(check({ a: 1 }, "object+")).toBe(undefined); | ||
expect(check({ a: 1 }, "obj+")).toBe(undefined); | ||
expect(check({}, "objectlike")).toBe(undefined); | ||
expect(check({ [Symbol.iterator]: () => {} }, "iterable")).toBe(undefined); | ||
expect(mockCheck(function() {}, "function")).toBe(undefined); | ||
expect(mockCheck(function() {}, "func")).toBe(undefined); | ||
expect(mockCheck({}, "object")).toBe(undefined); | ||
expect(mockCheck({ a: 1 }, "obj")).toBe(undefined); | ||
expect(mockCheck({}, "objectlike")).toBe(undefined); | ||
expect(mockCheck({ [Symbol.iterator]: () => {} }, "iterable")).toBe(undefined); | ||
expect(mockCheck(circular, "circular")).toBe(undefined); | ||
expect(mockCheck([], "array")).toBe(undefined); | ||
expect(mockCheck([], "arr")).toBe(undefined); | ||
expect(mockCheck({ "0": "abc", length: 1 }, "arraylike")).toBe(undefined); | ||
expect(mockCheck(arguments, "arguments")).toBe(undefined); | ||
expect(mockCheck(arguments, "args")).toBe(undefined); | ||
expect(mockCheck(new Date(), "date")).toBe(undefined); | ||
expect(mockCheck(new Date(2080, 0, 1), "future")).toBe(undefined); | ||
expect(mockCheck(new Date(1980, 0, 1), "past")).toBe(undefined); | ||
expect(mockCheck(new Map(), "map")).toBe(undefined); | ||
expect(mockCheck(new WeakMap(), "weakmap")).toBe(undefined); | ||
expect(mockCheck(new Set(), "set")).toBe(undefined); | ||
expect(mockCheck(new WeakSet(), "weakset")).toBe(undefined); | ||
expect(mockCheck(Promise.resolve(), "promise")).toBe(undefined); | ||
expect(mockCheck(/[abc]+/g, "regexp")).toBe(undefined); | ||
expect(mockCheck(/[abc]+/g, "regex")).toBe(undefined); | ||
expect(mockCheck(Symbol(), "symbol")).toBe(undefined); | ||
// Arrays. | ||
expect(check([], "array")).toBe(undefined); | ||
expect(check([], "arr")).toBe(undefined); | ||
expect(check([1], "array+")).toBe(undefined); | ||
expect(check([1], "arr+")).toBe(undefined); | ||
expect(check({ "0": "abc", length: 1 }, "arraylike")).toBe(undefined); | ||
expect(check(arguments, "arguments")).toBe(undefined); | ||
expect(check(arguments, "args")).toBe(undefined); | ||
// Dates. | ||
expect(check(new Date(), "date")).toBe(undefined); | ||
expect(check(new Date(2080, 0, 1), "date+")).toBe(undefined); | ||
expect(check(new Date(2080, 0, 1), "future")).toBe(undefined); | ||
expect(check(new Date(1980, 0, 1), "date-")).toBe(undefined); | ||
expect(check(new Date(1980, 0, 1), "past")).toBe(undefined); | ||
// Other. | ||
expect(check(new Map(), "map")).toBe(undefined); | ||
expect(check(new Map([[1, 1]]), "map+")).toBe(undefined); | ||
expect(check(new WeakMap(), "weakmap")).toBe(undefined); | ||
expect(check(new Set(), "set")).toBe(undefined); | ||
expect(check(new Set([1]), "set+")).toBe(undefined); | ||
expect(check(new WeakSet(), "weakset")).toBe(undefined); | ||
expect(check(Promise.resolve(), "promise")).toBe(undefined); | ||
expect(check(/[abc]+/g, "regexp")).toBe(undefined); | ||
expect(check(/[abc]+/g, "regex")).toBe(undefined); | ||
expect(check(Symbol(), "symbol")).toBe(undefined); | ||
expect(check(false, "any")).toBe(undefined); | ||
expect(check("abc", "mixed")).toBe(undefined); | ||
expect(mockCheck(false, "any")).toBe(undefined); | ||
expect(mockCheck("abc", "mixed")).toBe(undefined); | ||
expect(mockCheck({ num: 123, str: "abc" }, "json")).toBe(undefined); | ||
expect(mockCheck({ num: 123, str: "abc" }, "jsonable")).toBe(undefined); | ||
// Advanced. | ||
expect(check(circular, "circular")).toBe(undefined); | ||
expect(check({ num: 123, str: "abc" }, "json")).toBe(undefined); | ||
// Check we called every checker. | ||
// Done in this awkward way so we get an error that helps us find the one we're missing. | ||
const checkerNames = Object.keys(checkers); | ||
checkerNames.forEach(name => expect(called).toContain(name)); | ||
called.forEach(name => expect(checkerNames).toContain(name)); | ||
expect(called.length).toBe(checkerNames.length); | ||
}); | ||
test("Every named type fails correctly", () => { | ||
expect.assertions(Object.keys(checkers).length); | ||
// Mock check() so we can check we tested everything. | ||
const called = []; | ||
function mockCheck(v, k) { | ||
called.push(k); | ||
return check(v, k); | ||
} | ||
// Primatives.. | ||
expect(() => check(0, "null")).toThrow(TypeError); | ||
expect(() => check(null, "undefined")).toThrow(TypeError); | ||
expect(() => check(null, "void")).toThrow(TypeError); | ||
expect(() => check(null, "undef")).toThrow(TypeError); | ||
expect(() => check(undefined, "defined")).toThrow(TypeError); | ||
expect(() => check(undefined, "def")).toThrow(TypeError); | ||
expect(() => check(9, "boolean")).toThrow(TypeError); | ||
expect(() => check(9, "bool")).toThrow(TypeError); | ||
expect(() => check(1, "true")).toThrow(TypeError); | ||
expect(() => check(9, "false")).toThrow(TypeError); | ||
expect(() => check(0, "truthy")).toThrow(TypeError); | ||
expect(() => check(1, "falsy")).toThrow(TypeError); | ||
expect(() => mockCheck(Symbol(), "primitive")).toThrow(TypeError); | ||
expect(() => mockCheck(0, "null")).toThrow(TypeError); | ||
expect(() => mockCheck(null, "undefined")).toThrow(TypeError); | ||
expect(() => mockCheck(null, "void")).toThrow(TypeError); | ||
expect(() => mockCheck(null, "undef")).toThrow(TypeError); | ||
expect(() => mockCheck(undefined, "defined")).toThrow(TypeError); | ||
expect(() => mockCheck(undefined, "def")).toThrow(TypeError); | ||
// Booleans. | ||
expect(() => mockCheck(9, "boolean")).toThrow(TypeError); | ||
expect(() => mockCheck(9, "bool")).toThrow(TypeError); | ||
expect(() => mockCheck(1, "true")).toThrow(TypeError); | ||
expect(() => mockCheck(9, "false")).toThrow(TypeError); | ||
expect(() => mockCheck(0, "truthy")).toThrow(TypeError); | ||
expect(() => mockCheck(1, "falsy")).toThrow(TypeError); | ||
// Numbers. | ||
expect(() => check(1, "zero")).toThrow(TypeError); | ||
expect(() => check(0, "one")).toThrow(TypeError); | ||
expect(() => check(1, "nan")).toThrow(TypeError); | ||
expect(() => check("1", "number")).toThrow(TypeError); | ||
expect(() => check("1", "num")).toThrow(TypeError); | ||
expect(() => check(-1, "number+")).toThrow(TypeError); | ||
expect(() => check(-1, "num+")).toThrow(TypeError); | ||
expect(() => check(1, "number-")).toThrow(TypeError); | ||
expect(() => check(1, "num-")).toThrow(TypeError); | ||
expect(() => check(1.5, "integer")).toThrow(TypeError); | ||
expect(() => check(1.5, "int")).toThrow(TypeError); | ||
expect(() => check(1.5, "integer+")).toThrow(TypeError); | ||
expect(() => check(2.5, "int+")).toThrow(TypeError); | ||
expect(() => check(-1.5, "integer-")).toThrow(TypeError); | ||
expect(() => check(-2.5, "int-")).toThrow(TypeError); | ||
expect(() => mockCheck(1, "zero")).toThrow(TypeError); | ||
expect(() => mockCheck(0, "one")).toThrow(TypeError); | ||
expect(() => mockCheck(1, "nan")).toThrow(TypeError); | ||
expect(() => mockCheck("1", "number")).toThrow(TypeError); | ||
expect(() => mockCheck("1", "num")).toThrow(TypeError); | ||
expect(() => mockCheck("1", "+number")).toThrow(TypeError); | ||
expect(() => mockCheck("1", "+num")).toThrow(TypeError); | ||
expect(() => mockCheck("1", "-number")).toThrow(TypeError); | ||
expect(() => mockCheck("1", "-num")).toThrow(TypeError); | ||
expect(() => mockCheck(1.5, "integer")).toThrow(TypeError); | ||
expect(() => mockCheck(1.5, "int")).toThrow(TypeError); | ||
expect(() => mockCheck(-1, "+integer")).toThrow(TypeError); | ||
expect(() => mockCheck(-1, "+int")).toThrow(TypeError); | ||
expect(() => mockCheck(1, "-integer")).toThrow(TypeError); | ||
expect(() => mockCheck(1, "-int")).toThrow(TypeError); | ||
// Strings. | ||
expect(() => check(1, "string")).toThrow(TypeError); | ||
expect(() => check(1, "str")).toThrow(TypeError); | ||
expect(() => check("", "string+")).toThrow(TypeError); | ||
expect(() => check("", "str+")).toThrow(TypeError); | ||
expect(() => check("A", "lowercase")).toThrow(TypeError); | ||
expect(() => check("A", "lower")).toThrow(TypeError); | ||
expect(() => check("A", "lowercase+")).toThrow(TypeError); | ||
expect(() => check("A", "lower+")).toThrow(TypeError); | ||
expect(() => check("a", "uppercase")).toThrow(TypeError); | ||
expect(() => check("a", "upper")).toThrow(TypeError); | ||
expect(() => check("a", "uppercase+")).toThrow(TypeError); | ||
expect(() => check("a", "upper+")).toThrow(TypeError); | ||
expect(() => mockCheck(1, "string")).toThrow(TypeError); | ||
expect(() => mockCheck(1, "str")).toThrow(TypeError); | ||
expect(() => mockCheck("A", "lower")).toThrow(TypeError); | ||
expect(() => mockCheck("a", "upper")).toThrow(TypeError); | ||
expect(() => mockCheck("my-var", "camel")).toThrow(TypeError); | ||
expect(() => mockCheck("my-var", "pascal")).toThrow(TypeError); | ||
expect(() => mockCheck("MY_VAR", "snake")).toThrow(TypeError); | ||
expect(() => mockCheck("MY-VAR", "screaming")).toThrow(TypeError); | ||
expect(() => mockCheck("MY-VAR", "kebab")).toThrow(TypeError); | ||
expect(() => mockCheck("my-VAR", "slug")).toThrow(TypeError); | ||
expect(() => mockCheck("my-var", "train")).toThrow(TypeError); | ||
// Functions. | ||
expect(() => check({}, "function")).toThrow(TypeError); | ||
expect(() => check({}, "func")).toThrow(TypeError); | ||
// Objects. | ||
expect(() => check(1, "object")).toThrow(TypeError); | ||
expect(() => check(1, "obj")).toThrow(TypeError); | ||
expect(() => check({}, "object+")).toThrow(TypeError); | ||
expect(() => check({}, "obj+")).toThrow(TypeError); | ||
expect(() => check("a", "objectlike")).toThrow(TypeError); | ||
expect(() => check({}, "iterable")).toThrow(TypeError); | ||
expect(() => mockCheck({}, "function")).toThrow(TypeError); | ||
expect(() => mockCheck({}, "func")).toThrow(TypeError); | ||
expect(() => mockCheck(1, "object")).toThrow(TypeError); | ||
expect(() => mockCheck(1, "obj")).toThrow(TypeError); | ||
expect(() => mockCheck("a", "objectlike")).toThrow(TypeError); | ||
expect(() => mockCheck({}, "iterable")).toThrow(TypeError); | ||
expect(() => mockCheck([], "circular")).toThrow(TypeError); | ||
expect(() => mockCheck({}, "array")).toThrow(TypeError); | ||
expect(() => mockCheck({}, "arr")).toThrow(TypeError); | ||
expect(() => mockCheck({}, "arraylike")).toThrow(TypeError); | ||
expect(() => mockCheck({}, "arguments")).toThrow(TypeError); | ||
expect(() => mockCheck({}, "args")).toThrow(TypeError); | ||
expect(() => mockCheck("2016", "date")).toThrow(TypeError); | ||
expect(() => mockCheck(new Date(1080, 0, 1), "future")).toThrow(TypeError); | ||
expect(() => mockCheck(new Date(2980, 0, 1), "past")).toThrow(TypeError); | ||
expect(() => mockCheck([], "map")).toThrow(TypeError); | ||
expect(() => mockCheck([], "weakmap")).toThrow(TypeError); | ||
expect(() => mockCheck([], "set")).toThrow(TypeError); | ||
expect(() => mockCheck([], "weakset")).toThrow(TypeError); | ||
expect(() => mockCheck(true, "promise")).toThrow(TypeError); | ||
expect(() => mockCheck("/[abc]+/g", "regexp")).toThrow(TypeError); | ||
expect(() => mockCheck("/[abc]+/g", "regex")).toThrow(TypeError); | ||
expect(() => mockCheck("symbol", "symbol")).toThrow(TypeError); | ||
// Arrays. | ||
expect(() => check({}, "array")).toThrow(TypeError); | ||
expect(() => check({}, "arr")).toThrow(TypeError); | ||
expect(() => check({}, "array+")).toThrow(TypeError); | ||
expect(() => check({}, "arr+")).toThrow(TypeError); | ||
expect(() => check({}, "arraylike")).toThrow(TypeError); | ||
expect(() => check({}, "arguments")).toThrow(TypeError); | ||
expect(() => check({}, "args")).toThrow(TypeError); | ||
// Dates. | ||
expect(() => check("2016", "date")).toThrow(TypeError); | ||
expect(() => check(new Date(1080, 0, 1), "date+")).toThrow(TypeError); | ||
expect(() => check(new Date(1080, 0, 1), "future")).toThrow(TypeError); | ||
expect(() => check(new Date(2980, 0, 1), "date-")).toThrow(TypeError); | ||
expect(() => check(new Date(2980, 0, 1), "past")).toThrow(TypeError); | ||
// Other. | ||
expect(() => check([], "map")).toThrow(TypeError); | ||
expect(() => check(new Map(), "map+")).toThrow(TypeError); | ||
expect(() => check([], "weakmap")).toThrow(TypeError); | ||
expect(() => check([], "set")).toThrow(TypeError); | ||
expect(() => check(new Set(), "set+")).toThrow(TypeError); | ||
expect(() => check([], "weakset")).toThrow(TypeError); | ||
expect(() => check(true, "promise")).toThrow(TypeError); | ||
expect(() => check("/[abc]+/g", "regexp")).toThrow(TypeError); | ||
expect(() => check("/[abc]+/g", "regex")).toThrow(TypeError); | ||
expect(() => check("symbol", "symbol")).toThrow(TypeError); | ||
expect(check(false, "any")).toBe(undefined); | ||
expect(check("abc", "mixed")).toBe(undefined); | ||
expect(mockCheck(false, "any")).toBe(undefined); | ||
expect(mockCheck("abc", "mixed")).toBe(undefined); | ||
expect(() => mockCheck(undefined, "json")).toThrow(TypeError); | ||
expect(() => mockCheck({ a: undefined }, "jsonable")).toThrow(TypeError); | ||
// Advanced. | ||
expect(() => check([], "circular")).toThrow(TypeError); | ||
expect(() => check(undefined, "json")).toThrow(TypeError); | ||
// Check we called every checker. | ||
// Done in this awkward way so we get an error that helps us find the one we're missing. | ||
const checkerNames = Object.keys(checkers); | ||
checkerNames.forEach(name => expect(called).toContain(name)); | ||
called.forEach(name => expect(checkerNames).toContain(name)); | ||
expect(called.length).toBe(checkerNames.length); | ||
}); | ||
}); |
@@ -8,22 +8,22 @@ const ValueError = require("../lib/errors/ValueError"); | ||
test("Add and run a custom checker (no description)", () => { | ||
// Define a checker called '11218c'. | ||
expect(add("11218c", v => typeof v === "string")).toBeUndefined(); | ||
// Define a checker called 'a11218'. | ||
expect(add("a11218", v => typeof v === "string")).toBeUndefined(); | ||
// Check a passing value. | ||
expect(check("abc", "11218c")).toBe(undefined); | ||
expect(check("abc", "a11218")).toBe(undefined); | ||
// Check a failing value. | ||
expect(() => check(123, "11218c")).toThrow(TypeError); | ||
expect(() => check(123, "11218c")).toThrow(/11218c/); // Must contain name. | ||
expect(() => check(123, "a11218")).toThrow(TypeError); | ||
expect(() => check(123, "a11218")).toThrow(/a11218/); // Must contain name. | ||
}); | ||
test("Add and run a custom checker (with description)", () => { | ||
// Define a checker called '618e0e'. | ||
expect(add("618e0e", v => typeof v === "string", "a43829")).toBeUndefined(); | ||
// Define a checker called 'e618e0'. | ||
expect(add("e618e0", v => typeof v === "string", "e618e0")).toBeUndefined(); | ||
// Check a passing value. | ||
expect(check("abc", "618e0e")).toBe(undefined); | ||
expect(check("abc", "e618e0")).toBe(undefined); | ||
// Check a failing value. | ||
expect(() => check(123, "618e0e")).toThrow(TypeError); | ||
expect(() => check(123, "618e0e")).toThrow(/a43829/); // Must contain description. | ||
expect(() => check(123, "e618e0")).toThrow(TypeError); | ||
expect(() => check(123, "e618e0")).toThrow(/e618e0/); // Must contain description. | ||
}); | ||
@@ -48,26 +48,39 @@ test("Add and run a custom checker (with custom Error)", () => { | ||
// Define a checker called '013e93'. | ||
expect(add("013e93", v => typeof v === "string", "824b7c", IsNullError)).toBeUndefined(); | ||
// Define a checker called 't01393'. | ||
expect(add("t01393", v => typeof v === "string", "824b7c", IsNullError)).toBeUndefined(); | ||
// Check a passing value. | ||
expect(check("abc", "013e93")).toBe(undefined); | ||
expect(check("abc", "t01393")).toBe(undefined); | ||
// Check a failing value. | ||
expect(() => check(123, "013e93")).toThrow(IsNullError); | ||
expect(() => check(123, "013e93")).toThrow(/824b7c/); // Must contain description. | ||
expect(() => check(123, "t01393")).toThrow(IsNullError); | ||
expect(() => check(123, "t01393")).toThrow(/824b7c/); // Must contain description. | ||
}); | ||
test("Throw BlorkError if not non-empty lowercase string", () => { | ||
test("Throw BlorkError if name is not kebab-case string", () => { | ||
const func = () => {}; | ||
expect(() => add(123, func, "func")).toThrow(BlorkError); | ||
expect(() => add("", func, "func")).toThrow(BlorkError); | ||
expect(() => add("name_name", func, "func")).toThrow(BlorkError); | ||
expect(() => add("UPPER", func, "func")).toThrow(BlorkError); | ||
expect(() => add("UPPER", func, "func")).toThrow(/add\(\):/); | ||
expect(() => add("UPPER", func, "func")).toThrow(/name:/); | ||
expect(() => add("UPPER", func, "func")).toThrow(/kebab-case/); | ||
expect(() => add("UPPER", func, "func")).toThrow(/"UPPER"/); | ||
}); | ||
test("Throw BlorkError if passing a non-function", () => { | ||
expect(() => add("test.checker.nonfunction", true)).toThrow(BlorkError); | ||
expect(() => add("dc63b8", true)).toThrow(BlorkError); | ||
expect(() => add("dc63b8", true)).toThrow(/add\(\):/); | ||
expect(() => add("dc63b8", true)).toThrow(/checker:/); | ||
expect(() => add("dc63b8", true)).toThrow(/function/); | ||
expect(() => add("dc63b8", true)).toThrow(/true/); | ||
}); | ||
test("Throw BlorkError if same name as existing", () => { | ||
const func = () => {}; | ||
add("test.checker.samename", func, "samename"); | ||
expect(() => add("test.checker.samename", func)).toThrow(BlorkError); | ||
add("b89441", func, "samename"); | ||
expect(() => add("b89441", func)).toThrow(BlorkError); | ||
expect(() => add("b89441", func)).toThrow(/add\(\):/); | ||
expect(() => add("b89441", func)).toThrow(/name:/); | ||
expect(() => add("b89441", func)).toThrow(/exists/); | ||
expect(() => add("b89441", func)).toThrow(/"b89441"/); | ||
}); | ||
}); |
@@ -25,5 +25,10 @@ const BlorkError = require("../lib/errors/BlorkError"); | ||
expect(() => args(argsObj, [Boolean, Boolean])).toThrow(TypeError); | ||
expect(() => args(argsObj, [Boolean, Boolean])).toThrow(/3/i); | ||
expect(() => args(argsObj, [Boolean, Boolean])).toThrow(/arguments/i); | ||
}); | ||
test("Throw BlorkError if passing non-arguments-like object", () => { | ||
expect(() => args({}, [Number])).toThrow(BlorkError); | ||
expect(() => args({}, [Number])).toThrow(/args\(\):/); | ||
expect(() => args({}, [Number])).toThrow(/arraylike/); | ||
expect(() => args({}, [Number])).toThrow(/\{\}/); | ||
}); | ||
@@ -30,0 +35,0 @@ test("Throw BlorkError if types is not array", () => { |
const BlorkError = require("../lib/errors/BlorkError"); | ||
const { check } = require("../lib/exports"); | ||
const { check, CLASS, KEYS, VALUES } = require("../lib/exports"); | ||
@@ -34,50 +34,88 @@ // Tests. | ||
}); | ||
test("Object literal types with _any property pass correctly", () => { | ||
expect(check({ a: 1, b: 1, c: 1 }, { a: "num", _any: "num" })).toBe(undefined); | ||
expect(check({ a: "abc", b: "abc", c: "abc" }, { a: "str", _any: "str" })).toBe(undefined); | ||
expect(check({ a: 1, b: 2, c: undefined }, { a: "num", _any: "num?" })).toBe(undefined); | ||
expect(check({ a: new Map(), b: new Map(), c: new Map() }, { _any: Map })).toBe(undefined); | ||
describe("CLASS property", () => { | ||
class MyClass { | ||
constructor() { | ||
this.a = "abc"; | ||
} | ||
} | ||
test("Object literal types with CLASS property pass correctly", () => { | ||
expect(check(new MyClass(), { [CLASS]: MyClass, a: "string" })).toBe(undefined); | ||
}); | ||
test("Object literal types with CLASS property fails correctly", () => { | ||
expect(() => check({ a: "abc" }, { [CLASS]: MyClass, a: "string" })).toThrow(TypeError); | ||
expect(() => check({ a: "abc" }, { [CLASS]: MyClass, a: "string" })).toThrow(/instance of MyClass/); | ||
expect(() => check({ a: "abc" }, { [CLASS]: MyClass, a: "string" })).toThrow(/{ "a": "abc" }/); | ||
}); | ||
test("Object literal types without CLASS property fails correctly", () => { | ||
expect(() => check(new MyClass(), { a: "string" })).toThrow(TypeError); | ||
}); | ||
}); | ||
test("Object literal types with _any property pass correctly when _any isn't used", () => { | ||
expect(check({ a: "abc" }, { a: "str", _any: "str" })).toBe(undefined); | ||
describe("KEYS property", () => { | ||
test("Object literal types with KEYS property pass correctly", () => { | ||
expect(check({ A: 1, B: 1, C: 1 }, { A: "num", [KEYS]: "upper" })).toBe(undefined); | ||
expect(check({ aA: "abc", bB: "abc", cC: "abc" }, { aA: "str", [KEYS]: "camel" })).toBe(undefined); | ||
expect(check({ a: 1, b: 2, c: undefined }, { a: "num", [KEYS]: "lower" })).toBe(undefined); | ||
}); | ||
test("Object literal types with KEYS property pass correctly when KEYS isn't used", () => { | ||
expect(check({ A: "abc" }, { A: "str", [KEYS]: "lower" })).toBe(undefined); | ||
}); | ||
test("Object literal types with KEYS property fail correctly", () => { | ||
expect(() => check({ a: 1, b: 2 }, { a: "num", [KEYS]: "upper" })).toThrow(TypeError); | ||
expect(() => check({ a: 1, b: 2 }, { a: "num", [KEYS]: "upper" })).toThrow(/\.b:/); | ||
expect(() => check({ a: 1, b: 2 }, { a: "num", [KEYS]: "upper" })).toThrow(/UPPERCASE/i); | ||
expect(() => check({ a: 1, b: 2 }, { a: "num", [KEYS]: "upper" }, "doc")).toThrow(TypeError); | ||
expect(() => check({ a: 1, b: 2 }, { a: "num", [KEYS]: "upper" }, "doc")).toThrow(/doc\.b:/); | ||
expect(() => check({ a: 1, b: 2 }, { a: "num", [KEYS]: "upper" }, "doc")).toThrow(/UPPERCASE/i); | ||
expect(() => check({ a: 1, b: 2, c: "c" }, { a: "num", [KEYS]: "num" })).toThrow(TypeError); | ||
}); | ||
}); | ||
test("Using undefined as any ", () => { | ||
expect(check({ a: "abc" }, { a: "str", _any: "str" })).toBe(undefined); | ||
describe("VALUES property", () => { | ||
test("Object literal types with VALUES property pass correctly", () => { | ||
expect(check({ a: 1, b: 1, c: 1 }, { a: "num", [VALUES]: "num" })).toBe(undefined); | ||
expect(check({ a: "abc", b: "abc", c: "abc" }, { a: "str", [VALUES]: "str" })).toBe(undefined); | ||
expect(check({ a: 1, b: 2, c: undefined }, { a: "num", [VALUES]: "num?" })).toBe(undefined); | ||
expect(check({ a: new Map(), b: new Map(), c: new Map() }, { [VALUES]: Map })).toBe(undefined); | ||
}); | ||
test("Object literal types with VALUES property pass correctly when VALUES isn't used", () => { | ||
expect(check({ a: "abc" }, { a: "str", [VALUES]: "str" })).toBe(undefined); | ||
}); | ||
test("Object literal types with VALUES property fail correctly", () => { | ||
expect(() => check({ a: 1 }, { [VALUES]: "str" })).toThrow(TypeError); | ||
expect(() => check({ a: 1 }, { [VALUES]: "str" })).toThrow(/\.a:/); | ||
expect(() => check({ a: 1 }, { [VALUES]: "str" })).toThrow(/must be string/i); | ||
expect(() => check({ a: 1 }, { [VALUES]: "str" }, "doc")).toThrow(TypeError); | ||
expect(() => check({ a: 1 }, { [VALUES]: "str" }, "doc")).toThrow(/doc.a:/); | ||
expect(() => check({ a: 1 }, { [VALUES]: "str" }, "doc")).toThrow(/must be string/i); | ||
expect(() => check({ a: 1, b: 2, c: "c" }, { a: "num", [VALUES]: "num" })).toThrow(TypeError); | ||
expect(() => check({ a: new Map() }, { [VALUES]: Set })).toThrow(TypeError); | ||
expect(() => check({ a: new Map(), b: new Set(), c: new Set() }, { [VALUES]: Set })).toThrow(TypeError); | ||
}); | ||
test("Deep object literal types with VALUES property pass correctly", () => { | ||
expect(check({ a: "a", b: { bb: 22, bc: 23 } }, { a: "str", b: { [VALUES]: "num" } })).toBe(undefined); | ||
}); | ||
}); | ||
test("Object literal types with _any property fail correctly", () => { | ||
expect(() => check({ a: 1 }, { _any: "str" })).toThrow(TypeError); | ||
expect(() => check({ a: 1 }, { _any: "str" })).toThrow(/a:/); | ||
expect(() => check({ a: 1 }, { _any: "str" })).toThrow(/must be string/i); | ||
expect(() => check({ a: 1 }, { _any: "str" }, "doc")).toThrow(TypeError); | ||
expect(() => check({ a: 1 }, { _any: "str" }, "doc")).toThrow(/doc.a:/); | ||
expect(() => check({ a: 1 }, { _any: "str" }, "doc")).toThrow(/must be string/i); | ||
expect(() => check({ a: 1, b: 2, c: "c" }, { a: "num", _any: "num" })).toThrow(TypeError); | ||
expect(() => check({ a: new Map() }, { _any: Set })).toThrow(TypeError); | ||
expect(() => check({ a: new Map(), b: new Set(), c: new Set() }, { _any: Set })).toThrow(TypeError); | ||
describe("Circular references", () => { | ||
test("No infinite loop when value contains circular references", () => { | ||
const value = {}; | ||
value.c = value; | ||
expect(check(value, { c: { c: Object } })).toBe(undefined); | ||
}); | ||
test("No infinite loop when value contains deep circular references", () => { | ||
const value = { c: { c: { c: {} } } }; | ||
value.c.c.c.c = value; | ||
expect(check(value, { c: { c: { c: { c: { c: Object } } } } })).toBe(undefined); | ||
}); | ||
test("Throw BlorkError when type contains circular references", () => { | ||
const type = {}; | ||
type.c = type; | ||
expect(() => check({ c: { c: {} } }, type)).toThrow(BlorkError); | ||
expect(() => check({ c: { c: {} } }, type)).toThrow(/circular references/); | ||
}); | ||
test("Throw BlorkError when type contains deep circular references", () => { | ||
const type = { c: { c: { c: {} } } }; | ||
type.c = type; | ||
expect(() => check({ c: { c: { c: { c: { c: true } } } } }, type)).toThrow(BlorkError); | ||
expect(() => check({ c: { c: { c: { c: { c: true } } } } }, type)).toThrow(/circular references/); | ||
}); | ||
}); | ||
test("Deep object literal types with _any property pass correctly", () => { | ||
expect(check({ a: "a", b: { bb: 22, bc: 23 } }, { a: "str", b: { _any: "num" } })).toBe(undefined); | ||
}); | ||
test("No infinite loop when value contains circular references", () => { | ||
const value = {}; | ||
value.c = value; | ||
expect(check(value, { c: { c: Object } })).toBe(undefined); | ||
}); | ||
test("No infinite loop when value contains deep circular references", () => { | ||
const value = { c: { c: { c: {} } } }; | ||
value.c.c.c.c = value; | ||
expect(check(value, { c: { c: { c: { c: { c: Object } } } } })).toBe(undefined); | ||
}); | ||
test("Throw BlorkError when type contains circular references", () => { | ||
const type = {}; | ||
type.c = type; | ||
expect(() => check({ c: { c: {} } }, type)).toThrow(BlorkError); | ||
expect(() => check({ c: { c: {} } }, type)).toThrow(/circular references/); | ||
}); | ||
test("Throw BlorkError when type contains deep circular references", () => { | ||
const type = { c: { c: { c: {} } } }; | ||
type.c = type; | ||
expect(() => check({ c: { c: { c: { c: { c: true } } } } }, type)).toThrow(BlorkError); | ||
expect(() => check({ c: { c: { c: { c: { c: true } } } } }, type)).toThrow(/circular references/); | ||
}); | ||
}); |
@@ -17,74 +17,116 @@ const BlorkError = require("../lib/errors/BlorkError"); | ||
}); | ||
test("Optional types pass correctly", () => { | ||
expect(check(1, "number?")).toBe(undefined); | ||
expect(check("a", "string?")).toBe(undefined); | ||
expect(check({}, "object?")).toBe(undefined); | ||
expect(check(undefined, "number?")).toBe(undefined); | ||
expect(check(undefined, "string?")).toBe(undefined); | ||
expect(check(undefined, "object?")).toBe(undefined); | ||
describe("Optional types", () => { | ||
test("Optional types pass correctly", () => { | ||
expect(check(1, "number?")).toBe(undefined); | ||
expect(check("a", "string?")).toBe(undefined); | ||
expect(check({}, "object?")).toBe(undefined); | ||
expect(check(undefined, "number?")).toBe(undefined); | ||
expect(check(undefined, "string?")).toBe(undefined); | ||
expect(check(undefined, "object?")).toBe(undefined); | ||
}); | ||
test("Optional types fail correctly", () => { | ||
expect(() => check("a", "number?")).toThrow(TypeError); | ||
expect(() => check(1, "string?")).toThrow(TypeError); | ||
expect(() => check(1, "object?")).toThrow(TypeError); | ||
}); | ||
test("Optional types have correct error message", () => { | ||
expect(() => check(true, "string?")).toThrow(/Must be string or empty/); | ||
expect(() => check("abc", "boolean?")).toThrow(/Must be true or false or empty/); | ||
}); | ||
}); | ||
test("Optional types fail correctly", () => { | ||
expect(() => check("a", "number?")).toThrow(TypeError); | ||
expect(() => check(1, "string?")).toThrow(TypeError); | ||
expect(() => check(1, "object?")).toThrow(TypeError); | ||
describe("Invert types", () => { | ||
test("Invert types pass correctly", () => { | ||
expect(check("abc", "!number")).toBe(undefined); | ||
expect(check(123, "!string")).toBe(undefined); | ||
expect(check([], "!object")).toBe(undefined); | ||
expect(check(NaN, "!number")).toBe(undefined); | ||
expect(check(123, "!string")).toBe(undefined); | ||
expect(check({}, "!array")).toBe(undefined); | ||
}); | ||
test("Invert types fail correctly", () => { | ||
expect(() => check("abc", "!string")).toThrow(TypeError); | ||
expect(() => check(123, "!num")).toThrow(TypeError); | ||
expect(() => check({}, "!object")).toThrow(TypeError); | ||
}); | ||
test("Invert types have correct error message", () => { | ||
expect(() => check("abc", "!string")).toThrow(/Must be not string/); | ||
expect(() => check(true, "!boolean")).toThrow(/Must be not true or false/); | ||
}); | ||
}); | ||
test("Optional types have correct error message", () => { | ||
expect(() => check(true, "string?")).toThrow(/Must be string or empty/); | ||
expect(() => check("abc", "boolean?")).toThrow(/Must be true or false or empty/); | ||
describe("Non-empty types", () => { | ||
test("Non-empty types pass correctly", () => { | ||
expect(check("a", "string+")).toBe(undefined); | ||
expect(check("a", "str+")).toBe(undefined); | ||
expect(check("a", "lower+")).toBe(undefined); | ||
expect(check("A", "upper+")).toBe(undefined); | ||
expect(check({ a: 1 }, "object+")).toBe(undefined); | ||
expect(check({ a: 1 }, "obj+")).toBe(undefined); | ||
expect(check([1], "array+")).toBe(undefined); | ||
expect(check([1], "arr+")).toBe(undefined); | ||
expect(check(new Map([[1, 1]]), "map+")).toBe(undefined); | ||
expect(check(new Set([1]), "set+")).toBe(undefined); | ||
expect(check(true, "bool+")).toBe(undefined); // Not relevant. | ||
expect(check(123, "number+")).toBe(undefined); // Not relevant. | ||
}); | ||
test("Non-empty types fail correctly", () => { | ||
expect(() => check("", "string+")).toThrow(TypeError); | ||
expect(() => check("", "str+")).toThrow(TypeError); | ||
expect(() => check("A", "lower+")).toThrow(TypeError); | ||
expect(() => check("a", "upper+")).toThrow(TypeError); | ||
expect(() => check({}, "object+")).toThrow(TypeError); | ||
expect(() => check({}, "obj+")).toThrow(TypeError); | ||
expect(() => check([], "array+")).toThrow(TypeError); | ||
expect(() => check([], "arr+")).toThrow(TypeError); | ||
expect(() => check(new Map(), "map+")).toThrow(TypeError); | ||
expect(() => check(new Set(), "set+")).toThrow(TypeError); | ||
expect(() => check(false, "bool+")).toThrow(TypeError); // Not relevant. | ||
expect(() => check(0, "number+")).toThrow(TypeError); // Not relevant. | ||
}); | ||
}); | ||
test("Invert types pass correctly", () => { | ||
expect(check("abc", "!number")).toBe(undefined); | ||
expect(check(123, "!string")).toBe(undefined); | ||
expect(check([], "!object")).toBe(undefined); | ||
expect(check(NaN, "!number")).toBe(undefined); | ||
expect(check(123, "!string")).toBe(undefined); | ||
expect(check({}, "!array")).toBe(undefined); | ||
describe("Combined types", () => { | ||
test("AND combined types pass correctly", () => { | ||
expect(check(1, "number & integer")).toBe(undefined); | ||
expect(check(1, "num & +int")).toBe(undefined); | ||
expect(check("abc", "str & lower+")).toBe(undefined); | ||
expect(check("ABC", "str & upper+")).toBe(undefined); | ||
}); | ||
test("AND combined types fail correctly", () => { | ||
expect(() => check("a", "number & string")).toThrow(TypeError); | ||
expect(() => check("a", "number & string")).toThrow(/Must be finite number and string/); | ||
}); | ||
test("OR combined types pass correctly", () => { | ||
expect(check(1, "number|string")).toBe(undefined); | ||
expect(check("a", "number|string")).toBe(undefined); | ||
expect(check("ABC", "string | lower+")).toBe(undefined); | ||
expect(check(null, "string | number | null")).toBe(undefined); | ||
}); | ||
test("OR combined types fail correctly", () => { | ||
expect(() => check(true, "number|string")).toThrow(TypeError); | ||
expect(() => check(Symbol(), "number|string")).toThrow(TypeError); | ||
expect(() => check(Symbol(), "number|string")).toThrow(/finite number or string/); | ||
}); | ||
test("AND and OR combined types combine correctly", () => { | ||
// `&` has higher precedence than `|` | ||
expect(check("abc", "string & lower | upper")).toBe(undefined); | ||
expect(check("ABC", "string & lower | upper")).toBe(undefined); | ||
expect(() => check("ABCabc", "string & lower | upper")).toThrow(TypeError); | ||
expect(check("abc", "lower | upper & string")).toBe(undefined); | ||
expect(check("ABC", "lower | upper & string")).toBe(undefined); | ||
expect(() => check("ABCabc", "lower | upper & string")).toThrow(TypeError); | ||
}); | ||
test("AND and OR combined types have correct error message", () => { | ||
expect(() => check("ABCdef", "string & lower | upper")).toThrow(/string/); | ||
expect(() => check("ABCdef", "string & lower | upper")).toThrow(/and/); | ||
expect(() => check("ABCdef", "string & lower | upper")).toThrow(/or/); | ||
expect(() => check("ABCdef", "string & lower | upper")).toThrow(/UPPERCASE/); | ||
expect(() => check("ABCdef", "string & lower | upper")).toThrow(/lowercase/); | ||
expect(() => check("ABCdef", "string & lower | upper")).toThrow(/string/); | ||
expect(() => check("ABCdef", "lower | upper & string")).toThrow(/string/); | ||
expect(() => check("ABCdef", "lower | upper & string")).toThrow(/and/); | ||
expect(() => check("ABCdef", "lower | upper & string")).toThrow(/or/); | ||
expect(() => check("ABCdef", "lower | upper & string")).toThrow(/UPPERCASE/); | ||
expect(() => check("ABCdef", "lower | upper & string")).toThrow(/lowercase/); | ||
expect(() => check("ABCdef", "lower | upper & string")).toThrow(/string/); | ||
}); | ||
}); | ||
test("Invert types fail correctly", () => { | ||
expect(() => check("abc", "!string")).toThrow(TypeError); | ||
expect(() => check(123, "!num")).toThrow(TypeError); | ||
expect(() => check({}, "!object")).toThrow(TypeError); | ||
}); | ||
test("Invert types have correct error message", () => { | ||
expect(() => check("abc", "!string")).toThrow(/Must be not string/); | ||
expect(() => check(true, "!boolean")).toThrow(/Must be not true or false/); | ||
}); | ||
test("AND combined types pass correctly", () => { | ||
expect(check(1, "number & integer")).toBe(undefined); | ||
expect(check(1, "num & int+")).toBe(undefined); | ||
expect(check("abc", "str & lower+")).toBe(undefined); | ||
expect(check("ABC", "str & upper+")).toBe(undefined); | ||
}); | ||
test("AND combined types fail correctly", () => { | ||
expect(() => check("a", "number & string")).toThrow(TypeError); | ||
expect(() => check("a", "number & string")).toThrow(/Must be finite number and string/); | ||
}); | ||
test("OR combined types pass correctly", () => { | ||
expect(check(1, "number|string")).toBe(undefined); | ||
expect(check("a", "number|string")).toBe(undefined); | ||
expect(check("ABC", "string | lower+")).toBe(undefined); | ||
expect(check(null, "string | number | null")).toBe(undefined); | ||
}); | ||
test("OR combined types fail correctly", () => { | ||
expect(() => check(true, "number|string")).toThrow(TypeError); | ||
expect(() => check(Symbol(), "number|string")).toThrow(TypeError); | ||
expect(() => check(Symbol(), "number|string")).toThrow(/finite number or string/); | ||
}); | ||
test("AND and OR combined types combine correctly", () => { | ||
// `&` has higher precedence than `|` | ||
expect(check("abc", "string & lower | upper")).toBe(undefined); | ||
expect(check("ABC", "string & lower | upper")).toBe(undefined); | ||
expect(() => check("ABCabc", "string & lower | upper")).toThrow(TypeError); | ||
expect(check("abc", "lower | upper & string")).toBe(undefined); | ||
expect(check("ABC", "lower | upper & string")).toBe(undefined); | ||
expect(() => check("ABCabc", "lower | upper & string")).toThrow(TypeError); | ||
}); | ||
test("AND and OR combined types have correct error message", () => { | ||
expect(() => check("ABCabc", "string & lower | upper")).toThrow( | ||
/Must be string and lowercase string or uppercase string/ | ||
); | ||
expect(() => check("ABCabc", "lower | upper & string")).toThrow( | ||
/Must be lowercase string or uppercase string and string/ | ||
); | ||
}); | ||
}); |
@@ -15,3 +15,3 @@ const BlorkError = require("../lib/errors/BlorkError"); | ||
}); | ||
test("Do not throw error if passing string name", () => { | ||
test("Do not throw error if passing string prefix", () => { | ||
expect(check(true, "bool", "myValue")).toBe(undefined); | ||
@@ -22,5 +22,9 @@ expect(check(true, Boolean, "myValue")).toBe(undefined); | ||
}); | ||
test("Throw BlorkError if passing non-string name", () => { | ||
test("Throw BlorkError if passing non-string prefix", () => { | ||
expect(() => check(1, "bool", 123)).toThrow(BlorkError); | ||
expect(() => check(1, "bool", 123)).toThrow(/check\(\):/); | ||
expect(() => check(1, "bool", 123)).toThrow(/prefix:/); | ||
expect(() => check(1, "bool", 123)).toThrow(/string/); | ||
expect(() => check(1, "bool", 123)).toThrow(/123/); | ||
}); | ||
}); |
@@ -5,3 +5,3 @@ const BlorkError = require("../lib/errors/BlorkError"); | ||
// Tests. | ||
describe("exports.check()", () => { | ||
describe("exports.checker()", () => { | ||
test("Getting and using a checker works correctly", () => { | ||
@@ -17,3 +17,6 @@ expect(checker("string")("abc")).toBe(true); | ||
expect(() => checker("abc")).toThrow(BlorkError); | ||
expect(() => checker("abc")).toThrow(/checker\(\):/); | ||
expect(() => checker("abc")).toThrow(/not found/); | ||
expect(() => checker("abc")).toThrow(/"abc"/); | ||
}); | ||
}); |
@@ -32,5 +32,9 @@ const ValueError = require("../lib/errors/ValueError"); | ||
expect(() => throws(false)).toThrow(BlorkError); | ||
expect(() => throws({})).toThrow(BlorkError); | ||
expect(() => throws(123)).toThrow(BlorkError); | ||
expect(() => throws({})).toThrow(BlorkError); | ||
expect(() => throws(123)).toThrow("throws():"); | ||
expect(() => throws(123)).toThrow("error:"); | ||
expect(() => throws(123)).toThrow("function"); | ||
expect(() => throws(123)).toThrow("123"); | ||
}); | ||
}); |
132887
39
2250
704