utilitish 0.0.6 → 0.0.8

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.
@@ -0,0 +1,261 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("../map/map-prototype");
4
+ describe('Map.prototype', () => {
5
+ describe('toList()', () => {
6
+ describe('type: entries', () => {
7
+ it('should return array of entries by default', () => {
8
+ const map = new Map([
9
+ ['a', 1],
10
+ ['b', 2],
11
+ ]);
12
+ expect(map.toList()).toEqual([
13
+ ['a', 1],
14
+ ['b', 2],
15
+ ]);
16
+ });
17
+ it('should return entries when type is "entries"', () => {
18
+ const map = new Map([['x', 42]]);
19
+ expect(map.toList('entries')).toEqual([['x', 42]]);
20
+ });
21
+ it('should return empty array when map is empty', () => {
22
+ const map = new Map();
23
+ expect(map.toList()).toEqual([]);
24
+ });
25
+ });
26
+ describe('type: keys', () => {
27
+ it('should return array of keys when type is "keys"', () => {
28
+ const map = new Map([
29
+ ['a', 1],
30
+ ['b', 2],
31
+ ]);
32
+ expect(map.toList('keys')).toEqual(['a', 'b']);
33
+ });
34
+ it('should return empty array of keys when map is empty', () => {
35
+ const map = new Map();
36
+ expect(map.toList('keys')).toEqual([]);
37
+ });
38
+ });
39
+ describe('type: values', () => {
40
+ it('should return array of values when type is "values"', () => {
41
+ const map = new Map([
42
+ ['a', 1],
43
+ ['b', 2],
44
+ ]);
45
+ expect(map.toList('values')).toEqual([1, 2]);
46
+ });
47
+ it('should return empty array of values when map is empty', () => {
48
+ const map = new Map();
49
+ expect(map.toList('values')).toEqual([]);
50
+ });
51
+ });
52
+ describe('type: object', () => {
53
+ it('should return object when type is "object" with string keys', () => {
54
+ const map = new Map([
55
+ ['a', 1],
56
+ ['b', 2],
57
+ ]);
58
+ expect(map.toList('object')).toEqual({ a: 1, b: 2 });
59
+ });
60
+ it('should return object with numeric keys converted to strings', () => {
61
+ const map = new Map([
62
+ [1, 'x'],
63
+ [2, 'y'],
64
+ ]);
65
+ expect(map.toList('object')).toEqual({ '1': 'x', '2': 'y' });
66
+ });
67
+ it('should return empty object when map is empty', () => {
68
+ const map = new Map();
69
+ expect(map.toList('object')).toEqual({});
70
+ });
71
+ });
72
+ describe('error handling', () => {
73
+ it('should throw TypeError when key is not string/number/symbol for object conversion', () => {
74
+ const map = new Map();
75
+ const key = { foo: 1 };
76
+ map.set(key, 42);
77
+ expect(() => map.toList('object')).toThrow(TypeError);
78
+ });
79
+ it('should throw TypeError on unknown type', () => {
80
+ const map = new Map();
81
+ // @ts-expect-error
82
+ expect(() => map.toList('unknown')).toThrow(TypeError);
83
+ });
84
+ });
85
+ });
86
+ describe('ensureArray()', () => {
87
+ describe('with non-existing keys', () => {
88
+ it('should return empty array and set it in map when key does not exist', () => {
89
+ const map = new Map();
90
+ const arr = map.ensureArray('foo');
91
+ expect(Array.isArray(arr)).toBe(true);
92
+ expect(arr).toEqual([]);
93
+ expect(map.get('foo')).toBe(arr);
94
+ });
95
+ it('should work with non-string keys', () => {
96
+ const key = { id: 1 };
97
+ const map = new Map();
98
+ const arr = map.ensureArray(key);
99
+ expect(Array.isArray(arr)).toBe(true);
100
+ expect(arr).toEqual([]);
101
+ expect(map.get(key)).toBe(arr);
102
+ });
103
+ });
104
+ describe('with existing array values', () => {
105
+ it('should return existing array for existing key', () => {
106
+ const map = new Map();
107
+ map.set('bar', [1, 2]);
108
+ const arr = map.ensureArray('bar');
109
+ expect(arr).toEqual([1, 2]);
110
+ expect(map.get('bar')).toBe(arr);
111
+ });
112
+ });
113
+ describe('error handling', () => {
114
+ it('should throw TypeError when key is null or undefined', () => {
115
+ const map = new Map();
116
+ expect(() => map.ensureArray(null)).toThrow(TypeError);
117
+ expect(() => map.ensureArray(undefined)).toThrow(TypeError);
118
+ });
119
+ it('should throw TypeError when value for key is not an array', () => {
120
+ const map = new Map();
121
+ map.set('baz', 123);
122
+ expect(() => map.ensureArray('baz')).toThrow(TypeError);
123
+ });
124
+ });
125
+ });
126
+ describe('toObject()', () => {
127
+ describe('with string keys', () => {
128
+ it('should convert Map with string keys to object', () => {
129
+ const map = new Map([
130
+ ['a', 1],
131
+ ['b', 2],
132
+ ['c', 3],
133
+ ]);
134
+ const obj = map.toObject();
135
+ expect(obj).toEqual({ a: 1, b: 2, c: 3 });
136
+ expect(typeof obj).toBe('object');
137
+ });
138
+ it('should preserve insertion order for string keys', () => {
139
+ const map = new Map([
140
+ ['z', 26],
141
+ ['a', 1],
142
+ ['m', 13],
143
+ ]);
144
+ const obj = map.toObject();
145
+ const keys = Object.keys(obj);
146
+ expect(keys).toEqual(['z', 'a', 'm']);
147
+ });
148
+ });
149
+ describe('with numeric keys', () => {
150
+ it('should convert Map with numeric keys to object', () => {
151
+ const map = new Map([
152
+ [1, 'a'],
153
+ [2, 'b'],
154
+ [3, 'c'],
155
+ ]);
156
+ const obj = map.toObject();
157
+ expect(obj).toEqual({ 1: 'a', 2: 'b', 3: 'c' });
158
+ });
159
+ it('should convert Map with only numeric string keys', () => {
160
+ const map = new Map([
161
+ ['1', 'one'],
162
+ ['2', 'two'],
163
+ ['3', 'three'],
164
+ ]);
165
+ const obj = map.toObject();
166
+ expect(obj).toEqual({ 1: 'one', 2: 'two', 3: 'three' });
167
+ });
168
+ });
169
+ describe('with mixed key types', () => {
170
+ it('should convert Map with mixed string and numeric keys', () => {
171
+ const map = new Map([
172
+ ['name', 'Alice'],
173
+ [1, 'one'],
174
+ ['age', '30'],
175
+ [2, 'two'],
176
+ ]);
177
+ const obj = map.toObject();
178
+ expect(obj).toEqual({
179
+ name: 'Alice',
180
+ 1: 'one',
181
+ age: '30',
182
+ 2: 'two',
183
+ });
184
+ });
185
+ });
186
+ describe('with symbol keys', () => {
187
+ it('should convert Map with symbol keys to object', () => {
188
+ const sym1 = Symbol('key1');
189
+ const sym2 = Symbol('key2');
190
+ const map = new Map([
191
+ [sym1, 'value1'],
192
+ [sym2, 'value2'],
193
+ ]);
194
+ const obj = map.toObject();
195
+ expect(obj[sym1]).toBe('value1');
196
+ expect(obj[sym2]).toBe('value2');
197
+ });
198
+ });
199
+ describe('with various value types', () => {
200
+ it('should convert Map with various value types', () => {
201
+ const map = new Map([
202
+ ['string', 'hello'],
203
+ ['number', 42],
204
+ ['boolean', true],
205
+ ['null', null],
206
+ ['array', [1, 2, 3]],
207
+ ['object', { nested: 'value' }],
208
+ ]);
209
+ const obj = map.toObject();
210
+ expect(obj).toEqual({
211
+ string: 'hello',
212
+ number: 42,
213
+ boolean: true,
214
+ null: null,
215
+ array: [1, 2, 3],
216
+ object: { nested: 'value' },
217
+ });
218
+ });
219
+ });
220
+ describe('with empty map', () => {
221
+ it('should convert empty Map to empty object', () => {
222
+ const map = new Map();
223
+ const obj = map.toObject();
224
+ expect(obj).toEqual({});
225
+ });
226
+ });
227
+ describe('error handling', () => {
228
+ it('should throw TypeError when key is null', () => {
229
+ const map = new Map();
230
+ map.set('valid', 1);
231
+ map.set(null, 2);
232
+ expect(() => map.toObject()).toThrow(TypeError);
233
+ });
234
+ it('should throw TypeError when key is undefined', () => {
235
+ const map = new Map();
236
+ map.set('valid', 1);
237
+ map.set(undefined, 3);
238
+ expect(() => map.toObject()).toThrow(TypeError);
239
+ });
240
+ it('should throw TypeError when key is an object type', () => {
241
+ const map = new Map();
242
+ map.set({ invalid: 'key' }, 1);
243
+ expect(() => map.toObject()).toThrow(TypeError);
244
+ });
245
+ it('should throw TypeError when key is an array type', () => {
246
+ const map = new Map();
247
+ map.set(['invalid'], 'value');
248
+ expect(() => map.toObject()).toThrow(TypeError);
249
+ });
250
+ });
251
+ describe('with duplicate keys', () => {
252
+ it('should overwrite earlier keys with the same name', () => {
253
+ const map = new Map();
254
+ map.set('key', 1);
255
+ map.set('key', 2);
256
+ const obj = map.toObject();
257
+ expect(obj).toEqual({ key: 2 });
258
+ });
259
+ });
260
+ });
261
+ });
@@ -1,22 +1,113 @@
1
- export {};
2
1
  declare global {
3
2
  interface Object {
4
3
  /**
5
- * Creates a deep clone of the object.
6
- * @returns A deep copy of the original object.
4
+ * Creates a deep clone of the object using the structured clone algorithm.
5
+ * This preserves object types, reference integrity, and works with cyclic references.
6
+ *
7
+ * @template T The type of the object being cloned
8
+ * @this {T} The object to clone
9
+ * @returns {T} A deep copy of the original object with all nested objects and arrays cloned
10
+ * @throws {TypeError} If the object contains uncloneable values (functions, symbols, etc.)
11
+ *
12
+ * @example
13
+ * const original = { a: { b: 1 }, c: [2, 3] };
14
+ * const cloned = original.deepClone();
15
+ * cloned.a.b = 999;
16
+ * console.log(original.a.b); // 1 (original unchanged)
17
+ *
18
+ * @remarks
19
+ * Uses the native `structuredClone()` API which provides:
20
+ * - Support for Date, Map, Set, TypedArray objects
21
+ * - Preservation of prototype chains for built-in types
22
+ * - Proper handling of circular references
7
23
  */
8
24
  deepClone<T>(): T;
9
25
  /**
10
- * Deeply merges another object into the current object.
11
- * @param source - The object to merge from.
12
- * @returns The merged object (this).
26
+ * Deeply merges another object into the current object, recursively combining nested objects.
27
+ * Arrays and primitives are replaced (not merged). Modifies the current object in place.
28
+ *
29
+ * @template T The type of the object being merged into
30
+ * @this {T} The target object to merge into (will be modified)
31
+ * @param {object} source - The source object to merge from (must be a non-null object)
32
+ * @returns {T} The merged object (same as this)
33
+ * @throws {TypeError} If source is not a non-null object
34
+ *
35
+ * @example
36
+ * const target = { a: 1, b: { c: 2 } };
37
+ * const source = { b: { d: 3 }, e: 4 };
38
+ * target.deepMerge(source);
39
+ * // target: { a: 1, b: { c: 2, d: 3 }, e: 4 }
40
+ *
41
+ * @remarks
42
+ * - Primitive values in the source overwrite target values
43
+ * - Arrays in the source completely replace target arrays (not merged element-wise)
44
+ * - Only enumerable own properties are merged
45
+ * - The merge is performed recursively for nested objects
13
46
  */
14
47
  deepMerge<T>(source: any): T | typeof source;
15
48
  /**
16
- * Checks for deep equality with another object.
17
- * @param other - The object to compare with.
18
- * @returns True if deeply equal, false otherwise.
49
+ * Checks for deep equality with another object, comparing all nested properties recursively.
50
+ * Uses strict equality for primitives and deep comparison for objects.
51
+ *
52
+ * @template T The type of the object
53
+ * @this {T} The object to compare
54
+ * @param {unknown} other - The object to compare with
55
+ * @returns {boolean} True if both objects are deeply equal, false otherwise
56
+ *
57
+ * @example
58
+ * const obj1 = { a: { b: 1 }, c: [2, 3] };
59
+ * const obj2 = { a: { b: 1 }, c: [2, 3] };
60
+ * console.log(obj1.deepEquals(obj2)); // true
61
+ *
62
+ * const obj3 = { a: { b: 2 }, c: [2, 3] };
63
+ * console.log(obj1.deepEquals(obj3)); // false
64
+ *
65
+ * @remarks
66
+ * - Primitives are compared with strict equality (===)
67
+ * - Objects are compared property by property recursively
68
+ * - Array length and element order are considered
69
+ * - only enumerable own properties are compared
70
+ * - null and undefined are handled correctly
19
71
  */
20
72
  deepEquals(other: unknown): boolean;
73
+ /**
74
+ * Converts the object to a stable string representation with sorted keys.
75
+ * The resulting string is deterministic: the same object will always produce the same string.
76
+ *
77
+ * @returns {string} A stable string representation of the object
78
+ *
79
+ * @example
80
+ * const obj = { b: 2, a: 1 };
81
+ * obj.stableStringify(); // '{"a":1,"b":2}'
82
+ *
83
+ * const obj2 = { a: 1, b: 2 };
84
+ * obj2.stableStringify(); // '{"a":1,"b":2}' (same result, different key order)
85
+ *
86
+ * @remarks
87
+ * - Object keys are sorted alphabetically to ensure stable output
88
+ * - Arrays maintain their element order
89
+ * - Null and primitives are handled using JSON.stringify
90
+ */
91
+ stableStringify(): string;
92
+ /**
93
+ * Generates a stable hash of the object using FNV-1a algorithm.
94
+ * The hash is deterministic: the same object will always produce the same hash.
95
+ *
96
+ * @returns {string} A hexadecimal string representing the hash of the object
97
+ *
98
+ * @example
99
+ * const obj = { b: 2, a: 1 };
100
+ * obj.stableHash(); // '7a8c9f2b'
101
+ *
102
+ * const obj2 = { a: 1, b: 2 };
103
+ * obj2.stableHash(); // '7a8c9f2b' (same hash, different key order)
104
+ *
105
+ * @remarks
106
+ * - Uses FNV-1a (Fowler–Noll–Vo) hashing algorithm
107
+ * - The object is first converted to a stable string using stableStringify()
108
+ * - The hash is returned as a hexadecimal string
109
+ */
110
+ stableHash(): string;
21
111
  }
22
112
  }
