@planet-matrix/mobius-model 0.5.0 → 0.6.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 +24 -0
- package/README.md +123 -36
- package/dist/index.js +45 -4
- package/dist/index.js.map +183 -11
- package/oxlint.config.ts +6 -0
- package/package.json +16 -10
- package/src/abort/README.md +92 -0
- package/src/abort/abort-manager.ts +278 -0
- package/src/abort/abort-signal-listener-manager.ts +81 -0
- package/src/abort/index.ts +2 -0
- package/src/basic/README.md +69 -118
- package/src/basic/function.ts +81 -62
- package/src/basic/is.ts +152 -71
- package/src/basic/promise.ts +29 -8
- package/src/basic/string.ts +2 -33
- package/src/color/README.md +105 -0
- package/src/color/index.ts +3 -0
- package/src/color/internal.ts +42 -0
- package/src/color/rgb/analyze.ts +236 -0
- package/src/color/rgb/construct.ts +130 -0
- package/src/color/rgb/convert.ts +227 -0
- package/src/color/rgb/derive.ts +303 -0
- package/src/color/rgb/index.ts +6 -0
- package/src/color/rgb/internal.ts +208 -0
- package/src/color/rgb/parse.ts +302 -0
- package/src/color/rgb/serialize.ts +144 -0
- package/src/color/types.ts +57 -0
- package/src/color/xyz/analyze.ts +80 -0
- package/src/color/xyz/construct.ts +19 -0
- package/src/color/xyz/convert.ts +71 -0
- package/src/color/xyz/index.ts +3 -0
- package/src/color/xyz/internal.ts +23 -0
- package/src/css/README.md +93 -0
- package/src/css/class.ts +559 -0
- package/src/css/index.ts +1 -0
- package/src/encoding/README.md +66 -79
- package/src/encoding/base64.ts +13 -4
- package/src/environment/README.md +97 -0
- package/src/environment/basic.ts +26 -0
- package/src/environment/device.ts +311 -0
- package/src/environment/feature.ts +285 -0
- package/src/environment/geo.ts +337 -0
- package/src/environment/index.ts +7 -0
- package/src/environment/runtime.ts +400 -0
- package/src/environment/snapshot.ts +60 -0
- package/src/environment/variable.ts +239 -0
- package/src/event/README.md +90 -0
- package/src/event/class-event-proxy.ts +228 -0
- package/src/event/common.ts +19 -0
- package/src/event/event-manager.ts +203 -0
- package/src/event/index.ts +4 -0
- package/src/event/instance-event-proxy.ts +186 -0
- package/src/event/internal.ts +24 -0
- package/src/exception/README.md +96 -0
- package/src/exception/browser.ts +219 -0
- package/src/exception/index.ts +4 -0
- package/src/exception/nodejs.ts +169 -0
- package/src/exception/normalize.ts +106 -0
- package/src/exception/types.ts +99 -0
- package/src/identifier/README.md +92 -0
- package/src/identifier/id.ts +119 -0
- package/src/identifier/index.ts +2 -0
- package/src/identifier/uuid.ts +187 -0
- package/src/index.ts +16 -1
- package/src/log/README.md +79 -0
- package/src/log/index.ts +5 -0
- package/src/log/log-emitter.ts +72 -0
- package/src/log/log-record.ts +10 -0
- package/src/log/log-scheduler.ts +74 -0
- package/src/log/log-type.ts +8 -0
- package/src/log/logger.ts +543 -0
- package/src/orchestration/README.md +89 -0
- package/src/orchestration/coordination/barrier.ts +214 -0
- package/src/orchestration/coordination/count-down-latch.ts +215 -0
- package/src/orchestration/coordination/errors.ts +98 -0
- package/src/orchestration/coordination/index.ts +16 -0
- package/src/orchestration/coordination/internal/wait-constraints.ts +95 -0
- package/src/orchestration/coordination/internal/wait-queue.ts +109 -0
- package/src/orchestration/coordination/keyed-lock.ts +168 -0
- package/src/orchestration/coordination/mutex.ts +257 -0
- package/src/orchestration/coordination/permit.ts +127 -0
- package/src/orchestration/coordination/read-write-lock.ts +444 -0
- package/src/orchestration/coordination/semaphore.ts +280 -0
- package/src/orchestration/index.ts +1 -0
- package/src/random/README.md +55 -86
- package/src/random/index.ts +1 -1
- package/src/random/string.ts +35 -0
- package/src/reactor/README.md +4 -0
- package/src/reactor/reactor-core/primitive.ts +9 -9
- package/src/reactor/reactor-core/reactive-system.ts +5 -5
- package/src/singleton/README.md +79 -0
- package/src/singleton/factory.ts +55 -0
- package/src/singleton/index.ts +2 -0
- package/src/singleton/manager.ts +204 -0
- package/src/storage/README.md +107 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/table.ts +449 -0
- package/src/timer/README.md +86 -0
- package/src/timer/expiration/expiration-manager.ts +594 -0
- package/src/timer/expiration/index.ts +3 -0
- package/src/timer/expiration/min-heap.ts +208 -0
- package/src/timer/expiration/remaining-manager.ts +241 -0
- package/src/timer/index.ts +1 -0
- package/src/type/README.md +54 -307
- package/src/type/class.ts +2 -2
- package/src/type/index.ts +14 -14
- package/src/type/is.ts +265 -2
- package/src/type/object.ts +37 -0
- package/src/type/string.ts +7 -2
- package/src/type/tuple.ts +6 -6
- package/src/type/union.ts +16 -0
- package/src/web/README.md +77 -0
- package/src/web/capture.ts +35 -0
- package/src/web/clipboard.ts +97 -0
- package/src/web/dom.ts +117 -0
- package/src/web/download.ts +16 -0
- package/src/web/event.ts +46 -0
- package/src/web/index.ts +10 -0
- package/src/web/local-storage.ts +113 -0
- package/src/web/location.ts +28 -0
- package/src/web/permission.ts +172 -0
- package/src/web/script-loader.ts +432 -0
- package/tests/unit/abort/abort-manager.spec.ts +225 -0
- package/tests/unit/abort/abort-signal-listener-manager.spec.ts +62 -0
- package/tests/unit/basic/array.spec.ts +1 -1
- package/tests/unit/basic/stream.spec.ts +1 -1
- package/tests/unit/basic/string.spec.ts +0 -9
- package/tests/unit/color/rgb/analyze.spec.ts +110 -0
- package/tests/unit/color/rgb/construct.spec.ts +56 -0
- package/tests/unit/color/rgb/convert.spec.ts +60 -0
- package/tests/unit/color/rgb/derive.spec.ts +103 -0
- package/tests/unit/color/rgb/parse.spec.ts +66 -0
- package/tests/unit/color/rgb/serialize.spec.ts +46 -0
- package/tests/unit/color/xyz/analyze.spec.ts +33 -0
- package/tests/unit/color/xyz/construct.spec.ts +10 -0
- package/tests/unit/color/xyz/convert.spec.ts +18 -0
- package/tests/unit/css/class.spec.ts +157 -0
- package/tests/unit/environment/basic.spec.ts +20 -0
- package/tests/unit/environment/device.spec.ts +146 -0
- package/tests/unit/environment/feature.spec.ts +388 -0
- package/tests/unit/environment/geo.spec.ts +111 -0
- package/tests/unit/environment/runtime.spec.ts +364 -0
- package/tests/unit/environment/snapshot.spec.ts +4 -0
- package/tests/unit/environment/variable.spec.ts +190 -0
- package/tests/unit/event/class-event-proxy.spec.ts +225 -0
- package/tests/unit/event/event-manager.spec.ts +246 -0
- package/tests/unit/event/instance-event-proxy.spec.ts +187 -0
- package/tests/unit/exception/browser.spec.ts +213 -0
- package/tests/unit/exception/nodejs.spec.ts +144 -0
- package/tests/unit/exception/normalize.spec.ts +57 -0
- package/tests/unit/identifier/id.spec.ts +71 -0
- package/tests/unit/identifier/uuid.spec.ts +85 -0
- package/tests/unit/log/log-emitter.spec.ts +33 -0
- package/tests/unit/log/log-scheduler.spec.ts +40 -0
- package/tests/unit/log/log-type.spec.ts +7 -0
- package/tests/unit/log/logger.spec.ts +222 -0
- package/tests/unit/orchestration/coordination/barrier.spec.ts +96 -0
- package/tests/unit/orchestration/coordination/count-down-latch.spec.ts +63 -0
- package/tests/unit/orchestration/coordination/errors.spec.ts +29 -0
- package/tests/unit/orchestration/coordination/keyed-lock.spec.ts +109 -0
- package/tests/unit/orchestration/coordination/mutex.spec.ts +132 -0
- package/tests/unit/orchestration/coordination/permit.spec.ts +43 -0
- package/tests/unit/orchestration/coordination/read-write-lock.spec.ts +154 -0
- package/tests/unit/orchestration/coordination/semaphore.spec.ts +135 -0
- package/tests/unit/random/string.spec.ts +11 -0
- package/tests/unit/reactor/alien-signals-effect.spec.ts +11 -10
- package/tests/unit/reactor/preact-signal.spec.ts +1 -2
- package/tests/unit/singleton/singleton.spec.ts +49 -0
- package/tests/unit/storage/table.spec.ts +620 -0
- package/tests/unit/timer/expiration/expiration-manager.spec.ts +464 -0
- package/tests/unit/timer/expiration/min-heap.spec.ts +71 -0
- package/tests/unit/timer/expiration/remaining-manager.spec.ts +234 -0
- package/.oxlintrc.json +0 -5
- package/src/random/uuid.ts +0 -103
- package/tests/unit/random/uuid.spec.ts +0 -37
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { afterEach, expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { CoordinationAbortError, CoordinationTimeoutError, ReadWriteLock } from "#Source/orchestration/index.ts"
|
|
4
|
+
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
vi.clearAllTimers()
|
|
7
|
+
vi.useRealTimers()
|
|
8
|
+
vi.restoreAllMocks()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test("ReadWriteLock tracks reader and writer state across immediate acquisitions", () => {
|
|
12
|
+
const readWriteLock = new ReadWriteLock()
|
|
13
|
+
|
|
14
|
+
expect(readWriteLock.isLocked()).toBe(false)
|
|
15
|
+
expect(readWriteLock.isReadLocked()).toBe(false)
|
|
16
|
+
expect(readWriteLock.isWriteLocked()).toBe(false)
|
|
17
|
+
|
|
18
|
+
const firstReader = readWriteLock.tryAcquireRead()
|
|
19
|
+
const secondReader = readWriteLock.tryAcquireRead()
|
|
20
|
+
|
|
21
|
+
expect(firstReader?.details).toEqual({ coordination: "read-write-lock", mode: "read" })
|
|
22
|
+
expect(secondReader?.details).toEqual({ coordination: "read-write-lock", mode: "read" })
|
|
23
|
+
expect(readWriteLock.getActiveReaderCount()).toBe(2)
|
|
24
|
+
expect(readWriteLock.isReadLocked()).toBe(true)
|
|
25
|
+
expect(readWriteLock.tryAcquireWrite()).toBeUndefined()
|
|
26
|
+
|
|
27
|
+
firstReader?.release()
|
|
28
|
+
secondReader?.release()
|
|
29
|
+
|
|
30
|
+
const writer = readWriteLock.tryAcquireWrite()
|
|
31
|
+
|
|
32
|
+
expect(writer?.details).toEqual({ coordination: "read-write-lock", mode: "write" })
|
|
33
|
+
expect(readWriteLock.isWriteLocked()).toBe(true)
|
|
34
|
+
expect(readWriteLock.isLocked()).toBe(true)
|
|
35
|
+
|
|
36
|
+
writer?.release()
|
|
37
|
+
|
|
38
|
+
expect(readWriteLock.isLocked()).toBe(false)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test("ReadWriteLock keeps queued writers ahead of later readers", async () => {
|
|
42
|
+
const readWriteLock = new ReadWriteLock()
|
|
43
|
+
const activeReader = readWriteLock.tryAcquireRead()
|
|
44
|
+
|
|
45
|
+
expect(activeReader).toBeDefined()
|
|
46
|
+
|
|
47
|
+
const writerPromise = readWriteLock.acquireWrite()
|
|
48
|
+
const lateReaderPromise = readWriteLock.acquireRead()
|
|
49
|
+
|
|
50
|
+
expect(readWriteLock.getPendingWriterCount()).toBe(1)
|
|
51
|
+
expect(readWriteLock.getPendingReaderCount()).toBe(1)
|
|
52
|
+
expect(readWriteLock.getPendingCount()).toBe(2)
|
|
53
|
+
|
|
54
|
+
activeReader?.release()
|
|
55
|
+
|
|
56
|
+
const writer = await writerPromise
|
|
57
|
+
let lateReaderResolved = false
|
|
58
|
+
void lateReaderPromise.then(() => {
|
|
59
|
+
lateReaderResolved = true
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
await Promise.resolve()
|
|
63
|
+
|
|
64
|
+
expect(readWriteLock.isWriteLocked()).toBe(true)
|
|
65
|
+
expect(lateReaderResolved).toBe(false)
|
|
66
|
+
|
|
67
|
+
writer.release()
|
|
68
|
+
|
|
69
|
+
const lateReader = await lateReaderPromise
|
|
70
|
+
|
|
71
|
+
expect(readWriteLock.getPendingCount()).toBe(0)
|
|
72
|
+
expect(readWriteLock.getActiveReaderCount()).toBe(1)
|
|
73
|
+
|
|
74
|
+
lateReader.release()
|
|
75
|
+
|
|
76
|
+
expect(readWriteLock.isLocked()).toBe(false)
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
test("ReadWriteLock acquire methods reject on timeout and abort without leaking counts", async () => {
|
|
80
|
+
vi.useFakeTimers()
|
|
81
|
+
|
|
82
|
+
const readWriteLock = new ReadWriteLock()
|
|
83
|
+
const writer = readWriteLock.tryAcquireWrite()
|
|
84
|
+
|
|
85
|
+
expect(writer).toBeDefined()
|
|
86
|
+
|
|
87
|
+
const timeoutPromise = readWriteLock.acquireRead({ timeout: 20 })
|
|
88
|
+
|
|
89
|
+
expect(readWriteLock.getPendingReaderCount()).toBe(1)
|
|
90
|
+
|
|
91
|
+
await vi.advanceTimersByTimeAsync(20)
|
|
92
|
+
|
|
93
|
+
await expect(timeoutPromise).rejects.toBeInstanceOf(CoordinationTimeoutError)
|
|
94
|
+
expect(readWriteLock.getPendingReaderCount()).toBe(0)
|
|
95
|
+
|
|
96
|
+
const abortController = new AbortController()
|
|
97
|
+
const abortPromise = readWriteLock.acquireWrite({ abortSignal: abortController.signal })
|
|
98
|
+
|
|
99
|
+
expect(readWriteLock.getPendingWriterCount()).toBe(1)
|
|
100
|
+
|
|
101
|
+
abortController.abort("cancelled")
|
|
102
|
+
|
|
103
|
+
await expect(abortPromise).rejects.toBeInstanceOf(CoordinationAbortError)
|
|
104
|
+
expect(readWriteLock.getPendingWriterCount()).toBe(0)
|
|
105
|
+
|
|
106
|
+
writer?.release()
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test("ReadWriteLock runExclusive methods release permits after callback completion", async () => {
|
|
110
|
+
const readWriteLock = new ReadWriteLock()
|
|
111
|
+
|
|
112
|
+
await expect(readWriteLock.runExclusiveRead(() => "read")).resolves.toBe("read")
|
|
113
|
+
expect(readWriteLock.isLocked()).toBe(false)
|
|
114
|
+
|
|
115
|
+
const error = new Error("write failed")
|
|
116
|
+
|
|
117
|
+
await expect(readWriteLock.runExclusiveWrite(() => {
|
|
118
|
+
throw error
|
|
119
|
+
})).rejects.toThrow(error)
|
|
120
|
+
|
|
121
|
+
expect(readWriteLock.isLocked()).toBe(false)
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test("ReadWriteLock duplicate release stays silent by default and uses custom handler when provided", () => {
|
|
125
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {
|
|
126
|
+
// no-op to silence warnings during test
|
|
127
|
+
})
|
|
128
|
+
const silentReadWriteLock = new ReadWriteLock()
|
|
129
|
+
const silentPermit = silentReadWriteLock.tryAcquireWrite()
|
|
130
|
+
|
|
131
|
+
expect(silentPermit).toBeDefined()
|
|
132
|
+
|
|
133
|
+
silentPermit?.release()
|
|
134
|
+
silentPermit?.release()
|
|
135
|
+
silentPermit?.release()
|
|
136
|
+
|
|
137
|
+
expect(silentReadWriteLock.isLocked()).toBe(false)
|
|
138
|
+
expect(warnSpy).not.toHaveBeenCalled()
|
|
139
|
+
|
|
140
|
+
const onDuplicateRelease = vi.fn()
|
|
141
|
+
const customReadWriteLock = new ReadWriteLock({ onDuplicateRelease })
|
|
142
|
+
const customPermit = customReadWriteLock.tryAcquireRead()
|
|
143
|
+
|
|
144
|
+
expect(customPermit).toBeDefined()
|
|
145
|
+
|
|
146
|
+
customPermit?.release()
|
|
147
|
+
customPermit?.release()
|
|
148
|
+
customPermit?.release()
|
|
149
|
+
|
|
150
|
+
expect(customReadWriteLock.isLocked()).toBe(false)
|
|
151
|
+
expect(onDuplicateRelease).toHaveBeenCalledTimes(2)
|
|
152
|
+
expect(onDuplicateRelease).toHaveBeenCalledWith("ReadWriteLock permit release was called more than once.")
|
|
153
|
+
expect(warnSpy).not.toHaveBeenCalled()
|
|
154
|
+
})
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { afterEach, expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { CoordinationAbortError, CoordinationTimeoutError, Semaphore } from "#Source/orchestration/index.ts"
|
|
4
|
+
|
|
5
|
+
afterEach(() => {
|
|
6
|
+
vi.clearAllTimers()
|
|
7
|
+
vi.useRealTimers()
|
|
8
|
+
vi.restoreAllMocks()
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
test("Semaphore tracks capacity and grants permits in FIFO order", async () => {
|
|
12
|
+
const semaphore = new Semaphore(2)
|
|
13
|
+
|
|
14
|
+
expect(semaphore.getMaxConcurrency()).toBe(2)
|
|
15
|
+
expect(semaphore.getActiveCount()).toBe(0)
|
|
16
|
+
expect(semaphore.getAvailableCount()).toBe(2)
|
|
17
|
+
expect(semaphore.isSaturated()).toBe(false)
|
|
18
|
+
|
|
19
|
+
const firstPermit = semaphore.tryAcquire()
|
|
20
|
+
const secondPermit = semaphore.tryAcquire()
|
|
21
|
+
|
|
22
|
+
expect(firstPermit).toBeDefined()
|
|
23
|
+
expect(secondPermit).toBeDefined()
|
|
24
|
+
expect(semaphore.isSaturated()).toBe(true)
|
|
25
|
+
expect(semaphore.getActiveCount()).toBe(2)
|
|
26
|
+
expect(semaphore.getAvailableCount()).toBe(0)
|
|
27
|
+
|
|
28
|
+
const thirdPermitPromise = semaphore.acquire()
|
|
29
|
+
const fourthPermitPromise = semaphore.acquire()
|
|
30
|
+
|
|
31
|
+
expect(semaphore.getPendingCount()).toBe(2)
|
|
32
|
+
expect(semaphore.tryAcquire()).toBeUndefined()
|
|
33
|
+
|
|
34
|
+
firstPermit?.release()
|
|
35
|
+
|
|
36
|
+
const thirdPermit = await thirdPermitPromise
|
|
37
|
+
let fourthResolved = false
|
|
38
|
+
void fourthPermitPromise.then(() => {
|
|
39
|
+
fourthResolved = true
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
await Promise.resolve()
|
|
43
|
+
|
|
44
|
+
expect(semaphore.getPendingCount()).toBe(1)
|
|
45
|
+
expect(fourthResolved).toBe(false)
|
|
46
|
+
|
|
47
|
+
secondPermit?.release()
|
|
48
|
+
const fourthPermit = await fourthPermitPromise
|
|
49
|
+
|
|
50
|
+
expect(semaphore.getPendingCount()).toBe(0)
|
|
51
|
+
|
|
52
|
+
thirdPermit.release()
|
|
53
|
+
fourthPermit.release()
|
|
54
|
+
|
|
55
|
+
expect(semaphore.getActiveCount()).toBe(0)
|
|
56
|
+
expect(semaphore.getAvailableCount()).toBe(2)
|
|
57
|
+
expect(semaphore.isSaturated()).toBe(false)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test("Semaphore acquire rejects on timeout and abort without leaking queue entries", async () => {
|
|
61
|
+
vi.useFakeTimers()
|
|
62
|
+
|
|
63
|
+
const semaphore = new Semaphore(1)
|
|
64
|
+
const blockingPermit = semaphore.tryAcquire()
|
|
65
|
+
|
|
66
|
+
expect(blockingPermit).toBeDefined()
|
|
67
|
+
|
|
68
|
+
const timeoutPromise = semaphore.acquire({ timeout: 15 })
|
|
69
|
+
|
|
70
|
+
expect(semaphore.getPendingCount()).toBe(1)
|
|
71
|
+
|
|
72
|
+
await vi.advanceTimersByTimeAsync(15)
|
|
73
|
+
|
|
74
|
+
await expect(timeoutPromise).rejects.toBeInstanceOf(CoordinationTimeoutError)
|
|
75
|
+
expect(semaphore.getPendingCount()).toBe(0)
|
|
76
|
+
|
|
77
|
+
const abortController = new AbortController()
|
|
78
|
+
const abortPromise = semaphore.acquire({ abortSignal: abortController.signal })
|
|
79
|
+
|
|
80
|
+
expect(semaphore.getPendingCount()).toBe(1)
|
|
81
|
+
|
|
82
|
+
abortController.abort("stopped")
|
|
83
|
+
|
|
84
|
+
await expect(abortPromise).rejects.toBeInstanceOf(CoordinationAbortError)
|
|
85
|
+
expect(semaphore.getPendingCount()).toBe(0)
|
|
86
|
+
|
|
87
|
+
blockingPermit?.release()
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test("Semaphore runExclusive releases capacity after callback failure", async () => {
|
|
91
|
+
const semaphore = new Semaphore(1)
|
|
92
|
+
const error = new Error("boom")
|
|
93
|
+
|
|
94
|
+
await expect(semaphore.runExclusive(() => 1)).resolves.toBe(1)
|
|
95
|
+
expect(semaphore.getActiveCount()).toBe(0)
|
|
96
|
+
|
|
97
|
+
await expect(semaphore.runExclusive(() => {
|
|
98
|
+
throw error
|
|
99
|
+
})).rejects.toThrow(error)
|
|
100
|
+
|
|
101
|
+
expect(semaphore.getActiveCount()).toBe(0)
|
|
102
|
+
expect(semaphore.isSaturated()).toBe(false)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test("Semaphore duplicate release stays silent by default and uses custom handler when provided", () => {
|
|
106
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {
|
|
107
|
+
// no-op to silence warnings during test
|
|
108
|
+
})
|
|
109
|
+
const silentSemaphore = new Semaphore(1)
|
|
110
|
+
const silentPermit = silentSemaphore.tryAcquire()
|
|
111
|
+
|
|
112
|
+
expect(silentPermit).toBeDefined()
|
|
113
|
+
|
|
114
|
+
silentPermit?.release()
|
|
115
|
+
silentPermit?.release()
|
|
116
|
+
silentPermit?.release()
|
|
117
|
+
|
|
118
|
+
expect(silentSemaphore.getActiveCount()).toBe(0)
|
|
119
|
+
expect(warnSpy).not.toHaveBeenCalled()
|
|
120
|
+
|
|
121
|
+
const onDuplicateRelease = vi.fn()
|
|
122
|
+
const customSemaphore = new Semaphore(1, { onDuplicateRelease })
|
|
123
|
+
const customPermit = customSemaphore.tryAcquire()
|
|
124
|
+
|
|
125
|
+
expect(customPermit).toBeDefined()
|
|
126
|
+
|
|
127
|
+
customPermit?.release()
|
|
128
|
+
customPermit?.release()
|
|
129
|
+
customPermit?.release()
|
|
130
|
+
|
|
131
|
+
expect(customSemaphore.getActiveCount()).toBe(0)
|
|
132
|
+
expect(onDuplicateRelease).toHaveBeenCalledTimes(2)
|
|
133
|
+
expect(onDuplicateRelease).toHaveBeenCalledWith("Semaphore permit release was called more than once.")
|
|
134
|
+
expect(warnSpy).not.toHaveBeenCalled()
|
|
135
|
+
})
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { randomString } from "#Source/random/index.ts"
|
|
4
|
+
|
|
5
|
+
test("randomString returns expected output", () => {
|
|
6
|
+
const value = randomString(12)
|
|
7
|
+
expect(value).toHaveLength(12)
|
|
8
|
+
|
|
9
|
+
const constrained = randomString(10, "ab")
|
|
10
|
+
expect(constrained.split("").every(char => char === "a" || char === "b")).toBe(true)
|
|
11
|
+
})
|
|
@@ -49,16 +49,17 @@ test('should not run untracked inner effect', () => {
|
|
|
49
49
|
let outerEffectTruthyBranchRunTimes = 0;
|
|
50
50
|
const a = signal(() => 3);
|
|
51
51
|
const b = computed(() => a.get() > 0);
|
|
52
|
-
|
|
52
|
+
// outer effect
|
|
53
|
+
effect(() => {
|
|
53
54
|
if (b.get() === true) {
|
|
54
55
|
outerEffectTruthyBranchRunTimes = outerEffectTruthyBranchRunTimes + 1;
|
|
55
|
-
|
|
56
|
+
effect(() => {
|
|
56
57
|
if (a.get() === 0) {
|
|
57
58
|
throw new Error("bad");
|
|
58
59
|
}
|
|
59
|
-
});
|
|
60
|
+
}, { name: "inner effect" });
|
|
60
61
|
}
|
|
61
|
-
});
|
|
62
|
+
}, { name: "outer effect" });
|
|
62
63
|
|
|
63
64
|
expect(outerEffectTruthyBranchRunTimes).toBe(1);
|
|
64
65
|
a.set(2);
|
|
@@ -226,7 +227,7 @@ test('should duplicate subscribers do not affect the notify order', () => {
|
|
|
226
227
|
const srcB = signal(() => 0, { name: "srcB" });
|
|
227
228
|
const order: string[] = [];
|
|
228
229
|
|
|
229
|
-
|
|
230
|
+
effect(() => {
|
|
230
231
|
order.push('a');
|
|
231
232
|
reactiveSystem.setNoActiveNodeAsSub();
|
|
232
233
|
const isOne = srcB.get() === 1;
|
|
@@ -237,7 +238,7 @@ test('should duplicate subscribers do not affect the notify order', () => {
|
|
|
237
238
|
srcB.get();
|
|
238
239
|
srcA.get();
|
|
239
240
|
}, { name: "effect-a" });
|
|
240
|
-
|
|
241
|
+
effect(() => {
|
|
241
242
|
order.push('b');
|
|
242
243
|
srcA.get();
|
|
243
244
|
}, { name: "effect-b" });
|
|
@@ -357,20 +358,20 @@ test('should not execute skipped effects from previous failed flush when updatin
|
|
|
357
358
|
|
|
358
359
|
let effect3Executed = false;
|
|
359
360
|
|
|
360
|
-
|
|
361
|
+
effect(() => {
|
|
361
362
|
a.get();
|
|
362
363
|
}, { name: "effect1" });
|
|
363
|
-
|
|
364
|
+
effect(() => {
|
|
364
365
|
if (a.get() === 2) {
|
|
365
366
|
throw new Error('Error in effect 2');
|
|
366
367
|
}
|
|
367
368
|
}, { name: "effect2" });
|
|
368
|
-
|
|
369
|
+
effect(() => {
|
|
369
370
|
a.get();
|
|
370
371
|
d.get();
|
|
371
372
|
effect3Executed = true;
|
|
372
373
|
}, { name: "effect3" });
|
|
373
|
-
|
|
374
|
+
effect(() => {
|
|
374
375
|
b.get();
|
|
375
376
|
}, { name: "effect4" });
|
|
376
377
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { describe, it, vi, expect } from "vitest";
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import { Signal, computed, effect, effectScope, endBatch, reactiveSystem, signal, startBatch } from '#Source/reactor/index.ts';
|
|
3
|
+
import { Signal, effect, signal } from "#Source/reactor/index.ts"
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
6
|
* 这个测试文件应该与 https://github.com/preactjs/signals/blob/main/packages/core/test/signal.test.tsx 保持同步。
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import type { SingletonItem } from "#Source/singleton/index.ts"
|
|
4
|
+
import {
|
|
5
|
+
getSingletonFactory,
|
|
6
|
+
singletonCollection,
|
|
7
|
+
singletonItem,
|
|
8
|
+
} from "#Source/singleton/index.ts"
|
|
9
|
+
|
|
10
|
+
test("getSingletonFactory caches first produced value", () => {
|
|
11
|
+
let makeCalls = 0
|
|
12
|
+
const createSingleton = getSingletonFactory(() => {
|
|
13
|
+
makeCalls = makeCalls + 1
|
|
14
|
+
return { value: "singleton" }
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const first = createSingleton()
|
|
18
|
+
const second = createSingleton()
|
|
19
|
+
|
|
20
|
+
expect(first).toBe(second)
|
|
21
|
+
expect(first.value).toBe("singleton")
|
|
22
|
+
expect(makeCalls).toBe(1)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test("singletonItem creates named lazy singleton item", () => {
|
|
26
|
+
let valueCalls = 0
|
|
27
|
+
const item = singletonItem("config", () => {
|
|
28
|
+
valueCalls = valueCalls + 1
|
|
29
|
+
return { enabled: true }
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const first = item.getValue()
|
|
33
|
+
const second = item.getValue()
|
|
34
|
+
|
|
35
|
+
expect(item.getName()).toBe("config")
|
|
36
|
+
expect(first).toBe(second)
|
|
37
|
+
expect(first.enabled).toBe(true)
|
|
38
|
+
expect(valueCalls).toBe(1)
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test("singletonCollection resolves named items and throws for missing names", () => {
|
|
42
|
+
const portItem: SingletonItem<string, number> = singletonItem("port", () => 3_000)
|
|
43
|
+
const modeItem: SingletonItem<string, string> = singletonItem("mode", () => "test")
|
|
44
|
+
const collection = singletonCollection([portItem, modeItem])
|
|
45
|
+
|
|
46
|
+
expect(collection.getItem("port")).toBe(3_000)
|
|
47
|
+
expect(collection.getItem("mode")).toBe("test")
|
|
48
|
+
expect(() => collection.getItem("missing")).toThrow("GlobalService: item missing not found")
|
|
49
|
+
})
|