Skip to content

Commit 546d82e

Browse files
vonBraxlogaretm
andauthored
fix: normalize objects before equality checks (#5006)
* fix: fix missing key and undefined checks * test: add assertions tests * refactor: normalize objects to use fast-deep-equal logic * ci: fix "cannot find matching keyid" corepack error * ci: upgrade node to 22.11 * ci: use sudo for npm install * chore: added changeset --------- Co-authored-by: Abdelrahman Awad <logaretm1@gmail.com>
1 parent f164259 commit 546d82e

File tree

4 files changed

+258
-18
lines changed

4 files changed

+258
-18
lines changed

.changeset/tiny-lamps-kneel.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"vee-validate": patch
3+
---
4+
5+
fix: normalize objects before equality checks closes #5006

.circleci/config.yml

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,26 @@ jobs:
66
# Below is the definition of your job to build and test your app, you can rename and customize it as you want.
77
test:
88
docker:
9-
- image: cimg/node:22.10
9+
- image: cimg/node:22.11
1010
steps:
1111
# Checkout the code as the first step.
1212
- checkout
1313
- restore_cache:
1414
name: Restore pnpm Package Cache
1515
keys:
1616
- pnpm-packages-{{ checksum "pnpm-lock.yaml" }}
17+
- run:
18+
name: Use latest Corepack
19+
command: |
20+
echo "Before: corepack version => $(corepack --version || echo 'not installed')"
21+
sudo npm install -g corepack@latest
22+
echo "After : corepack version => $(corepack --version)"
1723
- run:
1824
name: Install pnpm package manager
1925
command: |
2026
corepack enable --install-directory ~/bin
2127
corepack prepare pnpm@latest-9 --activate
28+
pnpm --version
2229
pnpm config set store-dir .pnpm-store
2330
- run:
2431
name: Install dependencies

packages/vee-validate/src/utils/assertions.ts

Lines changed: 14 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -163,39 +163,36 @@ export function isEqual(a: any, b: any) {
163163
if (a.valueOf !== Object.prototype.valueOf) return a.valueOf() === b.valueOf();
164164
if (a.toString !== Object.prototype.toString) return a.toString() === b.toString();
165165

166-
keys = Object.keys(a);
167-
length = keys.length - countUndefinedValues(a, keys);
166+
// Remove undefined values before object comparison
167+
a = normalizeObject(a);
168+
b = normalizeObject(b);
168169

169-
if (length !== Object.keys(b).length - countUndefinedValues(b, Object.keys(b))) return false;
170+
keys = Object.keys(a);
171+
length = keys.length;
172+
if (length !== Object.keys(b).length) return false;
170173

171-
for (i = length; i-- !== 0; ) {
172-
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
173-
}
174+
for (i = length; i-- !== 0; ) if (!Object.prototype.hasOwnProperty.call(b, keys[i])) return false;
174175

175176
for (i = length; i-- !== 0; ) {
176177
// eslint-disable-next-line no-var
177178
var key = keys[i];
178-
179179
if (!isEqual(a[key], b[key])) return false;
180180
}
181181

182182
return true;
183183
}
184184

185185
// true if both NaN, false otherwise
186-
187186
return a !== a && b !== b;
188187
}
189188

