Skip to content

Commit

Permalink
fix tests and add ref support to oneOf/notOneOf
Browse files Browse the repository at this point in the history
  • Loading branch information
jquense committed Nov 8, 2017
1 parent 77a3a33 commit 061e590
Show file tree
Hide file tree
Showing 10 changed files with 120 additions and 109 deletions.
1 change: 1 addition & 0 deletions .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
],
"env": {
"test": {
"sourceMaps": "inline",
"presets": [
[
"jason",
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 4 additions & 0 deletions src/Reference.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 || '$';
Expand Down
4 changes: 2 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
57 changes: 41 additions & 16 deletions src/mixed.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand All @@ -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 = []

Expand Down Expand Up @@ -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(', ')
}
})
}
})

Expand All @@ -405,21 +431,20 @@ 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({
message,
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
Expand Down
20 changes: 4 additions & 16 deletions src/number.js
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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)
},
Expand Down
6 changes: 3 additions & 3 deletions src/util/createValidation.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 => {
Expand Down
20 changes: 13 additions & 7 deletions test/helpers.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import printValue from '../src/util/printValue';

export let castAndShouldFail = (schema, value) => {
(()=> schema.cast(value))
Expand All @@ -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)
)
Expand All @@ -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)
)
})
Expand Down
87 changes: 45 additions & 42 deletions test/mixed.js
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -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', () => {
Expand Down
Loading

0 comments on commit 061e590

Please sign in to comment.