@planet-matrix/mobius-model 0.3.0 → 0.4.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/CHANGELOG.md +7 -0
- package/README.md +4 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +18 -3
- package/package.json +3 -3
- package/scripts/build.ts +4 -4
- package/src/basic/README.md +143 -0
- package/src/basic/array.ts +872 -0
- package/src/basic/bigint.ts +114 -0
- package/src/basic/boolean.ts +180 -0
- package/src/basic/error.ts +51 -0
- package/src/basic/function.ts +453 -0
- package/src/basic/helper.ts +276 -0
- package/src/basic/index.ts +15 -0
- package/src/basic/is.ts +320 -0
- package/src/basic/number.ts +178 -0
- package/src/basic/object.ts +58 -0
- package/src/basic/promise.ts +464 -0
- package/src/basic/regexp.ts +7 -0
- package/src/basic/stream.ts +140 -0
- package/src/basic/string.ts +308 -0
- package/src/basic/symbol.ts +164 -0
- package/src/basic/temporal.ts +224 -0
- package/src/index.ts +2 -0
- package/src/type/README.md +330 -0
- package/src/type/array.ts +5 -0
- package/src/type/boolean.ts +471 -0
- package/src/type/class.ts +419 -0
- package/src/type/function.ts +1519 -0
- package/src/type/helper.ts +135 -0
- package/src/type/index.ts +14 -0
- package/src/type/intersection.ts +93 -0
- package/src/type/is.ts +247 -0
- package/src/type/iteration.ts +233 -0
- package/src/type/number.ts +732 -0
- package/src/type/object.ts +788 -0
- package/src/type/path.ts +73 -0
- package/src/type/string.ts +1004 -0
- package/src/type/tuple.ts +2424 -0
- package/src/type/union.ts +108 -0
- package/tests/unit/basic/array.spec.ts +290 -0
- package/tests/unit/basic/bigint.spec.ts +50 -0
- package/tests/unit/basic/boolean.spec.ts +74 -0
- package/tests/unit/basic/error.spec.ts +32 -0
- package/tests/unit/basic/function.spec.ts +175 -0
- package/tests/unit/basic/helper.spec.ts +118 -0
- package/tests/unit/basic/number.spec.ts +74 -0
- package/tests/unit/basic/object.spec.ts +15 -0
- package/tests/unit/basic/promise.spec.ts +232 -0
- package/tests/unit/basic/regexp.spec.ts +11 -0
- package/tests/unit/basic/stream.spec.ts +120 -0
- package/tests/unit/basic/string.spec.ts +74 -0
- package/tests/unit/basic/symbol.spec.ts +72 -0
- package/tests/unit/basic/temporal.spec.ts +78 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/reactor/index.d.ts +0 -3
- package/dist/reactor/index.d.ts.map +0 -1
- package/dist/reactor/reactor-core/flags.d.ts +0 -99
- package/dist/reactor/reactor-core/flags.d.ts.map +0 -1
- package/dist/reactor/reactor-core/index.d.ts +0 -4
- package/dist/reactor/reactor-core/index.d.ts.map +0 -1
- package/dist/reactor/reactor-core/primitive.d.ts +0 -276
- package/dist/reactor/reactor-core/primitive.d.ts.map +0 -1
- package/dist/reactor/reactor-core/reactive-system.d.ts +0 -241
- package/dist/reactor/reactor-core/reactive-system.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/branch.d.ts +0 -19
- package/dist/reactor/reactor-operators/branch.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/convert.d.ts +0 -30
- package/dist/reactor/reactor-operators/convert.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/create.d.ts +0 -26
- package/dist/reactor/reactor-operators/create.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/filter.d.ts +0 -269
- package/dist/reactor/reactor-operators/filter.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/index.d.ts +0 -8
- package/dist/reactor/reactor-operators/index.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/join.d.ts +0 -48
- package/dist/reactor/reactor-operators/join.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/map.d.ts +0 -165
- package/dist/reactor/reactor-operators/map.d.ts.map +0 -1
- package/dist/reactor/reactor-operators/utility.d.ts +0 -48
- package/dist/reactor/reactor-operators/utility.d.ts.map +0 -1
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
debounce,
|
|
5
|
+
functionCompose,
|
|
6
|
+
functionDebounceSimple,
|
|
7
|
+
functionIife,
|
|
8
|
+
functionMemorize,
|
|
9
|
+
functionOnce,
|
|
10
|
+
functionPipe,
|
|
11
|
+
functionThrottle,
|
|
12
|
+
functionThrottleSimple,
|
|
13
|
+
functionThrottleTimeSimple,
|
|
14
|
+
throttleTime,
|
|
15
|
+
} from "#Source/basic/index.ts"
|
|
16
|
+
|
|
17
|
+
test("functionIife invokes immediately", () => {
|
|
18
|
+
expect(functionIife((a: number, b: number) => a + b, 1, 2)).toBe(3)
|
|
19
|
+
expect(functionIife(() => "ok")).toBe("ok")
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test("functionOnce only executes once", () => {
|
|
23
|
+
let calls = 0
|
|
24
|
+
const onceFn = functionOnce((value: number) => {
|
|
25
|
+
calls = calls + 1
|
|
26
|
+
return value * 2
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
expect(onceFn(5)).toBe(10)
|
|
30
|
+
expect(onceFn(10)).toBe(10)
|
|
31
|
+
expect(calls).toBe(1)
|
|
32
|
+
|
|
33
|
+
let times = 0
|
|
34
|
+
const onceWithSubscriber = functionOnce((value: number) => value * 2, (count) => { times = count })
|
|
35
|
+
onceWithSubscriber(5)
|
|
36
|
+
onceWithSubscriber(10)
|
|
37
|
+
expect(times).toBe(2)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test("functionDebounceSimple delays execution", () => {
|
|
41
|
+
vi.useFakeTimers()
|
|
42
|
+
try {
|
|
43
|
+
const spy = vi.fn()
|
|
44
|
+
const debounced = functionDebounceSimple(spy, 100)
|
|
45
|
+
|
|
46
|
+
debounced(5)
|
|
47
|
+
debounced(10)
|
|
48
|
+
expect(spy).not.toHaveBeenCalled()
|
|
49
|
+
|
|
50
|
+
vi.advanceTimersByTime(100)
|
|
51
|
+
expect(spy).toHaveBeenCalledTimes(1)
|
|
52
|
+
expect(spy).toHaveBeenCalledWith(10)
|
|
53
|
+
}
|
|
54
|
+
finally {
|
|
55
|
+
vi.useRealTimers()
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test("debounce resolves all calls with the last result", async () => {
|
|
60
|
+
vi.useFakeTimers()
|
|
61
|
+
try {
|
|
62
|
+
const debounced = debounce((value: number) => value * 2, 100)
|
|
63
|
+
const result1 = debounced(5)
|
|
64
|
+
const result2 = debounced(10)
|
|
65
|
+
|
|
66
|
+
vi.advanceTimersByTime(100)
|
|
67
|
+
await expect(Promise.all([result1, result2])).resolves.toEqual([20, 20])
|
|
68
|
+
}
|
|
69
|
+
finally {
|
|
70
|
+
vi.useRealTimers()
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test("functionThrottleTimeSimple limits calls by time", () => {
|
|
75
|
+
vi.useFakeTimers()
|
|
76
|
+
try {
|
|
77
|
+
const spy = vi.fn()
|
|
78
|
+
const throttled = functionThrottleTimeSimple(spy, 100)
|
|
79
|
+
|
|
80
|
+
throttled(1)
|
|
81
|
+
throttled(2)
|
|
82
|
+
expect(spy).toHaveBeenCalledTimes(1)
|
|
83
|
+
|
|
84
|
+
vi.advanceTimersByTime(100)
|
|
85
|
+
throttled(3)
|
|
86
|
+
expect(spy).toHaveBeenCalledTimes(2)
|
|
87
|
+
}
|
|
88
|
+
finally {
|
|
89
|
+
vi.useRealTimers()
|
|
90
|
+
}
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
test("functionThrottleSimple coalesces async calls", async () => {
|
|
94
|
+
let calls = 0
|
|
95
|
+
// oxlint-disable-next-line require-await
|
|
96
|
+
const throttled = functionThrottleSimple(async () => {
|
|
97
|
+
calls = calls + 1
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
throttled()
|
|
101
|
+
throttled()
|
|
102
|
+
await Promise.resolve()
|
|
103
|
+
expect(calls).toBe(1)
|
|
104
|
+
|
|
105
|
+
throttled()
|
|
106
|
+
await Promise.resolve()
|
|
107
|
+
expect(calls).toBe(2)
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test("throttleTime resolves concurrent calls with one execution", async () => {
|
|
111
|
+
vi.useFakeTimers()
|
|
112
|
+
try {
|
|
113
|
+
const spy = vi.fn((value: number) => value * 2)
|
|
114
|
+
const throttled = throttleTime(spy, 100)
|
|
115
|
+
|
|
116
|
+
const result1 = throttled(2)
|
|
117
|
+
const result2 = throttled(3)
|
|
118
|
+
|
|
119
|
+
await expect(Promise.all([result1, result2])).resolves.toEqual([4, 4])
|
|
120
|
+
expect(spy).toHaveBeenCalledTimes(1)
|
|
121
|
+
|
|
122
|
+
vi.advanceTimersByTime(100)
|
|
123
|
+
const result3 = throttled(4)
|
|
124
|
+
await expect(result3).resolves.toBe(8)
|
|
125
|
+
expect(spy).toHaveBeenCalledTimes(2)
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
vi.useRealTimers()
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test("functionThrottle coalesces concurrent calls", async () => {
|
|
133
|
+
let calls = 0
|
|
134
|
+
const throttled = functionThrottle(() => {
|
|
135
|
+
calls = calls + 1
|
|
136
|
+
return "ok"
|
|
137
|
+
})
|
|
138
|
+
|
|
139
|
+
const result1 = throttled()
|
|
140
|
+
const result2 = throttled()
|
|
141
|
+
|
|
142
|
+
await expect(Promise.all([result1, result2])).resolves.toEqual(["ok", "ok"])
|
|
143
|
+
expect(calls).toBe(1)
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
test("functionCompose applies right-to-left", () => {
|
|
147
|
+
const addOne = (value: number): number => value + 1
|
|
148
|
+
const double = (value: number): number => value * 2
|
|
149
|
+
const composed = functionCompose(addOne, double)
|
|
150
|
+
|
|
151
|
+
expect(composed(2)).toBe(5)
|
|
152
|
+
expect(functionCompose()(3)).toBe(3)
|
|
153
|
+
})
|
|
154
|
+
|
|
155
|
+
test("functionPipe applies left-to-right", () => {
|
|
156
|
+
const addOne = (value: number): number => value + 1
|
|
157
|
+
const double = (value: number): number => value * 2
|
|
158
|
+
const piped = functionPipe(addOne, double)
|
|
159
|
+
|
|
160
|
+
expect(piped(2)).toBe(6)
|
|
161
|
+
expect(functionPipe()(3)).toBe(3)
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test("functionMemorize caches results", () => {
|
|
165
|
+
let calls = 0
|
|
166
|
+
const sum = (a: number, b: number): number => {
|
|
167
|
+
calls = calls + 1
|
|
168
|
+
return a + b
|
|
169
|
+
}
|
|
170
|
+
const memoized = functionMemorize(sum)
|
|
171
|
+
|
|
172
|
+
expect(memoized(1, 2)).toBe(3)
|
|
173
|
+
expect(memoized(1, 2)).toBe(3)
|
|
174
|
+
expect(calls).toBe(1)
|
|
175
|
+
})
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
asIs,
|
|
5
|
+
asNull,
|
|
6
|
+
asUndefined,
|
|
7
|
+
asVoid,
|
|
8
|
+
cases,
|
|
9
|
+
guards,
|
|
10
|
+
ifElse,
|
|
11
|
+
iif,
|
|
12
|
+
isLooseEqual,
|
|
13
|
+
isObjectEqual,
|
|
14
|
+
isStrictEqual,
|
|
15
|
+
tryCatch,
|
|
16
|
+
unless,
|
|
17
|
+
when,
|
|
18
|
+
} from "#Source/basic/index.ts"
|
|
19
|
+
|
|
20
|
+
test("asIs returns the input unchanged", () => {
|
|
21
|
+
expect(asIs(5)).toBe(5)
|
|
22
|
+
expect(asIs("hello", 1, 2, 3)).toBe("hello")
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test("asUndefined always returns undefined", () => {
|
|
26
|
+
expect(asUndefined()).toBeUndefined()
|
|
27
|
+
expect(asUndefined(1, "hello", null)).toBeUndefined()
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test("asNull always returns null", () => {
|
|
31
|
+
expect(asNull()).toBeNull()
|
|
32
|
+
expect(asNull(1, "hello", undefined)).toBeNull()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test("asVoid always returns undefined", () => {
|
|
36
|
+
expect(asVoid()).toBeUndefined()
|
|
37
|
+
expect(asVoid(1, "hello", null)).toBeUndefined()
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test("isStrictEqual compares with ===", () => {
|
|
41
|
+
expect(isStrictEqual(1, 1)).toBe(true)
|
|
42
|
+
expect(isStrictEqual(1, "1")).toBe(false)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test("isLooseEqual compares with ==", () => {
|
|
46
|
+
expect(isLooseEqual(1, "1")).toBe(true)
|
|
47
|
+
expect(isLooseEqual(0, "1")).toBe(false)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test("isObjectEqual compares with Object.is", () => {
|
|
51
|
+
expect(isObjectEqual(Number.NaN, Number.NaN)).toBe(true)
|
|
52
|
+
expect(isObjectEqual(0, -0)).toBe(false)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test("ifElse chooses branch based on predicate", () => {
|
|
56
|
+
const yes = (): string => "yes"
|
|
57
|
+
const no = (): string => "no"
|
|
58
|
+
|
|
59
|
+
expect(ifElse(true, yes, no)).toBe("yes")
|
|
60
|
+
expect(ifElse(false, yes, no)).toBe("no")
|
|
61
|
+
expect(ifElse((value?: number) => (value ?? 0) > 3, yes, no, 4)).toBe("yes")
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test("when invokes whenFn only for truthy predicate", () => {
|
|
65
|
+
const plusOne = (value?: number): number => (value ?? 0) + 1
|
|
66
|
+
|
|
67
|
+
expect(when(true, plusOne, 3)).toBe(4)
|
|
68
|
+
expect(when(false, plusOne, 3)).toBe(3)
|
|
69
|
+
expect(when((value?: number) => (value ?? 0) > 1, plusOne, 2)).toBe(3)
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test("unless invokes unlessFn only for falsy predicate", () => {
|
|
73
|
+
const plusOne = (value?: number): number => (value ?? 0) + 1
|
|
74
|
+
|
|
75
|
+
expect(unless(false, plusOne, 1)).toBe(2)
|
|
76
|
+
expect(unless(true, plusOne, 1)).toBe(1)
|
|
77
|
+
expect(unless((value?: number) => (value ?? 0) > 1, plusOne, 0)).toBe(1)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test("iif returns immediate values based on condition", () => {
|
|
81
|
+
expect(iif(true, "yes", "no")).toBe("yes")
|
|
82
|
+
expect(iif(false, "yes", "no")).toBe("no")
|
|
83
|
+
expect(iif((value?: string) => value === "ok", "ok", "no")).toBe("ok")
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
test("guards returns the first matching guard", () => {
|
|
87
|
+
expect(
|
|
88
|
+
guards(
|
|
89
|
+
[
|
|
90
|
+
[false, "a"],
|
|
91
|
+
[true, "b"],
|
|
92
|
+
],
|
|
93
|
+
"z",
|
|
94
|
+
),
|
|
95
|
+
).toBe("b")
|
|
96
|
+
expect(guards([[false, "a"]], "z")).toBe("z")
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test("cases returns the first predicate match", () => {
|
|
100
|
+
expect(
|
|
101
|
+
cases(
|
|
102
|
+
3,
|
|
103
|
+
[
|
|
104
|
+
[(value?: number): boolean => (value ?? 0) > 5, "big"],
|
|
105
|
+
[(value?: number): boolean => (value ?? 0) > 1, "mid"],
|
|
106
|
+
],
|
|
107
|
+
"small",
|
|
108
|
+
),
|
|
109
|
+
).toBe("mid")
|
|
110
|
+
expect(cases(0, [[(value?: number): boolean => (value ?? 0) > 1, "mid"]], "small")).toBe("small")
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test("tryCatch returns catch value or undefined", () => {
|
|
114
|
+
expect(tryCatch(() => "ok", () => "error")).toBe("ok")
|
|
115
|
+
expect(tryCatch(() => { throw new Error("fail") }, () => "error")).toBe("error")
|
|
116
|
+
expect(tryCatch(() => { throw new Error("fail") }, 0)).toBe(0)
|
|
117
|
+
expect(tryCatch(() => { throw new Error("fail") })).toBeUndefined()
|
|
118
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
between,
|
|
5
|
+
constrainNumber,
|
|
6
|
+
isEven,
|
|
7
|
+
isOdd,
|
|
8
|
+
maxOf,
|
|
9
|
+
maxTo,
|
|
10
|
+
minOf,
|
|
11
|
+
minTo,
|
|
12
|
+
normalize,
|
|
13
|
+
randomBetween,
|
|
14
|
+
randomIntBetween,
|
|
15
|
+
} from "#Source/basic/index.ts"
|
|
16
|
+
|
|
17
|
+
test("normalize scales values to 0-1 and validates ranges", () => {
|
|
18
|
+
expect(normalize(5, 0, 10)).toBe(0.5)
|
|
19
|
+
expect(normalize(2, 2, 6)).toBe(0)
|
|
20
|
+
expect(() => normalize(1, 2, 2)).toThrow("Min and max value are the same.")
|
|
21
|
+
expect(() => normalize(12, 0, 10)).toThrow("Value 12 is not in the range of 0 to 10.")
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test("isEven returns expected values", () => {
|
|
25
|
+
expect(isEven(10)).toBe(true)
|
|
26
|
+
expect(isEven(7)).toBe(false)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("isOdd returns expected values", () => {
|
|
30
|
+
expect(isOdd(7)).toBe(true)
|
|
31
|
+
expect(isOdd(10)).toBe(false)
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test("maxTo clamps values", () => {
|
|
35
|
+
expect(maxTo(5, 10)).toBe(5)
|
|
36
|
+
expect(maxTo(5, 3)).toBe(3)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
test("minTo clamps values", () => {
|
|
40
|
+
expect(minTo(5, 3)).toBe(5)
|
|
41
|
+
expect(minTo(5, 8)).toBe(8)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test("minOf returns expected values", () => {
|
|
45
|
+
expect(minOf(2, 9)).toBe(2)
|
|
46
|
+
expect(minOf(5, -1)).toBe(-1)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
test("maxOf returns expected values", () => {
|
|
50
|
+
expect(maxOf(2, 9)).toBe(9)
|
|
51
|
+
expect(maxOf(5, -1)).toBe(5)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("between clamps to provided bounds", () => {
|
|
55
|
+
expect(between(0, 10, 5)).toBe(5)
|
|
56
|
+
expect(between(0, 10, -3)).toBe(0)
|
|
57
|
+
expect(between(10, 0, 12)).toBe(10)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test("randomBetween returns inclusive values", () => {
|
|
61
|
+
expect(randomBetween(4, 4)).toBe(4)
|
|
62
|
+
expect(randomBetween(10, 10)).toBe(10)
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test("randomIntBetween returns inclusive values regardless of order", () => {
|
|
66
|
+
expect(randomIntBetween(4, 4)).toBe(4)
|
|
67
|
+
expect(randomIntBetween(10, 10)).toBe(10)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test("constrainNumber applies step and bounds", () => {
|
|
71
|
+
expect(constrainNumber(12, { step: 5, max: 10 })).toBe(10)
|
|
72
|
+
expect(constrainNumber(2.6, { step: 0.5, min: 3 })).toBe(3)
|
|
73
|
+
expect(constrainNumber(7, { min: 2, max: 9 })).toBe(7)
|
|
74
|
+
})
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { excludeFields, includeFields } from "#Source/basic/object.ts"
|
|
4
|
+
|
|
5
|
+
test("includeFields picks specified keys", () => {
|
|
6
|
+
expect(includeFields({ a: 1, b: 2, c: 3 }, ["a", "c"])).toEqual({ a: 1, c: 3 })
|
|
7
|
+
// @ts-expect-error - Testing behavior with null input
|
|
8
|
+
expect(includeFields(null, ["a"])).toEqual({})
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test("excludeFields omits specified keys", () => {
|
|
12
|
+
expect(excludeFields({ a: 1, b: 2 }, ["b"])).toEqual({ a: 1 })
|
|
13
|
+
// @ts-expect-error - Testing behavior with undefined input
|
|
14
|
+
expect(excludeFields(undefined, ["a"])).toEqual({})
|
|
15
|
+
})
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
// oxlint-disable require-await
|
|
2
|
+
import { expect, test, vi } from "vitest"
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
isPromiseFailResult,
|
|
6
|
+
promiseCatch,
|
|
7
|
+
promiseConstructFailResult,
|
|
8
|
+
promiseFilterFailResults,
|
|
9
|
+
promiseFilterSuccessResults,
|
|
10
|
+
promiseFinally,
|
|
11
|
+
promiseForever,
|
|
12
|
+
promiseInterval,
|
|
13
|
+
promiseQueue,
|
|
14
|
+
promiseRetryUntil,
|
|
15
|
+
promiseRetryWhile,
|
|
16
|
+
promiseThen,
|
|
17
|
+
} from "#Source/basic/index.ts"
|
|
18
|
+
|
|
19
|
+
test("promiseThen chains and transforms resolved values", async () => {
|
|
20
|
+
const example1 = await promiseThen((value: number) => value * 2, Promise.resolve(3))
|
|
21
|
+
const example2 = await promiseThen((value: string) => `${value}!`, Promise.resolve("ok"))
|
|
22
|
+
|
|
23
|
+
expect(example1).toBe(6)
|
|
24
|
+
expect(example2).toBe("ok!")
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
test("promiseCatch handles rejected and resolved promises", async () => {
|
|
28
|
+
const example1 = await promiseCatch(() => "fallback", Promise.reject(new Error("x")))
|
|
29
|
+
const example2 = await promiseCatch(() => 0, Promise.resolve(3))
|
|
30
|
+
|
|
31
|
+
expect(example1).toBe("fallback")
|
|
32
|
+
expect(example2).toBe(3)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test("promiseFinally runs finalizer and preserves resolution", async () => {
|
|
36
|
+
let cleaned = false
|
|
37
|
+
|
|
38
|
+
const example1 = await promiseFinally(() => {
|
|
39
|
+
cleaned = true
|
|
40
|
+
}, Promise.resolve(10))
|
|
41
|
+
|
|
42
|
+
expect(example1).toBe(10)
|
|
43
|
+
expect(cleaned).toBe(true)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test("promiseConstructFailResult creates standardized failure objects", () => {
|
|
47
|
+
const reason = new Error("x")
|
|
48
|
+
const failResult = promiseConstructFailResult(reason)
|
|
49
|
+
|
|
50
|
+
expect(isPromiseFailResult(failResult)).toBe(true)
|
|
51
|
+
expect(failResult.reason).toBe(reason)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("isPromiseFailResult identifies standardized failure objects", () => {
|
|
55
|
+
const failResult = promiseConstructFailResult(new Error("x"))
|
|
56
|
+
|
|
57
|
+
expect(isPromiseFailResult(failResult)).toBe(true)
|
|
58
|
+
expect(isPromiseFailResult({ reason: "x" })).toBe(false)
|
|
59
|
+
expect(isPromiseFailResult(null)).toBe(false)
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test("promiseFilterSuccessResults keeps only successful values", () => {
|
|
63
|
+
const failResult = promiseConstructFailResult(new Error("x"))
|
|
64
|
+
|
|
65
|
+
const filtered = promiseFilterSuccessResults([1, failResult, 2])
|
|
66
|
+
|
|
67
|
+
expect(filtered).toEqual([1, 2])
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test("promiseFilterFailResults keeps failure values with original indices", async () => {
|
|
71
|
+
const results = await promiseQueue<number>([
|
|
72
|
+
async (): Promise<number> => 1,
|
|
73
|
+
async () => {
|
|
74
|
+
throw new Error("a")
|
|
75
|
+
},
|
|
76
|
+
async () => {
|
|
77
|
+
throw new Error("b")
|
|
78
|
+
},
|
|
79
|
+
])
|
|
80
|
+
|
|
81
|
+
const filtered = promiseFilterFailResults(results)
|
|
82
|
+
|
|
83
|
+
expect(filtered).toHaveLength(2)
|
|
84
|
+
expect(filtered[0]?.index).toBe(1)
|
|
85
|
+
expect(filtered[1]?.index).toBe(2)
|
|
86
|
+
expect(isPromiseFailResult(filtered[0])).toBe(true)
|
|
87
|
+
expect(isPromiseFailResult(filtered[1])).toBe(true)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test("promiseQueue executes makers in sequence and keeps failed results", async () => {
|
|
91
|
+
const results = await promiseQueue<number>([
|
|
92
|
+
async (): Promise<number> => 1,
|
|
93
|
+
async ({ previousResult, index }): Promise<number> => {
|
|
94
|
+
return isPromiseFailResult(previousResult) ? -1 : previousResult + index + 1
|
|
95
|
+
},
|
|
96
|
+
async ({ previousResult }): Promise<number> => {
|
|
97
|
+
if (isPromiseFailResult(previousResult)) {
|
|
98
|
+
return -1
|
|
99
|
+
}
|
|
100
|
+
throw new Error(String(previousResult))
|
|
101
|
+
},
|
|
102
|
+
async ({ previousResult }): Promise<number> => {
|
|
103
|
+
return isPromiseFailResult(previousResult) ? -1 : previousResult + 1
|
|
104
|
+
},
|
|
105
|
+
])
|
|
106
|
+
|
|
107
|
+
expect(results[0]).toBe(1)
|
|
108
|
+
expect(results[1]).toBe(3)
|
|
109
|
+
expect(isPromiseFailResult(results[2])).toBe(true)
|
|
110
|
+
expect(results[3]).toBe(-1)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
test("promiseRetryWhile retries while predicate is true", async () => {
|
|
114
|
+
let attempts = 0
|
|
115
|
+
|
|
116
|
+
const success = await promiseRetryWhile(
|
|
117
|
+
(value) => value < 3,
|
|
118
|
+
async () => {
|
|
119
|
+
attempts = attempts + 1
|
|
120
|
+
return attempts
|
|
121
|
+
},
|
|
122
|
+
{ maxTryTimes: 5 },
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
attempts = 0
|
|
126
|
+
const failed = await promiseRetryWhile(
|
|
127
|
+
() => true,
|
|
128
|
+
async () => {
|
|
129
|
+
attempts = attempts + 1
|
|
130
|
+
throw new Error("x")
|
|
131
|
+
},
|
|
132
|
+
{ maxTryTimes: 2 },
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
await expect(promiseRetryWhile(async () => false, async () => 1, { maxTryTimes: 0 })).rejects.toThrow(
|
|
136
|
+
"`maxTryTimes` must be greater than 0.",
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
expect(success).toBe(3)
|
|
140
|
+
expect(attempts).toBe(2)
|
|
141
|
+
expect(isPromiseFailResult(failed)).toBe(true)
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
test("promiseRetryUntil retries until predicate becomes true", async () => {
|
|
145
|
+
let attempts = 0
|
|
146
|
+
|
|
147
|
+
const success = await promiseRetryUntil(
|
|
148
|
+
(value, time) => value >= 2 && time >= 2,
|
|
149
|
+
async () => {
|
|
150
|
+
attempts = attempts + 1
|
|
151
|
+
return attempts
|
|
152
|
+
},
|
|
153
|
+
{ maxTryTimes: 5 },
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
attempts = 0
|
|
157
|
+
const failed = await promiseRetryUntil(
|
|
158
|
+
() => false,
|
|
159
|
+
async () => {
|
|
160
|
+
attempts = attempts + 1
|
|
161
|
+
throw new Error("x")
|
|
162
|
+
},
|
|
163
|
+
{ maxTryTimes: 2 },
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
await expect(promiseRetryUntil(async () => true, async () => 1, { maxTryTimes: 0 })).rejects.toThrow(
|
|
167
|
+
"`maxTryTimes` must be greater than 0.",
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
expect(success).toBe(2)
|
|
171
|
+
expect(attempts).toBe(2)
|
|
172
|
+
expect(isPromiseFailResult(failed)).toBe(true)
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
test("promiseInterval runs by interval and returns a stopper", async () => {
|
|
176
|
+
vi.useFakeTimers()
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
const calls: number[] = []
|
|
180
|
+
const stop = promiseInterval(10, async (time) => {
|
|
181
|
+
calls.push(time)
|
|
182
|
+
return time
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
vi.advanceTimersByTime(35)
|
|
186
|
+
await Promise.resolve()
|
|
187
|
+
|
|
188
|
+
stop()
|
|
189
|
+
vi.advanceTimersByTime(50)
|
|
190
|
+
|
|
191
|
+
expect(calls).toEqual([1, 2, 3])
|
|
192
|
+
expect(typeof stop).toBe("function")
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
vi.useRealTimers()
|
|
196
|
+
}
|
|
197
|
+
})
|
|
198
|
+
|
|
199
|
+
test("promiseForever loops continuously and reports rejections", async () => {
|
|
200
|
+
vi.useFakeTimers()
|
|
201
|
+
|
|
202
|
+
try {
|
|
203
|
+
let value = 0
|
|
204
|
+
const onRejected = vi.fn()
|
|
205
|
+
const promiseMaker = vi.fn(async () => {
|
|
206
|
+
value = value + 1
|
|
207
|
+
if (value === 1) {
|
|
208
|
+
throw new Error("first")
|
|
209
|
+
}
|
|
210
|
+
return value
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
promiseForever(promiseMaker, { breakTime: 5, onRejected })
|
|
214
|
+
await Promise.resolve()
|
|
215
|
+
await Promise.resolve()
|
|
216
|
+
|
|
217
|
+
expect(promiseMaker).toHaveBeenCalledTimes(1)
|
|
218
|
+
expect(onRejected).toHaveBeenCalledTimes(1)
|
|
219
|
+
expect(onRejected.mock.calls[0]?.[0]).toBe(1)
|
|
220
|
+
expect(isPromiseFailResult(onRejected.mock.calls[0]?.[1])).toBe(true)
|
|
221
|
+
|
|
222
|
+
vi.advanceTimersByTime(16)
|
|
223
|
+
await Promise.resolve()
|
|
224
|
+
await Promise.resolve()
|
|
225
|
+
|
|
226
|
+
expect(promiseMaker.mock.calls.length).toBeGreaterThanOrEqual(2)
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
vi.clearAllTimers()
|
|
230
|
+
vi.useRealTimers()
|
|
231
|
+
}
|
|
232
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { regexpIsEmail } from "#Source/basic/index.ts"
|
|
4
|
+
|
|
5
|
+
test("regexpIsEmail validates email format", () => {
|
|
6
|
+
expect(regexpIsEmail("user@example.com")).toBe(true)
|
|
7
|
+
expect(regexpIsEmail("first.last+tag@example.co")).toBe(true)
|
|
8
|
+
expect(regexpIsEmail("user@localhost")).toBe(false)
|
|
9
|
+
expect(regexpIsEmail("user@example")).toBe(false)
|
|
10
|
+
expect(regexpIsEmail("user@.com")).toBe(false)
|
|
11
|
+
})
|