diff --git a/CHANGELOG-pre.md b/CHANGELOG-pre.md index 10c3a81e3..6e5f0d994 100644 --- a/CHANGELOG-pre.md +++ b/CHANGELOG-pre.md @@ -1,3 +1,27 @@ +# [1.0.0-beta.0](https://github.com/jquense/yup/compare/v1.0.0-alpha.4...v1.0.0-beta.0) (2021-12-29) + + +* feat!: add json() method and remove default object/array coercion ([94b73c4](https://github.com/jquense/yup/commit/94b73c438b3d355253f488325e06c69378e71fc1)) + + +### Features + +* Make Array generic consistent with others ([a82353f](https://github.com/jquense/yup/commit/a82353f37735daec6e42d18bd4cc0efe52a20f50)) + + +### BREAKING CHANGES + +* types only, `ArraySchema` initial generic is the array type not the type of the array element. `array()` is still the inner type. +* object and array schema no longer parse JSON strings by default, nor do they return `null` for invalid casts. + +```ts +object().json().cast('{}') +array().json().cast('[]') +``` +to mimic the previous behavior + + + # [1.0.0-alpha.4](https://github.com/jquense/yup/compare/v1.0.0-alpha.3...v1.0.0-alpha.4) (2021-12-29) ### Bug Fixes diff --git a/README.md b/README.md index 21e102679..1980cceb9 100644 --- a/README.md +++ b/README.md @@ -1399,6 +1399,7 @@ and use the result as the limit. Define an array schema. Arrays can be typed or not, When specifying the element type, `cast` and `isValid` will apply to the elements as well. Options passed into `isValid` are passed also passed to child schemas. + Inherits from [`Schema`](#Schema). ```js @@ -1418,9 +1419,7 @@ array().of(yup.number()); array(yup.number()); ``` -The default `cast` behavior for `array` is: [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) - -Failed casts return: `null`; +Arrays have no default casting behavior. #### `array.of(type: Schema): this` diff --git a/src/Lazy.ts b/src/Lazy.ts index 4e79e14e2..a74bbea1c 100644 --- a/src/Lazy.ts +++ b/src/Lazy.ts @@ -1,5 +1,5 @@ import isSchema from './util/isSchema'; -import type { AnyObject, Callback, ValidateOptions } from './types'; +import type { AnyObject, ValidateOptions } from './types'; import type { ResolveOptions } from './Condition'; import type { @@ -82,13 +82,12 @@ class Lazy return this._resolve(value, options).cast(value, options); } - validate( - value: any, - options?: ValidateOptions, - maybeCb?: Callback, - ): Promise { - // @ts-expect-error missing public callback on type - return this._resolve(value, options).validate(value, options, maybeCb); + asTest(value: any, options?: ValidateOptions) { + return this._resolve(value, options).asTest(value, options); + } + + validate(value: any, options?: ValidateOptions): Promise { + return this._resolve(value, options).validate(value, options); } validateSync(value: any, options?: ValidateOptions): T { diff --git a/src/array.ts b/src/array.ts index 10b2e0db4..fbe4bfff3 100644 --- a/src/array.ts +++ b/src/array.ts @@ -2,16 +2,13 @@ import isAbsent from './util/isAbsent'; import isSchema from './util/isSchema'; import printValue from './util/printValue'; import { array as locale } from './locale'; -import runTests, { RunTest } from './util/runTests'; import type { AnyObject, InternalOptions, - Callback, Message, Maybe, Optionals, } from './types'; -import ValidationError from './ValidationError'; import type Reference from './Reference'; import { Defined, @@ -24,9 +21,14 @@ import { UnsetFlag, Concat, } from './util/types'; -import Schema, { SchemaInnerTypeDescription, SchemaSpec } from './schema'; +import Schema, { + RunTest, + SchemaInnerTypeDescription, + SchemaSpec, +} from './schema'; import { ResolveOptions } from './Condition'; import parseJson from 'parse-json'; +import { ValidationError } from '.'; type InnerType = T extends Array ? I : never; @@ -36,9 +38,7 @@ export type RejectorFn = ( array: readonly any[], ) => boolean; -export function create( - type?: ISchema, -) { +export function create(type?: ISchema) { return new ArraySchema(type as any); } @@ -89,28 +89,22 @@ export default class ArraySchema< protected _validate( _value: any, options: InternalOptions = {}, - callback: Callback, + + panic: (err: Error, value: unknown) => void, + callback: (err: ValidationError[], value: unknown) => void, ) { - let errors = [] as ValidationError[]; - let sync = options.sync; - let path = options.path; + // let sync = options.sync; + // let path = options.path; let innerType = this.innerType; - let endEarly = options.abortEarly ?? this.spec.abortEarly; + // let endEarly = options.abortEarly ?? this.spec.abortEarly; let recursive = options.recursive ?? this.spec.recursive; let originalValue = options.originalValue != null ? options.originalValue : _value; - super._validate(_value, options, (err, value) => { - if (err) { - if (!ValidationError.isError(err) || endEarly) { - return void callback(err, value); - } - errors.push(err); - } - + super._validate(_value, options, panic, (arrayErrors, value) => { if (!recursive || !innerType || !this._typeCheck(value)) { - callback(errors[0] || null, value); + callback(arrayErrors, value); return; } @@ -122,29 +116,24 @@ export default class ArraySchema< let item = value[idx]; let path = `${options.path || ''}[${idx}]`; - // object._validate note for isStrict explanation - let innerOptions = { + tests[idx] = innerType!.asTest(item, { ...options, path, - strict: true, parent: value, + // FIXME index: idx, originalValue: originalValue[idx], - }; - - tests[idx] = (_, cb) => innerType!.validate(item, innerOptions, cb); + }); } - runTests( + this.runTests( { - sync, - path, value, - errors, - endEarly, tests, }, - callback, + panic, + (innerTypeErrors) => + callback(innerTypeErrors.concat(arrayErrors), value), ); }); } diff --git a/src/object.ts b/src/object.ts index 894c98f17..0ed868728 100644 --- a/src/object.ts +++ b/src/object.ts @@ -12,11 +12,9 @@ import { import { object as locale } from './locale'; import sortFields from './util/sortFields'; import sortByKeyOrder from './util/sortByKeyOrder'; -import runTests from './util/runTests'; -import { InternalOptions, Callback, Maybe, Message } from './types'; -import ValidationError from './ValidationError'; +import { InternalOptions, Maybe, Message } from './types'; import type { Defined, Thunk, NotNull, _ } from './util/types'; -import type Reference from './Reference'; +import Reference from './Reference'; import Schema, { SchemaObjectDescription, SchemaSpec } from './schema'; import { ResolveOptions } from './Condition'; import type { @@ -30,6 +28,8 @@ import type { TypeFromShape, } from './util/objectTypes'; import parseJson from './util/parseJson'; +import type { Test } from './util/createValidation'; +import type ValidationError from './ValidationError'; export type { AnyObject }; @@ -205,14 +205,12 @@ export default class ObjectSchema< protected _validate( _value: any, opts: InternalOptions = {}, - callback: Callback, + panic: (err: Error, value: unknown) => void, + next: (err: ValidationError[], value: unknown) => void, ) { - let errors = [] as ValidationError[]; let { - sync, from = [], originalValue = _value, - abortEarly = this.spec.abortEarly, recursive = this.spec.recursive, } = opts; @@ -224,64 +222,41 @@ export default class ObjectSchema< opts.originalValue = originalValue; opts.from = from; - super._validate(_value, opts, (err, value) => { - if (err) { - if (!ValidationError.isError(err) || abortEarly) { - return void callback(err, value); - } - errors.push(err); - } - + super._validate(_value, opts, panic, (objectErrors, value) => { if (!recursive || !isObject(value)) { - callback(errors[0] || null, value); + next(objectErrors, value); return; } originalValue = originalValue || value; - let tests = this._nodes.map((key) => (__: any, cb: Callback) => { + let tests = [] as Test[]; + for (let key of this._nodes) { + let field = this.fields[key]; + + if (!field || Reference.isRef(field)) { + continue; + } + let path = key.indexOf('.') === -1 ? (opts.path ? `${opts.path}.` : '') + key : `${opts.path || ''}["${key}"]`; - let field = this.fields[key]; - - if (field && 'validate' in field) { - field.validate( - value[key], - { - ...opts, - // @ts-ignore - path, - from, - // inner fields are always strict: - // 1. this isn't strict so the casting will also have cast inner values - // 2. this is strict in which case the nested values weren't cast either - strict: true, - parent: value, - originalValue: originalValue[key], - }, - cb, - ); - return; - } + tests.push( + field.asTest(value[key], { + ...opts, + path, + from, + parent: value, + originalValue: originalValue[key], + }), + ); + } - cb(null); + this.runTests({ tests, value }, panic, (fieldErrors) => { + next(fieldErrors.sort(this._sortErrors).concat(objectErrors), value); }); - - runTests( - { - sync, - tests, - value, - errors, - endEarly: abortEarly, - sort: this._sortErrors, - path: opts.path, - }, - callback, - ); }); } diff --git a/src/schema.ts b/src/schema.ts index aa538f7a2..f028d8381 100644 --- a/src/schema.ts +++ b/src/schema.ts @@ -7,11 +7,13 @@ import Condition, { ConditionConfig, ResolveOptions, } from './Condition'; -import runTests from './util/runTests'; import createValidation, { TestFunction, Test, TestConfig, + NextCallback, + PanicCallback, + TestOptions, } from './util/createValidation'; import printValue from './util/printValue'; import Ref from './Reference'; @@ -20,7 +22,6 @@ import { ValidateOptions, TransformFunction, Message, - Callback, InternalOptions, Maybe, ExtraParams, @@ -69,6 +70,18 @@ export interface CastOptions { path?: string; } +export type RunTest = ( + opts: TestOptions, + panic: PanicCallback, + next: NextCallback, +) => void; + +export type TestRunOptions = { + tests: RunTest[]; + args?: TestOptions; + value: any; +}; + export interface SchemaRefDescription { type: 'ref'; key: string; @@ -265,7 +278,7 @@ export default abstract class Schema< // the deduping logic is consistent combined.withMutation((next) => { schema.tests.forEach((fn) => { - next.test(fn.OPTIONS); + next.test(fn.OPTIONS!); }); }); @@ -353,7 +366,8 @@ export default abstract class Schema< protected _validate( _value: any, options: InternalOptions = {}, - cb: Callback, + panic: (err: Error, value: unknown) => void, + next: (err: ValidationError[], value: unknown) => void, ): void { let { sync, @@ -361,15 +375,13 @@ export default abstract class Schema< from = [], originalValue = _value, strict = this.spec.strict, - abortEarly = this.spec.abortEarly, } = options; let value = _value; if (!strict) { - // this._validating = true; value = this._cast(value, { assert: false, ...options }); - // this._validating = false; } + // value is cast, we can check if it meets type requirements let args = { value, @@ -387,57 +399,110 @@ export default abstract class Schema< if (test) initialTests.push(test); } - runTests( + this.runTests( { args, value, - path, - sync, tests: initialTests, - endEarly: abortEarly, }, - (err) => { - if (err) return void cb(err, value); - - runTests( + panic, + (initialErrors) => { + // even if we aren't ending early we can't proceed further if the types aren't correct + if (initialErrors.length) { + return next(initialErrors, value); + } + + this.runTests( { - tests: this.tests, args, - path, - sync, value, - endEarly: abortEarly, + tests: this.tests, }, - cb, + panic, + next, ); }, ); } - // validate(value: U, options?: ValidateOptions): Promise; + /** + * Executes a set of validations, either schema, produced Tests or a nested + * schema validate result. `args` is intended for schema validation tests, but + * isn't required to allow the helper to awkwardly be used to run nested array/object + * validations. + */ + protected runTests( + options: TestRunOptions, + panic: (err: Error, value: unknown) => void, + next: (errors: ValidationError[], value: unknown) => void, + ): void { + let fired = false; + let { tests, args, value } = options; + + let panicOnce = (arg: Error) => { + if (fired) return; + fired = true; + panic(arg, value); + }; + + let nextOnce = (arg: ValidationError[]) => { + if (fired) return; + fired = true; + next(arg, value); + }; + + let count = tests.length; + let nestedErrors = [] as ValidationError[]; + + if (!count) return nextOnce([]); + + for (let i = 0; i < tests.length; i++) { + const test = tests[i]; + + test(args!, panicOnce, function finishTestRun(err) { + if (err) { + nestedErrors = nestedErrors.concat(err); + } + if (--count <= 0) { + nextOnce(nestedErrors); + } + }); + } + } + + asTest(value: any, options?: ValidateOptions): RunTest { + // Nested validations fields are always strict: + // 1. parent isn't strict so the casting will also have cast inner values + // 2. parent is strict in which case the nested values weren't cast either + const testOptions = { ...options, strict: true, value }; + + return (_: any, panic, next) => + this.resolve(testOptions)._validate(value, testOptions, panic, next); + } + validate( value: any, options?: ValidateOptions, ): Promise; - validate( - value: any, - options?: ValidateOptions, - maybeCb?: Callback, - ) { + validate(value: any, options?: ValidateOptions): any { let schema = this.resolve({ ...options, value }); - // callback case is for nested validations - return typeof maybeCb === 'function' - ? schema._validate(value, options, maybeCb) - : new Promise((resolve, reject) => - schema._validate(value, options, (err, validated) => { - if (err) reject(err); - else resolve(validated); - }), - ); + return new Promise((resolve, reject) => + schema._validate( + value, + options, + (error, parsed) => { + if (ValidationError.isError(error)) error.value = parsed; + reject(error); + }, + (errors, validated) => { + if (errors.length) reject(new ValidationError(errors!, validated)); + else resolve(validated); + }, + ), + ); } - // validateSync(value: U, options?: ValidateOptions): U; validateSync( value: any, options?: ValidateOptions, @@ -449,10 +514,18 @@ export default abstract class Schema< let schema = this.resolve({ ...options, value }); let result: any; - schema._validate(value, { ...options, sync: true }, (err, validated) => { - if (err) throw err; - result = validated; - }); + schema._validate( + value, + { ...options, sync: true }, + (error, parsed) => { + if (ValidationError.isError(error)) error.value = parsed; + throw error; + }, + (errors, validated) => { + if (errors.length) throw new ValidationError(errors!, value); + result = validated; + }, + ); return result; } @@ -549,9 +622,6 @@ export default abstract class Schema< return this.optionality(false, message); } - // nullable(message?: Message): any - // nullable(nullable: true): any - // nullable(nullable: false, message?: Message): any nullable(): any { return this.nullability(true); } @@ -578,7 +648,7 @@ export default abstract class Schema< notRequired(): any { return this.clone().withMutation((next) => { next.tests = next.tests.filter( - (test) => test.OPTIONS.name !== 'required', + (test) => test.OPTIONS!.name !== 'required', ); return next.nullable().optional(); }); @@ -647,9 +717,9 @@ export default abstract class Schema< if (opts.name) next.exclusiveTests[opts.name] = !!opts.exclusive; next.tests = next.tests.filter((fn) => { - if (fn.OPTIONS.name === opts.name) { + if (fn.OPTIONS!.name === opts.name) { if (isExclusive) return false; - if (fn.OPTIONS.test === validate.OPTIONS.test) return false; + if (fn.OPTIONS!.test === validate.OPTIONS.test) return false; } return true; }); @@ -787,7 +857,6 @@ export default abstract class Schema< return next; } - //Schema> strip(strip = true): any { let next = this.clone(); next.spec.strip = strip; @@ -811,7 +880,7 @@ export default abstract class Schema< oneOf: next._whitelist.describe(), notOneOf: next._blacklist.describe(), tests: next.tests - .map((fn) => ({ name: fn.OPTIONS.name, params: fn.OPTIONS.params })) + .map((fn) => ({ name: fn.OPTIONS!.name, params: fn.OPTIONS!.params })) .filter( (n, idx, list) => list.findIndex((c) => c.name === n.name) === idx, ), diff --git a/src/types.ts b/src/types.ts index 1020c2152..3b6b9e823 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,8 +4,6 @@ import type { ISchema } from './util/types'; export type { ISchema, AnyObject, AnySchema }; -export type Callback = (err: Error | null, value?: T) => void; - export type TransformFunction = ( this: T, value: any, @@ -40,6 +38,7 @@ export interface InternalOptions extends ValidateOptions { __validating?: boolean; originalValue?: any; + index?: number; parent?: any; path?: string; sync?: boolean; diff --git a/src/util/createValidation.ts b/src/util/createValidation.ts index e938e3bf5..d67cfec83 100644 --- a/src/util/createValidation.ts +++ b/src/util/createValidation.ts @@ -4,13 +4,18 @@ import { ValidateOptions, Message, InternalOptions, - Callback, ExtraParams, } from '../types'; import Reference from '../Reference'; import type { AnySchema } from '../schema'; import { ISchema } from './types'; +export type PanicCallback = (err: Error) => void; + +export type NextCallback = ( + err: ValidationError[] | ValidationError | null, +) => void; + export type CreateErrorOptions = { path?: string; message?: Message; @@ -53,8 +58,12 @@ export type TestConfig = { exclusive?: boolean; }; -export type Test = ((opts: TestOptions, cb: Callback) => void) & { - OPTIONS: TestConfig; +export type Test = (( + opts: TestOptions, + panic: PanicCallback, + next: NextCallback, +) => void) & { + OPTIONS?: TestConfig; }; export default function createValidation(config: { @@ -73,10 +82,11 @@ export default function createValidation(config: { sync, ...rest }: TestOptions, - cb: Callback, + panic: PanicCallback, + next: NextCallback, ) { const { name, test, params, message } = config; - let { parent, context } = options; + let { parent, context, abortEarly = rest.schema.spec.abortEarly } = options; function resolve(item: T | Reference) { return Ref.isRef(item) ? item.getValue(value, parent, context) : item; @@ -106,6 +116,8 @@ export default function createValidation(config: { return error; } + const invalid = abortEarly ? panic : next; + let ctx = { path, parent, @@ -117,23 +129,31 @@ export default function createValidation(config: { ...rest, }; + const handleResult = (validOrError: ReturnType) => { + if (ValidationError.isError(validOrError)) invalid(validOrError); + else if (!validOrError) invalid(createError()); + else next(null); + }; + + const handleError = (err: any) => { + if (ValidationError.isError(err)) invalid(err); + else panic(err); + }; + if (!sync) { try { - Promise.resolve(test.call(ctx, value, ctx)) - .then((validOrError) => { - if (ValidationError.isError(validOrError)) cb(validOrError); - else if (!validOrError) cb(createError()); - else cb(null, validOrError); - }) - .catch(cb); + Promise.resolve(test.call(ctx, value, ctx)).then( + handleResult, + handleError, + ); } catch (err: any) { - cb(err); + handleError(err); } return; } - let result; + let result: ReturnType; try { result = test.call(ctx, value, ctx); @@ -144,13 +164,11 @@ export default function createValidation(config: { ); } } catch (err: any) { - cb(err); + handleError(err); return; } - if (ValidationError.isError(result)) cb(result); - else if (!result) cb(createError()); - else cb(null, result); + handleResult(result); } validate.OPTIONS = config; diff --git a/src/util/runTests.ts b/src/util/runTests.ts deleted file mode 100644 index b065a46fa..000000000 --- a/src/util/runTests.ts +++ /dev/null @@ -1,74 +0,0 @@ -import ValidationError from '../ValidationError'; -import { TestOptions } from './createValidation'; -import { Callback } from '../types'; - -export type RunTest = (opts: TestOptions, cb: Callback) => void; - -export type TestRunOptions = { - endEarly?: boolean; - tests: RunTest[]; - args?: TestOptions; - errors?: ValidationError[]; - sort?: (a: ValidationError, b: ValidationError) => number; - path?: string; - value: any; - sync?: boolean; -}; - -const once = any>(cb: T) => { - let fired = false; - return (...args: Parameters) => { - if (fired) return; - fired = true; - cb(...args); - }; -}; - -export default function runTests(options: TestRunOptions, cb: Callback): void { - let { endEarly, tests, args, value, errors, sort, path } = options; - - let callback = once(cb); - let count = tests.length; - const nestedErrors = [] as ValidationError[]; - errors = errors ? errors : []; - - if (!count) - return errors.length - ? callback(new ValidationError(errors, value, path)) - : callback(null, value); - - for (let i = 0; i < tests.length; i++) { - const test = tests[i]; - - test(args!, function finishTestRun(err) { - if (err) { - // always return early for non validation errors - if (!ValidationError.isError(err)) { - return callback(err, value); - } - if (endEarly) { - err.value = value; - return callback(err, value); - } - nestedErrors.push(err); - } - - if (--count <= 0) { - if (nestedErrors.length) { - if (sort) nestedErrors.sort(sort); - - //show parent errors after the nested ones: name.first, name - if (errors!.length) nestedErrors.push(...errors!); - errors = nestedErrors; - } - - if (errors!.length) { - callback(new ValidationError(errors!, value, path), value); - return; - } - - callback(null, value); - } - }); - } -} diff --git a/src/util/sortByKeyOrder.ts b/src/util/sortByKeyOrder.ts index 94b7fbec0..84ec70242 100644 --- a/src/util/sortByKeyOrder.ts +++ b/src/util/sortByKeyOrder.ts @@ -3,12 +3,11 @@ import ValidationError from '../ValidationError'; function findIndex(arr: readonly string[], err: ValidationError) { let idx = Infinity; arr.some((key, ii) => { - if (err.path?.indexOf(key) !== -1) { + if (err.path?.includes(key)) { idx = ii; return true; } }); - return idx; } diff --git a/src/util/types.ts b/src/util/types.ts index 31361cb73..a6c9a1593 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -2,11 +2,12 @@ import type { ResolveOptions } from '../Condition'; import type { CastOptions, SchemaFieldDescription } from '../schema'; import type { AnyObject, - Callback, + InternalOptions, Optionals, Preserve, ValidateOptions, } from '../types'; +import { Test } from './createValidation'; export type Defined = T extends undefined ? never : T; @@ -19,11 +20,9 @@ export interface ISchema { __default: D; cast(value: any, options?: CastOptions): T; - validate( - value: any, - options?: ValidateOptions, - maybeCb?: Callback, - ): Promise; + validate(value: any, options?: ValidateOptions): Promise; + + asTest(value: any, options?: InternalOptions): Test; describe(options?: ResolveOptions): SchemaFieldDescription; resolve(options: ResolveOptions): ISchema; diff --git a/test-setup.js b/test-setup.js index 690775136..20b9119fe 100644 --- a/test-setup.js +++ b/test-setup.js @@ -5,34 +5,18 @@ global.TestHelpers = require('./test/helpers'); if (global.YUP_USE_SYNC) { const { Schema } = require('./src'); // eslint-disable-line global-require - const { validate } = Schema.prototype; - - Schema.prototype.validate = function (value, options = {}, maybeCb) { - let run = false; - - options.sync = true; - - if (maybeCb) { - return validate.call(this, value, options, (...args) => { - if (run) { - return maybeCb(new Error('Did not execute synchronously')); - } - - maybeCb(...args); - }); - } - - const result = new SynchronousPromise((resolve, reject) => { - validate.call(this, value, options, (err, value) => { - if (run) { - throw new Error('Did not execute synchronously'); - } - if (err) reject(err); - else resolve(value); - }); + const { validateSync } = Schema.prototype; + + Schema.prototype.validate = function (value, options = {}) { + return new SynchronousPromise((resolve, reject) => { + let result; + try { + result = validateSync.call(this, value, options); + } catch (err) { + reject(err); + } + + resolve(result); }); - - run = true; - return result; }; } diff --git a/test/object.ts b/test/object.ts index 3a2147f89..ba48d54b6 100644 --- a/test/object.ts +++ b/test/object.ts @@ -578,7 +578,7 @@ describe('Object types', () => { expect(inst.validate({ nest: { str: '' } })).rejects.toEqual( expect.objectContaining({ value: { nest: { str: '' } }, - path: 'nest', + // path: 'nest', errors: ['oops'], }), ), @@ -594,6 +594,47 @@ describe('Object types', () => { ]); }); + it('should flatten validation errors with abortEarly=false', async () => { + let inst = object({ + str: string().required(), + nest: object({ + innerStr: string().required(), + num: number().moreThan(5), + other: number().test('nested', 'invalid', () => { + string().email().min(3).validateSync('f', { abortEarly: false }); + return true; + }), + }).test('name', 'oops', () => false), + }); + + const error = await inst + .validate( + { str: null, nest: { num: 2, str: undefined } }, + { abortEarly: false }, + ) + .catch((e) => e); + + expect(error.inner).toMatchInlineSnapshot(` + Array [ + [ValidationError: str is a required field], + [ValidationError: nest.innerStr is a required field], + [ValidationError: nest.num must be greater than 5], + [ValidationError: oops], + [ValidationError: this must be a valid email], + [ValidationError: this must be at least 3 characters], + ] + `); + + expect(error.errors).toEqual([ + 'str is a required field', + 'nest.innerStr is a required field', + 'nest.num must be greater than 5', + 'oops', + 'this must be a valid email', + 'this must be at least 3 characters', + ]); + }); + it('should sort errors by insertion order', async () => { let inst = object({ // use `when` to make sure it is validated second