@tim-code/my-util 0.5.3 → 0.5.5

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.5.3",
3
+ "version": "0.5.5",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "author": "Tim Sprowl",
package/src/array.js CHANGED
@@ -20,12 +20,38 @@ export function chunk(array, chunkSize = array.length) {
20
20
  }
21
21
 
22
22
  /**
23
- * Shorthand for returning all unique elements in an array.
23
+ * Returns all unique elements in an array, or alternatively by checking a key's value or function's result.
24
24
  * @param {Array} array
25
+ * @param {Object} $1
26
+ * @param {string|number|Function=} $1.key
27
+ * If a function, calls the provided function on an element to get the value to check for uniqueness.
28
+ * If a string or number, checks each element's value at key for uniqueness.
25
29
  * @returns {Array}
26
30
  */
27
- export function unique(array) {
28
- return [...new Set(array)]
31
+ export function unique(array, { key } = {}) {
32
+ const seen = new Set()
33
+ const result = []
34
+ if (typeof key === "function") {
35
+ for (let i = 0; i < array.length; i++) {
36
+ const element = array[i]
37
+ const value = key(element, i, array)
38
+ if (!seen.has(value)) {
39
+ seen.add(value)
40
+ result.push(element)
41
+ }
42
+ }
43
+ } else if (typeof key === "string" || typeof key === "number") {
44
+ for (const element of array) {
45
+ const value = element[key]
46
+ if (!seen.has(value)) {
47
+ seen.add(value)
48
+ result.push(element)
49
+ }
50
+ }
51
+ } else {
52
+ return [...new Set(array)]
53
+ }
54
+ return result
29
55
  }
30
56
 
31
57
  // sorts undefined and null to the end if applicable
package/src/array.test.js CHANGED
@@ -68,6 +68,98 @@ describe("unique", () => {
68
68
  const b = {}
69
69
  expect(unique([a, b, a])).toEqual([a, b])
70
70
  })
71
+
72
+ it("returns unique elements by key (string)", () => {
73
+ const arr = [
74
+ { id: 1, name: "a" },
75
+ { id: 2, name: "b" },
76
+ { id: 1, name: "c" },
77
+ { id: 3, name: "d" },
78
+ { id: 2, name: "e" },
79
+ ]
80
+ expect(unique(arr, { key: "id" })).toEqual([
81
+ { id: 1, name: "a" },
82
+ { id: 2, name: "b" },
83
+ { id: 3, name: "d" },
84
+ ])
85
+ })
86
+
87
+ it("returns unique elements by key (number)", () => {
88
+ const arr = [
89
+ { 0: "a", v: 1 },
90
+ { 0: "b", v: 2 },
91
+ { 0: "a", v: 3 },
92
+ { 0: "c", v: 4 },
93
+ ]
94
+ expect(unique(arr, { key: 0 })).toEqual([
95
+ { 0: "a", v: 1 },
96
+ { 0: "b", v: 2 },
97
+ { 0: "c", v: 4 },
98
+ ])
99
+ })
100
+
101
+ it("returns unique elements by function", () => {
102
+ const arr = [
103
+ { id: 1, name: "a" },
104
+ { id: 2, name: "b" },
105
+ { id: 1, name: "c" },
106
+ { id: 3, name: "d" },
107
+ { id: 2, name: "e" },
108
+ ]
109
+ expect(
110
+ unique(arr, {
111
+ key: (el) => el.id % 2, // group by odd/even id
112
+ })
113
+ ).toEqual([
114
+ { id: 1, name: "a" }, // id % 2 === 1
115
+ { id: 2, name: "b" }, // id % 2 === 0
116
+ ])
117
+ })
118
+
119
+ it("returns unique elements by function using index and array", () => {
120
+ const arr = ["a", "b", "c", "a"]
121
+ expect(
122
+ unique(arr, {
123
+ key: (el, i, array) => array.indexOf(el), // only first occurrence is unique
124
+ })
125
+ ).toEqual(["a", "b", "c"])
126
+ })
127
+
128
+ it("returns unique elements by key when some elements lack the key", () => {
129
+ const arr = [{ id: 1 }, {}, { id: 1 }, { id: 2 }, {}]
130
+ expect(unique(arr, { key: "id" })).toEqual([{ id: 1 }, {}, { id: 2 }])
131
+ })
132
+
133
+ it("returns unique elements by function when function returns undefined/null", () => {
134
+ const arr = [{ id: 1 }, {}, { id: 2 }, { id: null }, {}]
135
+ expect(
136
+ unique(arr, {
137
+ key: (el) => el.id,
138
+ })
139
+ ).toEqual([{ id: 1 }, {}, { id: 2 }, { id: null }])
140
+ })
141
+
142
+ it("returns unique elements by key when key value is undefined/null", () => {
143
+ const arr = [{ id: 1 }, { id: undefined }, { id: 2 }, { id: null }, { id: undefined }]
144
+ expect(unique(arr, { key: "id" })).toEqual([
145
+ { id: 1 },
146
+ { id: undefined },
147
+ { id: 2 },
148
+ { id: null },
149
+ ])
150
+ })
151
+
152
+ it("returns unique elements for primitive array if key is not provided", () => {
153
+ expect(unique([1, 2, 2, 3], {})).toEqual([1, 2, 3])
154
+ expect(unique([1, 2, 2, 3], { key: undefined })).toEqual([1, 2, 3])
155
+ })
156
+
157
+ it("returns unique elements for object array if key is not provided", () => {
158
+ const a = { x: 1 }
159
+ const b = { x: 2 }
160
+ expect(unique([a, b, a], {})).toEqual([a, b])
161
+ expect(unique([a, b, a], { key: undefined })).toEqual([a, b])
162
+ })
71
163
  })
