From fb4bcb730df4e9ce820d8ee0bc66ff85a33f9eb2 Mon Sep 17 00:00:00 2001 From: Boris Andreiko Date: Thu, 7 Nov 2024 13:47:06 +0300 Subject: [PATCH 1/4] feat: focusOptions changes, replaced focusName - with field and index, added focusOptions to all array methods --- src/App.vue | 4 +- src/components/ValidationFieldArray.vue | 55 +++++++++++++++++-------- src/types/field-array.ts | 3 +- tests/unit/ValidationFieldArray.test.js | 15 +++++-- tests/unit/ValidationForm.vue | 42 +++++++++++++++---- 5 files changed, 88 insertions(+), 31 deletions(-) diff --git a/src/App.vue b/src/App.vue index f973622..08f26d5 100644 --- a/src/App.vue +++ b/src/App.vue @@ -65,9 +65,7 @@ diff --git a/src/components/ValidationFieldArray.vue b/src/components/ValidationFieldArray.vue index 1bc35c3..e4a0bc4 100644 --- a/src/components/ValidationFieldArray.vue +++ b/src/components/ValidationFieldArray.vue @@ -83,49 +83,70 @@ function touch() { pristine.value = false; } -function append(value: Record, options?: FocusOptions) { +function append(value: Record, focusOptions: FocusOptions = null) { value[keyName.value] = getId(value); fields.value.push(value); touch(); - handleFocus(options); + if (focusOptions) { + // by default focus on last field + handleFocus({ index: fields.value.length - 1, ...focusOptions }); + } } -function prepend(value: Record, options?: FocusOptions) { +function prepend(value: Record, focusOptions: FocusOptions = null) { value[keyName.value] = getId(value); fields.value.unshift(value); touch(); - handleFocus(options); + if (focusOptions) { + // by default focus on first field + handleFocus(focusOptions); + } } -function insert(index: number, value: Record, options?: FocusOptions) { +function insert(index: number, value: Record, focusOptions: FocusOptions = null) { value[keyName.value] = getId(value); fields.value.splice(index, 0, value); touch(); - handleFocus(options); + if (focusOptions) { + // by default focus on inserted field + handleFocus({ index, ...focusOptions }); + } } -function swap(from: number, to: number) { +function swap(from: number, to: number, focusOptions: FocusOptions = null) { const temp = fields.value[from]; fields.value[from] = fields.value[to]; fields.value[to] = temp; touch(); + if (focusOptions) { + // by default focus on swapped field + handleFocus({ index: to, ...focusOptions }); + } } -function move(from: number, to: number) { +function move(from: number, to: number, focusOptions: FocusOptions = null) { fields.value.splice(to, 0, fields.value.splice(from, 1)[0]); touch(); + if (focusOptions) { + // by default focus on moved field + handleFocus({ index: to, ...focusOptions }); + } } -function remove(index: number) { +function remove(index: number, focusOptions: FocusOptions = null) { fields.value.splice(index, 1); touch(); + + if (focusOptions && fields.value.length) { + // by default focus on previous field, if there is no previous field focus on first field + handleFocus({ index: Math.max(index - 1, 0), ...focusOptions }); + } } -function handleFocus(options?: FocusOptions) { - if (!options) { - return; +function handleFocus({ field, index = 0 }: FocusOptions) { + if (!field) { + throw new Error(`Field name is required for focus, please provide field name in focus options`); } + + const itemName = `${name.value}.${index || 0}.${field}`; nextTick(() => { - const fieldComponent = fieldComponents.value.find(({ name }) => name === options.focusName); - if (!fieldComponent) { - return; - } - fieldComponent.onFocus(); + const fieldComponent = fieldComponents.value.find(({ name }) => name === itemName); + fieldComponent?.onFocus(); }); } diff --git a/src/types/field-array.ts b/src/types/field-array.ts index 07c49c9..61c7900 100644 --- a/src/types/field-array.ts +++ b/src/types/field-array.ts @@ -1,3 +1,4 @@ export interface FocusOptions { - focusName: string; + field: string; + index?: number; } diff --git a/tests/unit/ValidationFieldArray.test.js b/tests/unit/ValidationFieldArray.test.js index 69b7cb3..98acc36 100644 --- a/tests/unit/ValidationFieldArray.test.js +++ b/tests/unit/ValidationFieldArray.test.js @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { mount } from '@vue/test-utils'; import { yupResolver } from '@vue-validate-form/resolvers'; import { nextTick } from 'vue'; @@ -21,12 +21,14 @@ const resolver = yupResolver( describe('ValidationFieldArray', () => { let wrapper; + let handleFocus; const createComponent = ({ props } = {}) => { wrapper = mount(ValidationForm, { props, attachTo: document.body }); + handleFocus = vi.spyOn(wrapper.vm, 'handleFocus'); }; it('should render array fields', async () => { @@ -146,7 +148,8 @@ describe('ValidationFieldArray', () => { await nextTick(); expect(wrapper.findAllComponents(BaseInput).length).toBe(3); - await wrapper.find('#append').trigger('click', { firstName: 'new name' }); + await wrapper.find('#append').trigger('click'); + expect(handleFocus).toHaveBeenLastCalledWith({ name: 'arrayField.1.firstName' }); const baseInputWrappers = wrapper.findAllComponents(BaseInput); expect(baseInputWrappers.length).toBe(4); @@ -202,6 +205,7 @@ describe('ValidationFieldArray', () => { }); await wrapper.find('#remove').trigger('click'); + expect(handleFocus).toHaveBeenLastCalledWith({ name: 'arrayField.0.firstName' }); expect(wrapper.findComponent(FormInfo).props().errors).toEqual({ 'my.nested.value': [], @@ -271,7 +275,8 @@ describe('ValidationFieldArray', () => { ] }); - await wrapper.find('#prepend').trigger('click', { firstName: 'new name' }); + await wrapper.find('#prepend').trigger('click'); + expect(handleFocus).toHaveBeenLastCalledWith({ name: 'arrayField.0.firstName' }); expect(wrapper.findComponent(FormInfo).props().errors).toEqual({ 'my.nested.value': [], @@ -354,6 +359,7 @@ describe('ValidationFieldArray', () => { ] }); expect(wrapper.findComponent(FormInfo).props().values.arrayField[1].type).toEqual(undefined); + expect(handleFocus).toHaveBeenLastCalledWith({ name: 'arrayField.1.firstName' }); expect(wrapper.findAllComponents(BaseInput).at(3).props().modelValue).toBe('insert'); }); @@ -407,6 +413,7 @@ describe('ValidationFieldArray', () => { const formInfoWrapper = wrapper.findComponent(FormInfo); await wrapper.find('#swap').trigger('click'); + expect(handleFocus).toHaveBeenLastCalledWith({ name: 'arrayField.2.firstName' }); expect(formInfoWrapper.props().errors).toEqual({ 'my.nested.value': [], @@ -540,6 +547,8 @@ describe('ValidationFieldArray', () => { await wrapper.find('#move').trigger('click'); + expect(handleFocus).toHaveBeenLastCalledWith({ name: 'arrayField.2.firstName' }); + expect(formInfoWrapper.props().errors).toEqual({ 'my.nested.value': [], 'my-input': [], diff --git a/tests/unit/ValidationForm.vue b/tests/unit/ValidationForm.vue index 0bca3d6..b752e73 100644 --- a/tests/unit/ValidationForm.vue +++ b/tests/unit/ValidationForm.vue @@ -96,7 +96,10 @@
- +