Repository: baianat/vee-validate
Pull request #1677 (2018-10-26 13:17:32)

observer component
__Overview__

This PR adds `ValidationObserver` component to address some issues.

Introducing Validation via components: `ValidationProvider` while very useful but it introuduces its own problems:

__The current state of the form__

For example how you would tell the validation state of the current form, for example we may want to disable a button when the form is invalid.

Injecting more properties may sound tempting but it isn't clean and it is forced, no way to opt-out, however using another slot-scope to offer those properties is more appropiate, and suitable.

```vue
<ValidationObserver>
  <template slot-scope="{ invalid }">
    <InputWithValidation rules="required" type="text" v-model="first" :error-messages="errors" />

    <InputWithValidation rules="required" type="text" v-model="second" :error-messages="errors" />
    <v-btn @click="validate">Submit</v-btn>
  </template>
</ValidationObserver>
```

__No scoping/grouping__

Using multiple observers we can scope the fields without having to deal with the complicated API that comes with them.

```vue
<ValidationObserver>
  <template slot-scope="{ invalid }">
    <InputWithValidation rules="required" type="text" v-model="first" :error-messages="errors" />

	<!-- Other fields -->
  </template>
</ValidationObserver>

<ValidationObserver>
  <template slot-scope="{ invalid }">
    <InputWithValidation rules="required" type="text" v-model="first" :error-messages="errors" />

	<!-- Other fields -->
  </template>
</ValidationObserver>
```

__Manual Validation__

Validating fields manually is as easy as adding a ref on the observer and calling `validate()` off that observer.

```vue
<template>
  <ValidationObserver ref="form">
    <template slot-scope="{ invalid }">
	  <!-- fields -->
    </template>
  </ValidationObserver>
</template>

<script>
export default {
  // ...
  methods: {
  validate () {
      return this.$refs.form.validate();
    }
  },
  // ...
};
</script>
```



