cj-tauri 0.8.0 → 1.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 +3 -3
- package/cjpm.toml +52 -25
- package/cli/cjpm.toml +1 -1
- package/cli/src/doctor.cj +304 -0
- package/cli/src/main.cj +33 -7
- package/cli/src/project.cj +59 -1
- package/cli/src/resolve.cj +348 -304
- package/cli/src/scaffold.cj +139 -22
- package/cli/templates/ohos-pc/AppScope/app.json5 +13 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/element/string.json +8 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/media/background.png +0 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/media/foreground.png +0 -0
- package/cli/templates/ohos-pc/AppScope/resources/base/media/layered_image.json +6 -0
- package/cli/templates/ohos-pc/README.md +79 -0
- package/cli/templates/ohos-pc/build-hap.sh +190 -0
- package/cli/templates/ohos-pc/build-profile.json5 +46 -0
- package/cli/templates/ohos-pc/entry/build-profile.json5 +26 -0
- package/cli/templates/ohos-pc/entry/cjpm.toml +43 -0
- package/cli/templates/ohos-pc/entry/hvigorfile.ts +6 -0
- package/cli/templates/ohos-pc/entry/oh-package.json5 +9 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/ability_stage.cj +17 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/app_host.cj +94 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/index.cj +167 -0
- package/cli/templates/ohos-pc/entry/src/main/cangjie/main_ability.cj +139 -0
- package/cli/templates/ohos-pc/entry/src/main/module.json5 +49 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/element/color.json +8 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/element/string.json +16 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/background.png +0 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/foreground.png +0 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/layered_image.json +6 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/media/startIcon.png +0 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/base/profile/main_pages.json +2 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/rawfile/capabilities.json +6 -0
- package/cli/templates/ohos-pc/entry/src/main/resources/rawfile/index.html +92 -0
- package/cli/templates/ohos-pc/hvigor/hvigor-config.json5 +5 -0
- package/cli/templates/ohos-pc/hvigorfile.ts +6 -0
- package/cli/templates/ohos-pc/oh-package.json5 +10 -0
- package/native/build_linux.sh +30 -2
- package/native/build_win.bat +62 -61
- package/native/core/bridge_core.c +1626 -0
- package/native/{bridge_core.h → core/bridge_core.h} +246 -6
- package/native/platforms/harmonyos/README.md +106 -0
- package/native/platforms/linux/bridge_linux.c +2485 -0
- package/native/platforms/windows/bridge_win.c +3248 -0
- package/native/tests/test_bridge_core.c +528 -1
- package/package.json +12 -3
- package/prebuilt/win32-x64/cj-tauri.exe +0 -0
- package/src/api_system.cj +10 -2
- package/src/app.cj +248 -85
- package/src/arkweb_proxy.cj +56 -0
- package/src/arkweb_script.cj +154 -0
- package/src/capability.cj +140 -0
- package/src/clipboard_bridge.cj +55 -0
- package/src/dragdrop.cj +26 -0
- package/src/embedded_assets.cj +98 -0
- package/src/hexutil.cj +53 -0
- package/src/host.cj +197 -26
- package/src/host_arkweb.cj +740 -0
- package/src/host_factory.cj +48 -0
- package/src/host_webkit.cj +202 -26
- package/src/host_webview2.cj +176 -3
- package/src/hotkey.cj +66 -0
- package/src/ipc_hub.cj +49 -0
- package/src/ipc_message.cj +2 -1
- package/src/jsutil.cj +33 -0
- package/src/log_bridge.cj +107 -0
- package/src/logging.cj +127 -0
- package/src/menu.cj +197 -196
- package/src/plugin.cj +40 -0
- package/src/plugins/clipboard/plugin_clipboard.cj +144 -0
- package/src/{plugin_dialog.cj → plugins/dialog/plugin_dialog.cj} +74 -17
- package/src/plugins/fs/plugin_fs.cj +194 -0
- package/src/plugins/http/plugin_http.cj +459 -0
- package/src/plugins/log/plugin_log.cj +105 -0
- package/src/{plugin_menu.cj → plugins/menu/plugin_menu.cj} +4 -2
- package/src/plugins/mqtt/mqtt_codec.cj +661 -0
- package/src/plugins/mqtt/plugin_mqtt.cj +1446 -0
- package/src/plugins/notification/plugin_notification.cj +146 -0
- package/src/plugins/plugins.cj +5 -0
- package/src/plugins/redis/plugin_redis.cj +2409 -0
- package/src/plugins/redis/redis_codec.cj +556 -0
- package/src/{plugin_serial.cj → plugins/serial/plugin_serial.cj} +442 -489
- package/src/{serial_bridge.cj → plugins/serial/serial_bridge.cj} +185 -184
- package/src/{plugin_shell.cj → plugins/shell/plugin_shell.cj} +230 -228
- package/src/plugins/sqlite/plugin_sqlite.cj +697 -0
- package/src/plugins/sqlite/sqlite_ffi.cj +206 -0
- package/src/plugins/store/plugin_store.cj +287 -0
- package/src/plugins/ws/plugin_ws.cj +470 -0
- package/src/process_hidden.cj +174 -0
- package/src/scope.cj +391 -0
- package/src/shell_event.cj +69 -68
- package/src/tests/capability_test.cj +53 -0
- package/src/tests/fake_host.cj +85 -20
- package/src/tests/ipc_hub_test.cj +13 -0
- package/src/tests/jsutil_test.cj +62 -0
- package/src/tests/logging_test.cj +62 -0
- package/src/tests/mqtt_codec_test.cj +405 -0
- package/src/tests/plugin_clipboard_test.cj +222 -0
- package/src/tests/plugin_dialog_test.cj +98 -4
- package/src/tests/plugin_http_test.cj +124 -0
- package/src/tests/plugin_menu_test.cj +1 -0
- package/src/tests/plugin_notification_test.cj +205 -0
- package/src/tests/plugin_serial_test.cj +269 -267
- package/src/tests/plugin_shell_test.cj +189 -188
- package/src/tests/plugin_sqlite_test.cj +230 -0
- package/src/tests/plugin_store_test.cj +260 -0
- package/src/tests/plugin_test.cj +542 -524
- package/src/tests/plugin_ws_test.cj +40 -0
- package/src/tests/process_hidden_test.cj +129 -0
- package/src/tests/redis_codec_test.cj +396 -0
- package/src/tests/scope_test.cj +350 -0
- package/src/tests/shell_event_test.cj +0 -1
- package/src/tests/system_host_test.cj +81 -79
- package/src/tests/urlutil_test.cj +53 -0
- package/src/tests/version_test.cj +75 -65
- package/src/tray.cj +90 -0
- package/src/urlutil.cj +159 -0
- package/src/version.cj +23 -2
- package/src/window.cj +51 -42
- package/native/bridge_core.c +0 -797
- package/native/bridge_linux.c +0 -1268
- package/native/bridge_win.c +0 -1633
- package/src/plugin_fs.cj +0 -118
- /package/native/{bridge_js.h → core/bridge_js.h} +0 -0
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
package cjTauri.tests
|
|
2
|
+
|
|
3
|
+
import cjTauri.*
|
|
4
|
+
import cjTauri.plugins.store.*
|
|
5
|
+
import std.collection.*
|
|
6
|
+
import std.fs.*
|
|
7
|
+
import std.sync.*
|
|
8
|
+
import stdx.encoding.json.*
|
|
9
|
+
import std.unittest.*
|
|
10
|
+
import std.unittest.testmacro.*
|
|
11
|
+
|
|
12
|
+
/*
|
|
13
|
+
* store 插件单元测试:StoreBackend 的读写删 / keys / 原子写残留 / 损坏文件回落 / 并发写
|
|
14
|
+
* + 命令面参数校验 + 命名权限集的闸门。
|
|
15
|
+
*
|
|
16
|
+
* 纯仓颉插件(零 C 桥),所以这里能测到「真实落盘」——比 serial 的单测多覆盖一层。
|
|
17
|
+
* 临时文件全部放 /tmp(按固定名复用),不在仓库里留测试产物。
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const STORE_T_BASIC: String = "/tmp/store-unit-test-basic.json"
|
|
21
|
+
const STORE_T_CMD: String = "/tmp/store-unit-test-cmd.json"
|
|
22
|
+
const STORE_T_BAD: String = "/tmp/store-unit-test-bad.json"
|
|
23
|
+
const STORE_T_CONC: String = "/tmp/store-unit-test-conc.json"
|
|
24
|
+
// keys/clear 用例必须独享文件:basic 文件里留着前面用例写的键,共享会让 size 断言失真
|
|
25
|
+
const STORE_T_KEYS: String = "/tmp/store-unit-test-keys.json"
|
|
26
|
+
|
|
27
|
+
/** 取一条 store 命令处理器(插件带自定义路径,测试互不串文件) */
|
|
28
|
+
func storeCmd(short: String): CommandHandler {
|
|
29
|
+
return StorePlugin(STORE_T_CMD).commands().get(short).getOrThrow()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 直接调命令,把 CommandException 的原文取出来(没抛就返回空串)*/
|
|
33
|
+
func storeErrOf(short: String, args: JsonObject): String {
|
|
34
|
+
try {
|
|
35
|
+
storeCmd(short).handle("store:${short}", args, IpcContext("main", IpcHub()))
|
|
36
|
+
return ""
|
|
37
|
+
} catch (e: CommandException) {
|
|
38
|
+
return e.message
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
func storeArgs1(key: String): JsonObject {
|
|
43
|
+
var o = JsonObject()
|
|
44
|
+
o.put("key", JsonString(key))
|
|
45
|
+
return o
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
@Test
|
|
49
|
+
func testStorePluginSurface(): Unit {
|
|
50
|
+
let p = StorePlugin()
|
|
51
|
+
@Expect(p.name(), "store")
|
|
52
|
+
let names = pluginCommandNames(p)
|
|
53
|
+
@Expect(names.size, 5)
|
|
54
|
+
var found = 0
|
|
55
|
+
for (n in names) {
|
|
56
|
+
if (n == "store:get" || n == "store:set" || n == "store:delete" || n == "store:keys" || n == "store:clear") {
|
|
57
|
+
found++
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
@Expect(found, 5)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
@Test
|
|
64
|
+
func testStorePluginShimTargetsItsOwnNamespace(): Unit {
|
|
65
|
+
let js = StorePlugin().jsShim()
|
|
66
|
+
@Expect(js.contains("t.store"), true)
|
|
67
|
+
@Expect(js.contains("'store:get'"), true)
|
|
68
|
+
@Expect(js.contains("'store:set'"), true)
|
|
69
|
+
@Expect(js.contains("'store:delete'"), true)
|
|
70
|
+
@Expect(js.contains("'store:clear'"), true)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
@Test
|
|
74
|
+
func testStorePermissionSetsGateWrites(): Unit {
|
|
75
|
+
// 命名集要经 pluginPermissionSets 看(框架注册的是补全名,permissions() 只给短名)
|
|
76
|
+
let sets = pluginPermissionSets(StorePlugin())
|
|
77
|
+
@Expect(sets.size, 2)
|
|
78
|
+
let ro = sets.get("store:readonly").getOrThrow()
|
|
79
|
+
@Expect(ro.size, 2)
|
|
80
|
+
// readonly 不含写:只想读的应用引用它,写能力不会被顺带打开
|
|
81
|
+
var hasWrite = false
|
|
82
|
+
for (n in ro) {
|
|
83
|
+
if (n == "store:set" || n == "store:delete" || n == "store:clear") {
|
|
84
|
+
hasWrite = true
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
@Expect(hasWrite, false)
|
|
88
|
+
@Expect(sets.get("store:default").getOrThrow().size, 5)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@Test
|
|
92
|
+
func testStoreSetGetRoundtripPersistsToDisk(): Unit {
|
|
93
|
+
let path = STORE_T_BASIC
|
|
94
|
+
let s1 = StoreBackend(path)
|
|
95
|
+
let r1 = s1.set("theme", JsonString("dark"))
|
|
96
|
+
@Expect(r1, true)
|
|
97
|
+
let r2 = s1.set("count", JsonInt(3))
|
|
98
|
+
@Expect(r2, true)
|
|
99
|
+
// 新开一个 backend(=模拟应用重启):数据必须从盘上回来
|
|
100
|
+
let s2 = StoreBackend(path)
|
|
101
|
+
// get 的返回就是 JsonValue(缺失时是 JSON null 或给定的 default),直接用
|
|
102
|
+
@Expect(s2.get("theme", false, JsonBool(false)).asString().getValue(), "dark")
|
|
103
|
+
@Expect(s2.get("count", false, JsonBool(false)).asInt().getValue(), 3)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@Test
|
|
107
|
+
func testStoreGetMissingDefaultAndNull(): Unit {
|
|
108
|
+
let s = StoreBackend(STORE_T_BASIC)
|
|
109
|
+
// 没给 default:回 JSON null(不是抛错)。JsonKind 这个 enum 连 == 都没定义
|
|
110
|
+
// (invalid binary operator,实测 2026-10-09),比较一律用 match(music/serial 同款模式)
|
|
111
|
+
let missKind = s.get("no-such-key", false, JsonBool(false)).kind()
|
|
112
|
+
var missOk = false
|
|
113
|
+
match (missKind) {
|
|
114
|
+
case JsonKind.JsNull => missOk = true
|
|
115
|
+
case _ => ()
|
|
116
|
+
}
|
|
117
|
+
@Expect(missOk, true)
|
|
118
|
+
// 给了 default:原样返回
|
|
119
|
+
@Expect(s.get("no-such-key", true, JsonString("fallback")).asString().getValue(), "fallback")
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@Test
|
|
123
|
+
func testStoreDeleteReportsExisted(): Unit {
|
|
124
|
+
let s = StoreBackend(STORE_T_BASIC)
|
|
125
|
+
let r = s.set("gone-soon", JsonBool(true))
|
|
126
|
+
@Expect(r, true)
|
|
127
|
+
let (existed1, saved1) = s.delete("gone-soon")
|
|
128
|
+
@Expect(existed1, true)
|
|
129
|
+
@Expect(saved1, true)
|
|
130
|
+
// 再删一次:本来就没有,existed=false(页面据此区分「删了」与「没有过」)
|
|
131
|
+
let (existed2, saved2) = s.delete("gone-soon")
|
|
132
|
+
@Expect(existed2, false)
|
|
133
|
+
@Expect(saved2, true)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@Test
|
|
137
|
+
func testStoreKeysSortedAndClearCounts(): Unit {
|
|
138
|
+
let s = StoreBackend(STORE_T_KEYS)
|
|
139
|
+
let r1 = s.set("zebra", JsonInt(1))
|
|
140
|
+
@Expect(r1, true)
|
|
141
|
+
let r2 = s.set("alpha", JsonInt(2))
|
|
142
|
+
@Expect(r2, true)
|
|
143
|
+
let r3 = s.set("mid", JsonInt(3))
|
|
144
|
+
@Expect(r3, true)
|
|
145
|
+
let ks = s.keys()
|
|
146
|
+
@Expect(ks.size, 3)
|
|
147
|
+
@Expect(ks[0], "alpha")
|
|
148
|
+
@Expect(ks[1], "mid")
|
|
149
|
+
@Expect(ks[2], "zebra")
|
|
150
|
+
let (cleared, saved) = s.clear()
|
|
151
|
+
@Expect(cleared, 3)
|
|
152
|
+
@Expect(saved, true)
|
|
153
|
+
@Expect(s.keys().size, 0)
|
|
154
|
+
// 清空后重启:仍是空的
|
|
155
|
+
@Expect(StoreBackend(STORE_T_KEYS).keys().size, 0)
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
@Test
|
|
159
|
+
func testStoreAtomicWriteLeavesNoTmpBehind(): Unit {
|
|
160
|
+
let path = STORE_T_BASIC
|
|
161
|
+
let s = StoreBackend(path)
|
|
162
|
+
let r = s.set("atomic", JsonBool(true))
|
|
163
|
+
@Expect(r, true)
|
|
164
|
+
// 原子写的中间产物必须已经被 rename 走,不能残留
|
|
165
|
+
@Expect(exists(path + ".tmp"), false)
|
|
166
|
+
// 文件本体是合法 JSON 对象(页面/人都能直接读)
|
|
167
|
+
let parsed = JsonValue.fromStr(String.fromUtf8(File.readFrom(path)))
|
|
168
|
+
let roundKind = parsed.kind()
|
|
169
|
+
var roundOk = false
|
|
170
|
+
match (roundKind) {
|
|
171
|
+
case JsonKind.JsObject => roundOk = true
|
|
172
|
+
case _ => ()
|
|
173
|
+
}
|
|
174
|
+
@Expect(roundOk, true)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@Test
|
|
178
|
+
func testStoreCorruptedFileRecovers(): Unit {
|
|
179
|
+
let path = STORE_T_BAD
|
|
180
|
+
// 造一个坏文件(半截 JSON,模拟进程中途被杀的旧版落盘)
|
|
181
|
+
File.writeTo(path, "{\"theme\": \"da".toArray())
|
|
182
|
+
let s = StoreBackend(path)
|
|
183
|
+
// 读坏不是致命错误:从空开始,键为空
|
|
184
|
+
@Expect(s.keys().size, 0)
|
|
185
|
+
// 之后照常可用,且下一次写会覆盖掉坏文件
|
|
186
|
+
let r = s.set("ok", JsonBool(true))
|
|
187
|
+
@Expect(r, true)
|
|
188
|
+
let parsed = JsonValue.fromStr(String.fromUtf8(File.readFrom(path)))
|
|
189
|
+
let badKind = parsed.kind()
|
|
190
|
+
var badOk = false
|
|
191
|
+
match (badKind) {
|
|
192
|
+
case JsonKind.JsObject => badOk = true
|
|
193
|
+
case _ => ()
|
|
194
|
+
}
|
|
195
|
+
@Expect(badOk, true)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
@Test
|
|
199
|
+
func testStoreConcurrentWritersKeepFileValid(): Unit {
|
|
200
|
+
let path = STORE_T_CONC
|
|
201
|
+
let s = StoreBackend(path)
|
|
202
|
+
// 8 个 worker 各写 25 次,全部打同一个键:内存不丢更新、盘上始终是合法 JSON。
|
|
203
|
+
// spawn 写法照框架自己的命令分发(ipc_hub.cj 的 `spawn { => ... }`);
|
|
204
|
+
// SyncCounter 是倒数计数器:init(8) 起始为 8,每个 worker 结束 dec(),waitUntilZero 等齐。
|
|
205
|
+
let counter = SyncCounter(8)
|
|
206
|
+
var i = 0
|
|
207
|
+
while (i < 8) {
|
|
208
|
+
let tag = "w${i}"
|
|
209
|
+
let n = i
|
|
210
|
+
let _ = spawn { =>
|
|
211
|
+
var j = 0
|
|
212
|
+
while (j < 25) {
|
|
213
|
+
let r = s.set(tag, JsonInt(n * 100 + j))
|
|
214
|
+
if (!r) {
|
|
215
|
+
eprintln("[store-test] ${tag} 第 ${j} 轮写盘失败")
|
|
216
|
+
}
|
|
217
|
+
j++
|
|
218
|
+
}
|
|
219
|
+
counter.dec()
|
|
220
|
+
}
|
|
221
|
+
i++
|
|
222
|
+
}
|
|
223
|
+
counter.waitUntilZero()
|
|
224
|
+
// 8 个键都在;各自终值是 0..24 里的某一个(谁最后落盘就是谁的)
|
|
225
|
+
let ks = s.keys()
|
|
226
|
+
@Expect(ks.size, 8)
|
|
227
|
+
let parsed = JsonValue.fromStr(String.fromUtf8(File.readFrom(path)))
|
|
228
|
+
let concKind = parsed.kind()
|
|
229
|
+
var concOk = false
|
|
230
|
+
match (concKind) {
|
|
231
|
+
case JsonKind.JsObject => concOk = true
|
|
232
|
+
case _ => ()
|
|
233
|
+
}
|
|
234
|
+
@Expect(concOk, true)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
@Test
|
|
238
|
+
func testStoreCommandParamValidation(): Unit {
|
|
239
|
+
// 缺 key / 空 key / key 类型不对
|
|
240
|
+
let noKey = JsonObject()
|
|
241
|
+
@Expect(storeErrOf("get", noKey), "store: 缺少参数 \"key\"")
|
|
242
|
+
let emptyKey = storeArgs1("")
|
|
243
|
+
@Expect(storeErrOf("get", emptyKey), "store: key 不能为空字符串")
|
|
244
|
+
let badKey = JsonObject()
|
|
245
|
+
badKey.put("key", JsonInt(1))
|
|
246
|
+
@Expect(storeErrOf("get", badKey), "store: 参数 \"key\" 必须是字符串")
|
|
247
|
+
// set 缺 value(想存 null 要显式传)
|
|
248
|
+
let argsV = storeArgs1("k")
|
|
249
|
+
@Expect(storeErrOf("set", argsV), "store: 缺少参数 \"value\"(想存 null 就显式传 value: null)")
|
|
250
|
+
// delete 不存在的键也成功(existed=false),不是错误
|
|
251
|
+
let out = storeCmd("delete").handle("store:delete", storeArgs1("never-was"),
|
|
252
|
+
IpcContext("main", IpcHub()))
|
|
253
|
+
let existedKind = out.asObject().get("existed").getOrThrow().kind()
|
|
254
|
+
var existedOk = false
|
|
255
|
+
match (existedKind) {
|
|
256
|
+
case JsonKind.JsBool => existedOk = true
|
|
257
|
+
case _ => ()
|
|
258
|
+
}
|
|
259
|
+
@Expect(existedOk, true)
|
|
260
|
+
}
|