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

fix: Getting validators

BREAKING CHANGE: Removed form registry
This commit is contained in:
Zaytsev Kirill 2020-10-22 13:46:35 +03:00
parent 467dca656b
commit c85f3332eb
5 changed files with 160 additions and 335 deletions

View File

@ -10,7 +10,6 @@ import merge from '@/utils/merge'
import FileUpload from '@/FileUpload'
import FormularioForm from '@/FormularioForm.vue'
import FormularioFormInterface from '@/FormularioFormInterface'
import FormularioInput from '@/FormularioInput.vue'
import FormularioGrouping from '@/FormularioGrouping.vue'
@ -35,7 +34,6 @@ interface FormularioOptions {
*/
export default class Formulario {
public options: FormularioOptions
public registry: Map<string, FormularioFormInterface>
public idRegistry: { [name: string]: number }
constructor () {
@ -54,7 +52,6 @@ export default class Formulario {
validationMessages: messages,
idPrefix: 'formulario-'
}
this.registry = new Map()
this.idRegistry = {}
}
@ -116,68 +113,6 @@ export default class Formulario {
}
}
/**
* Given an instance of a FormularioForm register it.
*/
register (form: FormularioFormInterface): void {
if (typeof form.name === 'string') {
this.registry.set(form.name, form)
}
}
/**
* Given an instance of a form, remove it from the registry.
*/
deregister (form: FormularioFormInterface): void {
if (typeof form.name === 'string' && this.registry.has(form.name)) {
this.registry.delete(form.name)
}
}
/**
* Given an array, this function will attempt to make sense of the given error
* and hydrate a form with the resulting errors.
*/
handle ({ formErrors, inputErrors }: {
formErrors?: string[];
inputErrors?: Record<string, any>;
}, formName: string): void {
if (this.registry.has(formName)) {
const form = this.registry.get(formName) as FormularioFormInterface
form.loadErrors({
formErrors: formErrors || [],
inputErrors: inputErrors || {}
})
}
}
/**
* Reset a form.
*/
reset (formName: string, initialValue: Record<string, any> = {}): void {
this.resetValidation(formName)
this.setValues(formName, initialValue)
}
/**
* Reset the form's validation messages.
*/
resetValidation (formName: string): void {
if (this.registry.has(formName)) {
(this.registry.get(formName) as FormularioFormInterface).resetValidation()
}
}
/**
* Set the form values.
*/
setValues (formName: string, values?: Record<string, any>): void {
if (this.registry.has(formName) && values) {
(this.registry.get(formName) as FormularioFormInterface).setValues({ ...values })
}
}
/**
* Get the file uploader.
*/

View File

@ -27,10 +27,8 @@ import { ValidationErrorBag } from '@/validation/types'
import FileUpload from '@/FileUpload'
import FormularioFormInterface from '@/FormularioFormInterface'
@Component({ name: 'FormularioForm' })
export default class FormularioForm extends Vue implements FormularioFormInterface {
export default class FormularioForm extends Vue {
@Provide() formularioFieldValidation (errorBag: ValidationErrorBag): void {
this.$emit('validation', errorBag)
}
@ -133,14 +131,9 @@ export default class FormularioForm extends Vue implements FormularioFormInterfa
}
created (): void {
this.$formulario.register(this)
this.initProxy()
}
destroyed (): void {
this.$formulario.deregister(this)
}
onFormSubmit (): Promise<void> {
this.childrenShouldShowErrors = true
this.registry.forEach((input: FormularioInput) => {
@ -183,12 +176,6 @@ export default class FormularioForm extends Vue implements FormularioFormInterfa
this.registry.remove(field)
}
loadErrors ({ formErrors, inputErrors }: { formErrors: string[]; inputErrors: Record<string, string[]> }): void {
// given an object of errors, apply them to this form
this.localFormErrors = formErrors
this.localFieldErrors = inputErrors
}
resetValidation (): void {
this.localFormErrors = []
this.localFieldErrors = {}
@ -278,5 +265,11 @@ export default class FormularioForm extends Vue implements FormularioFormInterfa
this.$emit('input', { ...this.proxy })
}
}
setErrors ({ formErrors, inputErrors }: { formErrors?: string[]; inputErrors?: Record<string, string[]> }): void {
// given an object of errors, apply them to this form
this.localFormErrors = formErrors || []
this.localFieldErrors = inputErrors || {}
}
}
</script>

View File

@ -1,7 +0,0 @@
export default interface FormularioFormInterface {
name: string | boolean;
$options: Record<string, any>;
setValues(values: Record<string, any>): void;
loadErrors ({ formErrors, inputErrors }: { formErrors: string[]; inputErrors: Record<string, string[]> }): void;
resetValidation (): void;
}

View File

@ -305,12 +305,6 @@ export default class FormularioInput extends Vue {
}
}
get validators (): any {
return createValidatorGroups(
parseRules(this.validation, this.$formulario.rules(this.parsedValidationRules))
)
}
performValidation (): Promise<void> {
this.pendingValidation = this.validate().then(errors => {
this.didValidate(errors)
@ -348,7 +342,9 @@ export default class FormularioInput extends Vue {
resolve([])
}
}
resolveGroups(this.validators)
resolveGroups(createValidatorGroups(
parseRules(this.validation, this.$formulario.rules(this.parsedValidationRules))
))
})
}

