refactor!: FormularioForm - renamed prop errors to fieldsErrors, some internal renamings; tests semantic improvements
This commit is contained in:
parent
e55ea0c410
commit
4e05844e73
@ -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[]
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
})
|
||||
|
@ -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>
|
||||
|
@ -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" />'
|
||||
}
|
||||
})
|
||||
expect(wrapper.find('form div.default-slot-item').exists()).toBe(true)
|
||||
default: '<div data-default />'
|
||||
},
|
||||
})
|
||||
|
||||
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()
|
||||
expect(wrapper.find('form [data-default]').exists()).toBe(true)
|
||||
})
|
||||
|
||||
it('Can set a field’s initial value', async () => {
|
||||
test('can set a field’s 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: { test: 'has initial value' } },
|
||||
propsData: { state: { field: 'initial' } },
|
||||
slots: {
|
||||
default: `
|
||||
<FormularioField v-slot="{ context }" name="test" value="123">
|
||||
<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" />'
|
||||
default: '<FormularioField name="field" value="populated" />'
|
||||
},
|
||||
})
|
||||
|
||||
await Vue.nextTick()
|
||||
|
||||
const emitted = wrapper.emitted('input')
|
||||
|
||||
expect(emitted).toBeTruthy()
|
||||
expect(emitted[emitted.length - 1]).toEqual([{ test: 'Overrides' }])
|
||||
expect(emitted[emitted.length - 1]).toEqual([{ field: 'populated' }])
|
||||
})
|
||||
|
||||
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" >
|
||||
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>
|
||||
</FormularioForm>
|
||||
`
|
||||
`,
|
||||
},
|
||||
})
|
||||
|
||||
wrapper.vm.values = { test: '1234' }
|
||||
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: { field: 'initial' } },
|
||||
slots: {
|
||||
default: `
|
||||
<FormularioField v-slot="{ context }" name="field">
|
||||
<input v-model="context.model" type="text">
|
||||
</FormularioField>
|
||||
`
|
||||
},
|
||||
})
|
||||
|
||||
const input = wrapper.find('input')
|
||||
|
||||
expect(input).toBeTruthy()
|
||||
expect(input.element['value']).toBe('initial')
|
||||
|
||||
wrapper.setProps({ state: { field: 'updated' } })
|
||||
|
||||
await Vue.nextTick()
|
||||
|
||||
expect(input.element['value']).toBe('updated')
|
||||
})
|
||||
|
||||
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()
|
||||
|
||||
const input = wrapper.find('input[type="text"]')
|
||||
|
||||
expect(input).toBeTruthy()
|
||||
expect(input.element['value']).toBe('1234')
|
||||
expect(wrapper.emitted('submit')).toEqual([
|
||||
[{ fieldName: 'Justin' }],
|
||||
])
|
||||
})
|
||||
|
||||
it('Resolves hasValidationErrors to true', async () => {
|
||||
test('resolves hasValidationErrors to true', async () => {
|
||||
const wrapper = mount(FormularioForm, {
|
||||
slots: { default: '<FormularioField name="fieldName" validation="required" />' }
|
||||
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" />' }
|
||||
})
|
||||
wrapper.find('form').trigger('submit')
|
||||
await flushPromises()
|
||||
|
||||
const emitted = wrapper.emitted()
|
||||
|
||||
expect(emitted['submit']).toBeTruthy()
|
||||
expect(emitted['submit'].length).toBe(1)
|
||||
expect(emitted['submit'][0]).toEqual([{ fieldName: '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>',
|
||||
},
|
||||
})
|
||||
|
||||
it('Receives a form-errors prop and displays it', async () => {
|
||||
const wrapper = mount(FormularioForm, {
|
||||
propsData: { formErrors: ['first', 'second'] },
|
||||
})
|
||||
await flushPromises()
|
||||
expect(wrapper.vm.mergedFormErrors.length).toBe(2)
|
||||
test('via prop', async () => {
|
||||
const wrapper = createWrapper({ formErrors: ['first', 'second'] })
|
||||
|
||||
expect(wrapper.findAll('[data-error]').length).toBe(2)
|
||||
})
|
||||
|
||||
it('Aggregates form-errors prop with form-named errors', async () => {
|
||||
const wrapper = mount(FormularioForm, {
|
||||
propsData: { formErrors: ['first', 'second'] }
|
||||
})
|
||||
test('manually with setErrors()', async () => {
|
||||
const wrapper = createWrapper({ formErrors: ['first', 'second'] })
|
||||
|
||||
wrapper.vm.setErrors({ formErrors: ['third'] })
|
||||
|
||||
await flushPromises()
|
||||
await wrapper.vm.$nextTick()
|
||||
|
||||
expect(Object.keys(wrapper.vm.mergedFormErrors).length).toBe(3)
|
||||
expect(wrapper.findAll('[data-error]').length).toBe(3)
|
||||
})
|
||||
})
|
||||
|
||||
it('displays field errors on inputs with errors prop', async () => {
|
||||
test('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()
|
||||
|
||||
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)
|
||||
test('via prop', async () => {
|
||||
const wrapper = createWrapper({
|
||||
fieldsErrors: { fieldA: ['first'], fieldB: ['first', 'second']},
|
||||
})
|
||||
|
||||
it('emits correct validation event when no errors', async () => {
|
||||
const wrapper = mount(FormularioForm, {
|
||||
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)
|
||||
})
|
||||
})
|
||||
|
||||
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,8 +269,12 @@ describe('FormularioForm', () => {
|
||||
</FormularioField>
|
||||
<FormularioField name="bar" validation="required" />
|
||||
`,
|
||||
}
|
||||
},
|
||||
})
|
||||
|
||||
test('when no errors', async () => {
|
||||
const wrapper = createWrapper()
|
||||
|
||||
wrapper.find('input[type="text"]').setValue('foo')
|
||||
wrapper.find('input[type="text"]').trigger('blur')
|
||||
|
||||
@ -286,111 +287,91 @@ describe('FormularioForm', () => {
|
||||
}]])
|
||||
})
|
||||
|
||||
it('Emits correct validation event on entry', 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"
|
||||
/>
|
||||
` }
|
||||
})
|
||||
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').length).toBe(1)
|
||||
expect(wrapper.emitted('validation')[0][0]).toEqual({
|
||||
name: 'firstField',
|
||||
expect(wrapper.emitted('validation')).toEqual([[ {
|
||||
name: 'foo',
|
||||
violations: [ {
|
||||
rule: expect.any(String),
|
||||
args: ['foo'],
|
||||
context: {
|
||||
value: 'bar',
|
||||
formValues: expect.any(Object),
|
||||
name: 'firstField',
|
||||
name: 'foo',
|
||||
},
|
||||
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"
|
||||
>
|
||||
test('allows resetting form validation', async () => {
|
||||
const wrapper = mount(FormularioForm, {
|
||||
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>
|
||||
|
||||
<FormularioField v-slot="{ context }" name="password" validation="required|min:4,length">
|
||||
<input v-model="context.model" type="password">
|
||||
<input v-model="context.model" type="password" @blur="context.runValidation()">
|
||||
<div v-for="error in context.allErrors" data-password-error />
|
||||
</FormularioField>
|
||||
</FormularioForm>
|
||||
</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)
|
||||
})
|
||||
})
|
||||
|
Loading…
Reference in New Issue
Block a user