Skip to content

Commit

Permalink
feat: adapt the changes from the v3 master branch
Browse files Browse the repository at this point in the history
  • Loading branch information
logaretm committed Jan 10, 2020
1 parent de3c56a commit 2301c5a
Show file tree
Hide file tree
Showing 10 changed files with 77 additions and 84 deletions.
82 changes: 43 additions & 39 deletions packages/core/src/components/Observer.ts
Expand Up @@ -128,39 +128,7 @@ export const ValidationObserver = (Vue as withObserverNode).extend({
16
);

this.$watch(() => {
const vms = [...values(this.refs), ...this.observers];
let errors: ObserverErrors = {};
const flags: ValidationFlags = createObserverFlags();
let fields: Record<string, ObserverField> = {};

const length = vms.length;
for (let i = 0; i < length; i++) {
const vm = vms[i];

// validation provider
if (Array.isArray(vm.errors)) {
errors[vm.id] = vm.errors;
fields[vm.id] = {
id: vm.id,
name: vm.name,
failedRules: vm.failedRules,
...vm.flags
};
continue;
}

// Nested observer, merge errors and fields
errors = { ...errors, ...vm.errors };
fields = { ...fields, ...vm.fields };
}

FLAGS_STRATEGIES.forEach(([flag, method]) => {
flags[flag] = vms[method](vm => vm.flags[flag]);
});

return { errors, flags, fields };
}, onChange as any);
this.$watch(computeObserverState, onChange as any);
},
activated() {
register(this);
Expand All @@ -177,15 +145,15 @@ export const ValidationObserver = (Vue as withObserverNode).extend({
return this.slim && children.length <= 1 ? children[0] : h(this.tag, { on: this.$listeners }, children);
},
methods: {
subscribe(subscriber: any, kind = 'provider') {
observe(subscriber: any, kind = 'provider') {
if (kind === 'observer') {
this.observers.push(subscriber);
return;
}

this.refs = { ...this.refs, ...{ [subscriber.id]: subscriber } };
},
unsubscribe(id: string, kind = 'provider') {
unobserve(id: string, kind = 'provider') {
if (kind === 'provider') {
const provider = this.refs[id];
if (!provider) {
Expand Down Expand Up @@ -222,12 +190,14 @@ export const ValidationObserver = (Vue as withObserverNode).extend({
reset() {
return [...values(this.refs), ...this.observers].forEach(ref => ref.reset());
},
setErrors(errors: Record<string, string[]>) {
setErrors(errors: Record<string, string[] | string>) {
Object.keys(errors).forEach(key => {
const provider = this.refs[key];
if (!provider) return;
let errorArr = errors[key] || [];
errorArr = typeof errorArr === 'string' ? [errorArr] : errorArr;

provider.setErrors(errors[key] || []);
provider.setErrors(errorArr);
});

this.observers.forEach((observer: any) => {
Expand All @@ -241,13 +211,13 @@ type ObserverInstance = InstanceType<typeof ValidationObserver>;

function unregister(vm: ObserverInstance) {
if (vm.$_veeObserver) {
vm.$_veeObserver.unsubscribe(vm.id, 'observer');
vm.$_veeObserver.unobserve(vm.id, 'observer');
}
}

function register(vm: ObserverInstance) {
if (vm.$_veeObserver) {
vm.$_veeObserver.subscribe(vm, 'observer');
vm.$_veeObserver.observe(vm, 'observer');
}
}

Expand All @@ -271,3 +241,37 @@ function createObserverFlags() {
invalid: false
};
}

function computeObserverState(this: ObserverInstance) {
const vms = [...values(this.refs), ...this.observers];
let errors: ObserverErrors = {};
const flags: ValidationFlags = createObserverFlags();
let fields: Record<string, ObserverField> = {};

const length = vms.length;
for (let i = 0; i < length; i++) {
const vm = vms[i];

// validation provider
if (Array.isArray(vm.errors)) {
errors[vm.id] = vm.errors;
fields[vm.id] = {
id: vm.id,
name: vm.name,
failedRules: vm.failedRules,
...vm.flags
};
continue;
}

// Nested observer, merge errors and fields
errors = { ...errors, ...vm.errors };
fields = { ...fields, ...vm.fields };
}

FLAGS_STRATEGIES.forEach(([flag, method]) => {
flags[flag] = vms[method](vm => vm.flags[flag]);
});

return { errors, flags, fields };
}
11 changes: 6 additions & 5 deletions packages/core/src/components/Provider.ts
Expand Up @@ -215,7 +215,7 @@ export const ValidationProvider = (Vue as withProviderPrivates).extend({
},
beforeDestroy() {
// cleanup reference.
this.$_veeObserver.unsubscribe(this.id);
this.$_veeObserver.unobserve(this.id);
},
activated() {
this.isActive = true;
Expand All @@ -240,6 +240,7 @@ export const ValidationProvider = (Vue as withProviderPrivates).extend({
const flags = createFlags();
flags.required = this.isRequired;
this.setFlags(flags);
this.failedRules = {};
this.validateSilent();
},
async validate(...args: any[]): Promise<ValidationResult> {
Expand Down Expand Up @@ -377,20 +378,20 @@ function updateRenderingContextRefs(vm: ProviderInstance) {

// vid was changed.
if (id !== providedId && vm.$_veeObserver.refs[id] === vm) {
vm.$_veeObserver.unsubscribe(id);
vm.$_veeObserver.unobserve(id);
}

vm.id = providedId;
vm.$_veeObserver.subscribe(vm);
vm.$_veeObserver.observe(vm);
}

function createObserver(): VeeObserver {
return {
refs: {},
subscribe(vm: ProviderInstance) {
observe(vm: ProviderInstance) {
this.refs[vm.id] = vm;
},
unsubscribe(id: string) {
unobserve(id: string) {
delete this.refs[id];
}
};
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/localize.ts
@@ -1,6 +1,6 @@
import { isCallable, merge, interpolate } from './utils';
import { ValidationMessageTemplate } from './types';
import { getConfig, setConfig } from './config';
import { setConfig } from './config';
import { localeChanged } from './localeChanged';

interface PartialI18nDictionary {
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/types.ts
Expand Up @@ -68,8 +68,8 @@ export interface ValidationFlags {

export interface VeeObserver {
refs: Record<string, ProviderInstance>;
subscribe(provider: any, type?: 'provider' | 'observer'): void;
unsubscribe(id: string, type?: 'provider' | 'observer'): void;
observe(provider: any, type?: 'provider' | 'observer'): void;
unobserve(id: string, type?: 'provider' | 'observer'): void;
}

export interface InactiveRefCache {
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/utils/index.ts
Expand Up @@ -3,3 +3,4 @@ export * from './collections';
export * from './factories';
export * from './functions';
export * from './strings';
export * from './rules';
2 changes: 1 addition & 1 deletion packages/core/src/utils/vnode.ts
Expand Up @@ -252,7 +252,7 @@ function resolveTextualRules(vnode: VNode): Record<string, any> {
}

export function resolveRules(vnode: VNode) {
const htmlTags = ['input', 'select'];
const htmlTags = ['input', 'select', 'textarea'];
const attrs = vnode.data?.attrs;

if (!includes(htmlTags, vnode.tag) || !attrs) {
Expand Down
42 changes: 12 additions & 30 deletions packages/core/src/validate.ts
@@ -1,5 +1,6 @@
import { RuleContainer } from './extend';
import { interpolate, isEmptyArray, isLocator, isNullOrUndefined, isObject } from './utils';
import { interpolate, isEmptyArray, isLocator, isNullOrUndefined, isObject, normalizeRules } from './utils';
import { getConfig } from './config';
import {
RuleParamConfig,
RuleParamSchema,
Expand All @@ -8,8 +9,6 @@ import {
ValidationResult,
ValidationRuleSchema
} from './types';
import { getConfig } from './config';
import { normalizeRules } from './utils/rules';

interface FieldContext {
name: string;
Expand Down Expand Up @@ -201,14 +200,13 @@ async function _test(field: FieldContext, value: any, rule: { name: string; para
}

if (!isObject(result)) {
result = { valid: result, data: {} };
result = { valid: result };
}

return {
valid: result.valid,
required: result.required,
data: result.data || {},
error: result.valid ? undefined : _generateFieldError(field, value, ruleSchema, rule.name, params, result.data)
error: result.valid ? undefined : _generateFieldError(field, value, ruleSchema, rule.name, params)
};
}

Expand All @@ -220,15 +218,13 @@ function _generateFieldError(
value: any,
ruleSchema: ValidationRuleSchema,
ruleName: string,
params: Record<string, any>,
data?: Record<string, any>
params: Record<string, any>
) {
const message = field.customMessages[ruleName] || ruleSchema.message;
const ruleTargets = _getRuleTargets(field, ruleSchema, ruleName);
const { userTargets, userMessage } = _getUserTargets(field, ruleSchema, ruleName, message);
const values = {
...(params || {}),
...(data || {}),
_field_: field.name,
_value_: value,
_rule_: ruleName,
Expand Down Expand Up @@ -267,22 +263,15 @@ function _getRuleTargets(

for (let index = 0; index < params.length; index++) {
const param: RuleParamConfig = params[index] as RuleParamConfig;
if (!param.isTarget) {
continue;
}

let key = ruleConfig[index];
if (isLocator(key)) {
key = key.__locatorRef;
if (!isLocator(key)) {
continue;
}

key = key.__locatorRef;
const name = field.names[key] || key;
if (numTargets === 1) {
names._target_ = name;
break;
}

names[`_${param.name}Target_`] = name;
names[param.name] = name;
names[`_${param.name}_`] = field.crossTable[key];
}

return names;
Expand Down Expand Up @@ -319,15 +308,8 @@ function _getUserTargets(

// grab the name of the target
const name = rule.__locatorRef;
const placeholder = `_${name}Target_`;
userTargets[placeholder] = field.names[name] || name;
userTargets[name] = field.names[name] || name;

// update template if it's a string
if (typeof userMessage === 'string') {
const rx = new RegExp(`{${param.name}}`, 'g');
userMessage = userMessage.replace(rx, `{${placeholder}}`);
}
userTargets[param.name] = field.names[name] || name;
userTargets[`_${param.name}_`] = field.crossTable[name];
});

return {
Expand Down
6 changes: 5 additions & 1 deletion packages/core/tests/provider.spec.ts
Expand Up @@ -556,9 +556,10 @@ test('resets validation state', async () => {
},
template: `
<div>
<ValidationProvider rules="required" ref="provider" v-slot="{ errors }">
<ValidationProvider rules="required" ref="provider" v-slot="{ errors, failedRules }">
<TextInput v-model="value" ref="input"></TextInput>
<span id="error">{{ errors && errors[0] }}</span>
<span id="failed">{{ failedRules.required }}</span>
</ValidationProvider>
</div>
`
Expand All @@ -570,15 +571,18 @@ test('resets validation state', async () => {
const input = wrapper.find('#input');

expect(error.text()).toBe('');
expect(wrapper.find('#failed').text()).toBe('');

input.setValue('');
await flushPromises();

expect(error.text()).toBe(DEFAULT_REQUIRED_MESSAGE);
expect(wrapper.find('#failed').text()).toBe(DEFAULT_REQUIRED_MESSAGE);

(wrapper.vm as any).$refs.provider.reset();
await flushPromises();
expect(error.text()).toBe('');
expect(wrapper.find('#failed').text()).toBe('');
});

test('setting bails prop to false disables fast exit', async () => {
Expand Down
10 changes: 6 additions & 4 deletions packages/core/tests/targets.spec.ts
Expand Up @@ -4,7 +4,7 @@ import { between, confirmed } from '@vee-validate/rules';
describe('target field placeholder', () => {
extend('confirmed', {
...confirmed,
message: '{_field_} must match {_target_}'
message: '{_field_} must match {target}'
});

const names = { foo: 'Foo', bar: 'Bar', baz: 'Baz' };
Expand Down Expand Up @@ -34,7 +34,7 @@ describe('target field placeholder', () => {

test('works for multiple targets', async () => {
extend('sum_of', {
message: '{_field_} must be the sum of {_aTarget_} and {_bTarget_}',
message: '{_field_} must be the sum of {a} and {b}',
// eslint-disable-next-line prettier/prettier
params: [
{ name: 'a', isTarget: true },
Expand Down Expand Up @@ -92,10 +92,12 @@ describe('cross-field syntax', () => {

test('with options.customMessages function', async () => {
const customMessages = {
between(_: string, { min, _maxValueTarget_ }: Record<string, string>) {
return `Must be more than ${min} and less than ${_maxValueTarget_}`;
between(_: string, { min, max }: Record<string, any>) {
return `Must be more than ${min} and less than ${max}`;
}
};

// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
// @ts-ignore
const result = await validate(values.value, rules, { ...options, customMessages, names });
expect(result.errors[0]).toEqual('Must be more than 0 and less than Max Value');
Expand Down
1 change: 0 additions & 1 deletion packages/shared/src/types.ts
Expand Up @@ -13,7 +13,6 @@ export type ValidationRuleFunction = (
) => boolean | string | ValidationRuleResult | Promise<boolean | string | ValidationRuleResult>;

export interface ValidationRuleResult {
data?: Record<string, any>;
valid: boolean;
required?: boolean;
}

0 comments on commit 2301c5a

Please sign in to comment.