fix(assert): fix assertObjectMatch() prints arrays as objects (#5503)

fix(assert): fix ssertObjectMatch prints arrays as objects
This commit is contained in:
David Luis 2024-07-23 09:16:10 +07:00 committed by GitHub
parent b6b0d68381
commit 5cff014b02
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 115 additions and 2 deletions

View File

@ -49,7 +49,7 @@ function isObject(val: unknown): boolean {
return typeof val === "object" && val !== null; return typeof val === "object" && val !== null;
} }
function filter(a: loose, b: loose) { function filter(a: loose, b: loose): loose {
const seen = new WeakMap(); const seen = new WeakMap();
return filterObject(a, b); return filterObject(a, b);
@ -97,7 +97,7 @@ function filter(a: loose, b: loose) {
// On array references, build a filtered array and filter nested objects inside // On array references, build a filtered array and filter nested objects inside
if (Array.isArray(value) && Array.isArray(subset)) { if (Array.isArray(value) && Array.isArray(subset)) {
filtered[key] = filterObject({ ...value }, { ...subset }); filtered[key] = filterArray(value, subset);
continue; continue;
} }
@ -135,4 +135,66 @@ function filter(a: loose, b: loose) {
return filtered; return filtered;
} }
function filterArray(a: unknown[], b: unknown[]): unknown[] {
// Prevent infinite loop with circular references with same filter
if (seen.has(a) && (seen.get(a) === b)) {
return a;
}
seen.set(a, b);
const filtered: unknown[] = [];
const count = Math.min(a.length, b.length);
for (let i = 0; i < count; ++i) {
const value = a[i];
const subset = b[i];
// On regexp references, keep value as it to avoid loosing pattern and flags
if (value instanceof RegExp) {
filtered.push(value);
continue;
}
// On array references, build a filtered array and filter nested objects inside
if (Array.isArray(value) && Array.isArray(subset)) {
filtered.push(filterArray(value, subset));
continue;
}
// On nested objects references, build a filtered object recursively
if (isObject(value) && isObject(subset)) {
// When both operands are maps, build a filtered map with common keys and filter nested objects inside
if ((value instanceof Map) && (subset instanceof Map)) {
const map = new Map(
[...value].filter(([k]) => subset.has(k))
.map(([k, v]) => {
const v2 = subset.get(k);
if (isObject(v) && isObject(v2)) {
return [k, filterObject(v as loose, v2 as loose)];
}
return [k, v];
}),
);
filtered.push(map);
continue;
}
// When both operands are set, build a filtered set with common values
if ((value instanceof Set) && (subset instanceof Set)) {
filtered.push(value.intersection(subset));
continue;
}
filtered.push(filterObject(value as loose, subset as loose));
continue;
}
filtered.push(value);
}
return filtered;
}
} }

View File

@ -31,6 +31,11 @@ const n = {
["b", b], ["b", b],
]), ]),
}; };
const o = { foo: [new Map([["bar", n.bar], ["baz", null]])] };
const p = { bar: [new Set([1, 2, 3])] };
const q = { foo: [1, 2] as unknown[] };
q.foo[2] = q.foo;
const r = { bar: [[1, [2, [q]]]] };
Deno.test("assertObjectMatch() matches simple subset", () => { Deno.test("assertObjectMatch() matches simple subset", () => {
assertObjectMatch(a, { assertObjectMatch(a, {
@ -79,6 +84,7 @@ Deno.test("assertObjectMatch() matches subset with circular reference", () => {
}, },
}, },
}); });
assertObjectMatch(q, { foo: [1, 2, [1, 2, [1, 2, [1, 2]]]] });
}); });
Deno.test("assertObjectMatch() matches subset with interface", () => { Deno.test("assertObjectMatch() matches subset with interface", () => {
@ -105,6 +111,7 @@ Deno.test("assertObjectMatch() matches subset with nested array inside", () => {
assertObjectMatch(j, { foo: [[1, 2, 3]] }); assertObjectMatch(j, { foo: [[1, 2, 3]] });
assertObjectMatch(k, { foo: [[1, [2, [3]]]] }); assertObjectMatch(k, { foo: [[1, [2, [3]]]] });
assertObjectMatch(l, { foo: [[1, [2, [a, e, j, k]]]] }); assertObjectMatch(l, { foo: [[1, [2, [a, e, j, k]]]] });
assertObjectMatch(r, { bar: [[1, [2, [q]]]] });
}); });
Deno.test("assertObjectMatch() matches subset with regexp", () => { Deno.test("assertObjectMatch() matches subset with regexp", () => {
@ -117,6 +124,9 @@ Deno.test("assertObjectMatch() matches subset with built-in data structures", ()
assertObjectMatch(n, { bar: new Map([["bar", 2]]) }); assertObjectMatch(n, { bar: new Map([["bar", 2]]) });
assertObjectMatch(n, { baz: new Map([["b", b]]) }); assertObjectMatch(n, { baz: new Map([["b", b]]) });
assertObjectMatch(n, { baz: new Map([["b", { foo: true }]]) }); assertObjectMatch(n, { baz: new Map([["b", { foo: true }]]) });
assertObjectMatch(o, { foo: [new Map([["baz", null]])] });
assertObjectMatch(o, { foo: [new Map([["bar", n.bar]])] });
assertObjectMatch(p, { bar: [new Set([2, 3])] });
}); });
Deno.test("assertObjectMatch() throws when a key is missing from subset", () => { Deno.test("assertObjectMatch() throws when a key is missing from subset", () => {
@ -341,6 +351,10 @@ Deno.test("assertObjectMatch() prints inputs correctly", () => {
description: "foo", description: "foo",
}, },
name: "somegroup", name: "somegroup",
nodes: [
"somenode",
"someothernode",
],
}, },
}; };
@ -356,9 +370,46 @@ Deno.test("assertObjectMatch() prints inputs correctly", () => {
+ description: "foo", + description: "foo",
+ }, + },
+ name: "somegroup", + name: "somegroup",
+ nodes: [
+ "somenode",
+ "someothernode",
+ ],
- message: "NodeNotFound", - message: "NodeNotFound",
}, },
protocol: "graph", protocol: "graph",
}`, }`,
); );
assertThrows(
() => assertObjectMatch({ foo: [] }, { foo: ["bar"] }),
AssertionError,
` {
+ foo: [
+ "bar",
+ ],
- foo: [],
}`,
);
const a = {};
const b = {};
Object.defineProperty(a, "hello", {
value: "world",
enumerable: false,
});
Object.defineProperty(b, "foo", {
value: "bar",
enumerable: false,
});
assertThrows(
() => assertObjectMatch(a, b),
AssertionError,
` {
- hello: "world",
+ foo: "bar",
}`,
);
}); });