190-
function countUndefinedValues(a: any, keys: string[]) {
191-
let result = 0;
192-
for (let i = keys.length; i-- !== 0; ) {
193-
// eslint-disable-next-line no-var
194-
var key = keys[i];
195-
196-
if (a[key] === undefined) result++;
197-
}
198-
return result;
189+
/**
190+
* Returns a new object where keys with an `undefined` value are removed.
191+
*
192+
* @param a object to normalize
193+
*/
194+
function normalizeObject(a: Record<PropertyKey, unknown>) {
195+
return Object.fromEntries(Object.entries(a).filter(([, value]) => value !== undefined));
199196
}
200197

201198
export function isFile(a: unknown): a is File {
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import { isEqual } from 'packages/vee-validate/src/utils';
2+
3+
describe('assertions', () => {
4+
test('equal objects are equal', () => {
5+
const a1 = { field1: undefined, field2: 'value2', field3: 'value3' };
6+
const a2 = { field1: undefined, field3: 'value3', field2: 'value2' };
7+
const a3 = { field2: 'value2', field3: 'value3', field1: undefined };
8+
const a4 = { field2: 'value2', field1: undefined, field3: 'value3' };
9+
const a5 = { field3: 'value3', field1: undefined, field2: 'value2' };
10+
const a6 = { field3: 'value3', field2: 'value2', field1: undefined };
11+
12+
const b1 = { field1: undefined, field2: 'value2', field3: 'value3' };
13+
const b2 = { field1: undefined, field3: 'value3', field2: 'value2' };
14+
const b3 = { field2: 'value2', field3: 'value3', field1: undefined };
15+
const b4 = { field2: 'value2', field1: undefined, field3: 'value3' };
16+
const b5 = { field3: 'value3', field1: undefined, field2: 'value2' };
17+
const b6 = { field3: 'value3', field2: 'value2', field1: undefined };
18+
19+
expect(isEqual(a1, b1)).toBe(true);
20+
expect(isEqual(a1, b2)).toBe(true);
21+
expect(isEqual(a1, b3)).toBe(true);
22+
expect(isEqual(a1, b4)).toBe(true);
23+
expect(isEqual(a1, b5)).toBe(true);
24+
expect(isEqual(a1, b6)).toBe(true);
25+
26+
expect(isEqual(a2, b1)).toBe(true);
27+
expect(isEqual(a2, b2)).toBe(true);
28+
expect(isEqual(a2, b3)).toBe(true);
29+
expect(isEqual(a2, b4)).toBe(true);
30+
expect(isEqual(a2, b5)).toBe(true);
31+
expect(isEqual(a2, b6)).toBe(true);
32+
33+
expect(isEqual(a3, b1)).toBe(true);
34+
expect(isEqual(a3, b2)).toBe(true);
35+
expect(isEqual(a3, b3)).toBe(true);
36+
expect(isEqual(a3, b4)).toBe(true);
37+
expect(isEqual(a3, b5)).toBe(true);
38+
expect(isEqual(a3, b6)).toBe(true);
39+
40+
expect(isEqual(a4, b1)).toBe(true);
41+
expect(isEqual(a4, b2)).toBe(true);
42+
expect(isEqual(a4, b3)).toBe(true);
43+
expect(isEqual(a4, b4)).toBe(true);
44+
expect(isEqual(a4, b5)).toBe(true);
45+
expect(isEqual(a4, b6)).toBe(true);
46+
47+
expect(isEqual(a5, b1)).toBe(true);
48+
expect(isEqual(a5, b2)).toBe(true);
49+
expect(isEqual(a5, b3)).toBe(true);
50+
expect(isEqual(a5, b4)).toBe(true);
51+
expect(isEqual(a5, b5)).toBe(true);
52+
expect(isEqual(a5, b6)).toBe(true);
53+
54+
expect(isEqual(a6, b1)).toBe(true);
55+
expect(isEqual(a6, b2)).toBe(true);
56+
expect(isEqual(a6, b3)).toBe(true);
57+
expect(isEqual(a6, b4)).toBe(true);
58+
expect(isEqual(a6, b5)).toBe(true);
59+
expect(isEqual(a6, b6)).toBe(true);
60+
});
61+
62+
test('equal objects where A has missing keys are equal', () => {
63+
const a1 = { field2: 'value2', field3: 'value3' };
64+
const a2 = { field3: 'value3', field2: 'value2' };
65+
66+
const b1 = { field1: undefined, field2: 'value2', field3: 'value3' };
67+
const b2 = { field1: undefined, field3: 'value3', field2: 'value2' };
68+
const b3 = { field2: 'value2', field3: 'value3', field1: undefined };
69+
const b4 = { field2: 'value2', field1: undefined, field3: 'value3' };
70+
const b5 = { field3: 'value3', field1: undefined, field2: 'value2' };
71+
const b6 = { field3: 'value3', field2: 'value2', field1: undefined };
72+
73+
expect(isEqual(a1, b1)).toBe(true);
74+
expect(isEqual(a1, b2)).toBe(true);
75+
expect(isEqual(a1, b3)).toBe(true);
76+
expect(isEqual(a1, b4)).toBe(true);
77+
expect(isEqual(a1, b5)).toBe(true);
78+
expect(isEqual(a1, b6)).toBe(true);
79+
80+
expect(isEqual(a2, b1)).toBe(true);
81+
expect(isEqual(a2, b2)).toBe(true);
82+
expect(isEqual(a2, b3)).toBe(true);
83+
expect(isEqual(a2, b4)).toBe(true);
84+
expect(isEqual(a2, b5)).toBe(true);
85+
expect(isEqual(a2, b6)).toBe(true);
86+
});
87+
88+
test('equal objects where B has missing keys are equal', () => {
89+
const a1 = { field1: undefined, field2: 'value2', field3: 'value3' };
90+
const a2 = { field1: undefined, field3: 'value3', field2: 'value2' };
91+
const a3 = { field2: 'value2', field3: 'value3', field1: undefined };
92+
const a4 = { field2: 'value2', field1: undefined, field3: 'value3' };
93+
const a5 = { field3: 'value3', field1: undefined, field2: 'value2' };
94+
const a6 = { field3: 'value3', field2: 'value2', field1: undefined };
95+
96+
const b1 = { field2: 'value2', field3: 'value3' };
97+
const b2 = { field3: 'value3', field2: 'value2' };
98+
99+
expect(isEqual(a1, b1)).toBe(true);
100+
expect(isEqual(a1, b2)).toBe(true);
101+
102+
expect(isEqual(a2, b1)).toBe(true);
103+
expect(isEqual(a2, b2)).toBe(true);
104+
105+
expect(isEqual(a3, b1)).toBe(true);
106+
expect(isEqual(a3, b2)).toBe(true);
107+
108+
expect(isEqual(a4, b1)).toBe(true);
109+
expect(isEqual(a4, b2)).toBe(true);
110+
111+
expect(isEqual(a5, b1)).toBe(true);
112+
expect(isEqual(a5, b2)).toBe(true);
113+
114+
expect(isEqual(a6, b1)).toBe(true);
115+
expect(isEqual(a6, b2)).toBe(true);
116+
});
117+
118+
test('different objects are not equal', () => {
119+
const a1 = { field1: undefined, field2: 'value2', field3: 'value3' };
120+
const a2 = { field1: undefined, field3: 'value3', field2: 'value2' };
121+
const a3 = { field2: 'value2', field3: 'value3', field1: undefined };
122+
const a4 = { field2: 'value2', field1: undefined, field3: 'value3' };
123+
const a5 = { field3: 'value3', field1: undefined, field2: 'value2' };
124+
const a6 = { field3: 'value3', field2: 'value2', field1: undefined };
125+
126+
const b1 = { field1: undefined, field2: 'value2', field3: 'DIFF' };
127+
const b2 = { field1: undefined, field3: 'DIFF', field2: 'value2' };
128+
const b3 = { field2: 'value2', field3: 'DIFF', field1: undefined };
129+
const b4 = { field2: 'value2', field1: undefined, field3: 'DIFF' };
130+
const b5 = { field3: 'DIFF', field1: undefined, field2: 'value2' };
131+
const b6 = { field3: 'DIFF', field2: 'value2', field1: undefined };
132+
133+
expect(isEqual(a1, b1)).toBe(false);
134+
expect(isEqual(a1, b2)).toBe(false);
135+
expect(isEqual(a1, b3)).toBe(false);
136+
expect(isEqual(a1, b4)).toBe(false);
137+
expect(isEqual(a1, b5)).toBe(false);
138+
expect(isEqual(a1, b6)).toBe(false);
139+
140+
expect(isEqual(a2, b1)).toBe(false);
141+
expect(isEqual(a2, b2)).toBe(false);
142+
expect(isEqual(a2, b3)).toBe(false);
143+
expect(isEqual(a2, b4)).toBe(false);
144+
expect(isEqual(a2, b5)).toBe(false);
145+
expect(isEqual(a2, b6)).toBe(false);
146+
147+
expect(isEqual(a3, b1)).toBe(false);
148+
expect(isEqual(a3, b2)).toBe(false);
149+
expect(isEqual(a3, b3)).toBe(false);
150+
expect(isEqual(a3, b4)).toBe(false);
151+
expect(isEqual(a3, b5)).toBe(false);
152+
expect(isEqual(a3, b6)).toBe(false);
153+
154+
expect(isEqual(a4, b1)).toBe(false);
155+
expect(isEqual(a4, b2)).toBe(false);
156+
expect(isEqual(a4, b3)).toBe(false);
157+
expect(isEqual(a4, b4)).toBe(false);
158+
expect(isEqual(a4, b5)).toBe(false);
159+
expect(isEqual(a4, b6)).toBe(false);
160+
161+
expect(isEqual(a5, b1)).toBe(false);
162+
expect(isEqual(a5, b2)).toBe(false);
163+
expect(isEqual(a5, b3)).toBe(false);
164+
expect(isEqual(a5, b4)).toBe(false);
165+
expect(isEqual(a5, b5)).toBe(false);
166+
expect(isEqual(a5, b6)).toBe(false);
167+
168+
expect(isEqual(a6, b1)).toBe(false);
169+
expect(isEqual(a6, b2)).toBe(false);
170+
expect(isEqual(a6, b3)).toBe(false);
171+
expect(isEqual(a6, b4)).toBe(false);
172+
expect(isEqual(a6, b5)).toBe(false);
173+
expect(isEqual(a6, b6)).toBe(false);
174+
});
175+
176+
test('different objects where A has missing keys are not equal', () => {
177+
const a1 = { field2: 'value2', field3: 'value3' };
178+
const a2 = { field3: 'value3', field2: 'value2' };
179+
180+
const b1 = { field1: undefined, field2: 'value2', field3: 'DIFF' };
181+
const b2 = { field1: undefined, field3: 'DIFF', field2: 'value2' };
182+
const b3 = { field2: 'value2', field3: 'DIFF', field1: undefined };
183+
const b4 = { field2: 'value2', field1: undefined, field3: 'DIFF' };
184+
const b5 = { field3: 'DIFF', field1: undefined, field2: 'value2' };
185+
const b6 = { field3: 'DIFF', field2: 'value2', field1: undefined };
186+
187+
expect(isEqual(a1, b1)).toBe(false);
188+
expect(isEqual(a1, b2)).toBe(false);
189+
expect(isEqual(a1, b3)).toBe(false);
190+
expect(isEqual(a1, b4)).toBe(false);
191+
expect(isEqual(a1, b5)).toBe(false);
192+
expect(isEqual(a1, b6)).toBe(false);
193+
194+
expect(isEqual(a2, b1)).toBe(false);
195+
expect(isEqual(a2, b2)).toBe(false);
196+
expect(isEqual(a2, b3)).toBe(false);
197+
expect(isEqual(a2, b4)).toBe(false);
198+
expect(isEqual(a2, b5)).toBe(false);
199+
expect(isEqual(a2, b6)).toBe(false);
200+
});
201+
202+
test('different objects where B has missing keys not equal', () => {
203+
const a1 = { field1: undefined, field2: 'value2', field3: 'value3' };
204+
const a2 = { field1: undefined, field3: 'value3', field2: 'value2' };
205+
const a3 = { field2: 'value2', field3: 'value3', field1: undefined };
206+
const a4 = { field2: 'value2', field1: undefined, field3: 'value3' };
207+
const a5 = { field3: 'value3', field1: undefined, field2: 'value2' };
208+
const a6 = { field3: 'value3', field2: 'value2', field1: undefined };
209+
210+
const b1 = { field2: 'value2', field3: 'DIFF' };
211+
const b2 = { field3: 'DIFF', field2: 'value2' };
212+
213+
expect(isEqual(a1, b1)).toBe(false);
214+
expect(isEqual(a1, b2)).toBe(false);
215+
216+
expect(isEqual(a2, b1)).toBe(false);
217+
expect(isEqual(a2, b2)).toBe(false);
218+
219+
expect(isEqual(a3, b1)).toBe(false);
220+
expect(isEqual(a3, b2)).toBe(false);
221+
222+
expect(isEqual(a4, b1)).toBe(false);
223+
expect(isEqual(a4, b2)).toBe(false);
224+
225+
expect(isEqual(a5, b1)).toBe(false);
226+
expect(isEqual(a5, b2)).toBe(false);
227+
228+
expect(isEqual(a6, b1)).toBe(false);
229+
expect(isEqual(a6, b2)).toBe(false);
230+
});
231+
});

0 commit comments

Comments
 (0)