diff --git a/.babelrc b/.babelrc index ab8987b91..e67f2a959 100644 --- a/.babelrc +++ b/.babelrc @@ -9,6 +9,7 @@ ], "env": { "test": { + "sourceMaps": "inline", "presets": [ [ "jason", diff --git a/README.md b/README.md index 3b4e13c49..58d5cd8ac 100644 --- a/README.md +++ b/README.md @@ -101,12 +101,13 @@ json separate from validating it, via the `cast` method. npm install -S yup ``` -Yup always relies on the `Promise` global object to handle asynchronous values as well `Set`. +Yup always relies on the `Promise` global object to handle asynchronous values as well as `Set` and `Map`. For browsers that do not support these, you'll need to include a polyfill, such as core-js: ```js import 'core-js/es6/promise'; import 'core-js/es6/set'; +import 'core-js/es6/map'; ``` ## Usage diff --git a/src/Reference.js b/src/Reference.js index c95fb9d6b..12e40b79b 100644 --- a/src/Reference.js +++ b/src/Reference.js @@ -10,6 +10,10 @@ export default class Reference { return !!(value && (value.__isYupRef || value instanceof Reference)) } + toString() { + return `Ref(${this.key})` + } + constructor(key, mapFn, options = {}) { validateName(key) let prefix = options.contextPrefix || '$'; diff --git a/src/index.js b/src/index.js index 2728e04ef..761fe4016 100644 --- a/src/index.js +++ b/src/index.js @@ -12,9 +12,9 @@ import reach from './util/reach'; import isSchema from './util/isSchema'; let boolean = bool; -let ref =(key, options) => new Ref(key, options); +let ref = (key, options) => new Ref(key, options); -let lazy =(fn) => new Lazy(fn); +let lazy = (fn) => new Lazy(fn); function addMethod(schemaType, name, fn) { if (!schemaType || !isSchema(schemaType.prototype)) diff --git a/src/mixed.js b/src/mixed.js index 2d5cb3cf9..3ffa7ff5c 100644 --- a/src/mixed.js +++ b/src/mixed.js @@ -34,6 +34,29 @@ function extractTestParams(name, message, test) { return opts } +const listToArray = list => + toArray(list).concat(toArray(list.refs.values())) + +const removeFromList = (value, list) => { + Ref.isRef(value) ? list.refs.delete(value.key) : list.delete(value) +} + +const addToList = (value, list) => { + Ref.isRef(value) ? list.refs.set(value.key, value) : list.add(value) +} + +const hasInList = (value, resolve, list) => { + if (list.has(value)) return true; + + let item; let values = list.refs.values() + + while (item = values.next(), !item.done) { + if (resolve(item.value) === value) + return true; + } + return false +} + export default function SchemaType(options = {}){ if ( !(this instanceof SchemaType)) return new SchemaType() @@ -42,8 +65,12 @@ export default function SchemaType(options = {}){ this._conditions = [] this._options = { abortEarly: true, recursive: true } this._exclusive = Object.create(null) + this._whitelist = new Set() + this._whitelist.refs = new Map() + this._blacklist = new Set() + this._blacklist.refs = new Map() this.tests = [] this.transforms = [] @@ -380,23 +407,22 @@ SchemaType.prototype = { var next = this.clone(); enums.forEach(val => { - if (next._blacklist.has(val)) - next._blacklist.delete(val) - next._whitelist.add(val) + addToList(val, next._whitelist) + removeFromList(val, next._blacklist) }) next._whitelistError = createValidation({ message, name: 'oneOf', test(value) { + if (value === undefined) return true let valids = this.schema._whitelist - if (valids.size && !(value === undefined || valids.has(value))) - return this.createError({ - params: { - values: toArray(valids).join(', ') - } - }) - return true + + return hasInList(value, this.resolve, valids) ? true : this.createError({ + params: { + values: listToArray(valids).join(', ') + } + }) } }) @@ -405,10 +431,9 @@ SchemaType.prototype = { notOneOf(enums, message = locale.notOneOf) { var next = this.clone(); - - enums.forEach( val => { - next._whitelist.delete(val) - next._blacklist.add(val) + enums.forEach(val => { + addToList(val, next._blacklist) + removeFromList(val, next._whitelist) }) next._blacklistError = createValidation({ @@ -416,10 +441,10 @@ SchemaType.prototype = { name: 'notOneOf', test(value) { let invalids = this.schema._blacklist - if (invalids.size && invalids.has(value)) + if (hasInList(value, this.resolve, invalids)) return this.createError({ params: { - values: toArray(invalids).join(', ') + values: listToArray(invalids).join(', ') } }) return true diff --git a/src/number.js b/src/number.js index ff4692fe3..00889f3f9 100644 --- a/src/number.js +++ b/src/number.js @@ -58,9 +58,9 @@ inherits(NumberSchema, MixedSchema, { }) }, - less(less, msg) { + lessThan(less, msg) { return this.test({ - name: 'less', + name: 'max', exclusive: true, params: { less }, message: msg || locale.less, @@ -70,9 +70,9 @@ inherits(NumberSchema, MixedSchema, { }) }, - more(more, msg) { + moreThan(more, msg) { return this.test({ - name: 'more', + name: 'min', exclusive: true, params: { more }, message: msg || locale.more, @@ -82,18 +82,6 @@ inherits(NumberSchema, MixedSchema, { }) }, - notEqual(notEqual, msg) { - return this.test({ - name: 'notEqual', - exclusive: true, - params: { notEqual }, - message: msg || locale.notEqual, - test(value) { - return isAbsent(value) || value !== this.resolve(notEqual) - } - }) - }, - positive(msg) { return this.min(0, msg || locale.positive) }, diff --git a/src/util/createValidation.js b/src/util/createValidation.js index 1f308ec8f..7dcb0c324 100644 --- a/src/util/createValidation.js +++ b/src/util/createValidation.js @@ -52,16 +52,16 @@ export default function createValidation(options) { function validate({ value, path, label, options, originalValue, sync, ...rest }) { let parent = options.parent; - var resolve = (value) => Ref.isRef(value) + let resolve = (value) => Ref.isRef(value) ? value.getValue(parent, options.context) : value - var createError = createErrorFactory({ + let createError = createErrorFactory({ message, path, value, originalValue, params , label, resolve, name }) - var ctx = { path, parent, type: name, createError, resolve, options, ...rest } + let ctx = { path, parent, type: name, createError, resolve, options, ...rest } return runTest(test, ctx, value, sync) .then(validOrError => { diff --git a/test/helpers.js b/test/helpers.js index 5c6eb43d9..bac7e9fdf 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -1,3 +1,4 @@ +import printValue from '../src/util/printValue'; export let castAndShouldFail = (schema, value) => { (()=> schema.cast(value)) @@ -9,7 +10,7 @@ export let castAndShouldFail = (schema, value) => { export let castAll = (inst, { invalid = [], valid = [] }) => { valid.forEach(([value, result, schema = inst ]) => { - it(`should cast ${JSON.stringify(value)} to ${JSON.stringify(result)}`, () => { + it(`should cast ${printValue(value)} to ${printValue(result)}`, () => { expect( schema.cast(value) ) @@ -18,24 +19,29 @@ export let castAll = (inst, { invalid = [], valid = [] }) => { }) invalid.forEach((value) => { - it(`should not cast ${JSON.stringify(value)}`, () => { + it(`should not cast ${printValue(value)}`, () => { castAndShouldFail(inst, value) }) }) } export let validateAll = (inst, { valid = [], invalid = [] }) => { - runValidations(valid, true) - runValidations(invalid, false) + describe('valid:', () => { + runValidations(valid, true) + }) + + describe('invalid:', () => { + runValidations(invalid, false) + }) function runValidations(arr, isValid) { arr.forEach((config) => { - let value = config, schema = inst; + let message = '', value = config, schema = inst; if (Array.isArray(config)) - [ value, schema ] = config; + [ value, schema, message = '' ] = config; - it(`${JSON.stringify(value)} should be ${isValid ? 'valid' : 'invalid'}`, + it(`${printValue(value)}${message && ` (${message})`}`, () => schema.isValid(value).should.become(isValid) ) }) diff --git a/test/mixed.js b/test/mixed.js index b7e267a57..93dc9f119 100644 --- a/test/mixed.js +++ b/test/mixed.js @@ -1,9 +1,4 @@ -import mixed from '../src/mixed'; -import object from '../src/object'; -import string from '../src/string'; -import number from '../src/number'; -import reach from '../src/util/reach'; - +import {mixed, string, number, object, ref, reach } from '../src'; let noop = () => {} function ensureSync(fn) { @@ -136,48 +131,56 @@ describe('Mixed Types ', () => { }) }) - it('should ignore absent values', () => { - return Promise.all([ - mixed() - .oneOf(['hello']) - .isValid(undefined) - .should.eventually().equal(true), - mixed() - .nullable() - .oneOf(['hello']) - .isValid(null) - .should.eventually().equal(false), - mixed() - .oneOf(['hello']) - .required() - .isValid(undefined) - .should.eventually().equal(false), - mixed() - .nullable() - .oneOf(['hello']) - .required() - .isValid(null) - .should.eventually().equal(false) - ]) + describe('oneOf', () => { + let inst = mixed().oneOf(['hello']) + + TestHelpers.validateAll(inst, { + valid: [ + undefined, + 'hello' + ], + invalid: [ + 'YOLO', + [undefined, inst.required(), 'required'], + [null, inst.nullable()], + [null, inst.nullable().required(), 'required'], + ] + }) + + it('should work with refs', async () => { + let inst = object({ + foo: string(), + bar: string().oneOf([ref('foo'), 'b']) + }) + + await inst.validate({ foo: 'a', bar: 'a' }).should.be.fulfilled() + + await inst.validate({ foo: 'foo', bar: 'bar' }).should.be.rejected() + }) }) - it('should exclude values', () => { + describe('should exclude values', () => { let inst = mixed().notOneOf([5, 'hello']) - return Promise.all([ - inst.isValid(6).should.eventually().equal(true), - inst.isValid('hfhfh').should.eventually().equal(true), - - inst.isValid(5).should.eventually().equal(false), + TestHelpers.validateAll(inst, { + valid: [ + 6, + 'hfhfh', + [5, inst.oneOf([5]), '`oneOf` called after'], + null, + ], + invalid: [ + 5, + [null, inst.required(), 'required schema'] + ] + }) - inst.validate(5).should.be.rejected().then(err => { - err.errors[0].should.equal('this must not be one of the following values: 5, hello') - }), - inst.oneOf([5]).isValid(5).should.eventually().equal(true), + it('should throw the correct error', async () => { + let err = await inst.validate(5).should.be.rejected() - inst.isValid(null).should.eventually().equal(true), - inst.required().isValid(null).should.eventually().equal(false) - ]) + err.errors[0].should + .equal('this must not be one of the following values: 5, hello') + }) }) it('should run subset of validations first', () => { diff --git a/test/number.js b/test/number.js index b6382730c..1bdbe53ab 100644 --- a/test/number.js +++ b/test/number.js @@ -137,8 +137,8 @@ describe('Number types', function() { }) }) - describe('less', () => { - var schema = number().less(5); + describe('lessThan', () => { + var schema = number().lessThan(5); TestHelpers.validateAll(schema, { valid: [ @@ -150,13 +150,13 @@ describe('Number types', function() { 5, 7, null, - [14, schema.less(10).less(14)] + [14, schema.lessThan(10).lessThan(14)] ] }) }) describe('more', () => { - var schema = number().more(5); + var schema = number().moreThan(5); TestHelpers.validateAll(schema, { valid: [ @@ -168,24 +168,7 @@ describe('Number types', function() { 5, -10, null, - [64, schema.more(52).more(74)] - ] - }) - }) - - describe('notEqual', () => { - var schema = number().notEqual(5); - - TestHelpers.validateAll(schema, { - valid: [ - 6, - 56445435, - [null, schema.nullable()] - ], - invalid: [ - 5, - null, - [52, schema.notEqual(52).notEqual(74)] + [64, schema.moreThan(52).moreThan(74)] ] }) })