113
+ export {};
@@ -1,20 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- const defineIfNotExists = (name, fn) => {
4
- const descriptor = Object.getOwnPropertyDescriptor(Object.prototype, name);
5
- if (!descriptor || descriptor.writable || descriptor.configurable) {
6
- Object.defineProperty(Object.prototype, name, {
7
- value: fn,
8
- enumerable: false,
9
- configurable: true,
10
- writable: true,
11
- });
12
- }
13
- };
14
- defineIfNotExists('deepClone', function () {
3
+ const core_utils_1 = require("../utils/core.utils");
4
+ /**
5
+ * @see Object.prototype.deepClone
6
+ */
7
+ (0, core_utils_1.defineIfNotExists)(Object.prototype, 'deepClone', function () {
15
8
  return structuredClone(this);
16
9
  });
17
- defineIfNotExists('deepMerge', function (source) {
10
+ /**
11
+ * @see Object.prototype.deepMerge
12
+ */
13
+ (0, core_utils_1.defineIfNotExists)(Object.prototype, 'deepMerge', function (source) {
18
14
  if (typeof source !== 'object' || source === null) {
19
15
  throw new TypeError('Source must be a non-null object');
20
16
  }
@@ -34,7 +30,10 @@ defineIfNotExists('deepMerge', function (source) {
34
30
  };
35
31
  return merge(this.deepClone(), source);
36
32
  });
37
- defineIfNotExists('deepEquals', function (other) {
33
+ /**
34
+ * @see Object.prototype.deepEquals
35
+ */
36
+ (0, core_utils_1.defineIfNotExists)(Object.prototype, 'deepEquals', function (other) {
38
37
  function eq(a, b) {
39
38
  // Functions: always false (even if code is the same)
40
39
  if (typeof a === 'function' || typeof b === 'function') {
@@ -86,3 +85,33 @@ defineIfNotExists('deepEquals', function (other) {
86
85
  }
87
86
  return eq(this, other);
88
87
  });
88
+ /**
89
+ * @see Object.prototype.stableStringify
90
+ */
91
+ (0, core_utils_1.defineIfNotExists)(Object.prototype, 'stableStringify', function () {
92
+ const stringify = (v) => {
93
+ if (v === null || typeof v !== 'object') {
94
+ return JSON.stringify(v);
95
+ }
96
+ if (Array.isArray(v)) {
97
+ return '[' + v.map(stringify).join(',') + ']';
98
+ }
99
+ const obj = v;
100
+ const keys = Object.keys(obj).sort();
101
+ const entries = keys.map((key) => JSON.stringify(key) + ':' + stringify(obj[key]));
102
+ return '{' + entries.join(',') + '}';
103
+ };
104
+ return stringify(this);
105
+ });
106
+ /**
107
+ * @see Object.prototype.stableHash
108
+ */
109
+ (0, core_utils_1.defineIfNotExists)(Object.prototype, 'stableHash', function () {
110
+ const str = this.stableStringify();
111
+ let hash = 2166136261;
112
+ for (let i = 0; i < str.length; i++) {
113
+ hash ^= str.charCodeAt(i);
114
+ hash = Math.imul(hash, 16777619);
115
+ }
116
+ return (hash >>> 0).toString(16);
117
+ });
@@ -0,0 +1 @@
1
+ import './object-prototype';
@@ -0,0 +1,184 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ require("./object-prototype");
4
+ describe('Object.prototype', () => {
5
+ describe('deepClone()', () => {
6
+ it('should deeply clone a plain object', () => {
7
+ const obj = { a: 1, b: { c: 2 } };
8
+ const clone = structuredClone(obj);
9
+ expect(clone).toEqual(obj);
10
+ expect(clone).not.toBe(obj);
11
+ expect(clone.b).not.toBe(obj.b);
12
+ });
13
+ it('should deeply clone an array', () => {
14
+ const arr = [{ a: 1 }, { b: 2 }];
15
+ const clone = arr.deepClone();
16
+ expect(clone).toEqual(arr);
17
+ expect(clone).not.toBe(arr);
18
+ expect(clone[0]).not.toBe(arr[0]);
19
+ });
20
+ });
21
+ describe('deepMerge()', () => {
22
+ it('should deeply merge two objects', () => {
23
+ const obj = { a: 1, b: { c: 2 } };
24
+ const source = { b: { d: 3 }, e: 4 };
25
+ const merged = obj.deepMerge(source);
26
+ expect(merged).toEqual({ a: 1, b: { c: 2, d: 3 }, e: 4 });
27
+ console.log(merged);
28
+ });
29
+ it('should overwrite primitive values', () => {
30
+ const obj = { a: 1, b: 2 };
31
+ const merged = obj.deepMerge({ b: 3 });
32
+ expect(merged).toEqual({ a: 1, b: 3 });
33
+ console.log(merged);
34
+ });
35
+ describe('error handling', () => {
36
+ it('should throw if source is not an object', () => {
37
+ expect(() => ({}).deepMerge(null)).toThrowError(TypeError);
38
+ expect(() => ({}).deepMerge(42)).toThrowError(TypeError);
39
+ });
40
+ });
41
+ });
42
+ describe('deepEquals()', () => {
43
+ describe('with objects', () => {
44
+ it('should return true for objects with same keys and values, regardless of key order', () => {
45
+ expect({ a: 2, b: 3 }.deepEquals({ b: 3, a: 2 })).toBe(true);
46
+ expect({ a: 2, b: { c: 4 } }.deepEquals({ b: { c: 4 }, a: 2 })).toBe(true);
47
+ });
48
+ it('should return false for objects with different values', () => {
49
+ expect({ a: 2, b: 3 }.deepEquals({ a: 2, b: 4 })).toBe(false);
50
+ expect({ a: 2, b: { c: 4 } }.deepEquals({ a: 2, b: { c: 5 } })).toBe(false);
51
+ });
52
+ it('should return false for objects with different keys', () => {
53
+ expect({ a: 2 }.deepEquals({ b: 2 })).toBe(false);
54
+ });
55
+ it('should return true for empty objects', () => {
56
+ expect({}.deepEquals({})).toBe(true);
57
+ });
58
+ it('should return false for objects with different key counts', () => {
59
+ expect({ a: 1 }.deepEquals({ a: 1, b: 2 })).toBe(false);
60
+ });
61
+ });
62
+ describe('with arrays', () => {
63
+ it('should return true for arrays with same elements in same order', () => {
64
+ expect([1, 2, 3].deepEquals([1, 2, 3])).toBe(true);
65
+ expect([1, [2, 3]].deepEquals([1, [2, 3]])).toBe(true);
66
+ });
67
+ it('should return false for arrays with different elements or order', () => {
68
+ expect([1, 2, 3].deepEquals([1, 3, 2])).toBe(false);
69
+ expect([1, [2, 3]].deepEquals([1, [3, 2]])).toBe(false);
70
+ });
71
+ it('should return true for empty arrays', () => {
72
+ expect([].deepEquals([])).toBe(true);
73
+ });
74
+ it('should return false for arrays with different lengths', () => {
75
+ expect([1].deepEquals([1, 2])).toBe(false);
76
+ });
77
+ });
78
+ describe('with special values', () => {
79
+ it('should return true for NaN deepEquals NaN', () => {
80
+ expect({ a: NaN }.deepEquals({ a: NaN })).toBe(true);
81
+ expect([NaN].deepEquals([NaN])).toBe(true);
82
+ });
83
+ it('should return false for +0 and -0', () => {
84
+ expect({ a: +0 }.deepEquals({ a: -0 })).toBe(false);
85
+ expect([+0].deepEquals([-0])).toBe(false);
86
+ });
87
+ it('should return false for objects with undefined vs missing keys', () => {
88
+ expect({ a: undefined }.deepEquals({})).toBe(false);
89
+ expect({}.deepEquals({ a: undefined })).toBe(false);
90
+ });
91
+ it('should return false for objects with functions, even if functions are equal', () => {
92
+ expect({ a: () => 1 }.deepEquals({ a: () => 1 })).toBe(false);
93
+ });
94
+ });
95
+ describe('with nested structures', () => {
96
+ it('should return true for nested structures deeply equal', () => {
97
+ expect({ a: [1, { b: 2 }] }.deepEquals({ a: [1, { b: 2 }] })).toBe(true);
98
+ });
99
+ it('should return false for nested structures not deeply equal', () => {
100
+ expect({ a: [1, { b: 2 }] }.deepEquals({ a: [1, { b: 3 }] })).toBe(false);
101
+ });
102
+ });
103
+ describe('type mismatches', () => {
104
+ it('should return false for array vs object', () => {
105
+ expect([].deepEquals({})).toBe(false);
106
+ expect({}.deepEquals([])).toBe(false);
107
+ });
108
+ });
109
+ });
110
+ describe('stableStringify()', () => {
111
+ it('should stringify plain objects with sorted keys', () => {
112
+ const obj = { b: 2, a: 1 };
113
+ expect(obj.stableStringify()).toBe('{"a":1,"b":2}');
114
+ });
115
+ it('should produce same string regardless of key order', () => {
116
+ const obj1 = { b: 2, a: 1, c: 3 };
117
+ const obj2 = { c: 3, a: 1, b: 2 };
118
+ expect(obj1.stableStringify()).toBe(obj2.stableStringify());
119
+ });
120
+ it('should stringify arrays with element order preserved', () => {
121
+ const arr = [1, 2, 3];
122
+ expect(arr.stableStringify()).toBe('[1,2,3]');
123
+ });
124
+ it('should handle nested objects', () => {
125
+ const obj = { b: { d: 4, c: 3 }, a: 1 };
126
+ expect(obj.stableStringify()).toBe('{"a":1,"b":{"c":3,"d":4}}');
127
+ });
128
+ it('should handle arrays within objects', () => {
129
+ const obj = { a: [1, 2], b: 2 };
130
+ expect(obj.stableStringify()).toBe('{"a":[1,2],"b":2}');
131
+ });
132
+ it('should handle empty objects and arrays', () => {
133
+ expect({}.stableStringify()).toBe('{}');
134
+ expect([].stableStringify()).toBe('[]');
135
+ });
136
+ it('should handle undefined values in objects', () => {
137
+ const obj = { a: undefined, b: 1 };
138
+ expect(obj.stableStringify()).toBe('{"a":undefined,"b":1}');
139
+ });
140
+ });
141
+ describe('stableHash()', () => {
142
+ it('should generate same hash for objects with different key order', () => {
143
+ const obj1 = { b: 2, a: 1 };
144
+ const obj2 = { a: 1, b: 2 };
145
+ expect(obj1.stableHash()).toBe(obj2.stableHash());
146
+ });
147
+ it('should generate different hashes for different objects', () => {
148
+ const obj1 = { a: 1, b: 2 };
149
+ const obj2 = { a: 1, b: 3 };
150
+ expect(obj1.stableHash()).not.toBe(obj2.stableHash());
151
+ });
152
+ it('should generate consistent hashes', () => {
153
+ const obj = { a: 1, b: 2 };
154
+ const hash1 = obj.stableHash();
155
+ const hash2 = obj.stableHash();
156
+ expect(hash1).toBe(hash2);
157
+ });
158
+ it('should generate hex string', () => {
159
+ const obj = { a: 1 };
160
+ const hash = obj.stableHash();
161
+ expect(/^[0-9a-f]+$/.test(hash)).toBe(true);
162
+ });
163
+ it('should handle arrays', () => {
164
+ const arr1 = [1, 2, 3];
165
+ const arr2 = [1, 2, 3];
166
+ expect(arr1.stableHash()).toBe(arr2.stableHash());
167
+ });
168
+ it('should handle different arrays', () => {
169
+ const arr1 = [1, 2, 3];
170
+ const arr2 = [3, 2, 1];
171
+ expect(arr1.stableHash()).not.toBe(arr2.stableHash());
172
+ });
173
+ it('should handle nested structures', () => {
174
+ const obj1 = { a: [1, { b: 2 }], c: 3 };
175
+ const obj2 = { c: 3, a: [1, { b: 2 }] };
176
+ expect(obj1.stableHash()).toBe(obj2.stableHash());
177
+ });
178
+ it('should handle empty objects', () => {
179
+ const obj1 = {};
180
+ const obj2 = {};
181
+ expect(obj1.stableHash()).toBe(obj2.stableHash());
182
+ });
183
+ });
184
+ });