@planet-matrix/mobius-model 0.1.4 → 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 +53 -0
- package/README.md +25 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +27 -6
- package/package.json +9 -9
- 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 +3 -1
- package/src/reactor/README.md +18 -0
- package/src/reactor/index.ts +2 -0
- package/src/reactor/reactor-core/primitive.ts +1046 -0
- package/src/{signal/signal-core → reactor/reactor-core}/reactive-system.ts +392 -93
- package/src/reactor/reactor-operators/branch.ts +66 -0
- package/src/reactor/reactor-operators/convert.ts +70 -0
- package/src/reactor/reactor-operators/create.ts +66 -0
- package/src/reactor/reactor-operators/filter.ts +988 -0
- package/src/reactor/reactor-operators/index.ts +7 -0
- package/src/reactor/reactor-operators/join.ts +174 -0
- package/src/reactor/reactor-operators/map.ts +599 -0
- package/src/reactor/reactor-operators/utility.ts +102 -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/tests/unit/{signal/computed.spec.ts → reactor/alien-signals-computed.spec.ts} +15 -10
- package/tests/unit/reactor/alien-signals-effect-scope.spec.ts +86 -0
- package/tests/unit/reactor/alien-signals-effect.spec.ts +395 -0
- package/tests/unit/reactor/alien-signals-topology.spec.ts +361 -0
- package/tests/unit/reactor/alien-signals-trigger.spec.ts +75 -0
- package/tests/unit/reactor/alien-signals-untrack.spec.ts +91 -0
- package/tests/unit/reactor/preact-signal.spec.ts +73 -0
- package/tests/unit/reactor/reactor-core.spec.ts +219 -0
- package/tests/unit/reactor/reactor-operators-branch.spec.ts +33 -0
- package/tests/unit/reactor/reactor-operators-convert.spec.ts +31 -0
- package/tests/unit/reactor/reactor-operators-create.spec.ts +47 -0
- package/tests/unit/reactor/reactor-operators-filter.spec.ts +604 -0
- package/tests/unit/reactor/reactor-operators-join.spec.ts +94 -0
- package/tests/unit/reactor/reactor-operators-map.spec.ts +327 -0
- package/tests/unit/reactor/reactor-operators-utility.spec.ts +55 -0
- package/dist/index.d.ts +0 -2
- package/dist/index.d.ts.map +0 -1
- package/dist/signal/index.d.ts +0 -3
- package/dist/signal/index.d.ts.map +0 -1
- package/dist/signal/signal-core/flags.d.ts +0 -99
- package/dist/signal/signal-core/flags.d.ts.map +0 -1
- package/dist/signal/signal-core/index.d.ts +0 -4
- package/dist/signal/signal-core/index.d.ts.map +0 -1
- package/dist/signal/signal-core/primitive.d.ts +0 -67
- package/dist/signal/signal-core/primitive.d.ts.map +0 -1
- package/dist/signal/signal-core/reactive-system.d.ts +0 -161
- package/dist/signal/signal-core/reactive-system.d.ts.map +0 -1
- package/dist/signal/signal-operators/index.d.ts +0 -4
- package/dist/signal/signal-operators/index.d.ts.map +0 -1
- package/src/signal/index.ts +0 -2
- package/src/signal/signal-core/README.md +0 -4
- package/src/signal/signal-core/primitive.ts +0 -275
- package/src/signal/signal-operators/index.ts +0 -19
- package/tests/unit/signal/effect.spec.ts +0 -108
- /package/src/{signal/signal-core → reactor/reactor-core}/flags.ts +0 -0
- /package/src/{signal/signal-core → reactor/reactor-core}/index.ts +0 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
streamConsumeInAsyncMacroTask,
|
|
5
|
+
streamConsumeInSyncMacroTask,
|
|
6
|
+
streamFromArray,
|
|
7
|
+
streamTransformInAsyncMacroTask,
|
|
8
|
+
} from "#Source/basic/index.ts"
|
|
9
|
+
|
|
10
|
+
test("streamFromArray creates a readable stream from array values", async () => {
|
|
11
|
+
const source = [1, 2, 3]
|
|
12
|
+
const values: number[] = []
|
|
13
|
+
|
|
14
|
+
const stream = streamFromArray(source)
|
|
15
|
+
for await (const value of stream) {
|
|
16
|
+
values.push(value)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
expect(values).toEqual(source)
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
test("streamConsumeInSyncMacroTask consumes stream and handles callback errors", async () => {
|
|
23
|
+
const consumed: number[] = []
|
|
24
|
+
let doneCalled = false
|
|
25
|
+
|
|
26
|
+
await streamConsumeInSyncMacroTask<number>({
|
|
27
|
+
readableStream: streamFromArray([1, 2, 3]),
|
|
28
|
+
onValue: (chunk) => {
|
|
29
|
+
consumed.push(chunk)
|
|
30
|
+
},
|
|
31
|
+
onDone: () => {
|
|
32
|
+
doneCalled = true
|
|
33
|
+
},
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
expect(consumed).toEqual([1, 2, 3])
|
|
37
|
+
expect(doneCalled).toBe(true)
|
|
38
|
+
|
|
39
|
+
let errorMessage = ""
|
|
40
|
+
let doneCalledInErrorCase = false
|
|
41
|
+
await streamConsumeInSyncMacroTask<number>({
|
|
42
|
+
readableStream: streamFromArray([1, 2]),
|
|
43
|
+
onValue: (chunk) => {
|
|
44
|
+
if (chunk === 2) {
|
|
45
|
+
throw new Error("boom")
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
onDone: () => {
|
|
49
|
+
doneCalledInErrorCase = true
|
|
50
|
+
},
|
|
51
|
+
onError: (error) => {
|
|
52
|
+
errorMessage = error.message
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
expect(errorMessage).toContain("boom")
|
|
57
|
+
expect(doneCalledInErrorCase).toBe(false)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test("streamConsumeInAsyncMacroTask consumes stream and forwards callback failures", async () => {
|
|
61
|
+
const consumed: number[] = []
|
|
62
|
+
await new Promise<void>((resolve, reject) => {
|
|
63
|
+
streamConsumeInAsyncMacroTask<number>({
|
|
64
|
+
readableStream: streamFromArray([1, 2, 3]),
|
|
65
|
+
onValue: (chunk) => {
|
|
66
|
+
consumed.push(chunk)
|
|
67
|
+
},
|
|
68
|
+
onDone: () => {
|
|
69
|
+
resolve()
|
|
70
|
+
},
|
|
71
|
+
onError: (error) => {
|
|
72
|
+
reject(error)
|
|
73
|
+
},
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
expect(consumed).toEqual([1, 2, 3])
|
|
78
|
+
|
|
79
|
+
let errorMessage = ""
|
|
80
|
+
await new Promise<void>((resolve, reject) => {
|
|
81
|
+
streamConsumeInAsyncMacroTask<number>({
|
|
82
|
+
readableStream: streamFromArray([1]),
|
|
83
|
+
onValue: () => {
|
|
84
|
+
throw new Error("async-boom")
|
|
85
|
+
},
|
|
86
|
+
onDone: () => {
|
|
87
|
+
reject(new Error("onDone should not be called when onValue throws"))
|
|
88
|
+
},
|
|
89
|
+
onError: (error) => {
|
|
90
|
+
errorMessage = error.message
|
|
91
|
+
resolve()
|
|
92
|
+
},
|
|
93
|
+
})
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
expect(errorMessage).toContain("async-boom")
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test("streamTransformInAsyncMacroTask transforms values and handles invalid inputs/errors", async () => {
|
|
100
|
+
expect(() => {
|
|
101
|
+
streamTransformInAsyncMacroTask<number, number>({})
|
|
102
|
+
}).toThrowError("Either readableStream or reader must be provided")
|
|
103
|
+
|
|
104
|
+
const transformed = streamTransformInAsyncMacroTask<number, number>({
|
|
105
|
+
readableStream: streamFromArray([1, 2, 3]),
|
|
106
|
+
onChunk: (chunk, controller) => {
|
|
107
|
+
if (chunk.done === true) {
|
|
108
|
+
controller.close()
|
|
109
|
+
return
|
|
110
|
+
}
|
|
111
|
+
controller.enqueue(chunk.value * 10)
|
|
112
|
+
},
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
const values: number[] = []
|
|
116
|
+
for await (const value of transformed) {
|
|
117
|
+
values.push(value)
|
|
118
|
+
}
|
|
119
|
+
expect(values).toEqual([10, 20, 30])
|
|
120
|
+
})
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
stringCalculateUnits,
|
|
5
|
+
stringCamelCaseToKebabCase,
|
|
6
|
+
stringHelloWord,
|
|
7
|
+
stringKebabCaseToCamelCase,
|
|
8
|
+
stringRandom,
|
|
9
|
+
stringSliceByUnits,
|
|
10
|
+
stringSmartSplit,
|
|
11
|
+
stringSplit,
|
|
12
|
+
stringTruncate,
|
|
13
|
+
stringTruncateByUnits,
|
|
14
|
+
} from "#Source/basic/index.ts"
|
|
15
|
+
|
|
16
|
+
test("stringRandom returns expected output", () => {
|
|
17
|
+
const value = stringRandom(12)
|
|
18
|
+
expect(value).toHaveLength(12)
|
|
19
|
+
|
|
20
|
+
const constrained = stringRandom(10, "ab")
|
|
21
|
+
expect(constrained.split("").every(char => char === "a" || char === "b")).toBe(true)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test("stringCamelCaseToKebabCase converts correctly", () => {
|
|
25
|
+
expect(stringCamelCaseToKebabCase("helloWorld")).toBe("hello-world")
|
|
26
|
+
expect(stringCamelCaseToKebabCase("ab2Cd")).toBe("ab2-cd")
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("stringKebabCaseToCamelCase converts correctly", () => {
|
|
30
|
+
expect(stringKebabCaseToCamelCase("hello-world")).toBe("helloWorld")
|
|
31
|
+
expect(stringKebabCaseToCamelCase("ab2-cd")).toBe("ab2Cd")
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
test("stringHelloWord returns a greeting", () => {
|
|
35
|
+
const value = stringHelloWord()
|
|
36
|
+
expect(typeof value).toBe("string")
|
|
37
|
+
expect(value.length).toBeGreaterThan(0)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test("stringCalculateUnits counts half-width and full-width", () => {
|
|
41
|
+
expect(stringCalculateUnits("a中")).toBe(1.5)
|
|
42
|
+
expect(stringCalculateUnits("中文")).toBe(2)
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
test("stringTruncateByUnits respects unit limit", () => {
|
|
46
|
+
expect(stringTruncateByUnits("a中文", 1.5)).toBe("a中")
|
|
47
|
+
expect(stringTruncateByUnits("abc", 2)).toBe("abc")
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
test("stringSliceByUnits slices by unit indices", () => {
|
|
51
|
+
expect(stringSliceByUnits("a中文", 0, 1.5)).toBe("a中")
|
|
52
|
+
expect(stringSliceByUnits("中文ab", 1, 2)).toBe("文")
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test("stringSplit returns overlapping chunks", () => {
|
|
56
|
+
expect(stringSplit({ input: "hello world", chunkSize: 5, chunkOverlap: 2 })).toEqual([
|
|
57
|
+
"hello",
|
|
58
|
+
"lo wo",
|
|
59
|
+
"world",
|
|
60
|
+
])
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
test("stringSmartSplit respects max length", () => {
|
|
64
|
+
const parts = stringSmartSplit("a\nb\nc", 2)
|
|
65
|
+
expect(parts.join("\n")).toContain("a")
|
|
66
|
+
expect(parts.join("\n")).toContain("b")
|
|
67
|
+
expect(parts.join("\n")).toContain("c")
|
|
68
|
+
expect(parts.every(part => part.length <= 2)).toBe(true)
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test("stringTruncate adds ellipsis", () => {
|
|
72
|
+
expect(stringTruncate("hello world", 5)).toBe("hello...")
|
|
73
|
+
expect(stringTruncate("hi", 5)).toBe("hi")
|
|
74
|
+
})
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
symbolCreateGlobal,
|
|
5
|
+
symbolCreateLocal,
|
|
6
|
+
symbolGetDescription,
|
|
7
|
+
symbolGetKey,
|
|
8
|
+
symbolHasDescription,
|
|
9
|
+
symbolIsAnonymous,
|
|
10
|
+
symbolIsGlobal,
|
|
11
|
+
symbolIsLocal,
|
|
12
|
+
symbolIsWellKnown,
|
|
13
|
+
symbolToString,
|
|
14
|
+
} from "#Source/basic/index.ts"
|
|
15
|
+
|
|
16
|
+
test("symbolCreateLocal creates unique symbols", () => {
|
|
17
|
+
const first = symbolCreateLocal("demo")
|
|
18
|
+
const second = symbolCreateLocal("demo")
|
|
19
|
+
expect(typeof first).toBe("symbol")
|
|
20
|
+
expect(first === second).toBe(false)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test("symbolCreateGlobal returns registry symbols", () => {
|
|
24
|
+
const first = symbolCreateGlobal("demo")
|
|
25
|
+
const second = symbolCreateGlobal("demo")
|
|
26
|
+
expect(typeof first).toBe("symbol")
|
|
27
|
+
expect(first === second).toBe(true)
|
|
28
|
+
expect(first === Symbol.for("demo")).toBe(true)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test("symbolGetKey returns global key", () => {
|
|
32
|
+
expect(symbolGetKey(Symbol.for("demo"))).toBe("demo")
|
|
33
|
+
expect(symbolGetKey(Symbol("demo"))).toBeUndefined()
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test("symbolIsGlobal checks registry membership", () => {
|
|
37
|
+
expect(symbolIsGlobal(Symbol.for("demo"))).toBe(true)
|
|
38
|
+
expect(symbolIsGlobal(Symbol("demo"))).toBe(false)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test("symbolIsLocal checks non-registry symbols", () => {
|
|
42
|
+
expect(symbolIsLocal(Symbol.for("demo"))).toBe(false)
|
|
43
|
+
expect(symbolIsLocal(Symbol("demo"))).toBe(true)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test("symbolHasDescription checks description presence", () => {
|
|
47
|
+
expect(symbolHasDescription(Symbol("demo"))).toBe(true)
|
|
48
|
+
// oxlint-disable-next-line symbol-description
|
|
49
|
+
expect(symbolHasDescription(Symbol())).toBe(false)
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test("symbolGetDescription reads description", () => {
|
|
53
|
+
expect(symbolGetDescription(Symbol("demo"))).toBe("demo")
|
|
54
|
+
// oxlint-disable-next-line symbol-description
|
|
55
|
+
expect(symbolGetDescription(Symbol())).toBeUndefined()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("symbolIsAnonymous checks empty description", () => {
|
|
59
|
+
expect(symbolIsAnonymous(Symbol("demo"))).toBe(false)
|
|
60
|
+
// oxlint-disable-next-line symbol-description
|
|
61
|
+
expect(symbolIsAnonymous(Symbol())).toBe(true)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test("symbolIsWellKnown detects well-known symbols", () => {
|
|
65
|
+
expect(symbolIsWellKnown(Symbol.iterator)).toBe(true)
|
|
66
|
+
expect(symbolIsWellKnown(Symbol("iterator"))).toBe(false)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test("symbolToString converts to string", () => {
|
|
70
|
+
expect(symbolToString(Symbol("demo"))).toBe("Symbol(demo)")
|
|
71
|
+
expect(symbolToString("demo")).toBeUndefined()
|
|
72
|
+
})
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
temporalFormatToRelativeTime,
|
|
5
|
+
temporalFormatToWechatRelativeTime,
|
|
6
|
+
temporalFormatToYYYYMMDD,
|
|
7
|
+
temporalFormatToYYYYMMDDhhmmss,
|
|
8
|
+
temporalFormatTohhmmss,
|
|
9
|
+
temporalHumanize,
|
|
10
|
+
temporalIsOutdated,
|
|
11
|
+
} from "#Source/basic/index.ts"
|
|
12
|
+
|
|
13
|
+
test("temporalIsOutdated returns expected values", () => {
|
|
14
|
+
const now = new Date(2_024, 0, 2, 0, 0, 0).getTime()
|
|
15
|
+
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(now)
|
|
16
|
+
|
|
17
|
+
expect(temporalIsOutdated(new Date(now - 1_000))).toBe(true)
|
|
18
|
+
expect(temporalIsOutdated(new Date(now + 1_000))).toBe(false)
|
|
19
|
+
expect(temporalIsOutdated("not-a-date")).toBe(false)
|
|
20
|
+
|
|
21
|
+
nowSpy.mockRestore()
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
test("temporalFormatToYYYYMMDD formats dates", () => {
|
|
25
|
+
const timestamp = new Date(2_000, 0, 2, 3, 4, 5).getTime()
|
|
26
|
+
|
|
27
|
+
expect(temporalFormatToYYYYMMDD(timestamp)).toBe("2000-01-02")
|
|
28
|
+
expect(temporalFormatToYYYYMMDD(timestamp, "/")).toBe("2000/01/02")
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
test("temporalFormatTohhmmss formats times", () => {
|
|
32
|
+
const timestamp = new Date(2_000, 0, 2, 3, 4, 5).getTime()
|
|
33
|
+
|
|
34
|
+
expect(temporalFormatTohhmmss(timestamp)).toBe("03:04:05")
|
|
35
|
+
expect(temporalFormatTohhmmss(timestamp, "-")).toBe("03-04-05")
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
test("temporalFormatToYYYYMMDDhhmmss formats date and time", () => {
|
|
39
|
+
const timestamp = new Date(2_000, 0, 2, 3, 4, 5).getTime()
|
|
40
|
+
|
|
41
|
+
expect(temporalFormatToYYYYMMDDhhmmss(timestamp)).toBe("2000-01-02 03:04:05")
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test("temporalFormatToRelativeTime returns expected labels", () => {
|
|
45
|
+
const now = new Date(2_024, 0, 2, 0, 0, 0).getTime()
|
|
46
|
+
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(now)
|
|
47
|
+
|
|
48
|
+
expect(temporalFormatToRelativeTime(now - 30_000)).toBe("刚刚")
|
|
49
|
+
expect(temporalFormatToRelativeTime(now - 120_000)).toBe("2 分钟前")
|
|
50
|
+
expect(temporalFormatToRelativeTime(now - 3_600_000)).toBe("1 小时前")
|
|
51
|
+
expect(temporalFormatToRelativeTime(now - 90_000_000)).toBe("昨天")
|
|
52
|
+
expect(temporalFormatToRelativeTime(now - 259_200_000)).toBe("一周内")
|
|
53
|
+
expect(temporalFormatToRelativeTime(now - 691_200_000)).toBe("2023-12-25 00:00:00")
|
|
54
|
+
|
|
55
|
+
nowSpy.mockRestore()
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("temporalFormatToWechatRelativeTime formats WeChat-style labels", () => {
|
|
59
|
+
vi.useFakeTimers()
|
|
60
|
+
vi.setSystemTime(new Date(2_024, 0, 3, 12, 0, 0))
|
|
61
|
+
|
|
62
|
+
expect(temporalFormatToWechatRelativeTime({ timestamp: new Date(2_024, 0, 3, 9, 5, 0).getTime() })).toBe("09:05")
|
|
63
|
+
expect(temporalFormatToWechatRelativeTime({ timestamp: new Date(2_024, 0, 2, 9, 0, 0).getTime() })).toBe("昨天")
|
|
64
|
+
expect(temporalFormatToWechatRelativeTime({ timestamp: new Date(2_024, 0, 2, 9, 0, 0).getTime(), alwaysShowTime: true })).toBe("昨天 09:00")
|
|
65
|
+
expect(temporalFormatToWechatRelativeTime({ timestamp: new Date(2_024, 0, 1, 8, 0, 0).getTime() })).toBe("星期一")
|
|
66
|
+
expect(temporalFormatToWechatRelativeTime({ timestamp: new Date(2_023, 11, 24, 18, 30, 0).getTime() })).toBe("23/12/24")
|
|
67
|
+
|
|
68
|
+
vi.useRealTimers()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
test("temporalHumanize returns expected labels", () => {
|
|
72
|
+
const nowSpy = vi.spyOn(Date, "now").mockReturnValue(3_600_000)
|
|
73
|
+
|
|
74
|
+
expect(temporalHumanize(3_600_000)).toBe("刚刚")
|
|
75
|
+
expect(temporalHumanize(0)).toBe("1 小时前")
|
|
76
|
+
|
|
77
|
+
nowSpy.mockRestore()
|
|
78
|
+
})
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { expect, test } from 'vitest';
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
import { computed, signal } from '#Source/reactor/index.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 这个测试文件应该与 https://github.com/stackblitz/alien-signals/blob/master/tests/computed.spec.ts 保持同步。
|
|
7
|
+
*/
|
|
3
8
|
|
|
4
9
|
test('should correctly propagate changes through computed signals', () => {
|
|
5
10
|
/**
|
|
@@ -11,7 +16,7 @@ test('should correctly propagate changes through computed signals', () => {
|
|
|
11
16
|
* |
|
|
12
17
|
* c3
|
|
13
18
|
*/
|
|
14
|
-
const src = signal(0);
|
|
19
|
+
const src = signal(() => 0);
|
|
15
20
|
const c1 = computed(() => src.get() % 2);
|
|
16
21
|
const c2 = computed(() => c1.get());
|
|
17
22
|
const c3 = computed(() => c2.get());
|
|
@@ -33,7 +38,7 @@ test('should propagate updated source value through chained computations', () =>
|
|
|
33
38
|
* \ /
|
|
34
39
|
* d
|
|
35
40
|
*/
|
|
36
|
-
const src = signal(0);
|
|
41
|
+
const src = signal(() => 0);
|
|
37
42
|
const a = computed(() => src.get());
|
|
38
43
|
const b = computed(() => a.get() % 2);
|
|
39
44
|
const c = computed(() => src.get());
|
|
@@ -50,11 +55,11 @@ test('should handle flags are indirectly updated during resolve pending', () =>
|
|
|
50
55
|
* |
|
|
51
56
|
* b
|
|
52
57
|
* / \
|
|
53
|
-
* c
|
|
58
|
+
* c |
|
|
54
59
|
* \ /
|
|
55
60
|
* d
|
|
56
61
|
*/
|
|
57
|
-
const a = signal(false);
|
|
62
|
+
const a = signal(() => false);
|
|
58
63
|
const b = computed(() => a.get());
|
|
59
64
|
const c = computed(() => {
|
|
60
65
|
b.get();
|
|
@@ -76,17 +81,17 @@ test('should not update if the signal value is reverted', () => {
|
|
|
76
81
|
* |
|
|
77
82
|
* c1
|
|
78
83
|
*/
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
const src = signal(() => 0);
|
|
85
|
+
let c1RunTimes = 0;
|
|
81
86
|
const c1 = computed(() => {
|
|
82
|
-
|
|
87
|
+
c1RunTimes = c1RunTimes + 1;
|
|
83
88
|
return src.get();
|
|
84
89
|
});
|
|
85
90
|
|
|
86
91
|
expect(c1.get()).toBe(0);
|
|
87
|
-
expect(
|
|
92
|
+
expect(c1RunTimes).toBe(1);
|
|
88
93
|
src.set(1);
|
|
89
94
|
src.set(0);
|
|
90
95
|
expect(c1.get()).toBe(0);
|
|
91
|
-
expect(
|
|
96
|
+
expect(c1RunTimes).toBe(1);
|
|
92
97
|
});
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { effect, effectScope, signal } from '#Source/reactor/index.ts';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 这个测试文件应该与 https://github.com/stackblitz/alien-signals/blob/master/tests/effectScope.spec.ts 保持同步。
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
test('should not trigger after dispose', () => {
|
|
10
|
+
/**
|
|
11
|
+
* count
|
|
12
|
+
* |
|
|
13
|
+
* e(inner)
|
|
14
|
+
* |
|
|
15
|
+
* e(scope)
|
|
16
|
+
*/
|
|
17
|
+
const count = signal(() => 1);
|
|
18
|
+
let innerEffectRunTimes = 0;
|
|
19
|
+
const scope = effectScope(() => {
|
|
20
|
+
effect(() => {
|
|
21
|
+
innerEffectRunTimes = innerEffectRunTimes + 1;
|
|
22
|
+
count.get();
|
|
23
|
+
});
|
|
24
|
+
expect(innerEffectRunTimes).toBe(1);
|
|
25
|
+
|
|
26
|
+
count.set(2);
|
|
27
|
+
expect(innerEffectRunTimes).toBe(2);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
count.set(3);
|
|
31
|
+
expect(innerEffectRunTimes).toBe(3);
|
|
32
|
+
scope.dispose();
|
|
33
|
+
count.set(4);
|
|
34
|
+
expect(innerEffectRunTimes).toBe(3);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('should dispose inner effects if created in an effect', () => {
|
|
38
|
+
/**
|
|
39
|
+
* source
|
|
40
|
+
* |
|
|
41
|
+
* e(inner)
|
|
42
|
+
* |
|
|
43
|
+
* e(scope)
|
|
44
|
+
* |
|
|
45
|
+
* e(outer)
|
|
46
|
+
*/
|
|
47
|
+
const source = signal(() => 1);
|
|
48
|
+
let innerEffectRunTimes = 0;
|
|
49
|
+
effect(() => {
|
|
50
|
+
const scope = effectScope(() => {
|
|
51
|
+
effect(() => {
|
|
52
|
+
source.get();
|
|
53
|
+
innerEffectRunTimes = innerEffectRunTimes + 1;
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
expect(innerEffectRunTimes).toBe(1);
|
|
57
|
+
|
|
58
|
+
source.set(2);
|
|
59
|
+
expect(innerEffectRunTimes).toBe(2);
|
|
60
|
+
scope.dispose();
|
|
61
|
+
source.set(3);
|
|
62
|
+
expect(innerEffectRunTimes).toBe(2);
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test('should track signal updates in an inner scope when accessed by an outer effect', () => {
|
|
67
|
+
/**
|
|
68
|
+
* source
|
|
69
|
+
* |
|
|
70
|
+
* e(scope)
|
|
71
|
+
* |
|
|
72
|
+
* e(outer)
|
|
73
|
+
*/
|
|
74
|
+
const source = signal(() => 1);
|
|
75
|
+
let outerEffectRunTimes = 0;
|
|
76
|
+
effect(() => {
|
|
77
|
+
effectScope(() => {
|
|
78
|
+
source.get();
|
|
79
|
+
});
|
|
80
|
+
outerEffectRunTimes = outerEffectRunTimes + 1;
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
expect(outerEffectRunTimes).toBe(1);
|
|
84
|
+
source.set(2);
|
|
85
|
+
expect(outerEffectRunTimes).toBe(2);
|
|
86
|
+
});
|