mirror of
https://github.com/zhigang1992/DefinitelyTyped.git
synced 2026-04-23 12:56:46 +08:00
3481 lines
97 KiB
TypeScript
3481 lines
97 KiB
TypeScript
import jsen = require("jsen");
|
|
import JsenSettings = jsen.JsenSettings;
|
|
|
|
// any
|
|
{
|
|
// passes validation on any type
|
|
{
|
|
const schema = { type: "any" };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(validate(null));
|
|
console.assert(validate(undefined));
|
|
console.assert(validate(0));
|
|
console.assert(validate(''));
|
|
console.assert(validate(Math.PI));
|
|
console.assert(validate('abc'));
|
|
console.assert(validate(77));
|
|
console.assert(validate(false));
|
|
console.assert(validate(true));
|
|
console.assert(validate({}));
|
|
console.assert(validate([]));
|
|
}
|
|
}
|
|
|
|
// type: array
|
|
{
|
|
// required
|
|
{
|
|
const schema = { type: 'array' };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(!validate());
|
|
console.assert(!validate(null));
|
|
|
|
console.assert(validate([]));
|
|
}
|
|
// nullable
|
|
{
|
|
const schema = { type: ['array', 'null'] };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(!validate(undefined));
|
|
|
|
console.assert(validate(null));
|
|
console.assert(validate([]));
|
|
}
|
|
|
|
// type
|
|
{
|
|
const schema = { type: 'array' };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(!validate('123'));
|
|
console.assert(!validate(false));
|
|
console.assert(!validate({}));
|
|
console.assert(!validate(Math.PI));
|
|
|
|
console.assert(validate([]));
|
|
}
|
|
|
|
// minItems
|
|
{
|
|
const schema = { type: 'array', minItems: 3 };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(!validate([]));
|
|
console.assert(!validate([1, 2]));
|
|
|
|
console.assert(validate([1, 2, 3]));
|
|
console.assert(validate([1, 2, 3, 4]));
|
|
}
|
|
|
|
// maxItems
|
|
{
|
|
const schema = { type: 'array', maxItems: 3 };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(!validate([1, 2, 3, 4]));
|
|
|
|
console.assert(validate([]));
|
|
console.assert(validate([1, 2, 3]));
|
|
}
|
|
|
|
// items: object
|
|
{
|
|
let schema: any = { type: 'array', items: { type: 'string' } };
|
|
let validate = jsen(schema);
|
|
|
|
console.assert(!validate(null));
|
|
console.assert(!validate([1]));
|
|
console.assert(!validate(['a', false, 'b']));
|
|
console.assert(!validate(['a', 'b', 1]));
|
|
|
|
console.assert(validate([]));
|
|
console.assert(validate(['a']));
|
|
console.assert(validate(['a', 'b', 'c']));
|
|
|
|
schema = {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
strProp: { type: 'string' },
|
|
boolProp: { type: 'boolean' }
|
|
},
|
|
required: ['strProp']
|
|
}
|
|
};
|
|
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate([123]));
|
|
console.assert(!validate([{}]));
|
|
console.assert(!validate([{
|
|
strProp: 'value',
|
|
boolProp: 123
|
|
}]));
|
|
|
|
console.assert(validate([{
|
|
strProp: 'value',
|
|
boolProp: false
|
|
}]));
|
|
}
|
|
|
|
// items: array
|
|
{
|
|
const schema = {
|
|
type: 'array',
|
|
items: [
|
|
{ type: 'string' },
|
|
{ type: 'number' }
|
|
]
|
|
};
|
|
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(!validate([1]));
|
|
console.assert(!validate([1, 'a']));
|
|
|
|
console.assert(validate([]));
|
|
console.assert(validate(['a']));
|
|
console.assert(validate(['a', 1]));
|
|
console.assert(validate(['a', 1, null, 'b', 2]));
|
|
}
|
|
|
|
// additionalItems: boolean
|
|
{
|
|
let schema: any = { type: 'array', additionalItems: false };
|
|
let validate = jsen(schema);
|
|
|
|
console.assert(validate([]));
|
|
console.assert(validate([1]));
|
|
console.assert(validate([1, 'a', true]));
|
|
|
|
schema.items = { type: 'number' };
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(['a']));
|
|
|
|
console.assert(validate([]));
|
|
console.assert(validate([1]));
|
|
console.assert(validate([1, 2, 3]));
|
|
|
|
schema.items = [
|
|
{ type: 'string' },
|
|
{ type: 'number' }
|
|
];
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(['a', 1, 2]));
|
|
|
|
console.assert(validate([]));
|
|
console.assert(validate(['a']));
|
|
console.assert(validate(['a', 1]));
|
|
}
|
|
|
|
// additionalItems: object
|
|
{
|
|
// when `items` is an object schema, `additionalItems`
|
|
// is ignored and must not validate against
|
|
let schema: any = {
|
|
type: 'array',
|
|
items: {
|
|
type: 'string'
|
|
},
|
|
additionalItems: {
|
|
type: 'number'
|
|
}
|
|
};
|
|
|
|
let validate = jsen(schema);
|
|
|
|
console.assert(!validate(['abc', 'def', 123]));
|
|
|
|
// same as above description - only strings are valid
|
|
console.assert(validate(['abc', 'def']));
|
|
|
|
// when `items` is an array, any other positional
|
|
// data item must validate against `additionalItems`
|
|
schema.items = [
|
|
{ type: 'string' },
|
|
{ type: 'boolean' }
|
|
];
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(['abc', false, 'def']));
|
|
|
|
console.assert(validate(['abc', false]));
|
|
console.assert(validate(['abc', false, 123]));
|
|
console.assert(validate(['abc', false, 123, Math.PI]));
|
|
|
|
// when `additionalItems` is an empty object, anything is valid
|
|
schema.additionalItems = {};
|
|
validate = jsen(schema);
|
|
|
|
console.assert(validate(['abc', false, 'def', 123, {}, null]));
|
|
}
|
|
|
|
// uniqueItems
|
|
{
|
|
let schema = {
|
|
type: 'array',
|
|
items: { type: 'number' },
|
|
uniqueItems: false
|
|
};
|
|
|
|
let validate = jsen(schema);
|
|
|
|
console.assert(validate([1, 2, 1]));
|
|
|
|
schema.uniqueItems = true;
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate([1, 2, 1]));
|
|
|
|
console.assert(validate([1, 2, 3]));
|
|
}
|
|
}
|
|
|
|
// type: boolean
|
|
{
|
|
// required
|
|
{
|
|
const schema = { type: 'boolean' };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(!validate());
|
|
console.assert(!validate(null));
|
|
|
|
console.assert(validate(false));
|
|
console.assert(validate(true));
|
|
}
|
|
|
|
// nullable
|
|
{
|
|
const schema = { type: ['boolean', 'null'] };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(!validate(undefined));
|
|
|
|
console.assert(validate(true));
|
|
console.assert(validate(false));
|
|
console.assert(validate(null));
|
|
}
|
|
|
|
// type
|
|
{
|
|
const schema = { type: 'boolean' };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(!validate('123'));
|
|
console.assert(!validate([]));
|
|
console.assert(!validate({}));
|
|
console.assert(!validate(Math.PI));
|
|
|
|
console.assert(validate(true));
|
|
console.assert(validate(false));
|
|
}
|
|
}
|
|
|
|
// build
|
|
{
|
|
// validator function has a build property
|
|
{
|
|
const validate = jsen({});
|
|
|
|
console.assert(typeof validate.build === 'function');
|
|
console.assert(validate.build.length === 2);
|
|
}
|
|
|
|
// returns default value from schema if initial undefined
|
|
{
|
|
const validate = jsen({ type: 'string', default: 'abc' });
|
|
|
|
console.assert(validate.build() === 'abc');
|
|
console.assert(validate.build(undefined) === 'abc');
|
|
}
|
|
|
|
// does not modify initial defined value
|
|
{
|
|
const validate = jsen({ type: 'string', default: 'abc' });
|
|
const initials = [
|
|
null,
|
|
'',
|
|
'string value',
|
|
true,
|
|
false,
|
|
123,
|
|
Math.PI
|
|
];
|
|
|
|
let obj: any;
|
|
|
|
initials.forEach((initial) => {
|
|
obj = validate.build(initial);
|
|
console.assert(obj === initial);
|
|
});
|
|
}
|
|
|
|
// returns a copy of initial defined value
|
|
{
|
|
const validate = jsen({ type: 'string', default: 'abc' });
|
|
const initials = [
|
|
{},
|
|
[],
|
|
new Date()
|
|
];
|
|
|
|
let obj: any;
|
|
|
|
initials.forEach((initial) => {
|
|
obj = validate.build(initial);
|
|
console.assert(obj !== initial);
|
|
console.assert(JSON.stringify(obj) === JSON.stringify(initial));
|
|
});
|
|
}
|
|
|
|
// returns initial if no default in schema
|
|
{
|
|
const validate = jsen({}),
|
|
initials = [
|
|
undefined,
|
|
null,
|
|
'',
|
|
'string value',
|
|
true,
|
|
false,
|
|
123,
|
|
Math.PI,
|
|
{},
|
|
[],
|
|
new Date()
|
|
];
|
|
|
|
let obj: any;
|
|
|
|
initials.forEach((initial) => {
|
|
obj = validate.build(initial);
|
|
console.assert(JSON.stringify(obj) === JSON.stringify(initial));
|
|
});
|
|
}
|
|
|
|
// returns the default value specified in schema
|
|
{
|
|
const schemas = [
|
|
{ default: null },
|
|
{ default: undefined },
|
|
{ default: '' },
|
|
{ default: 'abc' },
|
|
{ default: true },
|
|
{ default: false },
|
|
{ default: 123 },
|
|
{ default: Math.PI }
|
|
];
|
|
|
|
schemas.forEach((schema) => {
|
|
let validate = jsen(schema);
|
|
console.assert(validate.build() === schema.default);
|
|
});
|
|
}
|
|
|
|
// returns a copy of a default object or array in schema
|
|
{
|
|
const schemas = [
|
|
{ default: { a: { b: 123 } } },
|
|
{ default: [[1, 2, 3], { a: { b: 123 } }] },
|
|
{ default: /\d+/ },
|
|
{ default: new Date('05/14/2015') }
|
|
];
|
|
|
|
schemas.forEach((schema) => {
|
|
let validate = jsen(schema);
|
|
let def = validate.build();
|
|
|
|
console.assert(def !== schema.default);
|
|
console.assert(JSON.stringify(def) === JSON.stringify(schema.default));
|
|
});
|
|
}
|
|
|
|
// recursively collects default values
|
|
{
|
|
const schema: any = {
|
|
type: 'object',
|
|
default: {},
|
|
properties: {
|
|
a: {
|
|
type: 'array',
|
|
default: [],
|
|
items: {
|
|
type: 'string'
|
|
}
|
|
},
|
|
b: {
|
|
type: 'array',
|
|
default: [],
|
|
items: {
|
|
type: 'string',
|
|
default: 'abc'
|
|
}
|
|
},
|
|
c: {
|
|
type: 'object',
|
|
default: {},
|
|
properties: {
|
|
d: {
|
|
type: 'boolean',
|
|
default: false
|
|
},
|
|
e: {
|
|
type: 'date',
|
|
default: new Date('05/14/2015')
|
|
},
|
|
f: {
|
|
type: 'array',
|
|
default: [{}, {}],
|
|
items: [{
|
|
type: 'object',
|
|
properties: {
|
|
g: {
|
|
type: 'string',
|
|
default: 'yes'
|
|
}
|
|
}
|
|
}, {
|
|
type: 'object',
|
|
properties: {
|
|
g: {
|
|
type: 'integer',
|
|
default: 0
|
|
}
|
|
}
|
|
}, {
|
|
type: 'object',
|
|
properties: {
|
|
g: {
|
|
type: 'boolean',
|
|
default: true
|
|
}
|
|
}
|
|
}]
|
|
},
|
|
h: {
|
|
type: 'array',
|
|
default: [{}, {}],
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
i: {
|
|
type: 'object',
|
|
default: { foo: 'bar' }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
i: {
|
|
type: 'object',
|
|
default: null,
|
|
properties: {
|
|
j: {
|
|
type: 'string',
|
|
default: 'baz'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
j: {
|
|
type: 'object',
|
|
properties: {
|
|
k: {
|
|
type: 'boolean',
|
|
default: false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const expected: any = {
|
|
a: [],
|
|
b: [],
|
|
c: {
|
|
d: false,
|
|
e: new Date('05/14/2015'),
|
|
f: [
|
|
{ g: 'yes' },
|
|
{ g: 0 }
|
|
],
|
|
h: [
|
|
{ i: { foo: 'bar' } },
|
|
{ i: { foo: 'bar' } }
|
|
],
|
|
i: null
|
|
}
|
|
};
|
|
|
|
const validate = jsen(schema);
|
|
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
|
|
}
|
|
|
|
// merges default values with the initial values
|
|
{
|
|
const schemas = [
|
|
{
|
|
properties: {
|
|
foo: { default: 'bar' }
|
|
}
|
|
},
|
|
{
|
|
properties: {
|
|
foo: { default: 'bar' }
|
|
}
|
|
},
|
|
{
|
|
properties: {
|
|
foo: { default: 'bar' }
|
|
}
|
|
},
|
|
{
|
|
items: {
|
|
properties: {
|
|
foo: { default: 'bar' }
|
|
}
|
|
}
|
|
},
|
|
{
|
|
items: {
|
|
properties: {
|
|
foo: { default: 'bar' }
|
|
}
|
|
}
|
|
},
|
|
{
|
|
items: [
|
|
{ default: 'foo' },
|
|
{ default: 'bar' },
|
|
{ default: 'baz' }
|
|
]
|
|
},
|
|
{
|
|
items: [
|
|
{ default: 'foo' },
|
|
{ default: 'bar' },
|
|
{ default: 'baz' }
|
|
]
|
|
}
|
|
];
|
|
|
|
const defaults = [
|
|
{},
|
|
{ foo: 'baz' },
|
|
{ x: 'yz' },
|
|
[],
|
|
[{}],
|
|
[],
|
|
[null, {}, undefined, false]
|
|
];
|
|
|
|
const expected = [
|
|
{ foo: 'bar' },
|
|
{ foo: 'baz' },
|
|
{ foo: 'bar', x: 'yz' },
|
|
[],
|
|
[{ foo: 'bar' }],
|
|
['foo', 'bar', 'baz'],
|
|
[null, {}, 'baz', false]
|
|
];
|
|
|
|
schemas.forEach((schema, index) => {
|
|
let validate = jsen(schema);
|
|
console.assert(JSON.stringify(validate.build(defaults[index])) === JSON.stringify(expected[index]));
|
|
});
|
|
}
|
|
|
|
// $ref
|
|
{
|
|
const schema = {
|
|
definitions: {
|
|
positiveInteger: {
|
|
type: 'integer',
|
|
minimum: 1,
|
|
default: 7
|
|
}
|
|
},
|
|
$ref: '#definitions/positiveInteger'
|
|
};
|
|
|
|
const validate = jsen(schema);
|
|
console.assert(validate.build() === 7);
|
|
}
|
|
|
|
// object
|
|
{
|
|
// clones default object and subproperties recursively
|
|
{
|
|
const schema = {
|
|
default: {},
|
|
properties: {
|
|
foo: {
|
|
type: 'string',
|
|
default: 'bar'
|
|
}
|
|
}
|
|
};
|
|
|
|
const expected = { foo: 'bar' };
|
|
|
|
const validate = jsen(schema);
|
|
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
|
|
}
|
|
|
|
// does not recursively assign defaults of children if parent default is not an object
|
|
{
|
|
const schema: any = {
|
|
default: [],
|
|
properties: {
|
|
foo: {
|
|
type: 'string',
|
|
default: 'bar'
|
|
}
|
|
}
|
|
};
|
|
|
|
const expected: any = [];
|
|
|
|
const validate = jsen(schema);
|
|
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
|
|
}
|
|
}
|
|
|
|
// array
|
|
{
|
|
// does not add new elements to default array: items schema is an object
|
|
{
|
|
const schema: any = {
|
|
default: [],
|
|
items: {
|
|
type: 'string',
|
|
default: 'bar'
|
|
}
|
|
};
|
|
|
|
const expected: any = [];
|
|
|
|
const validate = jsen(schema);
|
|
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
|
|
}
|
|
|
|
// adds new elements to default array: items schema is an array
|
|
{
|
|
const schema: any = {
|
|
default: [],
|
|
items: [{
|
|
type: 'string',
|
|
default: 'bar'
|
|
}]
|
|
};
|
|
|
|
const expected = ['bar'];
|
|
|
|
const validate = jsen(schema);
|
|
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
|
|
}
|
|
|
|
// adds default values to already existing child items of compatible type only: items schema is an object
|
|
{
|
|
const schema: any = {
|
|
default: [{}, null, []],
|
|
items: {
|
|
properties: {
|
|
foo: {
|
|
type: 'string',
|
|
default: 'bar'
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const expected: any = [{ foo: 'bar' }, null, []];
|
|
|
|
const validate = jsen(schema);
|
|
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
|
|
}
|
|
|
|
// adds default values to already existing child items of compatible type only: items schema is an array
|
|
{
|
|
const schema: any = {
|
|
default: [{}, null, {}, undefined],
|
|
items: [
|
|
{
|
|
properties: {
|
|
foo: {
|
|
type: 'string',
|
|
default: 'bar'
|
|
}
|
|
}
|
|
},
|
|
{ default: 'abc' },
|
|
{ default: 123 },
|
|
{ default: 123 }
|
|
]
|
|
};
|
|
|
|
const expected = [{ foo: 'bar' }, null, {}, 123];
|
|
|
|
const validate = jsen(schema);
|
|
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
|
|
}
|
|
|
|
// does not assign default child items if parent default is not an array
|
|
{
|
|
const schema: any = {
|
|
default: 'foobar',
|
|
items: {
|
|
type: 'string',
|
|
default: 'bar'
|
|
}
|
|
};
|
|
|
|
const expected = 'foobar';
|
|
|
|
const validate = jsen(schema);
|
|
console.assert(JSON.stringify(validate.build()) === JSON.stringify(expected));
|
|
}
|
|
}
|
|
|
|
// option: copy
|
|
{
|
|
// returns deep copy of the initial object by default
|
|
{
|
|
const schema = {};
|
|
const initial = {};
|
|
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(JSON.stringify(validate.build(initial)) === JSON.stringify(initial));
|
|
console.assert(validate.build(initial) !== initial);
|
|
}
|
|
|
|
// modifies the initial object when copy = false
|
|
{
|
|
const schema = {
|
|
properties: {
|
|
a: { default: 'foo' },
|
|
b: {
|
|
items: {
|
|
properties: {
|
|
c: { default: 'bar' },
|
|
d: { default: 'baz' }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
const initial = { b: [{ d: 'xyz' }] };
|
|
const expected = { a: 'foo', b: [{ c: 'bar', d: 'xyz' }] };
|
|
|
|
const validate = jsen(schema);
|
|
const actual = validate.build(initial, { copy: false });
|
|
|
|
console.assert(JSON.stringify(actual) === JSON.stringify(expected));
|
|
console.assert(actual === initial);
|
|
}
|
|
}
|
|
|
|
// option: additionalProperties
|
|
{
|
|
// includes by default
|
|
{
|
|
const schema = {
|
|
properties: {
|
|
foo: {}
|
|
}
|
|
};
|
|
|
|
const initial = { foo: 1, bar: 2 };
|
|
const expected = { foo: 1, bar: 2 };
|
|
|
|
const validate = jsen(schema);
|
|
console.assert(JSON.stringify(validate.build(initial)) === JSON.stringify(expected));
|
|
}
|
|
|
|
// excludes when schema.additionalProperties = false
|
|
{
|
|
const schema = {
|
|
additionalProperties: false,
|
|
properties: {
|
|
foo: {}
|
|
}
|
|
};
|
|
|
|
const initial = { foo: 1, bar: 2 };
|
|
const expected = { foo: 1 };
|
|
|
|
const validate = jsen(schema);
|
|
console.assert(JSON.stringify(validate.build(initial)) === JSON.stringify(expected));
|
|
}
|
|
|
|
// removes from the initial object when schema.additionalProperties = false and options.copy = false
|
|
{
|
|
const schema = {
|
|
additionalProperties: false,
|
|
properties: {
|
|
foo: {}
|
|
}
|
|
};
|
|
|
|
const initial = { foo: 1, bar: 2 };
|
|
const expected = { foo: 1 };
|
|
|
|
const validate = jsen(schema);
|
|
const actual = validate.build(initial, { copy: false });
|
|
|
|
console.assert(actual === initial);
|
|
console.assert(JSON.stringify(actual) === JSON.stringify(expected));
|
|
}
|
|
|
|
// removes from the initial object when options.additionalProperties = false and options.copy = false
|
|
{
|
|
const schema = {
|
|
properties: {
|
|
foo: {}
|
|
}
|
|
};
|
|
|
|
const initial = { foo: 1, bar: 2 };
|
|
const expected = { foo: 1 };
|
|
|
|
const validate = jsen(schema);
|
|
const actual = validate.build(initial, {
|
|
copy: false,
|
|
additionalProperties: false
|
|
});
|
|
|
|
console.assert(actual === initial);
|
|
console.assert(JSON.stringify(actual) === JSON.stringify(expected));
|
|
}
|
|
|
|
// schema.additionalProperties takes precedence
|
|
{
|
|
const schema = {
|
|
additionalProperties: true,
|
|
properties: {
|
|
foo: {}
|
|
}
|
|
};
|
|
|
|
const initial = { foo: 1, bar: 2 };
|
|
const expected = { foo: 1, bar: 2 };
|
|
|
|
const validate = jsen(schema);
|
|
console.assert(JSON.stringify(validate.build(initial, { additionalProperties: false })) === JSON.stringify(expected));
|
|
}
|
|
}
|
|
}
|
|
|
|
// clone
|
|
{
|
|
const clone = jsen.clone;
|
|
|
|
// string
|
|
{
|
|
console.assert(clone('abc') === 'abc');
|
|
}
|
|
|
|
// number
|
|
{
|
|
console.assert(clone(123) === 123);
|
|
}
|
|
|
|
// integer
|
|
{
|
|
console.assert(clone(Math.PI) === Math.PI);
|
|
}
|
|
|
|
// boolean
|
|
{
|
|
console.assert(clone(false) === false);
|
|
}
|
|
|
|
// function
|
|
{
|
|
const func = () => {
|
|
};
|
|
console.assert(clone(func) === func);
|
|
}
|
|
|
|
// regexp
|
|
{
|
|
const regex = /a/gim;
|
|
console.assert(clone(regex) !== regex);
|
|
console.assert(clone(regex).toString() === regex.toString());
|
|
}
|
|
|
|
// date
|
|
{
|
|
const today = new Date('05/14/2015');
|
|
console.assert(clone(today) !== today);
|
|
console.assert(clone(today).toJSON() === today.toJSON());
|
|
}
|
|
|
|
// null
|
|
{
|
|
console.assert(clone(null) === null);
|
|
}
|
|
|
|
// undefined
|
|
{
|
|
console.assert(clone(undefined) === undefined);
|
|
}
|
|
|
|
// object
|
|
{
|
|
const obj = { a: 1, b: 'a', c: false, d: { e: Math.PI, f: [1, 2, 3] } };
|
|
console.assert(clone(obj) !== obj);
|
|
console.assert(JSON.stringify(clone(obj)) === JSON.stringify(obj));
|
|
}
|
|
|
|
// array
|
|
{
|
|
const arr = [1, 'a', false, { d: [1, 2, 3] }];
|
|
console.assert(clone(arr) !== arr);
|
|
console.assert(JSON.stringify(clone(arr)) === JSON.stringify(arr));
|
|
}
|
|
}
|
|
|
|
// type: date
|
|
{
|
|
// required
|
|
{
|
|
const schema = { type: 'date' };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(!validate());
|
|
console.assert(!validate(null));
|
|
|
|
console.assert(validate(new Date()));
|
|
}
|
|
|
|
// nullable
|
|
{
|
|
const schema = { type: ['date', 'null'] };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(!validate(undefined));
|
|
|
|
console.assert(validate(new Date()));
|
|
console.assert(validate(null));
|
|
}
|
|
|
|
// type
|
|
{
|
|
const schema = { type: 'date' };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(!validate('123'));
|
|
console.assert(!validate([]));
|
|
console.assert(!validate({}));
|
|
console.assert(!validate(Math.PI));
|
|
|
|
console.assert(validate(new Date()));
|
|
}
|
|
}
|
|
|
|
// equal
|
|
{
|
|
const equal = jsen.equal;
|
|
// string
|
|
{
|
|
console.assert(equal('a', 'a'));
|
|
console.assert(!equal('a', 'b'));
|
|
}
|
|
|
|
// number
|
|
{
|
|
console.assert(equal(123, 123));
|
|
console.assert(equal(Math.PI, Math.PI));
|
|
console.assert(!equal(Math.PI, Math.E));
|
|
}
|
|
|
|
// boolean
|
|
{
|
|
console.assert(equal(true, true));
|
|
console.assert(!equal(true, false));
|
|
}
|
|
|
|
// null
|
|
{
|
|
console.assert(equal(null, null));
|
|
console.assert(!equal(null, undefined));
|
|
}
|
|
|
|
// undefined
|
|
{
|
|
console.assert(equal(undefined, undefined));
|
|
console.assert(!equal(null, undefined));
|
|
}
|
|
|
|
// function
|
|
{
|
|
const f1 = () => {
|
|
};
|
|
const f2 = () => {
|
|
};
|
|
const f3 = f1;
|
|
|
|
// two functions are only equal if they
|
|
// reference the same function object
|
|
console.assert(equal(f1, f3));
|
|
console.assert(!equal(f1, f2));
|
|
}
|
|
|
|
// array
|
|
{
|
|
const f = () => {
|
|
};
|
|
const obj1 = { a: 123, b: 'abc', c: f };
|
|
const obj2 = { a: 123, b: 'abc', c: f };
|
|
const arr1 = [1, 'a', f, obj1];
|
|
const arr2 = [1, 'a', f, obj1];
|
|
const arr3 = [1, 'a', f];
|
|
const arr4 = [1, 'a', f, obj2];
|
|
|
|
console.assert(equal(arr1, arr2));
|
|
console.assert(equal(arr1, arr4));
|
|
|
|
console.assert(!equal(arr1, arr3));
|
|
}
|
|
|
|
// object
|
|
{
|
|
const a = { a: 123, b: ['abc'], c: {} };
|
|
const b = { b: ['abc'], c: {}, a: 123 };
|
|
const c = a;
|
|
const d: any = { a: 123, b: ['abc'], c: { d: undefined } };
|
|
|
|
console.assert(equal(a, b));
|
|
console.assert(equal(a, c));
|
|
|
|
console.assert(!equal(a, d));
|
|
}
|
|
|
|
// regexp
|
|
{
|
|
const a = /a+/gim;
|
|
const b = new RegExp('a+', 'gim');
|
|
const c = /a/gim;
|
|
const d = a;
|
|
|
|
console.assert(equal(a, b));
|
|
console.assert(equal(a, d));
|
|
|
|
console.assert(!equal(a, c));
|
|
}
|
|
}
|
|
|
|
// error
|
|
{
|
|
// errors is empty array before validation
|
|
{
|
|
const schema = { type: 'number' };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(Array.isArray(validate.errors));
|
|
console.assert(validate.errors.length === 0);
|
|
}
|
|
|
|
// no errors on successful validation
|
|
{
|
|
const schema = { type: 'number' };
|
|
const validate = jsen(schema);
|
|
const valid = validate(123);
|
|
|
|
console.assert(valid);
|
|
console.assert(Array.isArray(validate.errors));
|
|
console.assert(validate.errors.length === 0);
|
|
}
|
|
|
|
// has errors when validation unsuccessful
|
|
{
|
|
const schema = { type: 'number' };
|
|
const validate = jsen(schema);
|
|
const valid = validate('123');
|
|
|
|
console.assert(!valid);
|
|
console.assert(Array.isArray(validate.errors));
|
|
console.assert(validate.errors.length === 1);
|
|
}
|
|
|
|
// clears errors on successive validation calls
|
|
{
|
|
const schema = { type: 'number' };
|
|
const validate = jsen(schema);
|
|
|
|
validate('123');
|
|
console.assert(Array.isArray(validate.errors));
|
|
console.assert(validate.errors.length === 1);
|
|
|
|
validate(123);
|
|
console.assert(Array.isArray(validate.errors));
|
|
console.assert(validate.errors.length === 0);
|
|
|
|
validate('123');
|
|
console.assert(Array.isArray(validate.errors));
|
|
console.assert(validate.errors.length === 1);
|
|
}
|
|
|
|
// two successive runs return different arrays
|
|
{
|
|
const schema = { type: 'number' };
|
|
const validate = jsen(schema);
|
|
|
|
let previous: any;
|
|
|
|
validate('123');
|
|
console.assert(validate.errors.length === 1);
|
|
previous = validate.errors;
|
|
|
|
validate('123');
|
|
console.assert(validate.errors.length === 1);
|
|
|
|
console.assert(validate.errors !== previous);
|
|
console.assert(JSON.stringify(validate.errors) === JSON.stringify(previous));
|
|
}
|
|
|
|
// error object
|
|
{
|
|
const schemas = [
|
|
{
|
|
type: 'number'
|
|
},
|
|
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
a: {
|
|
type: 'string'
|
|
}
|
|
}
|
|
},
|
|
|
|
{
|
|
type: 'array',
|
|
uniqueItems: true
|
|
},
|
|
|
|
{
|
|
type: 'array',
|
|
items: {
|
|
maximum: 10
|
|
}
|
|
},
|
|
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
a: {
|
|
type: 'array',
|
|
items: [{
|
|
type: 'object',
|
|
properties: {
|
|
b: {
|
|
multipleOf: 7
|
|
}
|
|
}
|
|
}]
|
|
}
|
|
}
|
|
},
|
|
|
|
{
|
|
allOf: [
|
|
{ minimum: 5 },
|
|
{ maximum: 10 }
|
|
]
|
|
},
|
|
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
a: {
|
|
anyOf: [
|
|
{ type: 'string' },
|
|
{ type: 'number' }
|
|
]
|
|
}
|
|
}
|
|
},
|
|
|
|
{
|
|
type: 'array',
|
|
items: [{
|
|
type: 'object',
|
|
properties: {
|
|
a: {
|
|
oneOf: [
|
|
{ type: 'boolean' },
|
|
{ type: 'null' }
|
|
]
|
|
}
|
|
}
|
|
}]
|
|
},
|
|
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
a: {
|
|
not: {
|
|
type: 'string'
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
{
|
|
definitions: {
|
|
positiveInteger: {
|
|
type: 'integer',
|
|
minimum: 0,
|
|
exclusiveMinimum: true
|
|
}
|
|
},
|
|
type: 'object',
|
|
properties: {
|
|
a: {
|
|
type: 'object',
|
|
properties: {
|
|
b: {
|
|
type: 'object',
|
|
properties: {
|
|
c: {
|
|
$ref: '#/definitions/positiveInteger'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
{
|
|
type: 'object',
|
|
required: ['a', 'b']
|
|
},
|
|
|
|
{
|
|
type: 'object',
|
|
dependencies: {
|
|
a: {
|
|
required: ['b']
|
|
}
|
|
}
|
|
},
|
|
|
|
{
|
|
type: 'object',
|
|
dependencies: {
|
|
a: ['b']
|
|
}
|
|
}
|
|
];
|
|
|
|
const data = [
|
|
'123',
|
|
{ a: 123 },
|
|
[7, 11, 7],
|
|
[10, 11, 9],
|
|
{ a: [{ b: 8 }] },
|
|
12,
|
|
{ a: false },
|
|
[{ a: 123 }],
|
|
{ a: 'abc' },
|
|
{ a: { b: { c: 0 } } },
|
|
{},
|
|
{ a: 123 },
|
|
{ a: 123 }
|
|
];
|
|
|
|
// property: path
|
|
{
|
|
const expectedPaths = [
|
|
[''],
|
|
['a'],
|
|
[''],
|
|
['1'],
|
|
['a.0.b'],
|
|
[''],
|
|
['a', 'a'],
|
|
['0.a', '0.a', '0.a'],
|
|
['a'],
|
|
['a.b.c'],
|
|
['a'],
|
|
['b'],
|
|
['b']
|
|
];
|
|
|
|
schemas.forEach((schema, index) => {
|
|
let validate = jsen(schema);
|
|
let valid = validate(data[index]);
|
|
|
|
console.assert(!valid);
|
|
|
|
expectedPaths[index].forEach((path, pindex) => {
|
|
try {
|
|
console.assert(validate.errors[pindex].path === path);
|
|
}
|
|
catch (e) {
|
|
// console.log(index);
|
|
// console.log(validate.errors);
|
|
throw e;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// property: keyword
|
|
{
|
|
const expectedKeywords = [
|
|
['type'],
|
|
['type'],
|
|
['uniqueItems'],
|
|
['maximum'],
|
|
['multipleOf'],
|
|
['maximum'],
|
|
['type', 'type', 'anyOf'],
|
|
['type', 'type', 'oneOf'],
|
|
['not'],
|
|
['exclusiveMinimum'],
|
|
['required'],
|
|
['required'],
|
|
['dependencies']
|
|
];
|
|
|
|
schemas.forEach((schema, index) => {
|
|
let validate = jsen(schema);
|
|
let valid = validate(data[index]);
|
|
|
|
console.assert(!valid);
|
|
|
|
expectedKeywords[index].forEach((keyword, kindex) => {
|
|
try {
|
|
console.assert(validate.errors[kindex].keyword === keyword);
|
|
}
|
|
catch (e) {
|
|
// console.log(index);
|
|
// console.log(validate.errors);
|
|
throw e;
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
// adds required property name to path
|
|
{
|
|
let schema: any = { type: 'object', required: ['a'] };
|
|
let validate = jsen(schema);
|
|
let valid = validate({});
|
|
|
|
console.assert(!valid);
|
|
console.assert(validate.errors.length === 1);
|
|
console.assert(validate.errors[0].path === 'a');
|
|
console.assert(validate.errors[0].keyword === 'required');
|
|
|
|
schema = {
|
|
type: 'object',
|
|
properties: {
|
|
a: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
required: ['b']
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
validate = jsen(schema);
|
|
valid = validate({ a: [{}] });
|
|
|
|
console.assert(!valid);
|
|
console.assert(validate.errors.length === 1);
|
|
console.assert(validate.errors[0].path === 'a.0.b');
|
|
console.assert(validate.errors[0].keyword === 'required');
|
|
}
|
|
|
|
// adds required dependency property to path
|
|
{
|
|
let schema: any = {
|
|
type: 'object',
|
|
dependencies: {
|
|
a: ['b']
|
|
}
|
|
};
|
|
|
|
let validate = jsen(schema);
|
|
let valid = validate({ a: 123 });
|
|
|
|
console.assert(!valid);
|
|
console.assert(validate.errors.length === 1);
|
|
console.assert(validate.errors[0].path === 'b');
|
|
console.assert(validate.errors[0].keyword === 'dependencies');
|
|
|
|
schema = {
|
|
type: 'object',
|
|
properties: {
|
|
a: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
dependencies: {
|
|
a: ['b']
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
validate = jsen(schema);
|
|
valid = validate({ a: [{ a: 123 }] });
|
|
|
|
console.assert(!valid);
|
|
console.assert(validate.errors.length === 1);
|
|
console.assert(validate.errors[0].path === 'a.0.b');
|
|
console.assert(validate.errors[0].keyword === 'dependencies');
|
|
}
|
|
}
|
|
|
|
// multiple errors
|
|
{
|
|
const schema = {
|
|
definitions: {
|
|
array: {
|
|
maxItems: 1
|
|
}
|
|
},
|
|
type: 'object',
|
|
properties: {
|
|
a: {
|
|
anyOf: [
|
|
{ items: { type: 'integer' } },
|
|
{ $ref: '#/definitions/array' },
|
|
{ items: [{ maximum: 3 }] }
|
|
]
|
|
}
|
|
}
|
|
};
|
|
|
|
const data = { a: [Math.PI, Math.E] };
|
|
const validate = jsen(schema);
|
|
|
|
// returns multiple errors
|
|
{
|
|
const valid = validate(data);
|
|
|
|
// console.log(validate.errors);
|
|
|
|
console.assert(!valid);
|
|
console.assert(validate.errors.length === 5);
|
|
}
|
|
}
|
|
|
|
// custom errors
|
|
{
|
|
const schemas = [
|
|
{
|
|
type: 'string',
|
|
invalidMessage: 'string is invalid',
|
|
requiredMessage: 'string is required'
|
|
},
|
|
{
|
|
type: 'object',
|
|
required: ['a'],
|
|
properties: {
|
|
a: {
|
|
invalidMessage: 'a is invalid',
|
|
requiredMessage: 'a is required'
|
|
}
|
|
}
|
|
},
|
|
{
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
a: {
|
|
type: 'object',
|
|
properties: {
|
|
b: {
|
|
invalidMessage: 'b is invalid',
|
|
requiredMessage: 'b is required'
|
|
}
|
|
},
|
|
required: ['b']
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
type: 'object',
|
|
properties: {
|
|
a: {
|
|
type: 'object',
|
|
properties: {
|
|
c: {
|
|
type: 'string',
|
|
invalidMessage: 'c is invalid',
|
|
requiredMessage: 'c is required'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
];
|
|
|
|
const data = [
|
|
undefined,
|
|
{},
|
|
[{ a: {} }],
|
|
{ a: { c: 123 } }
|
|
];
|
|
|
|
const expectedMessages = [
|
|
'string is invalid',
|
|
'a is required',
|
|
'b is required',
|
|
'c is invalid'
|
|
];
|
|
|
|
schemas.forEach((schema, index) => {
|
|
//it(expectedMessages[index], function () {
|
|
let validate = jsen(schema);
|
|
let valid = validate(data[index]);
|
|
|
|
console.assert(!valid);
|
|
console.assert(validate.errors.length === 1);
|
|
console.assert(validate.errors[0].message === expectedMessages[index]);
|
|
//});
|
|
});
|
|
}
|
|
|
|
// custom keyword messages
|
|
{
|
|
// uses custom messages on keywords
|
|
{
|
|
const schemas: any = [
|
|
{
|
|
type: 'string',
|
|
messages: { type: 'custom message for keyword "type"' }
|
|
},
|
|
{
|
|
enum: [1, 2, 3],
|
|
messages: { enum: 'custom message for keyword "enum"' }
|
|
},
|
|
{
|
|
minimum: 3,
|
|
messages: { minimum: 'custom message for keyword "minimum"' }
|
|
},
|
|
{
|
|
minimum: 3,
|
|
exclusiveMinimum: true,
|
|
messages: { exclusiveMinimum: 'custom message for keyword "exclusiveMinimum"' }
|
|
},
|
|
{
|
|
maximum: 10,
|
|
messages: { maximum: 'custom message for keyword "maximum"' }
|
|
},
|
|
{
|
|
maximum: 10,
|
|
exclusiveMaximum: true,
|
|
messages: { exclusiveMaximum: 'custom message for keyword "exclusiveMaximum"' }
|
|
},
|
|
{
|
|
multipleOf: 5,
|
|
messages: { multipleOf: 'custom message for keyword "multipleOf"' }
|
|
},
|
|
{
|
|
minLength: 3,
|
|
messages: { minLength: 'custom message for keyword "minLength"' }
|
|
},
|
|
{
|
|
maxLength: 5,
|
|
messages: { maxLength: 'custom message for keyword "maxLength"' }
|
|
},
|
|
{
|
|
pattern: '\\d+',
|
|
messages: { pattern: 'custom message for keyword "pattern"' }
|
|
},
|
|
{
|
|
format: 'email',
|
|
messages: { format: 'custom message for keyword "format"' }
|
|
},
|
|
{
|
|
minItems: 1,
|
|
messages: { minItems: 'custom message for keyword "minItems"' }
|
|
},
|
|
{
|
|
maxItems: 1,
|
|
messages: { maxItems: 'custom message for keyword "maxItems"' }
|
|
},
|
|
{
|
|
additionalItems: false,
|
|
items: [{ type: 'string' }],
|
|
messages: { additionalItems: 'custom message for keyword "additionalItems"' }
|
|
},
|
|
{
|
|
uniqueItems: true,
|
|
messages: { uniqueItems: 'custom message for keyword "uniqueItems"' }
|
|
},
|
|
{
|
|
minProperties: 1,
|
|
messages: { minProperties: 'custom message for keyword "minProperties"' }
|
|
},
|
|
{
|
|
maxProperties: 1,
|
|
messages: { maxProperties: 'custom message for keyword "maxProperties"' }
|
|
},
|
|
{
|
|
required: ['foo'],
|
|
messages: { required: 'custom message for keyword "required"' }
|
|
},
|
|
{
|
|
required: ['foo'],
|
|
properties: {
|
|
foo: {
|
|
messages: {
|
|
required: 'custom message for keyword "required"'
|
|
}
|
|
}
|
|
}
|
|
},
|
|
{
|
|
required: ['foo'],
|
|
properties: {
|
|
foo: {
|
|
messages: {
|
|
required: 'this custom message for keyword "required" is assigned'
|
|
}
|
|
}
|
|
},
|
|
messages: { required: 'this custom message for keyword "required" is NOT assigned' }
|
|
},
|
|
{
|
|
additionalProperties: false,
|
|
messages: { additionalProperties: 'custom message for keyword "additionalProperties"' }
|
|
},
|
|
{
|
|
dependencies: {
|
|
foo: ['bar']
|
|
},
|
|
messages: { dependencies: 'custom message for keyword "dependencies"' }
|
|
},
|
|
{
|
|
anyOf: [
|
|
{ type: 'string' },
|
|
{ type: 'integer' }
|
|
],
|
|
messages: { anyOf: 'custom message for keyword "anyOf"' }
|
|
},
|
|
{
|
|
oneOf: [
|
|
{ type: 'string' },
|
|
{ type: 'integer' }
|
|
],
|
|
messages: { oneOf: 'custom message for keyword "oneOf"' }
|
|
},
|
|
{
|
|
not: {
|
|
type: 'string'
|
|
},
|
|
messages: { not: 'custom message for keyword "not"' }
|
|
}
|
|
];
|
|
|
|
const data = [
|
|
123,
|
|
5,
|
|
1,
|
|
3,
|
|
11,
|
|
10,
|
|
12,
|
|
'ab',
|
|
'abcdef',
|
|
'abc',
|
|
'invalid email',
|
|
[],
|
|
[1, 2, 3],
|
|
['abc', 'def'],
|
|
[1, 2, 2],
|
|
{},
|
|
{ foo: 1, bar: 2 },
|
|
{},
|
|
{},
|
|
{},
|
|
{ foo: 'bar' },
|
|
{ foo: 'abc' },
|
|
null,
|
|
null,
|
|
'abc'
|
|
];
|
|
|
|
const expectedMessages = [
|
|
schemas[0].messages.type,
|
|
schemas[1].messages.enum,
|
|
schemas[2].messages.minimum,
|
|
schemas[3].messages.exclusiveMinimum,
|
|
schemas[4].messages.maximum,
|
|
schemas[5].messages.exclusiveMaximum,
|
|
schemas[6].messages.multipleOf,
|
|
schemas[7].messages.minLength,
|
|
schemas[8].messages.maxLength,
|
|
schemas[9].messages.pattern,
|
|
schemas[10].messages.format,
|
|
schemas[11].messages.minItems,
|
|
schemas[12].messages.maxItems,
|
|
schemas[13].messages.additionalItems,
|
|
schemas[14].messages.uniqueItems,
|
|
schemas[15].messages.minProperties,
|
|
schemas[16].messages.maxProperties,
|
|
schemas[17].messages.required,
|
|
schemas[18].properties.foo.messages.required,
|
|
schemas[19].properties.foo.messages.required,
|
|
schemas[20].messages.additionalProperties,
|
|
schemas[21].messages.dependencies,
|
|
schemas[22].messages.anyOf,
|
|
schemas[23].messages.oneOf,
|
|
schemas[24].messages.not
|
|
];
|
|
|
|
schemas.forEach((schema: any, index: number) => {
|
|
let validate = jsen(schema);
|
|
let valid = validate(data[index]);
|
|
|
|
console.assert(!valid);
|
|
console.assert(validate.errors[validate.errors.length - 1].message === expectedMessages[index]);
|
|
});
|
|
}
|
|
|
|
// does not use custom messages on keyword: items (object)
|
|
{
|
|
const schema = {
|
|
items: {
|
|
type: 'string',
|
|
messages: {
|
|
type: 'will be assigned'
|
|
}
|
|
},
|
|
messages: {
|
|
items: 'will not be assigned'
|
|
}
|
|
};
|
|
|
|
const validate = jsen(schema);
|
|
const valid = validate([123]);
|
|
|
|
console.assert(!valid);
|
|
console.assert(validate.errors.length === 1);
|
|
console.assert(validate.errors[0].message === 'will be assigned');
|
|
}
|
|
|
|
// does not use custom messages on keyword: items (array)
|
|
{
|
|
const schema = {
|
|
items: [{
|
|
type: 'string',
|
|
messages: {
|
|
type: 'will be assigned'
|
|
}
|
|
}],
|
|
messages: {
|
|
items: 'will not be assigned'
|
|
}
|
|
};
|
|
|
|
const validate = jsen(schema);
|
|
const valid = validate([123, 123]);
|
|
|
|
console.assert(!valid);
|
|
console.assert(validate.errors.length === 1);
|
|
console.assert(validate.errors[0].message === 'will be assigned');
|
|
}
|
|
|
|
// does not use custom messages on keyword: properties
|
|
{
|
|
const schema = {
|
|
properties: {
|
|
foo: {
|
|
type: 'number',
|
|
messages: {
|
|
type: 'will be assigned'
|
|
}
|
|
}
|
|
},
|
|
messages: {
|
|
properties: 'will not be assigned'
|
|
}
|
|
};
|
|
|
|
const validate = jsen(schema);
|
|
const valid = validate({ foo: 'bar' });
|
|
|
|
console.assert(!valid);
|
|
console.assert(validate.errors.length === 1);
|
|
console.assert(validate.errors[0].message === 'will be assigned');
|
|
}
|
|
|
|
// does not use custom messages on keyword: patternProperties
|
|
{
|
|
const schema = {
|
|
patternProperties: {
|
|
'^foo$': {
|
|
type: 'number',
|
|
messages: {
|
|
type: 'will be assigned'
|
|
}
|
|
}
|
|
},
|
|
messages: {
|
|
patternProperties: 'will not be assigned'
|
|
}
|
|
};
|
|
|
|
const validate = jsen(schema);
|
|
const valid = validate({ foo: 'bar' });
|
|
|
|
console.assert(!valid);
|
|
console.assert(validate.errors.length === 1);
|
|
console.assert(validate.errors[0].message === 'will be assigned');
|
|
}
|
|
|
|
// does not use custom messages on keyword: dependencies (schema)
|
|
{
|
|
const schema = {
|
|
dependencies: {
|
|
foo: {
|
|
minProperties: 2,
|
|
messages: {
|
|
minProperties: 'will be assigned'
|
|
}
|
|
}
|
|
},
|
|
messages: {
|
|
dependencies: 'will not be assigned'
|
|
}
|
|
};
|
|
|
|
const validate = jsen(schema);
|
|
const valid = validate({ foo: 'bar' });
|
|
|
|
console.assert(!valid);
|
|
console.assert(validate.errors.length === 1);
|
|
console.assert(validate.errors[0].message === 'will be assigned');
|
|
}
|
|
|
|
// does not use custom messages on keyword: allOf
|
|
{
|
|
const schema = {
|
|
dependencies: {
|
|
foo: {
|
|
minProperties: 2,
|
|
messages: {
|
|
minProperties: 'will be assigned'
|
|
}
|
|
}
|
|
},
|
|
allOf: [
|
|
{
|
|
minimum: 2,
|
|
messages: {
|
|
minimum: 'will not be assigned'
|
|
}
|
|
},
|
|
{
|
|
maximum: 5,
|
|
messages: {
|
|
maximum: 'will be assigned'
|
|
}
|
|
}
|
|
],
|
|
messages: {
|
|
allOf: 'will not be assigned'
|
|
}
|
|
};
|
|
|
|
const validate = jsen(schema);
|
|
const valid = validate(6);
|
|
|
|
console.assert(!valid);
|
|
console.assert(validate.errors.length === 1);
|
|
console.assert(validate.errors[0].message === 'will be assigned');
|
|
}
|
|
}
|
|
}
|
|
|
|
const doesThrow = (func: Function) => {
|
|
try {
|
|
func();
|
|
}
|
|
catch (e) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
const doesNotThrow = (func: Function) => {
|
|
return !doesThrow(func);
|
|
};
|
|
|
|
// fixes
|
|
{
|
|
// Fix broken inlining of regular expressions containing slashes (#15, #25)
|
|
{
|
|
const schema = {
|
|
type: 'string',
|
|
pattern: '^/dev/[^/]+(/[^/]+)*$'
|
|
};
|
|
|
|
console.assert(doesNotThrow(jsen(schema)));
|
|
}
|
|
|
|
// Fix code generation breaks when object properties in schema are not valid identifiers (#16)
|
|
{
|
|
const schema = {
|
|
type: 'object',
|
|
properties: {
|
|
123: {
|
|
type: 'boolean'
|
|
}
|
|
}
|
|
};
|
|
|
|
console.assert(doesNotThrow(() => {
|
|
let validate = jsen(schema);
|
|
console.assert(validate({ 123: true }));
|
|
})
|
|
);
|
|
}
|
|
|
|
// Fix cannot dereference schema when ids change resolution scope (#14)
|
|
{
|
|
let schema: any = {
|
|
$ref: '#child',
|
|
definitions: {
|
|
child: {
|
|
id: '#child',
|
|
type: 'string'
|
|
}
|
|
}
|
|
};
|
|
|
|
console.assert(doesNotThrow(() => {
|
|
let validate = jsen(schema);
|
|
|
|
console.assert(validate('abc'));
|
|
console.assert(!validate(123));
|
|
})
|
|
);
|
|
|
|
|
|
schema = {
|
|
$ref: '#child/definitions/subchild',
|
|
definitions: {
|
|
child: {
|
|
id: '#child',
|
|
definitions: {
|
|
subchild: {
|
|
type: 'number'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
console.assert(doesThrow(() => {
|
|
let validate = jsen(schema);
|
|
})
|
|
);
|
|
}
|
|
|
|
// Fix recursive calls to the same cached $ref validator resets the error object
|
|
{
|
|
const schema = {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
properties: {
|
|
foo: { $ref: '#' }
|
|
},
|
|
required: ['foo']
|
|
}
|
|
};
|
|
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(validate([{ foo: [] }]));
|
|
console.assert(validate([{ foo: [{ foo: [] }] }]));
|
|
console.assert(!validate([{ bar: [] }]));
|
|
console.assert(!validate([{ foo: [{ foo: [] }, { bar: [] }] }])); // Bug! False positive
|
|
}
|
|
}
|
|
|
|
// format
|
|
{
|
|
// date-time
|
|
{
|
|
const schema = { format: 'date-time' };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(validate(new Date().toJSON()));
|
|
|
|
console.assert(!validate(''));
|
|
console.assert(!validate(new Date().toUTCString()));
|
|
console.assert(!validate(new Date().toLocaleDateString()));
|
|
console.assert(!validate(new Date().toTimeString()));
|
|
}
|
|
|
|
// uri
|
|
{
|
|
const schema = { format: 'uri' };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(validate('http://google.com'));
|
|
console.assert(validate('ftp://my-site'));
|
|
console.assert(validate('custom://my-site/long/$cr@mbl3d/u_r-l?with=query%20string'));
|
|
console.assert(validate('//no-scheme-here'));
|
|
|
|
console.assert(!validate(''));
|
|
console.assert(!validate('google'));
|
|
console.assert(!validate('/google'));
|
|
console.assert(!validate('://google'));
|
|
console.assert(!validate('http://google.com/no space allowed'));
|
|
}
|
|
|
|
// email
|
|
{
|
|
const schema = { format: 'email' };
|
|
const validate = jsen(schema);
|
|
const maxLongHostname1 = new Array(5).join('.' + new Array(64).join('a')).substr(1); // 255 chars (4 groups x 63 chars)
|
|
const maxLongHostname2 = new Array(9).join('.' + new Array(32).join('a')).substr(1); // 255 chars (8 groups x 31 chars)
|
|
|
|
console.assert(validate('me@domain'));
|
|
console.assert(validate('first.last+plus-dash#hash!bang$dollar%percent&\'quote*star/dash=equal?question^pow_under`backtick{brace}|bar~tilde@domain'));
|
|
console.assert(validate('me@domain.with.multiple.subdomains'));
|
|
console.assert(validate('me@domain-parts.may.contain-dashes'));
|
|
console.assert(validate('me@a-single-domain-part-can-be-up-to-sixty-three-characters-long63'));
|
|
console.assert(validate('me@' + maxLongHostname1));
|
|
console.assert(validate('me@' + maxLongHostname2));
|
|
|
|
console.assert(!validate(''));
|
|
console.assert(!validate('qu"ote\'s@domain'));
|
|
console.assert(!validate('me@no_underscores+or?special$chars'));
|
|
console.assert(!validate('me@ends-with-dash-'));
|
|
console.assert(!validate('me@-starts-with-dash'));
|
|
console.assert(!validate('me@asingle-domain-part-cannot-be-longer-than-sixty-three-characters'));
|
|
|
|
// These verify that a hostname cannot be longer than 255 chars in total. However,
|
|
// maximum string length verification cannot be performed in the same regex, so
|
|
// these test cases fail. Users must additionall use the `maxlength` keyword in this case.
|
|
// assert(!validate('me@' + maxLongHostname1 + '.a'));
|
|
// assert(!validate('me@' + maxLongHostname2 + '.a'));
|
|
}
|
|
|
|
// ipv4
|
|
{
|
|
const schema = { format: 'ipv4' };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(validate('0.0.0.0'));
|
|
console.assert(validate('255.255.255.255'));
|
|
console.assert(validate('127.0.0.1'));
|
|
|
|
console.assert(!validate(''));
|
|
console.assert(!validate('...'));
|
|
console.assert(!validate('0.0.0.-1'));
|
|
console.assert(!validate('0.0.-1.0'));
|
|
console.assert(!validate('0.-1.0.0'));
|
|
console.assert(!validate('-1.0.0.0'));
|
|
console.assert(!validate('256.0.0.0'));
|
|
console.assert(!validate('0.256.0.0'));
|
|
console.assert(!validate('0.0.256.0'));
|
|
console.assert(!validate('0.0.0.256'));
|
|
}
|
|
|
|
// ipv6
|
|
{
|
|
const schema = { format: 'ipv6' };
|
|
const validate = jsen(schema);
|
|
|
|
console.assert(validate('1:2:3:4:5:6:7:8'));
|
|
console.assert(validate('1::'));
|
|
console.assert(validate('1:2:3:4:5:6:7::'));
|
|
console.assert(validate('1::8'));
|
|
console.assert(validate('1:2:3:4:5:6::8'));
|
|
console.assert(validate('1::7:8'));
|
|
console.assert(validate('1:2:3:4:5::7:8'));
|
|
console.assert(validate('1:2:3:4:5::8'));
|
|
console.assert(validate('1::6:7:8'));
|
|
console.assert(validate('1:2:3:4::6:7:8'));
|
|
console.assert(validate('1:2:3:4::8'));
|
|
console.assert(validate('1::5:6:7:8'));
|
|
console.assert(validate('1:2:3::5:6:7:8'));
|
|
console.assert(validate('1:2:3::8'));
|
|
console.assert(validate('1::4:5:6:7:8'));
|
|
console.assert(validate('1:2::4:5:6:7:8'));
|
|
console.assert(validate('1:2::8'));
|
|
console.assert(validate('1::3:4:5:6:7:8'));
|
|
console.assert(validate('1::8'));
|
|
console.assert(validate('::2:3:4:5:6:7:8'));
|
|
console.assert(validate('::8'));
|
|
console.assert(validate('::'));
|
|
|
|
// link-local IPv6 addresses with zone index
|
|
console.assert(validate('fe80::7:8%eth0'));
|
|
console.assert(validate('fe80::7:8%1'));
|
|
|
|
// IPv4-mapped IPv6 addresses and IPv4-translated addresses
|
|
console.assert(validate('::255.255.255.255'));
|
|
console.assert(validate('::ffff:255.255.255.255'));
|
|
console.assert(validate('::ffff:0:255.255.255.255'));
|
|
|
|
// IPv4-Embedded IPv6 Address
|
|
console.assert(validate('2001:db8:3:4::192.0.2.33'));
|
|
console.assert(validate('64:ff9b::192.0.2.33'));
|
|
|
|
console.assert(!validate(''));
|
|
console.assert(!validate('::_'));
|
|
|
|
// TODO: we may need more invalid cases here
|
|
}
|
|
|
|
// hostname
|
|
{
|
|
const schema = { format: 'hostname' },
|
|
validate = jsen(schema),
|
|
maxLong1 = new Array(5).join('.' + new Array(64).join('a')).substr(1), // 255 chars (4 groups x 63 chars)
|
|
maxLong2 = new Array(9).join('.' + new Array(32).join('a')).substr(1); // 255 chars (8 groups x 31 chars)
|
|
|
|
console.assert(validate('my.host'));
|
|
console.assert(validate('host'));
|
|
console.assert(validate('domain.with.multiple.subdomains'));
|
|
console.assert(validate('domain-parts.may.contain-dashes'));
|
|
console.assert(validate('a-single-domain-part-can-be-up-to-sixty-three-characters-long63'));
|
|
console.assert(validate(maxLong1));
|
|
console.assert(validate(maxLong2));
|
|
|
|
console.assert(!validate(''));
|
|
console.assert(!validate('me@domain'));
|
|
console.assert(!validate('qu"ote\'s'));
|
|
console.assert(!validate('no_underscores+or?special$chars'));
|
|
console.assert(!validate('ends-with-dash-'));
|
|
console.assert(!validate('-starts-with-dash'));
|
|
console.assert(!validate('asingle-domain-part-cannot-be-longer-than-sixty-three-characters'));
|
|
|
|
// These verify that a hostname cannot be longer than 255 chars in total. However,
|
|
// maximum string length verification cannot be performed in the same regex, so
|
|
// these test cases fail. Users must additionall use the `maxlength` keyword in this case.
|
|
// assert(!validate(maxLong1 + '.a'));
|
|
// assert(!validate(maxLong2 + '.a'));
|
|
}
|
|
|
|
// custom format
|
|
{
|
|
// accepts string
|
|
{
|
|
const schema = { format: 'custom' },
|
|
custom = '^\\d+$',
|
|
validate = jsen(schema, {
|
|
formats: {
|
|
custom: custom
|
|
}
|
|
});
|
|
|
|
console.assert(validate('123'));
|
|
console.assert(!validate('a123'));
|
|
}
|
|
|
|
// accepts regex
|
|
{
|
|
const schema = { format: 'custom' },
|
|
custom = /^\d+$/,
|
|
validate = jsen(schema, {
|
|
formats: {
|
|
custom: custom
|
|
}
|
|
});
|
|
|
|
console.assert(validate('123'));
|
|
console.assert(!validate('a123'));
|
|
}
|
|
|
|
// accepts function
|
|
{
|
|
let schema = { format: 'custom' },
|
|
callCount = 0,
|
|
custom = (value: any, childSchema: any) => {
|
|
console.assert(value.indexOf('123') > -1);
|
|
console.assert(childSchema === schema);
|
|
|
|
callCount++;
|
|
|
|
return /^\d+$/.test(value);
|
|
},
|
|
validate = jsen(schema, {
|
|
formats: {
|
|
custom: custom
|
|
}
|
|
});
|
|
|
|
console.assert(validate('123'));
|
|
console.assert(!validate('a123'));
|
|
console.assert(callCount === 2);
|
|
}
|
|
|
|
// is run for all types
|
|
{
|
|
let schema = { format: 'custom' },
|
|
callCount = 0,
|
|
options = <JsenSettings>{
|
|
formats: {
|
|
custom: () => {
|
|
callCount++;
|
|
return true;
|
|
}
|
|
}
|
|
},
|
|
validate = jsen(schema, options),
|
|
data = [
|
|
undefined,
|
|
null,
|
|
'abc',
|
|
123,
|
|
Math.PI,
|
|
true,
|
|
false,
|
|
{},
|
|
[],
|
|
new Date()
|
|
];
|
|
|
|
data.forEach((dataItem) => {
|
|
validate(dataItem);
|
|
console.assert(callCount === 1);
|
|
callCount = 0;
|
|
});
|
|
}
|
|
|
|
// is not run if a built-in keyword fails
|
|
{
|
|
let schema = {
|
|
format: 'custom',
|
|
type: 'number',
|
|
maximum: 10
|
|
},
|
|
callCount = 0,
|
|
options = <JsenSettings>{
|
|
formats: {
|
|
custom: () => {
|
|
callCount++;
|
|
return true;
|
|
}
|
|
}
|
|
},
|
|
validate = jsen(schema, options);
|
|
|
|
console.assert(!validate(123));
|
|
console.assert(callCount === 0);
|
|
|
|
console.assert(validate(7));
|
|
console.assert(callCount === 1);
|
|
}
|
|
|
|
// common scenarios
|
|
{
|
|
// verify passwords match
|
|
{
|
|
let schema = {
|
|
description: 'User account creation form',
|
|
type: 'object',
|
|
properties: {
|
|
password: {
|
|
type: 'string',
|
|
minLength: 8
|
|
},
|
|
password_confirm: {
|
|
type: 'string',
|
|
minLength: 8
|
|
}
|
|
},
|
|
format: 'passwordsMatch'
|
|
},
|
|
options = <JsenSettings>{
|
|
formats: {
|
|
passwordsMatch: (obj: any) => {
|
|
callCount++;
|
|
return obj.password === obj.password_confirm;
|
|
}
|
|
}
|
|
},
|
|
data = {
|
|
password: '1234567',
|
|
password_confirm: '1234567'
|
|
},
|
|
validate = jsen(schema, options),
|
|
callCount = 0;
|
|
|
|
console.assert(!validate(data)); // minLength validator failed
|
|
console.assert(callCount === 0);
|
|
|
|
data.password += '8';
|
|
data.password_confirm += '9';
|
|
|
|
console.assert(!validate(data)); // custom validator failed
|
|
console.assert(callCount === 1);
|
|
|
|
data.password_confirm = data.password;
|
|
|
|
console.assert(validate(data)); // OK
|
|
console.assert(callCount === 2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// option: greedy
|
|
{
|
|
// validates as many keywords as possible
|
|
{
|
|
let schema = {
|
|
type: 'object',
|
|
properties: {
|
|
test1: {
|
|
type: 'string'
|
|
},
|
|
test2: {
|
|
type: 'object',
|
|
properties: {
|
|
test21: {
|
|
type: 'number'
|
|
}
|
|
}
|
|
},
|
|
test3: {
|
|
type: 'number'
|
|
},
|
|
test4: { $ref: '#external' }
|
|
},
|
|
additionalProperties: false
|
|
},
|
|
options = {
|
|
greedy: true,
|
|
schemas: {
|
|
external: {
|
|
type: 'string'
|
|
}
|
|
}
|
|
},
|
|
validate = jsen(schema, options),
|
|
invalidTest = {
|
|
test1: 1,
|
|
test2: '2',
|
|
test3: 'j',
|
|
test4: 4
|
|
},
|
|
ret = validate(invalidTest);
|
|
|
|
console.assert(!ret); // false
|
|
console.assert(JSON.stringify(validate.errors)
|
|
=== JSON.stringify([
|
|
{ path: 'test1', keyword: 'type' },
|
|
{ path: 'test2', keyword: 'type' },
|
|
{ path: 'test3', keyword: 'type' },
|
|
{ path: 'test4', keyword: 'type' }
|
|
])
|
|
);
|
|
|
|
delete options.greedy;
|
|
|
|
validate = jsen(schema, options);
|
|
|
|
ret = validate(invalidTest);
|
|
|
|
console.assert(!ret); // false
|
|
console.assert(JSON.stringify(validate.errors)
|
|
=== JSON.stringify([{ path: 'test1', keyword: 'type' }])
|
|
);
|
|
}
|
|
|
|
// does not descend into invalid objects
|
|
{
|
|
let schema = {
|
|
type: 'object',
|
|
properties: {
|
|
test1: { type: 'object' },
|
|
test2: {
|
|
required: ['foo']
|
|
},
|
|
test3: {
|
|
properties: {
|
|
foo: { type: 'string' }
|
|
}
|
|
},
|
|
test4: {
|
|
type: 'array',
|
|
items: {
|
|
type: 'object',
|
|
required: ['foo']
|
|
}
|
|
}
|
|
}
|
|
},
|
|
options = { greedy: true },
|
|
data = {
|
|
test1: 123,
|
|
test2: {},
|
|
test3: 123,
|
|
test4: [{}, { foo: 123 }, null]
|
|
},
|
|
validate = jsen(schema, options),
|
|
ret = validate(data);
|
|
|
|
console.assert(!ret);
|
|
console.assert(JSON.stringify(validate.errors)
|
|
=== JSON.stringify([
|
|
{ path: 'test1', keyword: 'type' },
|
|
{ path: 'test2.foo', keyword: 'required' },
|
|
{ path: 'test4.0.foo', keyword: 'required' },
|
|
{ path: 'test4.2', keyword: 'type' }
|
|
])
|
|
);
|
|
}
|
|
}
|
|
|
|
// jsen
|
|
{
|
|
// is a function
|
|
{
|
|
console.assert(typeof jsen === "function");
|
|
}
|
|
|
|
// throws if schema is not an object
|
|
{
|
|
console.assert(doesThrow(jsen()));
|
|
console.assert(doesThrow(jsen(null)));
|
|
console.assert(doesThrow(jsen(false)));
|
|
console.assert(doesThrow(jsen(123)));
|
|
console.assert(doesThrow(jsen("abc")));
|
|
console.assert(doesThrow(jsen([])));
|
|
console.assert(doesNotThrow(jsen({})));
|
|
}
|
|
|
|
// produces a function
|
|
{
|
|
const validate = jsen({});
|
|
console.assert(typeof validate === 'function');
|
|
console.assert(validate() === true);
|
|
// console.assert(validate.error === null);
|
|
}
|
|
}
|
|
|
|
// type: integer
|
|
{
|
|
// required
|
|
{
|
|
const schema = { type: 'integer' },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate());
|
|
console.assert(!validate(null));
|
|
console.assert(validate(123));
|
|
}
|
|
|
|
// nullable
|
|
{
|
|
const schema = { type: ['integer', 'null'] },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(undefined));
|
|
|
|
console.assert(validate(null));
|
|
console.assert(validate(123));
|
|
}
|
|
|
|
// type
|
|
{
|
|
const schema = { type: 'integer' },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate('123'));
|
|
console.assert(!validate(true));
|
|
console.assert(!validate(false));
|
|
console.assert(!validate([]));
|
|
console.assert(!validate({}));
|
|
console.assert(!validate(Math.PI));
|
|
|
|
console.assert(validate(13));
|
|
}
|
|
|
|
// enum
|
|
{
|
|
const schema = { type: 'integer', enum: [1, 3, 5, 7] },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(4));
|
|
console.assert(validate(5));
|
|
}
|
|
|
|
// minimum
|
|
{
|
|
const schema = { type: 'integer', minimum: 7 },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(6));
|
|
|
|
console.assert(validate(7));
|
|
console.assert(validate(999));
|
|
}
|
|
|
|
// exclusiveMinimum
|
|
{
|
|
const schema = {
|
|
type: 'integer',
|
|
minimum: 7,
|
|
exclusiveMinimum: true
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(6));
|
|
console.assert(!validate(7));
|
|
|
|
console.assert(validate(8));
|
|
console.assert(validate(999));
|
|
}
|
|
|
|
// maximum
|
|
{
|
|
const schema = { type: 'integer', maximum: 77 },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(78));
|
|
|
|
console.assert(validate(-12));
|
|
console.assert(validate(76));
|
|
console.assert(validate(77));
|
|
}
|
|
|
|
// exclusiveMaximum
|
|
{
|
|
const schema = {
|
|
type: 'integer',
|
|
maximum: 77,
|
|
exclusiveMaximum: true
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(77));
|
|
console.assert(!validate(78));
|
|
|
|
console.assert(validate(-12));
|
|
console.assert(validate(75));
|
|
console.assert(validate(76));
|
|
}
|
|
|
|
// multipleOf
|
|
{
|
|
const schema = { type: 'integer', multipleOf: 7 },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(8));
|
|
|
|
console.assert(validate(14));
|
|
console.assert(validate(-49));
|
|
console.assert(validate(77));
|
|
}
|
|
}
|
|
|
|
// missing $ref
|
|
{
|
|
// passes validation with ignore missing $ref
|
|
{
|
|
let schema = {
|
|
type: 'object',
|
|
properties: {
|
|
test1: { $ref: '#external1' },
|
|
test2: {
|
|
type: 'number'
|
|
},
|
|
test3: { $ref: '#external3' } //missing
|
|
},
|
|
additionalProperties: false
|
|
},
|
|
external1 = {
|
|
type: 'object',
|
|
properties: {
|
|
test11: { $ref: '#external11' }, //missing
|
|
test12: {
|
|
type: 'number'
|
|
},
|
|
test13: { $ref: '#external11' } //duplicate
|
|
}
|
|
},
|
|
validate = jsen(schema, {
|
|
schemas: {
|
|
external1: external1
|
|
},
|
|
missing$Ref: true
|
|
}),
|
|
missingTest = {
|
|
test1: {
|
|
test11: 'missing',
|
|
test12: 5,
|
|
test13: 'missing too'
|
|
},
|
|
test2: 2,
|
|
test3: 3
|
|
},
|
|
invalidTest = {
|
|
test1: {
|
|
test11: 'missing',
|
|
test12: 5,
|
|
test13: 'missing too'
|
|
},
|
|
test2: 'fail',
|
|
test3: 3
|
|
},
|
|
ret: boolean;
|
|
|
|
ret = validate(missingTest);
|
|
console.assert(ret); // true
|
|
|
|
ret = validate(invalidTest);
|
|
console.assert(!ret); // !false
|
|
}
|
|
}
|
|
|
|
// multi schema
|
|
{
|
|
// allOf
|
|
{
|
|
const schema = {
|
|
allOf: [
|
|
{ type: 'number' },
|
|
{ type: 'integer' }
|
|
]
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(null));
|
|
console.assert(!validate(Math.PI));
|
|
|
|
console.assert(validate(0));
|
|
console.assert(validate(777));
|
|
console.assert(validate(-9));
|
|
}
|
|
|
|
// anyOf
|
|
{
|
|
const schema = {
|
|
anyOf: [
|
|
{ type: 'string' },
|
|
{ type: 'number' }
|
|
]
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(null));
|
|
console.assert(!validate(true));
|
|
console.assert(!validate({}));
|
|
console.assert(!validate([]));
|
|
|
|
console.assert(validate('abc'));
|
|
console.assert(validate(123));
|
|
console.assert(validate(''));
|
|
console.assert(validate(0));
|
|
}
|
|
|
|
// oneOf
|
|
{
|
|
const schema = {
|
|
oneOf: [
|
|
{ type: 'number', maximum: 5 },
|
|
{ type: 'number', minimum: 3 }
|
|
]
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(null));
|
|
console.assert(!validate(true));
|
|
console.assert(!validate({}));
|
|
console.assert(!validate([]));
|
|
// matches both validators
|
|
console.assert(!validate(3));
|
|
|
|
console.assert(validate(0));
|
|
console.assert(validate(1));
|
|
console.assert(validate(2));
|
|
console.assert(validate(6));
|
|
console.assert(validate(17));
|
|
}
|
|
|
|
// not
|
|
{
|
|
const schema = {
|
|
not: {
|
|
type: 'array'
|
|
}
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate([]));
|
|
|
|
console.assert(validate(0));
|
|
console.assert(validate(false));
|
|
console.assert(validate('abc'));
|
|
console.assert(validate({}));
|
|
console.assert(validate(null));
|
|
console.assert(validate());
|
|
}
|
|
}
|
|
|
|
// type: null
|
|
{
|
|
// required
|
|
{
|
|
const schema = { type: 'null' },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(undefined));
|
|
console.assert(validate(null));
|
|
}
|
|
|
|
// type
|
|
{
|
|
const schema = { type: 'null' },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate('123'));
|
|
console.assert(!validate([]));
|
|
console.assert(!validate({}));
|
|
console.assert(!validate(Math.PI));
|
|
|
|
console.assert(validate(null));
|
|
}
|
|
}
|
|
|
|
// type: number
|
|
{
|
|
// required
|
|
{
|
|
const schema = { type: 'number' },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate());
|
|
console.assert(!validate(null));
|
|
|
|
console.assert(validate(Math.PI));
|
|
console.assert(validate(123));
|
|
}
|
|
|
|
// nullable
|
|
{
|
|
const schema = { type: ['number', 'null'] },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(undefined));
|
|
|
|
console.assert(validate(null));
|
|
console.assert(validate(Math.PI));
|
|
}
|
|
|
|
// type
|
|
{
|
|
const schema = { type: 'number' },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate('123'));
|
|
console.assert(!validate(true));
|
|
console.assert(!validate(false));
|
|
console.assert(!validate([]));
|
|
console.assert(!validate({}));
|
|
|
|
console.assert(validate(13));
|
|
console.assert(validate(17.8));
|
|
console.assert(validate(Math.PI));
|
|
}
|
|
|
|
// enum
|
|
{
|
|
const schema = {
|
|
type: 'number',
|
|
enum: [1, Math.E, 3, 5, 7]
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(4));
|
|
console.assert(!validate(Math.PI));
|
|
|
|
console.assert(validate(5));
|
|
console.assert(validate(Math.E));
|
|
}
|
|
|
|
// minimum
|
|
{
|
|
const schema = { type: 'number', minimum: 7 },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(6));
|
|
console.assert(!validate(Math.PI));
|
|
|
|
console.assert(validate(7));
|
|
console.assert(validate(999));
|
|
}
|
|
|
|
// exclusiveMinimum
|
|
{
|
|
const schema = {
|
|
type: 'number',
|
|
minimum: 7,
|
|
exclusiveMinimum: true
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(6));
|
|
console.assert(!validate(7));
|
|
console.assert(!validate(Math.PI));
|
|
|
|
console.assert(validate(8));
|
|
console.assert(validate(999));
|
|
}
|
|
|
|
// maximum
|
|
{
|
|
const schema = { type: 'number', maximum: 77 },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(77.000001));
|
|
console.assert(!validate(78));
|
|
|
|
console.assert(validate(-12));
|
|
console.assert(validate(76));
|
|
console.assert(validate(77));
|
|
console.assert(validate(Math.PI));
|
|
}
|
|
|
|
// exclusiveMaximum
|
|
{
|
|
const schema = {
|
|
type: 'number',
|
|
maximum: 77,
|
|
exclusiveMaximum: true
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(77));
|
|
console.assert(!validate(78));
|
|
|
|
console.assert(validate(-12));
|
|
console.assert(validate(75));
|
|
console.assert(validate(76));
|
|
console.assert(validate(76.99999));
|
|
}
|
|
|
|
// multipleOf
|
|
{
|
|
let schema = { type: 'number', multipleOf: 7 },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(8));
|
|
|
|
console.assert(validate(14));
|
|
console.assert(validate(-49));
|
|
console.assert(validate(77));
|
|
|
|
schema = {
|
|
type: 'number',
|
|
multipleOf: 3.14 // Math.PI
|
|
};
|
|
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(2.5));
|
|
|
|
console.assert(validate(9.42)); // 3 * Math.PI
|
|
}
|
|
|
|
// fix multipleOf doesn't validate data for decimal point (#1)
|
|
{
|
|
const schema = { type: 'number', multipleOf: 0.01 },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(validate(18.15));
|
|
}
|
|
}
|
|
|
|
// type: object
|
|
{
|
|
// required
|
|
{
|
|
const schema = { type: 'object' },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate());
|
|
console.assert(!validate(null));
|
|
|
|
console.assert(validate({}));
|
|
}
|
|
|
|
// nullable
|
|
{
|
|
const schema = { type: ['object', 'null'] },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(undefined));
|
|
|
|
console.assert(validate(null));
|
|
console.assert(validate({}));
|
|
}
|
|
|
|
// type
|
|
{
|
|
const schema = { type: 'object' },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate('123'));
|
|
console.assert(!validate(false));
|
|
console.assert(!validate([]));
|
|
console.assert(!validate(Math.PI));
|
|
|
|
console.assert(validate({}));
|
|
console.assert(jsen({ type: 'object', properties: {} }, {})());
|
|
}
|
|
|
|
// maxProperties
|
|
{
|
|
const schema = { type: 'object', maxProperties: 3 },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate({ a: 1, b: 2, c: 3, d: 4 }));
|
|
|
|
console.assert(validate({}));
|
|
console.assert(validate({ a: 1 }));
|
|
console.assert(validate({ a: 1, b: 2 }));
|
|
console.assert(validate({ a: 1, b: 2, c: 3 }));
|
|
}
|
|
|
|
// minProperties
|
|
{
|
|
const schema = { type: 'object', minProperties: 2 },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate({}));
|
|
console.assert(!validate({ a: 1 }));
|
|
|
|
console.assert(validate({ a: 1, b: 2 }));
|
|
console.assert(validate({ a: 1, b: 2, c: 3 }));
|
|
}
|
|
|
|
// required properties
|
|
{
|
|
const schema = {
|
|
type: 'object',
|
|
properties: {
|
|
a: { type: 'string' },
|
|
b: { type: 'number' },
|
|
c: { type: 'boolean' }
|
|
},
|
|
required: ['a', 'b']
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate({}));
|
|
console.assert(!validate({ c: true }));
|
|
console.assert(!validate({ a: 'abc', c: true }));
|
|
console.assert(!validate({ b: 123, c: true }));
|
|
console.assert(!validate({ a: 'abc', b: undefined }));
|
|
|
|
console.assert(validate({ a: 'abc', b: 123 }));
|
|
console.assert(validate({ a: 'abc', b: 123, c: true }));
|
|
}
|
|
|
|
// additionalProperties
|
|
{
|
|
let schema = {
|
|
type: 'object',
|
|
properties: {
|
|
a: { type: 'string' },
|
|
b: { type: 'number' }
|
|
},
|
|
additionalProperties: true
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(validate({ a: 'abc' }));
|
|
console.assert(validate({ b: 123 }));
|
|
console.assert(validate({ a: 'abc', b: 123 }));
|
|
console.assert(validate({ a: 'abc', b: 123, c: true }));
|
|
|
|
schema.additionalProperties = false;
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate({ c: true }));
|
|
console.assert(!validate({ a: 'abc', b: 123, c: true }));
|
|
|
|
console.assert(validate({ a: 'abc', b: 123 }));
|
|
console.assert(jsen({ type: 'object', additionalProperties: false })({}));
|
|
}
|
|
|
|
// additionalProperties as schema
|
|
{
|
|
const schema = {
|
|
type: 'object',
|
|
properties: {
|
|
a: { type: 'string' },
|
|
b: { type: 'number' }
|
|
},
|
|
additionalProperties: {
|
|
type: 'boolean'
|
|
}
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate({ a: 'abc', b: 123, c: 123 }));
|
|
|
|
console.assert(validate({ a: 'abc', b: 123, c: false }));
|
|
}
|
|
|
|
// additionalProperties with patternProperties
|
|
{
|
|
let schema = {
|
|
type: 'object',
|
|
properties: {
|
|
a: { type: 'string' }
|
|
},
|
|
patternProperties: {
|
|
'^b': { type: 'number' }
|
|
},
|
|
additionalProperties: true
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(validate({ a: 'abc' }));
|
|
console.assert(validate({ b: 123 }));
|
|
console.assert(validate({ a: 'abc', b: 123, bar: Math.E, baz: Math.PI }));
|
|
console.assert(validate({ a: 'abc', baz: 123, c: true }));
|
|
|
|
schema.additionalProperties = false;
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate({ c: true }));
|
|
console.assert(!validate({ a: 'abc', bar: 123, c: true }));
|
|
|
|
console.assert(validate({ a: 'abc', baz: 123 }));
|
|
console.assert(jsen({ type: 'object', additionalProperties: false })({}));
|
|
}
|
|
|
|
// patternProperties
|
|
{
|
|
const schema = {
|
|
type: 'object',
|
|
patternProperties: {
|
|
'^a': { type: 'string' },
|
|
'^b': { type: 'number' }
|
|
}
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate({ a: 123 }));
|
|
console.assert(!validate({ b: 'abc' }));
|
|
|
|
console.assert(validate({}));
|
|
console.assert(validate({ a: 'abc' }));
|
|
console.assert(validate({ b: 123 }));
|
|
console.assert(validate({ a: 'abc', b: 123 }));
|
|
}
|
|
|
|
// dependencies: schema
|
|
{
|
|
const schema = {
|
|
type: 'object',
|
|
properties: {
|
|
a: { type: 'string' },
|
|
b: { type: 'number' }
|
|
},
|
|
dependencies: {
|
|
a: {
|
|
type: 'object',
|
|
required: ['c'],
|
|
properties: {
|
|
c: { type: 'boolean' }
|
|
}
|
|
},
|
|
b: {
|
|
type: 'object',
|
|
required: ['f'],
|
|
properties: {
|
|
f: { type: 'null' }
|
|
}
|
|
},
|
|
g: {
|
|
type: 'object',
|
|
required: ['b'],
|
|
properties: {
|
|
b: {
|
|
type: 'integer'
|
|
}
|
|
}
|
|
}
|
|
}
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate({ a: 'abc' }));
|
|
console.assert(!validate({ a: 'abc', c: 123 }));
|
|
console.assert(!validate({ b: Math.PI, f: false }));
|
|
console.assert(!validate({ b: Math.PI, g: null }));
|
|
|
|
console.assert(validate({}));
|
|
console.assert(validate({ a: 'abc', c: false }));
|
|
console.assert(validate({ b: Math.PI, f: null }));
|
|
console.assert(validate({ b: 123, g: 'any value', f: null }));
|
|
}
|
|
|
|
// dependencies: property
|
|
{
|
|
const schema = {
|
|
type: 'object',
|
|
properties: {
|
|
a: { type: 'string' },
|
|
b: { type: 'number' },
|
|
c: { type: 'boolean' }
|
|
},
|
|
dependencies: {
|
|
a: ['b', 'c']
|
|
}
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate({ a: 'abc' }));
|
|
console.assert(!validate({ a: 'abc', b: 123 }));
|
|
|
|
console.assert(validate({}));
|
|
console.assert(validate({ a: 'abc', b: 123, c: false }));
|
|
}
|
|
|
|
// nested graph
|
|
{
|
|
const schema = {
|
|
type: ['object', 'null'],
|
|
properties: {
|
|
a: { type: 'string' },
|
|
b: { type: 'number' },
|
|
c: {
|
|
type: 'array',
|
|
items: { type: 'boolean' }
|
|
}
|
|
},
|
|
required: ['a']
|
|
},
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate());
|
|
console.assert(!validate({}));
|
|
console.assert(!validate({ a: 123 }));
|
|
console.assert(!validate({ a: 'abc', b: false }));
|
|
console.assert(!validate({ a: 'abc', c: [null] }));
|
|
|
|
console.assert(validate(null));
|
|
console.assert(validate({ a: 'abc', b: 123.4, c: [true, false] }));
|
|
console.assert(validate({ a: 'abc', b: 0 }));
|
|
console.assert(validate({ a: 'abc', c: [true, false] }));
|
|
console.assert(validate({ a: 'abc', c: [] }));
|
|
}
|
|
}
|
|
|
|
// $ref
|
|
{
|
|
// throws if string is not in correct format
|
|
{
|
|
console.assert(doesThrow(
|
|
jsen({ $ref: '' })
|
|
));
|
|
|
|
console.assert(doesThrow(
|
|
jsen({ $ref: '#double//slash' })
|
|
));
|
|
|
|
console.assert(doesThrow(
|
|
jsen({ $ref: '#ends/with/slash/' })
|
|
));
|
|
|
|
console.assert(doesThrow(
|
|
// invalid reference, non-existent schema properties
|
|
jsen({ $ref: '#a/b/c' })
|
|
));
|
|
|
|
console.assert(doesNotThrow(
|
|
// schema resolves to itself
|
|
jsen({ $ref: '#' })
|
|
));
|
|
|
|
console.assert(doesNotThrow(
|
|
jsen({
|
|
a: {
|
|
b: {
|
|
c: {
|
|
type: 'any'
|
|
}
|
|
}
|
|
},
|
|
$ref: '#/a/b/c'
|
|
})
|
|
));
|
|
|
|
console.assert(doesNotThrow(
|
|
jsen({
|
|
arr: [
|
|
{ value: { type: 'string' } },
|
|
{ value: { type: 'number' } },
|
|
{ value: { type: 'boolean' } }
|
|
],
|
|
type: 'object',
|
|
properties: {
|
|
a: { $ref: '#arr/2/value' }
|
|
}
|
|
})
|
|
));
|
|
}
|
|
|
|
// external schema
|
|
{
|
|
// finds external schema with a hash
|
|
{
|
|
const external = { type: 'string' },
|
|
schema = { $ref: '#external' },
|
|
validate = jsen(schema, {
|
|
schemas: {
|
|
external: external
|
|
}
|
|
});
|
|
|
|
console.assert(validate('abc'));
|
|
console.assert(!validate(123));
|
|
}
|
|
|
|
// finds external schema without a hash
|
|
{
|
|
const external = { type: 'string' },
|
|
schema = { $ref: 'external' },
|
|
validate = jsen(schema, {
|
|
schemas: {
|
|
external: external
|
|
}
|
|
});
|
|
|
|
console.assert(validate('abc'));
|
|
console.assert(!validate(123));
|
|
}
|
|
|
|
// throws when no external schema found
|
|
{
|
|
const schema = { $ref: '#external' };
|
|
|
|
console.assert(doesNotThrow(
|
|
jsen(schema)
|
|
));
|
|
}
|
|
|
|
// own property takes precendence over external schema
|
|
{
|
|
const external = { type: 'string' },
|
|
schema = {
|
|
external: { type: 'number' },
|
|
$ref: '#external'
|
|
},
|
|
validate = jsen(schema, {
|
|
schemas: {
|
|
external: external
|
|
}
|
|
});
|
|
|
|
console.assert(!validate('abc'));
|
|
console.assert(validate(123));
|
|
}
|
|
|
|
// external schemas have their own dereferencing scope
|
|
{
|
|
const external = {
|
|
inner: { type: 'string' },
|
|
$ref: '#inner'
|
|
},
|
|
schema = {
|
|
inner: { type: 'number' },
|
|
$ref: '#external'
|
|
},
|
|
validate = jsen(schema, {
|
|
schemas: {
|
|
external: external
|
|
}
|
|
});
|
|
|
|
console.assert(validate('abc'));
|
|
console.assert(!validate(123));
|
|
}
|
|
}
|
|
}
|
|
|
|
// type: string
|
|
{
|
|
// required
|
|
{
|
|
const schema = { type: 'string' },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate());
|
|
console.assert(!validate(null));
|
|
|
|
console.assert(validate('abc'));
|
|
}
|
|
|
|
// nullable
|
|
{
|
|
const schema = { type: ['string', 'null'] },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(undefined));
|
|
|
|
console.assert(validate(null));
|
|
console.assert(validate(''));
|
|
}
|
|
|
|
// type
|
|
{
|
|
const schema = { type: 'string' },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate(123));
|
|
console.assert(!validate(true));
|
|
console.assert(!validate(false));
|
|
console.assert(!validate(0));
|
|
console.assert(!validate([]));
|
|
console.assert(!validate({}));
|
|
|
|
console.assert(validate('abc'));
|
|
}
|
|
|
|
// enum
|
|
{
|
|
const schema = { type: 'string', enum: ['a', 'b', 'c'] },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate('not in enum'));
|
|
console.assert(validate('b'));
|
|
}
|
|
|
|
// minLength
|
|
{
|
|
const schema = { type: 'string', minLength: 10 },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate('too short'));
|
|
console.assert(validate('just long enough'));
|
|
}
|
|
|
|
// maxLength
|
|
{
|
|
const schema = { type: 'string', maxLength: 12 },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate('this string is too long'));
|
|
console.assert(validate('short enough'));
|
|
}
|
|
|
|
// pattern
|
|
{
|
|
let schema: any = { type: 'string', pattern: '\\d' },
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate('a'));
|
|
console.assert(validate('1'));
|
|
|
|
schema = { type: 'string', pattern: /\d/ };
|
|
validate = jsen(schema);
|
|
|
|
console.assert(!validate('a'));
|
|
console.assert(validate('1'));
|
|
}
|
|
}
|
|
|
|
// unique
|
|
{
|
|
// filters unique values
|
|
{
|
|
const inputs = [
|
|
[1, 'a', 3, false, null, undefined],
|
|
['abc', 123, true, 123, false, Math.PI, 'abc', true, null, null]
|
|
],
|
|
expected = [
|
|
[1, 'a', 3, false, null, undefined],
|
|
['abc', 123, true, false, Math.PI, null]
|
|
];
|
|
|
|
for (let i = 0; i < inputs.length; i++) {
|
|
console.assert(JSON.stringify(jsen.unique(inputs[i])) === JSON.stringify(expected[i]));
|
|
}
|
|
}
|
|
|
|
// performs deep equality checks
|
|
{
|
|
const input: any = [
|
|
{},
|
|
{ a: 1 },
|
|
{ b: { c: { d: 123, f: null }, e: 'abc' } },
|
|
[1, 2, 3],
|
|
[{ a: 213 }],
|
|
{ b: 1 },
|
|
{ a: 1, b: undefined },
|
|
{ b: { e: 'abc', c: { f: null, d: 123 } } },
|
|
[1, 2, 3],
|
|
[{ a: 213 }]
|
|
],
|
|
expected: any = [
|
|
{},
|
|
{ a: 1 },
|
|
{ b: { c: { d: 123, f: null }, e: 'abc' } },
|
|
[1, 2, 3],
|
|
[{ a: 213 }],
|
|
{ b: 1 },
|
|
{ a: 1, b: undefined }
|
|
];
|
|
|
|
console.assert(JSON.stringify(jsen.unique(input)) === JSON.stringify(expected));
|
|
}
|
|
|
|
// unique.findIndex
|
|
{
|
|
// finds an item index with comparator
|
|
{
|
|
const arr = [{}, { a: 1 }, { a: 1, b: 2 }],
|
|
expected = 2,
|
|
comparator = (obj1: any, obj2: any) => {
|
|
return obj1.a === obj2.a && obj1.b === obj2.b;
|
|
};
|
|
|
|
console.assert(jsen.unique.findIndex(arr, { a: 1, b: 2 }, comparator) === expected);
|
|
}
|
|
|
|
// returns -1 when item cannot be found
|
|
{
|
|
const arr = [{}, { a: 1 }, { a: 1, b: 2 }],
|
|
expected = -1,
|
|
comparator = (obj1: any, obj2: any) => {
|
|
return obj1.a === obj2.a && obj1.b === obj2.b;
|
|
};
|
|
|
|
console.assert(jsen.unique.findIndex(arr, { a: 1, b: null }, comparator) === expected);
|
|
}
|
|
}
|
|
}
|