@evolu/common 8.7.0 → 8.8.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/dist/src/Array.d.ts +21 -21
- package/dist/src/Array.js +5 -5
- package/dist/src/Assert.d.ts +478 -141
- package/dist/src/Assert.d.ts.map +1 -1
- package/dist/src/Assert.js +644 -99
- package/dist/src/{Buffer.d.ts → Binary.d.ts} +85 -12
- package/dist/src/Binary.d.ts.map +1 -0
- package/dist/src/{Buffer.js → Binary.js} +206 -24
- package/dist/src/Console.d.ts +2 -10
- package/dist/src/Console.d.ts.map +1 -1
- package/dist/src/Console.js +6 -12
- package/dist/src/Crypto.d.ts +5 -5
- package/dist/src/Crypto.js +1 -1
- package/dist/src/Eq.d.ts +35 -40
- package/dist/src/Eq.d.ts.map +1 -1
- package/dist/src/Eq.js +51 -87
- package/dist/src/Function.d.ts +9 -14
- package/dist/src/Function.d.ts.map +1 -1
- package/dist/src/Function.js +7 -9
- package/dist/src/Http.d.ts +6 -7
- package/dist/src/Http.d.ts.map +1 -1
- package/dist/src/LeakDetector.d.ts.map +1 -1
- package/dist/src/LeakDetector.js +1 -0
- package/dist/src/LockManager.d.ts +1 -1
- package/dist/src/LockManager.js +1 -1
- package/dist/src/Lookup.d.ts +3 -2
- package/dist/src/Lookup.d.ts.map +1 -1
- package/dist/src/Lookup.js +4 -3
- package/dist/src/Number.d.ts +1 -1
- package/dist/src/Number.d.ts.map +1 -1
- package/dist/src/Number.js +1 -1
- package/dist/src/Object.d.ts +4 -4
- package/dist/src/Object.d.ts.map +1 -1
- package/dist/src/Object.js +5 -4
- package/dist/src/Option.d.ts +1 -2
- package/dist/src/Option.d.ts.map +1 -1
- package/dist/src/Option.js +1 -2
- package/dist/src/Platform.js +2 -0
- package/dist/src/Polyfills.js +4 -1
- package/dist/src/Random.d.ts +1 -1
- package/dist/src/Result.d.ts +49 -54
- package/dist/src/Result.d.ts.map +1 -1
- package/dist/src/Result.js +17 -22
- package/dist/src/Set.d.ts +6 -6
- package/dist/src/Set.js +2 -2
- package/dist/src/Sqlite.js +1 -1
- package/dist/src/String.js +3 -2
- package/dist/src/Task.d.ts +32 -32
- package/dist/src/Task.d.ts.map +1 -1
- package/dist/src/Task.js +20 -22
- package/dist/src/Test.d.ts +31 -3
- package/dist/src/Test.d.ts.map +1 -1
- package/dist/src/Test.js +46 -3
- package/dist/src/Time.d.ts +3 -11
- package/dist/src/Time.d.ts.map +1 -1
- package/dist/src/Time.js +10 -18
- package/dist/src/Type.d.ts +87 -85
- package/dist/src/Type.d.ts.map +1 -1
- package/dist/src/Type.js +70 -63
- package/dist/src/Types.d.ts +12 -12
- package/dist/src/Types.js +1 -1
- package/dist/src/WebSocket.d.ts +4 -7
- package/dist/src/WebSocket.d.ts.map +1 -1
- package/dist/src/Worker.d.ts +1 -1
- package/dist/src/Worker.js +7 -7
- package/dist/src/index.d.ts +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/intl/_en.d.ts.map +1 -1
- package/dist/src/intl/_en.js +1 -1
- package/dist/src/intl/ar.d.ts.map +1 -1
- package/dist/src/intl/ar.js +1 -1
- package/dist/src/intl/bn.d.ts.map +1 -1
- package/dist/src/intl/bn.js +1 -1
- package/dist/src/intl/ca.d.ts.map +1 -1
- package/dist/src/intl/ca.js +1 -1
- package/dist/src/intl/cs.d.ts.map +1 -1
- package/dist/src/intl/cs.js +1 -1
- package/dist/src/intl/da.d.ts.map +1 -1
- package/dist/src/intl/da.js +1 -1
- package/dist/src/intl/de.d.ts.map +1 -1
- package/dist/src/intl/de.js +1 -1
- package/dist/src/intl/el.d.ts.map +1 -1
- package/dist/src/intl/el.js +1 -1
- package/dist/src/intl/es.d.ts.map +1 -1
- package/dist/src/intl/es.js +1 -1
- package/dist/src/intl/fa.d.ts.map +1 -1
- package/dist/src/intl/fa.js +1 -1
- package/dist/src/intl/fi.d.ts.map +1 -1
- package/dist/src/intl/fi.js +1 -1
- package/dist/src/intl/fil.d.ts.map +1 -1
- package/dist/src/intl/fil.js +1 -1
- package/dist/src/intl/fr.d.ts.map +1 -1
- package/dist/src/intl/fr.js +1 -1
- package/dist/src/intl/he.d.ts.map +1 -1
- package/dist/src/intl/he.js +1 -1
- package/dist/src/intl/hi.d.ts.map +1 -1
- package/dist/src/intl/hi.js +1 -1
- package/dist/src/intl/hr.d.ts.map +1 -1
- package/dist/src/intl/hr.js +1 -1
- package/dist/src/intl/hu.d.ts.map +1 -1
- package/dist/src/intl/hu.js +1 -1
- package/dist/src/intl/id.d.ts.map +1 -1
- package/dist/src/intl/id.js +1 -1
- package/dist/src/intl/it.d.ts.map +1 -1
- package/dist/src/intl/it.js +1 -1
- package/dist/src/intl/ja.d.ts.map +1 -1
- package/dist/src/intl/ja.js +1 -1
- package/dist/src/intl/ko.d.ts.map +1 -1
- package/dist/src/intl/ko.js +1 -1
- package/dist/src/intl/ml.d.ts.map +1 -1
- package/dist/src/intl/ml.js +1 -1
- package/dist/src/intl/mr.d.ts.map +1 -1
- package/dist/src/intl/mr.js +1 -1
- package/dist/src/intl/ms.d.ts.map +1 -1
- package/dist/src/intl/ms.js +1 -1
- package/dist/src/intl/nb.d.ts.map +1 -1
- package/dist/src/intl/nb.js +1 -1
- package/dist/src/intl/nl.d.ts.map +1 -1
- package/dist/src/intl/nl.js +1 -1
- package/dist/src/intl/pa.d.ts.map +1 -1
- package/dist/src/intl/pa.js +1 -1
- package/dist/src/intl/pl.d.ts.map +1 -1
- package/dist/src/intl/pl.js +1 -1
- package/dist/src/intl/pt-BR.d.ts.map +1 -1
- package/dist/src/intl/pt-BR.js +1 -1
- package/dist/src/intl/pt.d.ts.map +1 -1
- package/dist/src/intl/pt.js +1 -1
- package/dist/src/intl/ro.d.ts.map +1 -1
- package/dist/src/intl/ro.js +1 -1
- package/dist/src/intl/sk.d.ts.map +1 -1
- package/dist/src/intl/sk.js +1 -1
- package/dist/src/intl/sl.d.ts.map +1 -1
- package/dist/src/intl/sl.js +1 -1
- package/dist/src/intl/sv.d.ts.map +1 -1
- package/dist/src/intl/sv.js +1 -1
- package/dist/src/intl/sw.d.ts.map +1 -1
- package/dist/src/intl/sw.js +1 -1
- package/dist/src/intl/ta.d.ts.map +1 -1
- package/dist/src/intl/ta.js +1 -1
- package/dist/src/intl/te.d.ts.map +1 -1
- package/dist/src/intl/te.js +1 -1
- package/dist/src/intl/th.d.ts.map +1 -1
- package/dist/src/intl/th.js +1 -1
- package/dist/src/intl/tr.d.ts.map +1 -1
- package/dist/src/intl/tr.js +1 -1
- package/dist/src/intl/uk.d.ts.map +1 -1
- package/dist/src/intl/uk.js +1 -1
- package/dist/src/intl/ur.d.ts.map +1 -1
- package/dist/src/intl/ur.js +1 -1
- package/dist/src/intl/vi.d.ts.map +1 -1
- package/dist/src/intl/vi.js +1 -1
- package/dist/src/intl/zh-CN.d.ts.map +1 -1
- package/dist/src/intl/zh-CN.js +1 -1
- package/dist/src/intl/zh-TW.d.ts.map +1 -1
- package/dist/src/intl/zh-TW.js +1 -1
- package/dist/src/local-first/Evolu.d.ts +12 -12
- package/dist/src/local-first/Evolu.d.ts.map +1 -1
- package/dist/src/local-first/Evolu.js +2 -2
- package/dist/src/local-first/Protocol.d.ts +14 -76
- package/dist/src/local-first/Protocol.d.ts.map +1 -1
- package/dist/src/local-first/Protocol.js +29 -206
- package/dist/src/local-first/Query.d.ts +11 -11
- package/dist/src/local-first/Query.js +6 -6
- package/dist/src/local-first/Relay.d.ts +2 -2
- package/dist/src/local-first/Schema.d.ts +3 -5
- package/dist/src/local-first/Schema.d.ts.map +1 -1
- package/dist/src/local-first/Schema.js +2 -4
- package/dist/src/local-first/Shared.d.ts.map +1 -1
- package/dist/src/local-first/Shared.js +5 -5
- package/dist/src/local-first/Storage.js +4 -4
- package/dist/src/local-first/Timestamp.d.ts +9 -0
- package/dist/src/local-first/Timestamp.d.ts.map +1 -1
- package/dist/src/local-first/Timestamp.js +13 -10
- package/package.json +7 -4
- package/src/Array.test.ts +809 -0
- package/src/Array.ts +21 -21
- package/src/Assert.test.ts +1181 -0
- package/src/Assert.ts +971 -227
- package/src/BigInt.test.ts +28 -0
- package/src/Binary.test.ts +1128 -0
- package/src/{Buffer.ts → Binary.ts} +273 -46
- package/src/Brand.test.ts +90 -0
- package/src/Cache.test.ts +183 -0
- package/src/Callbacks.test.ts +66 -0
- package/src/Console.test.ts +809 -0
- package/src/Console.ts +9 -17
- package/src/Crypto.test.ts +138 -0
- package/src/Crypto.ts +5 -5
- package/src/Eq.test.ts +333 -0
- package/src/Eq.ts +110 -176
- package/src/Error.test.ts +118 -0
- package/src/Function.test.ts +204 -0
- package/src/Function.ts +10 -15
- package/src/Http.test.ts +816 -0
- package/src/Http.ts +6 -7
- package/src/LeakDetector.test.ts +210 -0
- package/src/LeakDetector.ts +1 -0
- package/src/LockManager.ts +2 -2
- package/src/Lookup.test.ts +418 -0
- package/src/Lookup.ts +4 -3
- package/src/Microtask.test.ts +140 -0
- package/src/Number.test.ts +218 -0
- package/src/Number.ts +2 -2
- package/src/Object.test.ts +293 -0
- package/src/Object.ts +5 -4
- package/src/Option.test.ts +104 -0
- package/src/Option.ts +1 -2
- package/src/Order.test.ts +56 -0
- package/src/Platform.ts +2 -0
- package/src/Polyfills.ts +3 -4
- package/src/Random.test.ts +89 -0
- package/src/Random.ts +1 -1
- package/src/Ref.test.ts +139 -0
- package/src/RefCount.test.ts +184 -0
- package/src/Relation.test.ts +505 -0
- package/src/Resource.test.ts +4623 -0
- package/src/Result.test.ts +1829 -0
- package/src/Result.ts +49 -54
- package/src/Schedule.test.ts +1554 -0
- package/src/Set.test.ts +279 -0
- package/src/Set.ts +6 -6
- package/src/Sqlite.ts +1 -1
- package/src/Store.test.ts +288 -0
- package/src/String.test.ts +54 -0
- package/src/String.ts +3 -3
- package/src/Task.test.ts +10820 -0
- package/src/Task.ts +38 -43
- package/src/Test.test.ts +88 -0
- package/src/Test.ts +49 -3
- package/src/Time.test.ts +926 -0
- package/src/Time.ts +13 -21
- package/src/Type.base64.test.ts +88 -0
- package/src/Type.test.ts +20465 -0
- package/src/Type.ts +114 -111
- package/src/Types.test.ts +324 -0
- package/src/Types.ts +13 -13
- package/src/WebSocket.ts +4 -7
- package/src/Worker.test.ts +755 -0
- package/src/Worker.ts +13 -18
- package/src/index.ts +1 -1
- package/src/intl/_en.ts +1 -1
- package/src/intl/ar.ts +1 -1
- package/src/intl/bn.ts +1 -1
- package/src/intl/ca.ts +1 -1
- package/src/intl/cs.ts +1 -1
- package/src/intl/da.ts +1 -1
- package/src/intl/de.ts +1 -1
- package/src/intl/el.ts +1 -1
- package/src/intl/es.ts +1 -1
- package/src/intl/fa.ts +1 -1
- package/src/intl/fi.ts +1 -1
- package/src/intl/fil.ts +1 -1
- package/src/intl/fr.ts +1 -1
- package/src/intl/he.ts +1 -1
- package/src/intl/hi.ts +1 -1
- package/src/intl/hr.ts +1 -1
- package/src/intl/hu.ts +1 -1
- package/src/intl/id.ts +1 -1
- package/src/intl/intl.test.ts +3106 -0
- package/src/intl/it.ts +1 -1
- package/src/intl/ja.ts +1 -1
- package/src/intl/ko.ts +1 -1
- package/src/intl/ml.ts +1 -1
- package/src/intl/mr.ts +1 -1
- package/src/intl/ms.ts +1 -1
- package/src/intl/nb.ts +1 -1
- package/src/intl/nl.ts +1 -1
- package/src/intl/pa.ts +1 -1
- package/src/intl/pl.ts +1 -1
- package/src/intl/pt-BR.ts +1 -1
- package/src/intl/pt.ts +1 -1
- package/src/intl/ro.ts +1 -1
- package/src/intl/sk.ts +1 -1
- package/src/intl/sl.ts +1 -1
- package/src/intl/sv.ts +1 -1
- package/src/intl/sw.ts +1 -1
- package/src/intl/ta.ts +1 -1
- package/src/intl/te.ts +1 -1
- package/src/intl/th.ts +1 -1
- package/src/intl/tr.ts +1 -1
- package/src/intl/uk.ts +1 -1
- package/src/intl/ur.ts +1 -1
- package/src/intl/vi.ts +1 -1
- package/src/intl/zh-CN.ts +1 -1
- package/src/intl/zh-TW.ts +1 -1
- package/src/local-first/Evolu.test.ts +1921 -0
- package/src/local-first/Evolu.ts +18 -14
- package/src/local-first/Owner.test.ts +83 -0
- package/src/local-first/Protocol.test.ts +1037 -0
- package/src/local-first/Protocol.ts +58 -261
- package/src/local-first/Query.test.ts +349 -0
- package/src/local-first/Query.ts +11 -11
- package/src/local-first/Relay.ts +2 -2
- package/src/local-first/Schema.ts +3 -5
- package/src/local-first/Shared.test.ts +1828 -0
- package/src/local-first/Shared.ts +9 -8
- package/src/local-first/Storage.ts +4 -4
- package/src/local-first/Timestamp.test.ts +374 -0
- package/src/local-first/Timestamp.ts +22 -10
- package/dist/src/Buffer.d.ts.map +0 -1
|
@@ -0,0 +1,809 @@
|
|
|
1
|
+
import { describe, it } from "node:test";
|
|
2
|
+
import {
|
|
3
|
+
appendToArray,
|
|
4
|
+
arrayFrom,
|
|
5
|
+
arrayFromAsync,
|
|
6
|
+
concatArrays,
|
|
7
|
+
createMutableArray,
|
|
8
|
+
dedupeArray,
|
|
9
|
+
emptyArray,
|
|
10
|
+
filterArray,
|
|
11
|
+
firstInArray,
|
|
12
|
+
flatMapArray,
|
|
13
|
+
isNonEmptyArray,
|
|
14
|
+
lastInArray,
|
|
15
|
+
mapArray,
|
|
16
|
+
partitionArray,
|
|
17
|
+
prependToArray,
|
|
18
|
+
reverseArray,
|
|
19
|
+
sortArray,
|
|
20
|
+
spliceArray,
|
|
21
|
+
zipArray,
|
|
22
|
+
type AtLeastTwoReadonlyArray,
|
|
23
|
+
type NonEmptyArray,
|
|
24
|
+
type NonEmptyReadonlyArray,
|
|
25
|
+
} from "./Array.ts";
|
|
26
|
+
import {
|
|
27
|
+
assertEqual,
|
|
28
|
+
assertFalse,
|
|
29
|
+
assertLength,
|
|
30
|
+
assertSame,
|
|
31
|
+
assertTrue,
|
|
32
|
+
} from "./Assert.ts";
|
|
33
|
+
import { identity } from "./Function.ts";
|
|
34
|
+
import { err, ok } from "./Result.ts";
|
|
35
|
+
import { assertType, NonEmptyTrimmedString, PositiveInt } from "./Type.ts";
|
|
36
|
+
|
|
37
|
+
describe("Types", () => {
|
|
38
|
+
it("NonEmptyArray requires at least one element", () => {
|
|
39
|
+
// @ts-expect-error An empty array is not assignable to NonEmptyArray.
|
|
40
|
+
const _invalid: NonEmptyArray<number> = [];
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("NonEmptyReadonlyArray requires at least one element", () => {
|
|
44
|
+
// @ts-expect-error An empty array is not assignable to NonEmptyReadonlyArray.
|
|
45
|
+
const _invalid: NonEmptyReadonlyArray<string> = [];
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("AtLeastTwoReadonlyArray requires at least two elements", () => {
|
|
49
|
+
// @ts-expect-error An empty array is not assignable to AtLeastTwoReadonlyArray.
|
|
50
|
+
const _empty: AtLeastTwoReadonlyArray<string> = [];
|
|
51
|
+
// @ts-expect-error A single-element array is not assignable to AtLeastTwoReadonlyArray.
|
|
52
|
+
const _single: AtLeastTwoReadonlyArray<string> = ["a"];
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe("Constants", () => {
|
|
57
|
+
describe("emptyArray", () => {
|
|
58
|
+
it("is an empty array", () => {
|
|
59
|
+
assertEqual(emptyArray, []);
|
|
60
|
+
assertLength(emptyArray, 0);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("is assignable to any ReadonlyArray<T>", () => {
|
|
64
|
+
const numbers: ReadonlyArray<number> = emptyArray;
|
|
65
|
+
const strings: ReadonlyArray<string> = emptyArray;
|
|
66
|
+
const objects: ReadonlyArray<{ id: number }> = emptyArray;
|
|
67
|
+
|
|
68
|
+
assertType<typeof numbers, ReadonlyArray<number>>();
|
|
69
|
+
assertType<typeof strings, ReadonlyArray<string>>();
|
|
70
|
+
assertType<typeof objects, ReadonlyArray<{ id: number }>>();
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("enables reference equality checks", () => {
|
|
74
|
+
let items: ReadonlyArray<number> = emptyArray;
|
|
75
|
+
assertSame(items, emptyArray);
|
|
76
|
+
|
|
77
|
+
items = [1, 2, 3];
|
|
78
|
+
assertFalse(items === emptyArray);
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
describe("Constructors", () => {
|
|
84
|
+
describe("createMutableArray", () => {
|
|
85
|
+
it("creates a mutable sparse Array", () => {
|
|
86
|
+
const result = createMutableArray<number>(3);
|
|
87
|
+
|
|
88
|
+
assertType<typeof result, Array<number>>();
|
|
89
|
+
assertLength(result, 3);
|
|
90
|
+
assertFalse(0 in result);
|
|
91
|
+
|
|
92
|
+
result[0] = 1;
|
|
93
|
+
result[1] = 2;
|
|
94
|
+
result[2] = 3;
|
|
95
|
+
|
|
96
|
+
assertEqual(result, [1, 2, 3]);
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
describe("arrayFrom", () => {
|
|
101
|
+
it("creates array from iterable", () => {
|
|
102
|
+
const result = arrayFrom(new Set([1, 2, 3]));
|
|
103
|
+
assertEqual(result, [1, 2, 3]);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("returns input unchanged if already an array", () => {
|
|
107
|
+
const input = [1, 2, 3];
|
|
108
|
+
const result = arrayFrom(input);
|
|
109
|
+
assertSame(result, input);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("creates array with specified length", () => {
|
|
113
|
+
const result = arrayFrom(3, identity);
|
|
114
|
+
assertEqual(result, [0, 1, 2]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("returns readonly array", () => {
|
|
118
|
+
const result = arrayFrom(2, () => "x");
|
|
119
|
+
assertType<typeof result, ReadonlyArray<string>>();
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("passes index to callback", () => {
|
|
123
|
+
const result = arrayFrom(4, (i) => i * 10);
|
|
124
|
+
assertEqual(result, [0, 10, 20, 30]);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
describe("arrayFromAsync", () => {
|
|
129
|
+
it("creates array from async iterable", async () => {
|
|
130
|
+
const asyncIterable = {
|
|
131
|
+
async *[Symbol.asyncIterator]() {
|
|
132
|
+
yield await Promise.resolve(1);
|
|
133
|
+
yield await Promise.resolve(2);
|
|
134
|
+
yield await Promise.resolve(3);
|
|
135
|
+
},
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const result = await arrayFromAsync(asyncIterable);
|
|
139
|
+
assertEqual(result, [1, 2, 3]);
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
it("awaits promised values from sync iterable", async () => {
|
|
143
|
+
const result = await arrayFromAsync([
|
|
144
|
+
Promise.resolve(1),
|
|
145
|
+
Promise.resolve(2),
|
|
146
|
+
Promise.resolve(3),
|
|
147
|
+
]);
|
|
148
|
+
|
|
149
|
+
assertEqual(result, [1, 2, 3]);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it("returns readonly array", async () => {
|
|
153
|
+
const result = await arrayFromAsync([Promise.resolve("x")]);
|
|
154
|
+
assertType<typeof result, ReadonlyArray<string>>();
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("Type guards", () => {
|
|
160
|
+
describe("isNonEmptyArray", () => {
|
|
161
|
+
it("returns true for non-empty array", () => {
|
|
162
|
+
const arr = [1, 2, 3];
|
|
163
|
+
|
|
164
|
+
assertTrue(isNonEmptyArray(arr));
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
it("returns false for empty array", () => {
|
|
168
|
+
const arr: Array<number> = [];
|
|
169
|
+
assertFalse(isNonEmptyArray(arr));
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("returns true for single element array", () => {
|
|
173
|
+
const arr = [1];
|
|
174
|
+
assertTrue(isNonEmptyArray(arr));
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
it("narrows mutable array to NonEmptyArray", () => {
|
|
178
|
+
const arr: Array<number> = [1, 2, 3];
|
|
179
|
+
if (isNonEmptyArray(arr)) {
|
|
180
|
+
assertType<typeof arr, NonEmptyArray<number>>();
|
|
181
|
+
firstInArray(arr);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("narrows readonly array to NonEmptyReadonlyArray", () => {
|
|
186
|
+
const arr: ReadonlyArray<number> = [1, 2, 3];
|
|
187
|
+
if (isNonEmptyArray(arr)) {
|
|
188
|
+
assertType<typeof arr, NonEmptyReadonlyArray<number>>();
|
|
189
|
+
// Should work with accessor functions
|
|
190
|
+
firstInArray(arr);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("returns false for empty readonly array", () => {
|
|
195
|
+
const arr: ReadonlyArray<number> = [];
|
|
196
|
+
assertFalse(isNonEmptyArray(arr));
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
describe("Transformations", () => {
|
|
202
|
+
describe("appendToArray", () => {
|
|
203
|
+
it("appends item to empty array", () => {
|
|
204
|
+
const arr: ReadonlyArray<number> = [];
|
|
205
|
+
const result = appendToArray(arr, 1);
|
|
206
|
+
assertEqual(result, [1]);
|
|
207
|
+
assertType<typeof result, NonEmptyReadonlyArray<number>>();
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("appends item to non-empty array", () => {
|
|
211
|
+
const arr: ReadonlyArray<number> = [1, 2];
|
|
212
|
+
const result = appendToArray(arr, 3);
|
|
213
|
+
assertEqual(result, [1, 2, 3]);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("does not mutate original array", () => {
|
|
217
|
+
const arr: ReadonlyArray<number> = [1, 2];
|
|
218
|
+
appendToArray(arr, 3);
|
|
219
|
+
assertEqual(arr, [1, 2]);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("accepts mutable array and returns readonly", () => {
|
|
223
|
+
const mutableArr: Array<number> = [1, 2];
|
|
224
|
+
const result = appendToArray(mutableArr, 3);
|
|
225
|
+
assertEqual(result, [1, 2, 3]);
|
|
226
|
+
assertType<typeof result, NonEmptyReadonlyArray<number>>();
|
|
227
|
+
// Original mutable array is not mutated
|
|
228
|
+
assertEqual(mutableArr, [1, 2]);
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
describe("prependToArray", () => {
|
|
233
|
+
it("prepends item to empty array", () => {
|
|
234
|
+
const arr: ReadonlyArray<number> = [];
|
|
235
|
+
const result = prependToArray(arr, 1);
|
|
236
|
+
assertEqual(result, [1]);
|
|
237
|
+
assertType<typeof result, NonEmptyReadonlyArray<number>>();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("prepends item to non-empty array", () => {
|
|
241
|
+
const arr: ReadonlyArray<number> = [2, 3];
|
|
242
|
+
const result = prependToArray(arr, 1);
|
|
243
|
+
assertEqual(result, [1, 2, 3]);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
it("does not mutate original array", () => {
|
|
247
|
+
const arr: ReadonlyArray<number> = [2, 3];
|
|
248
|
+
prependToArray(arr, 1);
|
|
249
|
+
assertEqual(arr, [2, 3]);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("accepts mutable array and returns readonly", () => {
|
|
253
|
+
const mutableArr: Array<number> = [2, 3];
|
|
254
|
+
const result = prependToArray(mutableArr, 1);
|
|
255
|
+
assertEqual(result, [1, 2, 3]);
|
|
256
|
+
assertType<typeof result, NonEmptyReadonlyArray<number>>();
|
|
257
|
+
// Original mutable array is not mutated
|
|
258
|
+
assertEqual(mutableArr, [2, 3]);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
describe("mapArray", () => {
|
|
263
|
+
it("preserves non-empty type when mapping non-empty array", () => {
|
|
264
|
+
const nonEmpty: NonEmptyReadonlyArray<number> = [1, 2, 3];
|
|
265
|
+
const result = mapArray(nonEmpty, (x) => x * 2);
|
|
266
|
+
assertType<typeof result, NonEmptyReadonlyArray<number>>();
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it("returns readonly array when mapping regular array", () => {
|
|
270
|
+
const regular: ReadonlyArray<number> = [1, 2, 3];
|
|
271
|
+
const result = mapArray(regular, (x) => x * 2);
|
|
272
|
+
assertType<typeof result, ReadonlyArray<number>>();
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("accepts mutable non-empty array and returns readonly", () => {
|
|
276
|
+
const mutableArr: NonEmptyArray<number> = [1, 2, 3];
|
|
277
|
+
const result = mapArray(mutableArr, (x) => x * 2);
|
|
278
|
+
assertType<typeof result, NonEmptyReadonlyArray<number>>();
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it("accepts mutable regular array and returns readonly", () => {
|
|
282
|
+
const mutableArr: Array<number> = [1, 2, 3];
|
|
283
|
+
const result = mapArray(mutableArr, (x) => x * 2);
|
|
284
|
+
assertType<typeof result, ReadonlyArray<number>>();
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("passes the source array to the mapper", () => {
|
|
288
|
+
const array: ReadonlyArray<number> = [1, 2, 3];
|
|
289
|
+
const sourceArrays: Array<ReadonlyArray<number>> = [];
|
|
290
|
+
|
|
291
|
+
mapArray(array, (_value, _index, sourceArray) => {
|
|
292
|
+
assertType<typeof sourceArray, ReadonlyArray<number>>();
|
|
293
|
+
sourceArrays.push(sourceArray);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
assertEqual(sourceArrays, [array, array, array]);
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
it("skips and preserves holes in sparse arrays", () => {
|
|
300
|
+
const sparse = createMutableArray<number>(3);
|
|
301
|
+
sparse[1] = 1;
|
|
302
|
+
const visitedIndices: Array<number> = [];
|
|
303
|
+
|
|
304
|
+
const result = mapArray(sparse, (value, index) => {
|
|
305
|
+
visitedIndices.push(index);
|
|
306
|
+
return value * 2;
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
assertEqual(visitedIndices, [1]);
|
|
310
|
+
assertLength(result, 3);
|
|
311
|
+
assertFalse(0 in result);
|
|
312
|
+
assertEqual(result[1], 2);
|
|
313
|
+
assertFalse(2 in result);
|
|
314
|
+
});
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
describe("flatMapArray", () => {
|
|
318
|
+
it("flattens mapped arrays", () => {
|
|
319
|
+
const arr: ReadonlyArray<number> = [1, 2, 3];
|
|
320
|
+
const result = flatMapArray(arr, (x) => [x, x * 10]);
|
|
321
|
+
assertEqual(result, [1, 10, 2, 20, 3, 30]);
|
|
322
|
+
assertType<typeof result, ReadonlyArray<number>>();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("flattens nested arrays without mapper", () => {
|
|
326
|
+
const arr: ReadonlyArray<ReadonlyArray<number>> = [
|
|
327
|
+
[1, 2],
|
|
328
|
+
[3, 4],
|
|
329
|
+
];
|
|
330
|
+
const result = flatMapArray(arr);
|
|
331
|
+
assertEqual(result, [1, 2, 3, 4]);
|
|
332
|
+
assertType<typeof result, ReadonlyArray<number>>();
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
it("flattens non-empty nested arrays without mapper", () => {
|
|
336
|
+
const arr: NonEmptyReadonlyArray<NonEmptyReadonlyArray<number>> = [
|
|
337
|
+
[1, 2],
|
|
338
|
+
[3, 4],
|
|
339
|
+
];
|
|
340
|
+
const result = flatMapArray(arr);
|
|
341
|
+
assertEqual(result, [1, 2, 3, 4]);
|
|
342
|
+
assertType<typeof result, NonEmptyReadonlyArray<number>>();
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it("preserves non-empty type when mapper returns non-empty", () => {
|
|
346
|
+
const nonEmpty: NonEmptyReadonlyArray<number> = [1, 2, 3];
|
|
347
|
+
const result = flatMapArray(nonEmpty, (x) => [x, x * 10]);
|
|
348
|
+
assertEqual(result, [1, 10, 2, 20, 3, 30]);
|
|
349
|
+
assertType<typeof result, NonEmptyReadonlyArray<number>>();
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("returns readonly array for regular array input", () => {
|
|
353
|
+
const arr: ReadonlyArray<number> = [1, 2];
|
|
354
|
+
const result = flatMapArray(arr, (x) => [x]);
|
|
355
|
+
assertType<typeof result, ReadonlyArray<number>>();
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("does not mutate original array", () => {
|
|
359
|
+
const arr: ReadonlyArray<number> = [1, 2, 3];
|
|
360
|
+
flatMapArray(arr, (x) => [x, x]);
|
|
361
|
+
assertEqual(arr, [1, 2, 3]);
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("passes index and source array to mapper", () => {
|
|
365
|
+
const arr: ReadonlyArray<string> = ["a", "b"];
|
|
366
|
+
const sourceArrays: Array<ReadonlyArray<string>> = [];
|
|
367
|
+
const result = flatMapArray(arr, (x, i, sourceArray) => {
|
|
368
|
+
assertType<typeof sourceArray, ReadonlyArray<string>>();
|
|
369
|
+
sourceArrays.push(sourceArray);
|
|
370
|
+
return [x, String(i)];
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
assertEqual(result, ["a", "0", "b", "1"]);
|
|
374
|
+
assertEqual(sourceArrays, [arr, arr]);
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
it("filters and maps in one pass using [] and [value] pattern", () => {
|
|
378
|
+
const validate = (n: number) =>
|
|
379
|
+
n > 0 ? ok(n) : err(`${n} is not positive`);
|
|
380
|
+
|
|
381
|
+
const fields = [1, -2, 3, -4];
|
|
382
|
+
const errors = flatMapArray(fields, (f) => {
|
|
383
|
+
const result = validate(f);
|
|
384
|
+
return result.ok ? [] : [result.error];
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
assertEqual(errors, ["-2 is not positive", "-4 is not positive"]);
|
|
388
|
+
assertType<typeof errors, ReadonlyArray<string>>();
|
|
389
|
+
});
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
describe("concatArrays", () => {
|
|
393
|
+
it("concatenates two arrays", () => {
|
|
394
|
+
const first: ReadonlyArray<number> = [1, 2];
|
|
395
|
+
const second: ReadonlyArray<number> = [3, 4];
|
|
396
|
+
const result = concatArrays(first, second);
|
|
397
|
+
assertEqual(result, [1, 2, 3, 4]);
|
|
398
|
+
assertType<typeof result, ReadonlyArray<number>>();
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it("returns non-empty when first is non-empty", () => {
|
|
402
|
+
const first: NonEmptyReadonlyArray<number> = [1, 2];
|
|
403
|
+
const second: ReadonlyArray<number> = [];
|
|
404
|
+
const result = concatArrays(first, second);
|
|
405
|
+
assertEqual(result, [1, 2]);
|
|
406
|
+
assertType<typeof result, NonEmptyReadonlyArray<number>>();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
it("returns non-empty when second is non-empty", () => {
|
|
410
|
+
const first: ReadonlyArray<number> = [];
|
|
411
|
+
const second: NonEmptyReadonlyArray<number> = [3, 4];
|
|
412
|
+
const result = concatArrays(first, second);
|
|
413
|
+
assertEqual(result, [3, 4]);
|
|
414
|
+
assertType<typeof result, NonEmptyReadonlyArray<number>>();
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
it("returns non-empty when both are non-empty", () => {
|
|
418
|
+
const first: NonEmptyReadonlyArray<number> = [1];
|
|
419
|
+
const second: NonEmptyReadonlyArray<number> = [2];
|
|
420
|
+
const result = concatArrays(first, second);
|
|
421
|
+
assertEqual(result, [1, 2]);
|
|
422
|
+
assertType<typeof result, NonEmptyReadonlyArray<number>>();
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
it("does not mutate original arrays", () => {
|
|
426
|
+
const first: ReadonlyArray<number> = [1, 2];
|
|
427
|
+
const second: ReadonlyArray<number> = [3, 4];
|
|
428
|
+
concatArrays(first, second);
|
|
429
|
+
assertEqual(first, [1, 2]);
|
|
430
|
+
assertEqual(second, [3, 4]);
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it("accepts mutable arrays and returns readonly", () => {
|
|
434
|
+
const first: Array<number> = [1, 2];
|
|
435
|
+
const second: Array<number> = [3, 4];
|
|
436
|
+
const result = concatArrays(first, second);
|
|
437
|
+
assertEqual(result, [1, 2, 3, 4]);
|
|
438
|
+
assertType<typeof result, ReadonlyArray<number>>();
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
describe("filterArray", () => {
|
|
443
|
+
it("filters array and returns readonly", () => {
|
|
444
|
+
const arr: ReadonlyArray<number> = [1, 2, 3, 4, 5];
|
|
445
|
+
const result = filterArray(arr, (x) => x % 2 === 0);
|
|
446
|
+
assertEqual(result, [2, 4]);
|
|
447
|
+
assertType<typeof result, ReadonlyArray<number>>();
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
it("does not mutate original array", () => {
|
|
451
|
+
const arr: ReadonlyArray<number> = [1, 2, 3, 4, 5];
|
|
452
|
+
filterArray(arr, (x) => x % 2 === 0);
|
|
453
|
+
assertEqual(arr, [1, 2, 3, 4, 5]);
|
|
454
|
+
});
|
|
455
|
+
|
|
456
|
+
it("passes index and source array to predicate", () => {
|
|
457
|
+
const arr: ReadonlyArray<number> = [1, 2, 3];
|
|
458
|
+
const calls: Array<readonly [number, number, ReadonlyArray<number>]> = [];
|
|
459
|
+
|
|
460
|
+
filterArray(arr, (value, index, sourceArray) => {
|
|
461
|
+
assertType<typeof sourceArray, ReadonlyArray<number>>();
|
|
462
|
+
calls.push([value, index, sourceArray]);
|
|
463
|
+
return true;
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
assertEqual(calls, [
|
|
467
|
+
[1, 0, arr],
|
|
468
|
+
[2, 1, arr],
|
|
469
|
+
[3, 2, arr],
|
|
470
|
+
]);
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
it("works with refinements", () => {
|
|
474
|
+
const mixed: ReadonlyArray<NonEmptyTrimmedString | PositiveInt> = [
|
|
475
|
+
NonEmptyTrimmedString.orThrow("hello"),
|
|
476
|
+
PositiveInt.orThrow(42),
|
|
477
|
+
NonEmptyTrimmedString.orThrow("world"),
|
|
478
|
+
PositiveInt.orThrow(100),
|
|
479
|
+
];
|
|
480
|
+
|
|
481
|
+
const positiveInts = filterArray(mixed, PositiveInt.is);
|
|
482
|
+
|
|
483
|
+
// Type narrowing: positiveInts should be ReadonlyArray<PositiveInt>
|
|
484
|
+
assertType<typeof positiveInts, ReadonlyArray<PositiveInt>>();
|
|
485
|
+
|
|
486
|
+
assertLength(positiveInts, 2);
|
|
487
|
+
assertEqual(positiveInts, [42, 100]);
|
|
488
|
+
});
|
|
489
|
+
});
|
|
490
|
+
|
|
491
|
+
describe("dedupeArray", () => {
|
|
492
|
+
it("deduplicates primitives without callback and returns readonly", () => {
|
|
493
|
+
const arr: ReadonlyArray<number> = [1, 2, 1, 3, 2];
|
|
494
|
+
const result = dedupeArray(arr);
|
|
495
|
+
assertEqual(result, [1, 2, 3]);
|
|
496
|
+
assertType<typeof result, ReadonlyArray<number>>();
|
|
497
|
+
// original not mutated
|
|
498
|
+
assertEqual(arr, [1, 2, 1, 3, 2]);
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
it("deduplicates objects by callback and preserves first occurrence", () => {
|
|
502
|
+
const arr = [
|
|
503
|
+
{ id: 1, value: "a" },
|
|
504
|
+
{ id: 2, value: "b" },
|
|
505
|
+
{ id: 1, value: "c" },
|
|
506
|
+
];
|
|
507
|
+
const result = dedupeArray(arr, (x) => x.id);
|
|
508
|
+
assertEqual(result, [
|
|
509
|
+
{ id: 1, value: "a" },
|
|
510
|
+
{ id: 2, value: "b" },
|
|
511
|
+
]);
|
|
512
|
+
assertType<typeof result, ReadonlyArray<{ id: number; value: string }>>();
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
it("preserves non-empty type when deduping non-empty array", () => {
|
|
516
|
+
const nonEmpty: NonEmptyReadonlyArray<number> = [1, 2, 1, 3, 2];
|
|
517
|
+
const result = dedupeArray(nonEmpty);
|
|
518
|
+
assertEqual(result, [1, 2, 3]);
|
|
519
|
+
assertType<typeof result, NonEmptyReadonlyArray<number>>();
|
|
520
|
+
});
|
|
521
|
+
|
|
522
|
+
it("preserves non-empty type with callback on non-empty array", () => {
|
|
523
|
+
const nonEmpty: NonEmptyReadonlyArray<{ id: number; value: string }> = [
|
|
524
|
+
{ id: 1, value: "a" },
|
|
525
|
+
{ id: 2, value: "b" },
|
|
526
|
+
{ id: 1, value: "c" },
|
|
527
|
+
];
|
|
528
|
+
const result = dedupeArray(nonEmpty, (x) => x.id);
|
|
529
|
+
assertEqual(result, [
|
|
530
|
+
{ id: 1, value: "a" },
|
|
531
|
+
{ id: 2, value: "b" },
|
|
532
|
+
]);
|
|
533
|
+
assertType<
|
|
534
|
+
typeof result,
|
|
535
|
+
NonEmptyReadonlyArray<{ id: number; value: string }>
|
|
536
|
+
>();
|
|
537
|
+
});
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
describe("partitionArray", () => {
|
|
541
|
+
it("partitions array by predicate", () => {
|
|
542
|
+
const arr: ReadonlyArray<number> = [1, 2, 3, 4, 5];
|
|
543
|
+
const [evens, odds] = partitionArray(arr, (x) => x % 2 === 0);
|
|
544
|
+
assertEqual(evens, [2, 4]);
|
|
545
|
+
assertEqual(odds, [1, 3, 5]);
|
|
546
|
+
assertType<typeof evens, ReadonlyArray<number>>();
|
|
547
|
+
assertType<typeof odds, ReadonlyArray<number>>();
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
it("accepts mutable array and returns readonly", () => {
|
|
551
|
+
const mutableArr: Array<number> = [1, 2, 3, 4];
|
|
552
|
+
const [trueArr, falseArr] = partitionArray(mutableArr, (x) => x > 2);
|
|
553
|
+
assertEqual(trueArr, [3, 4]);
|
|
554
|
+
assertEqual(falseArr, [1, 2]);
|
|
555
|
+
assertType<typeof trueArr, ReadonlyArray<number>>();
|
|
556
|
+
assertType<typeof falseArr, ReadonlyArray<number>>();
|
|
557
|
+
// Original mutable array is not mutated
|
|
558
|
+
assertEqual(mutableArr, [1, 2, 3, 4]);
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
it("passes index and source array to predicate", () => {
|
|
562
|
+
const arr: ReadonlyArray<string> = ["a", "b", "c"];
|
|
563
|
+
const sourceArrays: Array<ReadonlyArray<string>> = [];
|
|
564
|
+
const [evenIndices, oddIndices] = partitionArray(
|
|
565
|
+
arr,
|
|
566
|
+
(_, i, sourceArray) => {
|
|
567
|
+
assertType<typeof sourceArray, ReadonlyArray<string>>();
|
|
568
|
+
sourceArrays.push(sourceArray);
|
|
569
|
+
return i % 2 === 0;
|
|
570
|
+
},
|
|
571
|
+
);
|
|
572
|
+
assertEqual(evenIndices, ["a", "c"]);
|
|
573
|
+
assertEqual(oddIndices, ["b"]);
|
|
574
|
+
assertEqual(sourceArrays, [arr, arr, arr]);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
it("works with refinements and type narrowing", () => {
|
|
578
|
+
// Using PositiveInt.is as a type guard with partitionArray
|
|
579
|
+
// With actual Evolu types: NonEmptyTrimmedString | PositiveInt
|
|
580
|
+
const mixed: ReadonlyArray<NonEmptyTrimmedString | PositiveInt> = [
|
|
581
|
+
NonEmptyTrimmedString.orThrow("hello"),
|
|
582
|
+
PositiveInt.orThrow(42),
|
|
583
|
+
NonEmptyTrimmedString.orThrow("world"),
|
|
584
|
+
PositiveInt.orThrow(100),
|
|
585
|
+
];
|
|
586
|
+
|
|
587
|
+
// Using partitionArray with PositiveInt.is type guard
|
|
588
|
+
const [positiveInts, strings] = partitionArray(mixed, PositiveInt.is);
|
|
589
|
+
|
|
590
|
+
// Type narrowing with Exclude: positiveInts is PositiveInt, strings is NonEmptyTrimmedString
|
|
591
|
+
assertType<typeof positiveInts, ReadonlyArray<PositiveInt>>();
|
|
592
|
+
assertType<typeof strings, ReadonlyArray<NonEmptyTrimmedString>>();
|
|
593
|
+
|
|
594
|
+
// PositiveInt values are separated from NonEmptyTrimmedString values
|
|
595
|
+
assertLength(positiveInts, 2);
|
|
596
|
+
assertLength(strings, 2);
|
|
597
|
+
|
|
598
|
+
// All values that pass PositiveInt.is are positive integers
|
|
599
|
+
for (const value of positiveInts) {
|
|
600
|
+
assertTrue(PositiveInt.is(value));
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
// All values that don't pass PositiveInt.is are strings (NonEmptyTrimmedString)
|
|
604
|
+
for (const value of strings) {
|
|
605
|
+
assertTrue(NonEmptyTrimmedString.is(value));
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
});
|
|
609
|
+
|
|
610
|
+
describe("sortArray", () => {
|
|
611
|
+
it("sorts array with compareFn", () => {
|
|
612
|
+
const arr: ReadonlyArray<number> = [3, 1, 2];
|
|
613
|
+
const result = sortArray(arr, (a, b) => a - b);
|
|
614
|
+
assertEqual(result, [1, 2, 3]);
|
|
615
|
+
assertType<typeof result, ReadonlyArray<number>>();
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
it("preserves non-empty type", () => {
|
|
619
|
+
const arr: NonEmptyReadonlyArray<number> = [3, 1, 2];
|
|
620
|
+
const result = sortArray(arr, (a, b) => a - b);
|
|
621
|
+
assertEqual(result, [1, 2, 3]);
|
|
622
|
+
assertType<typeof result, NonEmptyReadonlyArray<number>>();
|
|
623
|
+
});
|
|
624
|
+
|
|
625
|
+
it("does not mutate original array", () => {
|
|
626
|
+
const arr: ReadonlyArray<number> = [3, 1, 2];
|
|
627
|
+
sortArray(arr, (a, b) => a - b);
|
|
628
|
+
assertEqual(arr, [3, 1, 2]);
|
|
629
|
+
});
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
describe("reverseArray", () => {
|
|
633
|
+
it("reverses array", () => {
|
|
634
|
+
const arr: ReadonlyArray<number> = [1, 2, 3];
|
|
635
|
+
const result = reverseArray(arr);
|
|
636
|
+
assertEqual(result, [3, 2, 1]);
|
|
637
|
+
assertType<typeof result, ReadonlyArray<number>>();
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
it("preserves non-empty type", () => {
|
|
641
|
+
const arr: NonEmptyReadonlyArray<number> = [1, 2, 3];
|
|
642
|
+
const result = reverseArray(arr);
|
|
643
|
+
assertEqual(result, [3, 2, 1]);
|
|
644
|
+
assertType<typeof result, NonEmptyReadonlyArray<number>>();
|
|
645
|
+
});
|
|
646
|
+
|
|
647
|
+
it("does not mutate original array", () => {
|
|
648
|
+
const arr: ReadonlyArray<number> = [1, 2, 3];
|
|
649
|
+
reverseArray(arr);
|
|
650
|
+
assertEqual(arr, [1, 2, 3]);
|
|
651
|
+
});
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
describe("spliceArray", () => {
|
|
655
|
+
it("removes elements", () => {
|
|
656
|
+
const arr: ReadonlyArray<number> = [1, 2, 3, 4];
|
|
657
|
+
const result = spliceArray(arr, 1, 2);
|
|
658
|
+
assertEqual(result, [1, 4]);
|
|
659
|
+
assertType<typeof result, ReadonlyArray<number>>();
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it("removes and inserts elements", () => {
|
|
663
|
+
const arr: ReadonlyArray<number> = [1, 2, 3];
|
|
664
|
+
const result = spliceArray(arr, 1, 1, 10, 11);
|
|
665
|
+
assertEqual(result, [1, 10, 11, 3]);
|
|
666
|
+
});
|
|
667
|
+
|
|
668
|
+
it("does not mutate original array", () => {
|
|
669
|
+
const arr: ReadonlyArray<number> = [1, 2, 3, 4];
|
|
670
|
+
spliceArray(arr, 1, 2);
|
|
671
|
+
assertEqual(arr, [1, 2, 3, 4]);
|
|
672
|
+
});
|
|
673
|
+
});
|
|
674
|
+
|
|
675
|
+
describe("zipArray", () => {
|
|
676
|
+
it("combines arrays into tuples", () => {
|
|
677
|
+
const result = zipArray([
|
|
678
|
+
[1, 2, 3],
|
|
679
|
+
["a", "b", "c"],
|
|
680
|
+
]);
|
|
681
|
+
assertEqual(result, [
|
|
682
|
+
[1, "a"],
|
|
683
|
+
[2, "b"],
|
|
684
|
+
[3, "c"],
|
|
685
|
+
]);
|
|
686
|
+
|
|
687
|
+
assertType<
|
|
688
|
+
typeof result,
|
|
689
|
+
NonEmptyReadonlyArray<Readonly<[number, string]>>
|
|
690
|
+
>();
|
|
691
|
+
});
|
|
692
|
+
|
|
693
|
+
it("combines three arrays into tuples", () => {
|
|
694
|
+
const result = zipArray([
|
|
695
|
+
[1, 2],
|
|
696
|
+
["a", "b"],
|
|
697
|
+
[true, false],
|
|
698
|
+
]);
|
|
699
|
+
assertEqual(result, [
|
|
700
|
+
[1, "a", true],
|
|
701
|
+
[2, "b", false],
|
|
702
|
+
]);
|
|
703
|
+
});
|
|
704
|
+
|
|
705
|
+
it("stops at shortest array", () => {
|
|
706
|
+
const result = zipArray([
|
|
707
|
+
[1, 2],
|
|
708
|
+
["a", "b", "c", "d"],
|
|
709
|
+
]);
|
|
710
|
+
assertEqual(result, [
|
|
711
|
+
[1, "a"],
|
|
712
|
+
[2, "b"],
|
|
713
|
+
]);
|
|
714
|
+
});
|
|
715
|
+
|
|
716
|
+
it("returns empty array when any input is empty", () => {
|
|
717
|
+
const result = zipArray([[1, 2, 3], []]);
|
|
718
|
+
assertEqual(result, []);
|
|
719
|
+
});
|
|
720
|
+
|
|
721
|
+
it("returns empty array for empty outer array", () => {
|
|
722
|
+
const result = zipArray([]);
|
|
723
|
+
assertEqual(result, []);
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
it("handles single array", () => {
|
|
727
|
+
const result = zipArray([[1, 2, 3]]);
|
|
728
|
+
assertEqual(result, [[1], [2], [3]]);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
it("preserves non-empty type when all inputs are non-empty", () => {
|
|
732
|
+
const numbers: NonEmptyReadonlyArray<number> = [1, 2, 3];
|
|
733
|
+
const strings: NonEmptyReadonlyArray<string> = ["a", "b", "c"];
|
|
734
|
+
const result = zipArray([numbers, strings]);
|
|
735
|
+
|
|
736
|
+
assertType<
|
|
737
|
+
typeof result,
|
|
738
|
+
NonEmptyReadonlyArray<Readonly<[number, string]>>
|
|
739
|
+
>();
|
|
740
|
+
|
|
741
|
+
const first = firstInArray(result);
|
|
742
|
+
assertEqual(first, [1, "a"]);
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
it("returns possibly empty tuples when any input is possibly empty", () => {
|
|
746
|
+
const numbers: NonEmptyReadonlyArray<number> = [1, 2, 3];
|
|
747
|
+
const strings: ReadonlyArray<string> = ["a", "b", "c"];
|
|
748
|
+
const result = zipArray([numbers, strings]);
|
|
749
|
+
|
|
750
|
+
assertType<typeof result, ReadonlyArray<Readonly<[number, string]>>>();
|
|
751
|
+
});
|
|
752
|
+
});
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
describe("Accessors", () => {
|
|
756
|
+
describe("firstInArray", () => {
|
|
757
|
+
it("returns first element from non-empty array", () => {
|
|
758
|
+
const arr: NonEmptyReadonlyArray<number> = [1, 2, 3];
|
|
759
|
+
const result = firstInArray(arr);
|
|
760
|
+
assertEqual(result, 1);
|
|
761
|
+
assertType<typeof result, number>();
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
it("returns first element from single element array", () => {
|
|
765
|
+
const arr: NonEmptyReadonlyArray<string> = ["only"];
|
|
766
|
+
const result = firstInArray(arr);
|
|
767
|
+
assertEqual(result, "only");
|
|
768
|
+
});
|
|
769
|
+
|
|
770
|
+
it("does not mutate original array", () => {
|
|
771
|
+
const arr: NonEmptyReadonlyArray<number> = [1, 2, 3];
|
|
772
|
+
firstInArray(arr);
|
|
773
|
+
assertEqual(arr, [1, 2, 3]);
|
|
774
|
+
});
|
|
775
|
+
|
|
776
|
+
it("works with mutable non-empty arrays", () => {
|
|
777
|
+
const arr: NonEmptyArray<number> = [10, 20, 30];
|
|
778
|
+
const result = firstInArray(arr);
|
|
779
|
+
assertEqual(result, 10);
|
|
780
|
+
});
|
|
781
|
+
});
|
|
782
|
+
|
|
783
|
+
describe("lastInArray", () => {
|
|
784
|
+
it("returns last element from non-empty array", () => {
|
|
785
|
+
const arr: NonEmptyReadonlyArray<number> = [1, 2, 3];
|
|
786
|
+
const result = lastInArray(arr);
|
|
787
|
+
assertEqual(result, 3);
|
|
788
|
+
assertType<typeof result, number>();
|
|
789
|
+
});
|
|
790
|
+
|
|
791
|
+
it("returns last element from single element array", () => {
|
|
792
|
+
const arr: NonEmptyReadonlyArray<string> = ["only"];
|
|
793
|
+
const result = lastInArray(arr);
|
|
794
|
+
assertEqual(result, "only");
|
|
795
|
+
});
|
|
796
|
+
|
|
797
|
+
it("does not mutate original array", () => {
|
|
798
|
+
const arr: NonEmptyReadonlyArray<number> = [1, 2, 3];
|
|
799
|
+
lastInArray(arr);
|
|
800
|
+
assertEqual(arr, [1, 2, 3]);
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
it("works with mutable non-empty arrays", () => {
|
|
804
|
+
const arr: NonEmptyArray<number> = [10, 20, 30];
|
|
805
|
+
const result = lastInArray(arr);
|
|
806
|
+
assertEqual(result, 30);
|
|
807
|
+
});
|
|
808
|
+
});
|
|
809
|
+
});
|