1
0
mirror of synced 2024-11-25 14:56:03 +03:00

refactor!: FormularioForm - renamed prop errors to fieldsErrors, some internal renamings; tests semantic improvements

This commit is contained in:
Zaytsev Kirill 2021-05-24 23:12:40 +03:00
parent e55ea0c410
commit 4e05844e73
4 changed files with 321 additions and 341 deletions

View File

@ -94,16 +94,16 @@ export default class FormularioField extends Vue {
/**
* Determines if this formulario element is v-modeled or not.
*/
get hasModel (): boolean {
public get hasModel (): boolean {
return has(this.$options.propsData || {}, 'value')
}
private get model (): unknown {
public get model (): unknown {
const model = this.hasModel ? 'value' : 'proxy'
return this.modelGetConverter(this[model])
}
private set model (value: unknown) {
public set model (value: unknown) {
value = this.modelSetConverter(value, this.proxy)
if (!shallowEquals(value, this.proxy)) {
@ -113,7 +113,7 @@ export default class FormularioField extends Vue {
this.$emit('input', value)
if (typeof this.__FormularioForm_set === 'function') {
this.__FormularioForm_set(this.context.name, value)
this.__FormularioForm_set(this.fullPath, value)
}
}
@ -241,7 +241,7 @@ export default class FormularioField extends Vue {
*/
setErrors (errors: string[]): void {
if (!this.errorsDisabled) {
this.localErrors = arrayify(errors)
this.localErrors = arrayify(errors) as string[]
}
}

View File

@ -1,6 +1,6 @@
<template>
<form @submit.prevent="onFormSubmit">
<slot :errors="mergedFormErrors" />
<form @submit.prevent="onSubmit">
<slot :errors="formErrorsComputed" />
</form>
</template>
@ -38,9 +38,9 @@ export default class FormularioForm extends Vue {
@Model('input', { default: () => ({}) })
public readonly state!: Record<string, unknown>
// Errors record, describing state validation errors of whole form
@Prop({ default: () => ({}) }) readonly errors!: Record<string, string[]>
// Form errors only used on FormularioForm default slot
// Describes validation errors of whole form
@Prop({ default: () => ({}) }) readonly fieldsErrors!: Record<string, string[]>
// Only used on FormularioForm default slot
@Prop({ default: () => ([]) }) readonly formErrors!: string[]
public proxy: Record<string, unknown> = {}
@ -48,58 +48,57 @@ export default class FormularioForm extends Vue {
private registry: PathRegistry<FormularioField> = new PathRegistry()
// Local error messages are temporal, they wiped each resetValidation call
private localFieldsErrors: Record<string, string[]> = {}
private localFormErrors: string[] = []
private localFieldErrors: Record<string, string[]> = {}
get initialValues (): Record<string, unknown> {
private get hasModel (): boolean {
return has(this.$options.propsData || {}, 'state')
}
private get modelIsDefined (): boolean {
return this.state && typeof this.state === 'object'
}
private get modelCopy (): Record<string, unknown> {
if (this.hasModel && typeof this.state === 'object') {
// If there is a v-model on the form/group, use those values as first priority
return { ...this.state } // @todo - use a deep clone to detach reference types
}
return {}
}
get mergedFormErrors (): string[] {
private get fieldsErrorsComputed (): Record<string, string[]> {
return merge(this.fieldsErrors || {}, this.localFieldsErrors)
}
private get formErrorsComputed (): string[] {
return [...this.formErrors, ...this.localFormErrors]
}
get mergedFieldErrors (): Record<string, string[]> {
return merge(this.errors || {}, this.localFieldErrors)
}
get hasModel (): boolean {
return has(this.$options.propsData || {}, 'state')
}
get hasInitialValue (): boolean {
return this.state && typeof this.state === 'object'
}
@Watch('state', { deep: true })
onStateChange (values: Record<string, unknown>): void {
private onStateChange (values: Record<string, unknown>): void {
if (this.hasModel && values && typeof values === 'object') {
this.setValues(values)
}
}
@Watch('mergedFieldErrors', { deep: true, immediate: true })
onMergedFieldErrorsChange (errors: Record<string, string[]>): void {
@Watch('fieldsErrorsComputed', { deep: true, immediate: true })
private onFieldsErrorsChange (fieldsErrors: Record<string, string[]>): void {
this.registry.forEach((field, path) => {
field.setErrors(errors[path] || [])
field.setErrors(fieldsErrors[path] || [])
})
}
@Provide('__FormularioForm_getValue')
private getValue (): Record<string, unknown> {
return this.proxy
}
created (): void {
this.syncProxy()
}
@Provide('__FormularioForm_getValue')
getFormValues (): Record<string, unknown> {
return this.proxy
}
onFormSubmit (): Promise<void> {
private onSubmit (): Promise<void> {
return this.hasValidationErrors()
.then(hasErrors => hasErrors ? undefined : clone(this.proxy))
.then(data => {
@ -111,30 +110,24 @@ export default class FormularioForm extends Vue {
})
}
@Provide('__FormularioForm_emitValidation')
private emitValidation (payload: ValidationEventPayload): void {
this.$emit('validation', payload)
}
@Provide('__FormularioForm_register')
private register (path: string, field: FormularioField): void {
this.registry.add(path, field)
const value = getNested(this.initialValues, path)
const value = getNested(this.modelCopy, path)
if (!field.hasModel && this.hasInitialValue && value !== undefined) {
if (!field.hasModel && this.modelIsDefined && value !== undefined) {
// In the case that the form is carrying an initial value and the
// element is not, set it directly.
// @ts-ignore
field.context.model = value
field.model = value
} else if (field.hasModel && !shallowEquals(field.proxy, value)) {
// In this case, the field is v-modeled or has an initial value and the
// form has no value or a different value, so use the field value
this.setFieldValueAndEmit(path, field.proxy)
}
if (has(this.mergedFieldErrors, path)) {
field.setErrors(this.mergedFieldErrors[path] || [])
if (has(this.fieldsErrorsComputed, path)) {
field.setErrors(this.fieldsErrorsComputed[path] || [])
}
}
@ -148,9 +141,14 @@ export default class FormularioForm extends Vue {
}
}
syncProxy (): void {
if (this.hasInitialValue) {
this.proxy = this.initialValues
@Provide('__FormularioForm_emitValidation')
private emitValidation (payload: ValidationEventPayload): void {
this.$emit('validation', payload)
}
private syncProxy (): void {
if (this.modelIsDefined) {
this.proxy = this.modelCopy
}
}
@ -177,7 +175,7 @@ export default class FormularioForm extends Vue {
}
if (!shallowEquals(newValue, field.proxy)) {
field.context.model = newValue
field.model = newValue
}
})
})
@ -205,9 +203,9 @@ export default class FormularioForm extends Vue {
this.$emit('input', { ...this.proxy })
}
setErrors ({ formErrors, inputErrors }: { formErrors?: string[]; inputErrors?: Record<string, string[]> }): void {
setErrors ({ fieldsErrors, formErrors }: { fieldsErrors?: Record<string, string[]>; formErrors?: string[] }): void {
this.localFieldsErrors = fieldsErrors || {}
this.localFormErrors = formErrors || []
this.localFieldErrors = inputErrors || {}
}
hasValidationErrors (): Promise<boolean> {
@ -218,8 +216,8 @@ export default class FormularioForm extends Vue {
}
resetValidation (): void {
this.localFieldsErrors = {}
this.localFormErrors = []
this.localFieldErrors = {}
this.registry.forEach((field: FormularioField) => {
field.resetValidation()
})

View File

@ -10,7 +10,7 @@ import FormularioForm from '@/FormularioForm.vue'
Vue.use(Formulario)
describe('FormularioFieldGroup', () => {
it('Grouped fields to be set', async () => {
test('grouped fields to be set', async () => {
const wrapper = mount(FormularioForm, {
slots: {
default: `
@ -36,7 +36,7 @@ describe('FormularioFieldGroup', () => {
expect(emitted['submit']).toEqual([[{ group: { text: 'test' } }]])
})
it('Grouped fields to be got', async () => {
test('grouped fields to be got', async () => {
const wrapper = mount(FormularioForm, {
propsData: {
state: {
@ -57,11 +57,11 @@ describe('FormularioFieldGroup', () => {
expect(wrapper.find('input[type="text"]').element['value']).toBe('Group text')
})
it('Data reactive with grouped fields', async () => {
test('data reactive with grouped fields', async () => {
const wrapper = mount({
data: () => ({ values: {} }),
template: `
<FormularioForm name="form" v-model="values">
<FormularioForm v-model="values">
<FormularioFieldGroup name="group">
<FormularioField name="text" v-slot="{ context }">
<input type="text" v-model="context.model">
@ -71,22 +71,23 @@ describe('FormularioFieldGroup', () => {
</FormularioForm>
`
})
expect(wrapper.find('span').text()).toBe('')
wrapper.find('input[type="text"]').setValue('test')
await flushPromises()
expect(wrapper.find('span').text()).toBe('test')
})
it('Errors are set for grouped fields', async () => {
test('errors are set for grouped fields', async () => {
const wrapper = mount(FormularioForm, {
propsData: {
state: {},
errors: { 'group.text': 'Test error' },
},
propsData: { fieldsErrors: { 'address.street': 'Test error' } },
slots: {
default: `
<FormularioFieldGroup name="group">
<FormularioField ref="input" name="text" v-slot="{ context }">
<FormularioFieldGroup name="address">
<FormularioField ref="input" name="street" v-slot="{ context }">
<span v-for="error in context.errors">{{ error }}</span>
</FormularioField>
</FormularioFieldGroup>

View File

@ -1,6 +1,8 @@
import Vue from 'vue'
import { mount } from '@vue/test-utils'
import flushPromises from 'flush-promises'
import Formulario from '@/index.ts'
import FormularioForm from '@/FormularioForm.vue'
@ -13,32 +15,22 @@ Vue.use(Formulario, {
})
describe('FormularioForm', () => {
it('render a form DOM element', () => {
test('renders a form DOM element', () => {
const wrapper = mount(FormularioForm)
expect(wrapper.find('form').exists()).toBe(true)
})
it('accepts a default slot', () => {
test('accepts a default slot', () => {
const wrapper = mount(FormularioForm, {
slots: {
default: '<div class="default-slot-item" />'
}
default: '<div data-default />'
},
})
expect(wrapper.find('form div.default-slot-item').exists()).toBe(true)
expect(wrapper.find('form [data-default]').exists()).toBe(true)
})
it('Intercepts submit event', () => {
const wrapper = mount(FormularioForm, {
slots: {
default: '<button type="submit" />'
}
})
const spy = jest.spyOn(wrapper.vm, 'onFormSubmit')
wrapper.find('form').trigger('submit')
expect(spy).toHaveBeenCalled()
})
it('Can set a fields initial value', async () => {
test('can set a fields initial value', async () => {
const wrapper = mount(FormularioForm, {
propsData: { state: { test: 'Has initial value' } },
slots: {
@ -49,131 +41,123 @@ describe('FormularioForm', () => {
`
}
})
await flushPromises()
expect(wrapper.find('input').element['value']).toBe('Has initial value')
})
it('Lets individual fields override form initial value', () => {
describe('emits input event', () => {
test('when individual fields contain a populated value', async () => {
const wrapper = mount(FormularioForm, {
propsData: { state: { field: 'initial' } },
slots: {
default: '<FormularioField name="field" value="populated" />'
},
})
await Vue.nextTick()
const emitted = wrapper.emitted('input')
expect(emitted).toBeTruthy()
expect(emitted[emitted.length - 1]).toEqual([{ field: 'populated' }])
})
test('when individual fields are edited', () => {
const wrapper = mount(FormularioForm, {
propsData: { state: { field: 'initial' } },
slots: {
default: `
<FormularioField v-slot="{ context }" name="field" >
<input v-model="context.model" type="text">
</FormularioField>
`,
},
})
wrapper.find('input').setValue('updated')
const emitted = wrapper.emitted('input')
expect(emitted).toBeTruthy()
expect(emitted[emitted.length - 1]).toEqual([{ field: 'updated' }])
})
})
test('updates a field when the form v-model is modified', async () => {
const wrapper = mount(FormularioForm, {
propsData: { state: { test: 'has initial value' } },
propsData: { state: { field: 'initial' } },
slots: {
default: `
<FormularioField v-slot="{ context }" name="test" value="123">
<FormularioField v-slot="{ context }" name="field">
<input v-model="context.model" type="text">
</FormularioField>
`
}
})
expect(wrapper.find('input').element['value']).toBe('123')
})
it('Lets fields set form initial value with value prop', () => {
const wrapper = mount({
data: () => ({ values: {} }),
template: `
<FormularioForm v-model="values">
<FormularioField 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">
<FormularioField v-slot="{ context }" name="test" >
<input v-model="context.model" type="text">
</FormularioField>
</FormularioForm>
`
})
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 () => {
const wrapper = mount({
data: () => ({ formValues: { date: new Date(123) } }),
template: `
<FormularioForm v-model="formValues" ref="form">
<FormularioField v-slot="{ context }" name="date" >
<span v-if="context.model">{{ context.model.getTime() }}</span>
</FormularioField>
</FormularioForm>
`
})
expect(wrapper.find('span').text()).toBe('123')
wrapper.setData({ formValues: { date: new Date(234) } })
await flushPromises()
expect(wrapper.find('span').text()).toBe('234')
})
it('Updates initial form values when input contains a populated v-model', async () => {
const wrapper = mount({
data: () => ({
formValues: { test: '' },
fieldValue: '123',
}),
template: `
<FormularioForm v-model="formValues">
<FormularioField name="test" v-model="fieldValue" />
</FormularioForm>
`
})
await flushPromises()
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, {
propsData: {
state: { test: 'Initial' },
},
slots: {
default: '<FormularioField name="test" value="Overrides" />'
},
})
const emitted = wrapper.emitted('input')
expect(emitted).toBeTruthy()
expect(emitted[emitted.length - 1]).toEqual([{ test: 'Overrides' }])
})
it('updates an inputs value when the form v-model is modified', async () => {
const wrapper = mount({
data: () => ({ values: { test: 'abcd' } }),
template: `
<FormularioForm v-model="values">
<FormularioField v-slot="{ context }" name="test" >
<input v-model="context.model" type="text">
</FormularioField>
</FormularioForm>
`
})
wrapper.vm.values = { test: '1234' }
await flushPromises()
const input = wrapper.find('input[type="text"]')
const input = wrapper.find('input')
expect(input).toBeTruthy()
expect(input.element['value']).toBe('1234')
expect(input.element['value']).toBe('initial')
wrapper.setProps({ state: { field: 'updated' } })
await Vue.nextTick()
expect(input.element['value']).toBe('updated')
})
it('Resolves hasValidationErrors to true', async () => {
const wrapper = mount(FormularioForm, {
slots: { default: '<FormularioField name="fieldName" validation="required" />' }
test('updates a field when it is an instance of Date', async () => {
const dateA = new Date('1970-01-01')
const dateB = new Date()
const wrapper = mount(FormularioForm,{
propsData: { state: { date: dateA } },
scopedSlots: {
default: `
<FormularioField v-slot="{ context }" name="date">
<span v-if="context.model">{{ context.model.toISOString() }}</span>
</FormularioField>
`,
},
})
expect(wrapper.find('span').text()).toBe(dateA.toISOString())
wrapper.setProps({ state: { date: dateB } })
await Vue.nextTick()
expect(wrapper.find('span').text()).toBe(dateB.toISOString())
})
test('resolves submitted form values to an object', async () => {
const wrapper = mount(FormularioForm, {
slots: {
default: '<FormularioField name="fieldName" validation="required" value="Justin" />'
},
})
wrapper.find('form').trigger('submit')
await flushPromises()
expect(wrapper.emitted('submit')).toEqual([
[{ fieldName: 'Justin' }],
])
})
test('resolves hasValidationErrors to true', async () => {
const wrapper = mount(FormularioForm, {
slots: {
default: '<FormularioField name="fieldName" validation="required" />',
},
})
wrapper.find('form').trigger('submit')
await flushPromises()
const emitted = wrapper.emitted()
@ -182,89 +166,102 @@ describe('FormularioForm', () => {
expect(emitted['error'].length).toBe(1)
})
it('Resolves submitted form values to an object', async () => {
const wrapper = mount(FormularioForm, {
slots: { default: '<FormularioField name="fieldName" validation="required" value="Justin" />' }
describe('allows setting fields errors', () => {
/**
* @param props
* @return {Wrapper<FormularioForm>}
*/
const createWrapper = (props = {}) => mount(FormularioForm, {
propsData: props,
scopedSlots: {
default: '<div><div v-for="error in props.errors" data-error /></div>',
},
})
wrapper.find('form').trigger('submit')
await flushPromises()
const emitted = wrapper.emitted()
test('via prop', async () => {
const wrapper = createWrapper({ formErrors: ['first', 'second'] })
expect(emitted['submit']).toBeTruthy()
expect(emitted['submit'].length).toBe(1)
expect(emitted['submit'][0]).toEqual([{ fieldName: 'Justin' }])
expect(wrapper.findAll('[data-error]').length).toBe(2)
})
test('manually with setErrors()', async () => {
const wrapper = createWrapper({ formErrors: ['first', 'second'] })
wrapper.vm.setErrors({ formErrors: ['third'] })
await wrapper.vm.$nextTick()
expect(wrapper.findAll('[data-error]').length).toBe(3)
})
})
it('Receives a form-errors prop and displays it', async () => {
test('displays field errors on inputs with errors prop', async () => {
const wrapper = mount(FormularioForm, {
propsData: { formErrors: ['first', 'second'] },
})
await flushPromises()
expect(wrapper.vm.mergedFormErrors.length).toBe(2)
})
it('Aggregates form-errors prop with form-named errors', async () => {
const wrapper = mount(FormularioForm, {
propsData: { formErrors: ['first', 'second'] }
})
wrapper.vm.setErrors({ formErrors: ['third'] })
await flushPromises()
expect(Object.keys(wrapper.vm.mergedFormErrors).length).toBe(3)
})
it('displays field errors on inputs with errors prop', async () => {
const wrapper = mount(FormularioForm, {
propsData: { errors: { fieldWithErrors: ['This field has an error'] }},
propsData: { fieldsErrors: { field: ['This field has an error'] }},
slots: {
default: `
<FormularioField v-slot="{ context }" name="fieldWithErrors">
<FormularioField v-slot="{ context }" name="field">
<span v-for="error in context.errors">{{ error }}</span>
</FormularioField>
`
}
`,
},
})
await wrapper.vm.$nextTick()
expect(wrapper.find('span').exists()).toBe(true)
expect(wrapper.find('span').text()).toEqual('This field has an error')
})
it('Is able to display multiple errors on multiple elements', async () => {
const errors = { inputA: ['first'], inputB: ['first', 'second']}
const wrapper = mount(FormularioForm, { propsData: { errors } })
describe('allows setting fields errors', () => {
/**
* @param props
* @return {Wrapper<FormularioForm>}
*/
const createWrapper = (props = {}) => mount(FormularioForm, {
propsData: props,
slots: {
default: `
<div>
<FormularioField v-slot="{ context }" name="fieldA">
<div v-for="error in context.errors" data-error-a>{{ error }}</div>
</FormularioField>
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)
expect(Object.keys(wrapper.vm.mergedFieldErrors).length).toBe(0)
wrapper.vm.setErrors({
inputErrors: {
inputA: ['first'],
inputB: ['first', 'second'],
<FormularioField v-slot="{ context }" name="fieldB">
<div v-for="error in context.errors" data-error-b>{{ error }}</div>
</FormularioField>
</div>
`,
}
})
await wrapper.vm.$nextTick()
await flushPromises()
test('via prop', async () => {
const wrapper = createWrapper({
fieldsErrors: { fieldA: ['first'], fieldB: ['first', 'second']},
})
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)
expect(wrapper.findAll('[data-error-a]').length).toBe(1)
expect(wrapper.findAll('[data-error-b]').length).toBe(2)
})
test('manually with setErrors()', async () => {
const wrapper = createWrapper()
expect(wrapper.findAll('[data-error-a]').length).toBe(0)
expect(wrapper.findAll('[data-error-b]').length).toBe(0)
wrapper.vm.setErrors({ fieldsErrors: { fieldA: ['first'], fieldB: ['first', 'second'] } })
await Vue.nextTick()
expect(wrapper.findAll('[data-error-a]').length).toBe(1)
expect(wrapper.findAll('[data-error-b]').length).toBe(2)
})
})
it('emits correct validation event when no errors', async () => {
const wrapper = mount(FormularioForm, {
describe('emits correct validation event', () => {
/**
* @return {Wrapper<FormularioForm>}
*/
const createWrapper = () => mount(FormularioForm, {
slots: {
default: `
<FormularioField v-slot="{ context }" name="foo" validation="required|in:foo">
@ -272,125 +269,109 @@ describe('FormularioForm', () => {
</FormularioField>
<FormularioField name="bar" validation="required" />
`,
}
},
})
wrapper.find('input[type="text"]').setValue('foo')
wrapper.find('input[type="text"]').trigger('blur')
await flushPromises()
test('when no errors', async () => {
const wrapper = createWrapper()
expect(wrapper.emitted('validation')).toBeTruthy()
expect(wrapper.emitted('validation')).toEqual([[{
name: 'foo',
violations: [],
}]])
wrapper.find('input[type="text"]').setValue('foo')
wrapper.find('input[type="text"]').trigger('blur')
await flushPromises()
expect(wrapper.emitted('validation')).toBeTruthy()
expect(wrapper.emitted('validation')).toEqual([[{
name: 'foo',
violations: [],
}]])
})
test('on entry', async () => {
const wrapper = createWrapper()
wrapper.find('input[type="text"]').setValue('bar')
wrapper.find('input[type="text"]').trigger('blur')
await flushPromises()
expect(wrapper.emitted('validation')).toBeTruthy()
expect(wrapper.emitted('validation')).toEqual([[ {
name: 'foo',
violations: [ {
rule: expect.any(String),
args: ['foo'],
context: {
value: 'bar',
formValues: expect.any(Object),
name: 'foo',
},
message: expect.any(String),
} ],
} ]])
})
})
it('Emits correct validation event on entry', async () => {
test('allows resetting form validation', async () => {
const wrapper = mount(FormularioForm, {
slots: { default: `
<FormularioField
v-slot="{ context }"
name="firstField"
validation="required|in:foo"
>
<input
v-model="context.model"
type="text"
@blur="context.runValidation()"
>
</FormularioField>
<FormularioField
name="secondField"
validation="required"
/>
` }
})
wrapper.find('input[type="text"]').setValue('bar')
wrapper.find('input[type="text"]').trigger('blur')
slots: {
default: `
<div>
<FormularioField v-slot="{ context }" name="username" validation="required">
<input v-model="context.model" type="text">
<div v-for="error in context.allErrors" data-username-error />
</FormularioField>
await flushPromises()
expect(wrapper.emitted('validation')).toBeTruthy()
expect(wrapper.emitted('validation').length).toBe(1)
expect(wrapper.emitted('validation')[0][0]).toEqual({
name: 'firstField',
violations: [ {
rule: expect.any(String),
args: ['foo'],
context: {
value: 'bar',
formValues: expect.any(Object),
name: 'firstField',
},
message: expect.any(String),
} ],
})
})
it('Allows resetting a form, wiping validation.', async () => {
const wrapper = mount({
data: () => ({ values: {} }),
template: `
<FormularioForm
v-model="values"
name="login"
ref="form"
>
<FormularioField v-slot="{ context }" name="username" validation="required">
<input v-model="context.model" type="text">
</FormularioField>
<FormularioField v-slot="{ context }" name="password" validation="required|min:4,length">
<input v-model="context.model" type="password">
</FormularioField>
</FormularioForm>
`,
<FormularioField v-slot="{ context }" name="password" validation="required|min:4,length">
<input v-model="context.model" type="password" @blur="context.runValidation()">
<div v-for="error in context.allErrors" data-password-error />
</FormularioField>
</div>
`,
},
})
const password = wrapper.find('input[type="password"]')
password.setValue('foo')
password.trigger('input')
password.trigger('blur')
wrapper.find('form').trigger('submit')
wrapper.vm.$refs.form.setErrors({ inputErrors: { username: ['Failed'] } })
await flushPromises()
// First make sure we caught the errors
expect(Object.keys(wrapper.vm.$refs.form.mergedFieldErrors).length).toBe(1)
wrapper.vm.$refs.form.resetValidation()
wrapper.vm.setErrors({ fieldsErrors: { username: ['required'] } })
await flushPromises()
await Vue.nextTick()
expect(Object.keys(wrapper.vm.$refs.form.mergedFieldErrors).length).toBe(0)
expect(wrapper.findAll('[data-username-error]').length).toBe(1)
expect(wrapper.findAll('[data-password-error]').length).toBe(1)
wrapper.vm.resetValidation()
await Vue.nextTick()
expect(wrapper.findAll('[data-username-error]').length).toBe(0)
expect(wrapper.findAll('[data-password-error]').length).toBe(0)
})
it('Local errors resetted when errors prop cleared', async () => {
const wrapper = mount({
data: () => ({ values: {}, errors: { input: ['failure'] } }),
template: `
<FormularioForm
v-model="values"
:errors="errors"
ref="form"
>
<FormularioField
v-slot="{ context }"
name="input"
ref="form"
>
test('local errors are reset when errors prop cleared', async () => {
const wrapper = mount(FormularioForm, {
propsData: { fieldsErrors: { input: ['failure'] } },
slots: {
default: `
<FormularioField v-slot="{ context }" name="input">
<span v-for="error in context.allErrors">{{ error.message }}</span>
</FormularioField>
</FormularioForm>
`
`,
},
})
await flushPromises()
expect(wrapper.find('span').exists()).toBe(true)
wrapper.vm.errors = {}
await flushPromises()
wrapper.setProps({ fieldsErrors: {} })
await Vue.nextTick()
expect(wrapper.find('span').exists()).toBe(false)
})
})