From 760c2dc72db410a42337bb47611cc27b74df766e Mon Sep 17 00:00:00 2001 From: Erik van Paassen Date: Fri, 22 Mar 2024 15:25:50 +0100 Subject: [PATCH] fix: mark form dirty when object keys are deleted Fixes #4678 --- packages/vee-validate/src/utils/assertions.ts | 21 ++++++++++++++- packages/vee-validate/tests/useForm.spec.ts | 26 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/packages/vee-validate/src/utils/assertions.ts b/packages/vee-validate/src/utils/assertions.ts index 8411d344f..eb6729ae0 100644 --- a/packages/vee-validate/src/utils/assertions.ts +++ b/packages/vee-validate/src/utils/assertions.ts @@ -108,6 +108,8 @@ export function isPropPresent(obj: Record, prop: string) { * Compares if two values are the same borrowed from: * https://github.com/epoberezkin/fast-deep-equal * We added a case for file matching since `Object.keys` doesn't work with Files. + * + * NB: keys with the value undefined are ignored in the evaluation and considered equal to missing keys. * */ export function isEqual(a: any, b: any) { if (a === b) return true; @@ -162,7 +164,13 @@ export function isEqual(a: any, b: any) { if (a.toString !== Object.prototype.toString) return a.toString() === b.toString(); keys = Object.keys(a); - length = keys.length; + length = keys.length - countUndefinedValues(a, keys); + + if (length !== Object.keys(b).length - countUndefinedValues(b, Object.keys(b))) return false; + + for (i = length; i-- !== 0; ) { + if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false; + } for (i = length; i-- !== 0; ) { // eslint-disable-next-line no-var @@ -179,6 +187,17 @@ export function isEqual(a: any, b: any) { return a !== a && b !== b; } +function countUndefinedValues(a: any, keys: string[]) { + let result = 0; + for (let i = keys.length; i-- !== 0; ) { + // eslint-disable-next-line no-var + var key = keys[i]; + + if (a[key] === undefined) result++; + } + return result; +} + export function isFile(a: unknown): a is File { if (!isClient) { return false; diff --git a/packages/vee-validate/tests/useForm.spec.ts b/packages/vee-validate/tests/useForm.spec.ts index e99cfdf7a..d33332959 100644 --- a/packages/vee-validate/tests/useForm.spec.ts +++ b/packages/vee-validate/tests/useForm.spec.ts @@ -1133,6 +1133,32 @@ describe('useForm()', () => { expect(form.meta.value.dirty).toBe(false); }); + // #4678 + test('form is marked as dirty when key is removed', async () => { + let form!: FormContext; + mountWithHoc({ + setup() { + form = useForm({ + initialValues: { + fname: { + key1: 'value1', + key2: 'value2', + }, + }, + }); + + useField('fname'); + + return {}; + }, + template: `
`, + }); + + form.setFieldValue('fname', { key1: 'value1' }); + await flushPromises(); + expect(form.meta.value.dirty).toBe(true); + }); + describe('error paths can have dot or square bracket for the same field', () => { test('path is bracket, mutations are dot', async () => { let field!: FieldContext;