cj-tauri 1.1.2 → 2.1.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/README.md +137 -77
- package/bin/cj-tauri.js +287 -287
- package/cjpm.toml +13 -2
- package/cli/cjpm.toml +22 -13
- package/cli/src/doctor.cj +308 -304
- package/cli/src/main.cj +129 -121
- package/cli/src/manifest.cj +632 -0
- package/cli/src/manifest_test.cj +87 -0
- package/cli/src/project.cj +770 -754
- package/cli/src/resolve.cj +348 -348
- package/cli/src/scaffold.cj +46 -38
- package/cli/src/sync.cj +526 -0
- package/cli/src/util.cj +9 -0
- package/cli/templates/app/README.md +30 -59
- package/cli/templates/app/{ui → assets/ui}/index.html +2 -0
- package/cli/templates/app/cj-tauri.toml +32 -0
- package/cli/templates/app/cjpm.toml +21 -4
- package/cli/templates/app/src/main.cj +29 -16
- package/cli/templates/app-react/README.md +34 -45
- package/cli/templates/app-react/{ui → assets/ui}/index.html +25 -25
- package/cli/templates/app-react/{ui → assets/ui}/src/App.css +67 -67
- package/cli/templates/app-react/{ui → assets/ui}/src/App.jsx +124 -124
- package/cli/templates/app-react/cj-tauri.toml +33 -0
- package/cli/templates/app-react/cjpm.toml +21 -4
- package/cli/templates/app-react/src/main.cj +18 -29
- package/cli/templates/app-vue/README.md +34 -38
- package/cli/templates/app-vue/{ui → assets/ui}/index.html +25 -25
- package/cli/templates/app-vue/{ui → assets/ui}/src/App.vue +176 -176
- package/cli/templates/app-vue/cj-tauri.toml +33 -0
- package/cli/templates/app-vue/cjpm.toml +21 -4
- package/cli/templates/app-vue/src/main.cj +18 -29
- package/cli/templates/{ohos-pc → ohos-skeleton}/README.md +5 -0
- package/cli/templates/{ohos-pc → ohos-skeleton}/build-hap.sh +15 -0
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/cjpm.toml +8 -0
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/dialog_host.cj +217 -0
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/index.cj +527 -0
- package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/module.json5 +8 -0
- package/native/build_win.bat +62 -62
- package/native/core/bridge_core.c +1681 -1626
- package/native/core/bridge_core.h +675 -618
- package/native/core/bridge_js.h +108 -108
- package/native/platforms/harmonyos/README.md +115 -6
- package/native/platforms/linux/bridge_linux.c +2787 -2485
- package/native/platforms/windows/bridge_win.c +3740 -3248
- package/native/tests/gtk_threading_probe.c +143 -143
- package/native/tests/test_bridge_core.c +1339 -1228
- package/package.json +53 -51
- package/prebuilt/linux-x64/cj-tauri +0 -0
- package/src/api_system.cj +67 -67
- package/src/app.cj +325 -940
- package/src/app_events.cj +166 -0
- package/src/app_host_ops.cj +287 -0
- package/src/app_run.cj +283 -0
- package/src/app_spec.cj +76 -0
- package/src/arkweb_menu.cj +216 -0
- package/src/arkweb_script.cj +0 -12
- package/src/capability.cj +409 -409
- package/src/clipboard_bridge.cj +178 -0
- package/src/dialog_spec.cj +79 -0
- package/src/dragdrop.cj +26 -26
- package/src/embedded_assets.cj +98 -98
- package/src/host.cj +752 -604
- package/src/host_arkweb.cj +201 -17
- package/src/host_webkit.cj +663 -548
- package/src/host_webview2.cj +663 -548
- package/src/hotkey.cj +66 -66
- package/src/ipc_hub.cj +337 -337
- package/src/ipc_message.cj +286 -286
- package/src/launch.cj +62 -0
- package/src/logging.cj +127 -127
- package/src/menu.cj +197 -197
- package/src/platform/platform_app_context.cj +77 -0
- package/src/platform/platform_assets.cj +33 -0
- package/src/platform/platform_discover.cj +176 -0
- package/src/platform/platform_env.cj +29 -0
- package/src/platform/platform_feature.cj +31 -0
- package/src/platform/platform_page_source.cj +87 -0
- package/src/platform/platform_target_info.cj +32 -0
- package/src/platform_host_provider.cj +87 -0
- package/src/plugin.cj +333 -299
- package/src/plugins/clipboard/plugin_clipboard.cj +152 -144
- package/src/plugins/dialog/plugin_dialog.cj +209 -209
- package/src/plugins/fs/plugin_fs.cj +105 -1
- package/src/plugins/http/http_backend.cj +131 -0
- package/src/plugins/http/http_desktop.cj +195 -0
- package/src/plugins/http/http_ohos.cj +204 -0
- package/src/plugins/http/plugin_http.cj +310 -459
- package/src/plugins/log/plugin_log.cj +105 -105
- package/src/plugins/menu/plugin_menu.cj +8 -0
- package/src/plugins/notification/plugin_notification.cj +154 -146
- package/src/plugins/serial/plugin_serial.cj +17 -1
- package/src/plugins/serial/serial_bridge.cj +9 -1
- package/src/plugins/serial/serial_manager_ohos.cj +92 -0
- package/src/plugins/serial/serial_ohos.cj +529 -0
- package/src/plugins/shell/plugin_shell.cj +29 -1
- package/src/plugins/sqlite/plugin_sqlite.cj +8 -0
- package/src/plugins/ws/plugin_ws.cj +488 -470
- package/src/process_hidden.cj +174 -174
- package/src/scope.cj +391 -391
- package/src/shell_event.cj +69 -69
- package/src/tests/app_spec_test.cj +126 -0
- package/src/tests/capability_test.cj +243 -243
- package/src/tests/dialog_spec_test.cj +73 -0
- package/src/tests/fake_host.cj +201 -186
- package/src/tests/ipc_hub_test.cj +487 -487
- package/src/tests/logging_test.cj +62 -62
- package/src/tests/platform_contract_test.cj +251 -0
- package/src/tests/plugin_clipboard_test.cj +222 -222
- package/src/tests/plugin_dialog_test.cj +237 -237
- package/src/tests/plugin_fs_test.cj +207 -0
- package/src/tests/plugin_http_test.cj +124 -124
- package/src/tests/plugin_notification_test.cj +205 -205
- package/src/tests/plugin_serial_test.cj +48 -0
- package/src/tests/plugin_test.cj +66 -3
- package/src/tests/plugin_ws_test.cj +40 -40
- package/src/tests/process_hidden_test.cj +129 -129
- package/src/tests/scope_test.cj +350 -350
- package/src/tests/system_host_test.cj +99 -81
- package/src/tests/urlutil_test.cj +53 -53
- package/src/tests/version_test.cj +75 -75
- package/src/tests/window_registry_test.cj +123 -123
- package/src/tray.cj +90 -90
- package/src/urlutil.cj +159 -159
- package/src/version.cj +44 -44
- package/src/window.cj +51 -51
- package/src/window_registry.cj +116 -116
- package/cli/templates/ohos-pc/entry/src/main/cangjie/index.cj +0 -167
- package/prebuilt/win32-x64/cj-tauri.exe +0 -0
- package/src/host_factory.cj +0 -48
- /package/cli/templates/app/{capabilities → assets/capabilities}/default.json +0 -0
- /package/cli/templates/app-react/{capabilities → assets/capabilities}/default.json +0 -0
- /package/cli/templates/app-react/{ui → assets/ui}/package.json +0 -0
- /package/cli/templates/app-react/{ui → assets/ui}/src/main.jsx +0 -0
- /package/cli/templates/app-react/{ui → assets/ui}/vite.config.js +0 -0
- /package/cli/templates/app-vue/{capabilities → assets/capabilities}/default.json +0 -0
- /package/cli/templates/app-vue/{ui → assets/ui}/package.json +0 -0
- /package/cli/templates/app-vue/{ui → assets/ui}/src/main.js +0 -0
- /package/cli/templates/app-vue/{ui → assets/ui}/vite.config.js +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/app.json5 +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/element/string.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/background.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/foreground.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/layered_image.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/build-profile.json5 +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/build-profile.json5 +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/hvigorfile.ts +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/oh-package.json5 +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/ability_stage.cj +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/app_host.cj +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/main_ability.cj +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/color.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/string.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/background.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/foreground.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/layered_image.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/startIcon.png +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/profile/main_pages.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/capabilities.json +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/index.html +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/hvigor/hvigor-config.json5 +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/hvigorfile.ts +0 -0
- /package/cli/templates/{ohos-pc → ohos-skeleton}/oh-package.json5 +0 -0
|
@@ -1,205 +1,205 @@
|
|
|
1
|
-
package cjTauri.tests
|
|
2
|
-
|
|
3
|
-
import cjTauri.*
|
|
4
|
-
import cjTauri.plugins.notification.*
|
|
5
|
-
import std.collection.*
|
|
6
|
-
import stdx.encoding.json.*
|
|
7
|
-
import std.unittest.*
|
|
8
|
-
import std.unittest.testmacro.*
|
|
9
|
-
|
|
10
|
-
/*
|
|
11
|
-
* notification 插件单元测试:命令面 / shim / 命名权限集闸门 / 参数校验 / 未装配宿主的可读错误
|
|
12
|
-
* + 「宿主已装配」时的转交(FakeHost 记痕)。
|
|
13
|
-
*
|
|
14
|
-
* 真通知落在 C 桥(Linux 走 freedesktop Notifications,Windows 桩阶段未实现),
|
|
15
|
-
* 归实机验证;这里只锁入口逻辑。body 可省是契约的一部分,单独覆盖。
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/** 取一条「已装配宿主」的命令处理器 */
|
|
19
|
-
func notifCmdWithHost(host: FakeHost): CommandHandler {
|
|
20
|
-
let c = NotificationCommands()
|
|
21
|
-
c.attachHost(host)
|
|
22
|
-
return c
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
func notifErrOf(handler: CommandHandler, cmd: String, args: JsonObject): String {
|
|
26
|
-
try {
|
|
27
|
-
handler.handle(cmd, args, IpcContext("main", IpcHub()))
|
|
28
|
-
return ""
|
|
29
|
-
} catch (e: CommandException) {
|
|
30
|
-
return e.message
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** 造 { title, body } */
|
|
35
|
-
func notifArgs(title: String, body: JsonValue): JsonObject {
|
|
36
|
-
var o = JsonObject()
|
|
37
|
-
o.put("title", JsonString(title))
|
|
38
|
-
o.put("body", body)
|
|
39
|
-
return o
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
@Test
|
|
43
|
-
func testNotificationPluginSurface(): Unit {
|
|
44
|
-
let p = NotificationPlugin()
|
|
45
|
-
@Expect(p.name(), "notification")
|
|
46
|
-
let names = pluginCommandNames(p)
|
|
47
|
-
@Expect(names.size, 1)
|
|
48
|
-
@Expect(names[0], "notification:notify")
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
@Test
|
|
52
|
-
func testNotificationPluginShimTargetsItsOwnNamespace(): Unit {
|
|
53
|
-
let js = NotificationPlugin().jsShim()
|
|
54
|
-
@Expect(js.contains("t.notification"), true)
|
|
55
|
-
@Expect(js.contains("'notification:notify'"), true)
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
@Test
|
|
59
|
-
func testNotificationPluginDeclaresPermissionSets(): Unit {
|
|
60
|
-
let sets = pluginPermissionSets(NotificationPlugin())
|
|
61
|
-
@Expect(sets.size, 1)
|
|
62
|
-
let d = sets.get("notification:default").getOrThrow()
|
|
63
|
-
@Expect(d.size, 1)
|
|
64
|
-
@Expect(d[0], "notification:notify")
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
@Test
|
|
68
|
-
func testNotificationPermissionSetGatesCommand(): Unit {
|
|
69
|
-
let out = ArrayList<String>()
|
|
70
|
-
let reg = CapabilityRegistry()
|
|
71
|
-
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"n\",\"permissions\":[\"notification:default\"]}"))
|
|
72
|
-
let hub = hubWithRegistry(reg, out)
|
|
73
|
-
registerPluginCommands(hub, NotificationPlugin())
|
|
74
|
-
registerPluginPermissionSets(reg, NotificationPlugin())
|
|
75
|
-
|
|
76
|
-
// 集内命令放行:走到「宿主未装配」的业务错误,而不是「未授权」
|
|
77
|
-
hub.handleRawMessage(
|
|
78
|
-
"{\"type\":\"invoke\",\"id\":51,\"cmd\":\"notification:notify\",\"args\":{\"title\":\"t\"}}")
|
|
79
|
-
waitSink(out, 1)
|
|
80
|
-
@Expect(out.size, 1)
|
|
81
|
-
@Expect(strField(out[0], "error").contains("command not allowed"), false)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
@Test
|
|
85
|
-
func testNotificationWithoutPermissionIsDenied(): Unit {
|
|
86
|
-
let out = ArrayList<String>()
|
|
87
|
-
let reg = CapabilityRegistry()
|
|
88
|
-
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"empty\",\"permissions\":[]}"))
|
|
89
|
-
let hub = hubWithRegistry(reg, out)
|
|
90
|
-
registerPluginCommands(hub, NotificationPlugin())
|
|
91
|
-
registerPluginPermissionSets(reg, NotificationPlugin())
|
|
92
|
-
|
|
93
|
-
hub.handleRawMessage(
|
|
94
|
-
"{\"type\":\"invoke\",\"id\":52,\"cmd\":\"notification:notify\",\"args\":{\"title\":\"t\"}}")
|
|
95
|
-
waitSink(out, 1)
|
|
96
|
-
@Expect(strField(out[0], "error"), "command not allowed: notification:notify")
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
@Test
|
|
100
|
-
func testNotificationRequiresTitle(): Unit {
|
|
101
|
-
let cmd = NotificationPlugin().commands().get("notify").getOrThrow()
|
|
102
|
-
let ctx = IpcContext("main", IpcHub())
|
|
103
|
-
var threw = false
|
|
104
|
-
try {
|
|
105
|
-
cmd.handle("notification:notify", JsonObject(), ctx)
|
|
106
|
-
} catch (e: CommandException) {
|
|
107
|
-
threw = true
|
|
108
|
-
@Expect(e.message.contains("missing argument \"title\""), true)
|
|
109
|
-
}
|
|
110
|
-
@Expect(threw, true)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
@Test
|
|
114
|
-
func testNotificationRejectsNonStringTitle(): Unit {
|
|
115
|
-
var args = JsonObject()
|
|
116
|
-
args.put("title", JsonBool(true))
|
|
117
|
-
let cmd = NotificationPlugin().commands().get("notify").getOrThrow()
|
|
118
|
-
var threw = false
|
|
119
|
-
try {
|
|
120
|
-
cmd.handle("notification:notify", args, IpcContext("main", IpcHub()))
|
|
121
|
-
} catch (e: CommandException) {
|
|
122
|
-
threw = true
|
|
123
|
-
@Expect(e.message.contains("must be a string"), true)
|
|
124
|
-
}
|
|
125
|
-
@Expect(threw, true)
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
@Test
|
|
129
|
-
func testNotificationWithoutHostIsReadableError(): Unit {
|
|
130
|
-
@Expect(notifErrOf(NotificationPlugin().commands().get("notify").getOrThrow(), "notification:notify",
|
|
131
|
-
notifArgs("hi", JsonString("body"))).contains("宿主未装配"), true)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
@Test
|
|
135
|
-
func testNotificationRejectsUnknownCommand(): Unit {
|
|
136
|
-
@Expect(notifErrOf(NotificationPlugin().commands().get("notify").getOrThrow(), "notification:nope",
|
|
137
|
-
JsonObject()).contains("unknown notification command"), true)
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
@Test
|
|
141
|
-
func testNotificationBodyIsOptional(): Unit {
|
|
142
|
-
let host = FakeHost("main")
|
|
143
|
-
let cmd = notifCmdWithHost(host)
|
|
144
|
-
|
|
145
|
-
// 只给 title:body 按空串
|
|
146
|
-
var onlyTitle = JsonObject()
|
|
147
|
-
onlyTitle.put("title", JsonString("标题"))
|
|
148
|
-
let r1 = cmd.handle("notification:notify", onlyTitle, IpcContext("main", IpcHub()))
|
|
149
|
-
match (r1.kind()) {
|
|
150
|
-
case JsonKind.JsNull => ()
|
|
151
|
-
case _ => @Expect(false, true)
|
|
152
|
-
}
|
|
153
|
-
@Expect(host.notifiedArgs, "标题|")
|
|
154
|
-
|
|
155
|
-
// body 显式 null:同样按空串(前端 o.body 传 undefined 时序列化可能落成 null)
|
|
156
|
-
cmd.handle("notification:notify", notifArgs("标题2", JsonNull()), IpcContext("main", IpcHub()))
|
|
157
|
-
@Expect(host.notifiedArgs, "标题2|")
|
|
158
|
-
|
|
159
|
-
// body 有值:原样转交
|
|
160
|
-
cmd.handle("notification:notify", notifArgs("标题3", JsonString("正文")), IpcContext("main", IpcHub()))
|
|
161
|
-
@Expect(host.notifiedArgs, "标题3|正文")
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/** 桩侧「label → 宿主」解析器见 plugin_dialog_test.cj 的 routeProbeHost(同包可见) */
|
|
165
|
-
@Test
|
|
166
|
-
func testNotificationRoutesToTheWindowThatAsked(): Unit {
|
|
167
|
-
// 与 dialog / clipboard 同一条契约:报文里的 window 决定交给哪个宿主
|
|
168
|
-
let mainHost = FakeHost("main")
|
|
169
|
-
let secondHost = FakeHost("second")
|
|
170
|
-
let cmds = NotificationCommands()
|
|
171
|
-
cmds.attachHost(mainHost)
|
|
172
|
-
cmds.attachHostResolver({ label => routeProbeHost(label, mainHost, secondHost) })
|
|
173
|
-
|
|
174
|
-
cmds.handle("notification:notify", notifArgs("second 的通知", JsonString("正文")),
|
|
175
|
-
IpcContext("second", IpcHub()))
|
|
176
|
-
@Expect(secondHost.notifiedArgs, "second 的通知|正文")
|
|
177
|
-
// 关键断言:另一扇窗的宿主一点都没被碰到
|
|
178
|
-
@Expect(mainHost.notifiedArgs, "")
|
|
179
|
-
|
|
180
|
-
// 反向也成立
|
|
181
|
-
cmds.handle("notification:notify", notifArgs("main 的通知", JsonNull()), IpcContext("main", IpcHub()))
|
|
182
|
-
@Expect(mainHost.notifiedArgs, "main 的通知|")
|
|
183
|
-
@Expect(secondHost.notifiedArgs, "second 的通知|正文")
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
@Test
|
|
187
|
-
func testNotificationReportsUnknownWindowInsteadOfFallingBack(): Unit {
|
|
188
|
-
let mainHost = FakeHost("main")
|
|
189
|
-
let secondHost = FakeHost("second")
|
|
190
|
-
let cmds = NotificationCommands()
|
|
191
|
-
cmds.attachHost(mainHost)
|
|
192
|
-
cmds.attachHostResolver({ label => routeProbeHost(label, mainHost, secondHost) })
|
|
193
|
-
|
|
194
|
-
var threw = false
|
|
195
|
-
try {
|
|
196
|
-
cmds.handle("notification:notify", notifArgs("谁的", JsonNull()), IpcContext("ghost", IpcHub()))
|
|
197
|
-
} catch (e: CommandException) {
|
|
198
|
-
threw = true
|
|
199
|
-
@Expect(e.message.contains("ghost"), true)
|
|
200
|
-
}
|
|
201
|
-
@Expect(threw, true)
|
|
202
|
-
// 报错之后两个宿主都没被写过:不回落单槽
|
|
203
|
-
@Expect(mainHost.notifiedArgs, "")
|
|
204
|
-
@Expect(secondHost.notifiedArgs, "")
|
|
205
|
-
}
|
|
1
|
+
package cjTauri.tests
|
|
2
|
+
|
|
3
|
+
import cjTauri.*
|
|
4
|
+
import cjTauri.plugins.notification.*
|
|
5
|
+
import std.collection.*
|
|
6
|
+
import stdx.encoding.json.*
|
|
7
|
+
import std.unittest.*
|
|
8
|
+
import std.unittest.testmacro.*
|
|
9
|
+
|
|
10
|
+
/*
|
|
11
|
+
* notification 插件单元测试:命令面 / shim / 命名权限集闸门 / 参数校验 / 未装配宿主的可读错误
|
|
12
|
+
* + 「宿主已装配」时的转交(FakeHost 记痕)。
|
|
13
|
+
*
|
|
14
|
+
* 真通知落在 C 桥(Linux 走 freedesktop Notifications,Windows 桩阶段未实现),
|
|
15
|
+
* 归实机验证;这里只锁入口逻辑。body 可省是契约的一部分,单独覆盖。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** 取一条「已装配宿主」的命令处理器 */
|
|
19
|
+
func notifCmdWithHost(host: FakeHost): CommandHandler {
|
|
20
|
+
let c = NotificationCommands()
|
|
21
|
+
c.attachHost(host)
|
|
22
|
+
return c
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
func notifErrOf(handler: CommandHandler, cmd: String, args: JsonObject): String {
|
|
26
|
+
try {
|
|
27
|
+
handler.handle(cmd, args, IpcContext("main", IpcHub()))
|
|
28
|
+
return ""
|
|
29
|
+
} catch (e: CommandException) {
|
|
30
|
+
return e.message
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 造 { title, body } */
|
|
35
|
+
func notifArgs(title: String, body: JsonValue): JsonObject {
|
|
36
|
+
var o = JsonObject()
|
|
37
|
+
o.put("title", JsonString(title))
|
|
38
|
+
o.put("body", body)
|
|
39
|
+
return o
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@Test
|
|
43
|
+
func testNotificationPluginSurface(): Unit {
|
|
44
|
+
let p = NotificationPlugin()
|
|
45
|
+
@Expect(p.name(), "notification")
|
|
46
|
+
let names = pluginCommandNames(p)
|
|
47
|
+
@Expect(names.size, 1)
|
|
48
|
+
@Expect(names[0], "notification:notify")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
@Test
|
|
52
|
+
func testNotificationPluginShimTargetsItsOwnNamespace(): Unit {
|
|
53
|
+
let js = NotificationPlugin().jsShim()
|
|
54
|
+
@Expect(js.contains("t.notification"), true)
|
|
55
|
+
@Expect(js.contains("'notification:notify'"), true)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@Test
|
|
59
|
+
func testNotificationPluginDeclaresPermissionSets(): Unit {
|
|
60
|
+
let sets = pluginPermissionSets(NotificationPlugin())
|
|
61
|
+
@Expect(sets.size, 1)
|
|
62
|
+
let d = sets.get("notification:default").getOrThrow()
|
|
63
|
+
@Expect(d.size, 1)
|
|
64
|
+
@Expect(d[0], "notification:notify")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
@Test
|
|
68
|
+
func testNotificationPermissionSetGatesCommand(): Unit {
|
|
69
|
+
let out = ArrayList<String>()
|
|
70
|
+
let reg = CapabilityRegistry()
|
|
71
|
+
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"n\",\"permissions\":[\"notification:default\"]}"))
|
|
72
|
+
let hub = hubWithRegistry(reg, out)
|
|
73
|
+
registerPluginCommands(hub, NotificationPlugin())
|
|
74
|
+
registerPluginPermissionSets(reg, NotificationPlugin())
|
|
75
|
+
|
|
76
|
+
// 集内命令放行:走到「宿主未装配」的业务错误,而不是「未授权」
|
|
77
|
+
hub.handleRawMessage(
|
|
78
|
+
"{\"type\":\"invoke\",\"id\":51,\"cmd\":\"notification:notify\",\"args\":{\"title\":\"t\"}}")
|
|
79
|
+
waitSink(out, 1)
|
|
80
|
+
@Expect(out.size, 1)
|
|
81
|
+
@Expect(strField(out[0], "error").contains("command not allowed"), false)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@Test
|
|
85
|
+
func testNotificationWithoutPermissionIsDenied(): Unit {
|
|
86
|
+
let out = ArrayList<String>()
|
|
87
|
+
let reg = CapabilityRegistry()
|
|
88
|
+
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"empty\",\"permissions\":[]}"))
|
|
89
|
+
let hub = hubWithRegistry(reg, out)
|
|
90
|
+
registerPluginCommands(hub, NotificationPlugin())
|
|
91
|
+
registerPluginPermissionSets(reg, NotificationPlugin())
|
|
92
|
+
|
|
93
|
+
hub.handleRawMessage(
|
|
94
|
+
"{\"type\":\"invoke\",\"id\":52,\"cmd\":\"notification:notify\",\"args\":{\"title\":\"t\"}}")
|
|
95
|
+
waitSink(out, 1)
|
|
96
|
+
@Expect(strField(out[0], "error"), "command not allowed: notification:notify")
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@Test
|
|
100
|
+
func testNotificationRequiresTitle(): Unit {
|
|
101
|
+
let cmd = NotificationPlugin().commands().get("notify").getOrThrow()
|
|
102
|
+
let ctx = IpcContext("main", IpcHub())
|
|
103
|
+
var threw = false
|
|
104
|
+
try {
|
|
105
|
+
cmd.handle("notification:notify", JsonObject(), ctx)
|
|
106
|
+
} catch (e: CommandException) {
|
|
107
|
+
threw = true
|
|
108
|
+
@Expect(e.message.contains("missing argument \"title\""), true)
|
|
109
|
+
}
|
|
110
|
+
@Expect(threw, true)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
@Test
|
|
114
|
+
func testNotificationRejectsNonStringTitle(): Unit {
|
|
115
|
+
var args = JsonObject()
|
|
116
|
+
args.put("title", JsonBool(true))
|
|
117
|
+
let cmd = NotificationPlugin().commands().get("notify").getOrThrow()
|
|
118
|
+
var threw = false
|
|
119
|
+
try {
|
|
120
|
+
cmd.handle("notification:notify", args, IpcContext("main", IpcHub()))
|
|
121
|
+
} catch (e: CommandException) {
|
|
122
|
+
threw = true
|
|
123
|
+
@Expect(e.message.contains("must be a string"), true)
|
|
124
|
+
}
|
|
125
|
+
@Expect(threw, true)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@Test
|
|
129
|
+
func testNotificationWithoutHostIsReadableError(): Unit {
|
|
130
|
+
@Expect(notifErrOf(NotificationPlugin().commands().get("notify").getOrThrow(), "notification:notify",
|
|
131
|
+
notifArgs("hi", JsonString("body"))).contains("宿主未装配"), true)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@Test
|
|
135
|
+
func testNotificationRejectsUnknownCommand(): Unit {
|
|
136
|
+
@Expect(notifErrOf(NotificationPlugin().commands().get("notify").getOrThrow(), "notification:nope",
|
|
137
|
+
JsonObject()).contains("unknown notification command"), true)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
@Test
|
|
141
|
+
func testNotificationBodyIsOptional(): Unit {
|
|
142
|
+
let host = FakeHost("main")
|
|
143
|
+
let cmd = notifCmdWithHost(host)
|
|
144
|
+
|
|
145
|
+
// 只给 title:body 按空串
|
|
146
|
+
var onlyTitle = JsonObject()
|
|
147
|
+
onlyTitle.put("title", JsonString("标题"))
|
|
148
|
+
let r1 = cmd.handle("notification:notify", onlyTitle, IpcContext("main", IpcHub()))
|
|
149
|
+
match (r1.kind()) {
|
|
150
|
+
case JsonKind.JsNull => ()
|
|
151
|
+
case _ => @Expect(false, true)
|
|
152
|
+
}
|
|
153
|
+
@Expect(host.notifiedArgs, "标题|")
|
|
154
|
+
|
|
155
|
+
// body 显式 null:同样按空串(前端 o.body 传 undefined 时序列化可能落成 null)
|
|
156
|
+
cmd.handle("notification:notify", notifArgs("标题2", JsonNull()), IpcContext("main", IpcHub()))
|
|
157
|
+
@Expect(host.notifiedArgs, "标题2|")
|
|
158
|
+
|
|
159
|
+
// body 有值:原样转交
|
|
160
|
+
cmd.handle("notification:notify", notifArgs("标题3", JsonString("正文")), IpcContext("main", IpcHub()))
|
|
161
|
+
@Expect(host.notifiedArgs, "标题3|正文")
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** 桩侧「label → 宿主」解析器见 plugin_dialog_test.cj 的 routeProbeHost(同包可见) */
|
|
165
|
+
@Test
|
|
166
|
+
func testNotificationRoutesToTheWindowThatAsked(): Unit {
|
|
167
|
+
// 与 dialog / clipboard 同一条契约:报文里的 window 决定交给哪个宿主
|
|
168
|
+
let mainHost = FakeHost("main")
|
|
169
|
+
let secondHost = FakeHost("second")
|
|
170
|
+
let cmds = NotificationCommands()
|
|
171
|
+
cmds.attachHost(mainHost)
|
|
172
|
+
cmds.attachHostResolver({ label => routeProbeHost(label, mainHost, secondHost) })
|
|
173
|
+
|
|
174
|
+
cmds.handle("notification:notify", notifArgs("second 的通知", JsonString("正文")),
|
|
175
|
+
IpcContext("second", IpcHub()))
|
|
176
|
+
@Expect(secondHost.notifiedArgs, "second 的通知|正文")
|
|
177
|
+
// 关键断言:另一扇窗的宿主一点都没被碰到
|
|
178
|
+
@Expect(mainHost.notifiedArgs, "")
|
|
179
|
+
|
|
180
|
+
// 反向也成立
|
|
181
|
+
cmds.handle("notification:notify", notifArgs("main 的通知", JsonNull()), IpcContext("main", IpcHub()))
|
|
182
|
+
@Expect(mainHost.notifiedArgs, "main 的通知|")
|
|
183
|
+
@Expect(secondHost.notifiedArgs, "second 的通知|正文")
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
@Test
|
|
187
|
+
func testNotificationReportsUnknownWindowInsteadOfFallingBack(): Unit {
|
|
188
|
+
let mainHost = FakeHost("main")
|
|
189
|
+
let secondHost = FakeHost("second")
|
|
190
|
+
let cmds = NotificationCommands()
|
|
191
|
+
cmds.attachHost(mainHost)
|
|
192
|
+
cmds.attachHostResolver({ label => routeProbeHost(label, mainHost, secondHost) })
|
|
193
|
+
|
|
194
|
+
var threw = false
|
|
195
|
+
try {
|
|
196
|
+
cmds.handle("notification:notify", notifArgs("谁的", JsonNull()), IpcContext("ghost", IpcHub()))
|
|
197
|
+
} catch (e: CommandException) {
|
|
198
|
+
threw = true
|
|
199
|
+
@Expect(e.message.contains("ghost"), true)
|
|
200
|
+
}
|
|
201
|
+
@Expect(threw, true)
|
|
202
|
+
// 报错之后两个宿主都没被写过:不回落单槽
|
|
203
|
+
@Expect(mainHost.notifiedArgs, "")
|
|
204
|
+
@Expect(secondHost.notifiedArgs, "")
|
|
205
|
+
}
|
|
@@ -267,3 +267,51 @@ func testSerialPortListIsSortedAndWhitelisted(): Unit {
|
|
|
267
267
|
i++
|
|
268
268
|
}
|
|
269
269
|
}
|
|
270
|
+
|
|
271
|
+
/* ===== ohos 分支的可桌面验证面(serial_ohos.cj) =====
|
|
272
|
+
* 真正的 open/read/write 走 ohos 专属 FFI(@When 门禁),桌面上编译不到——那部分归真机取证
|
|
273
|
+
* (issue #5:USB 转串口接 OTG、DDK 权限)。下面锁的是 ohos 与桌面**共用同一份代码**的纯函数:
|
|
274
|
+
* 波特率 → termios 常量映射(不带门禁,专为可测升 public)与设备白名单的 /dev/bus 路径。
|
|
275
|
+
*/
|
|
276
|
+
|
|
277
|
+
@Test
|
|
278
|
+
func testSerialOhosBaudConstMatchesDesktopTiers(): Unit {
|
|
279
|
+
// 与 SERIAL_BAUDS 同一套档位:桌面允许的每一档,ohos 侧都必须有对应的 termios 常量
|
|
280
|
+
let tiers: Array<Int32> = [300, 600, 1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200,
|
|
281
|
+
230400, 460800, 500000, 921600, 1000000]
|
|
282
|
+
for (b in tiers) {
|
|
283
|
+
@Expect(serialOhosBaudConst(b).isSome(), true)
|
|
284
|
+
}
|
|
285
|
+
// 关键取值抽查(asm-generic:B38400 以下连续,往上 0x1000+n)
|
|
286
|
+
@Expect(serialOhosBaudConst(115200).getOrThrow(), 0x1002)
|
|
287
|
+
@Expect(serialOhosBaudConst(9600).getOrThrow(), 0xd)
|
|
288
|
+
@Expect(serialOhosBaudConst(1000000).getOrThrow(), 0x1008)
|
|
289
|
+
// 桌面已拒的档位,ohos 侧同样不收(两层拦同一套名单,不会出现桌面放行、ohos 拒)
|
|
290
|
+
@Expect(serialOhosBaudConst(14400).isNone(), true)
|
|
291
|
+
@Expect(serialOhosBaudConst(4000000).isNone(), true)
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
@Test
|
|
295
|
+
func testSerialPathLooksAllowedCoversOhosDevicePaths(): Unit {
|
|
296
|
+
// 鸿蒙特有的 USB 串口落点必须过白名单(ohos 侧 open 的边界就靠它)
|
|
297
|
+
@Expect(serialPathLooksAllowed("/dev/bus/ttyUSB0"), true)
|
|
298
|
+
@Expect(serialPathLooksAllowed("/dev/bus/ttyACM0"), true)
|
|
299
|
+
@Expect(serialPathLooksAllowed("/dev/bus/ttyS1"), true)
|
|
300
|
+
// 穿越与任意路径照旧拒
|
|
301
|
+
@Expect(serialPathLooksAllowed("/dev/bus/../ttyUSB0"), false)
|
|
302
|
+
@Expect(serialPathLooksAllowed("/data/local/ttyUSB0"), false)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
@Test
|
|
306
|
+
func testSerialPluginDescribesAllThreeHosts(): Unit {
|
|
307
|
+
let d = SerialPlugin().describe()
|
|
308
|
+
let hosts = d.supportedHosts
|
|
309
|
+
@Expect(hosts.size, 3)
|
|
310
|
+
var hasArkweb = false
|
|
311
|
+
for (h in hosts) {
|
|
312
|
+
if (h == "arkweb") {
|
|
313
|
+
hasArkweb = true
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
@Expect(hasArkweb, true)
|
|
317
|
+
}
|
package/src/tests/plugin_test.cj
CHANGED
|
@@ -218,12 +218,16 @@ func testFsPluginSurface(): Unit {
|
|
|
218
218
|
let p = FsPlugin()
|
|
219
219
|
@Expect(p.name(), "fs")
|
|
220
220
|
let names = pluginCommandNames(p)
|
|
221
|
-
@Expect(names.size,
|
|
221
|
+
@Expect(names.size, 9)
|
|
222
222
|
var hasRead = false
|
|
223
223
|
var hasWrite = false
|
|
224
224
|
var hasExists = false
|
|
225
225
|
var hasList = false
|
|
226
226
|
var hasRemove = false
|
|
227
|
+
var hasMkdir = false
|
|
228
|
+
var hasRename = false
|
|
229
|
+
var hasCopy = false
|
|
230
|
+
var hasStat = false
|
|
227
231
|
for (n in names) {
|
|
228
232
|
if (n == "fs:readText") {
|
|
229
233
|
hasRead = true
|
|
@@ -240,12 +244,28 @@ func testFsPluginSurface(): Unit {
|
|
|
240
244
|
if (n == "fs:remove") {
|
|
241
245
|
hasRemove = true
|
|
242
246
|
}
|
|
247
|
+
if (n == "fs:mkdir") {
|
|
248
|
+
hasMkdir = true
|
|
249
|
+
}
|
|
250
|
+
if (n == "fs:rename") {
|
|
251
|
+
hasRename = true
|
|
252
|
+
}
|
|
253
|
+
if (n == "fs:copy") {
|
|
254
|
+
hasCopy = true
|
|
255
|
+
}
|
|
256
|
+
if (n == "fs:stat") {
|
|
257
|
+
hasStat = true
|
|
258
|
+
}
|
|
243
259
|
}
|
|
244
260
|
@Expect(hasRead, true)
|
|
245
261
|
@Expect(hasWrite, true)
|
|
246
262
|
@Expect(hasExists, true)
|
|
247
263
|
@Expect(hasList, true) // 目录列举:笔记工具的文件树靠它,应用层不必自己拼 std.fs
|
|
248
264
|
@Expect(hasRemove, true)
|
|
265
|
+
@Expect(hasMkdir, true)
|
|
266
|
+
@Expect(hasRename, true)
|
|
267
|
+
@Expect(hasCopy, true)
|
|
268
|
+
@Expect(hasStat, true)
|
|
249
269
|
}
|
|
250
270
|
|
|
251
271
|
@Test
|
|
@@ -262,7 +282,7 @@ func testFsPluginDeclaresPermissionSets(): Unit {
|
|
|
262
282
|
let sets = pluginPermissionSets(FsPlugin())
|
|
263
283
|
@Expect(sets.size, 2)
|
|
264
284
|
let ro = sets.get("fs:readonly").getOrThrow()
|
|
265
|
-
@Expect(ro.size,
|
|
285
|
+
@Expect(ro.size, 4)
|
|
266
286
|
var roHasWrite = false
|
|
267
287
|
var roHasRemove = false
|
|
268
288
|
for (m in ro) {
|
|
@@ -275,7 +295,7 @@ func testFsPluginDeclaresPermissionSets(): Unit {
|
|
|
275
295
|
}
|
|
276
296
|
@Expect(roHasWrite, false) // 只读集不含写:最小权限不打折
|
|
277
297
|
@Expect(roHasRemove, false) // 删也同理——列目录是「读」,删除不是
|
|
278
|
-
@Expect(sets.get("fs:default").getOrThrow().size,
|
|
298
|
+
@Expect(sets.get("fs:default").getOrThrow().size, 9)
|
|
279
299
|
}
|
|
280
300
|
|
|
281
301
|
@Test
|
|
@@ -540,3 +560,46 @@ func testRenderPluginManifestEmptyList(): Unit {
|
|
|
540
560
|
@Expect(renderPluginManifest(none, "text"), "(未装配插件)\n")
|
|
541
561
|
@Expect(JsonValue.fromStr(renderPluginManifest(none, "json")).asArray().size(), 0)
|
|
542
562
|
}
|
|
563
|
+
|
|
564
|
+
class HostedDemoPlugin <: Plugin {
|
|
565
|
+
public init() {}
|
|
566
|
+
|
|
567
|
+
public func name(): String {
|
|
568
|
+
return "demo2"
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
public func commands(): HashMap<String, CommandHandler> {
|
|
572
|
+
return HashMap<String, CommandHandler>()
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
public func describe(): PluginManifest {
|
|
576
|
+
let hosts = ArrayList<String>()
|
|
577
|
+
hosts.add("webkitgtk")
|
|
578
|
+
return PluginManifest(hosts, ArrayList<String>())
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
@Test
|
|
583
|
+
func testDescribeDefault(): Unit {
|
|
584
|
+
let d = DemoPlugin().describe()
|
|
585
|
+
// 默认实现:全宿主、无能力依赖(空数组 = 不限)
|
|
586
|
+
@Expect(d.supportedHosts.size, 0)
|
|
587
|
+
@Expect(d.requiredCapabilities.size, 0)
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
@Test
|
|
591
|
+
func testDescribeOverride(): Unit {
|
|
592
|
+
let d = HostedDemoPlugin().describe()
|
|
593
|
+
@Expect(d.supportedHosts.size, 1)
|
|
594
|
+
@Expect(d.supportedHosts.get(0), "webkitgtk")
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
@Test
|
|
598
|
+
func testManifestJsonCarriesDescribe(): Unit {
|
|
599
|
+
let plugins = ArrayList<Plugin>()
|
|
600
|
+
plugins.add(HostedDemoPlugin())
|
|
601
|
+
let arr = jsonItemsOf(JsonValue.fromStr(renderPluginManifest(plugins, "json")))
|
|
602
|
+
let obj = arr[0].asObject()
|
|
603
|
+
@Expect(joinWithPipe(jsonStringsOf(obj, "supportedHosts")), "webkitgtk")
|
|
604
|
+
@Expect(jsonStringsOf(obj, "requiredCapabilities").size, 0)
|
|
605
|
+
}
|
|
@@ -1,40 +1,40 @@
|
|
|
1
|
-
package cjTauri.tests
|
|
2
|
-
|
|
3
|
-
import cjTauri.*
|
|
4
|
-
import cjTauri.plugins.ws.*
|
|
5
|
-
import std.unittest.*
|
|
6
|
-
import std.unittest.testmacro.*
|
|
7
|
-
|
|
8
|
-
/*
|
|
9
|
-
* ws 插件的策略层用例(帧 IO 归实机:真连接需要对端,单测不起网络)。
|
|
10
|
-
* urlHostOf 的 ws/wss 用例也放这里(核心共享层函数,随 ws 插件落地一起验)。
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
@Test
|
|
14
|
-
func testUrlHostOfAcceptsWsSchemes(): Unit {
|
|
15
|
-
// cjh web 的默认端点形状:ws://127.0.0.1:8765/ws
|
|
16
|
-
@Expect(urlHostOf("ws://127.0.0.1:8765/ws"), "127.0.0.1")
|
|
17
|
-
@Expect(urlHostOf("wss://Example.COM/ws"), "example.com")
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
@Test
|
|
21
|
-
func testWsPluginDeclarations(): Unit {
|
|
22
|
-
let p = WsPlugin()
|
|
23
|
-
@Expect(p.name(), "ws")
|
|
24
|
-
@Expect(p.scopeAware(), true)
|
|
25
|
-
// 权限集 default = connect + send + close:集声明 != 放行,清单引用才生效
|
|
26
|
-
@Expect(p.permissions().contains("default"), true)
|
|
27
|
-
let cmds = p.commands()
|
|
28
|
-
@Expect(cmds.contains("connect"), true)
|
|
29
|
-
@Expect(cmds.contains("send"), true)
|
|
30
|
-
@Expect(cmds.contains("close"), true)
|
|
31
|
-
// 推两个事件(frame / state),无前端 shim
|
|
32
|
-
@Expect(p.events().size == 2, true)
|
|
33
|
-
@Expect(p.jsShim().size == 0, true)
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
@Test
|
|
37
|
-
func testWsStateConstants(): Unit {
|
|
38
|
-
@Expect(CJ_WS_STATE_CONNECTED, "connected")
|
|
39
|
-
@Expect(CJ_WS_STATE_CLOSED, "closed")
|
|
40
|
-
}
|
|
1
|
+
package cjTauri.tests
|
|
2
|
+
|
|
3
|
+
import cjTauri.*
|
|
4
|
+
import cjTauri.plugins.ws.*
|
|
5
|
+
import std.unittest.*
|
|
6
|
+
import std.unittest.testmacro.*
|
|
7
|
+
|
|
8
|
+
/*
|
|
9
|
+
* ws 插件的策略层用例(帧 IO 归实机:真连接需要对端,单测不起网络)。
|
|
10
|
+
* urlHostOf 的 ws/wss 用例也放这里(核心共享层函数,随 ws 插件落地一起验)。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
@Test
|
|
14
|
+
func testUrlHostOfAcceptsWsSchemes(): Unit {
|
|
15
|
+
// cjh web 的默认端点形状:ws://127.0.0.1:8765/ws
|
|
16
|
+
@Expect(urlHostOf("ws://127.0.0.1:8765/ws"), "127.0.0.1")
|
|
17
|
+
@Expect(urlHostOf("wss://Example.COM/ws"), "example.com")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
@Test
|
|
21
|
+
func testWsPluginDeclarations(): Unit {
|
|
22
|
+
let p = WsPlugin()
|
|
23
|
+
@Expect(p.name(), "ws")
|
|
24
|
+
@Expect(p.scopeAware(), true)
|
|
25
|
+
// 权限集 default = connect + send + close:集声明 != 放行,清单引用才生效
|
|
26
|
+
@Expect(p.permissions().contains("default"), true)
|
|
27
|
+
let cmds = p.commands()
|
|
28
|
+
@Expect(cmds.contains("connect"), true)
|
|
29
|
+
@Expect(cmds.contains("send"), true)
|
|
30
|
+
@Expect(cmds.contains("close"), true)
|
|
31
|
+
// 推两个事件(frame / state),无前端 shim
|
|
32
|
+
@Expect(p.events().size == 2, true)
|
|
33
|
+
@Expect(p.jsShim().size == 0, true)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@Test
|
|
37
|
+
func testWsStateConstants(): Unit {
|
|
38
|
+
@Expect(CJ_WS_STATE_CONNECTED, "connected")
|
|
39
|
+
@Expect(CJ_WS_STATE_CLOSED, "closed")
|
|
40
|
+
}
|