1
0
mirror of synced 2024-11-22 05:16:05 +03:00

feat: Added possibility to run / reset form validation via $formulario

This commit is contained in:
Zaytsev Kirill 2021-05-30 17:40:33 +03:00
parent 6612d8a5f9
commit fd780cd585
5 changed files with 120 additions and 7 deletions

View File

@ -7,8 +7,11 @@ import {
ValidationRuleFn,
ValidationMessageFn,
ValidationMessageI18NFn,
ViolationsRecord,
} from '@/validation/validator'
import { FormularioFormInterface } from '@/types'
export interface FormularioOptions {
validationRules?: Record<string, ValidationRuleFn>;
validationMessages?: Record<string, ValidationMessageI18NFn|string>;
@ -21,7 +24,11 @@ export default class Formulario {
public validationRules: Record<string, ValidationRuleFn> = {}
public validationMessages: Record<string, ValidationMessageI18NFn|string> = {}
private readonly registry: Map<string, FormularioFormInterface>
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<ViolationsRecord> {
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<string, ValidationRuleFn> = {}): Record<string, ValidationRuleFn> {
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<string, ValidationMessageI18NFn|string>): Record<string, ValidationMessageFn> {
const raw = merge(this.validationMessages || {}, extendWith)

View File

@ -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<string, string[]>
@ -34,22 +37,23 @@ type ValidationEventPayload = {
violations: Violation[];
}
type ViolationsRecord = Record<string, Violation[]>
let counter = 0
@Component({ name: 'FormularioForm' })
export default class FormularioForm extends Vue {
@Model('input', { default: () => ({}) })
public readonly state!: Record<string, unknown>
@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<string, unknown> = {}
private proxy: Record<string, unknown> = {}
private registry: PathRegistry<FormularioFieldInterface> = 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<ViolationsRecord> {

View File

@ -1,4 +1,9 @@
import { Violation } from '@/validation/validator'
import { Violation, ViolationsRecord } from '@/validation/validator'
export interface FormularioFormInterface {
runValidation(): Promise<ViolationsRecord>;
resetValidation(): void;
}
export interface FormularioFieldInterface {
hasModel: boolean;

View File

@ -11,6 +11,8 @@ export interface Violation {
context: ValidationContext|null;
}
export type ViolationsRecord = Record<string, Violation[]>
export interface ValidationRuleFn {
(context: ValidationContext, ...args: any[]): Promise<boolean>|boolean;
}

View File

@ -192,6 +192,52 @@ describe('FormularioForm', () => {
})
})
test('resolves runValidation via $formulario', async () => {
const wrapper = mount(FormularioForm, {
propsData: {
id: 'address',
},
slots: {
default: `
<div>
<FormularioField name="address.street" validation="required" />
<FormularioField name="address.building" validation="required" />
</div>
`,
},
})
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: {