Repository: jquense/yup
Pull request #362 (2018-11-07 15:15:25)

Updated README intro to read a little easier
Feel free to ignore, but the sentence wasn't fully baked.


Patch:
diff --git a/README.md b/README.md
index 88a3cef..5dd6fec 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,7 @@
 
 Yup is a JavaScript object schema validator and object parser. The API and style is ~~stolen~~ heavily inspired
 by [Joi](https://github.com/hapijs/joi), which is an amazing library but is generally too large and difficult
-to package for use in a browser. Yup is a leaner in the same spirit without some of the fancy features.
+to package for use in a browser. Yup is leaner: in the same spirit, without some of the fancy features.
 You can use it on the server as well, but in that case you might as well just use Joi.
 
 Yup is also a good bit less opinionated than joi, allowing for custom transformations and async validation.
@@ -20,7 +20,7 @@ json separate from validating it, via the `cast` method.
   - [Using a custom locale dictionary](#using-a-custom-locale-dictionary)
 - [API](#api)
   - [`yup`](#yup)
-    - [`yup.reach(schema: Schema, path: string, value: ?object, context: ?object): Schema`](#yupreachschema-schema-path-string-value-object-context-object-schema)
+    - [`yup.reach(schema: Schema, path: string, value?: object, context?: object): Schema`](#yupreachschema-schema-path-string-value-object-context-object-schema)
     - [`yup.addMethod(schemaType: Schema, name: string, method: ()=> Schema): void`](#yupaddmethodschematype-schema-name-string-method--schema-void)
     - [`yup.ref(path: string, options: { contextPrefix: string }): Ref`](#yuprefpath-string-options--contextprefix-string--ref)
     - [`yup.lazy((value: any) => Schema): Lazy`](#yuplazyvalue-any--schema-lazy)
@@ -31,13 +31,13 @@ json separate from validating it, via the `cast` method.
     - [`mixed.meta(metadata: object): Schema`](#mixedmetametadata-object-schema)
     - [`mixed.describe(): SchemaDescription`](#mixeddescribe-schemadescription)
     - [`mixed.concat(schema: Schema)`](#mixedconcatschema-schema)
-    - [`mixed.validate(value: any, options: ?object): Promise<any, ValidationError>`](#mixedvalidatevalue-any-options-object-promiseany-validationerror)
-    - [`mixed.validateSync(value: any, options: ?object): any`](#mixedvalidatesyncvalue-any-options-object-any)
-    - [`mixed.validateAt(path: string, value: any, context?: object): Promise<any, ValidationError>`](#mixedvalidateatpath-string-value-any-context-object-promiseany-validationerror)
-    - [`mixed.validateSyncAt(path: string, value: any, context?: object): Promise<any, ValidationError>`](#mixedvalidatesyncatpath-string-value-any-context-object-promiseany-validationerror)
-    - [`mixed.isValid(value: any, options: ?object): Promise<boolean>`](#mixedisvalidvalue-any-options-object-promiseboolean)
-    - [`mixed.isValidSync(value: any, options: ?object): boolean`](#mixedisvalidsyncvalue-any-options-object-boolean)
-    - [`mixed.cast(value: any): any`](#mixedcastvalue-any-any)
+    - [`mixed.validate(value: any, options?: object): Promise<any, ValidationError>`](#mixedvalidatevalue-any-options-object-promiseany-validationerror)
+    - [`mixed.validateSync(value: any, options?: object): any`](#mixedvalidatesyncvalue-any-options-object-any)
+    - [`mixed.validateAt(path: string, value: any, options?: object): Promise<any, ValidationError>`](#mixedvalidateatpath-string-value-any-options-object-promiseany-validationerror)
+    - [`mixed.validateSyncAt(path: string, value: any, options?: object): any`](#mixedvalidatesyncatpath-string-value-any-options-object-any)
+    - [`mixed.isValid(value: any, options?: object): Promise<boolean>`](#mixedisvalidvalue-any-options-object-promiseboolean)
+    - [`mixed.isValidSync(value: any, options?: object): boolean`](#mixedisvalidsyncvalue-any-options-object-boolean)
+    - [`mixed.cast(value: any, options = {}): any`](#mixedcastvalue-any-options---any)
     - [`mixed.isType(value: any): boolean`](#mixedistypevalue-any-boolean)
     - [`mixed.strict(isStrict: boolean = false): Schema`](#mixedstrictisstrict-boolean--false-schema)
     - [`mixed.strip(stripField: boolean = true): Schema`](#mixedstripstripfield-boolean--true-schema)
@@ -45,52 +45,52 @@ json separate from validating it, via the `cast` method.
     - [`mixed.default(value: any): Schema`](#mixeddefaultvalue-any-schema)
     - [`mixed.default(): Any`](#mixeddefault-any)
     - [`mixed.nullable(isNullable: boolean = false): Schema`](#mixednullableisnullable-boolean--false-schema)
-    - [`mixed.required(message: ?string): Schema`](#mixedrequiredmessage-string-schema)
+    - [`mixed.required(message?: string | function): Schema`](#mixedrequiredmessage-string--function-schema)
     - [`mixed.notRequired(): Schema`](#mixednotrequired-schema)
     - [`mixed.typeError(message: string): Schema`](#mixedtypeerrormessage-string-schema)
-    - [`mixed.oneOf(arrayOfValues: Array<any>, string: ?message): Schema` Alias: `equals`](#mixedoneofarrayofvalues-arrayany-string-message-schema-alias-equals)
-    - [`mixed.notOneOf(arrayOfValues: Array<any>, string: ?message)`](#mixednotoneofarrayofvalues-arrayany-string-message)
+    - [`mixed.oneOf(arrayOfValues: Array<any>, message?: string | function): Schema` Alias: `equals`](#mixedoneofarrayofvalues-arrayany-message-string--function-schema-alias-equals)
+    - [`mixed.notOneOf(arrayOfValues: Array<any>, message?: string | function)`](#mixednotoneofarrayofvalues-arrayany-message-string--function)
     - [`mixed.when(keys: string | Array<string>, builder: object | (value, schema)=> Schema): Schema`](#mixedwhenkeys-string--arraystring-builder-object--value-schema-schema-schema)
     - [`mixed.test(name: string, message: string | function, test: function): Schema`](#mixedtestname-string-message-string--function-test-function-schema)
     - [`mixed.test(options: object): Schema`](#mixedtestoptions-object-schema)
     - [`mixed.transform((currentValue: any, originalValue: any) => any): Schema`](#mixedtransformcurrentvalue-any-originalvalue-any--any-schema)
   - [string](#string)
-    - [`string.required(message: ?string): Schema`](#stringrequiredmessage-string-schema)
-    - [`string.min(limit: number | Ref, message: ?string): Schema`](#stringminlimit-number--ref-message-string-schema)
-    - [`string.max(limit: number | Ref, message: ?string): Schema`](#stringmaxlimit-number--ref-message-string-schema)
-    - [`string.matches(regex: Regex, message: ?string): Schema`](#stringmatchesregex-regex-message-string-schema)
+    - [`string.required(message?: string | function): Schema`](#stringrequiredmessage-string--function-schema)
+    - [`string.min(limit: number | Ref, message?: string | function): Schema`](#stringminlimit-number--ref-message-string--function-schema)
+    - [`string.max(limit: number | Ref, message?: string | function): Schema`](#stringmaxlimit-number--ref-message-string--function-schema)
+    - [`string.matches(regex: Regex, message?: string | function): Schema`](#stringmatchesregex-regex-message-string--function-schema)
     - [`string.matches(regex: Regex, options: { message: string, excludeEmptyString: bool }): Schema`](#stringmatchesregex-regex-options--message-string-excludeemptystring-bool--schema)
-    - [`string.email(message: ?string): Schema`](#stringemailmessage-string-schema)
-    - [`string.url(message: ?string): Schema`](#stringurlmessage-string-schema)
+    - [`string.email(message?: string | function): Schema`](#stringemailmessage-string--function-schema)
+    - [`string.url(message?: string | function): Schema`](#stringurlmessage-string--function-schema)
     - [`string.ensure(): Schema`](#stringensure-schema)
-    - [`string.trim(message: ?string): Schema`](#stringtrimmessage-string-schema)
-    - [`string.lowercase(message: ?string): Schema`](#stringlowercasemessage-string-schema)
-    - [`string.uppercase(message: ?string): Schema`](#stringuppercasemessage-string-schema)
+    - [`string.trim(message?: string | function): Schema`](#stringtrimmessage-string--function-schema)
+    - [`string.lowercase(message?: string | function): Schema`](#stringlowercasemessage-string--function-schema)
+    - [`string.uppercase(message?: string | function): Schema`](#stringuppercasemessage-string--function-schema)
   - [number](#number)
-    - [`number.min(limit: number | Ref, message: ?string): Schema`](#numberminlimit-number--ref-message-string-schema)
-    - [`number.max(limit: number | Ref, message: ?string): Schema`](#numbermaxlimit-number--ref-message-string-schema)
-    - [`number.lessThan(max: number | Ref, message: ?string): Schema`](#numberlessthanmax-number--ref-message-string-schema)
-    - [`number.moreThan(min: number | Ref, message: ?string): Schema`](#numbermorethanmin-number--ref-message-string-schema)
-    - [`number.positive(message: ?string): Schema`](#numberpositivemessage-string-schema)
-    - [`number.negative(message: ?string): Schema`](#numbernegativemessage-string-schema)
-    - [`number.integer(message: ?string): Schema`](#numberintegermessage-string-schema)
+    - [`number.min(limit: number | Ref, message?: string | function): Schema`](#numberminlimit-number--ref-message-string--function-schema)
+    - [`number.max(limit: number | Ref, message?: string | function): Schema`](#numbermaxlimit-number--ref-message-string--function-schema)
+    - [`number.lessThan(max: number | Ref, message?: string | function): Schema`](#numberlessthanmax-number--ref-message-string--function-schema)
+    - [`number.moreThan(min: number | Ref, message?: string | function): Schema`](#numbermorethanmin-number--ref-message-string--function-schema)
+    - [`number.positive(message?: string | function): Schema`](#numberpositivemessage-string--function-schema)
+    - [`number.negative(message?: string | function): Schema`](#numbernegativemessage-string--function-schema)
+    - [`number.integer(message?: string | function): Schema`](#numberintegermessage-string--function-schema)
     - [`number.truncate(): Schema`](#numbertruncate-schema)
     - [`number.round(type: 'floor' | 'ceil' | 'trunc' | 'round' = 'round'): Schema`](#numberroundtype-floor--ceil--trunc--round--round-schema)
   - [boolean](#boolean)
   - [date](#date)
-    - [`date.min(limit: Date | string | Ref, message: ?string): Schema`](#dateminlimit-date--string--ref-message-string-schema)
-    - [`date.max(limit: Date | string | Ref, message: ?string): Schema`](#datemaxlimit-date--string--ref-message-string-schema)
+    - [`date.min(limit: Date | string | Ref, message?: string | function): Schema`](#dateminlimit-date--string--ref-message-string--function-schema)
+    - [`date.max(limit: Date | string | Ref, message?: string | function): Schema`](#datemaxlimit-date--string--ref-message-string--function-schema)
   - [array](#array)
     - [`array.of(type: Schema): Schema`](#arrayoftype-schema-schema)
-    - [`array.required(message: ?string): Schema`](#arrayrequiredmessage-string-schema)
-    - [`array.min(limit: number | Ref, message: ?string): Schema`](#arrayminlimit-number--ref-message-string-schema)
-    - [`array.max(limit: number | Ref, message: ?string): Schema`](#arraymaxlimit-number--ref-message-string-schema)
+    - [`array.required(message?: string | function): Schema`](#arrayrequiredmessage-string--function-schema)
+    - [`array.min(limit: number | Ref, message?: string | function): Schema`](#arrayminlimit-number--ref-message-string--function-schema)
+    - [`array.max(limit: number | Ref, message?: string | function): Schema`](#arraymaxlimit-number--ref-message-string--function-schema)
     - [`array.ensure(): Schema`](#arrayensure-schema)
     - [`array.compact(rejector: (value) => boolean): Schema`](#arraycompactrejector-value--boolean-schema)
   - [object](#object)
-    - [`object.shape(fields: object, noSortEdges: ?Array<[string, string]>): Schema`](#objectshapefields-object-nosortedges-arraystring-string-schema)
+    - [`object.shape(fields: object, noSortEdges?: Array<[string, string]>): Schema`](#objectshapefields-object-nosortedges-arraystring-string-schema)
     - [`object.from(fromKey: string, toKey: string, alias: boolean = false): Schema`](#objectfromfromkey-string-tokey-string-alias-boolean--false-schema)
-    - [`object.noUnknown(onlyKnownKeys: boolean = true, message: ?string): Schema`](#objectnounknownonlyknownkeys-boolean--true-message-string-schema)
+    - [`object.noUnknown(onlyKnownKeys: boolean = true, message?: string | function): Schema`](#objectnounknownonlyknownkeys-boolean--true-message-string--function-schema)
     - [`object.camelCase(): Schema`](#objectcamelcase-schema)
     - [`object.constantCase(): Schema`](#objectconstantcase-schema)
 - [Extending Schema Types](#extending-schema-types)
@@ -212,7 +212,7 @@ yup.addMethod;
 yup.ValidationError;
 ```
 
-#### `yup.reach(schema: Schema, path: string, value: ?object, context: ?object): Schema`
+#### `yup.reach(schema: Schema, path: string, value?: object, context?: object): Schema`
 
 For nested schema's `yup.reach` will retrieve a nested schema based on the provided path.
 
@@ -340,7 +340,7 @@ SchemaDescription {
   type: string,
   label: string,
   meta: object,
-  tests: Array<string>
+  tests: Array<{ name: string, params: object }>
 }
 ```
 
@@ -363,7 +363,7 @@ Options = {
   abortEarly: boolean = true;
   stripUnknown: boolean = false;
   recursive: boolean = true;
-  context: ?object;
+  context?: object;
 }
 ```
 
@@ -447,25 +447,27 @@ await schema.validateAt('foo[1].bar', rootValue); // -> ValidationError. must be
 
 Same as `validateAt` but synchronous.
 
-#### `mixed.isValid(value: any, options: ?object): Promise<boolean>`
+#### `mixed.isValid(value: any, options?: object): Promise<boolean>`
 
 Returns `true` when the passed in value matches the schema. `isValid`
 is **asynchronous** and returns a Promise object.
 
 Takes the same options as `validate()`.
 
-#### `mixed.isValidSync(value: any, options: ?object): boolean`
+#### `mixed.isValidSync(value: any, options?: object): boolean`
 
 Synchronously returns `true` when the passed in value matches the schema.
 
 Takes the same options as `validateSync()` and has the same caveats around async tests.
 
-#### `mixed.cast(value: any): any`
+#### `mixed.cast(value: any, options = {}): any`
 
 Attempts to coerce the passed in value to a value that matches the schema. For example: `'5'` will
 cast to `5` when using the `number()` type. Failed casts generally return `null`, but may also
 return results like `NaN` and unexpected strings.
 
+`options` parameter can be an object containing `context`. (For more info on `context` see `mixed.validate`)
+
 #### `mixed.isType(value: any): boolean`
 
 Runs a type check against the passed in `value`. It returns true if it matches,
@@ -541,9 +543,9 @@ Calling `default` with no arguments will return the current default value
 Indicates that `null` is a valid value for the schema. Without `nullable()`
 `null` is treated as a different type and will fail `isType()` checks.
 
-#### `mixed.required(message: ?string): Schema`
+#### `mixed.required(message?: string | function): Schema`
 
-Mark the schema as required. All field values apart from `undefined` meet this requirement.
+Mark the schema as required. All field values apart from `undefined` and `null` meet this requirement.
 
 #### `mixed.notRequired(): Schema`
 
@@ -554,7 +556,7 @@ Mark the schema as not required. Passing `undefined` as value will not fail vali
 Define an error message for failed type checks. The `${value}` and `${type}` interpolation can
 be used in the `message` argument.
 
-#### `mixed.oneOf(arrayOfValues: Array<any>, string: ?message): Schema` Alias: `equals`
+#### `mixed.oneOf(arrayOfValues: Array<any>, message?: string | function): Schema` Alias: `equals`
 
 Whitelist a set of values. Values added are automatically removed from any blacklist if they are in it.
 The `${values}` interpolation can be used in the `message` argument.
@@ -566,7 +568,7 @@ schema.isValid('jimmy'); //=> true
 schema.isValid(new Date()); //=> false
 ```
 
-#### `mixed.notOneOf(arrayOfValues: Array<any>, string: ?message)`
+#### `mixed.notOneOf(arrayOfValues: Array<any>, message?: string | function)`
 
 Blacklist a set of values. Values added are automatically removed from any whitelist if they are in it.
 The `${values}` interpolation can be used in the `message` argument.
@@ -609,21 +611,20 @@ You can also specify more than one dependent key, in which case each value will
 
 ```javascript
 var inst = yup.object({
-      isSpecial: yup.bool(),
-      isBig: yup.bool(),
-      count: yup.number()
-        .when(['isBig', 'isSpecial'], {
-          is: true,  // alternatively: (isBig, isSpecial) => isBig && isSpecial
-          then:      yup.number().min(5),
-          otherwise: yup.number().min(0)
-        })
-    })
+  isSpecial: yup.bool(),
+  isBig: yup.bool(),
+  count: yup.number().when(['isBig', 'isSpecial'], {
+    is: true, // alternatively: (isBig, isSpecial) => isBig && isSpecial
+    then: yup.number().min(5),
+    otherwise: yup.number().min(0),
+  }),
+});
 
 inst.validate({
   isBig: true,
   isSpecial: true,
-  count: 10
-})
+  count: 10,
+});
 ```
 
 Alternatively you can provide a function that returns a schema
@@ -712,6 +713,7 @@ If an exclusive test is added to a schema with non-exclusive tests of the same n
 the previous tests are removed and further tests of the same name will replace each other.
 
 ```javascript
+var max = 64;
 var schema = yup.mixed().test({
   name: 'max',
   exclusive: true,
@@ -770,19 +772,19 @@ empty values are not coerced (use `ensure()` to coerce empty values to empty str
 
 Failed casts return the input value.
 
-#### `string.required(message: ?string): Schema`
+#### `string.required(message?: string | function): Schema`
 
 The same as the `mixed()` schema required, except that empty strings are also considered 'missing' values.
 
-#### `string.min(limit: number | Ref, message: ?string): Schema`
+#### `string.min(limit: number | Ref, message?: string | function): Schema`
 
 Set an minimum length limit for the string value. The `${min}` interpolation can be used in the `message` argument
 
-#### `string.max(limit: number | Ref, message: ?string): Schema`
+#### `string.max(limit: number | Ref, message?: string | function): Schema`
 
 Set an maximum length limit for the string value. The `${max}` interpolation can be used in the `message` argument
 
-#### `string.matches(regex: Regex, message: ?string): Schema`
+#### `string.matches(regex: Regex, message?: string | function): Schema`
 
 Provide an arbitrary `regex` to match the value against.
 
@@ -808,11 +810,11 @@ v.isValid('')
   .equal(false);
 ```
 
-#### `string.email(message: ?string): Schema`
+#### `string.email(message?: string | function): Schema`
 
 Validates the value as an email address via a regex.
 
-#### `string.url(message: ?string): Schema`
+#### `string.url(message?: string | function): Schema`
 
 Validates the value as a valid URL via a regex.
 
@@ -821,17 +823,17 @@ Validates the value as a valid URL via a regex.
 Transforms `undefined` and `null` values to an empty string along with
 setting the `default` to an empty string.
 
-#### `string.trim(message: ?string): Schema`
+#### `string.trim(message?: string | function): Schema`
 
 Transforms string values by removing leading and trailing whitespace. If
 `strict()` is set it will only validate that the value is trimmed.
 
-#### `string.lowercase(message: ?string): Schema`
+#### `string.lowercase(message?: string | function): Schema`
 
 Transforms the string value to lowercase. If `strict()` is set it
 will only validate that the value is lowercase.
 
-#### `string.uppercase(message: ?string): Schema`
+#### `string.uppercase(message?: string | function): Schema`
 
 Transforms the string value to uppercase. If `strict()` is set it
 will only validate that the value is uppercase.
@@ -849,35 +851,35 @@ The default `cast` logic of `number` is: [`parseFloat`](https://developer.mozill
 
 Failed casts return `NaN`.
 
-#### `number.min(limit: number | Ref, message: ?string): Schema`
+#### `number.min(limit: number | Ref, message?: string | function): Schema`
 
 Set the minimum value allowed. The `${min}` interpolation can be used in the
 `message` argument.
 
-#### `number.max(limit: number | Ref, message: ?string): Schema`
+#### `number.max(limit: number | Ref, message?: string | function): Schema`
 
 Set the maximum value allowed. The `${max}` interpolation can be used in the
 `message` argument.
 
-#### `number.lessThan(max: number | Ref, message: ?string): Schema`
+#### `number.lessThan(max: number | Ref, message?: string | function): Schema`
 
-Value must be less than `max`. The `${max}` interpolation can be used in the
+Value must be less than `max`. The `${less}` interpolation can be used in the
 `message` argument.
 
-#### `number.moreThan(min: number | Ref, message: ?string): Schema`
+#### `number.moreThan(min: number | Ref, message?: string | function): Schema`
 
-Value must be strictly greater than `min`. The `${min}` interpolation can be used in the
+Value must be strictly greater than `min`. The `${more}` interpolation can be used in the
 `message` argument.
 
-#### `number.positive(message: ?string): Schema`
+#### `number.positive(message?: string | function): Schema`
 
 Value must be a positive number.
 
-#### `number.negative(message: ?string): Schema`
+#### `number.negative(message?: string | function): Schema`
 
 Value must be a negative number.
 
-#### `number.integer(message: ?string): Schema`
+#### `number.integer(message?: string | function): Schema`
 
 Validates that a number is an integer.
 
@@ -915,12 +917,12 @@ to parse the date as an ISO date string.
 
 Failed casts return an invalid Date.
 
-#### `date.min(limit: Date | string | Ref, message: ?string): Schema`
+#### `date.min(limit: Date | string | Ref, message?: string | function): Schema`
 
 Set the minimum date allowed. When a string is provided it will attempt to cast to a date first
 and use the result as the limit.
 
-#### `date.max(limit: Date | string | Ref, message: ?string): Schema`
+#### `date.max(limit: Date | string | Ref, message?: string | function): Schema`
 
 Set the maximum date allowed, When a string is provided it will attempt to cast to a date first
 and use the result as the limit.
@@ -956,15 +958,15 @@ Failed casts return: `null`;
 Specify the schema of array elements. `of()` is optional and when omitted the array schema will
 not validate its contents.
 
-#### `array.required(message: ?string): Schema`
+#### `array.required(message?: string | function): Schema`
 
 The same as the `mixed()` schema required, except that empty arrays are also considered 'missing' values.
 
-#### `array.min(limit: number | Ref, message: ?string): Schema`
+#### `array.min(limit: number | Ref, message?: string | function): Schema`
 
 Set an minimum length limit for the array. The `${min}` interpolation can be used in the `message` argument.
 
-#### `array.max(limit: number | Ref, message: ?string): Schema`
+#### `array.max(limit: number | Ref, message?: string | function): Schema`
 
 Set an maximum length limit for the array. The `${max}` interpolation can be used in the `message` argument.
 
@@ -1034,7 +1036,7 @@ The default `cast` behavior for `object` is: [`JSON.parse`](https://developer.mo
 
 Failed casts return: `null`;
 
-#### `object.shape(fields: object, noSortEdges: ?Array<[string, string]>): Schema`
+#### `object.shape(fields: object, noSortEdges?: Array<[string, string]>): Schema`
 
 Define the keys of the object and the schemas for said keys.
 
@@ -1075,7 +1077,7 @@ var schema = object({
 inst.cast({ prop: 5, other: 6 }); // => { myProp: 5, other: 6, Other: 6 }
 ```
 
-#### `object.noUnknown(onlyKnownKeys: boolean = true, message: ?string): Schema`
+#### `object.noUnknown(onlyKnownKeys: boolean = true, message?: string | function): Schema`
 
 Validate that the object value only contains keys specified in `shape`, pass `false` as the first
 argument to disable the check. Restricting keys to known, also enables `stripUnknown` option, when not in strict mode.
diff --git a/package.json b/package.json
index 4d5ae44..5f7f2f6 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "yup",
-  "version": "0.26.6",
+  "version": "0.26.10",
   "description": "Dead simple Object schema validation",
   "main": "lib/index.js",
   "runkitExampleFilename": "./runkit-example.js",
@@ -12,6 +12,7 @@
     "lint": "eslint src test",
     "precommit": "lint-staged",
     "toc": "doctoc README.md --github",
+    "release": "rollout",
     "build": "babel src --out-dir lib --delete-dir-on-start && npm run toc",
     "prepublishOnly": "npm run build"
   },
@@ -28,6 +29,9 @@
     "url": "https://github.com/jquense/yup/issues"
   },
   "homepage": "https://github.com/jquense/yup",
+  "release": {
+    "conventional-commits": true
+  },
   "prettier": {
     "singleQuote": true,
     "trailingComma": "all"
diff --git a/src/Lazy.js b/src/Lazy.js
index 3eb9ca8..d092667 100644
--- a/src/Lazy.js
+++ b/src/Lazy.js
@@ -10,18 +10,24 @@ class Lazy {
       return schema;
     };
   }
-
   resolve({ value, ...rest }) {
     return this._resolve(value, rest);
   }
-
   cast(value, options) {
     return this._resolve(value, options).cast(value, options);
   }
-
   validate(value, options) {
     return this._resolve(value, options).validate(value, options);
   }
+  validateSync(value, options) {
+    return this._resolve(value, options).validateSync(value, options);
+  }
+  validateAt(path, value, options) {
+    return this._resolve(value, options).validateAt(path, value, options);
+  }
+  validateSyncAt(path, value, options) {
+    return this._resolve(value, options).validateSyncAt(path, value, options);
+  }
 }
 
 Lazy.prototype.__isYupSchema__ = true;
diff --git a/src/array.js b/src/array.js
index 29dceea..e91084a 100644
--- a/src/array.js
+++ b/src/array.js
@@ -163,9 +163,10 @@ inherits(ArraySchema, MixedSchema, {
   },
 
   ensure() {
-    return this.default(() => []).transform(
-      val => (val === null ? [] : [].concat(val)),
-    );
+    return this.default(() => []).transform(val => {
+      if (this.isType(val)) return val;
+      return val === null ? [] : [].concat(val);
+    });
   },
 
   compact(rejector) {
diff --git a/src/mixed.js b/src/mixed.js
index ec166b8..ac45454 100644
--- a/src/mixed.js
+++ b/src/mixed.js
@@ -123,7 +123,7 @@ const proto = (SchemaType.prototype = {
     // manually add the new tests to ensure
     // the deduping logic is consistent
     schema.tests.forEach(fn => {
-      next = next.test(fn.TEST);
+      next = next.test(fn.OPTIONS);
     });
 
     next._type = schema._type;
@@ -311,7 +311,7 @@ const proto = (SchemaType.prototype = {
 
   notRequired() {
     var next = this.clone();
-    next.tests = next.tests.filter(test => test.TEST_NAME !== 'required');
+    next.tests = next.tests.filter(test => test.OPTIONS.name !== 'required');
     return next;
   },
 
@@ -369,9 +369,9 @@ const proto = (SchemaType.prototype = {
     next._exclusive[opts.name] = !!opts.exclusive;
 
     next.tests = next.tests.filter(fn => {
-      if (fn.TEST_NAME === opts.name) {
+      if (fn.OPTIONS.name === opts.name) {
         if (isExclusive) return false;
-        if (fn.TEST.test === validate.TEST.test) return false;
+        if (fn.OPTIONS.test === validate.OPTIONS.test) return false;
       }
       return true;
     });
@@ -477,15 +477,17 @@ const proto = (SchemaType.prototype = {
   },
 
   describe() {
-    let next = this.clone();
+    const next = this.clone();
 
     return {
       type: next._type,
       meta: next._meta,
       label: next._label,
       tests: next.tests
-        .map(fn => fn.TEST_NAME, {})
-        .filter((n, idx, list) => list.indexOf(n) === idx),
+        .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/number.js b/src/number.js
index 59a3efc..be65452 100644
--- a/src/number.js
+++ b/src/number.js
@@ -14,12 +14,18 @@ export default function NumberSchema() {
 
   this.withMutation(() => {
     this.transform(function(value) {
-      if (this.isType(value)) return value;
+      let parsed = value
+      
+      if (typeof parsed === 'string') {
+        parsed = parsed.replace(/\s/g, '');
+        if (parsed === '') return NaN;
+        // don't use parseFloat to avoid positives on alpha-numeric strings
+        parsed = +parsed;
+      }
 
-      let parsed = parseFloat(value);
       if (this.isType(parsed)) return parsed;
 
-      return NaN;
+      return parseFloat(parsed);
     });
   });
 }
@@ -80,11 +86,11 @@ inherits(NumberSchema, MixedSchema, {
   },
 
   positive(msg = locale.positive) {
-    return this.min(0, msg);
+    return this.moreThan(0, msg);
   },
 
   negative(msg = locale.negative) {
-    return this.max(0, msg);
+    return this.lessThan(0, msg);
   },
 
   integer(message = locale.integer) {
diff --git a/src/object.js b/src/object.js
index 61f6db7..f8b3f96 100644
--- a/src/object.js
+++ b/src/object.js
@@ -104,7 +104,7 @@ inherits(ObjectSchema, MixedSchema, {
         field = field.resolve(innerOptions);
 
         if (field._strip === true) {
-          isChanged = true;
+          isChanged = isChanged || prop in value;
           return;
         }
 
@@ -156,16 +156,15 @@ inherits(ObjectSchema, MixedSchema, {
             originalValue: originalValue[key],
           };
 
-          if (field) {
+          if (field && field.validate) {
             // 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
             innerOptions.strict = true;
-
-            if (field.validate) return field.validate(value[key], innerOptions);
+            return field.validate(value[key], innerOptions);
           }
 
-          return true;
+          return Promise.resolve(true);
         });
 
         return runValidations({
diff --git a/src/util/createValidation.js b/src/util/createValidation.js
index 48f23de..3cb4c2c 100644
--- a/src/util/createValidation.js
+++ b/src/util/createValidation.js
@@ -27,7 +27,13 @@ function resolveParams(oldParams, newParams, resolve) {
   return mapValues({ ...oldParams, ...newParams }, resolve);
 }
 
-function createErrorFactory({ value, label, resolve, originalValue, ...opts }) {
+export function createErrorFactory({
+  value,
+  label,
+  resolve,
+  originalValue,
+  ...opts
+}) {
   return function createError({
     path = opts.path,
     message = opts.message,
@@ -92,11 +98,7 @@ export default function createValidation(options) {
     });
   }
 
-  validate.TEST_NAME = name;
-  validate.TEST_FN = test;
-  validate.TEST = options;
+  validate.OPTIONS = options;
 
   return validate;
 }
-
-module.exports.createErrorFactory = createErrorFactory;
diff --git a/src/util/reach.js b/src/util/reach.js
index 772d018..d033d19 100644
--- a/src/util/reach.js
+++ b/src/util/reach.js
@@ -50,7 +50,7 @@ export function getIn(schema, path, value, context) {
 
       parent = value;
       value = value && value[part];
-      lastPart = _part;
+      lastPart = part;
       lastPartDebug = isBracket ? '[' + _part + ']' : '.' + _part;
     }
   });
diff --git a/src/util/runValidations.js b/src/util/runValidations.js
index 12346a4..e977452 100644
--- a/src/util/runValidations.js
+++ b/src/util/runValidations.js
@@ -36,13 +36,16 @@ export function propagateErrors(endEarly, errors) {
 }
 
 export function settled(promises, sync) {
-  let settle = promise =>
-    promise.then(
-      value => ({ fulfilled: true, value }),
-      value => ({ fulfilled: false, value }),
-    );
+  const Promise = promise(sync);
 
-  return promise(sync).all(promises.map(settle));
+  return Promise.all(
+    promises.map(p =>
+      Promise.resolve(p).then(
+        value => ({ fulfilled: true, value }),
+        value => ({ fulfilled: false, value }),
+      ),
+    ),
+  );
 }
 
 export function collectErrors({


Test patch:
diff --git a/test/array.js b/test/array.js
index 455f002..ff303ef 100644
--- a/test/array.js
+++ b/test/array.js
@@ -170,7 +170,8 @@ describe('Array types', () => {
   it('should ensure arrays', () => {
     var inst = array().ensure();
 
-    inst.cast([1, 4]).should.eql([1, 4]);
+    const a = [1, 4];
+    inst.cast(a).should.equal(a);
 
     inst.cast(null).should.eql([]);
   });
diff --git a/test/lazy.js b/test/lazy.js
new file mode 100644
index 0000000..7de2c07
--- /dev/null
+++ b/test/lazy.js
@@ -0,0 +1,30 @@
+import { lazy, mixed } from '../src';
+
+describe('lazy', function() {
+  it('should throw on a non-schema value', () => {
+    (() => lazy(() => undefined).validate()).should.throw();
+  });
+
+  describe('mapper', () => {
+    const value = 1;
+    let mapper;
+
+    beforeEach(() => {
+      mapper = sinon.stub();
+      mapper.returns(mixed());
+    });
+
+    it('should call with value', () => {
+      lazy(mapper).validate(value);
+      mapper.should.have.been.calledWith(value);
+    });
+
+    it('should call with context', () => {
+      const context = {
+        a: 1,
+      };
+      lazy(mapper).validate(value, context);
+      mapper.should.have.been.calledWithExactly(value, context);
+    });
+  });
+});
diff --git a/test/mixed.js b/test/mixed.js
index 511e53b..153f79e 100644
--- a/test/mixed.js
+++ b/test/mixed.js
@@ -334,8 +334,8 @@ describe('Mixed Types ', () => {
     let inst = mixed().test('test', noop);
 
     inst.tests.length.should.equal(1);
-    inst.tests[0].TEST.test.should.equal(noop);
-    inst.tests[0].TEST.message.should.equal('${path} is invalid');
+    inst.tests[0].OPTIONS.test.should.equal(noop);
+    inst.tests[0].OPTIONS.message.should.equal('${path} is invalid');
   });
 
   it('should allow non string messages', async () => {
@@ -343,7 +343,7 @@ describe('Mixed Types ', () => {
     let inst = mixed().test('test', message, () => false);
 
     inst.tests.length.should.equal(1);
-    inst.tests[0].TEST.message.should.equal(message);
+    inst.tests[0].OPTIONS.message.should.equal(message);
 
     let error = await inst.validate('foo').should.be.rejected();
 
@@ -356,7 +356,7 @@ describe('Mixed Types ', () => {
       .test('test', 'asdasd', noop);
 
     inst.tests.length.should.equal(1);
-    inst.tests[0].TEST.message.should.equal('asdasd');
+    inst.tests[0].OPTIONS.message.should.equal('asdasd');
   });
 
   it('should not dedupe tests with the same test function and different type', () => {
@@ -538,7 +538,7 @@ describe('Mixed Types ', () => {
     });
 
     it('should have the tests in the correct order', () => {
-      reach(next, 'str').tests[0].TEST_NAME.should.equal('required');
+      reach(next, 'str').tests[0].OPTIONS.name.should.equal('required');
     });
 
     it('should validate correctly', async () => {
@@ -730,8 +730,8 @@ describe('Mixed Types ', () => {
 
   it('should describe', () => {
     const desc = object({
-      foos: array(number().integer()).required(),
-      foo: string()
+      foo: array(number().integer()).required(),
+      bar: string()
         .max(2)
         .meta({ input: 'foo' })
         .label('str!'),
@@ -743,22 +743,32 @@ describe('Mixed Types ', () => {
       label: undefined,
       tests: [],
       fields: {
-        foos: {
+        foo: {
           type: 'array',
           meta: undefined,
           label: undefined,
-          tests: ['required'],
+          tests: [
+            {
+              name: 'required',
+              params: undefined,
+            },
+          ],
           innerType: {
             type: 'number',
             meta: undefined,
             label: undefined,
-            tests: ['integer'],
+            tests: [
+              {
+                name: 'integer',
+                params: undefined,
+              },
+            ],
           },
         },
-        foo: {
+        bar: {
           type: 'string',
           label: 'str!',
-          tests: ['max'],
+          tests: [{ name: 'max', params: { max: 2 } }],
           meta: {
             input: 'foo',
           },
diff --git a/test/number.js b/test/number.js
index 6b47c1c..4d9144b 100644
--- a/test/number.js
+++ b/test/number.js
@@ -115,6 +115,20 @@ describe('Number types', function() {
         .isValid(null)
         .should.eventually()
         .equal(true),
+      number()
+        .isValid(' ')
+        .should.eventually()
+        .equal(false),
+      number()
+        .isValid('12abc')
+        .should.eventually()
+        .equal(false),
+      number()
+        .isValid(0xff)
+        .should.eventually.equal(true),
+      number()
+        .isValid('0xff')
+        .should.eventually.equal(true),
 
       inst
         .isValid(5)
@@ -186,32 +200,19 @@ describe('Number types', function() {
   });
 
   describe('integer', () => {
-    TestHelpers.validateAll(number().integer(), {
+    var schema = number().integer();
+
+    TestHelpers.validateAll(schema, {
       valid: [4, -5222],
       invalid: [10.53, 0.1 * 0.2, -34512535.626, 3.12312e51, new Date()],
     });
-  });
-  it('should check integer', function() {
-    var v = number().positive();
 
-    return Promise.all([
-      v
-        .isValid(7)
-        .should.eventually()
-        .equal(true),
-
-      v
-        .isValid(0)
-        .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');
-        }),
-    ]);
+    it('should return default message', () => {
+      return schema
+        .validate(10.53)
+        .should.be.rejected.and.eventually.have.property('errors')
+        .that.contain('this must be an integer');
+    });
   });
 
   it('should check POSITIVE correctly', function() {
@@ -226,10 +227,10 @@ describe('Number types', function() {
       v
         .isValid(0)
         .should.eventually()
-        .equal(true),
+        .equal(false),
 
       v
-        .validate(-4)
+        .validate(0)
         .should.be.rejected()
         .then(null, function(err) {
           err.errors[0].should.contain('this must be a positive number');
@@ -249,7 +250,7 @@ describe('Number types', function() {
       v
         .isValid(0)
         .should.eventually()
-        .equal(true),
+        .equal(false),
 
       v
         .validate(10)
diff --git a/test/object.js b/test/object.js
index 02acfb3..543fd39 100644
--- a/test/object.js
+++ b/test/object.js
@@ -23,6 +23,7 @@ describe('Object types', () => {
         dte: date(),
         nested: object().shape({ str: string() }),
         arrNested: array().of(object().shape({ num: number() })),
+        stripped: string().strip(),
       });
     });
 
@@ -381,6 +382,24 @@ describe('Object types', () => {
     });
   });
 
+  it('should allow refs with abortEarly false', async () => {
+    let schema = object().shape({
+      field: string(),
+      dupField: ref('field'),
+    });
+
+    let actual = await schema
+      .validate(
+        {
+          field: 'test',
+        },
+        { abortEarly: false },
+      )
+      .should.not.be.rejected();
+
+    actual.should.eql({ field: 'test', dupField: 'test' });
+  });
+
   describe('lazy evaluation', () => {
     let types = {
       string: string(),
diff --git a/test/yup.js b/test/yup.js
index 8d9c34f..51f54e5 100644
--- a/test/yup.js
+++ b/test/yup.js
@@ -60,19 +60,19 @@ describe('Yup', function() {
         num: number().max(4),
 
         nested: object().shape({
-          arr: array().of(object().shape({ num: num })),
+          arr: array().of(object().shape({ 'num-1': num })),
         }),
       });
 
-    const value = { nested: { arr: [{}, { num: 2 }] } };
+    const value = { nested: { arr: [{}, { 'num-1': 2 }] } };
     const { schema, parent, parentPath } = getIn(
       inst,
-      'nested.arr[1].num',
+      'nested.arr[1].num-1',
       value,
     );
 
     expect(schema).to.equal(num);
-    expect(parentPath).to.equal('num');
+    expect(parentPath).to.equal('num-1');
     expect(parent).to.equal(value.nested.arr[1]);
   });