diff --git a/.eslintrc b/.eslintrc index 0d3777748..f3d348f04 100644 --- a/.eslintrc +++ b/.eslintrc @@ -3,21 +3,24 @@ "extends": "eslint:recommended", "globals": { "sinon": true, - "expect": true + "expect": true, + "TestHelpers": true, }, "env": { "browser": true, "node": true, - "mocha": true + "mocha": true, }, "rules": { + "eqeqeq": 0, + "no-loop-func": 0, + "comma-dangle": 0, "no-eval": 2, "strict": 0, "eol-last": 0, "dot-notation": [2, { "allowKeywords": true }], "semi": [0, "never"], "curly": 0, - "eqeqeq": [2, "allow-null"], "no-undef": 2, "quotes": [2, "single", "avoid-escape"], "no-trailing-spaces": 0, diff --git a/README.md b/README.md index eb3ede62c..4f15515a7 100644 --- a/README.md +++ b/README.md @@ -633,7 +633,7 @@ transforms are run before validations and only applied when `strict` is `true`. Transformations are useful for arbitrarily altering how the object is cast, __however, you should take care not to mutate the passed in value.__ Transforms are run sequentially so each `value` represents the -current state of the cast, you can use the `orignalValue` param if you need to work on the raw initial value. +current state of the cast, you can use the `originalValue` param if you need to work on the raw initial value. ```javascript var schema = yup.string().transform(function(currentValue, originalvalue){ @@ -671,6 +671,11 @@ var schema = yup.string(); schema.isValid('hello') //=> true ``` +By default, the `cast` logic of `string` is to call `toString` on the value if it exists. +empty values are not coerced (use `ensure()` to coerce empty values to empty strings). + +Failed casts return the input value. + #### `string.required(message: ?string): Schema` The same as the `mixed()` schema required, except that empty strings are also considered 'missing' values. @@ -702,6 +707,10 @@ Validates the value as an email address via a regex. Validates the value as a valid URL via a regex. +#### `string.ensure(): Schema` + +Transforms `undefined` and `null` values to an empty string along with +setting the `default` to an empty string. #### `string.trim(message: ?string): Schema` @@ -710,11 +719,13 @@ Transforms string values by removing leading and trailing whitespace. If #### `string.lowercase(message: ?string): Schema` -Transforms the string value to lowercase. If `strict()` is set it will only validate that the value is lowercase. +Transforms the string value to lowercase. If `strict()` is set it +will only validate that the value is lowercase. #### `string.uppercase(message: ?string): Schema` -Transforms the string value to uppercase. If `strict()` is set it will only validate that the value is uppercase. +Transforms the string value to uppercase. If `strict()` is set it +will only validate that the value is uppercase. ### number @@ -725,6 +736,10 @@ var schema = yup.number(); schema.isValid(10) //=> true ``` +The default `cast` logic of `number` is: [`parseFloat`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/parseFloat). + +Failed casts return `NaN`. + #### `number.min(limit: number | Ref, message: ?string): Schema` Set the minimum value allowed. The `${min}` interpolation can be used in the @@ -745,13 +760,16 @@ Value must be a negative number. #### `number.integer(message: ?string): Schema` -Transformation that coerces the value into an integer via truncation -` value | 0`. If `strict()` is set it will only validate that the value is an integer. +Validates that a number is an integer. + +#### `number.truncate(): Schema` -#### `number.round(type: 'floor' | 'ceil' | 'round' = 'round'): Schema` +Transformation that coerces the value to an integer by stripping off the digits +to the right of the decimal point. -Rounds the value by the specified method (defaults to 'round'). +#### `number.round(type: 'floor' | 'ceil' | 'trunc' | 'round' = 'round'): Schema` +Adjusts the value via the specified method of `Math` (defaults to 'round'). ### boolean @@ -773,6 +791,12 @@ var schema = yup.date(); schema.isValid(new Date) //=> true ``` +The default `cast` logic of `date` is pass the value to the `Date` constructor, failing that, it will attempt +to parse the date as an ISO date string. + +Failed casts return an invalid Date. + + #### `date.min(limit: Date | string | Ref, message: ?string): Schema` Set the minimum date allowed. When a string is provided it will attempt to cast to a date first @@ -805,6 +829,10 @@ array().of(number()) array(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`; + #### `array.of(type: Schema): Schema` Specify the schema of array elements. `of()` is optional and when omitted the array schema will @@ -876,6 +904,10 @@ object({ }) ``` +The default `cast` behavior for `object` is: [`JSON.parse`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) + +Failed casts return: `null`; + #### `object.shape(fields: object, noSortEdges: ?Array<[string, string]>): Schema` @@ -909,6 +941,8 @@ Transforms all object keys to camelCase Transforms all object keys to CONSTANT_CASE. + + ## Extending Schema Types The simplest way to extend an existing type is just to cache a configured schema and use that through your application. diff --git a/package.json b/package.json index 2b398b878..0f3f802b2 100644 --- a/package.json +++ b/package.json @@ -59,8 +59,10 @@ "dependencies": { "case": "^1.2.1", "fn-name": "~1.0.1", + "lodash": "^4.13.1", "property-expr": "^1.2.0", "toposort": "^0.2.10", + "type-name": "^2.0.1", "universal-promise": "^1.0.1" }, "release-script": { diff --git a/src/util/condition.js b/src/Condition.js similarity index 81% rename from src/util/condition.js rename to src/Condition.js index de701a95a..60a147e5e 100644 --- a/src/util/condition.js +++ b/src/Condition.js @@ -1,7 +1,5 @@ -'use strict'; -var { transform, has, isSchema } = require('./_') - -module.exports = Conditional +import has from 'lodash/has'; +import isSchema from './util/isSchema'; class Conditional { @@ -18,8 +16,9 @@ class Conditional { throw new TypeError('`is:` is required for `when()` conditions') if (!options.then && !options.otherwise) - throw new TypeError('either `then:` or `otherwise:` is required for `when()` conditions') - + throw new TypeError( + 'either `then:` or `otherwise:` is required for `when()` conditions' + ) let isFn = typeof is === 'function' ? is : ((...values) => values.every(value => value === is)) @@ -47,4 +46,4 @@ class Conditional { } } -module.exports = Conditional; +export default Conditional; diff --git a/src/util/lazy.js b/src/Lazy.js similarity index 93% rename from src/util/lazy.js rename to src/Lazy.js index d9ed062d0..8b4fd6397 100644 --- a/src/util/lazy.js +++ b/src/Lazy.js @@ -1,4 +1,4 @@ -var { isSchema } = require('./_') +import isSchema from './util/isSchema'; class Lazy { constructor(mapFn) { diff --git a/src/util/reference.js b/src/Reference.js similarity index 88% rename from src/util/reference.js rename to src/Reference.js index 2c3394d12..6db001c07 100644 --- a/src/util/reference.js +++ b/src/Reference.js @@ -1,13 +1,13 @@ -var getter = require('property-expr').getter +import { getter } from 'property-expr'; let validateName = d => { if (typeof d !== 'string') throw new TypeError('ref\'s must be strings, got: ' + d) } -export default class Ref { +export default class Reference { static isRef(value) { - return !!(value && (value.__isYupRef || value instanceof Ref)) + return !!(value && (value.__isYupRef || value instanceof Reference)) } constructor(key, mapFn, options = {}) { @@ -40,4 +40,4 @@ export default class Ref { } } -Ref.prototype.__isYupRef = true +Reference.prototype.__isYupRef = true diff --git a/src/util/validation-error.js b/src/ValidationError.js similarity index 96% rename from src/util/validation-error.js rename to src/ValidationError.js index f4972c554..933f0266c 100644 --- a/src/util/validation-error.js +++ b/src/ValidationError.js @@ -1,5 +1,5 @@ -'use strict'; -var strReg = /\$\{\s*(\w+)\s*\}/g; + +let strReg = /\$\{\s*(\w+)\s*\}/g; let replace = str => params => str.replace(strReg, (_, key) => params[key] || '') diff --git a/src/array.js b/src/array.js index 785675cb3..7aa0600c0 100644 --- a/src/array.js +++ b/src/array.js @@ -1,18 +1,13 @@ -'use strict'; -var MixedSchema = require('./mixed') - , Promise = require('universal-promise') - , isAbsent = require('./util/isAbsent') - , { mixed, array: locale } = require('./locale.js') - , { inherits, collectErrors } = require('./util/_'); - -let scopeError = value => err => { - err.value = value - throw err -} +import inherits from './util/inherits'; +import isAbsent from './util/isAbsent'; +import MixedSchema from './mixed'; +import { mixed, array as locale } from './locale.js'; +import runValidations, { propagateErrors } from './util/runValidations'; + let hasLength = value => !isAbsent(value) && value.length > 0; -module.exports = ArraySchema +export default ArraySchema function ArraySchema(type) { if (!(this instanceof ArraySchema)) @@ -54,29 +49,31 @@ inherits(ArraySchema, MixedSchema, { }, _validate(_value, options = {}) { - var errors = [] - , subType, endEarly, recursive; - - subType = this._subType - endEarly = this._option('abortEarly', options) - recursive = this._option('recursive', options) - - return MixedSchema.prototype._validate.call(this, _value, options) - .catch(endEarly ? null : err => { - errors = err - return err.value - }) + let errors = [] + let path = options.path + let subType = this._subType + let endEarly = this._option('abortEarly', options) + let recursive = this._option('recursive', options) + + return MixedSchema.prototype._validate + .call(this, _value, options) + .catch(propagateErrors(endEarly, errors)) .then((value) => { if (!recursive || !subType || !this._typeCheck(value) ) { if (errors.length) throw errors[0] return value } - let validations = value.map((item, key) => { - var path = (options.path || '') + '[' + key + ']' + let validations = value.map((item, idx) => { + var path = (options.path || '') + '[' + idx + ']' // object._validate note for isStrict explanation - var innerOptions = { ...options, path, key, strict: true, parent: value }; + var innerOptions = { + ...options, + path, + strict: true, + parent: value + }; if (subType.validate) return subType.validate(item, innerOptions) @@ -84,11 +81,13 @@ inherits(ArraySchema, MixedSchema, { return true }) - validations = endEarly - ? Promise.all(validations).catch(scopeError(value)) - : collectErrors({ validations, value, errors, path: options.path }) - - return validations.then(() => value) + return runValidations({ + path, + value, + errors, + endEarly, + validations + }) }) }, @@ -137,8 +136,8 @@ inherits(ArraySchema, MixedSchema, { ensure() { return this - .default([]) - .transform(val => val == null ? [] : [].concat(val)) + .default(() => []) + .transform(val => val === null ? [] : [].concat(val)) }, compact(rejector){ diff --git a/src/boolean.js b/src/boolean.js index d117da104..638636a3f 100644 --- a/src/boolean.js +++ b/src/boolean.js @@ -1,8 +1,7 @@ -'use strict'; -var MixedSchema = require('./mixed') - , inherits = require('./util/_').inherits; +import inherits from './util/inherits'; +import MixedSchema from './mixed'; -module.exports = BooleanSchema +export default BooleanSchema function BooleanSchema(){ if (!(this instanceof BooleanSchema)) @@ -12,15 +11,20 @@ function BooleanSchema(){ this.withMutation(() => { this.transform(function(value) { - if ( this.isType(value) ) return value - return (/true|1/i).test(value) + if (!this.isType(value)) { + if (/^(true|1)$/i.test(value)) return true + if (/^(false|0)$/i.test(value)) return false + } + return value }) }) } inherits(BooleanSchema, MixedSchema, { - _typeCheck(v){ - return (typeof v === 'boolean') || (typeof v === 'object' && v instanceof Boolean) + _typeCheck(v) { + if (v instanceof Boolean) v = v.valueOf(); + + return typeof v === 'boolean' } }) diff --git a/src/date.js b/src/date.js index b0bac9872..d15d3b7b3 100644 --- a/src/date.js +++ b/src/date.js @@ -1,14 +1,15 @@ -'use strict'; -var MixedSchema = require('./mixed') - , isoParse = require('./util/isodate') - , locale = require('./locale.js').date - , isAbsent = require('./util/isAbsent') - , Ref = require('./util/reference') - , { isDate, inherits } = require('./util/_'); +import MixedSchema from './mixed'; +import inherits from './util/inherits'; +import isoParse from './util/isodate'; +import { date as locale } from './locale.js'; +import isAbsent from './util/isAbsent'; +import Ref from './Reference'; let invalidDate = new Date('') -module.exports = DateSchema +let isDate = obj => Object.prototype.toString.call(obj) === '[object Date]' + +export default DateSchema function DateSchema(){ if ( !(this instanceof DateSchema)) return new DateSchema() diff --git a/src/index.js b/src/index.js index e43ea5db9..f891619fb 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,10 @@ -'use strict'; -var mixed = require('./mixed') - , bool = require('./boolean') - , Ref = require('./util/reference') - , Lazy = require('./util/lazy'); +import mixed from './mixed'; +import bool from './boolean'; +import Ref from './Reference'; +import Lazy from './Lazy'; +import isSchema from './util/isSchema'; -var isSchema = schema => schema && !!schema.__isYupSchema__; - -module.exports = { +export default { mixed: mixed, string: require('./string'), number: require('./number'), @@ -18,7 +16,7 @@ module.exports = { reach: require('./util/reach'), - ValidationError: require('./util/validation-error'), + ValidationError: require('./ValidationError'), ref: (key, options) => new Ref(key, options), lazy: (fn) => new Lazy(fn), diff --git a/src/locale.js b/src/locale.js index 44a04f699..2a413618f 100644 --- a/src/locale.js +++ b/src/locale.js @@ -1,48 +1,55 @@ +export let mixed = { + default: '${path} is invalid', + notType: '${path} must be a `${type}` type, got: "${value}" instead', + required: '${path} is a required field', + oneOf: '${path} must be one the following values: ${values}', + notOneOf: '${path} must not be one the following values: ${values}' +} + +export let string = { + required: '${path} is a required field', + min: '${path} must be at least ${min} characters', + max: '${path} must be at most ${max} characters', + matches: '${path} must match the following: "${regex}"', + email: '${path} must be a valid email', + url: '${path} must be a valid URL', + trim: '${path} must be a trimmed string', + lowercase: '${path} must be a lowercase string', + uppercase: '${path} must be a upper case string' +} + +export let number = { + min: '${path} must be greater than or equal to ${min}', + max: '${path} must be less than or equal to ${max}', + positive: '${path} must be a positive number', + negative: '${path} must be a negative number', + integer: '${path} must be an integer', +}; + +export let date = { + min: '${path} field must be later than ${min}', + max: '${path} field must be at earlier than ${max}', +} + +export let boolean = {}; + +export let object = { + noUnknown: '${path} field cannot have keys not specified in the object shape', +} + +export let array = { + required: '${path} is a required field', + min: '${path} field must have at least ${min} items', + max: '${path} field must have less than ${max} items', +} -module.exports = { - mixed: { - default: '${path} is invalid', - notType: '${path} (value: `${value}`) must be a `${type}` type', - required: '${path} is a required field', - oneOf: '${path} must be one the following values: ${values}', - notOneOf: '${path} must not be one the following values: ${values}' - }, - - string: { - required: '${path} is a required field', - min: '${path} must be at least ${min} characters', - max: '${path} must be at most ${max} characters', - matches: '${path} must match the following: "${regex}"', - email: '${path} must be a valid email', - url: '${path} must be a valid URL', - trim: '${path} must be a trimmed string', - lowercase: '${path} must be a lowercase string', - uppercase: '${path} must be a upper case string' - }, - - number: { - min: '${path} must be greater than or equal to ${min}', - max: '${path} must be less than or equal to ${max}', - positive: '${path} must be a positive number', - negative: '${path} must be a negative number', - integer: '${path} must be an integer' - }, - - date: { - min: '${path} field must be later than ${min}', - max: '${path} field must be at earlier than ${max}' - }, - - boolean: {}, - - object: { - noUnknown: '${path} field cannot have keys not specified in the object shape', - }, - - array: { - required: '${path} is a required field', - min: '${path} field must have at least ${min} items', - max: '${path} field must have less than ${max} items' - } +export default { + mixed, + string, + number, + date, + object, + array, + boolean, } diff --git a/src/mixed.js b/src/mixed.js index cb9173e69..f99cdfa7d 100644 --- a/src/mixed.js +++ b/src/mixed.js @@ -1,24 +1,19 @@ -'use strict'; - -var Promise = require('universal-promise') - , Condition = require('./util/condition') - , locale = require('./locale.js').mixed - , _ = require('./util/_') - , isAbsent = require('./util/isAbsent') - , cloneDeep = require('./util/clone') - , createValidation = require('./util/createValidation') - , BadSet = require('./util/set') - , Ref = require('./util/reference'); +import typeOf from 'type-name'; +import has from 'lodash/has'; + +import { mixed as locale } from './locale'; +import Condition from './Condition'; +import runValidations from './util/runValidations'; +import merge from './util/merge'; +import isAbsent from './util/isAbsent'; +import cloneDeep from './util/clone'; +import createValidation from './util/createValidation'; +import BadSet from './util/set'; +import Ref from './Reference'; let notEmpty = value => !isAbsent(value); -function runValidations(validations, endEarly, value, path) { - return endEarly - ? Promise.all(validations) - : _.collectErrors({ validations, value, path }) -} - function extractTestParams(name, message, test, useCallback) { var opts = name; @@ -56,7 +51,7 @@ function SchemaType(options = {}){ this.typeError(locale.notType) }) - if (_.has(options, 'default')) + if (has(options, 'default')) this._defaultDefault = options.default this._type = options.type || 'mixed' @@ -104,10 +99,10 @@ SchemaType.prototype = { if (schema._type !== this._type && this._type !== 'mixed') throw new TypeError(`You cannot \`concat()\` schema's of different types: ${this._type} and ${schema._type}`) var cloned = this.clone() - var next = _.merge(this.clone(), schema.clone()) + var next = merge(this.clone(), schema.clone()) // undefined isn't merged over, but is a valid value for default - if (schema._default === undefined && _.has(this, '_default')) + if (schema._default === undefined && has(this, '_default')) next._default = schema._default next.tests = cloned.tests; @@ -141,7 +136,16 @@ SchemaType.prototype = { cast(value, opts = {}) { let schema = this.resolve(opts) - return schema._cast(value, opts) + let result = schema._cast(value, opts); + + if (opts.assert !== false && !this.isType(result)) { + throw new TypeError( + `Expected ${opts.path || 'field'} to be type: "${this._type}". ` + + `Got "${typeOf(value)}" instead.` + ); + } + + return result; }, _cast(_value) { @@ -149,7 +153,7 @@ SchemaType.prototype = { : this.transforms.reduce( (value, transform) => transform.call(this, value, _value), _value) - if (value === undefined && (_.has(this, '_default'))) { + if (value === undefined && (has(this, '_default'))) { value = this.default() } @@ -178,7 +182,7 @@ SchemaType.prototype = { let label = this._label if (!isStrict) { - value = this._cast(value, options, options) + value = this._cast(value, { assert: false, ...options }) } // value is cast, we can check if it meets type requirements let validationParams = { value, path, schema: this, options, label } @@ -193,14 +197,13 @@ SchemaType.prototype = { if (this._blacklistError) initialTests.push(this._blacklistError(validationParams)); - return runValidations(initialTests, endEarly, value, path) - .then(() => runValidations( - this.tests.map(fn => fn(validationParams)) - , endEarly - , value - , path - )) - .then(() => value) + return runValidations({ validations: initialTests, endEarly, value, path }) + .then(value => runValidations({ + path, + value, + endEarly, + validations: this.tests.map(fn => fn(validationParams)), + })) }, @@ -225,7 +228,7 @@ SchemaType.prototype = { default(def) { if (arguments.length === 0) { - var dflt = _.has(this, '_default') ? this._default : this._defaultDefault + var dflt = has(this, '_default') ? this._default : this._defaultDefault return typeof dflt === 'function' ? dflt.call(this) : cloneDeep(dflt) } @@ -400,7 +403,7 @@ SchemaType.prototype = { }, _option(key, overrides){ - return _.has(overrides, key) + return has(overrides, key) ? overrides[key] : this._options[key] }, @@ -417,26 +420,22 @@ SchemaType.prototype = { } -var aliases = { +let aliases = { oneOf: ['equals', 'is'], notOneOf: ['not', 'nope'] } - -for (var method in aliases) if ( _.has(aliases, method) ) - aliases[method].forEach( - alias => SchemaType.prototype[alias] = SchemaType.prototype[method]) //eslint-disable-line no-loop-func - +Object.keys(aliases).forEach(method => { + aliases[method].forEach(alias => + SchemaType.prototype[alias] = SchemaType.prototype[method] + ) +}) function nodeify(promise, cb){ if(typeof cb !== 'function') return promise - promise.then(val => cb(null, val), err => cb(err)) + promise.then( + val => cb(null, val), + err => cb(err) + ) } - -// [{ value, exclude }] - -// values.every(({ value, exclude }) => { -// var isEql = eql(value, otherval) -// return (exclude && !isEql) || isEql -// }) diff --git a/src/number.js b/src/number.js index d6570b91c..a49a09c98 100644 --- a/src/number.js +++ b/src/number.js @@ -1,36 +1,39 @@ -'use strict'; -var SchemaObject = require('./mixed') - , locale = require('./locale.js').number - , isAbsent = require('./util/isAbsent') - , { isDate, inherits } = require('./util/_'); +import inherits from './util/inherits'; +import MixedSchema from './mixed'; +import { number as locale} from './locale.js'; +import isAbsent from './util/isAbsent'; module.exports = NumberSchema +let isNaN = value => value != +value + let isInteger = val => isAbsent(val) || val === (val | 0) -function NumberSchema(){ +function NumberSchema() { if ( !(this instanceof NumberSchema)) return new NumberSchema() - SchemaObject.call(this, { type: 'number' }) + MixedSchema.call(this, { type: 'number' }) this.withMutation(() => { this.transform(function(value) { if (this.isType(value)) return value - if (typeof value === 'boolean') return value ? 1 : 0 - return isDate(value) ? +value : parseFloat(value) + let parsed = parseFloat(value); + if (this.isType(parsed)) return parsed + + return NaN; }) }) } -inherits(NumberSchema, SchemaObject, { +inherits(NumberSchema, MixedSchema, { - _typeCheck(v) { - if (typeof v === 'number' && !(v !== +v)) return true - if (v instanceof Number && !isNaN(+v)) return true + _typeCheck(value) { + if (value instanceof Number) + value = value.valueOf(); - return false + return typeof value === 'number' && !isNaN(value) }, min(min, msg) { diff --git a/src/object.js b/src/object.js index c809308e3..d80cfc6ad 100644 --- a/src/object.js +++ b/src/object.js @@ -1,42 +1,46 @@ -'use strict'; -var MixedSchema = require('./mixed') - , Promise = require('universal-promise') - , toposort = require('toposort') - , locale = require('./locale.js').object - , split = require('property-expr').split - , Ref = require('./util/reference') - , c = require('case') - , sortByFields = require('./util/sortByFields') - , { - isObject - , transform - , inherits - , collectErrors - , isSchema, has } = require('./util/_'); - - -c.type('altCamel', function(str) { - let result = c.camel(str) - , idx = str.search(/[^_]/) +import changeCase from 'case'; +import has from 'lodash/has'; +import omit from 'lodash/omit'; +import mapKeys from 'lodash/mapKeys'; +import transform from 'lodash/transform'; - return idx === 0 ? result : (str.substr(0, idx) + result) -}) +import MixedSchema from './mixed'; +import { object as locale } from './locale.js'; +import sortFields from './util/sortFields'; +import sortByKeyOrder from './util/sortByKeyOrder'; +import inherits from './util/inherits'; +import runValidations, { propagateErrors } from './util/runValidations'; -let scopeError = value => err => { - err.value = value - throw err - } +let isObject = obj => Object.prototype.toString.call(obj) === '[object Object]'; +function unknown(ctx, value) { + var known = Object.keys(ctx.fields) + return Object.keys(value) + .filter(key => known.indexOf(key) === -1) +} + +/** + * maintain "private" fields + * `"__FOO_BAR"` becomes `"__fooBar"` not `"fooBar"` + */ +function camelize(str) { + let result = changeCase.camel(str) + , idx = str.search(/[^_]/) + + return idx === 0 ? result : (str.substr(0, idx) + result) +} module.exports = ObjectSchema function ObjectSchema(spec) { - if ( !(this instanceof ObjectSchema)) - return new ObjectSchema(spec) + if (!(this instanceof ObjectSchema)) + return new ObjectSchema(spec) MixedSchema.call(this, { type: 'object', default() { var dft = transform(this._nodes, (obj, key) => { - obj[key] = this.fields[key].default ? this.fields[key].default() : undefined + obj[key] = this.fields[key].default + ? this.fields[key].default() + : undefined }, {}) return Object.keys(dft).length === 0 ? undefined : dft @@ -60,8 +64,9 @@ function ObjectSchema(spec) { return null }) - if (spec) + if (spec) { this.shape(spec); + } }) } @@ -86,15 +91,18 @@ inherits(ObjectSchema, MixedSchema, { , extra = Object.keys(value).filter(v => this._nodes.indexOf(v) === -1) , props = this._nodes.concat(extra); - let innerOptions = { ...opts, parent: {}, __validating: false }; + let innerOptions = { + ...opts, + parent: {}, // is filled during the transform below + __validating: false, + }; - value = transform(props, function(obj, prop) { + value = transform(props, (obj, prop) => { let field = fields[prop] let exists = has(value, prop); if (field) { let fieldValue; - let strict = field._options && field._options.strict; if (field._strip === true) @@ -126,20 +134,17 @@ inherits(ObjectSchema, MixedSchema, { return MixedSchema.prototype._validate .call(this, _value, opts) - .catch(endEarly ? null : err => { - errors.push(err) - return err.value - }) + .catch(propagateErrors(endEarly, errors)) .then(value => { if (!recursive || !isObject(value)) { // only iterate though actual objects if (errors.length) throw errors[0] return value } - let validations = this._nodes.map((key) => { + let validations = this._nodes.map(key => { var path = (opts.path ? (opts.path + '.') : '') + key , field = this.fields[key] - , innerOptions = { ...opts, key, path, parent: value }; + , innerOptions = { ...opts, path, parent: value }; if (field) { // inner fields are always strict: @@ -154,14 +159,14 @@ inherits(ObjectSchema, MixedSchema, { return true }) - validations = endEarly - ? Promise.all(validations).catch(scopeError(value)) - : collectErrors({ validations, value, errors, - path: opts.path, - sort: sortByFields(this) - }) - - return validations.then(() => value) + return runValidations({ + validations, + value, + errors, + endEarly, + path: opts.path, + sort: sortByKeyOrder(this.fields) + }) }) }, @@ -182,9 +187,11 @@ inherits(ObjectSchema, MixedSchema, { next.fields = fields - if (excludes.length) - next._excludedEdges = next._excludedEdges.concat( - excludes.map(v => `${v[0]}-${v[1]}`)) // 'node-othernode' + if (excludes.length) { + let keys = excludes.map(([first, second]) => `${first}-${second}`); + + next._excludedEdges = next._excludedEdges.concat(keys) + } next._nodes = sortFields(fields, next._excludedEdges) @@ -192,17 +199,18 @@ inherits(ObjectSchema, MixedSchema, { }, from(from, to, alias) { - return this.transform( obj => { + return this.transform(obj => { var newObj = obj; if (obj == null) return obj if (has(obj, from)) { - newObj = transform(obj, (o, val, key) => key !== from && (o[key] = val), {}) + newObj = omit(obj, from); newObj[to] = obj[from] - if(alias) newObj[from] = obj[from] + if (alias) + newObj[from] = obj[from] } return newObj @@ -218,7 +226,11 @@ inherits(ObjectSchema, MixedSchema, { exclusive: true, message: message, test(value) { - return value == null || !noAllow || unknown(this.schema, value).length === 0 + return ( + value == null || + !noAllow || + unknown(this.schema, value).length === 0 + ) } }) @@ -228,59 +240,18 @@ inherits(ObjectSchema, MixedSchema, { return next }, - camelcase(){ - return this.transform(obj => obj == null ? obj - : transform(obj, (newobj, val, key ) => newobj[c.altCamel(key)] = val)) + camelcase() { + return this.transform(obj => + obj && mapKeys(obj, (_, key) => camelize(key)) + ) }, - constantcase(){ - return this.transform( obj => obj == null ? obj - : transform(obj, (newobj, val, key ) => newobj[c.constant(key)] = val)) - } + constantcase() { + return this.transform(obj => + obj && mapKeys(obj, (_, key) => changeCase.constant(key)) + ) + }, }) -function unknown(ctx, value) { - var known = Object.keys(ctx.fields) - return Object.keys(value) - .filter(key => known.indexOf(key) === -1) -} - -// ugly optimization avoiding a clone. clears default for recursive -// cast and resets it below; -// function tempClearDefault(schema, fn) { -// let hasDflt = has(schema, '_default') -// , dflt = schema._default; -// -// fn(schema) -// -// if (hasDflt) schema.default(dflt) -// else delete schema._default -// } - -function sortFields(fields, excludes = []){ - var edges = [], nodes = [] - - for (var key in fields) if (has(fields, key)) { - let value = fields[key]; - - if (!~nodes.indexOf(key)) - nodes.push(key) - - let addNode = depPath => { //eslint-disable-line no-loop-func - var node = split(depPath)[0] - - if (!~nodes.indexOf(node)) - nodes.push(node) - - if (!~excludes.indexOf(`${key}-${node}`)) - edges.push([key, node]) - } - - if (Ref.isRef(value) && !value.isContext) - addNode(value.path) - else if (isSchema(value) && value._deps) - value._deps.forEach(addNode) - } - - return toposort.array(nodes, edges).reverse() -} +ObjectSchema.prototype.camelCase = ObjectSchema.prototype.camelcase; +ObjectSchema.prototype.constantCase = ObjectSchema.prototype.constantcase; diff --git a/src/string.js b/src/string.js index 549384889..d4eb0a7f4 100644 --- a/src/string.js +++ b/src/string.js @@ -1,13 +1,12 @@ -'use strict'; -var MixedSchema = require('./mixed') - , { mixed, string: locale } = require('./locale.js') - , isAbsent = require('./util/isAbsent') - , inherits = require('./util/_').inherits; +import inherits from './util/inherits'; +import MixedSchema from './mixed'; +import { mixed, string as locale } from './locale'; +import isAbsent from './util/isAbsent'; -var rEmail = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i; -var rUrl = /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i; +let rEmail = /^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))$/i; +let rUrl = /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i; -let hasLength = value => !isAbsent(value) && value.length > 0; +let hasLength = value => isAbsent(value) || value.length > 0; let isTrimmed = value => isAbsent(value) || value === value.trim() module.exports = StringSchema; @@ -21,8 +20,9 @@ function StringSchema(){ this.withMutation(() => { this.transform(function(value) { if (this.isType(value)) return value - return value == null ? '' - : value.toString ? value.toString() : '' + value + return value != null && value.toString + ? value.toString() + : value }) }) } @@ -30,11 +30,15 @@ function StringSchema(){ inherits(StringSchema, MixedSchema, { _typeCheck(value) { - return (typeof value === 'string') || (typeof value === 'object' && value instanceof String) + if (value instanceof String) + value = value.valueOf(); + + return typeof value === 'string' }, required(msg) { - var next = MixedSchema.prototype.required.call(this, msg || mixed.required ) + var next = MixedSchema.prototype + .required.call(this, msg || mixed.required) return next.test( 'required' diff --git a/src/util/_.js b/src/util/_.js deleted file mode 100644 index 5a9df9937..000000000 --- a/src/util/_.js +++ /dev/null @@ -1,127 +0,0 @@ -var Promise = require('universal-promise') - , ValidationError = require('./validation-error'); - -let toString = Object.prototype.toString - -let isObject = obj => obj && toString.call(obj) === '[object Object]'; - -let isPlainObject = obj => isObject(obj) && Object.getPrototypeOf(obj) === Object.prototype; - -let isDate = obj => Object.prototype.toString.call(obj) === '[object Date]' - -let isSchema = obj => obj && obj.__isYupSchema__ - -function settled(promises){ - let settle = promise => promise.then( - value => ({ fulfilled: true, value }), - value => ({ fulfilled: false, value })) - - return Promise.all(promises.map(settle)) -} - -function collectErrors({ validations, value, path, errors = [], sort }){ - // unwrap aggregate errors - errors = errors.inner && errors.inner.length - ? errors.inner : [].concat(errors) - - return settled(validations).then(results => { - let nestedErrors = results - .filter(r => !r.fulfilled) - .reduce((arr, r) => arr.concat(r.value), []) - - if (sort) nestedErrors.sort(sort) - //show parent errors after the nested ones: name.first, name - errors = nestedErrors.concat(errors) - - if (errors.length) - throw new ValidationError(errors, value, path) - }) -} - -function assign(target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - - for (var key in source) if ( has(source, key)) - target[key] = source[key]; - } - - return target; -} - -function uniq(arr, iter){ - var seen = {} - - return arr.filter( (item, idx) => { - var key = iter(item, idx) - - if ( has(seen, key) ) return false - return seen[key] = true - }) -} - -function transform(obj, cb, seed){ - cb = cb.bind(null, seed = seed || (Array.isArray(obj) ? [] : {})) - - if( Array.isArray(obj)) - obj.forEach(cb) - else - for(var key in obj) if( has(obj, key) ) - cb(obj[key], key, obj) - - return seed -} - -function merge(target, source){ - for (var key in source) if ( has(source, key)) { - var targetVal = target[key] - , sourceVal = source[key]; - - if ( sourceVal === undefined ) - continue - - if ( isSchema(sourceVal) ) { - target[key] = isSchema(targetVal) - ? targetVal.concat(sourceVal) - : sourceVal - } - else if ( isObject(sourceVal) ) { - target[key] = isObject(targetVal) - ? merge(targetVal, sourceVal) - : sourceVal - } - else if ( Array.isArray(sourceVal) ) { - target[key] = Array.isArray(targetVal) - ? targetVal.concat(sourceVal) - : sourceVal - } - else - target[key] = source[key]; - } - - return target; -} - -function has(o, k){ - return o ? Object.prototype.hasOwnProperty.call(o, k) : false -} - -function inherits(ctor, superCtor, spec) { - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); - - assign(ctor.prototype, spec) -} - -module.exports = { - inherits, uniq, has, - assign, merge, transform, - isSchema, isObject, isPlainObject, isDate, - settled, collectErrors -} diff --git a/src/util/clone.js b/src/util/clone.js index 1e7aadcbe..54438f686 100644 --- a/src/util/clone.js +++ b/src/util/clone.js @@ -2,18 +2,15 @@ // Copyright (c) 2011, Yahoo Inc. // All rights reserved. https://github.com/hapijs/hoek/blob/master/LICENSE -var isSchema = schema => schema && !!schema.__isYupSchema__; +import isSchema from './isSchema'; -module.exports = function clone(obj, seen) { +export default function clone(obj, seen) { var isFirst = !seen , isImmutable = isSchema(obj) && !isFirst if (typeof obj !== 'object' || obj === null || isImmutable) return obj; - // if (global.REPORT_CLONE && isFirst) - // throw new Error() //console.log('clone') - seen = seen || { orig: [], copy: [] }; var lookup = seen.orig.indexOf(obj); diff --git a/src/util/createValidation.js b/src/util/createValidation.js index 0585239c4..07440d3b4 100644 --- a/src/util/createValidation.js +++ b/src/util/createValidation.js @@ -1,16 +1,12 @@ -'use strict'; -var Promise = require('universal-promise') - , ValidationError = require('./validation-error') - , Ref = require('./reference') - , { transform } = require('./_'); +import Promise from 'universal-promise' +import mapValues from 'lodash/mapValues'; +import ValidationError from '../ValidationError' +import Ref from '../Reference' let formatError = ValidationError.formatError function resolveParams(oldParams, newParams, resolve) { - let start = { ...oldParams, ...newParams } - return transform(start, (obj, value, key) => { - obj[key] = resolve(value) - }) + return mapValues({ ...oldParams, ...newParams }, resolve) } function createErrorFactory({ value, label, resolve, ...opts}) { diff --git a/src/util/inherits.js b/src/util/inherits.js new file mode 100644 index 000000000..89a7c2474 --- /dev/null +++ b/src/util/inherits.js @@ -0,0 +1,13 @@ + +export default function inherits(ctor, superCtor, spec) { + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + + Object.assign(ctor.prototype, spec); +} diff --git a/src/util/isSchema.js b/src/util/isSchema.js new file mode 100644 index 000000000..c214fd909 --- /dev/null +++ b/src/util/isSchema.js @@ -0,0 +1,3 @@ + + +export default obj => obj && obj.__isYupSchema__ diff --git a/src/util/isodate.js b/src/util/isodate.js index 32974b841..702fbf08d 100644 --- a/src/util/isodate.js +++ b/src/util/isodate.js @@ -22,12 +22,12 @@ module.exports = function parseIsoDate(date) { struct[3] = +struct[3] || 1; // allow arbitrary sub-second precision beyond milliseconds - struct[7] = struct[7] ? + (struct[7] + "00").substr(0, 3) : 0; + struct[7] = struct[7] ? + (struct[7] + '00').substr(0, 3) : 0; // timestamps without timezone identifiers should be considered local time - if ((struct[8] === undefined || struct[8] === '') && (struct[9] === undefined || struct[9] === '')) + if ((struct[8] === undefined || struct[8] === '') && (struct[9] === undefined || struct[9] === '')) timestamp = +new Date(struct[1], struct[2], struct[3], struct[4], struct[5], struct[6], struct[7]); - + else { if (struct[8] !== 'Z' && struct[9] !== undefined) { minutesOffset = struct[10] * 60 + struct[11]; @@ -38,9 +38,9 @@ module.exports = function parseIsoDate(date) { timestamp = Date.UTC(struct[1], struct[2], struct[3], struct[4], struct[5] + minutesOffset, struct[6], struct[7]); } - } - else + } + else timestamp = Date.parse ? Date.parse(date) : NaN; - + return timestamp; } diff --git a/src/util/merge.js b/src/util/merge.js new file mode 100644 index 000000000..80ddf6e99 --- /dev/null +++ b/src/util/merge.js @@ -0,0 +1,34 @@ +import has from 'lodash/has'; +import isSchema from './isSchema'; + +let isObject = obj => Object.prototype.toString.call(obj) === '[object Object]'; + +export default function merge(target, source){ + for (var key in source) if (has(source, key)) { + var targetVal = target[key] + , sourceVal = source[key]; + + if ( sourceVal === undefined ) + continue + + if (isSchema(sourceVal)) { + target[key] = isSchema(targetVal) + ? targetVal.concat(sourceVal) + : sourceVal + } + else if (isObject(sourceVal)) { + target[key] = isObject(targetVal) + ? merge(targetVal, sourceVal) + : sourceVal + } + else if (Array.isArray(sourceVal)) { + target[key] = Array.isArray(targetVal) + ? targetVal.concat(sourceVal) + : sourceVal + } + else + target[key] = source[key]; + } + + return target; +} diff --git a/src/util/reach.js b/src/util/reach.js index f20215b43..d56306f8c 100644 --- a/src/util/reach.js +++ b/src/util/reach.js @@ -1,5 +1,5 @@ -let { forEach } = require('property-expr') - , { has } = require('./_'); +import { forEach } from 'property-expr'; +import has from 'lodash/has'; let trim = part => part.substr(0, part.length - 1).substr(1) diff --git a/src/util/runValidations.js b/src/util/runValidations.js new file mode 100644 index 000000000..9af0d7a30 --- /dev/null +++ b/src/util/runValidations.js @@ -0,0 +1,69 @@ +import ValidationError from '../ValidationError'; + +let unwrapError = (errors = []) => + errors.inner && errors.inner.length + ? errors.inner + : [].concat(errors); + +function scopeToValue(promises, value) { + return Promise + .all(promises) + .catch(err => { + if (err.name === 'ValidationError') + err.value = value + throw err + }) + .then(() => value) +} + +/** + * If not failing on the first error, catch the errors + * and collect them in an array + */ +export function propagateErrors(endEarly, errors) { + return endEarly ? null : err => { + errors.push(err) + return err.value + } +} + +export function settled(promises){ + let settle = promise => promise.then( + value => ({ fulfilled: true, value }), + value => ({ fulfilled: false, value })) + + return Promise.all(promises.map(settle)) +} + + +export function collectErrors({ + validations, + value, + path, + errors = unwrapError(errors), + sort +}){ + return settled(validations).then(results => { + let nestedErrors = results + .filter(r => !r.fulfilled) + .reduce((arr, r) => arr.concat(r.value), []) + + if (sort) nestedErrors.sort(sort) + + //show parent errors after the nested ones: name.first, name + errors = nestedErrors.concat(errors) + + if (errors.length) + throw new ValidationError(errors, value, path) + + return value + }) +} + + +export default function runValidations({ endEarly, ...options }) { + if (endEarly) + return scopeToValue(options.validations, options.value) + + return collectErrors(options) +} diff --git a/src/util/scopeToValue.js b/src/util/scopeToValue.js new file mode 100644 index 000000000..4ccfb383f --- /dev/null +++ b/src/util/scopeToValue.js @@ -0,0 +1,16 @@ +import Promise from 'universal-promise'; + +/** + * Sets the error on a Validation error to a new + * value and re throws. + */ +export default function scopeToValue(promises, value) { + return Promise + .all(promises) + .catch(err => { + if (err.name === 'ValidationError') + err.value = value + throw err + }) + .then(() => value) +} diff --git a/src/util/set.js b/src/util/set.js index 6c14475fc..195e19031 100644 --- a/src/util/set.js +++ b/src/util/set.js @@ -1,4 +1,4 @@ -var { has } = require('./_') +import has from 'lodash/has'; module.exports = class BadSet { diff --git a/src/util/sortByFields.js b/src/util/sortByKeyOrder.js similarity index 74% rename from src/util/sortByFields.js rename to src/util/sortByKeyOrder.js index 04f4a8641..9483f3afe 100644 --- a/src/util/sortByFields.js +++ b/src/util/sortByKeyOrder.js @@ -1,5 +1,4 @@ - function findIndex(arr, err) { let idx = Infinity; arr.some((key, ii) => { @@ -12,8 +11,8 @@ function findIndex(arr, err) { return idx } -module.exports = function sortByFields(schema) { - let keys = Object.keys(schema.fields); +module.exports = function sortByKeyOrder(fields) { + let keys = Object.keys(fields); return (a, b) => { return findIndex(keys, a) - findIndex(keys, b) } diff --git a/src/util/sortFields.js b/src/util/sortFields.js new file mode 100644 index 000000000..5a429cfce --- /dev/null +++ b/src/util/sortFields.js @@ -0,0 +1,34 @@ +import toposort from 'toposort'; +import has from 'lodash/has'; +import { split } from 'property-expr'; +import Ref from '../Reference'; +import isSchema from './isSchema'; + +export default function sortFields(fields, excludes = []){ + var edges = [], nodes = [] + + function addNode(depPath, key) { + var node = split(depPath)[0] + + if (!~nodes.indexOf(node)) + nodes.push(node) + + if (!~excludes.indexOf(`${key}-${node}`)) + edges.push([key, node]) + } + + for (var key in fields) if (has(fields, key)) { + let value = fields[key]; + + if (!~nodes.indexOf(key)) + nodes.push(key) + + if (Ref.isRef(value) && !value.isContext) + addNode(value.path, key) + + else if (isSchema(value) && value._deps) + value._deps.forEach(path => addNode(path, key)) + } + + return toposort.array(nodes, edges).reverse() +} diff --git a/test/array.js b/test/array.js index 29767a8b0..834dbce9e 100644 --- a/test/array.js +++ b/test/array.js @@ -1,15 +1,8 @@ -'use strict'; -/* global describe, it */ -var chai = require('chai') - , chaiAsPromised = require('chai-as-promised') - , Promise = require('promise/src/es6-extensions') - , string = require('../src/string') - , number = require('../src/number') - , object = require('../src/object') - , array = require('../src/array'); - -chai.use(chaiAsPromised); -chai.should(); +import Promise from 'promise/src/es6-extensions' +import string from '../src/string' +import number from '../src/number' +import object from '../src/object' +import array from '../src/array' describe('Array types', function(){ @@ -22,16 +15,16 @@ describe('Array types', function(){ it ('should return null for failed casts', () => { expect( - array().cast('asfasf')).to.equal(null) + array().cast('asfasf', { assert: false })).to.equal(null) expect( - array().cast(null)).to.equal(null) + array().cast(null, { assert: false })).to.equal(null) }) it ('should recursively cast fields', () => { array().of(number()) - .cast(['4', 5, false]) - .should.eql([4, 5, 0]) + .cast(['4', '5']) + .should.eql([4, 5]) array().of(string()) .cast(['4', 5, false]) @@ -126,14 +119,14 @@ describe('Array types', function(){ .test('name', 'oops', function(){ return false }) return Promise.all([ - inst.validate([{ str: null }]).should.be.rejected + inst.validate([{ str: '' }]).should.be.rejected .then(function(err){ err.value.should.eql([{ str: '' }]) err.errors.length.should.equal(1) err.errors.should.eql(['oops']) }), - inst.validate([{ str: null }], { abortEarly: false }).should.be.rejected + inst.validate([{ str: '' }], { abortEarly: false }).should.be.rejected .then(function(err) { err.value.should.eql([{ str: '' }]) diff --git a/test/bool.js b/test/bool.js index addc01c82..5490cda99 100644 --- a/test/bool.js +++ b/test/bool.js @@ -15,14 +15,17 @@ describe('Boolean types', function(){ var inst = bool(); inst.cast('true').should.equal(true) + inst.cast('True').should.equal(true) + inst.cast('false').should.equal(false) + inst.cast('False').should.equal(false) inst.cast(1).should.equal(true) inst.cast(0).should.equal(false) - chai.expect( - inst.cast(null)).to.equal(false) + TestHelpers + .castAndShouldFail(inst, 'foo') - chai.expect( - inst.nullable().cast(null)).to.equal(null) + TestHelpers + .castAndShouldFail(inst, 'bar1') }) it('should handle DEFAULT', function(){ @@ -39,6 +42,8 @@ describe('Boolean types', function(){ inst.isType(false).should.equal(true) inst.isType('true').should.equal(false) inst.isType(NaN).should.equal(false) + inst.isType(new Number('foooo')).should.equal(false) + inst.isType(34545).should.equal(false) inst.isType(new Boolean(false)).should.equal(true) chai.expect( @@ -51,7 +56,7 @@ describe('Boolean types', function(){ var inst = bool().required() return Promise.all([ - bool().isValid(null).should.eventually.equal(true), //coerced to false + bool().isValid('1').should.eventually.equal(true), bool().strict().isValid(null).should.eventually.equal(false), diff --git a/test/date.js b/test/date.js index bcaf22d59..e61d3b0be 100644 --- a/test/date.js +++ b/test/date.js @@ -1,11 +1,5 @@ -'use strict'; -var chai = require('chai') - , chaiAsPromised = require('chai-as-promised') - , Promise = require('promise/src/es6-extensions') - , { ref, date } = require('../src'); - -chai.use(chaiAsPromised); -chai.should(); +import Promise from 'promise/src/es6-extensions'; +import { ref, date } from '../src'; function isValidDate(date){ return date instanceof Date && !isNaN(date.getTime()) @@ -13,19 +7,22 @@ function isValidDate(date){ describe('Date types', function(){ - it('should CAST correctly', function(){ + it('should CAST correctly', function(){ var inst = date() - inst.cast(null).should.not.satisfy(isValidDate) - inst.cast('').should.not.satisfy(isValidDate) - - inst.cast(new Date()).should.be.a('date') inst.cast(new Date()).should.be.a('date') inst.cast('jan 15 2014').should.eql(new Date(2014, 0, 15)) inst.cast('2014-09-23T19:25:25Z').should.eql(new Date(1411500325000)) }) + it('should return invalid date for failed casts', function(){ + var inst = date() + + inst.cast(null, { assert: false }).should.not.satisfy(isValidDate) + inst.cast('', { assert: false }).should.not.satisfy(isValidDate) + }) + it('should type check', function(){ var inst = date() diff --git a/test/helpers.js b/test/helpers.js new file mode 100644 index 000000000..d2c98d14e --- /dev/null +++ b/test/helpers.js @@ -0,0 +1,46 @@ +import typeOf from 'type-name'; + +export let castAndShouldFail = (schema, value) => { + (()=> schema.cast(value)) + .should.throw( + TypeError, + new RegExp(`Got "${typeOf(value)}" instead`, 'gi') + ) +} + +export let castAll = (inst, { invalid = [], valid = [] }) => { + valid.forEach(([value, result, schema = inst ]) => { + it(`should cast ${JSON.stringify(value)} to ${JSON.stringify(result)}`, () => + expect( + schema.cast(value) + ) + .to.equal(result) + ) + }) + + invalid.forEach((value) => { + it(`should not cast ${JSON.stringify(value)}`, () => + castAndShouldFail(inst, value) + ) + }) +} + +export let validateAll = (inst, { valid = [], invalid = [] }) => { + runValidations(valid, true) + runValidations(invalid, false) + + function runValidations(arr, isValid) { + arr.forEach((config) => { + let value = config, schema = inst; + + if (Array.isArray(config)) + [ value, schema ] = config; + + it(`${JSON.stringify(value)} should be ${isValid ? 'valid' : 'invalid'}`, + () => schema.isValid(value).should.become(isValid) + ) + }) + } + + +} diff --git a/test/mixed.js b/test/mixed.js index 4cf01c550..63c9901e8 100644 --- a/test/mixed.js +++ b/test/mixed.js @@ -1,20 +1,14 @@ -'use strict'; -/*global describe, it */ -var chai = require('chai') - , chaiAsPromised = require('chai-as-promised') - , ValidationError = require('../src/util/validation-error') - , Promise = require('promise/src/es6-extensions') - , mixed = require('../src/mixed') - , object = require('../src/object') - , string = require('../src/string') - , reach = require('../src/util/reach'); - -chai.use(chaiAsPromised); -chai.should(); + +import Promise from 'promise/src/es6-extensions'; +import mixed from '../src/mixed'; +import object from '../src/object'; +import string from '../src/string'; +import ValidationError from '../src/ValidationError'; +import reach from '../src/util/reach'; let noop = function(){} -describe( 'Mixed Types ', function(){ +describe.only( 'Mixed Types ', function(){ it('should be immutable', function(){ var inst = mixed(), next; @@ -51,7 +45,7 @@ describe( 'Mixed Types ', function(){ error = await inst.validate(5, { abortEarly: false }).should.be.rejected - chai.expect(error.type).to.not.exist + expect(error.type).to.not.exist error.message.should.equal('must be a string!') error.inner.length.should.equal(1) }) @@ -250,14 +244,14 @@ describe( 'Mixed Types ', function(){ name: 'max', test() { this.path.should.equal('test') - this.parent.should.eql({ other: 5, test : 'hi' }) + this.parent.should.eql({ other: 5, test: 'hi' }) this.options.context.should.eql({ user: 'jason' }) done() } }) }) - inst.validate({ other: 5, test : 'hi' }, { context: { user: 'jason' } }) + inst.validate({ other: 5, test: 'hi' }, { context: { user: 'jason' } }) }) it('tests can return an error', function(){ @@ -320,11 +314,11 @@ describe( 'Mixed Types ', function(){ inst.isValid(5, function(err, valid){ valid.should.equal(true) - chai.expect(err).to.equal(null) + expect(err).to.equal(null) inst.strict().validate(5, function(err, value){ err.should.be.an.instanceOf(ValidationError) - chai.expect(value).to.equal(undefined) + expect(value).to.equal(undefined) done() }) }) @@ -349,7 +343,7 @@ describe( 'Mixed Types ', function(){ })) }) - it ('should have teh correct number of tests', () => { + it ('should have the correct number of tests', () => { reach(next, 'str').tests.length.should.equal(3) // presence, alt presence, and trim }) @@ -405,7 +399,7 @@ describe( 'Mixed Types ', function(){ it('concat should maintain undefined defaults', function(){ var inst = string().default('hi') - chai.expect( + expect( inst.concat(string().default(undefined)).default()).to.equal(undefined) }) diff --git a/test/number.js b/test/number.js index de5b47748..8edee6b33 100644 --- a/test/number.js +++ b/test/number.js @@ -1,40 +1,68 @@ -'use strict'; -/* global describe, it */ -var chai = require('chai') - , chaiAsPromised = require('chai-as-promised') - , Promise = require('promise/src/es6-extensions') - , number = require('../src/number'); +import Promise from 'promise/src/es6-extensions'; +import number from '../src/number'; -chai.use(chaiAsPromised); -chai.should(); +describe('Number types', function() { -describe('Number types', function(){ - - it('should CAST correctly', function(){ - - var inst = number(), date = new Date() - - chai.expect( - inst.cast(null)).to.eql(NaN) - - inst.cast('5').should.equal(5) - inst.cast('').should.eql(NaN) - inst.cast(3).should.equal(3) - inst.cast(false).should.equal(0) - inst.cast(true).should.equal(1) - inst.cast(date).should.equal(date.getTime()) + it('is newable', () => { + let schema = new number(); + schema.integer().required() + }) - inst.integer().cast(45.55).should.equal(45) - inst.round('Floor').cast(45.99999).should.equal(45) - inst.round('ceIl').cast(45.1111).should.equal(46) - inst.round().cast(45.444444).should.equal(45) + it('is extensible', () => { + class MyNumber extends number { + foo() { + return this; + } + } - ;(function(){ inst.round('fasf') }).should.throw(TypeError) + new MyNumber().foo().integer().required() + }) - chai.expect(inst.nullable() - .integer() - .round() - .cast(null)).to.equal(null) + describe('casting', ()=> { + let schema = number(); + + TestHelpers.castAll(schema, { + valid: [ + ['5', 5], + [3, 3], + //[new Number(5), 5], + [' 5.656 ', 5.656], + ], + invalid: [ + '', + false, + true, + new Date(), + new Number('foo') + ] + }) + + it('should round', () => { + schema.round('floor').cast(45.99999).should.equal(45) + schema.round('ceIl').cast(45.1111).should.equal(46) + schema.round().cast(45.444444).should.equal(45) + + expect( + schema.nullable() + .integer() + .round() + .cast(null) + ).to.equal(null) + + ;(function(){ schema.round('fasf') }).should.throw(TypeError) + }) + + it('should truncate', () => { + schema.truncate().cast(45.55).should.equal(45) + }) + + it ('should return NaN for failed casts', () => { + expect( + number().cast('asfasf', { assert: false })).to.eql(NaN) + + expect( + number().cast(null, { assert: false })).to.eql(NaN) + }) }) it('should handle DEFAULT', function(){ @@ -49,6 +77,7 @@ describe('Number types', function(){ inst.isType(5).should.equal(true) inst.isType(new Number(5)).should.equal(true) + inst.isType(new Number('foo')).should.equal(false) inst.isType(false).should.equal(false) inst.isType(null).should.equal(false) inst.isType(NaN).should.equal(false) @@ -73,40 +102,69 @@ describe('Number types', function(){ ]) }) - it('should check MIN correctly', function(){ - var v = number().min(5); - - return Promise.all([ - v.isValid(7).should.eventually.equal(true), - v.isValid(2).should.eventually.equal(false), - v.isValid(35738787838).should.eventually.equal(true), - - v.min(10).min(15).isValid(14).should.eventually.equal(false), - - v.isValid(new Date).should.eventually.equal(true), - - v.isValid(null).should.eventually.equal(false), // -> NaN fails type check + describe('min', () => { + var schema = number().min(5); + + TestHelpers.validateAll(schema, { + valid: [ + 7, + 35738787838, + [null, schema.nullable()] + ], + invalid: [ + 2, + null, + [14, schema.min(10).min(15)] + ] + }) + }) - v.nullable().isValid(null).should.eventually.equal(true), - ]) + describe('max', () => { + var schema = number().max(5); + + TestHelpers.validateAll(schema, { + valid: [ + 4, + -5222, + [null, schema.nullable()] + ], + invalid: [ + 10, + null, + [16, schema.max(20).max(15)] + ] + }) }) - it('should check MAX correctly', function(){ - var v = number().max(5); + describe('integer', ()=> { + TestHelpers.validateAll( + number().integer(), + { + valid: [ + 4, + -5222, + ], + invalid: [ + 10.53, + 0.1 * 0.2, + -34512535.626, + 3.12312e+51, + new Date(), + ] + } + ) + }) + it('should check integer', function(){ + var v = number().positive(); return Promise.all([ - v.isValid(4).should.eventually.equal(true), - v.isValid(10).should.eventually.equal(false), - v.isValid(-5222).should.eventually.equal(true), - - v.isValid(false).should.eventually.equal(true), - v.isValid(new Date).should.eventually.equal(false), - - v.max(10).max(15).isValid(16).should.eventually.equal(false), + v.isValid(7).should.eventually.equal(true), - v.isValid(null).should.eventually.equal(false), // null -> NaN fails type check + v.isValid(0).should.eventually.equal(true), - v.nullable().isValid(null).should.eventually.equal(true), + v.validate(-4).should.be.rejected.then(null, function(err){ + err.errors[0].should.contain('this must be a positive number') + }) ]) }) diff --git a/test/object.js b/test/object.js index 9dd8ae8ed..f54fa8997 100644 --- a/test/object.js +++ b/test/object.js @@ -1,16 +1,9 @@ -'use strict'; -/*global describe, it */ -var chai = require('chai') - , chaiAsPromised = require('chai-as-promised') - , Promise = require('promise/src/es6-extensions') - , { - mixed, string, date, number - , bool, array, object, ref, lazy, reach - } = require('../src'); - -chai.use(chaiAsPromised); -chai.should(); +import Promise from 'promise/src/es6-extensions'; +import { + mixed, string, date, number + , bool, array, object, ref, lazy, reach +} from '../src'; describe('Object types', function(){ @@ -24,15 +17,15 @@ describe('Object types', function(){ }) it ('should return null for failed casts', () => { - chai.expect( - object().cast('dfhdfh')).to.equal(null) + expect( + object().cast('dfhdfh', { assert: false })).to.equal(null) }) it ('should recursively cast fields', () => { var obj = { num: '5', str: 'hello', - arr: ['4', 5, false], + arr: ['4', 5], dte: '2014-09-23T19:25:25Z', nested: { str: 5 }, arrNested: [{ num: 5 }, { num: '5' }] @@ -51,7 +44,7 @@ describe('Object types', function(){ .cast(obj).should.eql({ num: 5, str: 'hello', - arr: [4, 5, 0], + arr: [4, 5], dte: new Date(1411500325000), nested: { str: '5' }, arrNested: [{ num: 5 }, { num: 5 }] @@ -74,14 +67,14 @@ describe('Object types', function(){ obj = { num: '4', str: 'hello', - arr: ['4', 5, false], + arr: ['4', 5, 6], dte: '2014-09-23T19:25:25Z', nested: { str: 5 }, arrNested: [{ num: 5 }, { num: '2' }] } }) - it ('should run validations recursively', async () => { + it('should run validations recursively', async () => { let error = await inst.validate(obj).should.be.rejected; error.errors.length.should.equal(1) @@ -468,7 +461,7 @@ describe('Object types', function(){ return Promise.all([ inst - .validate({ nest: { str: null } }).should.be.rejected + .validate({ nest: { str: '' } }).should.be.rejected .then(function(err) { err.value.should.eql({ nest: { str: '' } }) err.errors.length.should.equal(1) @@ -478,7 +471,11 @@ describe('Object types', function(){ }), inst - .validate({ nest: { str: null } }, { abortEarly: false }).should.be.rejected + .validate( + { nest: { str: '' } }, + { abortEarly: false } + ) + .should.be.rejected .then(function(err) { err.value.should.eql({ nest: { str: '' } }) err.errors.length.should.equal(2) @@ -496,7 +493,7 @@ describe('Object types', function(){ }) let err = await inst.validate( - { foo: 'foo', bar: null }, + { foo: 'foo' }, { abortEarly: false }).should.rejected; err.errors.should.eql([ @@ -608,9 +605,9 @@ describe('Object types', function(){ }) .default(undefined) - chai.expect(inst.concat(object()).default()).to.equal(undefined) + expect(inst.concat(object()).default()).to.equal(undefined) - chai.expect(inst.concat(object().default({})).default()).to.eql({}) + expect(inst.concat(object().default({})).default()).to.eql({}) }) it('should handle nested conditionals', function(){ @@ -659,14 +656,14 @@ describe('Object types', function(){ inst.cast({ CON_STAT: 5, CaseStatus: 6, 'hi john': 4 }) .should.eql({ conStat: 5, caseStatus: 6, hiJohn: 4 }) - chai.expect(inst + expect(inst .nullable() .cast(null)).to.equal(null) }) it('should camelCase with leading underscore', function(){ var inst = object().camelcase() - + inst .cast({ CON_STAT: 5, __isNew: true, __IS_FUN: true }) .should @@ -684,7 +681,7 @@ describe('Object types', function(){ inst.cast({ conStat: 5, CaseStatus: 6, 'hi john': 4 }) .should.eql({ CON_STAT: 5, CASE_STATUS: 6, HI_JOHN: 4 }) - chai.expect(inst + expect(inst .nullable() .cast(null)).to.equal(null) }) diff --git a/test/string.js b/test/string.js index 0146afae3..91407ac07 100644 --- a/test/string.js +++ b/test/string.js @@ -1,45 +1,69 @@ 'use strict'; -/* global describe, it */ -var chai = require('chai') - , Promise = require('promise/src/es6-extensions') - , sinonChai = require('sinon-chai') - , chaiAsPromised = require('chai-as-promised') - , { string, number, object, ref } = require('../src'); - -chai.use(chaiAsPromised); -chai.use(sinonChai); -chai.should(); +import Promise from 'promise/src/es6-extensions' +import { string, number, object, ref } from '../src'; describe('String types', function(){ - it('should CAST correctly', function(){ - - var inst = string() - - inst.cast(5).should.equal('5') - - chai.expect( - inst.cast(null)).to.equal('') - - chai.expect( - inst.nullable().cast(null)).to.equal(null) - - inst.cast('3').should.equal('3') - inst.cast(false).should.equal('false') - inst.cast(true).should.equal('true') + describe('casting', ()=> { + let schema = string(); + + TestHelpers.castAll(schema, { + valid: [ + [5, '5'], + ['3', '3'], + //[new String('foo'), 'foo'], + ['', ''], + [true, 'true'], + [false, 'false'], + [0, '0'], + [null, null, schema.nullable()] + ], + invalid: [ + undefined, + null, + ] + }) - chai.expect(inst.cast()).to.equal(undefined) + describe('ensure', () => { + let schema = string().ensure(); + + TestHelpers.castAll( + schema, + { + valid: [ + [5, '5'], + ['3', '3'], + [null, ''], + [undefined, ''], + [null, '', schema.default('foo')], + [undefined, 'foo', schema.default('foo')], + ] + } + ) + }) - inst.trim().cast(' 3 ').should.equal('3') + it('should trim', () => { + schema.trim().cast(' 3 ').should.equal('3') + }) - inst.lowercase().cast('HellO JohN').should.equal('hello john') - inst.uppercase().cast('HellO JohN').should.equal('HELLO JOHN') + it('should transform to lowercase', () => { + schema.lowercase() + .cast('HellO JohN') + .should.equal('hello john') + }) + it('should transform to lowercase', () => { + schema.uppercase() + .cast('HellO JohN') + .should.equal('HELLO JOHN') + }) - chai.expect(inst.nullable() - .trim() - .lowercase() - .uppercase() - .cast(null)).to.equal(null) + it('should handle nulls', () => { + expect(schema.nullable() + .trim() + .lowercase() + .uppercase() + .cast(null)).to.equal(null) + }) }) it('should handle DEFAULT', function(){ @@ -118,10 +142,7 @@ describe('String types', function(){ v.isValid('bigdfdsfsdf').should.eventually.equal(false), v.isValid('no').should.eventually.equal(true), - v.isValid(5).should.eventually.equal(true), - v.isValid(new Date()).should.eventually.equal(false), - - v.isValid(null).should.eventually.equal(true), + v.isValid(null).should.eventually.equal(false), v.nullable().isValid(null).should.eventually.equal(true), diff --git a/test/yup.js b/test/yup.js index a6e9b14ff..f4e1d8381 100644 --- a/test/yup.js +++ b/test/yup.js @@ -1,16 +1,10 @@ -'use strict'; -/*global describe, it */ -var Promise = require('promise/src/es6-extensions') - , chai = require('chai') - , chaiAsPromised = require('chai-as-promised') - , reach = require('../src/util/reach') - , BadSet = require('../src/util/set') - , { object, array, string, lazy, number } = require('../src') - , _ = require('../src/util/_'); +import Promise from 'promise/src/es6-extensions'; +import reach from '../src/util/reach'; +import BadSet from '../src/util/set'; +import merge from '../src/util/merge'; +import { settled } from '../src/util/runValidations'; -chai.use(chaiAsPromised); - -chai.should(); +import { object, array, string, lazy, number } from '../src'; describe('Yup', function(){ @@ -18,18 +12,10 @@ describe('Yup', function(){ require('../lib') }) - it('should uniq', function(){ - _.uniq([1, 1, 2, 3, 4, 3], function(i){ return i}) - .should.eql([1, 2, 3, 4]) - - _.uniq([{ a: 1}, { a: 2}, { a: 3}, { a: 1}], function(i){ return i.a}) - .should.deep.eql([{ a: 1}, { a: 2}, { a: 3}]) - }) - it('should do settled', function(){ return Promise.all([ - _.settled([Promise.resolve('hi'), Promise.reject('error')]).should.be.fulfilled + settled([Promise.resolve('hi'), Promise.reject('error')]).should.be.fulfilled .then(function (results) { results.length.should.equal(2) results[0].fulfilled.should.equal(true) @@ -42,10 +28,9 @@ describe('Yup', function(){ it('should merge', function(){ var a = { a: 1, b: 'hello', c: [1, 2, 3], d: { a: /hi/ }, e: { b: 5} } - var b = { a: 4, c: [4, 5, 3], d: { b: 'hello' }, f: { c: 5}, g: null } - _.merge(a, b).should.deep.eql({ + merge(a, b).should.deep.eql({ a: 4, b: 'hello', c: [1, 2, 3, 4, 5, 3], diff --git a/tests-webpack.js b/tests-webpack.js index 4272c1028..1347e3845 100644 --- a/tests-webpack.js +++ b/tests-webpack.js @@ -7,7 +7,9 @@ chai.use(require('chai-as-promised')) chai.use(require('sinon-chai')) chai.should(); + global.expect = window.expect = chai.expect; +global.TestHelpers = window.TestHelpers = require('./test/helpers'); var testsContext = require.context('./test', true);