@tim-code/my-util 0.6.3 → 0.6.4

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.6.3",
3
+ "version": "0.6.4",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "author": "Tim Sprowl",
package/src/array.js CHANGED
@@ -176,9 +176,30 @@ export function descending(transform) {
176
176
  }
177
177
  }
178
178
 
179
+ function compareUndefinedNullEmpty(a, b) {
180
+ if (b === undefined || b === null || b === "") {
181
+ if (a === undefined || a === null || a === "") {
182
+ return 0
183
+ }
184
+ return -1
185
+ } else if (a === undefined || a === null || a === "") {
186
+ return 1
187
+ }
188
+ return undefined
189
+ }
190
+
191
+ function naturalCompare(a, b) {
192
+ if (a[0] === "-" && b[0] === "-") {
193
+ a = a.slice(1, a.length)
194
+ b = b.slice(1, b.length)
195
+ return b.localeCompare(a, undefined, { numeric: true })
196
+ }
197
+ return a.localeCompare(b, undefined, { numeric: true })
198
+ }
199
+
179
200
  /**
180
201
  * Returns an "ascending" comparator using natural string sort, to be used to sort an array of strings.
181
- * Undefined or null values are always sorted to the end.
202
+ * Empty string or undefined or null values are always sorted to the end.
182
203
  * @param {string|number|Function=} transform
183
204
  * If a function, calls the provided function on an element to get the value to sort on.
184
205
  * If a string or number, treats transform as a key and sorts on each element's value at key.
@@ -189,22 +210,22 @@ export function naturalAsc(transform) {
189
210
  return (a, b) => {
190
211
  a = transform(a)
191
212
  b = transform(b)
192
- return compareUndefinedNull(a, b) ?? a.localeCompare(b, undefined, { numeric: true })
213
+ return compareUndefinedNullEmpty(a, b) ?? naturalCompare(a, b)
193
214
  }
194
215
  }
195
216
  if (typeof transform === "string" || typeof transform === "number") {
196
217
  return (a, b) => {
197
- const invalid = compareUndefinedNull(a[transform], b[transform])
198
- return invalid ?? a[transform].localeCompare(b[transform], undefined, { numeric: true })
218
+ const invalid = compareUndefinedNullEmpty(a[transform], b[transform])
219
+ return invalid ?? naturalCompare(a[transform], b[transform])
199
220
  }
200
221
  }
201
222
  return (a, b) => {
202
- return compareUndefinedNull(a, b) ?? a.localeCompare(b, undefined, { numeric: true })
223
+ return compareUndefinedNullEmpty(a, b) ?? naturalCompare(a, b)
203
224
  }
204
225
  }
205
226
  /**
206
227
  * Returns a "descending" comparator using natural string sort, to be used to sort an array of strings.
207
- * Undefined or null values are always sorted to the end.
228
+ * Empty string or undefined or null values are always sorted to the end.
208
229
  * @param {string|number|Function=} transform
209
230
  * If a function, calls the provided function on an element to get the value to sort on.
210
231
  * If a string or number, treats transform as a key and sorts on each element's value at key.
@@ -215,17 +236,17 @@ export function naturalDesc(transform) {
215
236
  return (a, b) => {
216
237
  b = transform(b)
217
238
  a = transform(a)
218
- return compareUndefinedNull(a, b) ?? b.localeCompare(a, undefined, { numeric: true })
239
+ return compareUndefinedNullEmpty(a, b) ?? naturalCompare(b, a)
219
240
  }
220
241
  }
221
242
  if (typeof transform === "string" || typeof transform === "number") {
222
243
  return (a, b) => {
223
- const invalid = compareUndefinedNull(a[transform], b[transform])
224
- return invalid ?? b[transform].localeCompare(a[transform], undefined, { numeric: true })
244
+ const invalid = compareUndefinedNullEmpty(a[transform], b[transform])
245
+ return invalid ?? naturalCompare(b[transform], a[transform])
225
246
  }
226
247
  }
227
248
  return (a, b) => {
228
- return compareUndefinedNull(a, b) ?? b.localeCompare(a, undefined, { numeric: true })
249
+ return compareUndefinedNullEmpty(a, b) ?? naturalCompare(b, a)
229
250
  }
230
251
  }
231
252
 
package/src/array.test.js CHANGED
@@ -511,12 +511,24 @@ describe("naturalAsc", () => {
511
511
  expect(arr.map((o) => o.v)).toEqual(["file1", "file2", "file10", undefined, null])
512
512
  })
513
513
 
514
+ it("defers empty string to the end along with undefined/null (default path)", () => {
515
+ const arr = ["b", "", "a", undefined, null, "c"]
516
+ arr.sort(naturalAsc())
517
+ expect(arr).toEqual(["a", "b", "c", "", null, undefined])
518
+ })
519
+
520
+ it("defers empty string to the end along with undefined/null (key path)", () => {
521
+ const arr = [{ v: "" }, { v: "a2" }, { v: undefined }, { v: "a1" }, { v: null }]
522
+ arr.sort(naturalAsc("v"))
523
+ expect(arr.map((o) => o.v)).toEqual(["a1", "a2", "", undefined, null])
524
+ })
525
+
514
526
  it("returns 0 for equal values (function and key paths)", () => {
515
527
  expect(naturalAsc((x) => x)("a", "a")).toBe(0)
516
528
  expect(naturalAsc("v")({ v: "x" }, { v: "x" })).toBe(0)
517
529
  })
518
530
 
519
- it("default comparator sorts with undefined/null at end but uses non-natural, descending semantics", () => {
531
+ it("default comparator sorts naturally with undefined/null at end", () => {
520
532
  const arr = [undefined, "b", null, "a", "c"]
521
533
  arr.sort(naturalAsc())
522
534
  expect(arr).toEqual(["a", "b", "c", null, undefined])
@@ -524,6 +536,12 @@ describe("naturalAsc", () => {
524
536
  numericLike.sort(naturalAsc())
525
537
  expect(numericLike).toEqual(["a1", "a2", "a10"])
526
538
  })
539
+
540
+ it("handles negative number-like strings by comparing magnitudes (both negative)", () => {
541
+ const arr = ["-2", "-10", "-1"]
542
+ arr.sort(naturalAsc())
543
+ expect(arr).toEqual(["-10", "-2", "-1"])
544
+ })
527
545
  })
528
546
 
529
547
  describe("naturalDesc", () => {
@@ -551,10 +569,23 @@ describe("naturalDesc", () => {
551
569
  expect(arr.map((o) => o.v)).toEqual(["file10", "file2", "file1", undefined, null])
552
570
  })
553
571
 
572
+ it("defers empty string to the end along with undefined/null (default path)", () => {
573
+ const arr = ["b", "", "a", undefined, null, "c"]
574
+ arr.sort(naturalDesc())
575
+ expect(arr.slice(-3)).toEqual(["", null, undefined])
576
+ expect(arr.slice(0, 3)).toEqual(["c", "b", "a"])
577
+ })
578
+
554
579
  it("returns 0 for equal values (function and key paths)", () => {
555
580
  expect(naturalDesc((x) => x)("a", "a")).toBe(0)
556
581
  expect(naturalDesc("v")({ v: "x" }, { v: "x" })).toBe(0)
557
582
  })
583
+
584
+ it("handles negative number-like strings by comparing magnitudes (both negative) in descending", () => {
585
+ const arr = ["-2", "-10", "-1"]
586
+ arr.sort(naturalDesc())
587
+ expect(arr).toEqual(["-1", "-2", "-10"])
588
+ })
558
589
  })
559
590
 
560
591
  describe("multilevel", () => {