diff --git a/src/Formulario.ts b/src/Formulario.ts index 9c41268..8a6d253 100644 --- a/src/Formulario.ts +++ b/src/Formulario.ts @@ -7,8 +7,11 @@ import { ValidationRuleFn, ValidationMessageFn, ValidationMessageI18NFn, + ViolationsRecord, } from '@/validation/validator' +import { FormularioFormInterface } from '@/types' + export interface FormularioOptions { validationRules?: Record; validationMessages?: Record; @@ -21,7 +24,11 @@ export default class Formulario { public validationRules: Record = {} public validationMessages: Record = {} + private readonly registry: Map + constructor (options?: FormularioOptions) { + this.registry = new Map() + this.validationRules = validationRules this.validationMessages = validationMessages @@ -37,11 +44,54 @@ export default class Formulario { this.validationMessages = merge(this.validationMessages, extendWith.validationMessages || {}) return this } - throw new Error(`[Formulario]: Formulario.extend() should be passed an object (was ${typeof extendWith})`) + throw new Error(`[Formulario]: Formulario.extend(): should be passed an object (was ${typeof extendWith})`) + } + + runValidation (id: string): Promise { + if (!this.registry.has(id)) { + throw new Error(`[Formulario]: Formulario.runValidation(): no forms with id "${id}"`) + } + + const form = this.registry.get(id) as FormularioFormInterface + + return form.runValidation() + } + + resetValidation (id: string): void { + if (!this.registry.has(id)) { + return + } + + const form = this.registry.get(id) as FormularioFormInterface + + form.resetValidation() + } + + /** + * Used by forms instances to add themselves into a registry + * @internal + */ + register (id: string, form: FormularioFormInterface): void { + if (this.registry.has(id)) { + throw new Error(`[Formulario]: Formulario.register(): id "${id}" is already in use`) + } + + this.registry.set(id, form) + } + + /** + * Used by forms instances to remove themselves from a registry + * @internal + */ + unregister (id: string): void { + if (this.registry.has(id)) { + this.registry.delete(id) + } } /** * Get validation rules by merging any passed in with global rules. + * @internal */ getRules (extendWith: Record = {}): Record { return merge(this.validationRules, extendWith) @@ -49,6 +99,7 @@ export default class Formulario { /** * Get validation messages by merging any passed in with global messages. + * @internal */ getMessages (vm: Vue, extendWith: Record): Record { const raw = merge(this.validationMessages || {}, extendWith) diff --git a/src/FormularioForm.vue b/src/FormularioForm.vue index 1b7d44b..a07cb3a 100644 --- a/src/FormularioForm.vue +++ b/src/FormularioForm.vue @@ -25,7 +25,10 @@ import { import PathRegistry from '@/PathRegistry' import { FormularioFieldInterface } from '@/types' -import { Violation } from '@/validation/validator' +import { + Violation, + ViolationsRecord, +} from '@/validation/validator' type ErrorsRecord = Record @@ -34,22 +37,23 @@ type ValidationEventPayload = { violations: Violation[]; } -type ViolationsRecord = Record +let counter = 0 @Component({ name: 'FormularioForm' }) export default class FormularioForm extends Vue { @Model('input', { default: () => ({}) }) public readonly state!: Record + @Prop({ default: () => `formulario-form-${++counter}` }) + public readonly id!: string + // Describes validation errors of whole form @Prop({ default: () => ({}) }) readonly fieldsErrors!: ErrorsRecord // Only used on FormularioForm default slot @Prop({ default: () => ([]) }) readonly formErrors!: string[] - public proxy: Record = {} - + private proxy: Record = {} private registry: PathRegistry = new PathRegistry() - // Local error messages are temporal, they wiped each resetValidation call private localFieldsErrors: ErrorsRecord = {} private localFormErrors: string[] = [] @@ -148,6 +152,11 @@ export default class FormularioForm extends Vue { public created (): void { this.syncProxy() + this.$formulario.register(this.id, this) + } + + public beforeDestroy (): void { + this.$formulario.unregister(this.id) } public runValidation (): Promise { diff --git a/src/types.ts b/src/types.ts index 873fe67..5e2542d 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,4 +1,9 @@ -import { Violation } from '@/validation/validator' +import { Violation, ViolationsRecord } from '@/validation/validator' + +export interface FormularioFormInterface { + runValidation(): Promise; + resetValidation(): void; +} export interface FormularioFieldInterface { hasModel: boolean; diff --git a/src/validation/validator.ts b/src/validation/validator.ts index a34b2b5..6f8a964 100644 --- a/src/validation/validator.ts +++ b/src/validation/validator.ts @@ -11,6 +11,8 @@ export interface Violation { context: ValidationContext|null; } +export type ViolationsRecord = Record + export interface ValidationRuleFn { (context: ValidationContext, ...args: any[]): Promise|boolean; } diff --git a/test/unit/FormularioForm.test.js b/test/unit/FormularioForm.test.js index 4529b9f..05dec68 100644 --- a/test/unit/FormularioForm.test.js +++ b/test/unit/FormularioForm.test.js @@ -192,6 +192,52 @@ describe('FormularioForm', () => { }) }) + test('resolves runValidation via $formulario', async () => { + const wrapper = mount(FormularioForm, { + propsData: { + id: 'address', + }, + slots: { + default: ` +
+ + +
+ `, + }, + }) + + const violations = await wrapper.vm.$formulario.runValidation('address') + const state = { + address: { + street: null, + }, + } + + expect(violations).toEqual({ + 'address.street': [{ + message: expect.any(String), + rule: 'required', + args: [], + context: { + name: 'address.street', + value: null, + formValues: state, + }, + }], + 'address.building': [{ + message: expect.any(String), + rule: 'required', + args: [], + context: { + name: 'address.building', + value: '', + formValues: state, + }, + }], + }) + }) + test('resolves hasValidationErrors to true', async () => { const wrapper = mount(FormularioForm, { slots: {