From 2301c5ae75eb8590cb2cc919215ffe4ae934b885 Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Fri, 10 Jan 2020 22:12:46 +0200 Subject: [PATCH] feat: adapt the changes from the v3 master branch --- packages/core/src/components/Observer.ts | 82 +++++++++++++----------- packages/core/src/components/Provider.ts | 11 ++-- packages/core/src/localize.ts | 2 +- packages/core/src/types.ts | 4 +- packages/core/src/utils/index.ts | 1 + packages/core/src/utils/vnode.ts | 2 +- packages/core/src/validate.ts | 42 ++++-------- packages/core/tests/provider.spec.ts | 6 +- packages/core/tests/targets.spec.ts | 10 +-- packages/shared/src/types.ts | 1 - 10 files changed, 77 insertions(+), 84 deletions(-) diff --git a/packages/core/src/components/Observer.ts b/packages/core/src/components/Observer.ts index 659f0e700..3ea5f1843 100644 --- a/packages/core/src/components/Observer.ts +++ b/packages/core/src/components/Observer.ts @@ -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 = {}; - - 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); @@ -177,7 +145,7 @@ 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; @@ -185,7 +153,7 @@ export const ValidationObserver = (Vue as withObserverNode).extend({ 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) { @@ -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) { + setErrors(errors: Record) { 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) => { @@ -241,13 +211,13 @@ type ObserverInstance = InstanceType; 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'); } } @@ -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 = {}; + + 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 }; +} diff --git a/packages/core/src/components/Provider.ts b/packages/core/src/components/Provider.ts index 56f26688d..b18f6176d 100644 --- a/packages/core/src/components/Provider.ts +++ b/packages/core/src/components/Provider.ts @@ -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; @@ -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 { @@ -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]; } }; diff --git a/packages/core/src/localize.ts b/packages/core/src/localize.ts index 5bbd3c1a2..3fc74d288 100644 --- a/packages/core/src/localize.ts +++ b/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 { diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index dc6edd94b..03a863428 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -68,8 +68,8 @@ export interface ValidationFlags { export interface VeeObserver { refs: Record; - 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 { diff --git a/packages/core/src/utils/index.ts b/packages/core/src/utils/index.ts index 84738041a..b9693a744 100644 --- a/packages/core/src/utils/index.ts +++ b/packages/core/src/utils/index.ts @@ -3,3 +3,4 @@ export * from './collections'; export * from './factories'; export * from './functions'; export * from './strings'; +export * from './rules'; diff --git a/packages/core/src/utils/vnode.ts b/packages/core/src/utils/vnode.ts index afd119460..c8209b5b9 100644 --- a/packages/core/src/utils/vnode.ts +++ b/packages/core/src/utils/vnode.ts @@ -252,7 +252,7 @@ function resolveTextualRules(vnode: VNode): Record { } 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) { diff --git a/packages/core/src/validate.ts b/packages/core/src/validate.ts index 82e65990a..fe202feb1 100644 --- a/packages/core/src/validate.ts +++ b/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, @@ -8,8 +9,6 @@ import { ValidationResult, ValidationRuleSchema } from './types'; -import { getConfig } from './config'; -import { normalizeRules } from './utils/rules'; interface FieldContext { name: string; @@ -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) }; } @@ -220,15 +218,13 @@ function _generateFieldError( value: any, ruleSchema: ValidationRuleSchema, ruleName: string, - params: Record, - data?: Record + params: Record ) { 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, @@ -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; @@ -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 { diff --git a/packages/core/tests/provider.spec.ts b/packages/core/tests/provider.spec.ts index 3a99977ab..ba0a36b5a 100644 --- a/packages/core/tests/provider.spec.ts +++ b/packages/core/tests/provider.spec.ts @@ -556,9 +556,10 @@ test('resets validation state', async () => { }, template: `
- + {{ errors && errors[0] }} + {{ failedRules.required }}
` @@ -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 () => { diff --git a/packages/core/tests/targets.spec.ts b/packages/core/tests/targets.spec.ts index b78e57b32..977996cd4 100644 --- a/packages/core/tests/targets.spec.ts +++ b/packages/core/tests/targets.spec.ts @@ -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' }; @@ -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 }, @@ -92,10 +92,12 @@ describe('cross-field syntax', () => { test('with options.customMessages function', async () => { const customMessages = { - between(_: string, { min, _maxValueTarget_ }: Record) { - return `Must be more than ${min} and less than ${_maxValueTarget_}`; + between(_: string, { min, max }: Record) { + 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'); diff --git a/packages/shared/src/types.ts b/packages/shared/src/types.ts index 8d549f596..5e4023ab9 100644 --- a/packages/shared/src/types.ts +++ b/packages/shared/src/types.ts @@ -13,7 +13,6 @@ export type ValidationRuleFunction = ( ) => boolean | string | ValidationRuleResult | Promise; export interface ValidationRuleResult { - data?: Record; valid: boolean; required?: boolean; }