codecov[bot]: # [Codecov](https://codecov.io/gh/baianat/vee-validate/pull/1677?src=pr&el=h1) Report
> Merging [#1677](https://codecov.io/gh/baianat/vee-validate/pull/1677?src=pr&el=desc) into [master](https://codecov.io/gh/baianat/vee-validate/commit/44a71b370114360ad8448999b4fc9a9f6f73c20d?src=pr&el=desc) will **decrease** coverage by `0.66%`.
> The diff coverage is `58.33%`.

[![Impacted file tree graph](https://codecov.io/gh/baianat/vee-validate/pull/1677/graphs/tree.svg?width=650&token=XFzlvHKFP5&height=150&src=pr)](https://codecov.io/gh/baianat/vee-validate/pull/1677?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1677      +/-   ##
==========================================
- Coverage   98.57%   97.91%   -0.67%     
==========================================
  Files          55       56       +1     
  Lines        2035     2060      +25     
  Branches      643      643              
==========================================
+ Hits         2006     2017      +11     
- Misses         28       42      +14     
  Partials        1        1
```


| [Impacted Files](https://codecov.io/gh/baianat/vee-validate/pull/1677?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/plugin.js](https://codecov.io/gh/baianat/vee-validate/pull/1677/diff?src=pr&el=tree#diff-c3JjL3BsdWdpbi5qcw==) | `84.21% <100%> (+0.21%)` | :arrow_up: |
| [src/components/observer.js](https://codecov.io/gh/baianat/vee-validate/pull/1677/diff?src=pr&el=tree#diff-c3JjL2NvbXBvbmVudHMvb2JzZXJ2ZXIuanM=) | `12.5% <12.5%> (ø)` | |
| [src/components/provider.js](https://codecov.io/gh/baianat/vee-validate/pull/1677/diff?src=pr&el=tree#diff-c3JjL2NvbXBvbmVudHMvcHJvdmlkZXIuanM=) | `98.48% <94.73%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/baianat/vee-validate/pull/1677?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/baianat/vee-validate/pull/1677?src=pr&el=footer). Last update [44a71b3...d852f39](https://codecov.io/gh/baianat/vee-validate/pull/1677?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index f1ecdabc..2d2c8fa3 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -1,6 +1,8 @@
 const sidebars = {
   components: [
-    '/guide/components'
+    'components',
+    'components/validation-provider',
+    'components/validation-observer'
   ],
   guide: [
     '',
diff --git a/docs/guide/components.md b/docs/guide/components.md
index 8f2c236a..cf0472f0 100644
--- a/docs/guide/components.md
+++ b/docs/guide/components.md
@@ -4,190 +4,9 @@
  This feature is available as of `v2.1.0`.
 :::
 
-VeeValidate ships with components for more advanced use cases. The `ValidationProvider` component is a regular component that wraps your inputs and provides validation using the [scoped slots feature](https://vuejs.org/v2/guide/components-slots.html#Scoped-Slots) to pass down validation state and results.
+VeeValidate ships with components for more advanced use cases. These components offer a different approach for validation using the [scoped slots feature](https://vuejs.org/v2/guide/components-slots.html#Scoped-Slots) to pass down validation state and results.
 
-Using the ValidationProvider component offers isolated scope for each field validation state, and does not inject/mutate anything in your template or the Vue instance. And you can import it and use whenever you need it. Using the validation context will allow you to apply classes, flags and pass state to your template.
+Currently there are two components:
 
-Here is a quick example:
-
-```vue
-<template>
-  <div>
-    <ValidationProvider rules="required">
-      <template slot-scope="{ errors }">
-        <input v-model="value" type="text">
-        <span id="error">{{ errors[0] }}</span>
-      </template>
-    </ValidationProvider>
-  </div>
-</template>
-
-<script>
-import { ValidationProvider } from 'vee-validate';
-
-export default {
-  components: {
-    ValidationProvider
-  }
-};
-</script>
-```
-
-It also works for custom components and solves the issue of authoring __self validated__ components which are hard to achieve normally because of the directive limitations.
-
-::: tip
-The fields being validated __must have__ a `v-model` so the component can correctly identify the element/component being validated.
-:::
-
-## Validation Context
-
-The object passed down to the slot scope is called the __validation context__ it has the following properties:
-
-| Name    | Type                       |  Description |
-|:--------|:--------------------------:|:--------------------------------------------------------------------|
-| errors  | `string[]`                 | The list of error messages.                                         |
-| valid   | `boolean`                  | The current validation state.                                       |
-| flags   | `{ [x: string]: boolean }` | The flags map object state.                                         |
-| aria    | `{ [x: string]: string }`  | Map object of aria attributes for accessibility.                    |
-| classes | `{ [x: string]: boolean }` | Map object of the classes configured based on the validation state. |
-
-Since slot scopes can take advantage of ES6 destructing; you can opt-in for any of those properties and pass down to your slot template as you see fit. The example above only needed the `errors` array.
-
-## Usage Examples
-
-The previous quick sample validates simple HTML inputs, lets take this up a notch and validate popular 3rd party components like [Vuetify's TextInput](https://vuetifyjs.com/en/components/text-fields).
-
-### Basic Example
-
-Thise example wraps and passes error messages down to Vuetify's text field component.
-
-```html
-<ValidationProvider rules="required">
-  <template slot-scope="{ errors }">
-    <VTextField v-model="value" :error-messages="errors" />
-  </template>
-</ValidationProvider>
-```
-
-### Confirmed/Target based Validation
-
-When using the directive, the `confirmed` rule targets the other field that has a match ref. Using the ValidationProvider is slightly diffrent as it looks for provider components that has a matching `vid` prop which can be either a number or a string.
-
-```html
-<ValidationProvider rules="required|confirmed:confirm">
-  <template slot-scope="{ errors }">
-    <VTextField v-model="password" type="password" :error-messages="errors" />
-  </template>
-</ValidationProvider>
-
-<ValidationProvider vid="confirm" rules="required">
-  <template slot-scope="{ errors }">
-    <VTextField v-model="passwordConfirm" type="password" :error-messages="errors" />
-  </template>
-</ValidationProvider>
-```
-
-## Refactoring Validation Providers
-
-The ValidationProvider while have its advantages, it is more verbose than using the directive, and can be very annoying when create large forms, there are a couple of ways to address this issue.
-
-### Creating High Order Components
-
-A common pattern in React is to use higher order components to produce new components with slightly different behavior, It is similair to creating a wrapper or a mixin for our component except it uses props/events to communicate state.
-
-The ValidationProvider provides a `wrap` method which takes in a component and outputs another with the validation behavior enabled. Lets create a `VTextFieldWithValidation` using this method:
-
-```js
-import { ValidationProvider } from 'vee-validate';
-import { VTextField } from 'vuetify/lib';
-
-const VTextFieldWithValidation = ValidationProvider.wrap(VTextField, ({ errors }) => ({
-  'error-messages': errors
-}));
-
-export default {
-  components: {
-    VTextFieldWithValidation
-  }
-};
-```
-
-::: tip
-Note that the second parameter is a function that transforms the validation context to props object to be passed to the wrapped component. In this case we want to pass the `errors` array as the `error-messages` prop to the `VTextField` component.
-:::
-
-With this approach the last example becomes:
-
-```html
-<VTextFieldWithValidation rules="required|confirmed:confirm" v-model="password" />
-
-<VTextFieldWithValidation vid="confirm" rules="required" v-model="password" />
-```
-
-::: danger
-This approach has some cons, for example if the wrapped component accepts props that has the same name as the `ValidationProvider` component - while it will receive them - it may be in a different type which might cause serious issues. The problem with HOCs is that you need to be aware of the underlying component implementation which can be problematic for 3rd party components.
-:::
-
-### Wrapping Components Manually
-
-Instead We can wrap the field component with the ValidationProvider in a new component. This is much simpler and flexible and doesn't have any of the HOC problems.
-
-Consider this new `VTextFieldWithValidation` component.
-
-```vue
-<template>
-  <ValidationProvider :rules="rules">
-    <template slot-scope="{ errors }">
-      <VTextField v-model="innerValue" :error-messages="errors" />
-    </template>
-  </ValidationProvider>
-</template>
-
-<script>
-import { ValidationProvider } from 'vee-validate';
-
-export default {
-  props: {
-    rules: [String],
-    value: null
-  },
-  components: {
-    ValidationProvider
-  },
-  data: () => ({
-    innerValue: null
-  }),
-  watch: {
-    innerValue (val) {
-      this.$emit('input', val);
-    }
-  }
-};
-</script>
-
-```
-
-Ideally you would pass the props you need to either `ValidationProvider` or the `VTextField` being validated, with this approach solves the verbosity problem while preserving the simple scoped slots API. It also allows you to distrubute props without the issues of having a conflict unlike HOC.
-
-Using either approaches is up to you.
-
-## Reference
-
-Below is the reference of the ValidationProvider public API.
-
-### Props
-
-All the following props are optional.
-
-|Prop  |Type  |Default Value  | Description  |
-|---------|---------|---------|---------|
-| rules   | `string`      | `undefined` | The validation rules. |
-| vid     |  `string` | auto increment number | Identifier used for target based rules. |
-| immediate  | `boolean` | `false` | If the field should be validated immediatly after render (initially). |
-| events  | `string | string[]` | `['input']` | Events that will trigger validation. |
-| name  | `string` | `undefined` | a String that will be used to replace `{field}` in error messages. |
-| tag  | `string` | `span` | The default HTML tag to be used to render the root element of the component. |
-
-### Events
-
-The validation provider does not emit any events at this time.
+- [ValidationProvider](./components/validation-provider.md)
+- [ValidationObserver](./components/validation-observer.md)
diff --git a/docs/guide/components/validation-observer.md b/docs/guide/components/validation-observer.md
new file mode 100644
index 00000000..771e98a2
--- /dev/null
+++ b/docs/guide/components/validation-observer.md
@@ -0,0 +1,130 @@
+# Validation Observer
+
+Using [providers](./validation-provider.md) for validation is very handy but it introduces few usage problems of its own, for example how would you tell the current state. Let's say you want to disable a button as long as the form is invalid, how would you do that?
+
+The ValidationObserver is a convenient component that also uses the [scoped slots feature](https://vuejs.org/v2/guide/components-slots.html#Scoped-Slots) to communicate the current state of your inputs as a whole.
+
+Here is a small example, again with Vuetify components wrapped by the [Provider's wrap](./validation-provider.md#creating-high-order-components) method:
+
+```vue
+<ValidationObserver>
+  <template slot-scope="{ invalid }">
+    <InputWithValidation rules="required" v-model="first" :error-messages="errors" />
+
+    <InputWithValidation rules="required" v-model="second" :error-messages="errors" />
+
+    <v-btn :disabled="invalid" @click="submit()">Submit</v-btn>
+  </template>
+</ValidationObserver>
+```
+
+## Scoped Slot Data
+
+The scoped slot is passed an object containing a flags object representing the merged state of all providers registered under the observer. It contains the following properties:
+
+| Name      | Type       |  Description                                                        |
+|:----------|:----------:|:--------------------------------------------------------------------|
+| dirty     | `boolean`  | True if at least one field is dirty.                                |
+| pristine  | `boolean`  | True if all fields are pristine (not dirty).                        |
+| valid     | `boolean`  | True if all fields are valid.                                       |
+| invalid   | `boolean`  | True if at least one field is invalid.                              |
+| pending   | `boolean`  | True if at least one field's validation is in progress.             |
+| touched   | `boolean`  | True if at least one field has been touched (blurred).              |
+| untouched | `boolean`  | True if all fields hasn't been tocuhed (blurred).                   |
+
+## Examples
+
+### Validate Before Submit
+
+Validating before submit is even easier than the old way, using the [public methods](#methods) and a simple [ref](https://vuejs.org/v2/api/#ref) we can validate all providers before submitting the form.
+
+```vue
+<template>
+  <ValidationObserver ref="observer">
+    <template slot-scope="{ invalid }">
+      <InputWithValidation rules="required" v-model="first" :error-messages="errors" />
+
+      <InputWithValidation rules="required" v-model="second" :error-messages="errors" />
+
+      <v-btn :disabled="invalid" @click="submit()">Submit</v-btn>
+    </template>
+  </ValidationObserver>
+</template>
+
+<script>
+export default {
+  methods: {
+    async submit () {
+      const isValid = await this.$refs.observer.validate();
+      if (!isValid) {
+        // ABORT!!
+      }
+
+      // 🐿 ship it
+    }
+  }
+};
+</script>
+```
+
+::: tip
+  Using the same approach you can reset validation state for all providers using the public method `reset()`.
+:::
+
+### Scopes And Groups
+
+The Validation Components API does not implement scopes and won't be, you can use the __ValidationObserver__ to group your fields without the complexties of the scopes API by using multiple observers and refs.
+
+```vue
+<template>
+  <div>
+    <ValidationObserver ref="obs1">
+      <template slot-scope="{ invalid }">
+        <!-- Fields -->
+      </template>
+    </ValidationObserver>
+
+    <ValidationObserver ref="obs2">
+      <template slot-scope="{ invalid }">
+        <!-- Fields -->
+      </template>
+    </ValidationObserver>
+  </div>
+</template>
+
+<script>
+// Somewhere in a method ...
+// validate the first observer.
+this.$refs.$obs1.validate();
+
+// validate the second observer.
+this.$refs.$obs2.validate();
+</script>
+```
+
+Simple and clean.
+
+## Reference
+
+Below is the reference of the ValidationObserver public API.
+
+### Props
+
+All the following props are optional.
+
+|Prop       | Type      | Default Value         | Description                                                                  |
+|-----------|-----------|-----------------------|------------------------------------------------------------------------------|
+| tag       | `string`  | `span`                | The default HTML tag to be used to render the root element of the component. |
+
+## Methods
+
+Those are the only methods meant for public usage, other methods that may exist on the ValidationProvider are strictly internal.
+
+|Method       | Args    | Return Value                  | Description                                                     |
+|-------------|:-------:|:-----------------------------:|-----------------------------------------------------------------|
+| validate    | `void`  | `Promise<boolean>`            | Validates all the child providers and also mutates their state. |
+| reset       | `void`  | `void`                        | Resets validation state for all child providers.                |
+
+### Events
+
+The validation observer does not emit any events at this time.
diff --git a/docs/guide/components/validation-provider.md b/docs/guide/components/validation-provider.md
new file mode 100644
index 00000000..e9103a04
--- /dev/null
+++ b/docs/guide/components/validation-provider.md
@@ -0,0 +1,239 @@
+# Validation Provider
+
+The `ValidationProvider` component is a regular component that wraps your inputs and provides validation state using [scoped slots](https://vuejs.org/v2/guide/components-slots.html#Scoped-Slots).
+
+Using the ValidationProvider offers isolated scope for each field validation state, and does not inject/mutate anything outside its slot. You can import it and use whenever you need it. Using the validation context will allow you to apply classes, flags and pass state to your template.
+
+Here is a quick example:
+
+```vue
+<template>
+  <div>
+    <ValidationProvider rules="required">
+      <template slot-scope="{ errors }">
+        <input v-model="value" type="text">
+        <span id="error">{{ errors[0] }}</span>
+      </template>
+    </ValidationProvider>
+  </div>
+</template>
+
+<script>
+import { ValidationProvider } from 'vee-validate';
+
+export default {
+  components: {
+    ValidationProvider
+  }
+};
+</script>
+```
+
+It also works for custom components and solves the issue of authoring __self validated__ components which are hard to achieve normally because of the directive limitations.
+
+::: tip
+The fields being validated __must have__ a `v-model` so the component can correctly identify the element/component being validated.
+:::
+
+## Scoped Slot Data
+
+The object passed down to the slot scope is called the __validation context__ it has the following properties:
+
+| Name    | Type                       |  Description |
+|:--------|:--------------------------:|:--------------------------------------------------------------------|
+| errors  | `string[]`                 | The list of error messages.                                         |
+| valid   | `boolean`                  | The current validation state.                                       |
+| flags   | `{ [x: string]: boolean }` | The flags map object state.                                         |
+| aria    | `{ [x: string]: string }`  | Map object of aria attributes for accessibility.                    |
+| classes | `{ [x: string]: boolean }` | Map object of the classes configured based on the validation state. |
+
+Since slot scopes can take advantage of ES6 destructing; you can opt-in for any of those properties and pass down to your slot template as you see fit. The example above only needed the `errors` array.
+
+## Examples
+
+The previous quick sample validates simple HTML inputs, lets take this up a notch and validate popular 3rd party components like [Vuetify's TextInput](https://vuetifyjs.com/en/components/text-fields).
+
+### Basic Example
+
+This example wraps and passes error messages down to Vuetify's text field component.
+
+```vue
+<ValidationProvider rules="required">
+  <template slot-scope="{ errors }">
+    <VTextField v-model="value" :error-messages="errors" />
+  </template>
+</ValidationProvider>
+```
+
+### Manual Validation
+
+Triggering validation on any of the providers is simple, but it is opt-in. Meaning you need to explicity call the validation on the provider instance. Using [refs](https://vuejs.org/v2/api/#ref) and the [public methods](#methods) `validate` and `applyResult` makes it a breeze.
+
+```vue
+<template>
+  <div>
+    <ValidationProvider rules="required" ref="myinput">
+      <template slot-scope="{ errors }">
+        <VTextField v-model="value" :error-messages="errors" />
+      </template>
+    </ValidationProvider>
+
+    <v-btn @click="validateField('myinput')" >Submit</v-btn>
+  </div>
+</template>
+
+<script>
+export default {
+  // ...
+  methods: {
+    validateField (field) {
+      const provider = this.$refs[field];
+
+      // validates the field, but does not mutate its state.
+      provider.validate().then(
+        // mutates state.
+        provider.applyResult
+      );
+    }
+  },
+  // ..
+};
+</script>
+```
+
+::: tip
+  Using the same approach you can reset validation state for the provider using the public method `reset()`.
+:::
+
+### Confirmed/Target based Validation
+
+When using the directive, the `confirmed` rule targets the other field that has a match ref. Using the ValidationProvider is slightly diffrent as it looks for provider components that has a matching `vid` prop which can be either a number or a string.
+
+```vue
+<ValidationProvider rules="required|confirmed:confirm">
+  <template slot-scope="{ errors }">
+    <VTextField v-model="password" type="password" :error-messages="errors" />
+  </template>
+</ValidationProvider>
+
+<ValidationProvider vid="confirm" rules="required">
+  <template slot-scope="{ errors }">
+    <VTextField v-model="passwordConfirm" type="password" :error-messages="errors" />
+  </template>
+</ValidationProvider>
+```
+
+## Refactoring Validation Providers
+
+The ValidationProvider while have its advantages, it is more verbose than using the directive, and can be very annoying when creating large forms, there are a couple of ways to address this issue.
+
+### Creating High Order Components
+
+A common pattern in React is to use higher order components to produce new components with slightly different behavior, It is similair to creating a wrapper or a mixin for our component except it uses props/events to communicate state.
+
+The ValidationProvider provides a `wrap` method which takes in a component and outputs another with the validation behavior enabled. Lets create a `VTextFieldWithValidation` using this method:
+
+```js
+import { ValidationProvider } from 'vee-validate';
+import { VTextField } from 'vuetify/lib';
+
+const VTextFieldWithValidation = ValidationProvider.wrap(VTextField, ({ errors }) => ({
+  'error-messages': errors
+}));
+
+export default {
+  components: {
+    VTextFieldWithValidation
+  }
+};
+```
+
+::: tip
+Note that the second parameter is a function that transforms the validation context to props object to be passed to the wrapped component. In this case we want to pass the `errors` array as the `error-messages` prop to the `VTextField` component.
+:::
+
+With this approach the last example becomes:
+
+```vue
+<VTextFieldWithValidation rules="required|confirmed:confirm" v-model="password" />
+
+<VTextFieldWithValidation vid="confirm" rules="required" v-model="password" />
+```
+
+::: danger
+This approach has some cons, for example if the wrapped component accepts props that has the same name as the `ValidationProvider` component - while it will receive them - it may be in a different type which might cause serious issues. The problem with HOCs is that you need to be aware of the underlying component implementation which can be problematic for 3rd party components.
+:::
+
+### Wrapping Components Manually
+
+Instead We can wrap the field component with the ValidationProvider in a new component. This is much simpler and flexible and doesn't have any of the HOC problems.
+
+Consider this new `VTextFieldWithValidation` component.
+
+```vue
+<template>
+  <ValidationProvider :rules="rules">
+    <template slot-scope="{ errors }">
+      <VTextField v-model="innerValue" :error-messages="errors" />
+    </template>
+  </ValidationProvider>
+</template>
+
+<script>
+import { ValidationProvider } from 'vee-validate';
+
+export default {
+  props: {
+    rules: [String],
+    value: null
+  },
+  components: {
+    ValidationProvider
+  },
+  data: () => ({
+    innerValue: null
+  }),
+  watch: {
+    innerValue (val) {
+      this.$emit('input', val);
+    }
+  }
+};
+</script>
+
+```
+
+Ideally you would pass the props you need to either `ValidationProvider` or the `VTextField` being validated, with this approach solves the verbosity problem while preserving the simple scoped slots API. It also allows you to distrubute props without the issues of having a conflict unlike HOC.
+
+Using either approaches is up to you.
+
+## Reference
+
+Below is the reference of the ValidationProvider public API.
+
+### Props
+
+All the following props are optional.
+
+|Prop       | Type      | Default Value         | Description                                                                  |
+|-----------|-----------|-----------------------|------------------------------------------------------------------------------|
+| rules     | `string`  | `undefined`           | The validation rules.                                                        |
+| vid       | `string`  | auto increment number | Identifier used for target based rules.                                      |
+| immediate | `boolean` | `false`               | If the field should be validated immediatly after render (initially).        |
+| events    | `string[]`| `['input']`           | Events that will trigger validation.                                         |
+| name      | `string`  | `undefined`           | a String that will be used to replace `{field}` in error messages.           |
+| tag       | `string`  | `span`                | The default HTML tag to be used to render the root element of the component. |
+
+## Methods
+
+Those are the only methods meant for public usage, other methods that may exist on the ValidationProvider are strictly internal.
+
+|Method       | Args    | Return Value                | Description                                                                                                 |
+|-------------|:-------:|:---------------------------:|-------------------------------------------------------------------------------------------------------------|
+| validate    | `void`  | `Promise<ValidationResult>` | Runs a validation of the current value against the rules defined. __does not mutate the validation state.__ |
+| applyResult | `ValidationResult` | `void`           | Takes a __validation result__ object and applies it on the current state.                                   |
+| reset       | `void`  | `void`                      | Resets validation state.                                                                                    |
+
+### Events
+
+The validation provider does not emit any events at this time.
diff --git a/locale/ne.js b/locale/ne.js
new file mode 100644
index 00000000..bfce696f
--- /dev/null
+++ b/locale/ne.js
@@ -0,0 +1,56 @@
+import { formatFileSize, isDefinedGlobally } from './utils';
+
+const messages = {
+  _default: (field) => `यो ${field} फिल्ड मान्य छैन।`,
+  after: (field, [target, inclusion]) => `${field} ${target} भन्दा पछि  ${inclusion ? 'वा बराबर' : ''} हुनु पर्छ।`, 
+  alpha_dash: (field) => `${field} फील्डलमा वर्ण-संख्या अक्षरहरू साथै ड्याश र अन्डरसेर्सहरू समावेश गर्न सक्छ।`,
+  alpha_num: (field) => `${field} फील्डमा वर्ण-संख्या अक्षरहरू मात्र समावेश गर्न सक्छ।`,
+  alpha_spaces: (field) => `${field} फिल्डमा वर्णमाला अक्षरहरू र स्पेसहरूमा मात्र समावेश गर्न सक्छ।`,
+  alpha: (field) => `${field} फिल्डले वर्णमाला अक्षरहरू मात्र समावेश गर्न सक्छ।`,
+  before: (field, [target, inclusion]) => `${field} ${target} भन्दा अघि  ${inclusion ? 'वा बराबर' : ''} हुनु पर्छ।`,
+  between: (field, [min, max]) => `${field} फिल्ड ${min} र ${max} को बीच हुनुपर्दछ।`,
+  confirmed: (field) => `${field} पुष्टिकरण मेल खाँदैन।`,
+  credit_card: (field) => `${field} फिल्ड मान्य छैन।`,
+  date_between: (field, [min, max]) => `${field} ${min} र ${max} को बीच हुनुपर्दछ।`,
+  date_format: (field, [format]) => `${field} ढाँचा ${format} मा हुनुपर्दछ।`,
+  decimal: (field, [decimals = '*'] = []) => `${field} फिल्ड संख्यात्मक हुनुपर्छ र ${!decimals || decimals === '*' ? '' : decimals} दशमलव अंक हुन सक्छ।`,
+  digits: (field, [length]) => `${field} फिल्ड संख्यात्मक हुनुपर्छ र ${length} अङ्क समावेश गर्दछ।`,
+  dimensions: (field, [width, height]) => `${field} फिल्ड ${width} पिक्सेलमा ${height} पिक्सेल हुनु पर्दछ।`,
+  email: (field) => `${field} फिल्ड मान्य ईमेल हुनु पर्छ।`,
+  ext: (field) => `${field} फिल्ड मान्य फाइल हुनु पर्छ।`,
+  image: (field) => `${field} फिल्ड मान्य फोटो हुनु पर्छ।`,
+  included: (field) => `${field} फिल्ड मान्य परिमाण हुनु पर्छ।`,
+  integer: (field) => `${field} फिल्ड मान्य पूर्णांक हुनु पर्छ।`,
+  ip: (field) => `${field} फिल्ड मान्य आईपी ठेगाना हुनु पर्छ।`,
+  length: (field, [length, max]) => {
+    if (max) {
+      return `${field} लम्बाई ${length} र ${max} बीचमा हुनुपर्दछ।`;
+    }
+
+    return `${field} लम्बाई ${length} हुनुपर्दछ।`;
+  },
+  max: (field, [length]) => `${field} फिल्ड ${length} अक्षरहरू भन्दा ठूलो हुन सक्छ।`,
+  max_value: (field, [max]) => `${field} फिल्ड ${max} वा कम हुनुपर्दछ।`,
+  mimes: (field) => `${field} फिल्ड मान्य फाइल प्रकार हुनु पर्दछ।`,
+  min: (field, [length]) => `${field} फिल्ड कम्तिमा ${length} अक्षरहरू हुनुपर्दछ।`,
+  min_value: (field, [min]) => `${field} इमेल फिल्ड ${min} वा बढी हुनुपर्दछ।`,
+  excluded: (field) => `${field} फिल्ड मान्य मान हुनुपर्छ।`,
+  numeric: (field) => `${field} फिल्डले संख्यात्मक अक्षरहरूमा मात्र समावेश गर्न सक्छ।`,
+  regex: (field) => `${field} फिल्ड ढाँचा अमान्य छ।`,
+  required: (field) => `${field} फिल्ड आवश्यक छ।`,
+  size: (field, [size]) => `${field} परिणाम ${formatFileSize(size)} भन्दा कम हुनुपर्दछ।`,
+  url: (field) => `${field} फिल्ड मान्य यूआरएल होइन।`
+};
+
+const locale = {
+  name: 'ne',
+  messages,
+  attributes: {}
+};
+
+if (isDefinedGlobally()) {
+  // eslint-disable-next-line
+  VeeValidate.Validator.localize({ [locale.name]: locale });
+}
+
+export default locale;
diff --git a/src/components/index.js b/src/components/index.js
new file mode 100644
index 00000000..730cbb66
--- /dev/null
+++ b/src/components/index.js
@@ -0,0 +1,2 @@
+export { ValidationProvider } from './provider';
+export { ValidationObserver } from './observer';
diff --git a/src/components/observer.js b/src/components/observer.js
new file mode 100644
index 00000000..0c0dfd10
--- /dev/null
+++ b/src/components/observer.js
@@ -0,0 +1,72 @@
+import { normalizeSlots } from '../utils/vnode';
+import { isCallable } from '../utils';
+
+const flagMergingStrategy = {
+  pristine: 'every',
+  dirty: 'some',
+  touched: 'some',
+  untouched: 'every',
+  valid: 'every',
+  invalid: 'some',
+  pending: 'some',
+  validated: 'every'
+};
+
+export const ValidationObserver = {
+  name: 'ValidationObserver',
+  provide () {
+    return {
+      $_veeObserver: this
+    };
+  },
+  props: {
+    tag: {
+      type: String,
+      default: 'span'
+    }
+  },
+  data: () => ({
+    refs: {}
+  }),
+  methods: {
+    $subscribe (provider) {
+      this.refs = Object.assign({}, this.refs, { [provider.vid]: provider });
+    },
+    $unsubscribe (provider) {
+      delete this.refs[provider.vid];
+      this.refs = Object.assign({}, this.refs);
+    },
+    validate () {
+      return Promise.all(Object.keys(this.refs).map(ref => {
+        return this.refs[ref].validate().then(result => {
+          this.refs[ref].applyResult(result);
+
+          return result;
+        });
+      })).then(results => results.every(r => r.valid));
+    },
+    reset () {
+      return Object.keys(this.refs).forEach(ref => {
+        this.refs[ref].reset();
+      });
+    }
+  },
+  computed: {
+    ctx () {
+      return Object.keys(flagMergingStrategy).reduce((acc, flag) => {
+        const strategy = flagMergingStrategy[flag];
+        acc[flag] = Object.keys(this.refs)[strategy](p => this.refs[p].flags[flag]);
+
+        return acc;
+      }, {});
+    }
+  },
+  render (h) {
+    let slots = this.$scopedSlots.default;
+    if (!isCallable(slots)) {
+      slots = () => normalizeSlots(this.$slots, this.$vnode.context);
+    }
+
+    return h(this.tag, slots(this.ctx));
+  }
+};
diff --git a/src/components.js b/src/components/provider.js
similarity index 78%
rename from src/components.js
rename to src/components/provider.js
index 95764843..40d7ce12 100644
--- a/src/components.js
+++ b/src/components/provider.js
@@ -1,8 +1,8 @@
-import VeeValidate from './plugin';
-import RuleContainer from './core/ruleContainer';
-import { normalizeEvents, isEvent } from './utils/events';
-import { createFlags, assign, isCallable, normalizeRules, warn } from './utils';
-import { findModel, findModelNodes, findModelConfig, addListenerToVNode, addListenerToObject, getInputEventName, normalizeSlots } from './utils/vnode';
+import VeeValidate from '../plugin';
+import RuleContainer from '../core/ruleContainer';
+import { normalizeEvents, isEvent } from '../utils/events';
+import { createFlags, assign, isCallable, normalizeRules, warn } from '../utils';
+import { findModel, findModelNodes, findModelConfig, addListenerToVNode, addListenerToObject, getInputEventName, normalizeSlots } from '../utils/vnode';
 
 let $validator = null;
 
@@ -11,9 +11,7 @@ function createValidationCtx (ctx) {
     errors: ctx.messages,
     flags: ctx.flags,
     classes: ctx.classes,
-    get valid () {
-      return ctx.isValid;
-    },
+    valid: ctx.isValid,
     aria: {
       'aria-invalid': ctx.flags.invalid ? 'true' : 'false',
       'aria-required': ctx.isRequired ? 'true' : 'false'
@@ -44,6 +42,21 @@ function onRenderUpdate (model) {
   this._needsValidation = false;
 }
 
+// Creates the common listeners for a validatable context.
+function createCommonListeners (ctx) {
+  const onInput = (e) => {
+    ctx.syncValue(e); // track and keep the value updated.
+    ctx.setFlags({ dirty: true, pristine: false });
+  };
+
+  // Blur event listener.
+  const onBlur = () => {
+    ctx.setFlags({ touched: true, untouched: false });
+  };
+
+  return { onInput, onBlur };
+}
+
 // Adds all plugin listeners to the vnode.
 function addListeners (node) {
   const model = findModel(node);
@@ -52,32 +65,20 @@ function addListeners (node) {
 
   onRenderUpdate.call(this, model);
 
-  const onInput = (e) => {
-    // track and keep the value updated.
-    this.syncValue(e);
-    // set the flags.
-    this.setFlags({ dirty: true, pristine: false });
-  };
-
+  const { onInput, onBlur } = createCommonListeners(this);
   addListenerToVNode(node, this._inputEventName, onInput);
+  addListenerToVNode(node, 'blur', onBlur);
 
   // add the validation listeners.
   this.normalizedEvents.forEach(evt => {
     addListenerToVNode(node, evt, () => this.validate().then(this.applyResult));
   });
 
-  // touched, untouched flags listener.
-  const setFlagsAfterBlur = () => {
-    this.setFlags({ touched: true, untouched: false });
-  };
-
-  addListenerToVNode(node, 'blur', setFlagsAfterBlur);
-
   this.initialized = true;
 }
 
 function createValuesLookup (ctx) {
-  let providers = ctx.$parent.$_veeValidate;
+  let providers = ctx.$_veeObserver.refs;
 
   return ctx.fieldDeps.reduce((acc, depName) => {
     if (providers[depName]) {
@@ -92,30 +93,58 @@ function createValuesLookup (ctx) {
   }, {});
 }
 
-function updateRenderingContextReference (ctx) {
-  const { id, vid, $vnode } = ctx;
+function updateRenderingContextRefs (ctx) {
+  const { id, vid } = ctx;
+
   // Nothing has changed.
-  if (id === vid && $vnode.context.$_veeValidate[id]) {
+  if (id === vid && ctx.$_veeObserver.refs[id]) {
     return;
   }
 
   // vid was changed.
-  if (id !== vid && $vnode.context.$_veeValidate[id] === ctx) {
-    delete $vnode.context.$_veeValidate[id];
+  if (id !== vid && ctx.$_veeObserver.refs[id] === ctx) {
+    ctx.$_veeObserver.$unsubscribe(ctx);
   }
 
-  $vnode.context.$_veeValidate[vid] = ctx;
+  ctx.$_veeObserver.$subscribe(ctx);
   ctx.id = vid;
 }
 
+function createObserver () {
+  return {
+    refs: {},
+    $subscribe (ctx) {
+      this.refs[ctx.vid] = ctx;
+    },
+    $unsubscribe (ctx) {
+      delete this.refs[ctx.vid];
+    }
+  };
+}
+
 let id = 0;
 
 export const ValidationProvider = {
   $__veeInject: false,
+  inject: {
+    $_veeObserver: {
+      from: '$_veeObserver',
+      default () {
+        if (!this.$vnode.context.$_veeObserver) {
+          this.$vnode.context.$_veeObserver = createObserver();
+        }
+
+        return this.$vnode.context.$_veeObserver;
+      }
+    }
+  },
   props: {
     vid: {
       type: [String, Number],
-      default: id++,
+      default: () => {
+        id++;
+        return id;
+      }
     },
     name: {
       type: String,
@@ -165,6 +194,13 @@ export const ValidationProvider = {
 
       this.value = value;
     },
+    reset () {
+      this.messages = [];
+      this.initialValue = this.value;
+      const flags = createFlags();
+      flags.changed = false;
+      this.setFlags(flags);
+    },
     validate () {
       this.setFlags({ pending: true });
 
@@ -198,11 +234,7 @@ export const ValidationProvider = {
         $validator = VeeValidate.instance._validator;
       }
 
-      if (!this.$vnode.context.$_veeValidate) {
-        this.$vnode.context.$_veeValidate = {};
-      }
-
-      updateRenderingContextReference(this);
+      updateRenderingContextRefs(this);
     }
   },
   computed: {
@@ -267,7 +299,7 @@ export const ValidationProvider = {
   },
   beforeDestroy () {
     // cleanup reference.
-    delete this.$vnode.context.$_veeValidate[this.vid];
+    this.$_veeObserver.$unsubscribe(this);
   },
   // Creates an HoC with validation capablities.
   wrap (component, ctxToProps = null) {
@@ -279,7 +311,8 @@ export const ValidationProvider = {
       data: this.data,
       computed: assign({}, this.computed),
       methods: assign({}, this.methods),
-      $__veeInject: false
+      $__veeInject: false,
+      inject: this.inject
     };
 
     // Default ctx converts ctx props to component props.
@@ -298,10 +331,10 @@ export const ValidationProvider = {
       this._inputEventName = this._inputEventName || getInputEventName(this.$vnode, model);
       onRenderUpdate.call(this, model);
 
-      addListenerToObject(listeners, eventName, (e) => {
-        this.syncValue(e);
-        this.setFlags({ dirty: true, pristine: false });
-      });
+      const { onInput, onBlur } = createCommonListeners(this);
+
+      addListenerToObject(listeners, eventName, onInput);
+      addListenerToObject(listeners, 'blur', onBlur);
 
       this.normalizedEvents.forEach((evt, idx) => {
         addListenerToObject(listeners, evt, (e) => {
@@ -309,10 +342,6 @@ export const ValidationProvider = {
         });
       });
 
-      addListenerToObject(listeners, 'blur', () => {
-        this.setFlags({ touched: true, untouched: false });
-      });
-
       // Props are any attrs not associated with ValidationProvider Plus the model prop.
       // WARNING: Accidental prop overwrite will probably happen.
       const { prop } = findModelConfig(this.$vnode) || { prop: 'value' };
diff --git a/src/index.esm.js b/src/index.esm.js
index 3dfe077a..1c24daa7 100644
--- a/src/index.esm.js
+++ b/src/index.esm.js
@@ -7,7 +7,7 @@ import mapFields from './core/mapFields';
 import Validator from './core/validator';
 import ErrorBag from './core/errorBag';
 import { assign } from './utils';
-import { ValidationProvider } from './components';
+import { ValidationProvider, ValidationObserver } from './components';
 
 const version = '__VERSION__';
 
@@ -32,7 +32,8 @@ export {
   ErrorBag,
   Rules,
   version,
-  ValidationProvider
+  ValidationProvider,
+  ValidationObserver
 };
 
 export default VeeValidate;
diff --git a/src/index.minimal.esm.js b/src/index.minimal.esm.js
index 7b1a8902..1ed8ff55 100644
--- a/src/index.minimal.esm.js
+++ b/src/index.minimal.esm.js
@@ -4,7 +4,7 @@ import mixin from './mixin';
 import Validator from './core/validator';
 import ErrorBag from './core/errorBag';
 import mapFields from './core/mapFields';
-import { ValidationProvider } from './components';
+import { ValidationProvider, ValidationObserver } from './components';
 
 const version = '__VERSION__';
 const install = VeeValidate.install;
@@ -19,7 +19,8 @@ export {
   Validator,
   ErrorBag,
   version,
-  ValidationProvider
+  ValidationProvider,
+  ValidationObserver
 };
 
 export default VeeValidate;
diff --git a/src/plugin.js b/src/plugin.js
index 11bc335b..1165726a 100644
--- a/src/plugin.js
+++ b/src/plugin.js
@@ -5,7 +5,7 @@ import { assign, getPath, warn, isCallable } from './utils';
 import Validator from './core/validator';
 import ErrorBag from './core/errorBag';
 import mapFields from './core/mapFields';
-import { ValidationProvider } from './components';
+import { ValidationProvider, ValidationObserver } from './components';
 import I18nDictionary from './localization/i18n';
 import { detectPassiveSupport } from './utils/events';
 
@@ -170,5 +170,6 @@ VeeValidate.Validator = Validator;
 VeeValidate.ErrorBag = ErrorBag;
 VeeValidate.mapFields = mapFields;
 VeeValidate.ValidationProvider = ValidationProvider;
+VeeValidate.ValidationObserver = ValidationObserver;
 
 export default VeeValidate;


Test patch:
diff --git a/tests/unit/component.js b/tests/unit/component.js
index f4e830c8..a9b1ccec 100644
--- a/tests/unit/component.js
+++ b/tests/unit/component.js
@@ -7,6 +7,7 @@ import SelectWithoutValidation from './components/Select';
 const Vue = createLocalVue();
 Vue.use(VeeValidate, { inject: false });
 Vue.component('ValidationProvider', VeeValidate.ValidationProvider);
+Vue.component('ValidationObserver', VeeValidate.ValidationObserver);
 
 const DEFAULT_REQUIRED_MESSAGE = 'The {field} field is required.';
 
@@ -80,7 +81,7 @@ describe('Validation Provider Component', () => {
       }),
       template: `
         <div>
-          <ValidationProvider ref="provider" rules="required">
+          <ValidationProvider rules="required">
             <template slot-scope="{ errors }">
               <select v-model="value">
                 <option value="">0</option>
@@ -121,7 +122,7 @@ describe('Validation Provider Component', () => {
       }),
       template: `
         <div>
-          <ValidationProvider ref="provider" :immediate="true" rules="required">
+          <ValidationProvider :immediate="true" rules="required">
             <template slot-scope="{ errors }">
               <input v-model="value" type="text">
               <span id="error">{{ errors[0] }}</span>
@@ -156,7 +157,7 @@ describe('Validation Provider Component', () => {
       },
       template: `
         <div>
-          <ValidationProvider rules="required" ref="provider">
+          <ValidationProvider rules="required">
             <template slot-scope="{ errors }">
               <TextInput v-model="value" ref="input"></TextInput>
               <span id="error">{{ errors && errors[0] }}</span>
@@ -199,7 +200,7 @@ describe('Validation Provider Component', () => {
       },
       template: `
         <div>
-          <ValidationProvider ref="provider" rules="required">
+          <ValidationProvider rules="required">
             <template slot-scope="{ errors }">
               <TextInput v-model="value" ref="input"></TextInput>
               <span id="error">{{ errors[0] }}</span>
@@ -268,12 +269,12 @@ describe('Validation Provider Component', () => {
         <div>
           <ValidationProvider rules="required" vid="confirmation">
             <template slot-scope="ctx">
-              <input type="password" v-model="confirmation" ref="confirmInput">
+              <input type="password" v-model="confirmation">
             </template>
           </ValidationProvider>
-          <ValidationProvider  rules="required|confirmed:confirmation">
+          <ValidationProvider rules="required|confirmed:confirmation">
             <template slot-scope="{ errors }">
-              <input type="password" v-model="password" ref="input">
+              <input type="password" v-model="password">
               <span id="err1">{{ errors[0] }}</span>
             </template>
           </ValidationProvider>
@@ -310,7 +311,7 @@ describe('Validation Provider Component', () => {
       `
     }, { localVue: Vue });
 
-    const providersMap = wrapper.vm.$_veeValidate;
+    const providersMap = wrapper.vm.$_veeObserver.refs;
     expect(providersMap.named).toBe(wrapper.vm.$refs.provider);
     wrapper.destroy();
     expect(providersMap.named).toBeUndefined();
@@ -368,4 +369,192 @@ describe('Validation Provider Component', () => {
 
     expect(wrapper.html()).toBe(`<div><div><select><option value="">0</option> <option value="1">1</option></select> <span id="error"></span></div></div>`);
   });
+
+  test('resets validation state', async () => {
+    const wrapper = mount({
+      data: () => ({
+        value: ''
+      }),
+      components: {
+        TextInput: {
+          props: ['value'],
+          template: `
+            <div>
+              <input id="input" :value="value" @input="$emit('input', $event.target.value)">
+            </div>
+          `
+        }
+      },
+      template: `
+        <div>
+          <ValidationProvider rules="required" ref="provider">
+            <template slot-scope="{ errors }">
+              <TextInput v-model="value" ref="input"></TextInput>
+              <span id="error">{{ errors && errors[0] }}</span>
+            </template>
+          </ValidationProvider>
+        </div>
+      `
+    }, { localVue: Vue, attachToDocument: true, sync: false });
+
+    const error = wrapper.find('#error');
+    const input = wrapper.find('#input');
+
+    expect(error.text()).toBe('');
+
+    input.element.value = '';
+    input.trigger('input');
+    await flushPromises();
+
+    expect(error.text()).toBe(DEFAULT_REQUIRED_MESSAGE);
+
+    wrapper.vm.$refs.provider.reset();
+    await flushPromises();
+    expect(error.text()).toBe('');
+  });
+});
+
+describe('Validation Observer Component', () => {
+  test('renders the tag prop', () => {
+    const wrapper = mount({
+      template: `
+        <ValidationObserver></ValidationObserver>
+      `
+    }, { localVue: Vue });
+
+    expect(wrapper.html()).toBe(`<span></span>`);
+  });
+
+  test('observes the current state of providers', async () => {
+    const wrapper = mount({
+      data: () => ({
+        value: ''
+      }),
+      template: `
+        <ValidationObserver>
+          <template slot-scope="{ valid }">
+            <ValidationProvider rules="required">
+              <template slot-scope="ctx">
+                <input v-model="value" type="text">
+              </template>
+            </ValidationProvider>
+
+            <span id="state">{{ valid }}</span>
+          </template>
+        </ValidationObserver>
+      `
+    }, { localVue: Vue });
+
+    const stateSpan = wrapper.find('#state');
+    const input = wrapper.find('input');
+
+    await flushPromises();
+    expect(stateSpan.text()).toBe('false');
+
+    input.element.value = 'value';
+    input.trigger('input');
+    await flushPromises();
+
+    expect(stateSpan.text()).toBe('true');
+  });
+
+  test('triggers validation manually on its children providers', async () => {
+    const wrapper = mount({
+      data: () => ({
+        value: ''
+      }),
+      template: `
+        <ValidationObserver ref="obs">
+          <template slot-scope="ctx">
+
+            <ValidationProvider rules="required">
+
+              <template slot-scope="{ errors }">
+                <input v-model="value" type="text">
+                <span id="error">{{ errors[0] }}</span>
+              </template>
+
+            </ValidationProvider>
+
+          </template>
+        </ValidationObserver>
+      `
+    }, { localVue: Vue });
+
+    const error = wrapper.find('#error');
+    await flushPromises();
+    expect(error.text()).toBe('');
+
+    await wrapper.vm.$refs.obs.validate();
+
+    expect(error.text()).toBe(DEFAULT_REQUIRED_MESSAGE);
+  });
+
+  test('removes child ref when the child is destroyed', async () => {
+    const wrapper = mount({
+      data: () => ({
+        value: ''
+      }),
+      template: `
+        <ValidationObserver ref="obs">
+          <template slot-scope="ctx">
+
+            <ValidationProvider rules="required" vid="id">
+
+              <template slot-scope="{ errors }">
+                <input v-model="value" type="text">
+                <span id="error">{{ errors[0] }}</span>
+              </template>
+
+            </ValidationProvider>
+
+          </template>
+        </ValidationObserver>
+      `
+    }, { localVue: Vue });
+
+    const obs = wrapper.vm.$refs.obs;
+    expect(obs.refs).toHaveProperty('id');
+
+    wrapper.destroy();
+
+    expect(obs.refs).not.toHaveProperty('id');
+  });
+
+  test('resets child refs', async () => {
+    const wrapper = mount({
+      data: () => ({
+        value: ''
+      }),
+      template: `
+        <ValidationObserver ref="obs">
+          <template slot-scope="ctx">
+
+            <ValidationProvider rules="required">
+
+              <template slot-scope="{ errors }">
+                <input v-model="value" type="text">
+                <span id="error">{{ errors[0] }}</span>
+              </template>
+
+            </ValidationProvider>
+
+          </template>
+        </ValidationObserver>
+      `
+    }, { localVue: Vue });
+
+    const error = wrapper.find('#error');
+    await flushPromises();
+    expect(error.text()).toBe('');
+
+    await wrapper.vm.$refs.obs.validate();
+
+    expect(error.text()).toBe(DEFAULT_REQUIRED_MESSAGE);
+
+    wrapper.vm.$refs.obs.reset();
+    await flushPromises();
+
+    expect(error.text()).toBe('');
+  });
 });