72
164
 
73
165
  describe("ascending", () => {
package/src/object.js CHANGED
@@ -135,16 +135,14 @@ export function deepMerge(target, ...sources) {
135
135
  }
136
136
 
137
137
  /**
138
- * Produces a deep merge of a deep copy of each source object. See deepCopy() and deepMerge() documentation for caveats.
138
+ * Merges a deep copy of each source object into target. See deepCopy() and deepMerge() documentation for caveats.
139
+ * @param {Object} target The target object that will receive the merged properties.
139
140
  * @param {...Object} sources The source objects whose properties will be merged into the returned object
140
141
  * @returns {Object}
141
142
  */
142
- export function deepMergeCopy(...sources) {
143
- if (!sources.length) {
144
- return {}
145
- }
143
+ export function deepMergeCopy(target, ...sources) {
146
144
  const copies = sources.map(deepCopy)
147
- const result = deepMerge(...copies)
145
+ const result = deepMerge(target, ...copies)
148
146
  return result
149
147
  }
150
148
 
@@ -423,35 +423,70 @@ describe("deepMerge", () => {
423
423
 
424
424
  // --- deepMergeCopy ---
425
425
  describe("deepMergeCopy", () => {
426
- it("deeply merges deep copies of sources", () => {
427
- const s1 = { a: { b: 1 } }
428
- const s2 = { a: { c: 2 } }
429
- const merged = deepMergeCopy(s1, s2)
430
- expect(merged).toEqual({ a: { b: 1, c: 2 } })
431
- expect(merged).not.toBe(s1)
432
- expect(merged).not.toBe(s2)
426
+ it("deeply merges deep copies of sources into the target", () => {
427
+ const target = { a: { b: 1 } }
428
+ const s1 = { a: { c: 2 } }
429
+ const s2 = { a: { d: 3 } }
430
+ const origTarget = JSON.stringify(target)
431
+ const merged = deepMergeCopy(target, s1, s2)
432
+ expect(merged).toEqual({ a: { b: 1, c: 2, d: 3 } })
433
+ expect(merged).toBe(target)
433
434
  expect(merged.a).not.toBe(s1.a)
434
435
  expect(merged.a).not.toBe(s2.a)
436
+ expect(JSON.stringify(target)).not.toBe(origTarget)
437
+ expect(s1).toEqual({ a: { c: 2 } })
438
+ expect(s2).toEqual({ a: { d: 3 } })
435
439
  })
436
440
 
437
441
  it("does not mutate source objects", () => {
438
- const s1 = { a: 1 }
439
- const s2 = { b: 2 }
442
+ const target = { a: 1 }
443
+ const s1 = { b: 2 }
444
+ const s2 = { c: 3 }
440
445
  const orig1 = JSON.stringify(s1)
441
446
  const orig2 = JSON.stringify(s2)
442
- deepMergeCopy(s1, s2)
447
+ deepMergeCopy(target, s1, s2)
443
448
  expect(JSON.stringify(s1)).toBe(orig1)
444
449
  expect(JSON.stringify(s2)).toBe(orig2)
445
450
  })
446
451
 
447
- it("handles arrays and primitives", () => {
448
- const s1 = { a: [1, 2] }
449
- const s2 = { a: [3, 4], b: 5 }
450
- expect(deepMergeCopy(s1, s2)).toEqual({ a: [3, 4], b: 5 })
452
+ it("handles arrays and primitives in sources", () => {
453
+ const target = { a: [1, 2] }
454
+ const s1 = { a: [3, 4], b: 5 }
455
+ expect(deepMergeCopy(target, s1)).toEqual({ a: [3, 4], b: 5 })
451
456
  })
452
457
 
453
- it("returns {} if called with no sources", () => {
454
- expect(deepMergeCopy()).toEqual({})
458
+ it("returns the target object", () => {
459
+ const target = { x: 1 }
460
+ const s1 = { y: 2 }
461
+ expect(deepMergeCopy(target, s1)).toBe(target)
462
+ })
463
+
464
+ it("merges nothing if no sources provided (returns target as is)", () => {
465
+ const target = { foo: 1 }
466
+ expect(deepMergeCopy(target)).toBe(target)
467
+ expect(target).toEqual({ foo: 1 })
468
+ })
469
+
470
+ it("returns target if called with no arguments", () => {
471
+ expect(deepMergeCopy({})).toEqual({})
472
+ expect(deepMergeCopy()).toEqual(undefined)
473
+ })
474
+
475
+ it("does not mutate the target if no sources are provided", () => {
476
+ const target = { z: 9 }
477
+ const result = deepMergeCopy(target)
478
+ expect(result).toBe(target)
479
+ expect(result).toEqual({ z: 9 })
480
+ })
481
+
482
+ it("does not merge inherited properties from sources", () => {
483
+ const target = {}
484
+ const proto = { x: 1 }
485
+ const s1 = Object.create(proto)
486
+ s1.a = 2
487
+ deepMergeCopy(target, s1)
488
+ expect(target).toEqual({ a: 2 })
489
+ expect("x" in target).toBe(false)
455
490
  })
456
491
  })
457
492