@planet-matrix/mobius-model 0.6.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 +37 -0
- package/dist/index.js +706 -36
- package/dist/index.js.map +855 -59
- package/package.json +28 -16
- 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 +20 -15
- package/src/basic/error.ts +19 -5
- package/src/basic/function.ts +2 -2
- package/src/basic/index.ts +1 -0
- package/src/basic/schedule.ts +111 -0
- package/src/basic/stream.ts +135 -25
- 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/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/event/class-event-proxy.ts +6 -5
- package/src/event/common.ts +13 -3
- package/src/event/event-manager.ts +3 -3
- package/src/event/instance-event-proxy.ts +6 -5
- package/src/event/internal.ts +4 -4
- 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/index.ts +19 -2
- package/src/json/README.md +92 -0
- package/src/json/index.ts +1 -0
- package/src/json/repair.ts +18 -0
- package/src/log/logger.ts +15 -4
- 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 +9 -7
- 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 +2 -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 +8 -7
- 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/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/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/table.ts +3 -3
- package/src/timer/expiration/expiration-manager.ts +3 -3
- package/src/timer/expiration/remaining-manager.ts +3 -3
- 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/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/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/error.spec.ts +16 -4
- package/tests/unit/basic/schedule.spec.ts +74 -0
- package/tests/unit/basic/stream.spec.ts +90 -37
- 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/event/class-event-proxy.spec.ts +3 -3
- package/tests/unit/event/event-manager.spec.ts +3 -3
- package/tests/unit/event/instance-event-proxy.spec.ts +3 -3
- 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/json/repair.spec.ts +11 -0
- package/tests/unit/log/logger.spec.ts +19 -4
- package/tests/unit/openai/openai.spec.ts +64 -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/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/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/tube/helper.spec.ts +139 -0
- package/tests/unit/tube/tube.spec.ts +501 -0
- package/src/random/string.ts +0 -35
- package/tests/unit/random/string.spec.ts +0 -11
|
@@ -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
|
+
})
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { expect, test, vi } from "vitest"
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { BuildEvents, ClassEventProxyOptions } from "#Source/event/index.ts"
|
|
4
4
|
import {
|
|
5
5
|
ClassEventProxy,
|
|
6
6
|
EventManager,
|
|
7
7
|
} from "#Source/event/index.ts"
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
type TestEvents = BuildEvents<{
|
|
10
10
|
change: (value: number) => void
|
|
11
11
|
close: (reason: string) => void
|
|
12
|
-
}
|
|
12
|
+
}>
|
|
13
13
|
|
|
14
14
|
interface TestTarget {
|
|
15
15
|
events: EventManager<TestEvents>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { expect, test, vi } from "vitest"
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { BuildEvents } from "#Source/event/index.ts"
|
|
4
4
|
import { EventManager } from "#Source/event/index.ts"
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
type TestEvents = BuildEvents<{
|
|
7
7
|
ready: (value: number) => void | Promise<void>
|
|
8
8
|
done: (label: string) => void | Promise<void>
|
|
9
|
-
}
|
|
9
|
+
}>
|
|
10
10
|
const createEventManager = (): EventManager<TestEvents> => {
|
|
11
11
|
return new EventManager<TestEvents>()
|
|
12
12
|
}
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { expect, test, vi } from "vitest"
|
|
2
2
|
|
|
3
|
-
import type {
|
|
3
|
+
import type { BuildEvents, InstanceEventProxyOptions } from "#Source/event/index.ts"
|
|
4
4
|
import {
|
|
5
5
|
EventManager,
|
|
6
6
|
InstanceEventProxy,
|
|
7
7
|
} from "#Source/event/index.ts"
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
type TestEvents = BuildEvents<{
|
|
10
10
|
change: (value: number) => void
|
|
11
11
|
error: (message: string) => void
|
|
12
|
-
}
|
|
12
|
+
}>
|
|
13
13
|
|
|
14
14
|
const createInstanceEventProxyHarness = (options?: {
|
|
15
15
|
onSubscriberError?: (error: unknown) => void
|
|
@@ -0,0 +1,458 @@
|
|
|
1
|
+
import { expect, test, vi } from "vitest"
|
|
2
|
+
|
|
3
|
+
import type {
|
|
4
|
+
BaseInputorContext,
|
|
5
|
+
BaseInputorControllerOptions,
|
|
6
|
+
BaseInputorSchema,
|
|
7
|
+
} from "#Source/form/index.ts"
|
|
8
|
+
import { BaseInputorController } from "#Source/form/index.ts"
|
|
9
|
+
|
|
10
|
+
const TEST_INPUTOR_TYPE_NAME = "test" as const
|
|
11
|
+
|
|
12
|
+
type TestExternalValue = string | undefined
|
|
13
|
+
type TestInternalValue = string | undefined
|
|
14
|
+
|
|
15
|
+
interface TestInputorSchema extends BaseInputorSchema<TestExternalValue> {
|
|
16
|
+
inputorTypeName: typeof TEST_INPUTOR_TYPE_NAME
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface TestInputorContext extends BaseInputorContext<TestExternalValue, TestInternalValue> {
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface TestInputorControllerOptions extends BaseInputorControllerOptions<TestExternalValue, TestInputorSchema> {
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class TestInputorController extends BaseInputorController<
|
|
26
|
+
TestExternalValue,
|
|
27
|
+
TestInternalValue,
|
|
28
|
+
TestInputorSchema,
|
|
29
|
+
TestInputorContext
|
|
30
|
+
> {
|
|
31
|
+
declare schemaType: TestInputorSchema
|
|
32
|
+
declare optionsType: TestInputorControllerOptions
|
|
33
|
+
declare constructorType: typeof TestInputorController
|
|
34
|
+
|
|
35
|
+
static inputorTypeName = TEST_INPUTOR_TYPE_NAME
|
|
36
|
+
|
|
37
|
+
inputorTypeName = TEST_INPUTOR_TYPE_NAME
|
|
38
|
+
protected voidExternalValue = undefined
|
|
39
|
+
protected voidInternalValue = undefined
|
|
40
|
+
|
|
41
|
+
protected override async constrainExternalValue(value: TestExternalValue): Promise<TestExternalValue> {
|
|
42
|
+
if (value === undefined) {
|
|
43
|
+
return await Promise.resolve(undefined)
|
|
44
|
+
}
|
|
45
|
+
return await Promise.resolve(value.trim())
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
protected override async constrainInternalValue(value: TestInternalValue): Promise<TestInternalValue> {
|
|
49
|
+
if (value === undefined) {
|
|
50
|
+
return await Promise.resolve(undefined)
|
|
51
|
+
}
|
|
52
|
+
return await Promise.resolve(value.trim())
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const createTestInputor = (
|
|
57
|
+
overrides: Partial<TestInputorControllerOptions> = {},
|
|
58
|
+
): TestInputorController => {
|
|
59
|
+
return new TestInputorController({
|
|
60
|
+
id: "test-id",
|
|
61
|
+
name: "test-name",
|
|
62
|
+
placeholder: "test-placeholder",
|
|
63
|
+
value: " initial ",
|
|
64
|
+
...overrides,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
test("BaseInputorController.isReady and waitForReady initialize defaults and emit initial hooks", async () => {
|
|
69
|
+
const valueChanges: Array<{ newValue: TestExternalValue; oldValue: TestExternalValue }> = []
|
|
70
|
+
const schemaChanges: Array<{ newSchema: TestInputorSchema; oldSchema: TestInputorSchema }> = []
|
|
71
|
+
const controller = createTestInputor({
|
|
72
|
+
onValueChange: (context) => {
|
|
73
|
+
valueChanges.push(context)
|
|
74
|
+
},
|
|
75
|
+
onSchemaChange: (context) => {
|
|
76
|
+
schemaChanges.push(context)
|
|
77
|
+
},
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
expect(controller.isReady()).toBe(false)
|
|
81
|
+
|
|
82
|
+
await controller.waitForReady()
|
|
83
|
+
|
|
84
|
+
expect(controller.isReady()).toBe(true)
|
|
85
|
+
expect(await controller.getSchema()).toEqual({
|
|
86
|
+
inputorTypeName: TEST_INPUTOR_TYPE_NAME,
|
|
87
|
+
id: "test-id",
|
|
88
|
+
name: "test-name",
|
|
89
|
+
placeholder: "test-placeholder",
|
|
90
|
+
value: "initial",
|
|
91
|
+
isVoid: false,
|
|
92
|
+
isFocused: false,
|
|
93
|
+
isTouched: false,
|
|
94
|
+
isDirty: false,
|
|
95
|
+
isDisabled: false,
|
|
96
|
+
isDebugMode: false,
|
|
97
|
+
})
|
|
98
|
+
expect(valueChanges).toEqual([
|
|
99
|
+
{
|
|
100
|
+
newValue: "initial",
|
|
101
|
+
oldValue: "initial",
|
|
102
|
+
},
|
|
103
|
+
])
|
|
104
|
+
expect(schemaChanges).toHaveLength(1)
|
|
105
|
+
expect(schemaChanges[0]!.newSchema).toEqual(schemaChanges[0]!.oldSchema)
|
|
106
|
+
expect(schemaChanges[0]!.newSchema.value).toBe("initial")
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test("BaseInputorController.getInternalValue, getContext, and isEqualSchema expose current state", async () => {
|
|
110
|
+
const controller = createTestInputor({
|
|
111
|
+
value: " alpha ",
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
await controller.waitForReady()
|
|
115
|
+
|
|
116
|
+
expect(controller.getInternalValue()).toBe("alpha")
|
|
117
|
+
expect(await controller.getContext()).toEqual({
|
|
118
|
+
inputorTypeName: TEST_INPUTOR_TYPE_NAME,
|
|
119
|
+
id: "test-id",
|
|
120
|
+
name: "test-name",
|
|
121
|
+
placeholder: "test-placeholder",
|
|
122
|
+
value: "alpha",
|
|
123
|
+
isVoid: false,
|
|
124
|
+
isFocused: false,
|
|
125
|
+
isTouched: false,
|
|
126
|
+
isDirty: false,
|
|
127
|
+
isDisabled: false,
|
|
128
|
+
isDebugMode: false,
|
|
129
|
+
internalValue: "alpha",
|
|
130
|
+
isReady: true,
|
|
131
|
+
})
|
|
132
|
+
expect(await controller.isEqualSchema({ value: "alpha", placeholder: "test-placeholder" })).toBe(true)
|
|
133
|
+
expect(await controller.isEqualSchema({ value: "beta" })).toBe(false)
|
|
134
|
+
expect(await controller.isEqualSchema(null)).toBe(false)
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
test("BaseInputorController.subscribeInputChange and updateInputValue emit input updates after debounce", async () => {
|
|
138
|
+
vi.useFakeTimers()
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const controller = createTestInputor({
|
|
142
|
+
value: "start",
|
|
143
|
+
inputDebounce: 20,
|
|
144
|
+
})
|
|
145
|
+
const inputChanges: Array<{ newInput: TestExternalValue; oldInput: TestExternalValue; oldValue: TestExternalValue }> = []
|
|
146
|
+
|
|
147
|
+
await controller.waitForReady()
|
|
148
|
+
controller.subscribeInputChange((context) => {
|
|
149
|
+
inputChanges.push(context)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
await controller.updateInputValue(" next ")
|
|
153
|
+
|
|
154
|
+
expect(inputChanges).toEqual([])
|
|
155
|
+
|
|
156
|
+
await vi.advanceTimersByTimeAsync(20)
|
|
157
|
+
|
|
158
|
+
expect(inputChanges).toEqual([
|
|
159
|
+
{
|
|
160
|
+
newInput: " next ",
|
|
161
|
+
oldInput: "start",
|
|
162
|
+
oldValue: "start",
|
|
163
|
+
},
|
|
164
|
+
])
|
|
165
|
+
}
|
|
166
|
+
finally {
|
|
167
|
+
vi.useRealTimers()
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test("BaseInputorController.unsubscribeInputChange removes one input subscriber", async () => {
|
|
172
|
+
vi.useFakeTimers()
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const controller = createTestInputor({ value: "start" })
|
|
176
|
+
let calls = 0
|
|
177
|
+
const subscriber = (): void => {
|
|
178
|
+
calls = calls + 1
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
await controller.waitForReady()
|
|
182
|
+
controller.subscribeInputChange(subscriber)
|
|
183
|
+
controller.unsubscribeInputChange(subscriber)
|
|
184
|
+
|
|
185
|
+
await controller.updateInputValue("next")
|
|
186
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
187
|
+
|
|
188
|
+
expect(calls).toBe(0)
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
vi.useRealTimers()
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
|
|
195
|
+
test("BaseInputorController.clearInputChangeSubscribers removes all input subscribers", async () => {
|
|
196
|
+
vi.useFakeTimers()
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const controller = createTestInputor({ value: "start" })
|
|
200
|
+
let calls = 0
|
|
201
|
+
|
|
202
|
+
await controller.waitForReady()
|
|
203
|
+
controller.subscribeInputChange(() => {
|
|
204
|
+
calls = calls + 1
|
|
205
|
+
})
|
|
206
|
+
controller.subscribeInputChange(() => {
|
|
207
|
+
calls = calls + 1
|
|
208
|
+
})
|
|
209
|
+
controller.clearInputChangeSubscribers()
|
|
210
|
+
|
|
211
|
+
await controller.updateInputValue("next")
|
|
212
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
213
|
+
|
|
214
|
+
expect(calls).toBe(0)
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
vi.useRealTimers()
|
|
218
|
+
}
|
|
219
|
+
})
|
|
220
|
+
|
|
221
|
+
test("BaseInputorController.subscribeValueChange, applyInputValue, and updateValue emit value changes", async () => {
|
|
222
|
+
const controller = createTestInputor({ value: "start" })
|
|
223
|
+
const valueChanges: Array<{ newValue: TestExternalValue; oldValue: TestExternalValue }> = []
|
|
224
|
+
|
|
225
|
+
await controller.waitForReady()
|
|
226
|
+
controller.subscribeValueChange((context) => {
|
|
227
|
+
valueChanges.push(context)
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
await controller.updateInputValue(" next ")
|
|
231
|
+
await controller.applyInputValue()
|
|
232
|
+
await controller.updateValue(" final ")
|
|
233
|
+
|
|
234
|
+
expect(valueChanges).toEqual([
|
|
235
|
+
{
|
|
236
|
+
newValue: "next",
|
|
237
|
+
oldValue: "start",
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
newValue: "final",
|
|
241
|
+
oldValue: "next",
|
|
242
|
+
},
|
|
243
|
+
])
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
test("BaseInputorController.unsubscribeValueChange removes one value subscriber", async () => {
|
|
247
|
+
const controller = createTestInputor({ value: "start" })
|
|
248
|
+
let calls = 0
|
|
249
|
+
const subscriber = (): void => {
|
|
250
|
+
calls = calls + 1
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
await controller.waitForReady()
|
|
254
|
+
controller.subscribeValueChange(subscriber)
|
|
255
|
+
controller.unsubscribeValueChange(subscriber)
|
|
256
|
+
await controller.updateValue("next")
|
|
257
|
+
|
|
258
|
+
expect(calls).toBe(0)
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
test("BaseInputorController.clearValueChangeSubscribers removes all value subscribers", async () => {
|
|
262
|
+
const controller = createTestInputor({ value: "start" })
|
|
263
|
+
let calls = 0
|
|
264
|
+
|
|
265
|
+
await controller.waitForReady()
|
|
266
|
+
controller.subscribeValueChange(() => {
|
|
267
|
+
calls = calls + 1
|
|
268
|
+
})
|
|
269
|
+
controller.subscribeValueChange(() => {
|
|
270
|
+
calls = calls + 1
|
|
271
|
+
})
|
|
272
|
+
controller.clearValueChangeSubscribers()
|
|
273
|
+
await controller.updateValue("next")
|
|
274
|
+
|
|
275
|
+
expect(calls).toBe(0)
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
test("BaseInputorController.subscribeSchemaChange and updatePlaceholder emit schema changes", async () => {
|
|
279
|
+
const controller = createTestInputor({ value: "start" })
|
|
280
|
+
const schemaChanges: Array<{ newSchema: TestInputorSchema; oldSchema: TestInputorSchema }> = []
|
|
281
|
+
|
|
282
|
+
await controller.waitForReady()
|
|
283
|
+
await Promise.resolve()
|
|
284
|
+
controller.subscribeSchemaChange((context) => {
|
|
285
|
+
schemaChanges.push(context)
|
|
286
|
+
})
|
|
287
|
+
await controller.updatePlaceholder("next-placeholder")
|
|
288
|
+
|
|
289
|
+
expect(schemaChanges.length).toBeGreaterThanOrEqual(1)
|
|
290
|
+
expect(schemaChanges.at(-1)!.oldSchema.placeholder).toBe("test-placeholder")
|
|
291
|
+
expect(schemaChanges.at(-1)!.newSchema.placeholder).toBe("next-placeholder")
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
test("BaseInputorController.unsubscribeSchemaChange removes one schema subscriber", async () => {
|
|
295
|
+
const controller = createTestInputor({ value: "start" })
|
|
296
|
+
let calls = 0
|
|
297
|
+
const subscriber = (): void => {
|
|
298
|
+
calls = calls + 1
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
await controller.waitForReady()
|
|
302
|
+
controller.subscribeSchemaChange(subscriber)
|
|
303
|
+
controller.unsubscribeSchemaChange(subscriber)
|
|
304
|
+
await controller.updatePlaceholder("next-placeholder")
|
|
305
|
+
|
|
306
|
+
expect(calls).toBe(0)
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
test("BaseInputorController.clearSchemaChangeSubscribers removes all schema subscribers", async () => {
|
|
310
|
+
const controller = createTestInputor({ value: "start" })
|
|
311
|
+
let calls = 0
|
|
312
|
+
|
|
313
|
+
await controller.waitForReady()
|
|
314
|
+
controller.subscribeSchemaChange(() => {
|
|
315
|
+
calls = calls + 1
|
|
316
|
+
})
|
|
317
|
+
controller.subscribeSchemaChange(() => {
|
|
318
|
+
calls = calls + 1
|
|
319
|
+
})
|
|
320
|
+
controller.clearSchemaChangeSubscribers()
|
|
321
|
+
await controller.updatePlaceholder("next-placeholder")
|
|
322
|
+
|
|
323
|
+
expect(calls).toBe(0)
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
test("BaseInputorController.updateIsVoid resets the value to its void value", async () => {
|
|
327
|
+
const controller = createTestInputor({ value: "start" })
|
|
328
|
+
|
|
329
|
+
await controller.waitForReady()
|
|
330
|
+
await controller.updateIsVoid(true)
|
|
331
|
+
|
|
332
|
+
expect(await controller.getSchema()).toMatchObject({
|
|
333
|
+
value: undefined,
|
|
334
|
+
isVoid: true,
|
|
335
|
+
isDirty: true,
|
|
336
|
+
})
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
test("BaseInputorController.updateIsFocused also marks the controller as touched", async () => {
|
|
340
|
+
const controller = createTestInputor({ value: "start" })
|
|
341
|
+
|
|
342
|
+
await controller.waitForReady()
|
|
343
|
+
await controller.updateIsFocused(true)
|
|
344
|
+
|
|
345
|
+
expect(await controller.getSchema()).toMatchObject({
|
|
346
|
+
isFocused: true,
|
|
347
|
+
isTouched: true,
|
|
348
|
+
})
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
test("BaseInputorController.updateIsTouched updates touched state directly", async () => {
|
|
352
|
+
const controller = createTestInputor({ value: "start" })
|
|
353
|
+
|
|
354
|
+
await controller.waitForReady()
|
|
355
|
+
await controller.updateIsTouched(true)
|
|
356
|
+
|
|
357
|
+
expect(await controller.getSchema()).toMatchObject({
|
|
358
|
+
isTouched: true,
|
|
359
|
+
})
|
|
360
|
+
})
|
|
361
|
+
|
|
362
|
+
test("BaseInputorController.updateIsDirty updates dirty state directly", async () => {
|
|
363
|
+
const controller = createTestInputor({ value: "start" })
|
|
364
|
+
|
|
365
|
+
await controller.waitForReady()
|
|
366
|
+
await controller.updateIsDirty(true)
|
|
367
|
+
|
|
368
|
+
expect(await controller.getSchema()).toMatchObject({
|
|
369
|
+
isDirty: true,
|
|
370
|
+
})
|
|
371
|
+
})
|
|
372
|
+
|
|
373
|
+
test("BaseInputorController.updateIsDisabled updates disabled state directly", async () => {
|
|
374
|
+
const controller = createTestInputor({ value: "start" })
|
|
375
|
+
|
|
376
|
+
await controller.waitForReady()
|
|
377
|
+
await controller.updateIsDisabled(true)
|
|
378
|
+
|
|
379
|
+
expect(await controller.getSchema()).toMatchObject({
|
|
380
|
+
isDisabled: true,
|
|
381
|
+
})
|
|
382
|
+
})
|
|
383
|
+
|
|
384
|
+
test("BaseInputorController.updateIsDebugMode updates debug mode directly", async () => {
|
|
385
|
+
const controller = createTestInputor({ value: "start" })
|
|
386
|
+
|
|
387
|
+
await controller.waitForReady()
|
|
388
|
+
await controller.updateIsDebugMode(true)
|
|
389
|
+
|
|
390
|
+
expect(await controller.getSchema()).toMatchObject({
|
|
391
|
+
isDebugMode: true,
|
|
392
|
+
})
|
|
393
|
+
})
|
|
394
|
+
|
|
395
|
+
test("BaseInputorController.updateSchema applies multiple schema fields in one pass", async () => {
|
|
396
|
+
const controller = createTestInputor({ value: "start" })
|
|
397
|
+
|
|
398
|
+
await controller.waitForReady()
|
|
399
|
+
await controller.updateSchema({
|
|
400
|
+
...(await controller.getSchema()),
|
|
401
|
+
placeholder: "schema-placeholder",
|
|
402
|
+
value: " schema-value ",
|
|
403
|
+
isFocused: true,
|
|
404
|
+
isTouched: true,
|
|
405
|
+
isDirty: true,
|
|
406
|
+
isDisabled: true,
|
|
407
|
+
isDebugMode: true,
|
|
408
|
+
})
|
|
409
|
+
|
|
410
|
+
expect(await controller.getSchema()).toEqual({
|
|
411
|
+
inputorTypeName: TEST_INPUTOR_TYPE_NAME,
|
|
412
|
+
id: "test-id",
|
|
413
|
+
name: "test-name",
|
|
414
|
+
placeholder: "schema-placeholder",
|
|
415
|
+
value: "schema-value",
|
|
416
|
+
isVoid: false,
|
|
417
|
+
isFocused: true,
|
|
418
|
+
isTouched: true,
|
|
419
|
+
isDirty: true,
|
|
420
|
+
isDisabled: true,
|
|
421
|
+
isDebugMode: true,
|
|
422
|
+
})
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
test("BaseInputorController.destroy clears every subscriber collection", async () => {
|
|
426
|
+
vi.useFakeTimers()
|
|
427
|
+
|
|
428
|
+
try {
|
|
429
|
+
const controller = createTestInputor({ value: "start" })
|
|
430
|
+
let inputCalls = 0
|
|
431
|
+
let valueCalls = 0
|
|
432
|
+
let schemaCalls = 0
|
|
433
|
+
|
|
434
|
+
await controller.waitForReady()
|
|
435
|
+
controller.subscribeInputChange(() => {
|
|
436
|
+
inputCalls = inputCalls + 1
|
|
437
|
+
})
|
|
438
|
+
controller.subscribeValueChange(() => {
|
|
439
|
+
valueCalls = valueCalls + 1
|
|
440
|
+
})
|
|
441
|
+
controller.subscribeSchemaChange(() => {
|
|
442
|
+
schemaCalls = schemaCalls + 1
|
|
443
|
+
})
|
|
444
|
+
controller.destroy()
|
|
445
|
+
|
|
446
|
+
await controller.updateInputValue("next")
|
|
447
|
+
await vi.advanceTimersByTimeAsync(0)
|
|
448
|
+
await controller.updateValue("final")
|
|
449
|
+
await controller.updatePlaceholder("after-destroy")
|
|
450
|
+
|
|
451
|
+
expect(inputCalls).toBe(0)
|
|
452
|
+
expect(valueCalls).toBe(0)
|
|
453
|
+
expect(schemaCalls).toBe(0)
|
|
454
|
+
}
|
|
455
|
+
finally {
|
|
456
|
+
vi.useRealTimers()
|
|
457
|
+
}
|
|
458
|
+
})
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
BOOLEAN_INPUTOR_TYPE_NAME,
|
|
5
|
+
BooleanInputorController,
|
|
6
|
+
} from "#Source/form/index.ts"
|
|
7
|
+
|
|
8
|
+
test("BooleanInputorController.getSchema exposes boolean schema state and updateValue toggles it", async () => {
|
|
9
|
+
const controller = new BooleanInputorController({
|
|
10
|
+
id: "boolean-id",
|
|
11
|
+
value: false,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
await controller.waitForReady()
|
|
15
|
+
await controller.updateValue(true)
|
|
16
|
+
|
|
17
|
+
expect(await controller.getSchema()).toEqual({
|
|
18
|
+
inputorTypeName: BOOLEAN_INPUTOR_TYPE_NAME,
|
|
19
|
+
id: "boolean-id",
|
|
20
|
+
name: `boolean-boolean-id`,
|
|
21
|
+
placeholder: "",
|
|
22
|
+
value: true,
|
|
23
|
+
isVoid: false,
|
|
24
|
+
isFocused: false,
|
|
25
|
+
isTouched: false,
|
|
26
|
+
isDirty: true,
|
|
27
|
+
isDisabled: false,
|
|
28
|
+
isDebugMode: false,
|
|
29
|
+
})
|
|
30
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { expect, test } from "vitest"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
FILE_INPUTOR_TYPE_NAME,
|
|
5
|
+
FileInputorController,
|
|
6
|
+
} from "#Source/form/index.ts"
|
|
7
|
+
|
|
8
|
+
test("FileInputorController.getSchema stores file values and reports file metadata", async () => {
|
|
9
|
+
const file = new File(["alpha"], "alpha.txt", {
|
|
10
|
+
type: "text/plain",
|
|
11
|
+
})
|
|
12
|
+
const controller = new FileInputorController({
|
|
13
|
+
id: "file-id",
|
|
14
|
+
value: file,
|
|
15
|
+
})
|
|
16
|
+
|
|
17
|
+
await controller.waitForReady()
|
|
18
|
+
|
|
19
|
+
const schema = await controller.getSchema()
|
|
20
|
+
|
|
21
|
+
expect(schema.inputorTypeName).toBe(FILE_INPUTOR_TYPE_NAME)
|
|
22
|
+
expect(schema.id).toBe("file-id")
|
|
23
|
+
expect(schema.name).toBe("file-file-id")
|
|
24
|
+
expect(schema.value?.name).toBe("alpha.txt")
|
|
25
|
+
expect(schema.value?.type).toBe("text/plain")
|
|
26
|
+
expect(schema.isDirty).toBe(false)
|
|
27
|
+
})
|