@excom/hash-object 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.rush/temp/chunked-rush-logs/hash-object.apply-exports.chunks.jsonl +1 -0
- package/.rush/temp/chunked-rush-logs/hash-object.build_package-metas.chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/all.log +1 -0
- package/.rush/temp/operation/apply-exports/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/apply-exports/state.json +3 -0
- package/.rush/temp/operation/build_package-metas/all.log +1 -0
- package/.rush/temp/operation/build_package-metas/log-chunks.jsonl +1 -0
- package/.rush/temp/operation/build_package-metas/state.json +3 -0
- package/.rush/temp/shrinkwrap-deps.json +3 -0
- package/config/rig.json +6 -0
- package/index.ts +194 -0
- package/package.json +35 -0
- package/rush-logs/hash-object.apply-exports.cache.log +1 -0
- package/rush-logs/hash-object.apply-exports.log +1 -0
- package/rush-logs/hash-object.build_package-metas.cache.log +1 -0
- package/rush-logs/hash-object.build_package-metas.log +1 -0
- package/support/package-meta.json +29 -0
- package/support/tests/hash-object.test.ts +204 -0
- package/tsconfig.json +5 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: cd \"$RUSH_PROJECT_FOLDER\" && node ../heft-rig/scripts/apply-exports.mjs \n"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"kind":"O","text":"Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs \n"}
|
package/config/rig.json
ADDED
package/index.ts
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// e.g. hashObject({a: 1.1, b: "hello world", c: [{f: 2}], d: false, e: null}) => "31eh9pr0sbb82"
|
|
2
|
+
export function hashObject(value: unknown): string {
|
|
3
|
+
// 64-bit FNV-1a
|
|
4
|
+
let h1 = 0xcbf29ce4,
|
|
5
|
+
h2 = 0x84222325; // offset basis (split hi/lo 32s)
|
|
6
|
+
|
|
7
|
+
const seen = new Map<any, number>();
|
|
8
|
+
let seenId = 0;
|
|
9
|
+
|
|
10
|
+
const u8 = (n: number) => {
|
|
11
|
+
// hash one byte
|
|
12
|
+
h2 ^= n & 0xff;
|
|
13
|
+
// multiply by FNV prime 0x100000001b3 (64-bit) using 32-bit pieces
|
|
14
|
+
const a = (h2 >>> 0) * 0x1b3;
|
|
15
|
+
const b = (h1 >>> 0) * 0x1b3 + a / 0x100000000;
|
|
16
|
+
h2 = a >>> 0;
|
|
17
|
+
h1 = b >>> 0;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const u32 = (n: number) => {
|
|
21
|
+
u8(n);
|
|
22
|
+
u8(n >>> 8);
|
|
23
|
+
u8(n >>> 16);
|
|
24
|
+
u8(n >>> 24);
|
|
25
|
+
};
|
|
26
|
+
const str = (s: string) => {
|
|
27
|
+
for (let i = 0; i < s.length; i++) {
|
|
28
|
+
const c = s.charCodeAt(i);
|
|
29
|
+
u8(c);
|
|
30
|
+
u8(c >>> 8);
|
|
31
|
+
}
|
|
32
|
+
u8(0);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const num = (x: number) => {
|
|
36
|
+
// canonicalize tricky numerics
|
|
37
|
+
if (Number.isNaN(x)) {
|
|
38
|
+
u8(0x4e);
|
|
39
|
+
return;
|
|
40
|
+
} // 'N'
|
|
41
|
+
if (x === 0) {
|
|
42
|
+
u8(0);
|
|
43
|
+
u8(1 / x < 0 ? 1 : 0);
|
|
44
|
+
return;
|
|
45
|
+
} // distinguish -0
|
|
46
|
+
if (!Number.isFinite(x)) {
|
|
47
|
+
u8(0x49);
|
|
48
|
+
u8(x < 0 ? 1 : 0);
|
|
49
|
+
return;
|
|
50
|
+
} // 'I'
|
|
51
|
+
// hash IEEE754 bytes
|
|
52
|
+
const f = new Float64Array(1);
|
|
53
|
+
f[0] = x;
|
|
54
|
+
const b = new Uint8Array(f.buffer);
|
|
55
|
+
for (let i = 0; i < 8; i++) u8(b[i]);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const tag = (t: number) => u8(t);
|
|
59
|
+
|
|
60
|
+
const walk = (v: any) => {
|
|
61
|
+
const t = typeof v;
|
|
62
|
+
|
|
63
|
+
if (v === null) {
|
|
64
|
+
tag(0);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (t === "undefined") {
|
|
68
|
+
tag(1);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
if (t === "boolean") {
|
|
72
|
+
tag(2);
|
|
73
|
+
u8(v ? 1 : 0);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
if (t === "number") {
|
|
77
|
+
tag(3);
|
|
78
|
+
num(v);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (t === "bigint") {
|
|
82
|
+
tag(4);
|
|
83
|
+
str(v.toString(10));
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (t === "string") {
|
|
87
|
+
tag(5);
|
|
88
|
+
str(v);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
if (t === "symbol") {
|
|
92
|
+
tag(6);
|
|
93
|
+
str(String(v.description ?? ""));
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (t === "function") {
|
|
97
|
+
tag(7);
|
|
98
|
+
str(v.name || "");
|
|
99
|
+
return;
|
|
100
|
+
} // intentionally shallow for fns
|
|
101
|
+
|
|
102
|
+
// objects
|
|
103
|
+
const prev = seen.get(v);
|
|
104
|
+
if (prev !== undefined) {
|
|
105
|
+
tag(8);
|
|
106
|
+
u32(prev);
|
|
107
|
+
return;
|
|
108
|
+
} // cycle ref
|
|
109
|
+
seen.set(v, ++seenId);
|
|
110
|
+
|
|
111
|
+
// special cases
|
|
112
|
+
if (v instanceof Date) {
|
|
113
|
+
tag(9);
|
|
114
|
+
num(v.getTime());
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (v instanceof RegExp) {
|
|
118
|
+
tag(10);
|
|
119
|
+
str(v.source);
|
|
120
|
+
str(v.flags);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (ArrayBuffer.isView(v)) {
|
|
125
|
+
tag(11);
|
|
126
|
+
str(v.constructor?.name || "View");
|
|
127
|
+
const bytes = new Uint8Array(v.buffer, v.byteOffset, v.byteLength);
|
|
128
|
+
u32(bytes.length);
|
|
129
|
+
for (let i = 0; i < bytes.length; i++) u8(bytes[i]);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (v instanceof ArrayBuffer) {
|
|
133
|
+
tag(12);
|
|
134
|
+
const bytes = new Uint8Array(v);
|
|
135
|
+
u32(bytes.length);
|
|
136
|
+
for (let i = 0; i < bytes.length; i++) u8(bytes[i]);
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (v instanceof Map) {
|
|
141
|
+
tag(13);
|
|
142
|
+
// stable: hash entries then sort by entry-hash
|
|
143
|
+
const entries: { k: any; val: any; hk: string }[] = [];
|
|
144
|
+
v.forEach((val, k) => {
|
|
145
|
+
const hk = hashObject([k, val]); // small reuse for ordering
|
|
146
|
+
entries.push({ k, val, hk });
|
|
147
|
+
});
|
|
148
|
+
entries.sort((a, b) => (a.hk < b.hk ? -1 : a.hk > b.hk ? 1 : 0));
|
|
149
|
+
u32(entries.length);
|
|
150
|
+
for (const e of entries) {
|
|
151
|
+
walk(e.k);
|
|
152
|
+
walk(e.val);
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (v instanceof Set) {
|
|
158
|
+
tag(14);
|
|
159
|
+
const items: { v: any; hv: string }[] = [];
|
|
160
|
+
v.forEach((x) => items.push({ v: x, hv: hashObject(x) }));
|
|
161
|
+
items.sort((a, b) => (a.hv < b.hv ? -1 : a.hv > b.hv ? 1 : 0));
|
|
162
|
+
u32(items.length);
|
|
163
|
+
for (const it of items) walk(it.v);
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (Array.isArray(v)) {
|
|
168
|
+
tag(15);
|
|
169
|
+
u32(v.length);
|
|
170
|
+
for (let i = 0; i < v.length; i++) walk(v[i]);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// plain-ish object: stable key ordering
|
|
175
|
+
tag(16);
|
|
176
|
+
const proto = Object.getPrototypeOf(v);
|
|
177
|
+
str(proto && proto.constructor ? proto.constructor.name : "");
|
|
178
|
+
const keys = Object.keys(v).sort();
|
|
179
|
+
u32(keys.length);
|
|
180
|
+
for (let i = 0; i < keys.length; i++) {
|
|
181
|
+
const k = keys[i];
|
|
182
|
+
str(k);
|
|
183
|
+
walk(v[k]);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
walk(value);
|
|
188
|
+
|
|
189
|
+
// base36 from 64-bit (hi/lo)
|
|
190
|
+
const hi = h1 >>> 0,
|
|
191
|
+
lo = h2 >>> 0;
|
|
192
|
+
const asBig = (BigInt(hi) << 32n) | BigInt(lo);
|
|
193
|
+
return asBig.toString(36);
|
|
194
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@excom/hash-object",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "hash-object library",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=24.13.0"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"dependencies": {},
|
|
11
|
+
"peerDependencies": {},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"@excom/heft-rig": "^0.1.0"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"url": "excom-dev/nucleus",
|
|
17
|
+
"directory": "packages/hash-object"
|
|
18
|
+
},
|
|
19
|
+
"homepage": "https://github.com/excom-dev/nucleus/tree/main/packages/hash-object/support/docs/README.md",
|
|
20
|
+
"bugs": "https://github.com/excom-dev/nucleus/issues",
|
|
21
|
+
"keywords": [
|
|
22
|
+
"hash-object"
|
|
23
|
+
],
|
|
24
|
+
"excom": {
|
|
25
|
+
"packageType": "library"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "node node_modules/@excom/heft-rig/scripts/vite-build.mjs",
|
|
29
|
+
"build:watch": "node node_modules/@excom/heft-rig/scripts/vite-build-watch.mjs",
|
|
30
|
+
"format": "node node_modules/@excom/heft-rig/scripts/format.mjs",
|
|
31
|
+
"test": "node node_modules/@excom/heft-rig/scripts/vitest.mjs",
|
|
32
|
+
"coverage": "node node_modules/@excom/heft-rig/scripts/coverage.mjs",
|
|
33
|
+
"build:package-metas": "node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Caching has been disabled for this project's "apply-exports" command.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: cd "$RUSH_PROJECT_FOLDER" && node ../heft-rig/scripts/apply-exports.mjs
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
This project does not define the caching behavior of the "build:package-metas" command, so caching has been disabled.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
Invoking: node node_modules/@excom/heft-rig/scripts/build-package-metas.mjs
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"shortName": "hash-object",
|
|
3
|
+
"package": {
|
|
4
|
+
"name": "@excom/hash-object",
|
|
5
|
+
"version": "0.1.0",
|
|
6
|
+
"description": "hash-object library",
|
|
7
|
+
"peerDependencies": {},
|
|
8
|
+
"excom": {
|
|
9
|
+
"packageType": "library"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"demos": {},
|
|
13
|
+
"installation": {
|
|
14
|
+
"name": "@excom/hash-object",
|
|
15
|
+
"shortName": "hash-object",
|
|
16
|
+
"version": "0.1.0",
|
|
17
|
+
"description": "hash-object library",
|
|
18
|
+
"packageType": "library",
|
|
19
|
+
"install": {
|
|
20
|
+
"npm": "npm install @excom/hash-object"
|
|
21
|
+
},
|
|
22
|
+
"imports": {
|
|
23
|
+
"js": "import { /* … */ } from \"@excom/hash-object\";"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": []
|
|
26
|
+
},
|
|
27
|
+
"elementApis": [],
|
|
28
|
+
"exportedFiles": {}
|
|
29
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { hashObject } from "../../index";
|
|
2
|
+
import { describe, expect, it } from "@excom/heft-rig/node_modules/vitest";
|
|
3
|
+
|
|
4
|
+
describe("hashObject", () => {
|
|
5
|
+
it("returns deterministic hashes", () => {
|
|
6
|
+
const first = hashObject({ a: 1, b: "two" });
|
|
7
|
+
const second = hashObject({ a: 1, b: "two" });
|
|
8
|
+
const different = hashObject({ a: 2, b: "two" });
|
|
9
|
+
expect(first).toBe(second);
|
|
10
|
+
expect(first).not.toBe(different);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it("returns hashes for complex objects", () => {
|
|
14
|
+
const obj1 = { a: 1, b: "two", c: [{ d: 3 }, { e: 4 }] };
|
|
15
|
+
const hash1 = hashObject(obj1);
|
|
16
|
+
const obj2 = { a: 1, b: "two", c: [{ d: 3 }, { e: 4 }] };
|
|
17
|
+
const hash2 = hashObject(obj2);
|
|
18
|
+
expect(hash1).toBe(hash2);
|
|
19
|
+
expect(hash1).not.toBe(
|
|
20
|
+
hashObject({ a: 1, b: "two", c: [{ d: 3 }, { e: 5 }] }),
|
|
21
|
+
);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it("handles cycles without throwing", () => {
|
|
25
|
+
const obj: Record<string, unknown> = { a: 1 };
|
|
26
|
+
obj.self = obj;
|
|
27
|
+
const result = hashObject(obj);
|
|
28
|
+
expect(typeof result).toBe("string");
|
|
29
|
+
expect(result.length).toBeGreaterThan(0);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it("hashes primitive types distinctly", () => {
|
|
33
|
+
const results = new Set([
|
|
34
|
+
hashObject(null),
|
|
35
|
+
hashObject(undefined),
|
|
36
|
+
hashObject(true),
|
|
37
|
+
hashObject(false),
|
|
38
|
+
hashObject(0),
|
|
39
|
+
hashObject(1),
|
|
40
|
+
hashObject(""),
|
|
41
|
+
hashObject("a"),
|
|
42
|
+
]);
|
|
43
|
+
expect(results.size).toBe(8);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("distinguishes -0 from 0", () => {
|
|
47
|
+
expect(hashObject(-0)).not.toBe(hashObject(0));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("handles NaN and Infinity", () => {
|
|
51
|
+
const nan = hashObject(NaN);
|
|
52
|
+
const posInf = hashObject(Infinity);
|
|
53
|
+
const negInf = hashObject(-Infinity);
|
|
54
|
+
expect(nan).not.toBe(posInf);
|
|
55
|
+
expect(posInf).not.toBe(negInf);
|
|
56
|
+
expect(hashObject(NaN)).toBe(nan);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("hashes bigint values", () => {
|
|
60
|
+
const a = hashObject(1n);
|
|
61
|
+
const b = hashObject(2n);
|
|
62
|
+
expect(a).not.toBe(b);
|
|
63
|
+
expect(hashObject(1n)).toBe(a);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("hashes symbols by description", () => {
|
|
67
|
+
const a = hashObject(Symbol("foo"));
|
|
68
|
+
const b = hashObject(Symbol("bar"));
|
|
69
|
+
const c = hashObject(Symbol("foo"));
|
|
70
|
+
expect(a).toBe(c);
|
|
71
|
+
expect(a).not.toBe(b);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("hashes functions by name", () => {
|
|
75
|
+
function myFunc() {}
|
|
76
|
+
const hash = hashObject(myFunc);
|
|
77
|
+
expect(typeof hash).toBe("string");
|
|
78
|
+
expect(hash).toBe(hashObject(myFunc));
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("hashes Date objects by timestamp", () => {
|
|
82
|
+
const d1 = new Date(1700000000000);
|
|
83
|
+
const d2 = new Date(1700000000000);
|
|
84
|
+
const d3 = new Date(0);
|
|
85
|
+
expect(hashObject(d1)).toBe(hashObject(d2));
|
|
86
|
+
expect(hashObject(d1)).not.toBe(hashObject(d3));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("hashes RegExp by source and flags", () => {
|
|
90
|
+
expect(hashObject(/abc/g)).toBe(hashObject(/abc/g));
|
|
91
|
+
expect(hashObject(/abc/g)).not.toBe(hashObject(/abc/i));
|
|
92
|
+
expect(hashObject(/abc/)).not.toBe(hashObject(/def/));
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("hashes Map in stable key order", () => {
|
|
96
|
+
const m1 = new Map([
|
|
97
|
+
["a", 1],
|
|
98
|
+
["b", 2],
|
|
99
|
+
]);
|
|
100
|
+
const m2 = new Map([
|
|
101
|
+
["b", 2],
|
|
102
|
+
["a", 1],
|
|
103
|
+
]);
|
|
104
|
+
expect(hashObject(m1)).toBe(hashObject(m2));
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it("hashes Set in stable order", () => {
|
|
108
|
+
const s1 = new Set([1, 2, 3]);
|
|
109
|
+
const s2 = new Set([3, 1, 2]);
|
|
110
|
+
expect(hashObject(s1)).toBe(hashObject(s2));
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
it("hashes ArrayBuffer", () => {
|
|
114
|
+
const buf1 = new Uint8Array([1, 2, 3]).buffer;
|
|
115
|
+
const buf2 = new Uint8Array([1, 2, 3]).buffer;
|
|
116
|
+
const buf3 = new Uint8Array([4, 5, 6]).buffer;
|
|
117
|
+
expect(hashObject(buf1)).toBe(hashObject(buf2));
|
|
118
|
+
expect(hashObject(buf1)).not.toBe(hashObject(buf3));
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it("hashes typed arrays", () => {
|
|
122
|
+
const a = new Uint8Array([10, 20]);
|
|
123
|
+
const b = new Uint8Array([10, 20]);
|
|
124
|
+
const c = new Float32Array([10, 20]);
|
|
125
|
+
expect(hashObject(a)).toBe(hashObject(b));
|
|
126
|
+
expect(hashObject(a)).not.toBe(hashObject(c));
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("produces stable hashes regardless of key order", () => {
|
|
130
|
+
const a = { z: 1, a: 2, m: 3 };
|
|
131
|
+
const b = { a: 2, m: 3, z: 1 };
|
|
132
|
+
expect(hashObject(a)).toBe(hashObject(b));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("distinguishes arrays from objects", () => {
|
|
136
|
+
expect(hashObject([1, 2])).not.toBe(hashObject({ 0: 1, 1: 2 }));
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it("distinguishes empty containers", () => {
|
|
140
|
+
const results = new Set([
|
|
141
|
+
hashObject([]),
|
|
142
|
+
hashObject({}),
|
|
143
|
+
hashObject(new Map()),
|
|
144
|
+
hashObject(new Set()),
|
|
145
|
+
hashObject(""),
|
|
146
|
+
hashObject(null),
|
|
147
|
+
]);
|
|
148
|
+
expect(results.size).toBe(6);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
describe("hashObject (fallback branches)", () => {
|
|
153
|
+
it("hashes a symbol without a description", () => {
|
|
154
|
+
const anonymous = hashObject(Symbol());
|
|
155
|
+
expect(anonymous).toBe(hashObject(Symbol()));
|
|
156
|
+
expect(anonymous).toBe(hashObject(Symbol("")));
|
|
157
|
+
expect(anonymous).not.toBe(hashObject(Symbol("named")));
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("hashes an anonymous function like an empty name", () => {
|
|
161
|
+
const anonymous = hashObject([() => {}][0]);
|
|
162
|
+
expect(anonymous).toBe(hashObject([function () {}][0]));
|
|
163
|
+
expect(anonymous).not.toBe(hashObject(function named() {}));
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it("hashes a typed array whose constructor is unavailable", () => {
|
|
167
|
+
const view = new Uint8Array([1, 2, 3]);
|
|
168
|
+
Object.defineProperty(view, "constructor", { value: undefined });
|
|
169
|
+
const hash = hashObject(view);
|
|
170
|
+
expect(typeof hash).toBe("string");
|
|
171
|
+
expect(hash).not.toBe(hashObject(new Uint8Array([1, 2, 3])));
|
|
172
|
+
// same bytes, same missing constructor -> same hash
|
|
173
|
+
const twin = new Uint8Array([1, 2, 3]);
|
|
174
|
+
Object.defineProperty(twin, "constructor", { value: undefined });
|
|
175
|
+
expect(hash).toBe(hashObject(twin));
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("orders equal-hashing Map entries and Set items stably", () => {
|
|
179
|
+
const mapA = new Map<object, number>([
|
|
180
|
+
[{ k: 1 }, 1],
|
|
181
|
+
[{ k: 1 }, 1],
|
|
182
|
+
]);
|
|
183
|
+
const mapB = new Map<object, number>([
|
|
184
|
+
[{ k: 1 }, 1],
|
|
185
|
+
[{ k: 1 }, 1],
|
|
186
|
+
]);
|
|
187
|
+
expect(hashObject(mapA)).toBe(hashObject(mapB));
|
|
188
|
+
expect(hashObject(mapA)).not.toBe(hashObject(new Map([[{ k: 1 }, 1]])));
|
|
189
|
+
|
|
190
|
+
const setA = new Set([{ k: 1 }, { k: 1 }]);
|
|
191
|
+
const setB = new Set([{ k: 1 }, { k: 1 }]);
|
|
192
|
+
expect(hashObject(setA)).toBe(hashObject(setB));
|
|
193
|
+
expect(hashObject(setA)).not.toBe(hashObject(new Set([{ k: 1 }])));
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
it("hashes null-prototype objects with an empty constructor name", () => {
|
|
197
|
+
const bare = Object.assign(Object.create(null), { a: 1 });
|
|
198
|
+
const plain = { a: 1 };
|
|
199
|
+
expect(hashObject(bare)).toBe(
|
|
200
|
+
hashObject(Object.assign(Object.create(null), { a: 1 })),
|
|
201
|
+
);
|
|
202
|
+
expect(hashObject(bare)).not.toBe(hashObject(plain));
|
|
203
|
+
});
|
|
204
|
+
});
|