Repository: baianat/vee-validate (js)

Issue:
Verify - dependent rules
The docs state that `validator.verify()` cannot use dependent rules without `v-validate`:

> Note that Target Dependant Rules won't work as they require a target field being tracked which cannot be done without using the v-validate directive.

However, I don't understand why this is so.

Surely as an independent function, the additional values / rules could be passed in?

```js
validator.verify(value, rule, values, rules)
```

In my case, I will have all these, so it should be simple to derive a response?


Pull request description:
Verify Options
This introduces an options object as a third parameter that can be passed to the `validator.verify` method, which allows configuring field name, bailing behavior, and providing a value map to cross check for target dependent rules.

The options are:
```js
{
  bails: true,
  name: '{field}',
  values: {}
}
```

closes #1628 

Patch:
diff --git a/flow/validator.js b/flow/validator.js
index f32ef870..8e4ec239 100644
--- a/flow/validator.js
+++ b/flow/validator.js
@@ -46,4 +46,10 @@ declare type FieldOptions = {
   targetOf?: string | null, // the id of the field that targets this one
   validity?: boolean, // if constrained validation API should be used (mdn)
   vm?: any // the vue instance that owns this field
-};
\ No newline at end of file
+};
+
+declare type VerifyOptions = {
+  name?: string,
+  values?: MapObject,
+  bails?: boolean
+};
diff --git a/src/core/validator.js b/src/core/validator.js
index 9e4e225e..0fe45925 100644
--- a/src/core/validator.js
+++ b/src/core/validator.js
@@ -1,5 +1,5 @@
 import ErrorBag from './errorBag';
-import { isObject, isCallable, toArray, createError, assign, find, isNullOrUndefined, includes, normalizeRules, isEmptyArray } from './utils';
+import { isObject, getPath, isCallable, toArray, createError, assign, find, isNullOrUndefined, includes, normalizeRules, isEmptyArray } from './utils';
 import FieldBag from './fieldBag';
 import Field from './field';
 import Config from '../config';
