deno.land / std@0.224.0 / assert / assert_object_match.ts

assert_object_match.ts
View Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.// This module is browser compatible.import { assertEquals } from "./assert_equals.ts";
/** * Make an assertion that `actual` object is a subset of `expected` object, * deeply. If not, then throw. * * @example * ```ts * import { assertObjectMatch } from "https://deno.land/std@$STD_VERSION/assert/assert_object_match.ts"; * * assertObjectMatch({ foo: "bar" }, { foo: "bar" }); // Doesn't throw * assertObjectMatch({ foo: "bar" }, { foo: "baz" }); // Throws * ``` */export function assertObjectMatch( // deno-lint-ignore no-explicit-any actual: Record<PropertyKey, any>, expected: Record<PropertyKey, unknown>, msg?: string,): void { type loose = Record<PropertyKey, unknown>;
function filter(a: loose, b: loose) { const seen = new WeakMap(); return fn(a, b);
function fn(a: loose, b: loose): loose { // Prevent infinite loop with circular references with same filter if ((seen.has(a)) && (seen.get(a) === b)) { return a; } try { seen.set(a, b); } catch (err) { if (err instanceof TypeError) { throw new TypeError( `Cannot assertObjectMatch ${ a === null ? null : `type ${typeof a}` }`, ); } else throw err; } // Filter keys and symbols which are present in both actual and expected const filtered = {} as loose; const entries = [ ...Object.getOwnPropertyNames(a), ...Object.getOwnPropertySymbols(a), ] .filter((key) => key in b) .map((key) => [key, a[key as string]]) as Array<[string, unknown]>; for (const [key, value] of entries) { // On array references, build a filtered array and filter nested objects inside if (Array.isArray(value)) { const subset = (b as loose)[key]; if (Array.isArray(subset)) { filtered[key] = fn({ ...value }, { ...subset }); continue; } } // On regexp references, keep value as it to avoid loosing pattern and flags else if (value instanceof RegExp) { filtered[key] = value; continue; } // On nested objects references, build a filtered object recursively else if (typeof value === "object" && value !== null) { const subset = (b as loose)[key]; if ((typeof subset === "object") && 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)) { filtered[key] = new Map( [...value].filter(([k]) => subset.has(k)).map(( [k, v], ) => [k, typeof v === "object" ? fn(v, subset.get(k)) : v]), ); continue; } // When both operands are set, build a filtered set with common values if ((value instanceof Set) && (subset instanceof Set)) { filtered[key] = new Set([...value].filter((v) => subset.has(v))); continue; } filtered[key] = fn(value as loose, subset as loose); continue; } } filtered[key] = value; } return filtered; } } return assertEquals( // get the intersection of "actual" and "expected" // side effect: all the instances' constructor field is "Object" now. filter(actual, expected), // set (nested) instances' constructor field to be "Object" without changing expected value. // see https://github.com/denoland/deno_std/pull/1419 filter(expected, expected), msg, );}
std

Version Info

Tagged at
6 months ago