@tim-code/my-util 0.4.9 → 0.5.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tim-code/my-util",
3
- "version": "0.4.9",
3
+ "version": "0.5.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "author": "Tim Sprowl",
package/src/find.js CHANGED
@@ -5,7 +5,7 @@ export function findClosestAbs(array, desired, { key, cutoff = Infinity } = {})
5
5
  const element = array[i]
6
6
  const value = key(element, i, array)
7
7
  const diff = Math.abs(value - desired)
8
- if (diff < cutoff) {
8
+ if (diff < cutoff || (diff === cutoff && !closest)) {
9
9
  closest = element
10
10
  cutoff = diff
11
11
  }
@@ -14,7 +14,7 @@ export function findClosestAbs(array, desired, { key, cutoff = Infinity } = {})
14
14
  for (const element of array) {
15
15
  const value = element[key]
16
16
  const diff = Math.abs(value - desired)
17
- if (diff < cutoff) {
17
+ if (diff < cutoff || (diff === cutoff && !closest)) {
18
18
  closest = element
19
19
  cutoff = diff
20
20
  }
@@ -22,7 +22,7 @@ export function findClosestAbs(array, desired, { key, cutoff = Infinity } = {})
22
22
  } else {
23
23
  for (const value of array) {
24
24
  const diff = Math.abs(value - desired)
25
- if (diff < cutoff) {
25
+ if (diff < cutoff || (diff === cutoff && !closest)) {
26
26
  closest = value
27
27
  cutoff = diff
28
28
  }
@@ -37,7 +37,7 @@ export function findClosestLT(array, desired, { key, cutoff = -Infinity } = {})
37
37
  for (let i = 0; i < array.length; i++) {
38
38
  const element = array[i]
39
39
  const value = key(element, i, array)
40
- if (value < desired && value > cutoff) {
40
+ if (value < desired && (value > cutoff || (value === cutoff && !closest))) {
41
41
  closest = element
42
42
  cutoff = value
43
43
  }
@@ -45,14 +45,14 @@ export function findClosestLT(array, desired, { key, cutoff = -Infinity } = {})
45
45
  } else if (typeof key === "number" || typeof key === "string") {
46
46
  for (const element of array) {
47
47
  const value = element[key]
48
- if (value < desired && value > cutoff) {
48
+ if (value < desired && (value > cutoff || (value === cutoff && !closest))) {
49
49
  closest = element
50
50
  cutoff = value
51
51
  }
52
52
  }
53
53
  } else {
54
54
  for (const value of array) {
55
- if (value < desired && value > cutoff) {
55
+ if (value < desired && (value > cutoff || (value === cutoff && !closest))) {
56
56
  closest = value
57
57
  cutoff = value
58
58
  }
@@ -67,7 +67,7 @@ export function findClosestLTE(array, desired, { key, cutoff = -Infinity } = {})
67
67
  for (let i = 0; i < array.length; i++) {
68
68
  const element = array[i]
69
69
  const value = key(element, i, array)
70
- if (value <= desired && value > cutoff) {
70
+ if (value <= desired && (value > cutoff || (value === cutoff && !closest))) {
71
71
  closest = element
72
72
  cutoff = value
73
73
  }
@@ -75,14 +75,14 @@ export function findClosestLTE(array, desired, { key, cutoff = -Infinity } = {})
75
75
  } else if (typeof key === "number" || typeof key === "string") {
76
76
  for (const element of array) {
77
77
  const value = element[key]
78
- if (value <= desired && value > cutoff) {
78
+ if (value <= desired && (value > cutoff || (value === cutoff && !closest))) {
79
79
  closest = element
80
80
  cutoff = value
81
81
  }
82
82
  }
83
83
  } else {
84
84
  for (const value of array) {
85
- if (value <= desired && value > cutoff) {
85
+ if (value <= desired && (value > cutoff || (value === cutoff && !closest))) {
86
86
  closest = value
87
87
  cutoff = value
88
88
  }
@@ -97,7 +97,7 @@ export function findClosestGT(array, desired, { key, cutoff = Infinity } = {}) {
97
97
  for (let i = 0; i < array.length; i++) {
98
98
  const element = array[i]
99
99
  const value = key(element, i, array)
100
- if (value > desired && value < cutoff) {
100
+ if (value > desired && (value < cutoff || (value === cutoff && !closest))) {
101
101
  closest = element
102
102
  cutoff = value
103
103
  }
@@ -105,14 +105,14 @@ export function findClosestGT(array, desired, { key, cutoff = Infinity } = {}) {
105
105
  } else if (typeof key === "number" || typeof key === "string") {
106
106
  for (const element of array) {
107
107
  const value = element[key]
108
- if (value > desired && value < cutoff) {
108
+ if (value > desired && (value < cutoff || (value === cutoff && !closest))) {
109
109
  closest = element
110
110
  cutoff = value
111
111
  }
112
112
  }
113
113
  } else {
114
114
  for (const value of array) {
115
- if (value > desired && value < cutoff) {
115
+ if (value > desired && (value < cutoff || (value === cutoff && !closest))) {
116
116
  closest = value
117
117
  cutoff = value
118
118
  }
@@ -127,7 +127,7 @@ export function findClosestGTE(array, desired, { key, cutoff = Infinity } = {})
127
127
  for (let i = 0; i < array.length; i++) {
128
128
  const element = array[i]
129
129
  const value = key(element, i, array)
130
- if (value >= desired && value < cutoff) {
130
+ if (value >= desired && (value < cutoff || (value === cutoff && !closest))) {
131
131
  closest = element
132
132
  cutoff = value
133
133
  }
@@ -135,14 +135,14 @@ export function findClosestGTE(array, desired, { key, cutoff = Infinity } = {})
135
135
  } else if (typeof key === "number" || typeof key === "string") {
136
136
  for (const element of array) {
137
137
  const value = element[key]
138
- if (value >= desired && value < cutoff) {
138
+ if (value >= desired && (value < cutoff || (value === cutoff && !closest))) {
139
139
  closest = element
140
140
  cutoff = value
141
141
  }
142
142
  }
143
143
  } else {
144
144
  for (const value of array) {
145
- if (value >= desired && value < cutoff) {
145
+ if (value >= desired && (value < cutoff || (value === cutoff && !closest))) {
146
146
  closest = value
147
147
  cutoff = value
148
148
  }
@@ -165,7 +165,7 @@ export function findClosestGTE(array, desired, { key, cutoff = Infinity } = {})
165
165
  * If a function, called with the element, index and array (same as .map() callback) to produce the value to sort on.
166
166
  * @param {string=} options.comparator "abs", "lt", "lte", "gt", "gte", "abs". Default is "abs" which implies T is number.
167
167
  * @param {V=} options.cutoff If specified, sets a initial constraint on how close the found value must be.
168
- * For example, if used with "lt", the found element would need to be greater than the cutoff but still less than the desired value.
168
+ * For example, if used with "lt", the found element would need to be greater than or equal to the cutoff but still less than the desired value.
169
169
  * If used with "abs", the found element would need to have a difference with the desired value less than the cutoff.
170
170
  * @returns {T|undefined}
171
171
  */
package/src/find.test.js CHANGED
@@ -45,7 +45,8 @@ describe("findClosestAbs", () => {
45
45
 
46
46
  it("respects cutoff", () => {
47
47
  expect(findClosestAbs([1, 5, 9], 6, { cutoff: 2 })).toBe(5)
48
- expect(findClosestAbs([1, 5, 9], 6, { cutoff: 1 })).toBeUndefined()
48
+ expect(findClosestAbs([1, 5, 9], 6, { cutoff: 1 })).toBe(5)
49
+ expect(findClosestAbs([1, 5, 9], 6, { cutoff: 0 })).toBeUndefined()
49
50
  })
50
51
  })
51
52
 
@@ -81,7 +82,8 @@ describe("findClosestLT", () => {
81
82
 
82
83
  it("respects cutoff", () => {
83
84
  expect(findClosestLT([1, 5, 9], 6, { cutoff: 4 })).toBe(5)
84
- expect(findClosestLT([1, 5, 9], 6, { cutoff: 5 })).toBeUndefined()
85
+ expect(findClosestLT([1, 5, 9], 6, { cutoff: 5 })).toBe(5)
86
+ expect(findClosestLT([1, 5, 9], 6, { cutoff: 6 })).toBe(undefined)
85
87
  })
86
88
  })
87
89
 
@@ -118,7 +120,8 @@ describe("findClosestLTE", () => {
118
120
 
119
121
  it("respects cutoff", () => {
120
122
  expect(findClosestLTE([1, 5, 9], 6, { cutoff: 4 })).toBe(5)
121
- expect(findClosestLTE([1, 5, 9], 6, { cutoff: 5 })).toBeUndefined()
123
+ expect(findClosestLTE([1, 5, 9], 6, { cutoff: 5 })).toBe(5)
124
+ expect(findClosestLTE([1, 5, 9], 6, { cutoff: 6 })).toBe(undefined)
122
125
  })
123
126
  })
124
127
 
@@ -153,8 +156,9 @@ describe("findClosestGT", () => {
153
156
  })
154
157
 
155
158
  it("respects cutoff", () => {
156
- expect(findClosestGT([1, 5, 9], 6, { cutoff: 8 })).toBeUndefined()
157
- expect(findClosestGT([1, 5, 9], 4, { cutoff: 8 })).toBe(5)
159
+ expect(findClosestGTE([1, 5, 9], 6, { cutoff: 10 })).toBe(9)
160
+ expect(findClosestGTE([1, 5, 9], 6, { cutoff: 9 })).toBe(9)
161
+ expect(findClosestGTE([1, 5, 9], 6, { cutoff: 8 })).toBeUndefined()
158
162
  })
159
163
  })
160
164
 
@@ -189,8 +193,9 @@ describe("findClosestGTE", () => {
189
193
  })
190
194
 
191
195
  it("respects cutoff", () => {
196
+ expect(findClosestGTE([1, 5, 9], 6, { cutoff: 10 })).toBe(9)
197
+ expect(findClosestGTE([1, 5, 9], 6, { cutoff: 9 })).toBe(9)
192
198
  expect(findClosestGTE([1, 5, 9], 6, { cutoff: 8 })).toBeUndefined()
193
- expect(findClosestGTE([1, 5, 9], 4, { cutoff: 8 })).toBe(5)
194
199
  })
195
200
  })
196
201
 
package/src/object.js CHANGED
@@ -89,3 +89,37 @@ export function deepMerge(target, ...sources) {
89
89
  }
90
90
  return target
91
91
  }
92
+
93
+ /**
94
+ * Deeply compares two values to determine if they are equal.
95
+ * Objects and arrays are compared recursively by their properties and elements.
96
+ * Primitives are compared with strict equality.
97
+ * Caveats:
98
+ * Does not check class: [1] is considered equal to {0: 1}.
99
+ * Any Symbol keys in the arguments are ignored (Object.keys only returns string keys).
100
+ * @param {any} a The first value to compare.
101
+ * @param {any} b The second value to compare.
102
+ * @returns {boolean} True if the values are deeply equal, false otherwise.
103
+ */
104
+ export function deepEqual(a, b) {
105
+ if (a === b) {
106
+ return true
107
+ }
108
+ if (typeof a !== "object" || typeof b !== "object" || !a || !b) {
109
+ return false
110
+ }
111
+ const keysA = Object.keys(a)
112
+ const keysB = Object.keys(b)
113
+ if (keysA.length !== keysB.length) {
114
+ return false
115
+ }
116
+ for (const key of keysA) {
117
+ if (!Object.hasOwn(b, key)) {
118
+ return false
119
+ }
120
+ if (!deepEqual(a[key], b[key])) {
121
+ return false
122
+ }
123
+ }
124
+ return true
125
+ }
@@ -1,6 +1,13 @@
1
1
  /* eslint-disable no-restricted-syntax */
2
2
  import { jest } from "@jest/globals"
3
- import { deepMerge, deleteUndefinedValues, like, mutateValues, via } from "./object.js"
3
+ import {
4
+ deepEqual,
5
+ deepMerge,
6
+ deleteUndefinedValues,
7
+ like,
8
+ mutateValues,
9
+ via,
10
+ } from "./object.js"
4
11
 
5
12
  describe("mutateValues", () => {
6
13
  it("mutates values in the object using the callback", () => {
@@ -257,3 +264,106 @@ describe("deepMerge", () => {
257
264
  expect(result).toBe(target)
258
265
  })
259
266
  })
267
+
268
+ describe("deepEqual", () => {
269
+ it("returns true for strictly equal primitives", () => {
270
+ expect(deepEqual(1, 1)).toBe(true)
271
+ expect(deepEqual("foo", "foo")).toBe(true)
272
+ expect(deepEqual(true, true)).toBe(true)
273
+ expect(deepEqual(null, null)).toBe(true)
274
+ expect(deepEqual(undefined, undefined)).toBe(true)
275
+ })
276
+
277
+ it("returns false for different primitives", () => {
278
+ expect(deepEqual(1, 2)).toBe(false)
279
+ expect(deepEqual("foo", "bar")).toBe(false)
280
+ expect(deepEqual(true, false)).toBe(false)
281
+ expect(deepEqual(null, undefined)).toBe(false)
282
+ })
283
+
284
+ it("returns true for deeply equal objects", () => {
285
+ expect(deepEqual({ a: 1, b: 2 }, { a: 1, b: 2 })).toBe(true)
286
+ expect(deepEqual({ a: { b: 2 } }, { a: { b: 2 } })).toBe(true)
287
+ expect(deepEqual({}, {})).toBe(true)
288
+ })
289
+
290
+ it("returns false for objects with different keys or values", () => {
291
+ expect(deepEqual({ a: 1 }, { a: 2 })).toBe(false)
292
+ expect(deepEqual({ a: 1 }, { b: 1 })).toBe(false)
293
+ expect(deepEqual({ a: 1 }, {})).toBe(false)
294
+ expect(deepEqual({}, { a: 1 })).toBe(false)
295
+ })
296
+
297
+ it("returns true for deeply equal arrays", () => {
298
+ expect(deepEqual([1, 2, 3], [1, 2, 3])).toBe(true)
299
+ expect(deepEqual([], [])).toBe(true)
300
+ expect(deepEqual([[1], [2]], [[1], [2]])).toBe(true)
301
+ })
302
+
303
+ it("returns false for arrays with different elements or lengths", () => {
304
+ expect(deepEqual([1, 2], [1, 2, 3])).toBe(false)
305
+ expect(deepEqual([1, 2, 3], [1, 2])).toBe(false)
306
+ expect(deepEqual([1, 2, 3], [3, 2, 1])).toBe(false)
307
+ })
308
+
309
+ it("returns true if one is array and one is object", () => {
310
+ expect(deepEqual([1, 2], { 0: 1, 1: 2 })).toBe(true)
311
+ expect(deepEqual({ 0: 1, 1: 2 }, [1, 2])).toBe(true)
312
+ })
313
+
314
+ it("returns true for objects with same keys in different order", () => {
315
+ expect(deepEqual({ a: 1, b: 2 }, { b: 2, a: 1 })).toBe(true)
316
+ })
317
+
318
+ it("returns true for nested objects and arrays", () => {
319
+ const a = { foo: [1, { bar: 2 }], baz: { qux: [3] } }
320
+ const b = { foo: [1, { bar: 2 }], baz: { qux: [3] } }
321
+ expect(deepEqual(a, b)).toBe(true)
322
+ })
323
+
324
+ it("returns false for nested difference", () => {
325
+ const a = { foo: [1, { bar: 2 }], baz: { qux: [3] } }
326
+ const b = { foo: [1, { bar: 3 }], baz: { qux: [3] } }
327
+ expect(deepEqual(a, b)).toBe(false)
328
+ })
329
+
330
+ it("returns false if keys differ in nested objects", () => {
331
+ expect(deepEqual({ a: { b: 1 } }, { a: { c: 1 } })).toBe(false)
332
+ })
333
+
334
+ it("returns false if one is null or undefined and the other is object", () => {
335
+ expect(deepEqual(null, {})).toBe(false)
336
+ expect(deepEqual({}, null)).toBe(false)
337
+ expect(deepEqual(undefined, {})).toBe(false)
338
+ expect(deepEqual({}, undefined)).toBe(false)
339
+ })
340
+
341
+ it("returns true for self-references (same object)", () => {
342
+ const obj = { a: 1 }
343
+ expect(deepEqual(obj, obj)).toBe(true)
344
+ const arr = [1, 2]
345
+ expect(deepEqual(arr, arr)).toBe(true)
346
+ })
347
+
348
+ it("returns false for objects with different number of keys", () => {
349
+ expect(deepEqual({ a: 1, b: 2 }, { a: 1 })).toBe(false)
350
+ expect(deepEqual({ a: 1 }, { a: 1, b: 2 })).toBe(false)
351
+ })
352
+
353
+ it("returns true if object keys match but values are objects of different types", () => {
354
+ expect(deepEqual({ a: [1, 2] }, { a: { 0: 1, 1: 2 } })).toBe(true)
355
+ })
356
+
357
+ it("returns false for objects with missing keys", () => {
358
+ expect(deepEqual({ a: 1, b: 2 }, { a: 1 })).toBe(false)
359
+ })
360
+
361
+ it("returns true for objects with undefined values if both have them", () => {
362
+ expect(deepEqual({ a: undefined }, { a: undefined })).toBe(true)
363
+ })
364
+
365
+ it("returns false if one object has undefined key and the other doesn't", () => {
366
+ expect(deepEqual({ a: undefined }, {})).toBe(false)
367
+ expect(deepEqual({}, { a: undefined })).toBe(false)
368
+ })
369
+ })