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,243 +1,243 @@
|
|
|
1
|
-
package cjTauri.tests
|
|
2
|
-
|
|
3
|
-
import cjTauri.*
|
|
4
|
-
import std.collection.*
|
|
5
|
-
import stdx.encoding.json.*
|
|
6
|
-
import std.unittest.*
|
|
7
|
-
import std.unittest.testmacro.*
|
|
8
|
-
|
|
9
|
-
/*
|
|
10
|
-
* 能力安全模型单元测试:默认最小权限是框架的核心承诺,
|
|
11
|
-
* 这里锁住「未声明一律拒绝」「声明了才放行」「畸形清单不炸」三条行为。
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
@Test
|
|
15
|
-
func testFromJsonParsesAllFields(): Unit {
|
|
16
|
-
let raw = "{\"identifier\":\"default\",\"windows\":[\"main\"],\"commands\":[\"greet\",\"system:ping\"],\"events\":[\"tick\"]}"
|
|
17
|
-
let cap = Capability.fromJson(JsonValue.fromStr(raw))
|
|
18
|
-
@Expect(cap.isSome(), true)
|
|
19
|
-
let c = cap.getOrThrow()
|
|
20
|
-
@Expect(c.identifier, "default")
|
|
21
|
-
@Expect(c.commands.size, 2)
|
|
22
|
-
@Expect(c.canInvoke("greet"), true)
|
|
23
|
-
@Expect(c.canInvoke("system:ping"), true)
|
|
24
|
-
@Expect(c.canEmit("tick"), true)
|
|
25
|
-
@Expect(c.windows.size, 1)
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
@Test
|
|
29
|
-
func testFromJsonDefaultsToDenyAll(): Unit {
|
|
30
|
-
// 只给 identifier:清单合法但什么都不放行(默认最小权限)
|
|
31
|
-
let cap = Capability.fromJson(JsonValue.fromStr("{\"identifier\":\"empty\"}"))
|
|
32
|
-
@Expect(cap.isSome(), true)
|
|
33
|
-
let c = cap.getOrThrow()
|
|
34
|
-
@Expect(c.canInvoke("greet"), false)
|
|
35
|
-
@Expect(c.canEmit("tick"), false)
|
|
36
|
-
@Expect(c.commands.size, 0)
|
|
37
|
-
@Expect(c.events.size, 0)
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
@Test
|
|
41
|
-
func testFromJsonRejectsInvalidShape(): Unit {
|
|
42
|
-
// 顶层不是对象
|
|
43
|
-
@Expect(Capability.fromJson(JsonValue.fromStr("[1,2]")).isNone(), true)
|
|
44
|
-
// 缺 identifier
|
|
45
|
-
@Expect(Capability.fromJson(JsonValue.fromStr("{\"commands\":[\"a\"]}")).isNone(), true)
|
|
46
|
-
// identifier 不是字符串
|
|
47
|
-
@Expect(Capability.fromJson(JsonValue.fromStr("{\"identifier\":7}")).isNone(), true)
|
|
48
|
-
// commands 不是数组:宽容跳过,清单本身仍合法(放行集合为空)
|
|
49
|
-
let lenient = Capability.fromJson(JsonValue.fromStr("{\"identifier\":\"x\",\"commands\":\"greet\"}"))
|
|
50
|
-
@Expect(lenient.isSome(), true)
|
|
51
|
-
@Expect(lenient.getOrThrow().canInvoke("greet"), false)
|
|
52
|
-
// 注:非 JSON 文本在 JsonValue.fromStr 处就抛 JsonException,进不到 fromJson;
|
|
53
|
-
// 这层防护在调用方——InvokeRequest.parse 与 capability_loader 的 try 里(前者见 ipc 测试)。
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
@Test
|
|
57
|
-
func testFromJsonSkipsNonStringEntries(): Unit {
|
|
58
|
-
// 数组里混入数字/对象:跳过而不是整体失败
|
|
59
|
-
let raw = "{\"identifier\":\"mixed\",\"commands\":[\"ok\",7,{\"a\":1}],\"events\":[true,\"tick\"]}"
|
|
60
|
-
let cap = Capability.fromJson(JsonValue.fromStr(raw))
|
|
61
|
-
@Expect(cap.isSome(), true)
|
|
62
|
-
let c = cap.getOrThrow()
|
|
63
|
-
@Expect(c.canInvoke("ok"), true)
|
|
64
|
-
@Expect(c.commands.size, 1)
|
|
65
|
-
@Expect(c.canEmit("tick"), true)
|
|
66
|
-
@Expect(c.events.size, 1)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
@Test
|
|
70
|
-
func testRegistryAggregatesCapabilities(): Unit {
|
|
71
|
-
let reg = CapabilityRegistry()
|
|
72
|
-
let a = Capability("a")
|
|
73
|
-
a.allowCommand("greet")
|
|
74
|
-
let b = Capability("b")
|
|
75
|
-
b.allowCommand("system:ping")
|
|
76
|
-
b.allowEvent("tick")
|
|
77
|
-
reg.add(a)
|
|
78
|
-
reg.add(b)
|
|
79
|
-
@Expect(reg.size(), 2)
|
|
80
|
-
// 任一清单放行即通过
|
|
81
|
-
@Expect(reg.canInvoke("greet"), true)
|
|
82
|
-
@Expect(reg.canInvoke("system:ping"), true)
|
|
83
|
-
// 都没声明的一律拒绝
|
|
84
|
-
@Expect(reg.canInvoke("system:echo"), false)
|
|
85
|
-
@Expect(reg.canEmit("tick"), true)
|
|
86
|
-
@Expect(reg.canEmit("other"), false)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
@Test
|
|
90
|
-
func testRegistryEmptyDeniesEverything(): Unit {
|
|
91
|
-
let reg = CapabilityRegistry()
|
|
92
|
-
@Expect(reg.canInvoke("system:version"), false)
|
|
93
|
-
@Expect(reg.canEmit("tick"), false)
|
|
94
|
-
@Expect(reg.size(), 0)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
@Test
|
|
98
|
-
func testRegistryAddFromJson(): Unit {
|
|
99
|
-
let reg = CapabilityRegistry()
|
|
100
|
-
let ok = reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"x\",\"commands\":[\"greet\"]}"))
|
|
101
|
-
@Expect(ok, true)
|
|
102
|
-
@Expect(reg.canInvoke("greet"), true)
|
|
103
|
-
// 畸形清单:返回 false 且不影响已有能力
|
|
104
|
-
let bad = reg.addFromJson(JsonValue.fromStr("{\"nope\":1}"))
|
|
105
|
-
@Expect(bad, false)
|
|
106
|
-
@Expect(reg.size(), 1)
|
|
107
|
-
@Expect(reg.canInvoke("greet"), true)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
@Test
|
|
111
|
-
func testLoaderToleratesMissingDir(): Unit {
|
|
112
|
-
// 目录不存在不是错误:返回 0,注册表保持为空(首次放到没有 capabilities/ 的目录里跑就是这样)
|
|
113
|
-
let reg = CapabilityRegistry()
|
|
114
|
-
@Expect(loadCapabilitiesFromDir("no/such/capabilities/dir", reg), 0)
|
|
115
|
-
@Expect(reg.size(), 0)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/*
|
|
119
|
-
* 命名权限集(v1.1):清单里按集名引用,成员展开后与明文同权。
|
|
120
|
-
* 三条底线要锁住:集名单独存在不放行、注册了没引用不放行、未知集不生效也不炸。
|
|
121
|
-
*/
|
|
122
|
-
|
|
123
|
-
@Test
|
|
124
|
-
func testFromJsonParsesPermissionSets(): Unit {
|
|
125
|
-
let cap = Capability.fromJson(JsonValue.fromStr("{\"identifier\":\"default\",\"permissions\":[\"fs:readonly\"]}"))
|
|
126
|
-
@Expect(cap.isSome(), true)
|
|
127
|
-
let c = cap.getOrThrow()
|
|
128
|
-
@Expect(c.permissions.size, 1)
|
|
129
|
-
// 集名只是「引用」:没有插件注册同名集时,它一条也不放行
|
|
130
|
-
@Expect(c.canInvoke("fs:readText"), false)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
@Test
|
|
134
|
-
func testPermissionSetExpandsToMembers(): Unit {
|
|
135
|
-
let reg = CapabilityRegistry()
|
|
136
|
-
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\",\"permissions\":[\"fs:readonly\"]}"))
|
|
137
|
-
let members = ArrayList<String>()
|
|
138
|
-
members.add("fs:readText")
|
|
139
|
-
members.add("fs:exists")
|
|
140
|
-
reg.addPermissionSet("fs:readonly", members)
|
|
141
|
-
@Expect(reg.canInvoke("fs:readText"), true)
|
|
142
|
-
@Expect(reg.canInvoke("fs:exists"), true)
|
|
143
|
-
@Expect(reg.canInvoke("fs:writeText"), false) // 集外命令照旧拒绝
|
|
144
|
-
@Expect(reg.canInvoke("system:version"), false) // 内置命令的放行规则没变
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
@Test
|
|
148
|
-
func testPermissionSetCoversEventsToo(): Unit {
|
|
149
|
-
let reg = CapabilityRegistry()
|
|
150
|
-
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\",\"permissions\":[\"demo:watch\"]}"))
|
|
151
|
-
let members = ArrayList<String>()
|
|
152
|
-
members.add("demo:changed")
|
|
153
|
-
reg.addPermissionSet("demo:watch", members)
|
|
154
|
-
@Expect(reg.canEmit("demo:changed"), true)
|
|
155
|
-
@Expect(reg.canEmit("demo:other"), false)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
@Test
|
|
159
|
-
func testRegisteredPermissionSetNotReferencedStaysDenied(): Unit {
|
|
160
|
-
// 集注册了,但清单没引用它:一条也不放行(默认最小权限不打折)
|
|
161
|
-
let reg = CapabilityRegistry()
|
|
162
|
-
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\"}"))
|
|
163
|
-
let members = ArrayList<String>()
|
|
164
|
-
members.add("fs:readText")
|
|
165
|
-
reg.addPermissionSet("fs:readonly", members)
|
|
166
|
-
@Expect(reg.canInvoke("fs:readText"), false)
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
@Test
|
|
170
|
-
func testPermissionSetWithoutProviderIsInert(): Unit {
|
|
171
|
-
let reg = CapabilityRegistry()
|
|
172
|
-
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\",\"permissions\":[\"nope:none\"],\"commands\":[\"greet\"]}"))
|
|
173
|
-
@Expect(reg.canInvoke("greet"), true) // 明文命令不受影响
|
|
174
|
-
@Expect(reg.canInvoke("nope:thing"), false) // 未知集不生效、也不炸
|
|
175
|
-
@Expect(reg.declaredPermissionSets().size, 1) // 但要能被列出来,供启动提示
|
|
176
|
-
@Expect(reg.hasPermissionSet("nope:none"), false)
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
@Test
|
|
180
|
-
func testPlainNameAndPermissionSetCoexist(): Unit {
|
|
181
|
-
// 两种写法混用:并集生效(旧清单可以逐条迁移)
|
|
182
|
-
let reg = CapabilityRegistry()
|
|
183
|
-
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\",\"commands\":[\"fs:exists\"],\"permissions\":[\"fs:readonly\"]}"))
|
|
184
|
-
let members = ArrayList<String>()
|
|
185
|
-
members.add("fs:readText")
|
|
186
|
-
reg.addPermissionSet("fs:readonly", members)
|
|
187
|
-
@Expect(reg.canInvoke("fs:exists"), true)
|
|
188
|
-
@Expect(reg.canInvoke("fs:readText"), true)
|
|
189
|
-
@Expect(reg.canInvoke("fs:writeText"), false)
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/*
|
|
193
|
-
* 白名单的**运行期加白**(D-021):与清单同权、先 deny 后 allow、只收具体值。
|
|
194
|
-
* 这三条正是它敢开「运行期」这个口子的前提——缺一条就等于把防线交给页面。
|
|
195
|
-
*/
|
|
196
|
-
|
|
197
|
-
@Test
|
|
198
|
-
func testRuntimeAllowExtendsManifestScope(): Unit {
|
|
199
|
-
let reg = CapabilityRegistry()
|
|
200
|
-
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\",\"scopes\":{\"http\":{\"allow\":[\"api.a.com\"]}}}"))
|
|
201
|
-
@Expect(reg.scopeAllowsValue("http", "api.a.com"), true)
|
|
202
|
-
@Expect(reg.scopeAllowsValue("http", "api.b.com"), false)
|
|
203
|
-
// 叠加一条后:新旧都在,没加的照旧拒
|
|
204
|
-
@Expect(reg.addRuntimeScopeAllow("http", "api.b.com"), true)
|
|
205
|
-
@Expect(reg.scopeAllowsValue("http", "api.b.com"), true)
|
|
206
|
-
@Expect(reg.scopeAllowsValue("http", "api.a.com"), true)
|
|
207
|
-
@Expect(reg.scopeAllowsValue("http", "api.evil.com"), false)
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
@Test
|
|
211
|
-
func testRuntimeAllowCannotBeatDeny(): Unit {
|
|
212
|
-
// deny 命中即拒:加白掀不掉它(运行期条目走的仍是「先 deny 后 allow」同一条判定)
|
|
213
|
-
let reg = CapabilityRegistry()
|
|
214
|
-
reg.addFromJson(JsonValue.fromStr(
|
|
215
|
-
"{\"identifier\":\"d\",\"scopes\":{\"http\":{\"allow\":[\"api.a.com\"],\"deny\":[\"api.a.com\"]}}}"))
|
|
216
|
-
@Expect(reg.scopeAllowsValue("http", "api.a.com"), false)
|
|
217
|
-
@Expect(reg.addRuntimeScopeAllow("http", "api.a.com"), true)
|
|
218
|
-
@Expect(reg.scopeAllowsValue("http", "api.a.com"), false)
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
@Test
|
|
222
|
-
func testRuntimeAllowRefusesGlobPattern(): Unit {
|
|
223
|
-
// 页面可控字符串里带一个通配就等于全放行:注册中心必须当场拒,且名单一条都不多
|
|
224
|
-
let reg = CapabilityRegistry()
|
|
225
|
-
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\",\"scopes\":{\"http\":{\"allow\":[\"api.a.com\"]}}}"))
|
|
226
|
-
@Expect(reg.addRuntimeScopeAllow("http", "*"), false)
|
|
227
|
-
@Expect(reg.addRuntimeScopeAllow("http", "api.*.com"), false)
|
|
228
|
-
@Expect(reg.addRuntimeScopeAllow("http", "api.a.com\n"), false)
|
|
229
|
-
@Expect(reg.addRuntimeScopeAllow("http", ""), false)
|
|
230
|
-
@Expect(reg.scopeAllowsValue("http", "api.evil.com"), false)
|
|
231
|
-
@Expect(reg.scopeForPlugin("http").getOrThrow().allow.size, 1)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
@Test
|
|
235
|
-
func testRuntimeAllowIgnoredWhenManifestHasNoScope(): Unit {
|
|
236
|
-
// 清单没给该插件写 scope:调用方按旧行为全量放行,叠加项不得把它收窄成「只放行这一条」
|
|
237
|
-
let reg = CapabilityRegistry()
|
|
238
|
-
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\"}"))
|
|
239
|
-
@Expect(reg.scopeAllowsValue("http", "anything.at.all"), true)
|
|
240
|
-
@Expect(reg.addRuntimeScopeAllow("http", "api.b.com"), true)
|
|
241
|
-
@Expect(reg.scopeForPlugin("http").isNone(), true)
|
|
242
|
-
@Expect(reg.scopeAllowsValue("http", "anything.at.all"), true)
|
|
243
|
-
}
|
|
1
|
+
package cjTauri.tests
|
|
2
|
+
|
|
3
|
+
import cjTauri.*
|
|
4
|
+
import std.collection.*
|
|
5
|
+
import stdx.encoding.json.*
|
|
6
|
+
import std.unittest.*
|
|
7
|
+
import std.unittest.testmacro.*
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* 能力安全模型单元测试:默认最小权限是框架的核心承诺,
|
|
11
|
+
* 这里锁住「未声明一律拒绝」「声明了才放行」「畸形清单不炸」三条行为。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
@Test
|
|
15
|
+
func testFromJsonParsesAllFields(): Unit {
|
|
16
|
+
let raw = "{\"identifier\":\"default\",\"windows\":[\"main\"],\"commands\":[\"greet\",\"system:ping\"],\"events\":[\"tick\"]}"
|
|
17
|
+
let cap = Capability.fromJson(JsonValue.fromStr(raw))
|
|
18
|
+
@Expect(cap.isSome(), true)
|
|
19
|
+
let c = cap.getOrThrow()
|
|
20
|
+
@Expect(c.identifier, "default")
|
|
21
|
+
@Expect(c.commands.size, 2)
|
|
22
|
+
@Expect(c.canInvoke("greet"), true)
|
|
23
|
+
@Expect(c.canInvoke("system:ping"), true)
|
|
24
|
+
@Expect(c.canEmit("tick"), true)
|
|
25
|
+
@Expect(c.windows.size, 1)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
@Test
|
|
29
|
+
func testFromJsonDefaultsToDenyAll(): Unit {
|
|
30
|
+
// 只给 identifier:清单合法但什么都不放行(默认最小权限)
|
|
31
|
+
let cap = Capability.fromJson(JsonValue.fromStr("{\"identifier\":\"empty\"}"))
|
|
32
|
+
@Expect(cap.isSome(), true)
|
|
33
|
+
let c = cap.getOrThrow()
|
|
34
|
+
@Expect(c.canInvoke("greet"), false)
|
|
35
|
+
@Expect(c.canEmit("tick"), false)
|
|
36
|
+
@Expect(c.commands.size, 0)
|
|
37
|
+
@Expect(c.events.size, 0)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
@Test
|
|
41
|
+
func testFromJsonRejectsInvalidShape(): Unit {
|
|
42
|
+
// 顶层不是对象
|
|
43
|
+
@Expect(Capability.fromJson(JsonValue.fromStr("[1,2]")).isNone(), true)
|
|
44
|
+
// 缺 identifier
|
|
45
|
+
@Expect(Capability.fromJson(JsonValue.fromStr("{\"commands\":[\"a\"]}")).isNone(), true)
|
|
46
|
+
// identifier 不是字符串
|
|
47
|
+
@Expect(Capability.fromJson(JsonValue.fromStr("{\"identifier\":7}")).isNone(), true)
|
|
48
|
+
// commands 不是数组:宽容跳过,清单本身仍合法(放行集合为空)
|
|
49
|
+
let lenient = Capability.fromJson(JsonValue.fromStr("{\"identifier\":\"x\",\"commands\":\"greet\"}"))
|
|
50
|
+
@Expect(lenient.isSome(), true)
|
|
51
|
+
@Expect(lenient.getOrThrow().canInvoke("greet"), false)
|
|
52
|
+
// 注:非 JSON 文本在 JsonValue.fromStr 处就抛 JsonException,进不到 fromJson;
|
|
53
|
+
// 这层防护在调用方——InvokeRequest.parse 与 capability_loader 的 try 里(前者见 ipc 测试)。
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
@Test
|
|
57
|
+
func testFromJsonSkipsNonStringEntries(): Unit {
|
|
58
|
+
// 数组里混入数字/对象:跳过而不是整体失败
|
|
59
|
+
let raw = "{\"identifier\":\"mixed\",\"commands\":[\"ok\",7,{\"a\":1}],\"events\":[true,\"tick\"]}"
|
|
60
|
+
let cap = Capability.fromJson(JsonValue.fromStr(raw))
|
|
61
|
+
@Expect(cap.isSome(), true)
|
|
62
|
+
let c = cap.getOrThrow()
|
|
63
|
+
@Expect(c.canInvoke("ok"), true)
|
|
64
|
+
@Expect(c.commands.size, 1)
|
|
65
|
+
@Expect(c.canEmit("tick"), true)
|
|
66
|
+
@Expect(c.events.size, 1)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@Test
|
|
70
|
+
func testRegistryAggregatesCapabilities(): Unit {
|
|
71
|
+
let reg = CapabilityRegistry()
|
|
72
|
+
let a = Capability("a")
|
|
73
|
+
a.allowCommand("greet")
|
|
74
|
+
let b = Capability("b")
|
|
75
|
+
b.allowCommand("system:ping")
|
|
76
|
+
b.allowEvent("tick")
|
|
77
|
+
reg.add(a)
|
|
78
|
+
reg.add(b)
|
|
79
|
+
@Expect(reg.size(), 2)
|
|
80
|
+
// 任一清单放行即通过
|
|
81
|
+
@Expect(reg.canInvoke("greet"), true)
|
|
82
|
+
@Expect(reg.canInvoke("system:ping"), true)
|
|
83
|
+
// 都没声明的一律拒绝
|
|
84
|
+
@Expect(reg.canInvoke("system:echo"), false)
|
|
85
|
+
@Expect(reg.canEmit("tick"), true)
|
|
86
|
+
@Expect(reg.canEmit("other"), false)
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@Test
|
|
90
|
+
func testRegistryEmptyDeniesEverything(): Unit {
|
|
91
|
+
let reg = CapabilityRegistry()
|
|
92
|
+
@Expect(reg.canInvoke("system:version"), false)
|
|
93
|
+
@Expect(reg.canEmit("tick"), false)
|
|
94
|
+
@Expect(reg.size(), 0)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
@Test
|
|
98
|
+
func testRegistryAddFromJson(): Unit {
|
|
99
|
+
let reg = CapabilityRegistry()
|
|
100
|
+
let ok = reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"x\",\"commands\":[\"greet\"]}"))
|
|
101
|
+
@Expect(ok, true)
|
|
102
|
+
@Expect(reg.canInvoke("greet"), true)
|
|
103
|
+
// 畸形清单:返回 false 且不影响已有能力
|
|
104
|
+
let bad = reg.addFromJson(JsonValue.fromStr("{\"nope\":1}"))
|
|
105
|
+
@Expect(bad, false)
|
|
106
|
+
@Expect(reg.size(), 1)
|
|
107
|
+
@Expect(reg.canInvoke("greet"), true)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
@Test
|
|
111
|
+
func testLoaderToleratesMissingDir(): Unit {
|
|
112
|
+
// 目录不存在不是错误:返回 0,注册表保持为空(首次放到没有 capabilities/ 的目录里跑就是这样)
|
|
113
|
+
let reg = CapabilityRegistry()
|
|
114
|
+
@Expect(loadCapabilitiesFromDir("no/such/capabilities/dir", reg), 0)
|
|
115
|
+
@Expect(reg.size(), 0)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/*
|
|
119
|
+
* 命名权限集(v1.1):清单里按集名引用,成员展开后与明文同权。
|
|
120
|
+
* 三条底线要锁住:集名单独存在不放行、注册了没引用不放行、未知集不生效也不炸。
|
|
121
|
+
*/
|
|
122
|
+
|
|
123
|
+
@Test
|
|
124
|
+
func testFromJsonParsesPermissionSets(): Unit {
|
|
125
|
+
let cap = Capability.fromJson(JsonValue.fromStr("{\"identifier\":\"default\",\"permissions\":[\"fs:readonly\"]}"))
|
|
126
|
+
@Expect(cap.isSome(), true)
|
|
127
|
+
let c = cap.getOrThrow()
|
|
128
|
+
@Expect(c.permissions.size, 1)
|
|
129
|
+
// 集名只是「引用」:没有插件注册同名集时,它一条也不放行
|
|
130
|
+
@Expect(c.canInvoke("fs:readText"), false)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@Test
|
|
134
|
+
func testPermissionSetExpandsToMembers(): Unit {
|
|
135
|
+
let reg = CapabilityRegistry()
|
|
136
|
+
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\",\"permissions\":[\"fs:readonly\"]}"))
|
|
137
|
+
let members = ArrayList<String>()
|
|
138
|
+
members.add("fs:readText")
|
|
139
|
+
members.add("fs:exists")
|
|
140
|
+
reg.addPermissionSet("fs:readonly", members)
|
|
141
|
+
@Expect(reg.canInvoke("fs:readText"), true)
|
|
142
|
+
@Expect(reg.canInvoke("fs:exists"), true)
|
|
143
|
+
@Expect(reg.canInvoke("fs:writeText"), false) // 集外命令照旧拒绝
|
|
144
|
+
@Expect(reg.canInvoke("system:version"), false) // 内置命令的放行规则没变
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@Test
|
|
148
|
+
func testPermissionSetCoversEventsToo(): Unit {
|
|
149
|
+
let reg = CapabilityRegistry()
|
|
150
|
+
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\",\"permissions\":[\"demo:watch\"]}"))
|
|
151
|
+
let members = ArrayList<String>()
|
|
152
|
+
members.add("demo:changed")
|
|
153
|
+
reg.addPermissionSet("demo:watch", members)
|
|
154
|
+
@Expect(reg.canEmit("demo:changed"), true)
|
|
155
|
+
@Expect(reg.canEmit("demo:other"), false)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@Test
|
|
159
|
+
func testRegisteredPermissionSetNotReferencedStaysDenied(): Unit {
|
|
160
|
+
// 集注册了,但清单没引用它:一条也不放行(默认最小权限不打折)
|
|
161
|
+
let reg = CapabilityRegistry()
|
|
162
|
+
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\"}"))
|
|
163
|
+
let members = ArrayList<String>()
|
|
164
|
+
members.add("fs:readText")
|
|
165
|
+
reg.addPermissionSet("fs:readonly", members)
|
|
166
|
+
@Expect(reg.canInvoke("fs:readText"), false)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@Test
|
|
170
|
+
func testPermissionSetWithoutProviderIsInert(): Unit {
|
|
171
|
+
let reg = CapabilityRegistry()
|
|
172
|
+
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\",\"permissions\":[\"nope:none\"],\"commands\":[\"greet\"]}"))
|
|
173
|
+
@Expect(reg.canInvoke("greet"), true) // 明文命令不受影响
|
|
174
|
+
@Expect(reg.canInvoke("nope:thing"), false) // 未知集不生效、也不炸
|
|
175
|
+
@Expect(reg.declaredPermissionSets().size, 1) // 但要能被列出来,供启动提示
|
|
176
|
+
@Expect(reg.hasPermissionSet("nope:none"), false)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@Test
|
|
180
|
+
func testPlainNameAndPermissionSetCoexist(): Unit {
|
|
181
|
+
// 两种写法混用:并集生效(旧清单可以逐条迁移)
|
|
182
|
+
let reg = CapabilityRegistry()
|
|
183
|
+
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\",\"commands\":[\"fs:exists\"],\"permissions\":[\"fs:readonly\"]}"))
|
|
184
|
+
let members = ArrayList<String>()
|
|
185
|
+
members.add("fs:readText")
|
|
186
|
+
reg.addPermissionSet("fs:readonly", members)
|
|
187
|
+
@Expect(reg.canInvoke("fs:exists"), true)
|
|
188
|
+
@Expect(reg.canInvoke("fs:readText"), true)
|
|
189
|
+
@Expect(reg.canInvoke("fs:writeText"), false)
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/*
|
|
193
|
+
* 白名单的**运行期加白**(D-021):与清单同权、先 deny 后 allow、只收具体值。
|
|
194
|
+
* 这三条正是它敢开「运行期」这个口子的前提——缺一条就等于把防线交给页面。
|
|
195
|
+
*/
|
|
196
|
+
|
|
197
|
+
@Test
|
|
198
|
+
func testRuntimeAllowExtendsManifestScope(): Unit {
|
|
199
|
+
let reg = CapabilityRegistry()
|
|
200
|
+
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\",\"scopes\":{\"http\":{\"allow\":[\"api.a.com\"]}}}"))
|
|
201
|
+
@Expect(reg.scopeAllowsValue("http", "api.a.com"), true)
|
|
202
|
+
@Expect(reg.scopeAllowsValue("http", "api.b.com"), false)
|
|
203
|
+
// 叠加一条后:新旧都在,没加的照旧拒
|
|
204
|
+
@Expect(reg.addRuntimeScopeAllow("http", "api.b.com"), true)
|
|
205
|
+
@Expect(reg.scopeAllowsValue("http", "api.b.com"), true)
|
|
206
|
+
@Expect(reg.scopeAllowsValue("http", "api.a.com"), true)
|
|
207
|
+
@Expect(reg.scopeAllowsValue("http", "api.evil.com"), false)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
@Test
|
|
211
|
+
func testRuntimeAllowCannotBeatDeny(): Unit {
|
|
212
|
+
// deny 命中即拒:加白掀不掉它(运行期条目走的仍是「先 deny 后 allow」同一条判定)
|
|
213
|
+
let reg = CapabilityRegistry()
|
|
214
|
+
reg.addFromJson(JsonValue.fromStr(
|
|
215
|
+
"{\"identifier\":\"d\",\"scopes\":{\"http\":{\"allow\":[\"api.a.com\"],\"deny\":[\"api.a.com\"]}}}"))
|
|
216
|
+
@Expect(reg.scopeAllowsValue("http", "api.a.com"), false)
|
|
217
|
+
@Expect(reg.addRuntimeScopeAllow("http", "api.a.com"), true)
|
|
218
|
+
@Expect(reg.scopeAllowsValue("http", "api.a.com"), false)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@Test
|
|
222
|
+
func testRuntimeAllowRefusesGlobPattern(): Unit {
|
|
223
|
+
// 页面可控字符串里带一个通配就等于全放行:注册中心必须当场拒,且名单一条都不多
|
|
224
|
+
let reg = CapabilityRegistry()
|
|
225
|
+
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\",\"scopes\":{\"http\":{\"allow\":[\"api.a.com\"]}}}"))
|
|
226
|
+
@Expect(reg.addRuntimeScopeAllow("http", "*"), false)
|
|
227
|
+
@Expect(reg.addRuntimeScopeAllow("http", "api.*.com"), false)
|
|
228
|
+
@Expect(reg.addRuntimeScopeAllow("http", "api.a.com\n"), false)
|
|
229
|
+
@Expect(reg.addRuntimeScopeAllow("http", ""), false)
|
|
230
|
+
@Expect(reg.scopeAllowsValue("http", "api.evil.com"), false)
|
|
231
|
+
@Expect(reg.scopeForPlugin("http").getOrThrow().allow.size, 1)
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
@Test
|
|
235
|
+
func testRuntimeAllowIgnoredWhenManifestHasNoScope(): Unit {
|
|
236
|
+
// 清单没给该插件写 scope:调用方按旧行为全量放行,叠加项不得把它收窄成「只放行这一条」
|
|
237
|
+
let reg = CapabilityRegistry()
|
|
238
|
+
reg.addFromJson(JsonValue.fromStr("{\"identifier\":\"d\"}"))
|
|
239
|
+
@Expect(reg.scopeAllowsValue("http", "anything.at.all"), true)
|
|
240
|
+
@Expect(reg.addRuntimeScopeAllow("http", "api.b.com"), true)
|
|
241
|
+
@Expect(reg.scopeForPlugin("http").isNone(), true)
|
|
242
|
+
@Expect(reg.scopeAllowsValue("http", "anything.at.all"), true)
|
|
243
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
package cjTauri.tests
|
|
2
|
+
|
|
3
|
+
import cjTauri.*
|
|
4
|
+
import std.unittest.*
|
|
5
|
+
import std.unittest.testmacro.*
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* 对话框「形状」判定的单元测试(共享层 `src/dialog_spec.cj`)。
|
|
9
|
+
*
|
|
10
|
+
* 这些判定由桌面原生对话框与鸿蒙应用侧 overlay 共用,所以放这里测——原先把判定写在
|
|
11
|
+
* `host_arkweb.cj` 里,那个文件带 `@When[env == "ohos"]` 门禁,桌面 `cjpm test` 编不到,
|
|
12
|
+
* 等于这段语义一直没有回归网。
|
|
13
|
+
*
|
|
14
|
+
* `@Test` 只能修饰**顶层函数**(写成 class 方法会报 `@Test modified function is not top level`)。
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/** 形状的可读名(enum 没派生 Equatable,断言走字符串比,绕开 `@Expect` 对 enum 的推断限制) */
|
|
18
|
+
func dialogShapeName(shape: DialogShape): String {
|
|
19
|
+
match (shape) {
|
|
20
|
+
case DialogShape.Message => return "message"
|
|
21
|
+
case DialogShape.Confirm => return "confirm"
|
|
22
|
+
case DialogShape.Prompt => return "prompt"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@Test
|
|
27
|
+
func dialogKindEmptyFallsBackToInfo() {
|
|
28
|
+
@Expect(normalizeDialogKind("") == "info")
|
|
29
|
+
@Expect(normalizeDialogKind(" ") == "info")
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Test
|
|
33
|
+
func dialogKindTrimsAndLowers() {
|
|
34
|
+
@Expect(normalizeDialogKind(" WARNING ") == "warning")
|
|
35
|
+
@Expect(normalizeDialogKind("Confirm") == "confirm")
|
|
36
|
+
@Expect(normalizeDialogKind("ERROR") == "error")
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@Test
|
|
40
|
+
func dialogKindKeepsKnownValues() {
|
|
41
|
+
@Expect(normalizeDialogKind("info") == "info")
|
|
42
|
+
@Expect(normalizeDialogKind("prompt") == "prompt")
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* 页面把 kind 写错(打错字、传了个不存在的词)时回落 info:只影响外观,不该让对话框不弹 */
|
|
46
|
+
@Test
|
|
47
|
+
func dialogKindUnknownFallsBackToInfo() {
|
|
48
|
+
@Expect(normalizeDialogKind("fatal") == "info")
|
|
49
|
+
@Expect(normalizeDialogKind("警告") == "info")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@Test
|
|
53
|
+
func dialogShapeOfSingleKeyKinds() {
|
|
54
|
+
@Expect(dialogShapeName(dialogShapeOf("info")) == "message")
|
|
55
|
+
@Expect(dialogShapeName(dialogShapeOf("warning")) == "message")
|
|
56
|
+
@Expect(dialogShapeName(dialogShapeOf("error")) == "message")
|
|
57
|
+
@Expect(dialogShapeName(dialogShapeOf("")) == "message")
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
@Test
|
|
61
|
+
func dialogShapeOfTwoKeyKinds() {
|
|
62
|
+
@Expect(dialogShapeName(dialogShapeOf("confirm")) == "confirm")
|
|
63
|
+
@Expect(dialogShapeName(dialogShapeOf("PROMPT")) == "prompt")
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* 两键判定的口径:Confirm 与 Prompt 都要「确定 / 取消」,Message 只有一个确定键 */
|
|
67
|
+
@Test
|
|
68
|
+
func dialogShapeTwoKeyMatchesShape() {
|
|
69
|
+
@Expect(dialogShapeIsTwoKey(dialogShapeOf("confirm")))
|
|
70
|
+
@Expect(dialogShapeIsTwoKey(dialogShapeOf("prompt")))
|
|
71
|
+
@Expect(!dialogShapeIsTwoKey(dialogShapeOf("info")))
|
|
72
|
+
@Expect(!dialogShapeIsTwoKey(dialogShapeOf("fatal")))
|
|
73
|
+
}
|