From 3d49faa4101902c2e77aee0a2d43cd29b69f7b4e Mon Sep 17 00:00:00 2001 From: Abdelrahman Awad Date: Sun, 10 Apr 2022 20:19:23 +0200 Subject: [PATCH] fix: ignore validation of removed array elements closes #3748 --- packages/vee-validate/src/useField.ts | 12 +++ .../vee-validate/tests/FieldArray.spec.ts | 92 +++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/packages/vee-validate/src/useField.ts b/packages/vee-validate/src/useField.ts index 763d966e0..75e9906fd 100644 --- a/packages/vee-validate/src/useField.ts +++ b/packages/vee-validate/src/useField.ts @@ -96,6 +96,8 @@ function _useField( const form = !standalone ? injectWithSelf(FormContextKey) : undefined; + // a flag indicating if the field is about to be removed/unmounted. + let markedForRemoval = false; const { id, value, initialValue, meta, setState, errors, errorMessage } = useFieldState(name, { modelValue, standalone, @@ -138,6 +140,11 @@ function _useField( meta.pending = true; meta.validated = true; const result = await validateCurrentValue('validated-only'); + if (markedForRemoval) { + result.valid = true; + result.errors = []; + } + setState({ errors: result.errors }); meta.pending = false; @@ -146,6 +153,10 @@ function _useField( async function validateValidStateOnly(): Promise { const result = await validateCurrentValue('silent'); + if (markedForRemoval) { + result.valid = true; + } + meta.valid = result.valid; return result; @@ -290,6 +301,7 @@ function _useField( form.register(field); onBeforeUnmount(() => { + markedForRemoval = true; form.unregister(field); }); diff --git a/packages/vee-validate/tests/FieldArray.spec.ts b/packages/vee-validate/tests/FieldArray.spec.ts index a259856df..9ef9d7d3a 100644 --- a/packages/vee-validate/tests/FieldArray.spec.ts +++ b/packages/vee-validate/tests/FieldArray.spec.ts @@ -1,3 +1,6 @@ +import { defineRule, useField } from '@/vee-validate'; +import { defineComponent } from '@vue/runtime-core'; +import { toRef } from 'vue'; import * as yup from 'yup'; import { mountWithHoc, setValue, getValue, dispatchEvent, flushPromises } from './helpers'; @@ -574,3 +577,92 @@ test('clears old errors path when item is removed when no form schema is present expect(errorList.children).toHaveLength(2); }); + +// #3748 +test('clears old errors path when last item is removed and value update validation is on', async () => { + const onSubmit = jest.fn(); + defineRule('required', (v: any) => (v ? true : REQUIRED_MESSAGE)); + const InputField = defineComponent({ + props: { + rules: { + type: String, + required: true, + }, + name: { + type: String, + required: true, + }, + label: String, + type: { type: String, default: 'text' }, + }, + setup(props) { + const { value, handleChange, errors } = useField(toRef(props, 'name'), props.rules, { + label: props.label, + type: props.type, + }); + + return { + value, + errors, + handleChange, + }; + }, + template: ` + + + {{ errors[0] }} + `, + }); + + mountWithHoc({ + components: { + InputField, + }, + setup() { + const initialValues = { + users: ['first', 'second', 'third'], + }; + + const schema = yup.string().required(); + + return { + onSubmit, + schema, + initialValues, + }; + }, + template: ` + + +
+ User #{{ idx }} + + + + +
+
+ + +
    +
  • {{ error }}
  • +
+ + +
+ `, + }); + + await flushPromises(); + const submitBtn = document.querySelector('.submit') as HTMLButtonElement; + const errorList = document.querySelector('ul') as HTMLUListElement; + const removeBtnAt = (idx: number) => document.querySelectorAll('.remove')[idx] as HTMLButtonElement; // remove the second item + + submitBtn.click(); + await flushPromises(); + expect(errorList.children).toHaveLength(0); + removeBtnAt(2).click(); + await flushPromises(); + + expect(errorList.children).toHaveLength(0); +});