View File

@ -38,161 +38,128 @@ describe('FormularioForm', () => {
expect(spy).toHaveBeenCalled()
})
it('Registers its subcomponents', () => {
it('Adds subcomponents to the registry', () => {
const wrapper = mount(FormularioForm, {
propsData: { formularioValue: { testinput: 'has initial value' } },
propsData: { formularioValue: {} },
slots: {
default: `
<FormularioInput type="text" name="subinput1" />
<FormularioInput type="checkbox" name="subinput2" />
<FormularioInput type="text" name="sub1" />
<FormularioInput type="checkbox" name="sub2" />
`
}
})
expect(wrapper.vm.registry.keys()).toEqual(['subinput1', 'subinput2'])
expect(wrapper.vm.registry.keys()).toEqual(['sub1', 'sub2'])
})
it('deregisters a subcomponents', async () => {
it('Removes subcomponents from the registry', async () => {
const wrapper = mount({
data () {
return {
active: true
}
},
data: () => ({ active: true }),
template: `
<FormularioForm>
<FormularioInput v-if="active" type="text" name="subinput1" />
<FormularioInput type="checkbox" name="subinput2" />
<FormularioInput v-if="active" type="text" name="sub1" />
<FormularioInput type="checkbox" name="sub2" />
</FormularioForm>
`
})
await flushPromises()
expect(wrapper.findComponent(FormularioForm).vm.registry.keys()).toEqual(['subinput1', 'subinput2'])
expect(wrapper.findComponent(FormularioForm).vm.registry.keys()).toEqual(['sub1', 'sub2'])
wrapper.setData({ active: false })
await flushPromises()
expect(wrapper.findComponent(FormularioForm).vm.registry.keys()).toEqual(['subinput2'])
expect(wrapper.findComponent(FormularioForm).vm.registry.keys()).toEqual(['sub2'])
})
it('can set a fields initial value', async () => {
it('Can set a fields initial value', async () => {
const wrapper = mount(FormularioForm, {
propsData: { formularioValue: { testinput: 'has initial value' } },
slots: { default: `
<FormularioInput v-slot="inputProps" validation="required|in:bar" name="testinput" >
<input v-model="inputProps.context.model" type="text">
</FormularioInput>
` }
propsData: { formularioValue: { test: 'Has initial value' } },
slots: {
default: `
<FormularioInput v-slot="{ context }" validation="required|in:bar" name="test" >
<input v-model="context.model" type="text">
</FormularioInput>
`
}
})
await flushPromises()
expect(wrapper.find('input').element.value).toBe('has initial value')
expect(wrapper.find('input').element['value']).toBe('Has initial value')
})
it('lets individual fields override form initial value', () => {
it('Lets individual fields override form initial value', () => {
const wrapper = mount(FormularioForm, {
propsData: { formularioValue: { testinput: 'has initial value' } },
slots: { default: `
<FormularioInput v-slot="inputProps" formulario-value="123" name="testinput" >
<input v-model="inputProps.context.model" type="text">
</FormularioInput>
` }
propsData: { formularioValue: { test: 'has initial value' } },
slots: {
default: `
<FormularioInput v-slot="{ context }" formulario-value="123" name="test" >
<input v-model="context.model" type="text">
</FormularioInput>
`
}
})
expect(wrapper.find('input').element.value).toBe('123')
expect(wrapper.find('input').element['value']).toBe('123')
})
it('lets fields set form initial value with value prop', () => {
it('Lets fields set form initial value with value prop', () => {
const wrapper = mount({
data () {
return {
formValues: {}
}
},
template: `<FormularioForm v-model="formValues">
<FormularioInput name="name" value="123" />
</FormularioForm>`
})
expect(wrapper.vm.formValues).toEqual({ name: '123' })
})
it('receives updates to form model when individual fields are edited', () => {
const wrapper = mount({
data () {
return {
formValues: {
testinput: '',
}
}
},
data: () => ({ values: {} }),
template: `
<FormularioForm v-model="formValues">
<FormularioInput v-slot="inputProps" name="testinput" >
<input v-model="inputProps.context.model" type="text">
<FormularioForm v-model="values">
<FormularioInput name="test" value="123" />
</FormularioForm>
`
})
expect(wrapper.vm['values']).toEqual({ test: '123' })
})
it('Receives updates to form model when individual fields are edited', () => {
const wrapper = mount({
data: () => ({ values: { test: '' } }),
template: `
<FormularioForm v-model="values">
<FormularioInput v-slot="{ context }" name="test" >
<input v-model="context.model" type="text">
</FormularioInput>
</FormularioForm>
`
})
wrapper.find('input').setValue('edited value')
expect(wrapper.vm.formValues).toEqual({ testinput: 'edited value' })
wrapper.find('input').setValue('Edited value')
expect(wrapper.vm['values']).toEqual({ test: 'Edited value' })
})
it('field data updates when it is type of date', async () => {
it('Field data updates when it is type of date', async () => {
const wrapper = mount({
data () {
return {
formValues: {
testdate: new Date(123),
}
}
},
data: () => ({ formValues: { date: new Date(123) } }),
template: `
<FormularioForm v-model="formValues" ref="form">
<FormularioInput v-slot="inputProps" name="testdate" >
<span v-if="inputProps.context.model">{{ inputProps.context.model.getTime() }}</span>
<FormularioInput v-slot="{ context }" name="date" >
<span v-if="context.model">{{ context.model.getTime() }}</span>
</FormularioInput>
</FormularioForm>
`
})
expect(wrapper.find('span').text()).toBe('123')
wrapper.setData({ formValues: { testdate: new Date(234) } })
wrapper.setData({ formValues: { date: new Date(234) } })
await flushPromises()
expect(wrapper.find('span').text()).toBe('234')
})
// ===========================================================================
/**
* @todo in vue-test-utils version 1.0.0-beta.29 has some bugs related to
* synchronous updating. Some details are here:
*
* @update this test was re-implemented in version 1.0.0-beta.31 and seems to
* be workign now with flushPromises(). Leaving these docs here for now.
*
* https://github.com/vuejs/vue-test-utils/issues/1130
*
* This test is being commented out until there is a resolution on this issue,
* and instead being replaced with a mock call.
*/
it('updates initial form values when input contains a populated v-model', async () => {
it('Updates initial form values when input contains a populated v-model', async () => {
const wrapper = mount({
data () {
return {
formValues: {
testinput: '',
},
fieldValue: '123'
}
},
data: () => ({
formValues: { test: '' },
fieldValue: '123',
}),
template: `
<FormularioForm v-model="formValues">
<FormularioInput type="text" name="testinput" v-model="fieldValue" />
<FormularioInput type="text" name="test" v-model="fieldValue" />
</FormularioForm>
`
})
await flushPromises()
expect(wrapper.vm.formValues).toEqual({ testinput: '123' })
expect(wrapper.vm['formValues']).toEqual({ test: '123' })
})
// ===========================================================================
// Replacement test for the above test - not quite as good of a test.
it('updates calls setFieldValue on form when a field contains a populated v-model on registration', () => {
const wrapper = mount(FormularioForm, {
@ -208,25 +175,19 @@ describe('FormularioForm', () => {
it('updates an inputs value when the form v-model is modified', async () => {
const wrapper = mount({
data () {
return {
formValues: {
testinput: 'abcd',
}
}
},
data: () => ({ formValues: { test: 'abcd' } }),
template: `
<FormularioForm v-model="formValues">
<FormularioInput v-slot="inputProps" name="testinput" >
<input v-model="inputProps.context.model" type="text">
<FormularioInput v-slot="{ context }" name="test" >
<input v-model="context.model" type="text">
</FormularioInput>
</FormularioForm>
`
})
await flushPromises()
wrapper.vm.formValues = { testinput: '1234' }
wrapper.vm.formValues = { test: '1234' }
await flushPromises()
expect(wrapper.find('input[type="text"]').element.value).toBe('1234')
expect(wrapper.find('input[type="text"]').element['value']).toBe('1234')
})
it('resolves hasValidationErrors to true', async () => {
@ -266,64 +227,12 @@ describe('FormularioForm', () => {
` }
})
await flushPromises()
expect(wrapper.find('input[type="text"]').element.value).toBe('Dave Barnett')
expect(wrapper.find('input[type="text"]').element['value']).toBe('Dave Barnett')
})
it('automatically registers with root plugin', async () => {
it('Receives a form-errors prop and displays it', async () => {
const wrapper = mount(FormularioForm, {
propsData: { formularioValue: { box3: [] }, name: 'login' }
})
expect(wrapper.vm.$formulario.registry.has('login')).toBe(true)
expect(wrapper.vm.$formulario.registry.get('login')).toBe(wrapper.vm)
})
it('Calls custom error handler with error and name', async () => {
const wrapper = mount({
template: `
<div>
<FormularioForm name="login" />
<FormularioForm name="register" />
</div>
`
})
wrapper.vm.$formulario.handle({ formErrors: ['This is an error message'] }, 'login')
})
it('Errors are displayed on correctly named components', async () => {
const wrapper = mount({
template: `
<div>
<FormularioForm
class="form form--login"
name="login"
v-slot="{ errors }"
>
<span v-for="error in errors" class="error">{{ error }}</span>
</FormularioForm>
<FormularioForm
class="form form--register"
name="register"
v-slot="{ errors }"
>
<span v-for="error in errors" class="error">{{ error }}</span>
</FormularioForm>
</div>
`
})
expect(
wrapper.vm.$formulario.registry.has('login') &&
wrapper.vm.$formulario.registry.has('register')
).toBe(true)
wrapper.vm.$formulario.handle({ formErrors: ['This is an error message'] }, 'login')
await flushPromises()
expect(wrapper.findAll('.form').length).toBe(2)
expect(wrapper.find('.form--login .error').exists()).toBe(true)
expect(wrapper.find('.form--register .error').exists()).toBe(false)
})
it('receives a form-errors prop and displays it', async () => {
const wrapper = mount(FormularioForm, {
propsData: { name: 'main', formErrors: ['first', 'second'] },
propsData: { formErrors: ['first', 'second'] },
scopedSlots: {
default: `
<div>
@ -334,18 +243,18 @@ describe('FormularioForm', () => {
}
})
await flushPromises()
expect(wrapper.vm.$formulario.registry.get('main').mergedFormErrors.length).toBe(2)
expect(wrapper.vm.mergedFormErrors.length).toBe(2)
})
it('it aggregates form-errors prop with form-named errors', async () => {
it('Aggregates form-errors prop with form-named errors', async () => {
const wrapper = mount(FormularioForm, {
propsData: { formErrors: ['first', 'second'], name: 'login' }
propsData: { formErrors: ['first', 'second'] }
})
wrapper.vm.$formulario.handle({ formErrors: ['third'] }, 'login')
wrapper.vm.setErrors({ formErrors: ['third'] })
await flushPromises()
let errors = wrapper.vm.$formulario.registry.get('login').mergedFormErrors
expect(Object.keys(errors).length).toBe(3)
expect(Object.keys(wrapper.vm.mergedFormErrors).length).toBe(3)
})
it('displays field errors on inputs with errors prop', async () => {
@ -365,76 +274,75 @@ describe('FormularioForm', () => {
expect(wrapper.find('span').text()).toEqual('This field has an error')
})
it('Is able to display multiple errors on multiple elements', async () => {
const wrapper = mount(FormularioForm, {
propsData: {
errors: { inputA: ['first'], inputB: ['first', 'second']},
},
slots: {
default: `
<FormularioInput name="inputA" />
<FormularioInput name="inputB" type="textarea" />
`
},
})
await wrapper.vm.$nextTick()
expect(Object.keys(wrapper.vm.mergedFieldErrors).length).toBe(2)
expect(wrapper.vm.mergedFieldErrors.inputA.length).toBe(1)
expect(wrapper.vm.mergedFieldErrors.inputB.length).toBe(2)
})
it('Can set multiple field errors with setErrors()', async () => {
const wrapper = mount(FormularioForm, {
slots: {
default: `
<FormularioInput name="inputA" />
<FormularioInput name="inputB" type="textarea" />
`
},
})
expect(Object.keys(wrapper.vm.mergedFieldErrors).length).toBe(0)
wrapper.vm.setErrors({
inputErrors: {
inputA: ['first'],
inputB: ['first', 'second'],
}
})
await wrapper.vm.$nextTick()
await flushPromises()
expect(Object.keys(wrapper.vm.mergedFieldErrors).length).toBe(2)
expect(wrapper.vm.mergedFieldErrors.inputA.length).toBe(1)
expect(wrapper.vm.mergedFieldErrors.inputB.length).toBe(2)
})
return
it('is able to display multiple errors on multiple elements', async () => {
const wrapper = mount({
template: `
<FormularioForm
name="register"
:errors="{inputA: ['first', 'second'], inputB: 'only one here', inputC: ['and one here']}"
>
<FormularioInput name="inputA" />
<FormularioInput name="inputB" type="textarea" />
<FormularioInput name="inputC" type="checkbox" />
</FormularioForm>
`
})
await wrapper.vm.$nextTick()
let errors = wrapper.vm.$formulario.registry.get('register').mergedFieldErrors
expect(Object.keys(errors).length).toBe(3)
expect(errors.inputA.length).toBe(2)
expect(errors.inputB.length).toBe(1)
expect(errors.inputC.length).toBe(1)
})
it('it can set multiple field errors with handle()', async () => {
const wrapper = mount({
template: `
<FormularioForm name="register">
<FormularioInput name="inputA" />
<FormularioInput name="inputB" type="textarea" />
<FormularioInput name="inputC" type="checkbox" />
</FormularioForm>
`
})
let errors = wrapper.vm.$formulario.registry.get('register').mergedFieldErrors
expect(Object.keys(errors).length).toBe(0)
wrapper.vm.$formulario.handle({ inputErrors: {inputA: ['first', 'second'], inputB: 'only one here', inputC: ['and one here']} }, "register")
await wrapper.vm.$nextTick()
await flushPromises()
errors = wrapper.vm.$formulario.registry.get('register').mergedFieldErrors
expect(Object.keys(errors).length).toBe(3)
expect(errors.inputA.length).toBe(2)
expect(errors.inputB.length).toBe(1)
expect(errors.inputC.length).toBe(1)
})
it('emits correct validation event on entry', async () => {
it('Emits correct validation event on entry', async () => {
const wrapper = mount(FormularioForm, {
slots: { default: `
<div>
<FormularioInput v-slot="inputProps" validation="required|in:bar" name="testinput" >
<input v-model="inputProps.context.model" type="text">
</FormularioInput>
<FormularioInput type="radio" validation="required" name="bar" />
</div>
<FormularioInput v-slot="{ context }" validation="required|in:foo" name="foo" >
<input v-model="context.model" type="text">
</FormularioInput>
<FormularioInput type="radio" validation="required" name="bar" />
` }
})
wrapper.find('input[type="text"]').setValue('foo')
wrapper.find('input[type="text"]').setValue('bar')
await flushPromises()
const errorObjects = wrapper.emitted('validation')
// There should be 3 events, both inputs mounting, and the value being set removing required on testinput
expect(errorObjects.length).toBe(3)
// this should be the event from the setValue()
const errorObject = errorObjects[2][0]
expect(errorObject).toEqual({
name: 'testinput',
name: 'foo',
errors: [
expect.any(String)
],
@ -490,41 +398,41 @@ describe('FormularioForm', () => {
expect(wrapper.vm.formData).toEqual({ foo: 'bar' })
})
it('it allows resetting a form, hiding validation and clearing inputs.', async () => {
it('Allows resetting a form, hiding validation and clearing inputs.', async () => {
const wrapper = mount({
data: () => ({ values: {} }),
template: `
<FormularioForm
v-model="formData"
v-model="values"
name="login"
ref="form"
>
<FormularioInput v-slot="inputProps" name="username" validation="required">
<input v-model="inputProps.context.model" type="text">
<FormularioInput v-slot="{ context }" name="username" validation="required">
<input v-model="context.model" type="text">
</FormularioInput>
<FormularioInput v-slot="inputProps" name="password" validation="required|min:4,length">
<input v-model="inputProps.context.model" type="password">
<FormularioInput v-slot="{ context }" name="password" validation="required|min:4,length">
<input v-model="context.model" type="password">
</FormularioInput>
</FormularioForm>
`,
data () {
return {
formData: {}
}
}
})
const password = wrapper.find('input[type="password"]')
password.setValue('foo')
password.trigger('blur')
wrapper.find('form').trigger('submit')
wrapper.vm.$formulario.handle({
inputErrors: { username: ['Failed'] }
}, 'login')
wrapper.vm.$refs.form.setErrors({ inputErrors: { username: ['Failed'] } })
await flushPromises()
// First make sure we caugth the errors
// First make sure we caught the errors
expect(Object.keys(wrapper.vm.$refs.form.mergedFieldErrors).length).toBe(1)
wrapper.vm.$formulario.reset('login')
wrapper.vm.$refs.form.resetValidation()
wrapper.vm.$refs.form.setValues({ })
await flushPromises()
expect(Object.keys(wrapper.vm.$refs.form.mergedFieldErrors).length).toBe(0)
expect(wrapper.vm.formData).toEqual({})
expect(wrapper.vm.values).toEqual({})
})
})