@tim-code/my-util 0.6.2 → 0.6.3

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.2",
3
+ "version": "0.6.3",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "author": "Tim Sprowl",
package/src/array.js CHANGED
@@ -176,6 +176,59 @@ export function descending(transform) {
176
176
  }
177
177
  }
178
178
 
179
+ /**
180
+ * 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.
182
+ * @param {string|number|Function=} transform
183
+ * If a function, calls the provided function on an element to get the value to sort on.
184
+ * If a string or number, treats transform as a key and sorts on each element's value at key.
185
+ * @returns {Function}
186
+ */
187
+ export function naturalAsc(transform) {
188
+ if (typeof transform === "function") {
189
+ return (a, b) => {
190
+ a = transform(a)
191
+ b = transform(b)
192
+ return compareUndefinedNull(a, b) ?? a.localeCompare(b, undefined, { numeric: true })
193
+ }
194
+ }
195
+ if (typeof transform === "string" || typeof transform === "number") {
196
+ return (a, b) => {
197
+ const invalid = compareUndefinedNull(a[transform], b[transform])
198
+ return invalid ?? a[transform].localeCompare(b[transform], undefined, { numeric: true })
199
+ }
200
+ }
201
+ return (a, b) => {
202
+ return compareUndefinedNull(a, b) ?? a.localeCompare(b, undefined, { numeric: true })
203
+ }
204
+ }
205
+ /**
206
+ * 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.
208
+ * @param {string|number|Function=} transform
209
+ * If a function, calls the provided function on an element to get the value to sort on.
210
+ * If a string or number, treats transform as a key and sorts on each element's value at key.
211
+ * @returns {Function}
212
+ */
213
+ export function naturalDesc(transform) {
214
+ if (typeof transform === "function") {
215
+ return (a, b) => {
216
+ b = transform(b)
217
+ a = transform(a)
218
+ return compareUndefinedNull(a, b) ?? b.localeCompare(a, undefined, { numeric: true })
219
+ }
220
+ }
221
+ if (typeof transform === "string" || typeof transform === "number") {
222
+ return (a, b) => {
223
+ const invalid = compareUndefinedNull(a[transform], b[transform])
224
+ return invalid ?? b[transform].localeCompare(a[transform], undefined, { numeric: true })
225
+ }
226
+ }
227
+ return (a, b) => {
228
+ return compareUndefinedNull(a, b) ?? b.localeCompare(a, undefined, { numeric: true })
229
+ }
230
+ }
231
+
179
232
  /**
180
233
  * Combines multiple ascending and descending comparators.
181
234
  * @param {...Function} comparators
package/src/array.test.js CHANGED
@@ -1,8 +1,16 @@
1
1
  import { describe, expect, it, jest } from "@jest/globals"
2
2
 
3
- const { chunk, unique, duplicates, ascending, descending, multilevel, sortN } = await import(
4
- "./array.js"
5
- )
3
+ const {
4
+ chunk,
5
+ unique,
6
+ duplicates,
7
+ ascending,
8
+ descending,
9
+ naturalAsc,
10
+ naturalDesc,
11
+ multilevel,
12
+ sortN,
13
+ } = await import("./array.js")
6
14
 
7
15
  describe("chunk", () => {
8
16
  it("splits array into chunks of specified size", () => {
@@ -484,6 +492,71 @@ describe("descending", () => {
484
492
  })
485
493
  })
486
494
 
495
+ describe("naturalAsc", () => {
496
+ it("sorts strings using natural order when transform is a function", () => {
497
+ const arr = ["a10", "a2", "a1"]
498
+ arr.sort(naturalAsc((s) => s))
499
+ expect(arr).toEqual(["a1", "a2", "a10"])
500
+ })
501
+
502
+ it("sorts by key using natural order and defers undefined/null to the end", () => {
503
+ const arr = [
504
+ { v: "file10" },
505
+ { v: undefined },
506
+ { v: "file2" },
507
+ { v: null },
508
+ { v: "file1" },
509
+ ]
510
+ arr.sort(naturalAsc("v"))
511
+ expect(arr.map((o) => o.v)).toEqual(["file1", "file2", "file10", undefined, null])
512
+ })
513
+
514
+ it("returns 0 for equal values (function and key paths)", () => {
515
+ expect(naturalAsc((x) => x)("a", "a")).toBe(0)
516
+ expect(naturalAsc("v")({ v: "x" }, { v: "x" })).toBe(0)
517
+ })
518
+
519
+ it("default comparator sorts with undefined/null at end but uses non-natural, descending semantics", () => {
520
+ const arr = [undefined, "b", null, "a", "c"]
521
+ arr.sort(naturalAsc())
522
+ expect(arr).toEqual(["a", "b", "c", null, undefined])
523
+ const numericLike = ["a10", "a2", "a1"]
524
+ numericLike.sort(naturalAsc())
525
+ expect(numericLike).toEqual(["a1", "a2", "a10"])
526
+ })
527
+ })
528
+
529
+ describe("naturalDesc", () => {
530
+ it("sorts strings using natural order descending (default path)", () => {
531
+ const arr = [undefined, "a", null, "c", "b"]
532
+ arr.sort(naturalDesc())
533
+ expect(arr).toEqual(["c", "b", "a", null, undefined])
534
+ })
535
+
536
+ it("sorts using transform function in natural descending order", () => {
537
+ const arr = ["a1", "a10", "a2"]
538
+ arr.sort(naturalDesc((s) => s))
539
+ expect(arr).toEqual(["a10", "a2", "a1"])
540
+ })
541
+
542
+ it("sorts by key using natural descending order and defers undefined/null to the end", () => {
543
+ const arr = [
544
+ { v: "file1" },
545
+ { v: "file10" },
546
+ { v: undefined },
547
+ { v: "file2" },
548
+ { v: null },
549
+ ]
550
+ arr.sort(naturalDesc("v"))
551
+ expect(arr.map((o) => o.v)).toEqual(["file10", "file2", "file1", undefined, null])
552
+ })
553
+
554
+ it("returns 0 for equal values (function and key paths)", () => {
555
+ expect(naturalDesc((x) => x)("a", "a")).toBe(0)
556
+ expect(naturalDesc("v")({ v: "x" }, { v: "x" })).toBe(0)
557
+ })
558
+ })
559
+
487
560
  describe("multilevel", () => {
488
561
  it("returns 0 if all comparators return 0", () => {
489
562
  const cmp = multilevel(