From 32e194e842d682c0dfc86165f795e2aaa3e06e25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E8=89=B3=E5=85=B5?= Date: Mon, 22 Dec 2025 16:42:52 +0800 Subject: [PATCH 1/3] fix: avoid array length truncation in mergeWith and add sparse merge test --- src/test/mergeWith.test.ts | 16 ++++++++++++++++ src/utils/set.ts | 5 ++++- 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/test/mergeWith.test.ts diff --git a/src/test/mergeWith.test.ts b/src/test/mergeWith.test.ts new file mode 100644 index 00000000..c01b5654 --- /dev/null +++ b/src/test/mergeWith.test.ts @@ -0,0 +1,16 @@ +import { mergeWith } from '../utils/set'; + +describe('mergeWith array merge', () => { + it('should keep existing array length when merging sparse updates', () => { + const allValues = { list: ['A', 'B', 'C', 'D'] }; + const changedValues = { list: new Array(2) }; + changedValues.list[1] = 'BB'; // 仅更新第 2 项,长度为 2 + + const merged = mergeWith([allValues, changedValues], { + prepareArray: current => [...(current || [])], + }); + + expect(merged.list).toEqual(['A', 'BB', 'C', 'D']); + expect(merged.list).toHaveLength(4); + }); +}); diff --git a/src/utils/set.ts b/src/utils/set.ts index c0db65d5..061ef0c9 100644 --- a/src/utils/set.ts +++ b/src/utils/set.ts @@ -110,7 +110,10 @@ export function mergeWith( clone = set(clone, path, createEmpty(value)); } - keys(value).forEach(key => { + const valueKeys = isArr + ? keys(value).filter(key => key !== 'length') + : keys(value); + valueKeys.forEach(key => { internalMerge([...path, key], loopSet); }); } From 4550dd2bf59585715f8ffac3338bfcfe60a35afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E8=89=B3=E5=85=B5?= Date: Mon, 22 Dec 2025 16:51:44 +0800 Subject: [PATCH 2/3] fix: skip array length key inline in mergeWith traversal --- src/utils/set.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/utils/set.ts b/src/utils/set.ts index 061ef0c9..0968e108 100644 --- a/src/utils/set.ts +++ b/src/utils/set.ts @@ -110,10 +110,10 @@ export function mergeWith( clone = set(clone, path, createEmpty(value)); } - const valueKeys = isArr - ? keys(value).filter(key => key !== 'length') - : keys(value); - valueKeys.forEach(key => { + keys(value).forEach(key => { + if (isArr && key === 'length') { + return; + } internalMerge([...path, key], loopSet); }); } From a2f831cae6b5a8785dc7f6ebfc625a64c8fb5770 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=AB=98=E8=89=B3=E5=85=B5?= Date: Mon, 22 Dec 2025 18:04:08 +0800 Subject: [PATCH 3/3] refactor: avoid hardcoded length handling in merge traversal --- src/utils/set.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/utils/set.ts b/src/utils/set.ts index 0968e108..7d465a56 100644 --- a/src/utils/set.ts +++ b/src/utils/set.ts @@ -111,10 +111,9 @@ export function mergeWith( } keys(value).forEach(key => { - if (isArr && key === 'length') { - return; + if (Object.getOwnPropertyDescriptor(value, key).enumerable) { + internalMerge([...path, key], loopSet); } - internalMerge([...path, key], loopSet); }); } } else {