@@ -370,9 +370,22 @@ export default class Validator {
   /**
    * Validates a value against the rules.
    */
-  verify (value: any, rules: string | MapObject) {
-    const field = { name: '{field}', rules: normalizeRules(rules) };
+  verify (value: any, rules: string | MapObject, options?: VerifyOptions = {}): Promise<VerifyOptions> {
+    const field = {
+      name: (options && options.name) || '{field}',
+      rules: normalizeRules(rules),
+      bails: getPath('bails', options, true)
+    };
+
     field.isRequired = field.rules.required;
+    const targetRules = Object.keys(field.rules).filter(Validator.isTargetRule);
+    if (targetRules.length && options && isObject(options.values)) {
+      // patch the field params with the targets' values.
+      targetRules.forEach(rule => {
+        const [first, ...rest] = field.rules[rule];
+        field.rules[rule] = [options.values[first], ...rest];
+      });
+    }
 
     return this._validate(field, value).then(result => {
       return { valid: result.valid, errors: result.errors.map(e => e.msg) };
@@ -508,7 +521,7 @@ export default class Validator {
     }
 
     // has field dependencies.
-    if (rule.options.hasTarget) {
+    if (rule.options.hasTarget && field.dependencies) {
       const target = find(field.dependencies, d => d.name === rule.name);
       if (target) {
         targetName = target.field.alias;
@@ -685,7 +698,7 @@ export default class Validator {
     return !field.isRequired && (isNullOrUndefined(value) || value === '' || isEmptyArray(value));
   }
 
-  _shouldBail (field, value) {
+  _shouldBail (field) {
     // if the field was configured explicitly.
     if (field.bails !== undefined) {
       return field.bails;
@@ -715,7 +728,7 @@ export default class Validator {
       const result = this._test(field, value, { name: rule, params: field.rules[rule], options: ruleOptions });
       if (isCallable(result.then)) {
         promises.push(result);
-      } else if (!result.valid && this._shouldBail(field, value)) {
+      } else if (!result.valid && this._shouldBail(field)) {
         errors.push(...result.errors);
         isExitEarly = true;
       } else {
diff --git a/types/vee-validate.d.ts b/types/vee-validate.d.ts
index 07ea1076..8335da42 100644
--- a/types/vee-validate.d.ts
+++ b/types/vee-validate.d.ts
@@ -126,6 +126,12 @@ export interface VerifyResult {
     errors: string[];
 }
 
+export interface VerifyOptions {
+    bails: boolean;
+    name: string;
+    values: { [x: string]: any };
+}
+
 export class Validator {
     errors: ErrorBag;
     fields: FieldBag;
@@ -155,7 +161,7 @@ export class Validator {
     validate(name: string, value?: any, scope?: string, silent?: boolean): Promise<any>;
     validateAll(values?: Object, scope?: string, silent?: boolean): Promise<any>;
     validateScopes(silent?: boolean): Promise<any>;
-    verify(value: any, rules: string|Object): Promise<VerifyResult>;
+    verify(value: any, rules: string|Object, options?: VerifyOptions): Promise<VerifyResult>;
     static create(validations: Object, options: any): Validator;
     static extend(name: string, validator: Object|Function, options?:ExtendOptions): void;
     static remove(name: string): void;


Test patch:
diff --git a/tests/unit/validator.js b/tests/unit/validator.js
index 8d0137b1..b1b9c6e8 100644
--- a/tests/unit/validator.js
+++ b/tests/unit/validator.js
@@ -1067,18 +1067,52 @@ test('removes the vm localeChanged listener when the validator is destroyed', ()
   expect(v._vm.$off).toHaveBeenCalled();
 });
 
-test('validates values without adding fields', async () => {
-  const v = new Validator();
-  expect(await v.verify('test', 'max:3')).toEqual({ 'errors': ['The {field} field may not be greater than 3 characters.'], 'valid': false });
-  expect(v.errors.count()).toBe(0); // Errors not added.
-  expect(await v.verify('tst', 'max:3')).toEqual({ valid: true, errors: [] });
+describe('Verify API', () => {
+  test('passing values and results', async () => {
+    const v = new Validator();
+    expect(await v.verify('test', 'max:3')).toEqual({ 'errors': ['The {field} field may not be greater than 3 characters.'], 'valid': false });
+    expect(v.errors.count()).toBe(0); // Errors not added.
+    expect(await v.verify('tst', 'max:3')).toEqual({ valid: true, errors: [] });
+    // test required rule
+    expect(await v.verify('', 'required')).toEqual({ 'errors': ['The {field} field is required.'], 'valid': false });
+    // test #1353
+    expect(await v.verify('föö@bar.de', { email: { allow_utf8_local_part: true } })).toEqual({ valid: true, errors: [] });
+    expect(await v.verify('föö@bar.de', { email: { allow_utf8_local_part: false } })).toEqual({ valid: false, errors: ['The {field} field must be a valid email.'] });
+  });
 
-  // test required rule
-  expect(await v.verify('', 'required')).toEqual({ 'errors': ['The {field} field is required.'], 'valid': false });
+  test('target rules validation using options.values', async () => {
+    const v = new Validator();
+    let result = await v.verify('test', 'confirmed:pass', {
+      values: {
+        pass: 'tes'
+      }
+    });
+    expect(result.valid).toBe(false);
+    result = await v.verify('test', 'confirmed:pass', {
+      values: {
+        pass: 'test'
+      }
+    });
+    expect(result.valid).toBe(true);
+  });
 
-  // test #1353
-  expect(await v.verify('föö@bar.de', { email: { allow_utf8_local_part: true } })).toEqual({ valid: true, errors: [] });
-  expect(await v.verify('föö@bar.de', { email: { allow_utf8_local_part: false } })).toEqual({ valid: false, errors: ['The {field} field must be a valid email.'] });
+  test('bailing using options.bail', async () => {
+    const v = new Validator();
+    let { errors } = await v.verify('', 'required|min:3', {
+      bails: false
+    });
+    expect(errors).toHaveLength(2);
+    expect(errors).toEqual([
+      'The {field} field is required.',
+      'The {field} field must be at least 3 characters.'
+    ]);
+  });
+
+  test('customize field name using options.name', async () => {
+    const v = new Validator();
+    const { errors } = await v.verify('', 'required', { name: 'username' });
+    expect(errors).toEqual(['The username field is required.']);
+  });
 });
 
 test('maps rules params array to an object', async () => {