Comparing version 8.0.0 to 8.1.1
@@ -153,3 +153,3 @@ const ValueError = require("../errors/ValueError"); | ||
* Checker function should take in a value, check it and return `true` (success) or `false` (fail). | ||
* This format is chosen because it allows buttery constructions like `check.add(const str = v => (typeof v === 'string' || 'must be string'); | ||
* This format is chosen because it allows buttery constructions like `check.add(const str = v => typeof v === 'string', 'string'); | ||
* | ||
@@ -308,3 +308,3 @@ * @param {string} name The type reference for the checker in kebab-case format. | ||
if (stack.indexOf(type) >= 0) | ||
throwError(BlorkError, "Blork type must not contain circular references", type); | ||
throwError(BlorkError, "Blork array type must not contain circular references", type); | ||
@@ -315,3 +315,4 @@ // Value can have circular references, but don't keep checking it over and over. | ||
// Push type and value into the stack. | ||
stack = [...stack, type, value]; | ||
stack.push(type); | ||
stack.push(value); | ||
} else { | ||
@@ -326,10 +327,15 @@ // First loop. Start a stack. | ||
// Loop through items and check they match type[0] | ||
value.forEach((v, i) => this._check(v, type[0], `${prefix}[${i}]`, error, stack)); | ||
} else { | ||
// Tuple array | ||
for (let i = 0, l = value.length; i < l; i++) | ||
this._check(value[i], type[0], `${prefix}[${i}]`, error, stack); | ||
} else if (type.length > 1) { | ||
// Tuple array (more than | ||
// Loop through types and match each with a value recursively. | ||
type.forEach((t, i) => this._check(value[i], t, `${prefix}[${i}]`, error, stack)); | ||
for (let i = 0, l = type.length; i < l; i++) | ||
this._check(value[i], type[i], `${prefix}[${i}]`, error, stack); | ||
// No excess items in a tuple. | ||
if (value.length > type.length) throwError(error, `Must have ${type.length} items`, value.length, prefix); | ||
} else { | ||
// Must have at least one item. | ||
throwError(BlorkError, `Blork array type must have one or more items`, value.length, prefix); | ||
} | ||
@@ -365,3 +371,3 @@ } | ||
if (stack.indexOf(type) >= 0) | ||
throwError(BlorkError, "Blork type must not contain circular references", type); | ||
throwError(BlorkError, "Blork object type must not contain circular references", type); | ||
@@ -372,3 +378,4 @@ // Value can have circular references, but don't keep checking it over and over. | ||
// Push type and value into the stack. | ||
stack = [...stack, type, value]; | ||
stack.push(type); | ||
stack.push(value); | ||
} else { | ||
@@ -379,10 +386,11 @@ // First loop. Kick off a stack. | ||
// Vars. | ||
// Loop through each key in the types object (must match exactly). | ||
const typeKeys = Object.keys(type); | ||
for (let i = 0, l = typeKeys.length; i < l; i++) { | ||
// Vars. | ||
const key = typeKeys[i]; | ||
// Loop through each item in the types object. | ||
typeKeys.forEach(key => { | ||
// Check that the value matches the specified key. | ||
this._check(value[key], type[key], `${prefix}.${key}`, error, stack); | ||
}); | ||
} | ||
@@ -403,9 +411,15 @@ // Get the KEYS and VALUES types. | ||
// 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); | ||
for (let i = 0, l = valueKeys.length; i < l; i++) { | ||
// Vars. | ||
const key = valueKeys[i]; | ||
// Check the value against the VALUES type. | ||
if (valuesType) this._check(value[key], valuesType, `${prefix}.${key}`, error, stack); | ||
}); | ||
// Ignore ones in type. | ||
if (!type.hasOwnProperty(key)) { | ||
// If there's a KEYS type, check the key against that. | ||
if (keysType) this._check(key, keysType, `${prefix}.${key}: Key`, error, stack); | ||
// Check the value against the VALUES type. | ||
if (valuesType) this._check(value[key], valuesType, `${prefix}.${key}`, error, stack); | ||
} | ||
} | ||
} | ||
@@ -412,0 +426,0 @@ } |
const BlorkError = require("./errors/BlorkError"); | ||
// Regexs. | ||
// `&` ampersand with possible whitespace either side that isn't not enclosed in parenthesis or braces (via `(?!` lookahead). | ||
// `&` ampersand with possible whitespace either side that isn't enclosed in `([{` any parenthesis (via `(?!` lookahead). | ||
const R_AND_SPLIT = /\s*&+\s*(?![^(]*\))(?![^{]*\})(?![^[]*\])/; | ||
// `|` ampersand with possible whitespace either side that isn't not enclosed in parenthesis or braces (via `(?!` lookahead). | ||
// `|` ampersand with possible whitespace either side that isn't enclosed in `([{` any parenthesis (via `(?!` lookahead). | ||
const R_OR_SPLIT = /\s*\|+\s*(?![^(]*\))(?![^{]*\})(?![^[]*\])/; | ||
@@ -65,3 +65,3 @@ // Split commas in a tuple. | ||
// Loop through and call each checker. | ||
for (let i = 0; i < ands.length; i++) if (!ands[i](v)) return false; // Fail. | ||
for (let i = 0, l = ands.length; i < l; i++) if (!ands[i](v)) return false; // Fail. | ||
return true; // Otherwise pass. | ||
@@ -95,3 +95,3 @@ }; | ||
// Loop through and call each checker. | ||
for (let i = 0; i < ors.length; i++) if (ors[i](v)) return true; // Pass. | ||
for (let i = 0, l = ors.length; i < l; i++) if (ors[i](v)) return true; // Pass. | ||
return false; // Otherwise fail. | ||
@@ -222,3 +222,3 @@ }; | ||
// Create an optional checker for this optional type. | ||
// Create an optional checker for this inverted type. | ||
// Returns 0 if undefined, or passes through to the normal checker. | ||
@@ -238,3 +238,3 @@ const checker = v => !valueChecker(v); | ||
{ | ||
// One or more non-whitespace followed by `+` plus. | ||
// One or more chars followed by `+` plus. | ||
regex: /^(.+)\+$/, | ||
@@ -246,3 +246,3 @@ callback(matches, find) { | ||
// Create a length checker for this optional type. | ||
// Create a length checker for this non-empty type. | ||
// Returns true if checker passes and there's a numeric length or size property with a value of >0. | ||
@@ -265,5 +265,57 @@ const checker = v => { | ||
// Size type. | ||
// e.g. `str{12}` (string with exactly 12 chars) | ||
// e.g. `num{64,128}` (nums between 64 and 128) | ||
// e.g. `arr{4,}` (arrays with a minimum of 4 itmes) | ||
{ | ||
// One or more chars followed by {12} or {12,} or {,12} or {6,12} | ||
regex: /^(.+)\{(?:([0-9]+)|([0-9]+),([0-9]*)|,([0-9]+))\}$/, | ||
callback(matches, find) { | ||
// Get normal checker. | ||
const valueChecker = find(matches[1]); | ||
// Vars. | ||
const min = matches[2] ? parseInt(matches[2], 10) : matches[3] ? parseInt(matches[3], 10) : null; | ||
const max = matches[2] | ||
? parseInt(matches[2], 10) | ||
: matches[4] ? parseInt(matches[4], 10) : matches[5] ? parseInt(matches[5]) : null; | ||
// Create a length checker for this type. | ||
const checker = v => { | ||
// Must pass the checker first. | ||
if (!valueChecker(v)) return false; | ||
// Numbers use exact number | ||
if (typeof v === "number") return (min === null || v >= min) && (max === null || v <= max); | ||
// String and Array use .length | ||
if (typeof v === "string" || v instanceof Array) | ||
return (min === null || v.length >= min) && (max === null || v.length <= max); | ||
// Map and set use .size | ||
if (v instanceof Map || v instanceof Set) | ||
return (min === null || v.size >= min) && (max === null || v.size <= max); | ||
// Objects use the number of keys. | ||
if (typeof v === "object" && v !== null) { | ||
const l = Object.keys(v).length; | ||
return (min === null || l >= min) && (max === null || l <= max); | ||
} | ||
// Nothing else has length to check. | ||
return false; | ||
}; | ||
// Checker settings. | ||
checker.modified = true; | ||
checker.desc = wrapCombined(valueChecker); | ||
if (min === max) checker.desc += ` with size ${min}`; | ||
else if (typeof min === "number") { | ||
if (typeof max === "number") checker.desc += ` with size between ${min} and ${max}`; | ||
else checker.desc += ` with minimum size ${min}`; | ||
} else checker.desc += ` with maximum size ${max}`; | ||
// Return it. | ||
return checker; | ||
} | ||
}, | ||
// Optional type, e.g. `num?` | ||
{ | ||
// One or more non-whitespace followed by `?` question mark. | ||
// One or more chars followed by `?` question mark. | ||
regex: /^(.+)\?$/, | ||
@@ -270,0 +322,0 @@ callback(matches, find) { |
{ | ||
"name": "blork", | ||
"description": "Blork! Mini runtime type checking in Javascript", | ||
"version": "8.0.0", | ||
"version": "8.1.1", | ||
"license": "0BSD", | ||
@@ -6,0 +6,0 @@ "author": "Dave Houlbrooke <dave@shax.com>", |
147
README.md
@@ -19,38 +19,5 @@ # Blork! Mini runtime type checking in Javascript | ||
### args(): Check function arguments | ||
The primary use case of Blork is validating function input arguments. The `args()` function is provided for this purpose, and can be passed four arguments: | ||
1. `arguments` | The **arguments** object provided automatically to functions in Javascript | ||
2. `types` | An array identifying the types for the arguments (list of types is available below) | ||
3. `prefix` An optional string name/prefix for the value, which is prepended to any error message thrown to help debugging | ||
4. `error` An optional custom error type to throw if the check fails | ||
```js | ||
import { args } from "blork"; | ||
// An exported function other (untrusted) developers may use. | ||
export default function myFunc(definitelyString, optionalNumber) | ||
{ | ||
// Check the args. | ||
args(arguments, ["string", "number?"]); | ||
// Rest of the function. | ||
return "It passed!"; | ||
} | ||
// Call with good args. | ||
myFunc("abc", 123); // Returns "It passed!" | ||
myFunc("abc"); // Returns "It passed!" | ||
// Call with invalid args. | ||
myFunc(123); // Throws ValueError "myFunc(): arguments[0]: Must be string (received 123)" | ||
myFunc("abc", "abc"); // Throws ValueError "myFunc(): arguments[1]: Must be number (received "abc")" | ||
myFunc(); // Throws ValueError "myFunc(): arguments[0]: Must be string (received undefined)" | ||
myFunc("abc", 123, true); // Throws ValueError "myFunc(): arguments: Too many arguments (expected 2) (received 3)" | ||
``` | ||
### check(): Check individual values | ||
The `check()` function allows you to test individual values with more granularity. The `check()` function is more versatile and allows more use cases than validating function input arguments. | ||
The `check()` function allows you to test that individual values correspond to a type, and throw a `TypeError` if not. This is primarily designed for checking function arguments but can be used for any purpose. | ||
@@ -60,3 +27,3 @@ `check()` accepts four arguments: | ||
1. `value` The value to check | ||
2. `type` The type to check the value against (list of types is available below) | ||
2. `type` The type to check the value against (full reference list of types is available below) | ||
3. `prefix` An optional string name/prefix for the value, which is prepended to any error message thrown to help debugging | ||
@@ -84,30 +51,19 @@ 4. `error` An optional custom error type to throw if the check fails | ||
Another common use for `check()` is to validate an options object: | ||
## Type modifiers | ||
```js | ||
import { check } from "blork"; | ||
`type` will mostly be specified with a type string (a full list of string types is available below), and these string types can also be modified using other characters: | ||
// Make a custom function. | ||
function myFunc(options) | ||
{ | ||
// Check all the options with a literal type (note that keepAlive is optional). | ||
check(options, { name: "string", required: "boolean", keepAlive: "number?" }, "options"); | ||
} | ||
- Appending `?` question mark to any type string makes it optional (which means it also allows `undefined`). | ||
- Prepending a `!` exclaimation mark to any type string makes it inverted (e.g. `!string` means anything except string). | ||
- Multiple types can be combined with `|` and `&` for OR and AND conditions (optionally grouped with `()` parens to resolve ambiguity). | ||
- Appending a `+` means non-empty (e.g. `arr+` `str+` means non-empty arrays and strings respectively). | ||
// Checks that pass. | ||
myFunc({ name: "Dog", required: true }); // No error. | ||
// Checks that fail. | ||
myFunc({ name: 123, required: false }); // Throws ValueError "myFunc(): options.name: Must be string (received 123)" | ||
``` | ||
There are more complex types available: Appending `?` question mark to any type string makes it optional (which means it also allows `undefined`). Prepending a `!` exclaimation mark to any type string makes it inverted. Multiple types can be combined with `|` and `&` for OR and AND conditions. | ||
```js | ||
// Optional types. | ||
check(undefined, "number"); // Throws ValueError "Must be number (received undefined)" | ||
check(undefined, "number"); // Throws ValueError "Must be finite number (received undefined)" | ||
check(undefined, "number?"); // No error. | ||
// Note that null does not count as optional. | ||
check(null, "number?"); // Throws ValueError "Must be number (received null)" | ||
check(null, "number?"); // Throws ValueError "Must be finite number (received null)" | ||
@@ -119,4 +75,4 @@ // Inverted types. | ||
// Combined OR types. | ||
check(1234, "num | str"); // No error. | ||
check(null, "num | str"); // Throws ValueError "Must be number or string (received null)" | ||
check(1234, "int | str"); // No error. | ||
check(null, "int | str"); // Throws ValueError "Must be integer or string (received null)" | ||
@@ -126,2 +82,24 @@ // Combined AND types. | ||
check("", "string & !falsy"); // Throws ValueError "Must be string and not falsy (received "")" | ||
// Non-empty types. | ||
check("abc", "str+"); // No error. | ||
check("", "str+"); // Throws ValueError "Must be non-empty string (received "")" | ||
// Size types. | ||
check([1, 2, 3], "arr{2,4}"); // No error. | ||
check([1], "arr{2,3}"); // Throws ValueError "Must be plain array (minimum 2) (maximum 3) (received [1])" | ||
check([1, 3, 3, 4], "arr{,3}"); // Throws ValueError "Must be plain array (maximum 3) (received [1])" | ||
check([1, 2], "arr{3,}"); // Throws ValueError "Must be plain array (minimum 2) (received [1])" | ||
// Array types. | ||
check([1, 2, 3], "num[]"); // No error. | ||
check(["a", "b"], "num[]"); // Throws ValueError "Must be plain array containing finite number (received ["a", "b"])" | ||
// Tuple types. | ||
check([1, "a"], "[int, str]"); // No error. | ||
check([1, false], "[int, str]"); // Throws ValueError "Must be plain array tuple containing integer, string (received [1, false])" | ||
// Object types. | ||
check({ a: 1 }, "{ camel: integer }"); // No error. | ||
check({ "$": 1 }, "{ camel: integer }"); // Throws ValueError "Must be plain object with camelCase string keys containing integer (received { "$": 1 })" | ||
``` | ||
@@ -131,3 +109,3 @@ | ||
Blork can perform deep checks on objects and arrays to ensure the schema is correct. To do object or array checks pass literal arrays or literal objects to `check()` or `args()`: | ||
Blork can also perform deep checks on objects and arrays to ensure the schema is correct deeply. You can use literal arrays or literal objects with `check()` or `args()` to do so: | ||
@@ -179,2 +157,35 @@ ```js | ||
### args(): Check function arguments | ||
The primary use case of Blork is validating function input arguments. The `args()` function is provided for this purpose and can be passed four arguments: | ||
1. `arguments` | The **arguments** object provided automatically to functions in Javascript | ||
2. `types` | An array identifying the types for the arguments (list of types is available below) | ||
3. `prefix` An optional string name/prefix for the value, which is prepended to any error message thrown to help debugging | ||
4. `error` An optional custom error type to throw if the check fails | ||
```js | ||
import { args } from "blork"; | ||
// An exported function other (untrusted) developers may use. | ||
export default function myFunc(definitelyString, optionalNumber) | ||
{ | ||
// Check the args. | ||
args(arguments, ["string", "number?"]); | ||
// Rest of the function. | ||
return "It passed!"; | ||
} | ||
// Call with good args. | ||
myFunc("abc", 123); // Returns "It passed!" | ||
myFunc("abc"); // Returns "It passed!" | ||
// Call with invalid args. | ||
myFunc(123); // Throws ValueError "myFunc(): arguments[0]: Must be string (received 123)" | ||
myFunc("abc", "abc"); // Throws ValueError "myFunc(): arguments[1]: Must be number (received "abc")" | ||
myFunc(); // Throws ValueError "myFunc(): arguments[0]: Must be string (received undefined)" | ||
myFunc("abc", 123, true); // Throws ValueError "myFunc(): arguments: Too many arguments (expected 2) (received 3)" | ||
``` | ||
### assert(): Check a random true/false statement. | ||
@@ -417,3 +428,3 @@ | ||
| `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) | ||
| `json`, `jsonable` | Values that can be successfully converted to JSON _and back again!_ (null, true, false, finite numbers, strings, plain objects, plain arrays) | ||
@@ -524,6 +535,22 @@ ### String modifiers | ||
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. | ||
To specify a size for the type, you can prepend minimum/maximum with e.g. `{12}`, `{4,8}`, `{4,}` or `{,8}` (e.g. RegExp style quantifiers). This allows you to specify e.g. a string with 12 characters, an array with between 10 and 20 items, or an integer with a minimum value of 4. | ||
```js | ||
// Pass. | ||
check("abc", "str{3}"); // No error (string with exact length 3 characters). | ||
check(4, "num{,4}"); // No error (number with maximum value 4). | ||
check(["a", "b"], "arr{1,}"); // No error (array with more than 1 item). | ||
check([1, 2, 3], "num[]{2,4}"); // No error (array of numbers with between 2 and 4 items). | ||
// Fail. | ||
check("ab", "str{3}"); // Throws ValueError "Must be string with size 3" | ||
check(4, "num{,4}"); // Throws ValueError "Must be finite number with maximum size 4" | ||
check(["a", "b"], "arr{1,}"); // Throws ValueError "Must be array with minimum size 1" | ||
check([1, 2, 3], "num[]{2,4}"); // Throws ValueError "Must be plain array containing finite number with size between 2 and 4" | ||
``` | ||
Any string type can inverted by prepending a `!` exclamation mark to the type reference. This means the check will only pass if the _inverse_ of its type is true. | ||
```js | ||
// Pass. | ||
check(undefined, "!str"); // No error. | ||
@@ -761,2 +788,4 @@ check("Abc", "!lower"); // No error. | ||
- 8.1.0 | ||
- Add min/max size constraints on types via e.g. `{4,8}` suffix | ||
- 8.0.0 | ||
@@ -763,0 +792,0 @@ - Remove `props()` functionality (bloat) |
@@ -15,2 +15,12 @@ const isJSONable = require("../../lib/checkers/isJSONable"); | ||
expect(isJSONable("abc")).toBe(true); | ||
expect(isJSONable({ a: true })).toBe(true); | ||
expect(isJSONable({ a: false })).toBe(true); | ||
expect(isJSONable({ a: null })).toBe(true); | ||
expect(isJSONable({ a: 123 })).toBe(true); | ||
expect(isJSONable({ a: -123 })).toBe(true); | ||
expect(isJSONable({ a: 1.5 })).toBe(true); | ||
expect(isJSONable({ a: -1.5 })).toBe(true); | ||
expect(isJSONable({ a: "" })).toBe(true); | ||
expect(isJSONable({ a: "a" })).toBe(true); | ||
expect(isJSONable({ a: "abc" })).toBe(true); | ||
}); | ||
@@ -20,2 +30,4 @@ test("Plain arrays are JSON friendly", () => { | ||
expect(isJSONable(arr)).toBe(true); | ||
const deepArr = [[1, 2, 3]]; | ||
expect(isJSONable(deepArr)).toEqual(true); | ||
}); | ||
@@ -25,30 +37,53 @@ test("Plain objects are JSON friendly", () => { | ||
expect(isJSONable(obj)).toBe(true); | ||
const deepObj = { deep: { a: 1, b: 2, c: 3 } }; | ||
expect(isJSONable(deepObj)).toEqual(true); | ||
}); | ||
test("Deep plain arrays are JSON friendly", () => { | ||
const arr = [[1, 2, 3]]; | ||
expect(isJSONable(arr)).toEqual(true); | ||
}); | ||
test("Deep plain objects are JSON friendly", () => { | ||
const obj = { deep: { a: 1, b: 2, c: 3 } }; | ||
expect(isJSONable(obj)).toEqual(true); | ||
}); | ||
test("Undefined is not JSON friendly", () => { | ||
expect(isJSONable(undefined)).toBe(false); | ||
expect(isJSONable({ a: undefined })).toBe(false); | ||
}); | ||
test("Symbols is not JSON friendly", () => { | ||
test("Symbols are not JSON friendly", () => { | ||
expect(isJSONable(Symbol("abc"))).toBe(false); | ||
expect(isJSONable({ a: Symbol("abc") })).toBe(false); | ||
}); | ||
test("Infinite numbers is not JSON friendly", () => { | ||
test("Infinite numbers are not JSON friendly", () => { | ||
expect(isJSONable(Infinity)).toBe(false); | ||
expect(isJSONable(-Infinity)).toBe(false); | ||
expect(isJSONable(NaN)).toBe(false); | ||
expect(isJSONable({ a: Infinity })).toBe(false); | ||
expect(isJSONable({ a: -Infinity })).toBe(false); | ||
expect(isJSONable({ a: NaN })).toBe(false); | ||
}); | ||
test("Complex objects is not JSON friendly", () => { | ||
expect(isJSONable({ complex: new class Something {}() })).toBe(false); | ||
expect(isJSONable({ arr: new class Megarray extends Array {}() })).toBe(false); | ||
expect(isJSONable({ str: new String("abc") })).toBe(false); | ||
expect(isJSONable({ date: new Date() })).toBe(false); | ||
expect(isJSONable({ func: () => {} })).toBe(false); | ||
test("Functions are not JSON friendly", () => { | ||
expect(isJSONable(() => {})).toBe(false); | ||
expect(isJSONable({ a: () => {} })).toBe(false); | ||
expect(isJSONable(String)).toBe(false); | ||
expect(isJSONable({ a: String })).toBe(false); | ||
}); | ||
test("Circular references in objects are not JSON friendly", () => { | ||
test("Constructors are not JSON friendly", () => { | ||
expect(isJSONable(new String("abc"))).toBe(false); | ||
expect(isJSONable({ a: new String("abc") })).toBe(false); | ||
expect(isJSONable(new Number(123))).toBe(false); | ||
expect(isJSONable({ a: new Number(123) })).toBe(false); | ||
}); | ||
test("Class instances are not JSON friendly", () => { | ||
class Something {} | ||
class Megarray extends Array {} | ||
expect(isJSONable(new Something())).toBe(false); | ||
expect(isJSONable(new Megarray())).toBe(false); | ||
expect(isJSONable({ a: new Something() })).toBe(false); | ||
expect(isJSONable({ a: new Megarray() })).toBe(false); | ||
}); | ||
test("Class instances are not JSON friendly", () => { | ||
expect(isJSONable(new Date())).toBe(false); | ||
expect(isJSONable({ a: new Date() })).toBe(false); | ||
class MyClass { | ||
toJSON() { | ||
return "whatever"; | ||
} | ||
} | ||
expect(isJSONable(new MyClass())).toBe(false); | ||
expect(isJSONable({ a: new MyClass() })).toBe(false); | ||
}); | ||
test("Circular references are not JSON friendly", () => { | ||
const obj1 = {}; | ||
@@ -60,4 +95,2 @@ obj1.circular = obj1; | ||
expect(isJSONable(obj2)).toBe(false); | ||
}); | ||
test("Circular references in arrays are not JSON friendly", () => { | ||
const arr1 = []; | ||
@@ -64,0 +97,0 @@ arr1[0] = arr1; |
@@ -15,2 +15,6 @@ const BlorkError = require("../lib/errors/BlorkError"); | ||
}); | ||
test("Throw BlorkError if array type is empty", () => { | ||
const arr = ["abc"]; | ||
expect(() => check(arr, [])).toThrow(BlorkError); | ||
}); | ||
test("Array literal types pass correctly", () => { | ||
@@ -17,0 +21,0 @@ expect(check([1, 2, 3], [Number])).toBe(undefined); |
@@ -32,3 +32,3 @@ const BlorkError = require("../lib/errors/BlorkError"); | ||
}); | ||
test("Optional types have correct error message", () => { | ||
test("Correct error message", () => { | ||
expect(() => check(true, "string?")).toThrow(/Must be string or empty/); | ||
@@ -54,3 +54,3 @@ expect(() => check("abc", "boolean?")).toThrow(/Must be boolean or empty/); | ||
}); | ||
test("Invert types have correct error message", () => { | ||
test("Correct error message", () => { | ||
expect(() => check("abc", "!string")).toThrow(/Must be anything except string/); | ||
@@ -91,3 +91,138 @@ expect(() => check(true, "!boolean")).toThrow(/Must be anything except boolean/); | ||
}); | ||
test("Correct error message", () => { | ||
expect(() => check(true, "str+")).toThrow(/Must be non-empty string/); | ||
expect(() => check([], "arr+")).toThrow(/Must be non-empty plain array/); | ||
}); | ||
}); | ||
describe("Size types", () => { | ||
describe("Exact size types", () => { | ||
test("Size types pass correctly", () => { | ||
expect(check("a", "string{1}")).toBe(undefined); | ||
expect(check("aaaa", "lower{4}")).toBe(undefined); | ||
expect(check("AAAAA", "upper{5}")).toBe(undefined); | ||
expect(check({ a: 1, b: 2 }, "obj{2}")).toBe(undefined); | ||
expect(check([1, "b", true], "array{3}")).toBe(undefined); | ||
expect(check(new Map([[1, 1], [2, 2]]), "map{2}")).toBe(undefined); | ||
expect(check(new Set([1, 2, "c", 4]), "set{4}")).toBe(undefined); | ||
expect(check(123, "number{123}")).toBe(undefined); | ||
}); | ||
test("Size types fail correctly", () => { | ||
expect(() => check("", "string{1}")).toThrow(TypeError); | ||
expect(() => check("aa", "string{1}")).toThrow(TypeError); | ||
expect(() => check("aaa", "lower{4}")).toThrow(TypeError); | ||
expect(() => check("aaaaa", "lower{4}")).toThrow(TypeError); | ||
expect(() => check("AAAA", "lower{4}")).toThrow(TypeError); | ||
expect(() => check({ a: 1 }, "obj{2}")).toThrow(TypeError); | ||
expect(() => check({ a: 1, b: 2, c: 3 }, "obj{2}")).toThrow(TypeError); | ||
expect(() => check([1, "b"], "array{3}")).toThrow(TypeError); | ||
expect(() => check([1, "b", 3, 4], "array{3}")).toThrow(TypeError); | ||
expect(() => check(new Map([[1, 1]]), "map{2}")).toThrow(TypeError); | ||
expect(() => check(new Map([[1, 1], [2, 2], [3, 3]]), "map{2}")).toThrow(TypeError); | ||
expect(() => check(new Set([1, 2, 3]), "set{4}")).toThrow(TypeError); | ||
expect(() => check(new Set([1, 2, 3, 4, 5]), "set{4}")).toThrow(TypeError); | ||
expect(() => check(122, "number{123}")).toThrow(TypeError); | ||
expect(() => check(124, "number{123}")).toThrow(TypeError); | ||
}); | ||
test("Correct error message", () => { | ||
expect(() => check("a", "str{10}")).toThrow(/Must be string with size 10/); | ||
expect(() => check(1, "int{12}")).toThrow(/Must be integer with size 12/); | ||
}); | ||
}); | ||
describe("Minimum size types", () => { | ||
test("Size types pass correctly", () => { | ||
expect(check("a", "string{1,}")).toBe(undefined); | ||
expect(check("aa", "string{1,}")).toBe(undefined); | ||
expect(check("aaaaaaaa", "lower{4,}")).toBe(undefined); | ||
expect(check("AAAAA", "upper{5,}")).toBe(undefined); | ||
expect(check({ a: 1, b: 2, c: 3 }, "obj{2,}")).toBe(undefined); | ||
expect(check([1, "b", true, 4], "array{3,}")).toBe(undefined); | ||
expect(check(new Map([[1, 1], [2, 2]]), "map{1,}")).toBe(undefined); | ||
expect(check(new Set([1, 2, "c", 4]), "set{4,}")).toBe(undefined); | ||
expect(check(124, "number{123,}")).toBe(undefined); | ||
}); | ||
test("Size types fail correctly", () => { | ||
expect(() => check("", "string{1,}")).toThrow(TypeError); | ||
expect(() => check("aaa", "lower{4,}")).toThrow(TypeError); | ||
expect(() => check("AAAAA", "lower{4,}")).toThrow(TypeError); | ||
expect(() => check({ a: 1 }, "obj{2,}")).toThrow(TypeError); | ||
expect(() => check([1, "b"], "array{3,}")).toThrow(TypeError); | ||
expect(() => check(new Map([]), "map{1,}")).toThrow(TypeError); | ||
expect(() => check(new Set([1, 2]), "set{4,}")).toThrow(TypeError); | ||
expect(() => check(122, "number{123,}")).toThrow(TypeError); | ||
}); | ||
test("Correct error message", () => { | ||
expect(() => check("a", "str{10,}")).toThrow(/Must be string with minimum size 10/); | ||
expect(() => check(1, "int{12,}")).toThrow(/Must be integer with minimum size 12/); | ||
}); | ||
}); | ||
describe("Maximum size types", () => { | ||
test("Size types pass correctly", () => { | ||
expect(check("a", "string{,2}")).toBe(undefined); | ||
expect(check("aa", "string{,2}")).toBe(undefined); | ||
expect(check("aaaaaaaa", "lower{,12}")).toBe(undefined); | ||
expect(check("AAAAA", "upper{,5}")).toBe(undefined); | ||
expect(check({ a: 1, b: 2, c: 3 }, "obj{,3}")).toBe(undefined); | ||
expect(check([1, "b", true, 4], "array{,4}")).toBe(undefined); | ||
expect(check(new Map([[1, 1], [2, 2]]), "map{,2}")).toBe(undefined); | ||
expect(check(new Set([1, 2, "c", 4]), "set{,4}")).toBe(undefined); | ||
expect(check(124, "number{,124}")).toBe(undefined); | ||
}); | ||
test("Size types fail correctly", () => { | ||
expect(() => check("aa", "string{,1}")).toThrow(TypeError); | ||
expect(() => check("aaaaa", "lower{,4}")).toThrow(TypeError); | ||
expect(() => check("AAAA", "lower{,4}")).toThrow(TypeError); | ||
expect(() => check({ a: 1, b: 2, c: 3 }, "obj{,2}")).toThrow(TypeError); | ||
expect(() => check([1, "b", 3, 4], "array{,3}")).toThrow(TypeError); | ||
expect(() => check(new Map([["a", 1]]), "map{,0}")).toThrow(TypeError); | ||
expect(() => check(new Set([1, 2, 3, 4, 5]), "set{,4}")).toThrow(TypeError); | ||
expect(() => check(122, "number{,121}")).toThrow(TypeError); | ||
}); | ||
test("Correct error message", () => { | ||
expect(() => check("abcdefgh", "str{,6}")).toThrow(/Must be string with maximum size 6/); | ||
expect(() => check(123456789, "int{,12}")).toThrow(/Must be integer with maximum size 12/); | ||
}); | ||
}); | ||
describe("Minimum and maximum size types", () => { | ||
test("Size types pass correctly", () => { | ||
expect(check("a", "string{1,2}")).toBe(undefined); | ||
expect(check("aa", "string{1,2}")).toBe(undefined); | ||
expect(check("aaaaa", "lower{4,6}")).toBe(undefined); | ||
expect(check("AAAAA", "upper{5,6}")).toBe(undefined); | ||
expect(check({ a: 1, b: 2, c: 3 }, "obj{2,6}")).toBe(undefined); | ||
expect(check([1, "b", true, 4], "array{3,6}")).toBe(undefined); | ||
expect(check(new Map([[1, 1], [2, 2]]), "map{1,6}")).toBe(undefined); | ||
expect(check(new Set([1, 2, "c", 4]), "set{4,6}")).toBe(undefined); | ||
expect(check(124, "number{123,125}")).toBe(undefined); | ||
}); | ||
test("Size types fail correctly", () => { | ||
expect(() => check("", "string{1,2}")).toThrow(TypeError); | ||
expect(() => check("aaa", "string{1,2}")).toThrow(TypeError); | ||
expect(() => check("aaa", "lower{4,6}")).toThrow(TypeError); | ||
expect(() => check("AAAAA", "lower{4,6}")).toThrow(TypeError); | ||
expect(() => check({ a: 1 }, "obj{2,3}")).toThrow(TypeError); | ||
expect(() => check({ a: 1, b: 2, c: 3, d: 4 }, "obj{2,3}")).toThrow(TypeError); | ||
expect(() => check([1, "b"], "array{3,4}")).toThrow(TypeError); | ||
expect(() => check([1, "b", 3, 4, 5], "array{3,4}")).toThrow(TypeError); | ||
expect(() => check(new Map([["a", 2]]), "map{2,3}")).toThrow(TypeError); | ||
expect(() => check(new Map([["a", 2], ["b", 3], ["c", 4], ["d", 5]]), "map{2,3}")).toThrow(TypeError); | ||
expect(() => check(new Set([1]), "set{2,3}")).toThrow(TypeError); | ||
expect(() => check(new Set([1, 2, 3, 4]), "set{2,3}")).toThrow(TypeError); | ||
expect(() => check(122, "number{123,125}")).toThrow(TypeError); | ||
expect(() => check(126, "number{123,125}")).toThrow(TypeError); | ||
}); | ||
test("Correct error message", () => { | ||
expect(() => check("abcdefgh", "str{3,6}")).toThrow(/Must be string with size between 3 and 6/); | ||
expect(() => check(1, "int{2,12}")).toThrow(/Must be integer with size between 2 and 12/); | ||
}); | ||
}); | ||
test("Other values (boolean, symbol) are not valid", () => { | ||
expect(() => check(true, "boolean{1,1}")).toThrow(TypeError); | ||
}); | ||
test("Not valid without at least minimum or maximum", () => { | ||
expect(() => check(126, "number{}")).toThrow(BlorkError); | ||
expect(() => check(126, "number{,}")).toThrow(BlorkError); | ||
expect(() => check(126, "number{a,}")).toThrow(BlorkError); | ||
expect(() => check(126, "number{,b}")).toThrow(BlorkError); | ||
}); | ||
}); | ||
describe("Array types", () => { | ||
@@ -107,3 +242,3 @@ test("Array types pass correctly", () => { | ||
}); | ||
test("Array types have correct error message", () => { | ||
test("Correct error message", () => { | ||
expect(() => check(true, "str[]")).toThrow(/Must be plain array containing string/); | ||
@@ -128,2 +263,5 @@ expect(() => check([], "str[]+")).toThrow(/Must be non-empty plain array containing string/); | ||
}); | ||
test("Correct error message", () => { | ||
expect(() => check(true, "[num, str]")).toThrow(/Must be plain array tuple containing finite number, string/); | ||
}); | ||
}); | ||
@@ -146,3 +284,3 @@ describe("Object types", () => { | ||
}); | ||
test("Object types have correct error message", () => { | ||
test("Correct error message", () => { | ||
expect(() => check(true, "{int}")).toThrow(/Must be plain object containing integer/); | ||
@@ -184,3 +322,3 @@ expect(() => check({ "ABC": true }, "{ upper: int }")).toThrow(/Must be plain object with UPPERCASE string keys containing integer/); | ||
}); | ||
test("AND and OR combined types have correct error message", () => { | ||
test("Correct error message", () => { | ||
expect(() => check(1, "string & string | string")).toThrow(/Must be string and \(string or string\)/); | ||
@@ -206,3 +344,3 @@ expect(() => check(1, "string | string & string")).toThrow(/Must be \(string or string\) and string/); | ||
}); | ||
test('Grouped types have correct error message', () => { | ||
test('Correct error message', () => { | ||
expect(() => check(true, "(str | num)")).toThrow(/Must be string or finite number/); | ||
@@ -209,0 +347,0 @@ expect(() => check(true, "(str & upper) | (num & int)")).toThrow(/Must be \(string and UPPERCASE string\) or \(finite number and integer\)/); |
185609
3261
840