cj-tauri 2.0.0 → 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 +113 -99
- package/bin/cj-tauri.js +287 -287
- package/cjpm.toml +63 -52
- package/cli/cjpm.toml +22 -22
- package/cli/src/doctor.cj +308 -306
- package/cli/src/main.cj +129 -129
- package/cli/src/manifest.cj +632 -558
- package/cli/src/manifest_test.cj +87 -0
- package/cli/src/project.cj +770 -770
- package/cli/src/resolve.cj +348 -348
- package/cli/src/scaffold.cj +369 -369
- package/cli/src/sync.cj +526 -526
- package/cli/src/util.cj +168 -168
- package/cli/templates/app/README.md +51 -48
- package/cli/templates/app/assets/capabilities/default.json +7 -7
- package/cli/templates/app/assets/ui/index.html +120 -118
- package/cli/templates/app/cj-tauri.toml +32 -22
- package/cli/templates/app/cjpm.toml +34 -34
- package/cli/templates/app/src/main.cj +101 -90
- package/cli/templates/app-react/README.md +56 -56
- package/cli/templates/app-react/assets/capabilities/default.json +7 -7
- package/cli/templates/app-react/assets/ui/index.html +25 -25
- package/cli/templates/app-react/assets/ui/package.json +19 -19
- package/cli/templates/app-react/assets/ui/src/App.css +67 -67
- package/cli/templates/app-react/assets/ui/src/App.jsx +124 -124
- package/cli/templates/app-react/assets/ui/src/main.jsx +6 -6
- package/cli/templates/app-react/assets/ui/vite.config.js +24 -24
- package/cli/templates/app-react/cj-tauri.toml +33 -23
- package/cli/templates/app-react/cjpm.toml +34 -34
- package/cli/templates/app-react/src/main.cj +91 -91
- package/cli/templates/app-vue/README.md +56 -56
- package/cli/templates/app-vue/assets/capabilities/default.json +7 -7
- package/cli/templates/app-vue/assets/ui/index.html +25 -25
- package/cli/templates/app-vue/assets/ui/package.json +18 -18
- package/cli/templates/app-vue/assets/ui/src/App.vue +176 -176
- package/cli/templates/app-vue/assets/ui/src/main.js +4 -4
- package/cli/templates/app-vue/assets/ui/vite.config.js +24 -24
- package/cli/templates/app-vue/cj-tauri.toml +33 -23
- package/cli/templates/app-vue/cjpm.toml +34 -34
- package/cli/templates/app-vue/src/main.cj +91 -91
- package/cli/templates/ohos-skeleton/AppScope/app.json5 +13 -13
- package/cli/templates/ohos-skeleton/AppScope/resources/base/element/string.json +8 -8
- package/cli/templates/ohos-skeleton/AppScope/resources/base/media/layered_image.json +6 -6
- package/cli/templates/ohos-skeleton/README.md +84 -79
- package/cli/templates/ohos-skeleton/build-hap.sh +15 -0
- package/cli/templates/ohos-skeleton/build-profile.json5 +46 -46
- package/cli/templates/ohos-skeleton/entry/build-profile.json5 +26 -26
- package/cli/templates/ohos-skeleton/entry/cjpm.toml +51 -43
- package/cli/templates/ohos-skeleton/entry/hvigorfile.ts +6 -6
- package/cli/templates/ohos-skeleton/entry/oh-package.json5 +9 -9
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/ability_stage.cj +17 -17
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/app_host.cj +94 -94
- 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 -167
- package/cli/templates/ohos-skeleton/entry/src/main/cangjie/main_ability.cj +139 -139
- package/cli/templates/ohos-skeleton/entry/src/main/module.json5 +57 -49
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/color.json +8 -8
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/string.json +16 -16
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/media/layered_image.json +6 -6
- package/cli/templates/ohos-skeleton/entry/src/main/resources/base/profile/main_pages.json +2 -2
- package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/capabilities.json +6 -6
- package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/index.html +92 -92
- package/cli/templates/ohos-skeleton/hvigor/hvigor-config.json5 +5 -5
- package/cli/templates/ohos-skeleton/hvigorfile.ts +6 -6
- package/cli/templates/ohos-skeleton/oh-package.json5 +10 -10
- 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 -53
- package/prebuilt/linux-x64/cj-tauri +0 -0
- package/src/api_system.cj +67 -67
- package/src/app.cj +325 -969
- 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 -76
- package/src/arkweb_menu.cj +216 -0
- package/src/arkweb_script.cj +142 -142
- 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 -607
- package/src/host_arkweb.cj +924 -778
- package/src/host_webkit.cj +663 -570
- package/src/host_webview2.cj +663 -570
- 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 -62
- package/src/logging.cj +127 -127
- package/src/menu.cj +197 -197
- package/src/platform/platform_app_context.cj +77 -77
- package/src/platform/platform_assets.cj +33 -33
- package/src/platform/platform_discover.cj +176 -176
- package/src/platform/platform_env.cj +29 -29
- package/src/platform/platform_feature.cj +31 -31
- package/src/platform/platform_page_source.cj +87 -87
- package/src/platform/platform_target_info.cj +32 -32
- package/src/platform_host_provider.cj +87 -87
- package/src/plugin.cj +333 -333
- package/src/plugins/clipboard/plugin_clipboard.cj +152 -152
- 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 +158 -158
- package/src/plugins/notification/plugin_notification.cj +154 -154
- package/src/plugins/serial/plugin_serial.cj +458 -454
- package/src/plugins/serial/serial_bridge.cj +194 -194
- 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 +258 -258
- package/src/plugins/sqlite/plugin_sqlite.cj +705 -710
- package/src/plugins/sqlite/sqlite_ffi.cj +206 -211
- 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 -127
- package/src/tests/capability_test.cj +243 -243
- package/src/tests/dialog_spec_test.cj +73 -0
- package/src/tests/fake_host.cj +201 -201
- 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 -251
- 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 +605 -585
- 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/prebuilt/win32-x64/cj-tauri.exe +0 -0
- package/src/plugins/serial/serial_ohos_stub.cj +0 -61
- package/src/plugins/sqlite/sqlite_ohos_stub.cj +0 -91
|
@@ -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
|
+
}
|