Repository: baianat/vee-validate
Pull request #1924 (2019-03-13 10:08:13)

feat: full v-slot support
This PR fixes the full support with `v-slot` which is the new way to use slots, and scoped slots.

Now both `ValidationProvider` and `ValidationObserver` are **no longer renderless** because the v-slot can be used on `template` and cannot be used on HTML elements. Which means the providers will be getting child roots more often than not. a `span` is used by default to render the providers or the observers. This makes the validation components fully compliant with `v-slot` semantics.

The rendered tag can be customized using the `tag` prop on either component, which makes the observer more useful by rendering it as a `form` tag instead of nesting a child form tag.

While I loved the idea to keep those components renderless, it would introduce SSR issues with frameworks like Nuxt where sometimes the provider would render a single root child or multiple ones which breaks the hydration process. A consistent rendered output is preferable over neat renderless behavior.

Maybe when Vue 3.0 releases with multiple root support it can go back to being renderless.

codecov[bot]: # [Codecov](https://codecov.io/gh/baianat/vee-validate/pull/1924?src=pr&el=h1) Report
> Merging [#1924](https://codecov.io/gh/baianat/vee-validate/pull/1924?src=pr&el=desc) into [master](https://codecov.io/gh/baianat/vee-validate/commit/d190185262a7c71d597c28bc1cdc6a142ed5420a?src=pr&el=desc) will **increase** coverage by `0.2%`.
> The diff coverage is `100%`.

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

```diff
@@            Coverage Diff            @@
##           master    #1924     +/-   ##
=========================================
+ Coverage   95.74%   95.94%   +0.2%     
=========================================
  Files          61       61             
  Lines        2324     2316      -8     
  Branches      720      717      -3     
=========================================
- Hits         2225     2222      -3     
+ Misses         88       84      -4     
+ Partials       11       10      -1
```


| [Impacted Files](https://codecov.io/gh/baianat/vee-validate/pull/1924?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/utils/vnode.js](https://codecov.io/gh/baianat/vee-validate/pull/1924/diff?src=pr&el=tree#diff-c3JjL3V0aWxzL3Zub2RlLmpz) | `98.41% <ø> (+5.45%)` | :arrow_up: |
| [src/components/provider.js](https://codecov.io/gh/baianat/vee-validate/pull/1924/diff?src=pr&el=tree#diff-c3JjL2NvbXBvbmVudHMvcHJvdmlkZXIuanM=) | `91.19% <100%> (ø)` | :arrow_up: |
| [src/components/observer.js](https://codecov.io/gh/baianat/vee-validate/pull/1924/diff?src=pr&el=tree#diff-c3JjL2NvbXBvbmVudHMvb2JzZXJ2ZXIuanM=) | `77.46% <100%> (+1.4%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/baianat/vee-validate/pull/1924?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/1924?src=pr&el=footer). Last update [d190185...eb850bf](https://codecov.io/gh/baianat/vee-validate/pull/1924?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js
index 7fc748d0..a2fd1fa0 100644
--- a/docs/.vuepress/enhanceApp.js
+++ b/docs/.vuepress/enhanceApp.js
@@ -1,5 +1,5 @@
 import VeeValidate from '../../dist/vee-validate.esm';
 
 export default ({ Vue }) => {
-  Vue.use(VeeValidate);
+  Vue.use(VeeValidate, { inject: false });
 };
diff --git a/docs/guide/components/validation-observer.md b/docs/guide/components/validation-observer.md
index c03129ad..e762aa0c 100644
--- a/docs/guide/components/validation-observer.md
+++ b/docs/guide/components/validation-observer.md
@@ -4,11 +4,11 @@ Using [providers](./validation-provider.md) for validation is very handy but it
 
 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:
+Here is a small example, again with Vuetify components wrapped by the [Provider's withValidation](./validation-provider.md#creating-high-order-components) method:
 
 ```vue
-<ValidationObserver>
-  <form slot-scope="{ invalid }" @submit.prevent="submit">
+<ValidationObserver v-slot="{ invalid }">
+  <form @submit.prevent="submit">
     <InputWithValidation rules="required" v-model="first" :error-messages="errors" />
 
     <InputWithValidation rules="required" v-model="second" :error-messages="errors" />
@@ -18,9 +18,25 @@ Here is a small example, again with Vuetify components wrapped by the [Provider'
 </ValidationObserver>
 ```
 
-:::tip
-  ValidationObserver is a __renderless__ component, meaning it does not render anything of its own. It only renders its slot, as such you need to have __only one root element__ in your slot, if you use the `template` tag it might cause render errors.
-:::
+## Rendering
+
+[Like providers](./validation-provider.md#rendering), observers render a `span` by default. You can customize the rendered tag using the `tag` prop, for example a `form` tag might be more useful.
+
+```vue
+<!-- Render a form -->
+<ValidationObserver tag="form">
+  <!-- Fields -->
+</ValidationObserver>
+```
+
+You can expand upon this by adding your form listeners like `submit` on the observer directly:
+
+```vue
+<!-- Render a form -->
+<ValidationObserver tag="form" @submit.prevent="onSubmit">
+  <!-- Fields -->
+</ValidationObserver>
+```
 
 ## Scoped Slot Data
 
@@ -47,14 +63,12 @@ Validating before submit is even easier than the old way, using the [public meth
 
 ```vue
 <template>
-  <ValidationObserver ref="observer">
-    <form slot-scope="{ invalid }" @submit.prevent="submit()">
-      <InputWithValidation rules="required" v-model="first" :error-messages="errors" />
+  <ValidationObserver ref="observer" v-slot="{ invalid }" tag="form" @submit.prevent="submit()">
+    <InputWithValidation rules="required" v-model="first" :error-messages="errors" />
 
-      <InputWithValidation rules="required" v-model="second" :error-messages="errors" />
+    <InputWithValidation rules="required" v-model="second" :error-messages="errors" />
 
-      <v-btn :disabled="invalid">Submit</v-btn>
-    </form>
+    <v-btn :disabled="invalid">Submit</v-btn>
   </ValidationObserver>
 </template>
 
@@ -74,12 +88,12 @@ export default {
 </script>
 ```
 
-If you plan to trigger validation from the template without using `refs` you can use the `validate` method present in the slot-scope data.
+If you plan to trigger validation from the template without using `refs` you can use the `validate` method present in the scopedSlot data.
 
 ```vue
 <template>
-  <ValidationObserver>
-    <form slot-scope="{ invalid, validate }" @submit.prevent="validate().then(submit)">
+  <ValidationObserver v-slot="{ invalid, validate }">
+    <form @submit.prevent="validate().then(submit)">
       <InputWithValidation rules="required" v-model="first" :error-messages="errors" />
 
       <InputWithValidation rules="required" v-model="second" :error-messages="errors" />
@@ -90,7 +104,7 @@ If you plan to trigger validation from the template without using `refs` you can
 </template>
 ```
 
-As you have guessed, the `validate` method on the Observer's slot-scope is thenable, meaning you can chain another method to run after the validation passes like the form submission handler. Note that the `validate` method does not return a promise, but a promise-like object that has a `then` method for convenience, which can be also chained further.
+As you have guessed, the `validate` method on the Observer's scopedSlot is thenable, meaning you can chain another method to run after the validation passes like the form submission handler. Note that the `validate` method does not return a promise, but a promise-like object that has a `then` method for convenience, which can be also chained further.
 
 ::: tip
   Using the same approach you can reset validation state for all providers using the public method `reset()`.
@@ -105,16 +119,12 @@ The Validation Components API does not implement scopes and won't be, you can us
 ```vue
 <template>
   <div>
-    <ValidationObserver ref="obs1">
-      <div slot-scope="{ invalid }">
-        <!-- Fields -->
-      </div>
+    <ValidationObserver tag="form" ref="obs1" v-slot="{ invalid }">
+      <!-- Fields -->
     </ValidationObserver>
 
-    <ValidationObserver ref="obs2">
-      <div slot-scope="{ invalid }">
-        <!-- Fields -->
-      </div>
+    <ValidationObserver tag="form" ref="obs2" v-slot="{ invalid }">
+      <!-- Fields -->
     </ValidationObserver>
   </div>
 </template>
@@ -136,20 +146,16 @@ Simple and clean.
 Building upon the previous example, observers can be nested to create nested forms for advanced use-cases. The outmost observer is able to trigger validation/resets on child obeservers and providers. Its state is also synced with the child observers and providers alike.
 
 ```vue
-<ValidationObserver ref="op">
-  <ValidationObserver ref="oc" slot-scope="obsctx">
-    <div>
-      <ValidationProvider rules="required">
-        <div slot-scope="ctx">
-          <input type="text" v-model="value">
-          <span>{{ ctx.errors[0] }}</span>
-        </div>
-      </ValidationProvider>
-      <!-- This is synced with the state of all children providers/observers -->
-      <pre>
-        {{ obsctx }}
-      </pre>
-    </div>
+<ValidationObserver ref="op" v-slot="observer">
+  <ValidationObserver ref="oc">
+    <ValidationProvider rules="required" v-slot="provider">
+      <input type="text" v-model="value">
+      <span>{{ provider.errors[0] }}</span>
+    </ValidationProvider>
+    <!-- This is synced with the state of all children providers/observers -->
+    <pre>
+      {{ observer }}
+    </pre>
   </ValidationObserver>
 </ValidationObserver>
 ```
@@ -160,7 +166,9 @@ Below is the reference of the ValidationObserver public API.
 
 ### Props
 
-The validation observer does not accept any props.
+|Prop  | Type     | Default Value         | Description                                                           |
+|------|----------|-----------------------|-----------------------------------------------------------------------|
+| tag  | `string` | `span`                | The default tag to [render](#rendering).      |
 
 ### Methods
 
diff --git a/docs/guide/components/validation-provider.md b/docs/guide/components/validation-provider.md
index eccf310d..0a0d8a6f 100644
--- a/docs/guide/components/validation-provider.md
+++ b/docs/guide/components/validation-provider.md
@@ -2,20 +2,20 @@
 
 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).
 
+::: tip
+The slots syntax has been changed in Vue 2.6, the following examples use the new `v-slot` syntax instead of the deprecated `slot-scope`, but it is still supported and you can use it. However `v-slot` have different semantics, consult the Vue docs for more information.
+:::
+
 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">
-      <div slot-scope="{ errors }">
-        <input v-model="value" type="text">
-        <span id="error">{{ errors[0] }}</span>
-      </div>
-    </ValidationProvider>
-  </div>
+  <ValidationProvider rules="required" v-slot="{ errors }">
+    <input v-model="value" type="text">
+    <span id="error">{{ errors[0] }}</span>
+  </ValidationProvider>
 </template>
 
 <script>
@@ -35,6 +35,47 @@ It also works for custom components and solves the issue of authoring __self val
   The fields being validated __must have__ a `v-model` so the component can correctly identify the element/component being validated __unless__ the field accepts a __file__.
 :::
 
+## Rendering
+
+By default, ValidationProvider renders a `span`, meaning it does not render anything of its own. Consider the following example
+
+```vue
+  <ValidationProvider rules="required" v-slot="{ errors }">
+    <input v-model="value" type="text">
+    <span>{{ errors[0] }}</span>
+  </ValidationProvider>
+
+  <!-- HTML Output -->
+  <span>
+    <input type="text">
+    <span>ERROR_MSG_PLACEHOLDER</span>
+  </span>
+```
+
+The default tag can be changed using the provider's `tag` prop.
+
+```vue
+  <!-- Multiple Child nodes using templates -->
+  <ValidationProvider rules="required" tag="div">
+    <template v-slot="{ errors }">
+      <input v-model="value" type="text">
+      <span>{{ errors[0] }}</span>
+    </template>
+  </ValidationProvider>
+
+  <!-- Multiple Child nodes directly -->
+  <ValidationProvider rules="required" v-slot="{ errors }" tag="div">
+    <input v-model="value" type="text">
+    <span>{{ errors[0] }}</span>
+  </ValidationProvider>
+
+  <!-- Both have the same HTML Output -->
+  <div>
+    <input type="text">
+    <span>ERROR_MSG_PLACEHOLDER</span>
+  </div>
+```
+
 ## Scoped Slot Data
 
 The object passed down to the slot scope is called the __validation context__. It has the following properties:
@@ -61,15 +102,11 @@ The previous quick sample validates simple HTML inputs, lets take this up a notc
 This passes error messages down to Vuetify's text field component.
 
 ```vue
-<ValidationProvider rules="required">
-  <VTextField slot-scope="{ errors }" v-model="value" :error-messages="errors" />
+<ValidationProvider rules="required" v-slot="{ errors }">
+  <VTextField v-model="value" :error-messages="errors" />
 </ValidationProvider>
 ```
 
-::: tip
-  ValidationProvider is a __renderless__ component, meaning it does not render anything of its own. It only renders its slot, as such you need to have __only one root element__ in your slot. Using the `template` tag might cause render errors.
-:::
-
 ### 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 method](#methods) `validate` makes it straight forward.
@@ -77,8 +114,8 @@ Triggering validation on any of the providers is simple, but it is opt-in. Meani
 ```vue
 <template>
   <div>
-    <ValidationProvider rules="required" ref="myinput">
-      <VTextField slot-scope="{ errors }" v-model="value" :error-messages="errors" />
+    <ValidationProvider rules="required" ref="myinput" v-slot="{ errors }">
+      <VTextField v-model="value" :error-messages="errors" />
     </ValidationProvider>
 
     <v-btn @click="validateField('myinput')" >Submit</v-btn>
@@ -101,11 +138,11 @@ export default {
 </script>
 ```
 
-If you only plan to trigger manual validation using the UI, you can use the `validate` handler on the slot-scope data to trigger validation without having to use `refs`.
+If you only plan to trigger manual validation using the UI, you can use the `validate` handler on the v-slot data to trigger validation without having to use `refs`.
 
 ```vue
-<ValidationProvider rules="required">
-  <div slot-scope="{ validate, errors }">
+<ValidationProvider rules="required" v-slot="{ validate, errors }">
+  <div>
     <input type="text" @input="validate">
     <p id="error">{{ errors[0] }}</p>
   </div>
@@ -115,8 +152,8 @@ If you only plan to trigger manual validation using the UI, you can use the `val
 Note that the `validate` method on the validation handler, you can use the `$event` in the Vue template to reference the event arg that is emitted with the event if your handlers are more complex.
 
 ```vue
-<ValidationProvider rules="required">
-  <div slot-scope="{ validate, errors }">
+<ValidationProvider rules="required" v-slot="{ validate, errors }">
+  <div>
     <input type="text" @input="syncVuex($event.target.value) || validate($event)">
     <p id="error">{{ errors[0] }}</p>
   </div>
@@ -132,8 +169,8 @@ Note that the `validate` method on the validation handler, you can use the `$eve
 While `v-model` is generally required when using the ValidationProvider component, some inputs like `file` do not benefit from having `v-model`. Instead, you can use the manual `validate` method to avoid having to use `v-model` in this instance.
 
 ```vue
-<ValidationProvider rules="required">
-  <div slot-scope="{ validate, errors }">
+<ValidationProvider rules="required" v-slot="{ validate, errors }">
+  <div>
     <input type="file" @change="handleFileChange($event) || validate($event)">
     <p id="error">{{ errors[0] }}</p>
   </div>
@@ -145,8 +182,8 @@ While `v-model` is generally required when using the ValidationProvider componen
 Like radio inputs and (sometimes) checkboxes, some inputs behave as a single input entity. You can wrap a whole group of inputs __given that they have the same `v-model`__ in a single ValidationProvider component. You can group as many inputs as you want inside the ValidationProvider component.
 
 ```vue
-<ValidationProvider rules="required">
-  <div slot-scope="{ errors }">
+<ValidationProvider rules="required" v-slot="{ errors }">
+  <div>
     <input type="radio" v-model="drink" value="">
     <input type="radio" v-model="drink" value="coffee">
     <input type="radio" v-model="drink" value="coke">
@@ -159,12 +196,12 @@ Like radio inputs and (sometimes) checkboxes, some inputs behave as a single inp
 When using the directive, the `confirmed` rule targets the other field that has a corresponding `ref`. Using the ValidationProvider is slightly different; it looks for a ValidationProvider component that has a matching `vid` prop. The `vid` can be either a number or a string.
 
 ```vue
-<ValidationProvider rules="required|confirmed:confirm">
-  <VTextField slot-scope="{ errors }" v-model="password" type="password" :error-messages="errors" />
+<ValidationProvider rules="required|confirmed:confirm" v-slot="{ errors }">
+  <VTextField v-model="password" type="password" :error-messages="errors" />
 </ValidationProvider>
 
-<ValidationProvider vid="confirm" rules="required">
-  <VTextField slot-scope="{ errors }" v-model="passwordConfirm" type="password" :error-messages="errors" />
+<ValidationProvider vid="confirm" rules="required" v-slot="{ errors }">
+  <VTextField v-model="passwordConfirm" type="password" :error-messages="errors" />
 </ValidationProvider>
 ```
 
@@ -219,8 +256,8 @@ Consider this new `VTextFieldWithValidation` component.
 
 ```vue
 <template>
-  <ValidationProvider :rules="rules">
-    <VTextField slot-scope="{ errors }" v-model="innerValue" :error-messages="errors" />
+  <ValidationProvider :rules="rules" v-slot="{ errors }">
+    <VTextField v-model="innerValue" :error-messages="errors" />
   </ValidationProvider>
 </template>
 
@@ -269,6 +306,7 @@ All the following props are optional.
 | name      | `string`  | `undefined`           | A string that will be used to replace `{field}` in error messages and for [custom error messages](/guide/messages.md#field-specific-custom-messages). |
 | bails     | `boolean` | `true`                | If true, the validation will stop on the first failing rule.                 |
 | debounce  | `number`  | `0`                   | Debounces the validation for the specified amount of milliseconds.           |
+| tag       | `string`  | `span`                | The default tag to [render](#rendering). |
 
 ### Methods
 
diff --git a/docs/guide/rules.md b/docs/guide/rules.md
index a42b2849..321da977 100644
--- a/docs/guide/rules.md
+++ b/docs/guide/rules.md
@@ -601,3 +601,11 @@ This rule can recieve any args from [validator.js isURL](https://github.com/chri
 ```html
 <input v-validate="{url: {require_protocol: true }}" data-vv-as="field" name="url_field" type="text">
 ```
+
+<script>
+export default {
+  $_veeValidate: {
+    validator: 'new'
+  }
+}
+</script>
diff --git a/src/components/observer.js b/src/components/observer.js
index 59a49d48..3dfffa2b 100644
--- a/src/components/observer.js
+++ b/src/components/observer.js
@@ -1,4 +1,3 @@
-import { createRenderless } from '../utils/vnode';
 import { isCallable, values, findIndex } from '../utils';
 
 const flagMergingStrategy = {
@@ -39,6 +38,12 @@ export const ValidationObserver = {
       }
     }
   },
+  props: {
+    tag: {
+      type: String,
+      default: 'span'
+    }
+  },
   data: () => ({
     vid: `obs_${OBSERVER_COUNTER++}`,
     refs: {},
@@ -111,10 +116,13 @@ export const ValidationObserver = {
   render (h) {
     let slots = this.$scopedSlots.default;
     if (!isCallable(slots)) {
-      return createRenderless(h, this.$slots.default);
+      return h(this.tag, this.$slots.default);
     }
 
-    return createRenderless(h, slots(this.ctx));
+    return h(this.tag, {
+      on: this.$listeners,
+      attrs: this.$attrs
+    }, slots(this.ctx));
   },
   methods: {
     subscribe (subscriber, kind = 'provider') {
diff --git a/src/components/provider.js b/src/components/provider.js
index 375599db..88c99448 100644
--- a/src/components/provider.js
+++ b/src/components/provider.js
@@ -5,7 +5,7 @@ import Validator from '../core/validator';
 import RuleContainer from '../core/ruleContainer';
 import { normalizeEvents, isEvent } from '../utils/events';
 import { createFlags, normalizeRules, warn, isCallable, debounce, isNullOrUndefined, assign } from '../utils';
-import { findModel, extractVNodes, addVNodeListener, getInputEventName, createRenderless } from '../utils/vnode';
+import { findModel, extractVNodes, addVNodeListener, getInputEventName } from '../utils/vnode';
 
 let $validator = null;
 
@@ -263,6 +263,10 @@ export const ValidationProvider = {
     debounce: {
       type: Number,
       default: () => getConfig().delay || 0
+    },
+    tag: {
+      type: String,
+      default: 'span'
     }
   },
   watch: {
@@ -355,7 +359,7 @@ export const ValidationProvider = {
         warn('ValidationProvider expects a scoped slot. Did you forget to add "slot-scope" to your slot?');
       }
 
-      return createRenderless(h, this.$slots.default);
+      return h(this.tag, this.$slots.default);
     }
 
     const nodes = slot(ctx);
@@ -364,7 +368,7 @@ export const ValidationProvider = {
       addListeners.call(this, input);
     });
 
-    return createRenderless(h, nodes);
+    return h(this.tag, nodes);
   },
   beforeDestroy () {
     // cleanup reference.
diff --git a/src/utils/vnode.js b/src/utils/vnode.js
index 8a225d78..bc58e254 100644
--- a/src/utils/vnode.js
+++ b/src/utils/vnode.js
@@ -1,5 +1,5 @@
 // VNode Utils
-import { find, isCallable, isNullOrUndefined, isTextInput, warn } from './index';
+import { find, isCallable, isNullOrUndefined, isTextInput } from './index';
 
 // Gets the model object on the vnode.
 export function findModel (vnode) {
@@ -138,22 +138,4 @@ export function normalizeSlots (slots, ctx) {
 
     return arr.concat(slots[key]);
   }, []);
-}
-
-export function createRenderless (h, vnode) {
-  // a single-root slot yay!
-  if (!Array.isArray(vnode)) {
-    return vnode;
-  }
-
-  if (vnode.length === 1) {
-    return vnode[0];
-  }
-
-  if (process.env.NODE_ENV !== 'production') {
-    warn('Your slot should have one root element. Rendering a span as the root.');
-  }
-
-  // Renders a multi-root node, should throw a Vue error.
-  return vnode;
 };


Test patch:
diff --git a/tests/unit/component.js b/tests/unit/component.js
index 0c7481cb..55c0568d 100644
--- a/tests/unit/component.js
+++ b/tests/unit/component.js
@@ -12,16 +12,17 @@ Vue.component('ValidationObserver', VeeValidate.ValidationObserver);
 const DEFAULT_REQUIRED_MESSAGE = 'The {field} field is required.';
 
 describe('Validation Provider Component', () => {
-  test('renders its slot', () => {
+  test('renders its tag attribute', () => {
     const wrapper = mount({
+      data: () => ({ val: '' }),
       template: `
-        <ValidationProvider>
-          <p slot-scope="ctx"></p>
+        <ValidationProvider v-slot="ctx">
+          <input v-model="val" type="text">
         </ValidationProvider>
       `
     }, { localVue: Vue });
 
-    expect(wrapper.html()).toBe(`<p></p>`);
+    expect(wrapper.html()).toBe(`<span><input type="text"></span>`);
   });
 
   test('SSR: render single root slot', () => {
@@ -33,7 +34,7 @@ describe('Validation Provider Component', () => {
       `
     }, { localVue: Vue });
 
-    expect(output).toBe('<p data-server-rendered="true"></p>');
+    expect(output).toBe('<span data-server-rendered="true"><p></p></span>');
   });
 
   test('validates lazy models', async () => {
@@ -581,8 +582,7 @@ describe('Validation Observer Component', () => {
   test('renders the slot', () => {
     const wrapper = mount({
       template: `
-        <ValidationObserver>
-          <form slot-scope="ctx"></form>
+        <ValidationObserver tag="form" v-slot="ctx">
         </ValidationObserver>
       `
     }, { localVue: Vue });
@@ -593,8 +593,7 @@ describe('Validation Observer Component', () => {
   test('renders the scoped slot in SSR', () => {
     const output = renderToString({
       template: `
-        <ValidationObserver>
-          <form slot-scope="ctx"></form>
+        <ValidationObserver tag="form" v-slot="ctx">
         </ValidationObserver>
       `
     }, { localVue: Vue });
@@ -605,8 +604,7 @@ describe('Validation Observer Component', () => {
   test('renders the default slot if no scoped slots in SSR', () => {
     const output = renderToString({
       template: `
-        <ValidationObserver>
-          <form></form>
+        <ValidationObserver tag="form">
         </ValidationObserver>
       `
     }, { localVue: Vue });