@tim-code/my-util 0.1.4 → 0.2.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.
Files changed (3) hide show
  1. package/package.json +1 -1
  2. package/src/find.js +60 -63
  3. package/src/find.test.js +135 -254
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tim-code/my-util",
3
- "version": "0.1.4",
3
+ "version": "0.2.0",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "author": "",
package/src/find.js CHANGED
@@ -1,150 +1,150 @@
1
- export function findClosestAbs(array, desired, { key, map, threshold = Infinity } = {}) {
1
+ export function findClosestAbs(array, desired, { key, cutoff = Infinity } = {}) {
2
2
  let closest
3
- if (map) {
3
+ if (typeof key === "function") {
4
4
  for (let i = 0; i < array.length; i++) {
5
5
  const element = array[i]
6
- const value = map(element, i, array)
6
+ const value = key(element, i, array)
7
7
  const diff = Math.abs(value - desired)
8
- if (diff < threshold) {
8
+ if (diff < cutoff) {
9
9
  closest = element
10
- threshold = diff
10
+ cutoff = diff
11
11
  }
12
12
  }
13
- } else if (key) {
13
+ } else if (typeof key === "number" || typeof key === "string") {
14
14
  for (const element of array) {
15
15
  const value = element[key]
16
16
  const diff = Math.abs(value - desired)
17
- if (diff < threshold) {
17
+ if (diff < cutoff) {
18
18
  closest = element
19
- threshold = diff
19
+ cutoff = diff
20
20
  }
21
21
  }
22
22
  } else {
23
23
  for (const value of array) {
24
24
  const diff = Math.abs(value - desired)
25
- if (diff < threshold) {
25
+ if (diff < cutoff) {
26
26
  closest = value
27
- threshold = diff
27
+ cutoff = diff
28
28
  }
29
29
  }
30
30
  }
31
31
  return closest
32
32
  }
33
33
 
34
- export function findClosestLT(array, desired, { key, map, threshold = -Infinity } = {}) {
34
+ export function findClosestLT(array, desired, { key, cutoff = -Infinity } = {}) {
35
35
  let closest
36
- if (map) {
36
+ if (typeof key === "function") {
37
37
  for (let i = 0; i < array.length; i++) {
38
38
  const element = array[i]
39
- const value = map(element, i, array)
40
- if (value < desired && value > threshold) {
39
+ const value = key(element, i, array)
40
+ if (value < desired && value > cutoff) {
41
41
  closest = element
42
- threshold = value
42
+ cutoff = value
43
43
  }
44
44
  }
45
- } else if (key) {
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 > threshold) {
48
+ if (value < desired && value > cutoff) {
49
49
  closest = element
50
- threshold = value
50
+ cutoff = value
51
51
  }
52
52
  }
53
53
  } else {
54
54
  for (const value of array) {
55
- if (value < desired && value > threshold) {
55
+ if (value < desired && value > cutoff) {
56
56
  closest = value
57
- threshold = value
57
+ cutoff = value
58
58
  }
59
59
  }
60
60
  }
61
61
  return closest
62
62
  }
63
63
 
64
- export function findClosestLTE(array, desired, { key, map, threshold = -Infinity } = {}) {
64
+ export function findClosestLTE(array, desired, { key, cutoff = -Infinity } = {}) {
65
65
  let closest
66
- if (map) {
66
+ if (typeof key === "function") {
67
67
  for (let i = 0; i < array.length; i++) {
68
68
  const element = array[i]
69
- const value = map(element, i, array)
70
- if (value <= desired && value > threshold) {
69
+ const value = key(element, i, array)
70
+ if (value <= desired && value > cutoff) {
71
71
  closest = element
72
- threshold = value
72
+ cutoff = value
73
73
  }
74
74
  }
75
- } else if (key) {
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 > threshold) {
78
+ if (value <= desired && value > cutoff) {
79
79
  closest = element
80
- threshold = value
80
+ cutoff = value
81
81
  }
82
82
  }
83
83
  } else {
84
84
  for (const value of array) {
85
- if (value <= desired && value > threshold) {
85
+ if (value <= desired && value > cutoff) {
86
86
  closest = value
87
- threshold = value
87
+ cutoff = value
88
88
  }
89
89
  }
90
90
  }
91
91
  return closest
92
92
  }
93
93
 
94
- export function findClosestGT(array, desired, { key, map, threshold = Infinity } = {}) {
94
+ export function findClosestGT(array, desired, { key, cutoff = Infinity } = {}) {
95
95
  let closest
96
- if (map) {
96
+ if (typeof key === "function") {
97
97
  for (let i = 0; i < array.length; i++) {
98
98
  const element = array[i]
99
- const value = map(element, i, array)
100
- if (value > desired && value < threshold) {
99
+ const value = key(element, i, array)
100
+ if (value > desired && value < cutoff) {
101
101
  closest = element
102
- threshold = value
102
+ cutoff = value
103
103
  }
104
104
  }
105
- } else if (key) {
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 < threshold) {
108
+ if (value > desired && value < cutoff) {
109
109
  closest = element
110
- threshold = value
110
+ cutoff = value
111
111
  }
112
112
  }
113
113
  } else {
114
114
  for (const value of array) {
115
- if (value > desired && value < threshold) {
115
+ if (value > desired && value < cutoff) {
116
116
  closest = value
117
- threshold = value
117
+ cutoff = value
118
118
  }
119
119
  }
120
120
  }
121
121
  return closest
122
122
  }
123
123
 
124
- export function findClosestGTE(array, desired, { key, map, threshold = Infinity } = {}) {
124
+ export function findClosestGTE(array, desired, { key, cutoff = Infinity } = {}) {
125
125
  let closest
126
- if (map) {
126
+ if (typeof key === "function") {
127
127
  for (let i = 0; i < array.length; i++) {
128
128
  const element = array[i]
129
- const value = map(element, i, array)
130
- if (value >= desired && value < threshold) {
129
+ const value = key(element, i, array)
130
+ if (value >= desired && value < cutoff) {
131
131
  closest = element
132
- threshold = value
132
+ cutoff = value
133
133
  }
134
134
  }
135
- } else if (key) {
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 < threshold) {
138
+ if (value >= desired && value < cutoff) {
139
139
  closest = element
140
- threshold = value
140
+ cutoff = value
141
141
  }
142
142
  }
143
143
  } else {
144
144
  for (const value of array) {
145
- if (value >= desired && value < threshold) {
145
+ if (value >= desired && value < cutoff) {
146
146
  closest = value
147
- threshold = value
147
+ cutoff = value
148
148
  }
149
149
  }
150
150
  }
@@ -152,27 +152,24 @@ export function findClosestGTE(array, desired, { key, map, threshold = Infinity
152
152
  }
153
153
 
154
154
  /**
155
- * Find the closest element in an array.
156
- * If using for strings, need to specify different values for "threshold" and "comparator".
157
- * "~" and "" are good threshold string values for gt/gte and lt/lte respectively.
155
+ * Find the closest element in an array. If there is a tie, then returns the first matching element by order in the array.
156
+ * If using for strings, need to specify different values for "cutoff" and "comparator".
157
+ * "~" and "" are good cutoff string values for gt/gte and lt/lte respectively.
158
158
  * @template T, V
159
159
  * @param {Array<T>} array
160
160
  * @param {V} value The desired value to search for
161
161
  * @param {Object} options
162
- * @param {string|number=} options.key If specified, will consider the value for each element's key instead of the element itself.
163
- * @param {Function=} options.map If specified, will compute value by calling provided function on the element. Takes precedence over key.
164
- * @param {string|number|Function=} options.transform Allows combining key and map as one parameter. Useful for piping in passed values.
162
+ * @param {string|number|Function=} options.key
163
+ * If specified, will consider the value for each element's key instead of the element itself.
164
+ * If a function, called with the element, index and array (same as .map() callback) to produce the value to sort on.
165
165
  * @param {string=} options.comparator "abs", "lt", "lte", "gt", "gte", "abs". Default is "abs" which implies T is number.
166
- * @param {V=} options.threshold If specified, uses a different initial min/max/difference than positive or negative infinity.
166
+ * @param {V=} options.cutoff If specified, sets a initial constraint on how close the found value must be.
167
+ * 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
+ * If used with "abs", the found element would need to have a difference with the desired value less than the cutoff.
167
169
  * @returns {T|undefined}
168
170
  */
169
171
  export function findClosest(array, value, options = {}) {
170
- const { comparator = "abs", transform } = options
171
- if (typeof transform === "function") {
172
- options = { ...options, map: transform }
173
- } else if (typeof transform === "string" || typeof transform === "number") {
174
- options = { ...options, key: transform }
175
- }
172
+ const { comparator = "abs" } = options
176
173
  switch (comparator) {
177
174
  case "lt":
178
175
  return findClosestLT(array, value, options)
package/src/find.test.js CHANGED
@@ -1,335 +1,216 @@
1
- import {
2
- findClosest,
1
+ import { describe, expect, it } from "@jest/globals"
2
+
3
+ const {
3
4
  findClosestAbs,
4
- findClosestGT,
5
- findClosestGTE,
6
5
  findClosestLT,
7
6
  findClosestLTE,
8
- } from "./find.js"
7
+ findClosestGT,
8
+ findClosestGTE,
9
+ findClosest,
10
+ } = await import("./find.js")
9
11
 
10
- describe("findClosest", () => {
11
- it("returns the closest value by absolute difference (default comparator)", () => {
12
- expect(findClosest([1, 5, 10], 6)).toBe(5)
13
- expect(findClosest([1, 5, 10], 8)).toBe(10)
14
- expect(findClosest([1, 5, 10], 1)).toBe(1)
12
+ describe("findClosestAbs", () => {
13
+ it("returns the element closest in absolute value to desired", () => {
14
+ expect(findClosestAbs([1, 5, 9], 6)).toBe(5)
15
+ expect(findClosestAbs([1, 5, 9], 8)).toBe(9)
16
+ expect(findClosestAbs([1, 5, 9], 1)).toBe(1)
15
17
  })
16
18
 
17
- it("returns undefined if array is empty", () => {
18
- expect(findClosest([], 10)).toBeUndefined()
19
+ it("returns the first element in case of tie", () => {
20
+ expect(findClosestAbs([4, 8], 6)).toBe(4)
19
21
  })
20
22
 
21
- it("returns the closest object by key (abs)", () => {
22
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
23
- expect(findClosest(arr, 6, { key: "v" })).toEqual({ v: 5 })
23
+ it("returns undefined for empty array", () => {
24
+ expect(findClosestAbs([], 10)).toBeUndefined()
24
25
  })
25
26
 
26
- it("returns the closest value less than input (lt comparator)", () => {
27
- expect(findClosest([1, 3, 5, 7], 6, { comparator: "lt" })).toBe(5)
28
- expect(findClosest([1, 3, 5, 7], 1, { comparator: "lt" })).toBeUndefined()
27
+ it("supports key as function", () => {
28
+ const arr = [{ v: 2 }, { v: 8 }]
29
+ expect(findClosestAbs(arr, 5, { key: (e) => e.v })).toEqual({ v: 2 })
29
30
  })
30
31
 
31
- it("returns the closest value less than or equal to input (lte comparator)", () => {
32
- expect(findClosest([1, 3, 5, 7], 5, { comparator: "lte" })).toBe(5)
33
- expect(findClosest([1, 3, 5, 7], 2, { comparator: "lte" })).toBe(1)
34
- expect(findClosest([1, 3, 5, 7], 0, { comparator: "lte" })).toBeUndefined()
32
+ it("supports key as string", () => {
33
+ const arr = [{ x: 1 }, { x: 10 }]
34
+ expect(findClosestAbs(arr, 8, { key: "x" })).toEqual({ x: 10 })
35
35
  })
36
36
 
37
- it("returns the closest value greater than input (gt comparator)", () => {
38
- expect(findClosest([1, 3, 5, 7], 5, { comparator: "gt" })).toBe(7)
39
- expect(findClosest([1, 3, 5, 7], 7, { comparator: "gt" })).toBeUndefined()
37
+ it("supports key as number", () => {
38
+ const arr = [[2], [8]]
39
+ expect(findClosestAbs(arr, 7, { key: 0 })).toEqual([8])
40
40
  })
41
41
 
42
- it("returns the closest value greater than or equal to input (gte comparator)", () => {
43
- expect(findClosest([1, 3, 5, 7], 5, { comparator: "gte" })).toBe(5)
44
- expect(findClosest([1, 3, 5, 7], 6, { comparator: "gte" })).toBe(7)
45
- expect(findClosest([1, 3, 5, 7], 8, { comparator: "gte" })).toBeUndefined()
42
+ it("respects cutoff", () => {
43
+ expect(findClosestAbs([1, 5, 9], 6, { cutoff: 2 })).toBe(5)
44
+ expect(findClosestAbs([1, 5, 9], 6, { cutoff: 1 })).toBeUndefined()
46
45
  })
46
+ })
47
47
 
48
- it("returns the closest object by key for lt/lte/gt/gte", () => {
49
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
50
- expect(findClosest(arr, 6, { comparator: "lt", key: "v" })).toEqual({ v: 5 })
51
- expect(findClosest(arr, 6, { comparator: "lte", key: "v" })).toEqual({ v: 5 })
52
- expect(findClosest(arr, 6, { comparator: "gt", key: "v" })).toEqual({ v: 10 })
53
- expect(findClosest(arr, 10, { comparator: "gte", key: "v" })).toEqual({ v: 10 })
48
+ describe("findClosestLT", () => {
49
+ it("returns the closest element less than desired", () => {
50
+ expect(findClosestLT([1, 5, 9], 6)).toBe(5)
51
+ expect(findClosestLT([1, 5, 9], 2)).toBe(1)
52
+ expect(findClosestLT([1, 5, 9], 1)).toBeUndefined()
54
53
  })
55
54
 
56
- it("respects the threshold option for abs comparator", () => {
57
- expect(findClosest([1, 5, 10], 6, { threshold: 0.5 })).toBeUndefined()
58
- expect(findClosest([1, 5, 10], 6, { threshold: 2 })).toBe(5)
55
+ it("returns first match if tie", () => {
56
+ expect(findClosestLT([2, 2, 1], 3)).toBe(2)
59
57
  })
60
58
 
61
- it("respects the threshold option for lt/lte/gt/gte", () => {
62
- expect(findClosest([1, 3, 5, 7], 6, { comparator: "lt", threshold: 4 })).toBe(5)
63
- expect(findClosest([1, 3, 5, 7], 6, { comparator: "lt", threshold: 5 })).toBeUndefined()
64
- expect(findClosest([1, 3, 5, 7], 6, { comparator: "gt", threshold: 7 })).toBeUndefined()
65
- expect(findClosest([1, 3, 5, 7], 6, { comparator: "gt", threshold: 10 })).toBe(7)
59
+ it("returns undefined for empty array", () => {
60
+ expect(findClosestLT([], 10)).toBeUndefined()
66
61
  })
67
62
 
68
- it("throws for unknown comparator", () => {
69
- expect(() => findClosest([1, 2, 3], 2, { comparator: "foo" })).toThrow(
70
- "Unknown comparator: foo"
71
- )
63
+ it("supports key as function", () => {
64
+ const arr = [{ v: 2 }, { v: 8 }]
65
+ expect(findClosestLT(arr, 8, { key: (e) => e.v })).toEqual({ v: 2 })
72
66
  })
73
67
 
74
- it("returns undefined if no element matches threshold/key criteria", () => {
75
- expect(
76
- findClosest([{ v: 1 }], 10, { comparator: "gt", key: "v", threshold: 1 })
77
- ).toBeUndefined()
78
- expect(
79
- findClosest([{ v: 1 }], 0, { comparator: "lt", key: "v", threshold: 1 })
80
- ).toBeUndefined()
81
- })
82
-
83
- it("works with negative numbers and zero", () => {
84
- expect(findClosest([-10, -5, 0, 5, 10], -7)).toBe(-5)
85
- expect(findClosest([-10, -5, 0, 5, 10], 0)).toBe(0)
86
- expect(findClosest([-10, -5, 0, 5, 10], 7)).toBe(5)
87
- })
88
-
89
- // ISSUE: findClosestAbs and related functions do not skip NaN values in key/map modes, only in value mode.
90
- it("skips NaN values in abs comparator", () => {
91
- expect(findClosest([1, NaN, 5], 4)).toBe(5)
92
- })
93
-
94
- it("skips objects missing the key in key-based comparators", () => {
95
- const arr = [{ v: 1 }, {}, { v: 5 }]
96
- expect(findClosest(arr, 2, { key: "v" })).toEqual({ v: 1 })
97
- })
98
-
99
- it("finds the closest string using abs comparator and a custom threshold/comparator", () => {
100
- // Since abs comparator expects numbers, we need to provide a custom comparator for strings.
101
- // We'll use threshold and comparator: "lt", "lte", "gt", "gte" for string comparisons.
102
- const arr = ["apple", "banana", "cherry", "date"]
103
- // Find the closest string less than "carrot" (alphabetically)
104
- expect(findClosest(arr, "carrot", { comparator: "lt", threshold: "" })).toBe("banana")
105
- // Find the closest string less than or equal to "banana"
106
- expect(findClosest(arr, "banana", { comparator: "lte", threshold: "" })).toBe("banana")
107
- // Find the closest string greater than "carrot"
108
- expect(findClosest(arr, "carrot", { comparator: "gt", threshold: "~" })).toBe("cherry")
109
- // Find the closest string greater than or equal to "date"
110
- expect(findClosest(arr, "date", { comparator: "gte", threshold: "~" })).toBe("date")
111
- // If nothing matches, returns undefined
112
- expect(findClosest(arr, "aardvark", { comparator: "lt", threshold: "" })).toBeUndefined()
113
- expect(findClosest(arr, "zebra", { comparator: "gt", threshold: "~" })).toBeUndefined()
114
- })
115
-
116
- it("finds the closest string by key in array of objects", () => {
117
- const arr = [{ name: "apple" }, { name: "banana" }, { name: "cherry" }]
118
- expect(
119
- findClosest(arr, "blueberry", { comparator: "lt", key: "name", threshold: "" })
120
- ).toEqual({
121
- name: "banana",
122
- })
123
- expect(
124
- findClosest(arr, "banana", { comparator: "lte", key: "name", threshold: "" })
125
- ).toEqual({
126
- name: "banana",
127
- })
128
- expect(
129
- findClosest(arr, "banana", { comparator: "gt", key: "name", threshold: "~" })
130
- ).toEqual({
131
- name: "cherry",
132
- })
133
- expect(
134
- findClosest(arr, "cherry", { comparator: "gte", key: "name", threshold: "~" })
135
- ).toEqual({
136
- name: "cherry",
137
- })
138
- expect(
139
- findClosest(arr, "aardvark", { comparator: "lt", key: "name", threshold: "" })
140
- ).toBeUndefined()
141
- })
142
-
143
- it("returns undefined if no string matches threshold/key criteria", () => {
144
- const arr = ["apple", "banana", "cherry"]
145
- expect(findClosest(arr, "apple", { comparator: "lt", threshold: "" })).toBeUndefined()
146
- expect(findClosest(arr, "cherry", { comparator: "gt" })).toBeUndefined()
147
- })
148
-
149
- it("can use abs comparator with string lengths", () => {
150
- // This is a reasonable use-case for abs: find string with length closest to 4
151
- const arr = ["a", "bb", "ccc", "dddd", "eeeee"]
152
- // Map to string lengths using key
153
- expect(findClosest(arr, 4, { comparator: "abs", key: "length" })).toEqual("dddd")
154
- // If threshold is set so no string length is close enough
155
- expect(
156
- findClosest(arr, 4, { comparator: "abs", key: "length", threshold: -1 })
157
- ).toBeUndefined()
158
- })
159
-
160
- it("uses transform as a function (same as map)", () => {
161
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
162
- const mapFn = (el) => el.v
163
- expect(findClosest(arr, 6, { transform: mapFn })).toEqual({ v: 5 })
164
- // Should take precedence over key if both are present
165
- expect(findClosest(arr, 6, { key: "notUsed", transform: mapFn })).toEqual({ v: 5 })
166
- })
167
-
168
- it("uses transform as a string (same as key)", () => {
169
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
170
- expect(findClosest(arr, 6, { transform: "v" })).toEqual({ v: 5 })
171
- // Should take precedence over key if both are present
172
- expect(findClosest(arr, 6, { key: "notUsed", transform: "v" })).toEqual({ v: 5 })
173
- })
174
-
175
- it("uses transform as a number (same as key)", () => {
176
- const arr = [[1], [5], [10]]
177
- expect(findClosest(arr, 6, { transform: 0 })).toEqual([5])
178
- // Should take precedence over key if both are present
179
- expect(findClosest(arr, 6, { key: "notUsed", transform: 0 })).toEqual([5])
180
- })
181
-
182
- it("transform does not override key if key is already present and transform is not provided", () => {
183
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
184
- expect(findClosest(arr, 6, { key: "v" })).toEqual({ v: 5 })
185
- })
186
-
187
- it("transform is ignored if not a function, string, or number", () => {
188
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
189
- // eslint-disable-next-line no-restricted-syntax
190
- expect(findClosest(arr, 6, { transform: null, key: "v" })).toEqual({ v: 5 })
191
- expect(findClosest(arr, 6, { transform: {}, key: "v" })).toEqual({ v: 5 })
68
+ it("supports key as string", () => {
69
+ const arr = [{ x: 1 }, { x: 10 }]
70
+ expect(findClosestLT(arr, 8, { key: "x" })).toEqual({ x: 1 })
192
71
  })
193
- })
194
72
 
195
- describe("findClosestAbs", () => {
196
- it("returns closest value by absolute difference", () => {
197
- expect(findClosestAbs([1, 5, 10], 6)).toBe(5)
198
- expect(findClosestAbs([1, 5, 10], 8)).toBe(10)
199
- expect(findClosestAbs([1, 5, 10], 1)).toBe(1)
73
+ it("supports key as number", () => {
74
+ const arr = [[2], [8]]
75
+ expect(findClosestLT(arr, 7, { key: 0 })).toEqual([2])
200
76
  })
201
77
 
202
- it("returns undefined for empty array", () => {
203
- expect(findClosestAbs([], 10)).toBeUndefined()
78
+ it("respects cutoff", () => {
79
+ expect(findClosestLT([1, 5, 9], 6, { cutoff: 4 })).toBe(5)
80
+ expect(findClosestLT([1, 5, 9], 6, { cutoff: 5 })).toBeUndefined()
204
81
  })
82
+ })
205
83
 
206
- it("returns closest object by key", () => {
207
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
208
- expect(findClosestAbs(arr, 6, { key: "v" })).toEqual({ v: 5 })
84
+ describe("findClosestLTE", () => {
85
+ it("returns the closest element less than or equal to desired", () => {
86
+ expect(findClosestLTE([1, 5, 9], 5)).toBe(5)
87
+ expect(findClosestLTE([1, 5, 9], 6)).toBe(5)
88
+ expect(findClosestLTE([1, 5, 9], 1)).toBe(1)
89
+ expect(findClosestLTE([1, 5, 9], 0)).toBeUndefined()
209
90
  })
210
91
 
211
- it("returns closest value by map", () => {
212
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
213
- expect(findClosestAbs(arr, 6, { map: (el) => el.v })).toEqual({ v: 5 })
92
+ it("returns first match if tie", () => {
93
+ expect(findClosestLTE([2, 2, 1], 2)).toBe(2)
214
94
  })
215
95
 
216
- it("respects threshold", () => {
217
- expect(findClosestAbs([1, 5, 10], 6, { threshold: 0.5 })).toBeUndefined()
218
- expect(findClosestAbs([1, 5, 10], 6, { threshold: 2 })).toBe(5)
96
+ it("returns undefined for empty array", () => {
97
+ expect(findClosestLTE([], 10)).toBeUndefined()
219
98
  })
220
99
 
221
- it("skips NaN in value mode but not in key/map mode", () => {
222
- expect(findClosestAbs([1, NaN, 5], 4)).toBe(5)
223
- const arr = [{ v: 1 }, { v: NaN }, { v: 5 }]
224
- expect(findClosestAbs(arr, 2, { key: "v" })).toEqual({ v: 1 })
225
- expect(findClosestAbs(arr, 2, { map: (el) => el.v })).toEqual({ v: 1 })
100
+ it("supports key as function", () => {
101
+ const arr = [{ v: 2 }, { v: 8 }]
102
+ expect(findClosestLTE(arr, 8, { key: (e) => e.v })).toEqual({ v: 8 })
226
103
  })
227
- })
228
104
 
229
- describe("findClosestLT", () => {
230
- it("returns closest value less than desired", () => {
231
- expect(findClosestLT([1, 3, 5, 7], 6)).toBe(5)
232
- expect(findClosestLT([1, 3, 5, 7], 1)).toBeUndefined()
105
+ it("supports key as string", () => {
106
+ const arr = [{ x: 1 }, { x: 10 }]
107
+ expect(findClosestLTE(arr, 8, { key: "x" })).toEqual({ x: 1 })
233
108
  })
234
109
 
235
- it("returns closest object by key", () => {
236
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
237
- expect(findClosestLT(arr, 6, { key: "v" })).toEqual({ v: 5 })
110
+ it("supports key as number", () => {
111
+ const arr = [[2], [8]]
112
+ expect(findClosestLTE(arr, 7, { key: 0 })).toEqual([2])
238
113
  })
239
114
 
240
- it("returns closest object by map", () => {
241
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
242
- expect(findClosestLT(arr, 6, { map: (el) => el.v })).toEqual({ v: 5 })
115
+ it("respects cutoff", () => {
116
+ expect(findClosestLTE([1, 5, 9], 6, { cutoff: 4 })).toBe(5)
117
+ expect(findClosestLTE([1, 5, 9], 6, { cutoff: 5 })).toBeUndefined()
243
118
  })
119
+ })
244
120
 
245
- it("respects threshold", () => {
246
- expect(findClosestLT([1, 3, 5, 7], 6, { threshold: 4 })).toBe(5)
247
- expect(findClosestLT([1, 3, 5, 7], 6, { threshold: 5 })).toBeUndefined()
121
+ describe("findClosestGT", () => {
122
+ it("returns the closest element greater than desired", () => {
123
+ expect(findClosestGT([1, 5, 9], 6)).toBe(9)
124
+ expect(findClosestGT([1, 5, 9], 0)).toBe(1)
125
+ expect(findClosestGT([1, 5, 9], 9)).toBeUndefined()
248
126
  })
249
127
 
250
- it("returns undefined for empty array", () => {
251
- expect(findClosestLT([], 10)).toBeUndefined()
128
+ it("returns first match if tie", () => {
129
+ expect(findClosestGT([8, 8, 10], 7)).toBe(8)
252
130
  })
253
- })
254
131
 
255
- describe("findClosestLTE", () => {
256
- it("returns closest value less than or equal to desired", () => {
257
- expect(findClosestLTE([1, 3, 5, 7], 5)).toBe(5)
258
- expect(findClosestLTE([1, 3, 5, 7], 2)).toBe(1)
259
- expect(findClosestLTE([1, 3, 5, 7], 0)).toBeUndefined()
132
+ it("returns undefined for empty array", () => {
133
+ expect(findClosestGT([], 10)).toBeUndefined()
260
134
  })
261
135
 
262
- it("returns closest object by key", () => {
263
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
264
- expect(findClosestLTE(arr, 6, { key: "v" })).toEqual({ v: 5 })
136
+ it("supports key as function", () => {
137
+ const arr = [{ v: 2 }, { v: 8 }]
138
+ expect(findClosestGT(arr, 2, { key: (e) => e.v })).toEqual({ v: 8 })
265
139
  })
266
140
 
267
- it("returns closest object by map", () => {
268
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
269
- expect(findClosestLTE(arr, 6, { map: (el) => el.v })).toEqual({ v: 5 })
141
+ it("supports key as string", () => {
142
+ const arr = [{ x: 1 }, { x: 10 }]
143
+ expect(findClosestGT(arr, 8, { key: "x" })).toEqual({ x: 10 })
270
144
  })
271
145
 
272
- it("respects threshold", () => {
273
- expect(findClosestLTE([1, 3, 5, 7], 6, { threshold: 4 })).toBe(5)
274
- expect(findClosestLTE([1, 3, 5, 7], 6, { threshold: 5 })).toBeUndefined()
146
+ it("supports key as number", () => {
147
+ const arr = [[2], [8]]
148
+ expect(findClosestGT(arr, 2, { key: 0 })).toEqual([8])
275
149
  })
276
150
 
277
- it("returns undefined for empty array", () => {
278
- expect(findClosestLTE([], 10)).toBeUndefined()
151
+ it("respects cutoff", () => {
152
+ expect(findClosestGT([1, 5, 9], 6, { cutoff: 8 })).toBeUndefined()
153
+ expect(findClosestGT([1, 5, 9], 4, { cutoff: 8 })).toBe(5)
279
154
  })
280
155
  })
281
156
 
282
- describe("findClosestGT", () => {
283
- it("returns closest value greater than desired", () => {
284
- expect(findClosestGT([1, 3, 5, 7], 5)).toBe(7)
285
- expect(findClosestGT([1, 3, 5, 7], 7)).toBeUndefined()
157
+ describe("findClosestGTE", () => {
158
+ it("returns the closest element greater than or equal to desired", () => {
159
+ expect(findClosestGTE([1, 5, 9], 5)).toBe(5)
160
+ expect(findClosestGTE([1, 5, 9], 4)).toBe(5)
161
+ expect(findClosestGTE([1, 5, 9], 10)).toBeUndefined()
162
+ })
163
+
164
+ it("returns first match if tie", () => {
165
+ expect(findClosestGTE([8, 8, 10], 8)).toBe(8)
286
166
  })
287
167
 
288
- it("returns closest object by key", () => {
289
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
290
- expect(findClosestGT(arr, 6, { key: "v" })).toEqual({ v: 10 })
168
+ it("returns undefined for empty array", () => {
169
+ expect(findClosestGTE([], 10)).toBeUndefined()
291
170
  })
292
171
 
293
- it("returns closest object by map", () => {
294
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
295
- expect(findClosestGT(arr, 6, { map: (el) => el.v })).toEqual({ v: 10 })
172
+ it("supports key as function", () => {
173
+ const arr = [{ v: 2 }, { v: 8 }]
174
+ expect(findClosestGTE(arr, 2, { key: (e) => e.v })).toEqual({ v: 2 })
296
175
  })
297
176
 
298
- it("respects threshold", () => {
299
- expect(findClosestGT([1, 3, 5, 7], 6, { threshold: 7 })).toBeUndefined()
300
- expect(findClosestGT([1, 3, 5, 7], 6, { threshold: 10 })).toBe(7)
177
+ it("supports key as string", () => {
178
+ const arr = [{ x: 1 }, { x: 10 }]
179
+ expect(findClosestGTE(arr, 8, { key: "x" })).toEqual({ x: 10 })
301
180
  })
302
181
 
303
- it("returns undefined for empty array", () => {
304
- expect(findClosestGT([], 10)).toBeUndefined()
182
+ it("supports key as number", () => {
183
+ const arr = [[2], [8]]
184
+ expect(findClosestGTE(arr, 2, { key: 0 })).toEqual([2])
305
185
  })
306
- })
307
186
 
308
- describe("findClosestGTE", () => {
309
- it("returns closest value greater than or equal to desired", () => {
310
- expect(findClosestGTE([1, 3, 5, 7], 5)).toBe(5)
311
- expect(findClosestGTE([1, 3, 5, 7], 6)).toBe(7)
312
- expect(findClosestGTE([1, 3, 5, 7], 8)).toBeUndefined()
187
+ it("respects cutoff", () => {
188
+ expect(findClosestGTE([1, 5, 9], 6, { cutoff: 8 })).toBeUndefined()
189
+ expect(findClosestGTE([1, 5, 9], 4, { cutoff: 8 })).toBe(5)
313
190
  })
191
+ })
314
192
 
315
- it("returns closest object by key", () => {
316
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
317
- expect(findClosestGTE(arr, 6, { key: "v" })).toEqual({ v: 10 })
318
- expect(findClosestGTE(arr, 10, { key: "v" })).toEqual({ v: 10 })
193
+ describe("findClosest", () => {
194
+ it("defaults to abs comparator", () => {
195
+ expect(findClosest([1, 5, 9], 6)).toBe(5)
319
196
  })
320
197
 
321
- it("returns closest object by map", () => {
322
- const arr = [{ v: 1 }, { v: 5 }, { v: 10 }]
323
- expect(findClosestGTE(arr, 6, { map: (el) => el.v })).toEqual({ v: 10 })
324
- expect(findClosestGTE(arr, 10, { map: (el) => el.v })).toEqual({ v: 10 })
198
+ it("calls correct comparator", () => {
199
+ expect(findClosest([1, 5, 9], 6, { comparator: "lt" })).toBe(5)
200
+ expect(findClosest([1, 5, 9], 6, { comparator: "lte" })).toBe(5)
201
+ expect(findClosest([1, 5, 9], 6, { comparator: "gt" })).toBe(9)
202
+ expect(findClosest([1, 5, 9], 6, { comparator: "gte" })).toBe(9)
203
+ expect(findClosest([1, 5, 9], 6, { comparator: "abs" })).toBe(5)
325
204
  })
326
205
 
327
- it("respects threshold", () => {
328
- expect(findClosestGTE([1, 3, 5, 7], 6, { threshold: 7 })).toBeUndefined()
329
- expect(findClosestGTE([1, 3, 5, 7], 6, { threshold: 10 })).toBe(7)
206
+ it("throws on unknown comparator", () => {
207
+ expect(() => findClosest([1, 5, 9], 6, { comparator: "foo" })).toThrow(
208
+ "Unknown comparator: foo"
209
+ )
330
210
  })
331
211
 
332
- it("returns undefined for empty array", () => {
333
- expect(findClosestGTE([], 10)).toBeUndefined()
212
+ it("passes options to underlying function", () => {
213
+ const arr = [{ x: 1 }, { x: 10 }]
214
+ expect(findClosest(arr, 8, { comparator: "abs", key: "x" })).toEqual({ x: 10 })
334
215
  })
335
216
  })