Skip to content

Commit

Permalink
Fix: Validation not working when using model less validation wi… (#2355)
Browse files Browse the repository at this point in the history
* fix: lint-staged. Remove invalid test

* fix: validation with initial value and without v-modal
  • Loading branch information
Ricky-rick authored and logaretm committed Sep 20, 2019
1 parent 576951d commit 673e7c7
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 41 deletions.
25 changes: 12 additions & 13 deletions src/components/common.ts
Expand Up @@ -2,19 +2,19 @@ import { VNodeDirective, VNode } from 'vue';
import { isCallable, debounce, identity } from '../utils';
import { modes, InteractionModeFactory } from '../modes';
import { ValidationResult, ValidationFlags, KnownKeys, ProviderInstance } from '../types';
import { findModel, getInputEventName, addVNodeListener } from '../utils/vnode';
import { findModel, getInputEventName, addVNodeListener, findValue } from '../utils/vnode';

/**
* Determines if a provider needs to run validation.
*/
function shouldValidate(ctx: ProviderInstance, model: VNodeDirective) {
function shouldValidate(ctx: ProviderInstance, value: string) {
// when an immediate/initial validation is needed and wasn't done before.
if (!ctx._ignoreImmediate && ctx.immediate) {
return true;
}

// when the value changes for whatever reason.
if (ctx.value !== model.value && ctx.normalizedEvents.length) {
if (ctx.value !== value && ctx.normalizedEvents.length) {
return true;
}

Expand All @@ -24,7 +24,7 @@ function shouldValidate(ctx: ProviderInstance, model: VNodeDirective) {
}

// when the initial value is undefined and the field wasn't rendered yet.
if (!ctx.initialized && model.value === undefined) {
if (!ctx.initialized && value === undefined) {
return true;
}

Expand Down Expand Up @@ -69,18 +69,18 @@ export function createValidationCtx(ctx: ProviderInstance): ValidationContext {
};
}

export function onRenderUpdate(vm: ProviderInstance, model: VNodeDirective | undefined) {
if (!model) {
export function onRenderUpdate(vm: ProviderInstance, value: any | undefined) {
if (value === undefined) {
return;
}

if (!vm.initialized) {
vm.initialValue = model.value;
vm.initialValue = value;
}

const validateNow = shouldValidate(vm, model);
const validateNow = shouldValidate(vm, value);
vm._needsValidation = false;
vm.value = model.value;
vm.value = value;
vm._ignoreImmediate = true;

if (!validateNow) {
Expand Down Expand Up @@ -150,11 +150,10 @@ export function createCommonHandlers(vm: ProviderInstance) {

// Adds all plugin listeners to the vnode.
export function addListeners(vm: ProviderInstance, node: VNode) {
const model = findModel(node);
const value = findValue(node);
// cache the input eventName.
vm._inputEventName = vm._inputEventName || getInputEventName(node, model);

onRenderUpdate(vm, model);
vm._inputEventName = vm._inputEventName || getInputEventName(node, findModel(node));
onRenderUpdate(vm, value);

const { onInput, onBlur, onValidate } = createCommonHandlers(vm);
addVNodeListener(node, vm._inputEventName, onInput);
Expand Down
12 changes: 10 additions & 2 deletions src/components/withValidation.ts
@@ -1,6 +1,13 @@
import { ValidationProvider } from './Provider';
import { identity } from '../utils';
import { findModel, findModelConfig, mergeVNodeListeners, getInputEventName, normalizeSlots } from '../utils/vnode';
import {
findModel,
findModelConfig,
mergeVNodeListeners,
getInputEventName,
normalizeSlots,
findValue
} from '../utils/vnode';
import { CreateElement, Component } from 'vue';
import { createValidationCtx, onRenderUpdate, createCommonHandlers, ValidationContext } from './common';

Expand Down Expand Up @@ -29,7 +36,8 @@ export function withValidation(component: ComponentLike, mapProps: ValidationCon

const model = findModel(this.$vnode);
this._inputEventName = this._inputEventName || getInputEventName(this.$vnode, model);
onRenderUpdate(this, model);
const value = findValue(this.$vnode);
onRenderUpdate(this, value);

const { onInput, onBlur, onValidate } = createCommonHandlers(this);

Expand Down
20 changes: 19 additions & 1 deletion src/utils/vnode.ts
Expand Up @@ -44,6 +44,24 @@ export function findModel(vnode: VNode): VNodeDirective | undefined {
return find(vnode.data.directives, d => d.name === 'model');
}

export function findValue(vnode: VNode): any | undefined {
const model = findModel(vnode);
if (model) {
return model.value;
}

if (vnode.componentOptions && vnode.componentOptions.propsData && 'value' in vnode.componentOptions.propsData) {
const propsDataWithValue = vnode.componentOptions.propsData as any;
return propsDataWithValue.value;
}

if (vnode.data && vnode.data.domProps && 'value' in vnode.data.domProps) {
return vnode.data.domProps.value;
}

return undefined;
}

function extractChildren(vnode: VNode | VNode[]): VNode[] {
if (Array.isArray(vnode)) {
return vnode;
Expand All @@ -62,7 +80,7 @@ function extractChildren(vnode: VNode | VNode[]): VNode[] {
}

export function extractVNodes(vnode: VNode | VNode[]): VNode[] {
if (!Array.isArray(vnode) && findModel(vnode)) {
if (!Array.isArray(vnode) && findValue(vnode) !== undefined) {
return [vnode];
}

Expand Down
4 changes: 4 additions & 0 deletions tests/helpers/ModelComp.js
@@ -0,0 +1,4 @@
export default {
props: ['value'],
template: `<input type="text" :value="value" @input="emit('input', $event)">`
};
77 changes: 52 additions & 25 deletions tests/providers/provider.js
Expand Up @@ -4,6 +4,7 @@ import flushPromises from 'flush-promises';
import { ValidationProvider, ValidationObserver, extend, withValidation, configure } from '@/index.full';
import InputWithoutValidation from './components/Input';
import SelectWithoutValidation from './components/Select';
import ModelComp from './../helpers/ModelComp';

const Vue = createLocalVue();
Vue.component('ValidationProvider', ValidationProvider);
Expand Down Expand Up @@ -781,6 +782,55 @@ test('validates manually using the validate event handler', async () => {
expect(error.text()).toBeFalsy();
});

test('validates manually with a initial value using the validate event handler on native comp', async () => {
const wrapper = mount(
{
data: () => ({
myValue: 'initial value'
}),
template: `
<ValidationObserver ref="obs">
<ValidationProvider rules="required" v-slot="{ validate, errors }">
<input type="text" :value="myValue" @input="validate">
<p id="error">{{ errors[0] }}</p>
</ValidationProvider>
</ValidationObserver>
`
},
{ localVue: Vue, sync: false }
);

await wrapper.vm.$refs.obs.validate();

const error = wrapper.find('#error');
expect(error.text()).toBe('');
});

test('validates manually with a initial value using the validate event handler on vue comp', async () => {
Vue.component('ModelComp', ModelComp);
const wrapper = mount(
{
data: () => ({
myValue: 'initial value'
}),
template: `
<ValidationObserver ref="obs">
<ValidationProvider rules="required" v-slot="{ validate, errors }">
<ModelComp :value="myValue" @input="validate" />
<p id="error">{{ errors[0] }}</p>
</ValidationProvider>
</ValidationObserver>
`
},
{ localVue: Vue, sync: false }
);

await wrapper.vm.$refs.obs.validate();

const error = wrapper.find('#error');
expect(error.text()).toBe('');
});

test('resets validation state using reset method in slot scope data', async () => {
const wrapper = mount(
{
Expand Down Expand Up @@ -1021,7 +1071,7 @@ test('array param collecting in the last parameter', async () => {
expect(wrapper.find('#error').text()).toBe('');
});

test('should throw if rule does not exist', () => {
test('should throw if rule does not exist', async () => {
const wrapper = mount(
{
data: () => ({ val: '123' }),
Expand All @@ -1033,30 +1083,7 @@ test('should throw if rule does not exist', () => {
},
{ localVue: Vue, sync: false }
);
expect(wrapper.vm.$refs.pro.validate()).rejects.toThrow();
});

test('should throw if required rule does not return an object', () => {
extend('faultyRequired', {
computesRequired: true,
validate() {
return false;
}
});

const wrapper = mount(
{
data: () => ({ val: '' }),
template: `
<ValidationProvider rules="faultyRequired" v-slot="ctx" ref="pro">
<input v-model="val" type="text">
</ValidationProvider>
`
},
{ localVue: Vue, sync: false }
);

expect(wrapper.vm.$refs.pro.validate()).rejects.toThrow();
await expect(wrapper.vm.$refs.pro.validate()).rejects.toThrow();
});

test('returns custom error messages passed in the customMessages prop', async () => {
Expand Down

0 comments on commit 673e7c7

Please sign in to comment.