Commit 7535db377f995c4c82944c74c7e1cd3d51eb6c55
Committed by
GitHub
1 parent
a0fdceea
fix(utils): deepMerge failing to correctly merge basic data types (#2872)
Showing
2 changed files
with
52 additions
and
26 deletions
src/utils/__test__/index.test.ts
@@ -4,6 +4,30 @@ import { describe, expect, test } from 'vitest'; | @@ -4,6 +4,30 @@ import { describe, expect, test } from 'vitest'; | ||
4 | import { deepMerge } from '@/utils'; | 4 | import { deepMerge } from '@/utils'; |
5 | 5 | ||
6 | describe('deepMerge function', () => { | 6 | describe('deepMerge function', () => { |
7 | + test('should correctly merge basic data types', () => { | ||
8 | + const source = { a: 1, b: 2, c: null }; | ||
9 | + const target = { | ||
10 | + a: 2, | ||
11 | + b: undefined, | ||
12 | + c: 3, | ||
13 | + }; | ||
14 | + const expected = { | ||
15 | + a: 2, | ||
16 | + b: 2, | ||
17 | + c: 3, | ||
18 | + }; | ||
19 | + expect(deepMerge(source, target)).toStrictEqual(expected); | ||
20 | + }); | ||
21 | + | ||
22 | + test('should return the same date if only 1 is passed', () => { | ||
23 | + const foo = new Date(); | ||
24 | + const merged = deepMerge(foo, null); | ||
25 | + const merged2 = deepMerge(undefined, foo); | ||
26 | + expect(merged).toStrictEqual(foo); | ||
27 | + expect(merged2).toStrictEqual(foo); | ||
28 | + expect(merged).toStrictEqual(merged2); | ||
29 | + }); | ||
30 | + | ||
7 | test('should merge two objects recursively', () => { | 31 | test('should merge two objects recursively', () => { |
8 | const source = { | 32 | const source = { |
9 | a: { b: { c: 1 }, d: [1, 2] }, | 33 | a: { b: { c: 1 }, d: [1, 2] }, |
@@ -15,6 +39,7 @@ describe('deepMerge function', () => { | @@ -15,6 +39,7 @@ describe('deepMerge function', () => { | ||
15 | too: [1, 2, 3], | 39 | too: [1, 2, 3], |
16 | }, | 40 | }, |
17 | ], | 41 | ], |
42 | + r: { a: 1 }, | ||
18 | }; | 43 | }; |
19 | const target = { | 44 | const target = { |
20 | a: { b: { d: [3] } }, | 45 | a: { b: { d: [3] } }, |
@@ -30,6 +55,7 @@ describe('deepMerge function', () => { | @@ -30,6 +55,7 @@ describe('deepMerge function', () => { | ||
30 | really: 'yes', | 55 | really: 'yes', |
31 | }, | 56 | }, |
32 | ], | 57 | ], |
58 | + r: { a: 2 }, | ||
33 | }; | 59 | }; |
34 | const expected = { | 60 | const expected = { |
35 | a: { b: { c: 1, d: [3] }, d: [1, 2] }, | 61 | a: { b: { c: 1, d: [3] }, d: [1, 2] }, |
@@ -48,8 +74,9 @@ describe('deepMerge function', () => { | @@ -48,8 +74,9 @@ describe('deepMerge function', () => { | ||
48 | }, | 74 | }, |
49 | ], | 75 | ], |
50 | qu: 5, | 76 | qu: 5, |
77 | + r: { a: 2 }, | ||
51 | }; | 78 | }; |
52 | - expect(deepMerge(source, target)).toEqual(expected); | 79 | + expect(deepMerge(source, target)).toStrictEqual(expected); |
53 | }); | 80 | }); |
54 | 81 | ||
55 | test('should replace arrays by default', () => { | 82 | test('should replace arrays by default', () => { |
@@ -65,7 +92,7 @@ describe('deepMerge function', () => { | @@ -65,7 +92,7 @@ describe('deepMerge function', () => { | ||
65 | a: { b: { d: [3] } }, | 92 | a: { b: { d: [3] } }, |
66 | e: [3], | 93 | e: [3], |
67 | }; | 94 | }; |
68 | - expect(deepMerge(source, target)).toEqual(expected); | 95 | + expect(deepMerge(source, target)).toStrictEqual(expected); |
69 | }); | 96 | }); |
70 | 97 | ||
71 | test("should union arrays using mergeArrays = 'union'", () => { | 98 | test("should union arrays using mergeArrays = 'union'", () => { |
@@ -75,13 +102,13 @@ describe('deepMerge function', () => { | @@ -75,13 +102,13 @@ describe('deepMerge function', () => { | ||
75 | }; | 102 | }; |
76 | const target = { | 103 | const target = { |
77 | a: { b: { d: [2, 3] } }, | 104 | a: { b: { d: [2, 3] } }, |
78 | - e: [3], | 105 | + e: [1, 3], |
79 | }; | 106 | }; |
80 | const expected = { | 107 | const expected = { |
81 | a: { b: { d: [1, 2, 3] } }, | 108 | a: { b: { d: [1, 2, 3] } }, |
82 | e: [1, 2, 3], | 109 | e: [1, 2, 3], |
83 | }; | 110 | }; |
84 | - expect(deepMerge(source, target, 'union')).toEqual(expected); | 111 | + expect(deepMerge(source, target, 'union')).toStrictEqual(expected); |
85 | }); | 112 | }); |
86 | 113 | ||
87 | test("should intersect arrays using mergeArrays = 'intersection'", () => { | 114 | test("should intersect arrays using mergeArrays = 'intersection'", () => { |
@@ -97,7 +124,7 @@ describe('deepMerge function', () => { | @@ -97,7 +124,7 @@ describe('deepMerge function', () => { | ||
97 | a: { b: { d: [2] } }, | 124 | a: { b: { d: [2] } }, |
98 | e: [], | 125 | e: [], |
99 | }; | 126 | }; |
100 | - expect(deepMerge(source, target, 'intersection')).toEqual(expected); | 127 | + expect(deepMerge(source, target, 'intersection')).toStrictEqual(expected); |
101 | }); | 128 | }); |
102 | 129 | ||
103 | test("should concatenate arrays using mergeArrays = 'concat'", () => { | 130 | test("should concatenate arrays using mergeArrays = 'concat'", () => { |
@@ -113,6 +140,6 @@ describe('deepMerge function', () => { | @@ -113,6 +140,6 @@ describe('deepMerge function', () => { | ||
113 | a: { b: { d: [1, 2, 2, 3] } }, | 140 | a: { b: { d: [1, 2, 2, 3] } }, |
114 | e: [1, 2, 3], | 141 | e: [1, 2, 3], |
115 | }; | 142 | }; |
116 | - expect(deepMerge(source, target, 'concat')).toEqual(expected); | 143 | + expect(deepMerge(source, target, 'concat')).toStrictEqual(expected); |
117 | }); | 144 | }); |
118 | }); | 145 | }); |
src/utils/index.ts
@@ -58,27 +58,26 @@ export function deepMerge<T extends object | null | undefined, U extends object | @@ -58,27 +58,26 @@ export function deepMerge<T extends object | null | undefined, U extends object | ||
58 | if (!source) { | 58 | if (!source) { |
59 | return target as T & U; | 59 | return target as T & U; |
60 | } | 60 | } |
61 | - if (isArray(target) && isArray(source)) { | ||
62 | - switch (mergeArrays) { | ||
63 | - case 'union': | ||
64 | - return unionWith(target, source, isEqual) as T & U; | ||
65 | - case 'intersection': | ||
66 | - return intersectionWith(target, source, isEqual) as T & U; | ||
67 | - case 'concat': | ||
68 | - return target.concat(source) as T & U; | ||
69 | - case 'replace': | ||
70 | - return source as T & U; | ||
71 | - default: | ||
72 | - throw new Error(`Unknown merge array strategy: ${mergeArrays}`); | ||
73 | - | 61 | + return mergeWith({}, source, target, (sourceValue, targetValue) => { |
62 | + if (isArray(targetValue) && isArray(sourceValue)) { | ||
63 | + switch (mergeArrays) { | ||
64 | + case 'union': | ||
65 | + return unionWith(sourceValue, targetValue, isEqual); | ||
66 | + case 'intersection': | ||
67 | + return intersectionWith(sourceValue, targetValue, isEqual); | ||
68 | + case 'concat': | ||
69 | + return sourceValue.concat(targetValue); | ||
70 | + case 'replace': | ||
71 | + return targetValue; | ||
72 | + default: | ||
73 | + throw new Error(`Unknown merge array strategy: ${mergeArrays as string}`); | ||
74 | + } | ||
74 | } | 75 | } |
75 | - } | ||
76 | - if (isObject(target) && isObject(source)) { | ||
77 | - return mergeWith({}, target, source, (targetValue, sourceValue) => { | ||
78 | - return deepMerge(targetValue, sourceValue, mergeArrays); | ||
79 | - }) as T & U; | ||
80 | - } | ||
81 | - return source as T & U; | 76 | + if (isObject(targetValue) && isObject(sourceValue)) { |
77 | + return deepMerge(sourceValue, targetValue, mergeArrays); | ||
78 | + } | ||
79 | + return undefined; | ||
80 | + }); | ||
82 | } | 81 | } |
83 | 82 | ||
84 | export function openWindow( | 83 | export function openWindow( |