@planet-matrix/mobius-model 0.5.0 → 0.9.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 +61 -0
- package/README.md +123 -36
- package/dist/index.js +715 -4
- package/dist/index.js.map +981 -13
- package/oxlint.config.ts +6 -0
- package/package.json +36 -18
- 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/ai/README.md +1 -0
- package/src/ai/ai.ts +107 -0
- package/src/ai/chat-completion-ai/aihubmix-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/chat-completion-ai.ts +270 -0
- package/src/ai/chat-completion-ai/chat-completion.ts +189 -0
- package/src/ai/chat-completion-ai/index.ts +7 -0
- package/src/ai/chat-completion-ai/lingyiwanwu-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/ohmygpt-chat-completion.ts +78 -0
- package/src/ai/chat-completion-ai/openai-next-chat-completion.ts +78 -0
- package/src/ai/embedding-ai/embedding-ai.ts +63 -0
- package/src/ai/embedding-ai/embedding.ts +50 -0
- package/src/ai/embedding-ai/index.ts +4 -0
- package/src/ai/embedding-ai/openai-next-embedding.ts +23 -0
- package/src/ai/index.ts +4 -0
- package/src/aio/README.md +100 -0
- package/src/aio/content.ts +141 -0
- package/src/aio/index.ts +3 -0
- package/src/aio/json.ts +127 -0
- package/src/aio/prompt.ts +246 -0
- package/src/basic/README.md +72 -116
- package/src/basic/error.ts +19 -5
- package/src/basic/function.ts +83 -64
- package/src/basic/index.ts +1 -0
- package/src/basic/is.ts +152 -71
- package/src/basic/promise.ts +29 -8
- package/src/basic/schedule.ts +111 -0
- package/src/basic/stream.ts +135 -25
- 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/credential/README.md +107 -0
- package/src/credential/api-key.ts +158 -0
- package/src/credential/bearer.ts +73 -0
- package/src/credential/index.ts +4 -0
- package/src/credential/json-web-token.ts +96 -0
- package/src/credential/password.ts +170 -0
- package/src/cron/README.md +86 -0
- package/src/cron/cron.ts +87 -0
- package/src/cron/index.ts +1 -0
- package/src/css/README.md +93 -0
- package/src/css/class.ts +559 -0
- package/src/css/index.ts +1 -0
- package/src/drizzle/README.md +1 -0
- package/src/drizzle/drizzle.ts +1 -0
- package/src/drizzle/helper.ts +47 -0
- package/src/drizzle/index.ts +5 -0
- package/src/drizzle/infer.ts +52 -0
- package/src/drizzle/kysely.ts +8 -0
- package/src/drizzle/pagination.ts +200 -0
- package/src/email/README.md +1 -0
- package/src/email/index.ts +1 -0
- package/src/email/resend.ts +25 -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 +229 -0
- package/src/event/common.ts +29 -0
- package/src/event/event-manager.ts +203 -0
- package/src/event/index.ts +4 -0
- package/src/event/instance-event-proxy.ts +187 -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/form/README.md +25 -0
- package/src/form/index.ts +1 -0
- package/src/form/inputor-controller/base.ts +874 -0
- package/src/form/inputor-controller/boolean.ts +39 -0
- package/src/form/inputor-controller/file.ts +39 -0
- package/src/form/inputor-controller/form.ts +181 -0
- package/src/form/inputor-controller/helper.ts +117 -0
- package/src/form/inputor-controller/index.ts +17 -0
- package/src/form/inputor-controller/multi-select.ts +99 -0
- package/src/form/inputor-controller/number.ts +116 -0
- package/src/form/inputor-controller/select.ts +109 -0
- package/src/form/inputor-controller/text.ts +82 -0
- package/src/http/READMD.md +1 -0
- package/src/http/api/api-core.ts +84 -0
- package/src/http/api/api-handler.ts +79 -0
- package/src/http/api/api-host.ts +47 -0
- package/src/http/api/api-result.ts +56 -0
- package/src/http/api/api-schema.ts +154 -0
- package/src/http/api/api-server.ts +130 -0
- package/src/http/api/api-test.ts +142 -0
- package/src/http/api/api-type.ts +37 -0
- package/src/http/api/api.ts +81 -0
- package/src/http/api/index.ts +11 -0
- package/src/http/api-adapter/api-core-node-http.ts +260 -0
- package/src/http/api-adapter/api-host-node-http.ts +156 -0
- package/src/http/api-adapter/api-result-arktype.ts +297 -0
- package/src/http/api-adapter/api-result-zod.ts +286 -0
- package/src/http/api-adapter/index.ts +5 -0
- package/src/http/bin/gen-api-list/gen-api-list.ts +126 -0
- package/src/http/bin/gen-api-list/index.ts +1 -0
- package/src/http/bin/gen-api-test/gen-api-test.ts +136 -0
- package/src/http/bin/gen-api-test/index.ts +1 -0
- package/src/http/bin/gen-api-type/calc-code.ts +25 -0
- package/src/http/bin/gen-api-type/gen-api-type.ts +127 -0
- package/src/http/bin/gen-api-type/index.ts +2 -0
- package/src/http/bin/index.ts +2 -0
- package/src/http/index.ts +3 -0
- package/src/huawei/README.md +1 -0
- package/src/huawei/index.ts +2 -0
- package/src/huawei/moderation/index.ts +1 -0
- package/src/huawei/moderation/moderation.ts +355 -0
- package/src/huawei/obs/esdk-obs-nodejs.d.ts +87 -0
- package/src/huawei/obs/index.ts +1 -0
- package/src/huawei/obs/obs.ts +42 -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 +33 -1
- package/src/json/README.md +92 -0
- package/src/json/index.ts +1 -0
- package/src/json/repair.ts +18 -0
- 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 +554 -0
- package/src/openai/README.md +1 -0
- package/src/openai/index.ts +1 -0
- package/src/openai/openai.ts +510 -0
- package/src/orchestration/README.md +91 -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/dispatching/dispatcher.ts +83 -0
- package/src/orchestration/dispatching/index.ts +2 -0
- package/src/orchestration/dispatching/selector/base-selector.ts +39 -0
- package/src/orchestration/dispatching/selector/down-count-selector.ts +119 -0
- package/src/orchestration/dispatching/selector/index.ts +2 -0
- package/src/orchestration/index.ts +3 -0
- package/src/orchestration/scheduling/index.ts +2 -0
- package/src/orchestration/scheduling/scheduler.ts +103 -0
- package/src/orchestration/scheduling/task.ts +32 -0
- package/src/random/README.md +56 -86
- package/src/random/base.ts +66 -0
- package/src/random/index.ts +5 -1
- package/src/random/random-boolean.ts +40 -0
- package/src/random/random-integer.ts +60 -0
- package/src/random/random-number.ts +72 -0
- package/src/random/random-string.ts +66 -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/request/README.md +108 -0
- package/src/request/fetch/base.ts +108 -0
- package/src/request/fetch/browser.ts +285 -0
- package/src/request/fetch/general.ts +20 -0
- package/src/request/fetch/index.ts +4 -0
- package/src/request/fetch/nodejs.ts +285 -0
- package/src/request/index.ts +2 -0
- package/src/request/request/base.ts +250 -0
- package/src/request/request/general.ts +64 -0
- package/src/request/request/index.ts +3 -0
- package/src/request/request/resource.ts +68 -0
- package/src/result/README.md +4 -0
- package/src/result/controller.ts +54 -0
- package/src/result/either.ts +193 -0
- package/src/result/index.ts +2 -0
- package/src/route/README.md +105 -0
- package/src/route/adapter/browser.ts +122 -0
- package/src/route/adapter/driver.ts +56 -0
- package/src/route/adapter/index.ts +2 -0
- package/src/route/index.ts +3 -0
- package/src/route/router/index.ts +2 -0
- package/src/route/router/route.ts +630 -0
- package/src/route/router/router.ts +1642 -0
- package/src/route/uri/hash.ts +308 -0
- package/src/route/uri/index.ts +7 -0
- package/src/route/uri/pathname.ts +376 -0
- package/src/route/uri/search.ts +413 -0
- 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/socket/README.md +105 -0
- package/src/socket/client/index.ts +2 -0
- package/src/socket/client/socket-unit.ts +660 -0
- package/src/socket/client/socket.ts +203 -0
- package/src/socket/common/index.ts +2 -0
- package/src/socket/common/socket-unit-common.ts +23 -0
- package/src/socket/common/socket-unit-heartbeat.ts +427 -0
- package/src/socket/index.ts +3 -0
- package/src/socket/server/index.ts +3 -0
- package/src/socket/server/server.ts +183 -0
- package/src/socket/server/socket-unit.ts +449 -0
- package/src/socket/server/socket.ts +264 -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/tube/README.md +99 -0
- package/src/tube/helper.ts +138 -0
- package/src/tube/index.ts +2 -0
- package/src/tube/tube.ts +880 -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/src/weixin/README.md +1 -0
- package/src/weixin/index.ts +2 -0
- package/src/weixin/official-account/authorization.ts +159 -0
- package/src/weixin/official-account/index.ts +2 -0
- package/src/weixin/official-account/js-api.ts +134 -0
- package/src/weixin/open/index.ts +1 -0
- package/src/weixin/open/oauth2.ts +133 -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/ai/ai.spec.ts +85 -0
- package/tests/unit/aio/content.spec.ts +105 -0
- package/tests/unit/aio/json.spec.ts +147 -0
- package/tests/unit/aio/prompt.spec.ts +111 -0
- package/tests/unit/basic/array.spec.ts +1 -1
- package/tests/unit/basic/error.spec.ts +16 -4
- package/tests/unit/basic/schedule.spec.ts +74 -0
- package/tests/unit/basic/stream.spec.ts +91 -38
- 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/credential/api-key.spec.ts +37 -0
- package/tests/unit/credential/bearer.spec.ts +23 -0
- package/tests/unit/credential/json-web-token.spec.ts +23 -0
- package/tests/unit/credential/password.spec.ts +41 -0
- package/tests/unit/cron/cron.spec.ts +84 -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/form/inputor-controller/base.spec.ts +458 -0
- package/tests/unit/form/inputor-controller/boolean.spec.ts +30 -0
- package/tests/unit/form/inputor-controller/file.spec.ts +27 -0
- package/tests/unit/form/inputor-controller/form.spec.ts +120 -0
- package/tests/unit/form/inputor-controller/helper.spec.ts +67 -0
- package/tests/unit/form/inputor-controller/multi-select.spec.ts +34 -0
- package/tests/unit/form/inputor-controller/number.spec.ts +36 -0
- package/tests/unit/form/inputor-controller/select.spec.ts +49 -0
- package/tests/unit/form/inputor-controller/text.spec.ts +34 -0
- package/tests/unit/http/api/api-core-host.spec.ts +207 -0
- package/tests/unit/http/api/api-schema.spec.ts +120 -0
- package/tests/unit/http/api/api-server.spec.ts +363 -0
- package/tests/unit/http/api/api-test.spec.ts +117 -0
- package/tests/unit/http/api/api.spec.ts +121 -0
- package/tests/unit/http/api-adapter/node-http.spec.ts +191 -0
- package/tests/unit/identifier/id.spec.ts +71 -0
- package/tests/unit/identifier/uuid.spec.ts +85 -0
- package/tests/unit/json/repair.spec.ts +11 -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 +237 -0
- package/tests/unit/openai/openai.spec.ts +64 -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/orchestration/dispatching/dispatcher.spec.ts +41 -0
- package/tests/unit/orchestration/dispatching/selector/down-count-selector.spec.ts +81 -0
- package/tests/unit/orchestration/scheduling/scheduler.spec.ts +103 -0
- package/tests/unit/random/base.spec.ts +58 -0
- package/tests/unit/random/random-boolean.spec.ts +25 -0
- package/tests/unit/random/random-integer.spec.ts +32 -0
- package/tests/unit/random/random-number.spec.ts +33 -0
- package/tests/unit/random/random-string.spec.ts +22 -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/request/fetch/browser.spec.ts +222 -0
- package/tests/unit/request/fetch/general.spec.ts +43 -0
- package/tests/unit/request/fetch/nodejs.spec.ts +225 -0
- package/tests/unit/request/request/base.spec.ts +385 -0
- package/tests/unit/request/request/general.spec.ts +161 -0
- package/tests/unit/route/router/route.spec.ts +431 -0
- package/tests/unit/route/router/router.spec.ts +407 -0
- package/tests/unit/route/uri/hash.spec.ts +72 -0
- package/tests/unit/route/uri/pathname.spec.ts +147 -0
- package/tests/unit/route/uri/search.spec.ts +107 -0
- package/tests/unit/singleton/singleton.spec.ts +49 -0
- package/tests/unit/socket/client.spec.ts +208 -0
- package/tests/unit/socket/server.spec.ts +135 -0
- package/tests/unit/socket/socket-unit-heartbeat.spec.ts +214 -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/tests/unit/tube/helper.spec.ts +139 -0
- package/tests/unit/tube/tube.spec.ts +501 -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,33 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
xyzColorValueToRelativeLuminance,
|
|
5
|
+
xyzColorValueToTristimulusSum,
|
|
6
|
+
xyzColorValueToXyChromaticityValue,
|
|
7
|
+
} from "#Source/color/index.ts"
|
|
8
|
+
|
|
9
|
+
test("xyzColorValueToRelativeLuminance returns y channel", () => {
|
|
10
|
+
expect(xyzColorValueToRelativeLuminance({ x: 0.25, y: 0.3, z: 0.4, alpha: 1 })).toBe(0.3)
|
|
11
|
+
expect(xyzColorValueToRelativeLuminance({ x: 0, y: 0, z: 0, alpha: 1 })).toBe(0)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test("xyzColorValueToTristimulusSum returns x plus y plus z", () => {
|
|
15
|
+
expect(xyzColorValueToTristimulusSum({ x: 0.25, y: 0.3, z: 0.4, alpha: 1 })).toBe(0.95)
|
|
16
|
+
expect(xyzColorValueToTristimulusSum({ x: 0, y: 0, z: 0, alpha: 1 })).toBe(0)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
test("xyzColorValueToXyChromaticityValue returns xy chromaticity coordinates", () => {
|
|
20
|
+
expect(xyzColorValueToXyChromaticityValue({ x: 0.25, y: 0.3, z: 0.4, alpha: 1 })).toEqual({
|
|
21
|
+
x: 0.263_158,
|
|
22
|
+
y: 0.315_789,
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
expect(xyzColorValueToXyChromaticityValue({ x: 0.412_456, y: 0.212_673, z: 0.019_334, alpha: 1 })).toEqual({
|
|
26
|
+
x: 0.64,
|
|
27
|
+
y: 0.33,
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
expect(() => xyzColorValueToXyChromaticityValue({ x: 0, y: 0, z: 0, alpha: 1 })).toThrow(
|
|
31
|
+
"XYZ tristimulus sum must be greater than 0 to compute xy chromaticity",
|
|
32
|
+
)
|
|
33
|
+
})
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { createXyzColorValue } from "#Source/color/index.ts"
|
|
4
|
+
|
|
5
|
+
test("createXyzColorValue validates non-negative channels", () => {
|
|
6
|
+
expect(createXyzColorValue({ x: 0.25, y: 0.3, z: 0.4, alpha: 1 }))
|
|
7
|
+
.toEqual({ x: 0.25, y: 0.3, z: 0.4, alpha: 1 })
|
|
8
|
+
|
|
9
|
+
expect(() => createXyzColorValue({ x: -0.1, y: 0.3, z: 0.4, alpha: 1 })).toThrow(RangeError)
|
|
10
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
linearRgbColorValueToXyzColorValue,
|
|
5
|
+
xyzColorValueToLinearRgbColorValue,
|
|
6
|
+
} from "#Source/color/index.ts"
|
|
7
|
+
|
|
8
|
+
test("xyzColorValueToLinearRgbColorValue converts XYZ values to Linear RGB", () => {
|
|
9
|
+
expect(xyzColorValueToLinearRgbColorValue({ x: 0.333_784_22, y: 0.371_900_46, z: 0.621_726_04, alpha: 1 }))
|
|
10
|
+
.toEqual({ red: 0.2, green: 0.4, blue: 0.6, alpha: 1 })
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
test("linearRgbColorValueToXyzColorValue converts Linear RGB values to XYZ", () => {
|
|
14
|
+
expect(linearRgbColorValueToXyzColorValue({ red: 0.2, green: 0.4, blue: 0.6, alpha: 1 }))
|
|
15
|
+
.toEqual({ x: 0.333_784, y: 0.371_9, z: 0.621_726, alpha: 1 })
|
|
16
|
+
|
|
17
|
+
expect(() => linearRgbColorValueToXyzColorValue({ red: Number.NaN, green: 0.4, blue: 0.6, alpha: 1 })).toThrow(RangeError)
|
|
18
|
+
})
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { generateApiKey, maskApiKey } from "#Source/credential/index.ts"
|
|
4
|
+
|
|
5
|
+
test("generateApiKey creates a stable sk-prefixed key shape", () => {
|
|
6
|
+
vi.spyOn(crypto, "getRandomValues").mockImplementation(buffer => {
|
|
7
|
+
if (buffer === null) {
|
|
8
|
+
return buffer
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
12
|
+
const target = buffer as Uint8Array
|
|
13
|
+
target.forEach((_, index) => {
|
|
14
|
+
target[index] = index
|
|
15
|
+
})
|
|
16
|
+
return buffer
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
const apiKey = generateApiKey({})
|
|
20
|
+
const customApiKey = generateApiKey({ prefix: "pk_", alphabet: "ABC123", bodyLength: 6 })
|
|
21
|
+
|
|
22
|
+
expect(apiKey.startsWith("sk-")).toBe(true)
|
|
23
|
+
expect(apiKey).toHaveLength(51)
|
|
24
|
+
expect(apiKey).toMatch(/^sk-[0-9A-Za-z]{48}$/)
|
|
25
|
+
expect(customApiKey).toBe("pk_ABC123")
|
|
26
|
+
expect(() => generateApiKey({ alphabet: "", bodyLength: 6 })).toThrow("Expected alphabet to contain at least one character")
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
test("maskApiKey hides the middle of a valid key and rejects malformed input", () => {
|
|
30
|
+
const apiKey = `sk-${"ab"}${"0".repeat(44)}yz`
|
|
31
|
+
const customOptions = { prefix: "pk_", alphabet: "ABC123", bodyLength: 6 } as const
|
|
32
|
+
|
|
33
|
+
expect(maskApiKey(apiKey)).toBe("sk-ab********************************************yz")
|
|
34
|
+
expect(maskApiKey("pk_ABC123", customOptions)).toBe("pk_AB**23")
|
|
35
|
+
expect(() => maskApiKey("not-an-api-key")).toThrow("Invalid API key format")
|
|
36
|
+
expect(() => maskApiKey(`sk-${"#".repeat(48)}`)).toThrow("Invalid API key format")
|
|
37
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { formatBearerCredential, isBearerCredential, parseBearerCredential } from "#Source/credential/index.ts"
|
|
4
|
+
|
|
5
|
+
test("isBearerCredential recognizes bearer credential strings", () => {
|
|
6
|
+
expect(isBearerCredential("Bearer token-value")).toBe(true)
|
|
7
|
+
expect(isBearerCredential("bearer token-value")).toBe(true)
|
|
8
|
+
expect(isBearerCredential("Basic token-value")).toBe(false)
|
|
9
|
+
expect(isBearerCredential("token-value")).toBe(false)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
test("formatBearerCredential prefixes a raw token with the Bearer scheme", () => {
|
|
13
|
+
expect(formatBearerCredential("token-value")).toBe("Bearer token-value")
|
|
14
|
+
expect(formatBearerCredential(" token-value ")).toBe("Bearer token-value")
|
|
15
|
+
expect(() => formatBearerCredential(" ")).toThrow("Expected token to contain at least one non-whitespace character")
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
test("parseBearerCredential extracts the raw token only from bearer credentials", () => {
|
|
19
|
+
expect(parseBearerCredential("Bearer token-value")).toBe("token-value")
|
|
20
|
+
expect(parseBearerCredential("bearer token-value")).toBe("token-value")
|
|
21
|
+
expect(parseBearerCredential("Basic token-value")).toBeUndefined()
|
|
22
|
+
expect(parseBearerCredential(undefined)).toBeUndefined()
|
|
23
|
+
})
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { formatBearerCredential, JsonWebToken } from "#Source/credential/index.ts"
|
|
4
|
+
|
|
5
|
+
test("JsonWebToken.signToken creates a compact JWT string", async () => {
|
|
6
|
+
const jwt = new JsonWebToken({ secret: "credential-secret", expiresIn: 3_600 })
|
|
7
|
+
|
|
8
|
+
const token = await jwt.signToken("user-1")
|
|
9
|
+
|
|
10
|
+
expect(token.split(".")).toHaveLength(3)
|
|
11
|
+
expect(typeof token).toBe("string")
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test("JsonWebToken.parseToken returns userId for valid tokens and undefined for invalid input", async () => {
|
|
15
|
+
const jwt = new JsonWebToken({ secret: "credential-secret", expiresIn: 3_600 })
|
|
16
|
+
const token = await jwt.signToken("user-1")
|
|
17
|
+
|
|
18
|
+
await expect(jwt.parseToken(token)).resolves.toBe("user-1")
|
|
19
|
+
await expect(jwt.parseToken(formatBearerCredential(token))).resolves.toBeUndefined()
|
|
20
|
+
await expect(jwt.parseToken(undefined)).resolves.toBeUndefined()
|
|
21
|
+
await expect(jwt.parseToken("broken.token.value")).resolves.toBeUndefined()
|
|
22
|
+
await expect(new JsonWebToken({ secret: "other-secret", expiresIn: 3_600 }).parseToken(token)).resolves.toBeUndefined()
|
|
23
|
+
})
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { Password } from "#Source/credential/index.ts"
|
|
4
|
+
|
|
5
|
+
test("Password.generateSalt returns a hexadecimal salt string of the requested byte length", () => {
|
|
6
|
+
vi.spyOn(crypto, "getRandomValues").mockImplementation(buffer => {
|
|
7
|
+
if (buffer === null) {
|
|
8
|
+
return buffer
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
|
12
|
+
const target = buffer as Uint8Array
|
|
13
|
+
target.fill(0xAB)
|
|
14
|
+
return buffer
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
const salt = Password.generateSalt(8)
|
|
18
|
+
|
|
19
|
+
expect(salt).toBe("abababababababab")
|
|
20
|
+
expect(salt).toHaveLength(16)
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test("Password.hashPasswordWithSalt derives a stable PBKDF2-SHA-512 hash for the same input", async () => {
|
|
24
|
+
const salt = "00112233445566778899AABBCCDDEEFF"
|
|
25
|
+
const hash1 = await Password.hashPasswordWithSalt("secret", salt)
|
|
26
|
+
const hash2 = await Password.hashPasswordWithSalt("secret", salt)
|
|
27
|
+
const hash3 = await Password.hashPasswordWithSalt("other", salt)
|
|
28
|
+
|
|
29
|
+
expect(hash1).toHaveLength(128)
|
|
30
|
+
expect(hash1).toMatch(/^[0-9A-F]+$/i)
|
|
31
|
+
expect(hash1).toBe(hash2)
|
|
32
|
+
expect(hash1).not.toBe(hash3)
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
test("Password.comparePassword distinguishes matching and mismatching passwords", async () => {
|
|
36
|
+
const salt = "00112233445566778899AABBCCDDEEFF"
|
|
37
|
+
const hash = await Password.hashPasswordWithSalt("secret", salt)
|
|
38
|
+
|
|
39
|
+
await expect(Password.comparePassword("secret", hash, salt)).resolves.toBe(true)
|
|
40
|
+
await expect(Password.comparePassword("other", hash, salt)).resolves.toBe(false)
|
|
41
|
+
})
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { Cron } from "#Source/cron/index.ts"
|
|
4
|
+
|
|
5
|
+
const internalCreateFutureMinute = (minutesAhead: number): Date => {
|
|
6
|
+
const date = new Date(Date.now() + minutesAhead * 60_000)
|
|
7
|
+
date.setSeconds(0, 0)
|
|
8
|
+
return date
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const internalCreatePastMinute = (minutesAgo: number): Date => {
|
|
12
|
+
const date = new Date(Date.now() - minutesAgo * 60_000)
|
|
13
|
+
date.setSeconds(0, 0)
|
|
14
|
+
return date
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
test("Cron creates schedule models from both cron expressions and one-shot dates", () => {
|
|
18
|
+
const expressionCron = new Cron({ pattern: "*/5 * * * *" })
|
|
19
|
+
const singleShotCron = new Cron({ pattern: internalCreateFutureMinute(5) })
|
|
20
|
+
|
|
21
|
+
expect(expressionCron).toBeInstanceOf(Cron)
|
|
22
|
+
expect(singleShotCron).toBeInstanceOf(Cron)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
test("Cron.prevRun returns undefined before a one-shot schedule is reached", () => {
|
|
26
|
+
const scheduledAt = internalCreateFutureMinute(5)
|
|
27
|
+
const cron = new Cron({ pattern: scheduledAt })
|
|
28
|
+
|
|
29
|
+
expect(cron.prevRun()).toBeUndefined()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
test("Cron.prevRuns returns historical one-shot runs that have already happened", () => {
|
|
33
|
+
const scheduledAt = internalCreatePastMinute(5)
|
|
34
|
+
const cron = new Cron({ pattern: scheduledAt })
|
|
35
|
+
const previousRuns = cron.prevRuns(2)
|
|
36
|
+
|
|
37
|
+
expect(previousRuns).toHaveLength(1)
|
|
38
|
+
expect(previousRuns[0]?.getTime()).toBe(scheduledAt.getTime())
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test("Cron.currentRun stays undefined when the model is only queried and not executing a task", () => {
|
|
42
|
+
const cron = new Cron({ pattern: internalCreateFutureMinute(5) })
|
|
43
|
+
|
|
44
|
+
expect(cron.currentRun()).toBeUndefined()
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test("Cron.nextRun returns the next upcoming one-shot run", () => {
|
|
48
|
+
const scheduledAt = internalCreateFutureMinute(5)
|
|
49
|
+
const cron = new Cron({ pattern: scheduledAt })
|
|
50
|
+
|
|
51
|
+
expect(cron.nextRun()?.getTime()).toBe(scheduledAt.getTime())
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
test("Cron.nextRuns returns upcoming expression runs in ascending order", () => {
|
|
55
|
+
const cron = new Cron({ pattern: "*/5 * * * *" })
|
|
56
|
+
const nextRuns = cron.nextRuns(2)
|
|
57
|
+
|
|
58
|
+
expect(nextRuns).toHaveLength(2)
|
|
59
|
+
expect(nextRuns[0]?.getTime()).toBeLessThan(nextRuns[1]?.getTime() ?? 0)
|
|
60
|
+
expect(cron.match(nextRuns[0]!)).toBe(true)
|
|
61
|
+
expect(cron.match(nextRuns[1]!)).toBe(true)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test("Cron.match reflects cron-expression semantics including domAndDow behavior", () => {
|
|
65
|
+
const yearlyCron = new Cron({ pattern: "0 0 1 1 *" })
|
|
66
|
+
const andCron = new Cron({ pattern: "0 0 1 * MON", domAndDow: true })
|
|
67
|
+
const orCron = new Cron({ pattern: "0 0 1 * MON", domAndDow: false })
|
|
68
|
+
const yearlyHit = new Date(2_026, 0, 1, 0, 0, 0, 0)
|
|
69
|
+
const yearlyMiss = new Date(2_026, 0, 1, 0, 1, 0, 0)
|
|
70
|
+
const mondayFirst = new Date(2_026, 5, 1, 0, 0, 0, 0)
|
|
71
|
+
const mondayNotFirst = new Date(2_026, 5, 8, 0, 0, 0, 0)
|
|
72
|
+
const firstNotMonday = new Date(2_026, 8, 1, 0, 0, 0, 0)
|
|
73
|
+
|
|
74
|
+
expect(yearlyCron.match(yearlyHit)).toBe(true)
|
|
75
|
+
expect(yearlyCron.match(yearlyMiss)).toBe(false)
|
|
76
|
+
|
|
77
|
+
expect(andCron.match(mondayFirst)).toBe(true)
|
|
78
|
+
expect(andCron.match(mondayNotFirst)).toBe(false)
|
|
79
|
+
expect(andCron.match(firstNotMonday)).toBe(false)
|
|
80
|
+
|
|
81
|
+
expect(orCron.match(mondayFirst)).toBe(true)
|
|
82
|
+
expect(orCron.match(mondayNotFirst)).toBe(true)
|
|
83
|
+
expect(orCron.match(firstNotMonday)).toBe(true)
|
|
84
|
+
})
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
addClass,
|
|
5
|
+
classArrayToClassObject,
|
|
6
|
+
classArrayToClassString,
|
|
7
|
+
classObjectToClassArray,
|
|
8
|
+
classObjectToClassString,
|
|
9
|
+
classStringToClassArray,
|
|
10
|
+
classStringToClassObject,
|
|
11
|
+
containClass,
|
|
12
|
+
formatClassToTarget,
|
|
13
|
+
neatenClassString,
|
|
14
|
+
prefixClassWith,
|
|
15
|
+
removeClass,
|
|
16
|
+
removePrefixOfClass,
|
|
17
|
+
replaceClass,
|
|
18
|
+
toClassArray,
|
|
19
|
+
toClassObject,
|
|
20
|
+
toClassString,
|
|
21
|
+
toggleClass,
|
|
22
|
+
} from "#Source/css/index.ts"
|
|
23
|
+
|
|
24
|
+
test("neatenClassString normalizes dots and spaces", () => {
|
|
25
|
+
expect(neatenClassString("mobius-base mobius-theme--light")).toBe("mobius-base mobius-theme--light")
|
|
26
|
+
expect(neatenClassString(".mobius-base.mobius-theme--light")).toBe("mobius-base mobius-theme--light")
|
|
27
|
+
expect(neatenClassString(" .mobius-base mobius-theme--light ")).toBe("mobius-base mobius-theme--light")
|
|
28
|
+
})
|
|
29
|
+
|
|
30
|
+
test("classStringToClassArray splits normalized class strings", () => {
|
|
31
|
+
expect(classStringToClassArray("mobius-base mobius-theme--light")).toEqual(["mobius-base", "mobius-theme--light"])
|
|
32
|
+
expect(classStringToClassArray(".mobius-base.mobius-theme--light")).toEqual(["mobius-base", "mobius-theme--light"])
|
|
33
|
+
expect(classStringToClassArray(" .mobius-base mobius-theme--light ")).toEqual(["mobius-base", "mobius-theme--light"])
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
test("classArrayToClassObject marks every class as true", () => {
|
|
37
|
+
expect(classArrayToClassObject(["mobius-base", "mobius-theme--light"]))
|
|
38
|
+
.toEqual({ "mobius-base": true, "mobius-theme--light": true })
|
|
39
|
+
expect(classArrayToClassObject(["mobius-base", ""]))
|
|
40
|
+
.toEqual({ "mobius-base": true })
|
|
41
|
+
expect(classArrayToClassObject([])).toEqual({})
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
test("classStringToClassObject converts strings through normalized array form", () => {
|
|
45
|
+
expect(classStringToClassObject("mobius-base mobius-theme--light"))
|
|
46
|
+
.toEqual({ "mobius-base": true, "mobius-theme--light": true })
|
|
47
|
+
expect(classStringToClassObject(".mobius-base.mobius-theme--light"))
|
|
48
|
+
.toEqual({ "mobius-base": true, "mobius-theme--light": true })
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
test("classObjectToClassArray keeps only truthy class names", () => {
|
|
52
|
+
expect(classObjectToClassArray({ active: true, disabled: false, primary: true }))
|
|
53
|
+
.toEqual(["active", "primary"])
|
|
54
|
+
expect(classObjectToClassArray({ "": true, active: true, disabled: false })).toEqual(["active"])
|
|
55
|
+
expect(classObjectToClassArray({ disabled: false })).toEqual([])
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
test("classArrayToClassString joins non-empty items with spaces", () => {
|
|
59
|
+
expect(classArrayToClassString(["mobius-base", "mobius-theme--light"]))
|
|
60
|
+
.toBe("mobius-base mobius-theme--light")
|
|
61
|
+
expect(classArrayToClassString(["mobius-base", "", "mobius-theme--light"]))
|
|
62
|
+
.toBe("mobius-base mobius-theme--light")
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
test("classObjectToClassString serializes only truthy keys", () => {
|
|
66
|
+
expect(classObjectToClassString({ active: true, disabled: false, primary: true }))
|
|
67
|
+
.toBe("active primary")
|
|
68
|
+
expect(classObjectToClassString({ "": true, active: true, disabled: false })).toBe("active")
|
|
69
|
+
expect(classObjectToClassString({ disabled: false })).toBe("")
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
test("toClassString preserves string input and serializes other shapes", () => {
|
|
73
|
+
expect(toClassString(" .button active ")).toBe(" .button active ")
|
|
74
|
+
expect(toClassString(["button", "active"])).toBe("button active")
|
|
75
|
+
expect(toClassString({ button: true, active: true, disabled: false })).toBe("button active")
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
test("toClassArray normalizes strings and clones arrays", () => {
|
|
79
|
+
const classArray = ["button", "", "active"]
|
|
80
|
+
const result = toClassArray(classArray)
|
|
81
|
+
|
|
82
|
+
expect(toClassArray(".button.active")).toEqual(["button", "active"])
|
|
83
|
+
expect(result).toEqual(["button", "active"])
|
|
84
|
+
expect(result).not.toBe(classArray)
|
|
85
|
+
expect(toClassArray({ button: true, active: true, disabled: false })).toEqual(["button", "active"])
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test("toClassObject normalizes strings and clones objects", () => {
|
|
89
|
+
const classObject = { button: true, active: false, "": true }
|
|
90
|
+
const result = toClassObject(classObject)
|
|
91
|
+
|
|
92
|
+
expect(toClassObject("button active")).toEqual({ button: true, active: true })
|
|
93
|
+
expect(toClassObject(["button", "active"])).toEqual({ button: true, active: true })
|
|
94
|
+
expect(result).toEqual({ button: true, active: false })
|
|
95
|
+
expect(result).not.toBe(classObject)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test("formatClassToTarget returns the same external shape as target", () => {
|
|
99
|
+
expect(formatClassToTarget("", ["button", "active"])).toBe("button active")
|
|
100
|
+
expect(formatClassToTarget([], "button active")).toEqual(["button", "active"])
|
|
101
|
+
expect(formatClassToTarget({}, ["button", "active"])).toEqual({ button: true, active: true })
|
|
102
|
+
})
|
|
103
|
+
|
|
104
|
+
test("prefixClassWith adds prefixes only when missing", () => {
|
|
105
|
+
expect(prefixClassWith("pm-", "button active")).toBe("pm-button pm-active")
|
|
106
|
+
expect(prefixClassWith("pm-", ["button", "", "pm-active"])).toEqual(["pm-button", "pm-active"])
|
|
107
|
+
expect(prefixClassWith("pm-", { button: true, "": true, "pm-active": false }))
|
|
108
|
+
.toEqual({ "pm-button": true, "pm-active": false })
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
test("removePrefixOfClass removes prefixes only when present", () => {
|
|
112
|
+
expect(removePrefixOfClass("pm-", "pm-button pm-active plain")).toBe("button active plain")
|
|
113
|
+
expect(removePrefixOfClass("pm-", ["pm-button", "pm-", "plain"]))
|
|
114
|
+
.toEqual(["button", "plain"])
|
|
115
|
+
expect(removePrefixOfClass("pm-", { "pm-button": true, plain: false, "pm-": true }))
|
|
116
|
+
.toEqual({ button: true, plain: false })
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
test("addClass merges class names while preserving target shape", () => {
|
|
120
|
+
expect(addClass("button", "active primary")).toBe("button active primary")
|
|
121
|
+
expect(addClass(["button", ""], { active: true, primary: true, "": false }))
|
|
122
|
+
.toEqual(["button", "active", "primary"])
|
|
123
|
+
expect(addClass({ button: true }, ["active", "primary"]))
|
|
124
|
+
.toEqual({ button: true, active: true, primary: true })
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
test("removeClass removes selected class names while preserving target shape", () => {
|
|
128
|
+
expect(removeClass("button active primary", "active")).toBe("button primary")
|
|
129
|
+
expect(removeClass(["button", "active", "primary"], { active: true })).toEqual(["button", "primary"])
|
|
130
|
+
expect(removeClass({ button: true, active: true, primary: true }, ["active"]))
|
|
131
|
+
.toEqual({ button: true, active: false, primary: true })
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
test("toggleClass flips membership for each target class", () => {
|
|
135
|
+
expect(toggleClass("button active", "active primary")).toBe("button primary")
|
|
136
|
+
expect(toggleClass(["button", "active"], ["active", "", "primary"]))
|
|
137
|
+
.toEqual(["button", "primary"])
|
|
138
|
+
expect(toggleClass({ button: true, active: true }, ["active", "primary"]))
|
|
139
|
+
.toEqual({ button: true, active: false, primary: true })
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
test("replaceClass supports tuple arrays, string arrays, records, and string removals", () => {
|
|
143
|
+
expect(replaceClass("button active", [["active", "selected"]])).toBe("button selected")
|
|
144
|
+
expect(replaceClass("button active primary", ["active", "primary"])).toBe("button")
|
|
145
|
+
expect(replaceClass(["button", "active"], { active: "selected" })).toEqual(["button", "selected"])
|
|
146
|
+
expect(replaceClass({ button: true, active: true }, "active")).toEqual({ active: false, button: true })
|
|
147
|
+
expect(replaceClass({ "": true, button: true, active: false }, { "": "selected", active: "featured" }))
|
|
148
|
+
.toEqual({ button: true, featured: false })
|
|
149
|
+
expect(replaceClass("button active primary", [["active", ""], ["primary", "featured"]]))
|
|
150
|
+
.toBe("button featured")
|
|
151
|
+
})
|
|
152
|
+
|
|
153
|
+
test("containClass checks whether all required class names are present", () => {
|
|
154
|
+
expect(containClass("button active", "button active primary")).toBe(true)
|
|
155
|
+
expect(containClass(["button", "missing"], { button: true, active: true })).toBe(false)
|
|
156
|
+
expect(containClass({ button: true }, ["button", "active"])).toBe(true)
|
|
157
|
+
})
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import { useFactory } from "#Source/environment/basic.ts"
|
|
4
|
+
|
|
5
|
+
test("useFactory executes selected branch by availability", () => {
|
|
6
|
+
const useEnabled = useFactory(() => 42, () => true)
|
|
7
|
+
const useDisabled = useFactory(() => 42, () => false)
|
|
8
|
+
|
|
9
|
+
const enabledResult = useEnabled(
|
|
10
|
+
(value) => value + 1,
|
|
11
|
+
() => -1,
|
|
12
|
+
)
|
|
13
|
+
const disabledResult = useDisabled(
|
|
14
|
+
(value) => value + 1,
|
|
15
|
+
() => -1,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
expect(enabledResult).toBe(43)
|
|
19
|
+
expect(disabledResult).toBe(-1)
|
|
20
|
+
})
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
getDeviceInfo,
|
|
5
|
+
getDeviceNavigatorInfo,
|
|
6
|
+
getDeviceScreenInfo,
|
|
7
|
+
parseUserAgent,
|
|
8
|
+
} from "#Source/environment/device.ts"
|
|
9
|
+
|
|
10
|
+
const setupForGetDeviceNavigatorInfo = (): (() => void) => {
|
|
11
|
+
vi.stubGlobal("navigator", {
|
|
12
|
+
oscpu: "Windows NT 10.0",
|
|
13
|
+
hardwareConcurrency: 8,
|
|
14
|
+
deviceMemory: 16,
|
|
15
|
+
maxTouchPoints: 5,
|
|
16
|
+
platform: "Win32",
|
|
17
|
+
devicePosture: { type: "continuous" },
|
|
18
|
+
connection: {
|
|
19
|
+
downlink: 10,
|
|
20
|
+
downlinkMax: 100,
|
|
21
|
+
effectiveType: "4g",
|
|
22
|
+
rtt: 40,
|
|
23
|
+
saveData: false,
|
|
24
|
+
type: "wifi",
|
|
25
|
+
},
|
|
26
|
+
onLine: true,
|
|
27
|
+
cookieEnabled: true,
|
|
28
|
+
language: "en-US",
|
|
29
|
+
languages: ["en-US", "zh-CN"],
|
|
30
|
+
pdfViewerEnabled: true,
|
|
31
|
+
userAgent:
|
|
32
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36",
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
return () => {
|
|
36
|
+
vi.unstubAllGlobals()
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
test("getDeviceNavigatorInfo collects navigator metadata", () => {
|
|
40
|
+
const restore = setupForGetDeviceNavigatorInfo()
|
|
41
|
+
|
|
42
|
+
const info = getDeviceNavigatorInfo()
|
|
43
|
+
|
|
44
|
+
expect(info.oscpu).toBe("Windows NT 10.0")
|
|
45
|
+
expect(info.hardwareConcurrency).toBe(8)
|
|
46
|
+
expect(info.connectionType).toBe("wifi")
|
|
47
|
+
expect(info.languages).toEqual(["en-US", "zh-CN"])
|
|
48
|
+
|
|
49
|
+
restore()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test("parseUserAgent normalizes user-agent metadata", () => {
|
|
53
|
+
const userAgent =
|
|
54
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
|
|
55
|
+
|
|
56
|
+
const info = parseUserAgent(userAgent)
|
|
57
|
+
|
|
58
|
+
expect(info.ua).toBe(userAgent)
|
|
59
|
+
expect(typeof info.browserIsBot).toBe("boolean")
|
|
60
|
+
expect(typeof info.browserIsChromeFamily).toBe("boolean")
|
|
61
|
+
expect(typeof info.deviceIsAppleSilicon).toBe("boolean")
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
const setupForGetDeviceScreenInfo = (): (() => void) => {
|
|
65
|
+
vi.stubGlobal("document", {
|
|
66
|
+
documentElement: {
|
|
67
|
+
clientWidth: 1_200,
|
|
68
|
+
clientHeight: 700,
|
|
69
|
+
scrollWidth: 2_000,
|
|
70
|
+
scrollHeight: 3_000,
|
|
71
|
+
},
|
|
72
|
+
})
|
|
73
|
+
vi.stubGlobal("window", {
|
|
74
|
+
devicePixelRatio: 2,
|
|
75
|
+
screen: {
|
|
76
|
+
width: 1_920,
|
|
77
|
+
height: 1_080,
|
|
78
|
+
availWidth: 1_900,
|
|
79
|
+
availHeight: 1_060,
|
|
80
|
+
orientation: { type: "landscape-primary" },
|
|
81
|
+
},
|
|
82
|
+
outerWidth: 1_400,
|
|
83
|
+
outerHeight: 900,
|
|
84
|
+
innerWidth: 1_200,
|
|
85
|
+
innerHeight: 700,
|
|
86
|
+
visualViewport: {
|
|
87
|
+
width: 1_180,
|
|
88
|
+
height: 680,
|
|
89
|
+
offsetLeft: 10,
|
|
90
|
+
offsetTop: 8,
|
|
91
|
+
pageLeft: 0,
|
|
92
|
+
pageTop: 0,
|
|
93
|
+
scale: 1,
|
|
94
|
+
},
|
|
95
|
+
})
|
|
96
|
+
vi.stubGlobal("getComputedStyle", () => ({
|
|
97
|
+
getPropertyValue: (name: string): string => {
|
|
98
|
+
if (name.includes("top")) {
|
|
99
|
+
return "10"
|
|
100
|
+
}
|
|
101
|
+
if (name.includes("bottom")) {
|
|
102
|
+
return "20"
|
|
103
|
+
}
|
|
104
|
+
if (name.includes("left")) {
|
|
105
|
+
return "30"
|
|
106
|
+
}
|
|
107
|
+
if (name.includes("right")) {
|
|
108
|
+
return "40"
|
|
109
|
+
}
|
|
110
|
+
return "0"
|
|
111
|
+
},
|
|
112
|
+
}))
|
|
113
|
+
|
|
114
|
+
return () => {
|
|
115
|
+
vi.unstubAllGlobals()
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
test("getDeviceScreenInfo collects screen and viewport metadata", () => {
|
|
119
|
+
const restore = setupForGetDeviceScreenInfo()
|
|
120
|
+
|
|
121
|
+
const info = getDeviceScreenInfo()
|
|
122
|
+
|
|
123
|
+
expect(info.pixelRatio).toBe(2)
|
|
124
|
+
expect(info.screenWidth).toBe(1_920)
|
|
125
|
+
expect(info.viewportAvailableWidth).toBe(1_200)
|
|
126
|
+
expect(info.safeAreaTop).toBe(10)
|
|
127
|
+
expect(info.safeAreaRight).toBe(40)
|
|
128
|
+
|
|
129
|
+
restore()
|
|
130
|
+
})
|
|
131
|
+
|
|
132
|
+
test("getDeviceInfo aggregates navigator, user-agent, and screen metadata", () => {
|
|
133
|
+
const restore1 = setupForGetDeviceNavigatorInfo()
|
|
134
|
+
const restore2 = setupForGetDeviceScreenInfo()
|
|
135
|
+
|
|
136
|
+
const userAgent =
|
|
137
|
+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36"
|
|
138
|
+
const info = getDeviceInfo(userAgent)
|
|
139
|
+
|
|
140
|
+
expect(info.navigator.language).toBe("en-US")
|
|
141
|
+
expect(info.userAgent.ua).toBe(userAgent)
|
|
142
|
+
expect(info.screen.viewportHeight).toBe(700)
|
|
143
|
+
|
|
144
|
+
restore1()
|
|
145
|
+
restore2()
|
|
146
|
+
})
|