Repository: jquense/yup (ts)

Issue:
Add test `params` to `SchemaDescription`
In some situations, it may be useful to retrieve test `params`. For e.g. given we have some schema creator:
```
const getSchema = count => yup.mixed().test({
  name: 'max',
  exclusive: true,
  params: { count },
  message: 'Must be less than ${count} characters',
  test: value => value == null || value.length <= count,
});

const schema = getSchema(21);
```
With `formik` we then can connect validation with UI but often it's useful to display some help text (in this case we can display current length and max `3 of 21`).
Currently, it's not possible to retrieve `params` passed to yup test.
To improve this we can add an additional field to `SchemaDescription` called `testsParams`.
```
const description = schema. describe();

console.log(description);
// {
//     ...
//     testsParams: { max: { count: 21 } },
// }
```

@jquense what do you think?

Pull request:
#411 Add testsParams to SchemaDescription
Closes #411 

Patch:
diff --git a/README.md b/README.md
index c6a7925..327adad 100644
--- a/README.md
+++ b/README.md
@@ -340,7 +340,7 @@ SchemaDescription {
   type: string,
   label: string,
   meta: object,
-  tests: Array<string>
+  tests: Array<{ name: string, params: object }>
 }
 ```
 
@@ -609,21 +609,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 +711,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,
@@ -861,12 +861,12 @@ Set the maximum value allowed. The `${max}` interpolation can be used in the
 
 #### `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 | 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 | function): Schema`
diff --git a/package.json b/package.json
index 4d5ae44..76c0206 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "yup",
-  "version": "0.26.6",
+  "version": "0.26.7",
   "description": "Dead simple Object schema validation",
   "main": "lib/index.js",
   "runkitExampleFilename": "./runkit-example.js",
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 9621d56..be65452 100644
--- a/src/number.js
+++ b/src/number.js
@@ -86,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/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;
     }
   });


Test patch:
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 6f55b7b..4d9144b 100644
--- a/test/number.js
+++ b/test/number.js
@@ -200,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() {
@@ -240,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');
@@ -263,7 +250,7 @@ describe('Number types', function() {
       v
         .isValid(0)
         .should.eventually()
-        .equal(true),
+        .equal(false),
 
       v
         .validate(10)
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]);
   });