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,62 +1,62 @@
|
|
|
1
|
-
package cjTauri.tests
|
|
2
|
-
|
|
3
|
-
import cjTauri.*
|
|
4
|
-
import std.unittest.*
|
|
5
|
-
import std.unittest.testmacro.*
|
|
6
|
-
|
|
7
|
-
/*
|
|
8
|
-
* 日志落盘的策略层用例(RFC-003 第 5 期)。
|
|
9
|
-
*
|
|
10
|
-
* 只测**纯函数**(默认目录推导、环境变量回退、路径拼接):真的开 fd 那一段归桩自检
|
|
11
|
-
* (`native/tests/test_bridge_core.c` 的 `log file` 用例)与实机 E2E——单测里调桥的
|
|
12
|
-
* `foreign func` 会踩「子包调不了 foreign func」那条(AGENTS §4,链接期报未定义符号)。
|
|
13
|
-
*
|
|
14
|
-
* 环境变量类的用例一律用**肯定不存在**的变量名:跑测试的机器上 `CJ_TAURI_LOG` 之类
|
|
15
|
-
* 可能真的设着,拿它当「未设置」来断言就是一条会随机器飘的红线。
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/** 保证不存在的变量名(前缀统一,便于将来 grep) */
|
|
19
|
-
let ABSENT_LOG_ENV: String = "CJ_TAURI_LOG_TEST_ABSENT"
|
|
20
|
-
|
|
21
|
-
@Test
|
|
22
|
-
func testLogEnvFallsBackWhenVariableMissing(): Unit {
|
|
23
|
-
@Expect(logEnvOr(ABSENT_LOG_ENV, "dflt"), "dflt")
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
@Test
|
|
27
|
-
func testLogBudgetFallsBackWhenVariableMissing(): Unit {
|
|
28
|
-
@Expect(logBudgetFromEnv(ABSENT_LOG_ENV, 7), 7)
|
|
29
|
-
// 0 与负数在调用方语义里是「不轮转」,不是「用 0 当默认」——回退值原样带回
|
|
30
|
-
@Expect(logBudgetFromEnv(ABSENT_LOG_ENV, 0), 0)
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
@Test
|
|
34
|
-
func testDefaultLogPathUsesAppNameAndPlatformDir(): Unit {
|
|
35
|
-
let p = defaultLogPath("music")
|
|
36
|
-
// 分隔符按平台给,所以不整体比对路径;钉的是「文件名 = 应用名」、「落在 cj-tauri 目录下」
|
|
37
|
-
@Expect(p.endsWith("music.log"), true)
|
|
38
|
-
@Expect(p.contains("cj-tauri"), true)
|
|
39
|
-
// 应用没给名字时走兜底:不能拼出没有主名的 ".log"
|
|
40
|
-
@Expect(defaultLogPath("").endsWith("app.log"), true)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
@Test
|
|
44
|
-
func testDefaultLogDirIsNonEmptyAndConventional(): Unit {
|
|
45
|
-
let d = defaultLogDir()
|
|
46
|
-
@Expect(d.size > 0, true)
|
|
47
|
-
@Expect(d.contains("cj-tauri"), true)
|
|
48
|
-
// 默认预算是个能落地的值:>0 才会轮转,keep>0 才有档案可留
|
|
49
|
-
@Expect(CJ_LOG_DEFAULT_MAX_KB > 0, true)
|
|
50
|
-
@Expect(CJ_LOG_DEFAULT_KEEP > 0, true)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
@Test
|
|
54
|
-
func testLogPathOrDefaultPrefersEnvOverPlatformDir(): Unit {
|
|
55
|
-
// 报障路径优先:环境变量给了路径就用它,平台惯例目录被忽略(人家不必知道目录在哪)
|
|
56
|
-
@Expect(logPathOrDefault("/tmp/from-env.log", "music"), "/tmp/from-env.log")
|
|
57
|
-
// 空串=未设置,才落平台惯例目录,文件名跟应用名走(与 logEnvOr 同口径)
|
|
58
|
-
let d = logPathOrDefault("", "music")
|
|
59
|
-
@Expect(d.endsWith("music.log"), true)
|
|
60
|
-
@Expect(d.contains("cj-tauri"), true)
|
|
61
|
-
@Expect(d == defaultLogPath("music"), true)
|
|
62
|
-
}
|
|
1
|
+
package cjTauri.tests
|
|
2
|
+
|
|
3
|
+
import cjTauri.*
|
|
4
|
+
import std.unittest.*
|
|
5
|
+
import std.unittest.testmacro.*
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
* 日志落盘的策略层用例(RFC-003 第 5 期)。
|
|
9
|
+
*
|
|
10
|
+
* 只测**纯函数**(默认目录推导、环境变量回退、路径拼接):真的开 fd 那一段归桩自检
|
|
11
|
+
* (`native/tests/test_bridge_core.c` 的 `log file` 用例)与实机 E2E——单测里调桥的
|
|
12
|
+
* `foreign func` 会踩「子包调不了 foreign func」那条(AGENTS §4,链接期报未定义符号)。
|
|
13
|
+
*
|
|
14
|
+
* 环境变量类的用例一律用**肯定不存在**的变量名:跑测试的机器上 `CJ_TAURI_LOG` 之类
|
|
15
|
+
* 可能真的设着,拿它当「未设置」来断言就是一条会随机器飘的红线。
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/** 保证不存在的变量名(前缀统一,便于将来 grep) */
|
|
19
|
+
let ABSENT_LOG_ENV: String = "CJ_TAURI_LOG_TEST_ABSENT"
|
|
20
|
+
|
|
21
|
+
@Test
|
|
22
|
+
func testLogEnvFallsBackWhenVariableMissing(): Unit {
|
|
23
|
+
@Expect(logEnvOr(ABSENT_LOG_ENV, "dflt"), "dflt")
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
@Test
|
|
27
|
+
func testLogBudgetFallsBackWhenVariableMissing(): Unit {
|
|
28
|
+
@Expect(logBudgetFromEnv(ABSENT_LOG_ENV, 7), 7)
|
|
29
|
+
// 0 与负数在调用方语义里是「不轮转」,不是「用 0 当默认」——回退值原样带回
|
|
30
|
+
@Expect(logBudgetFromEnv(ABSENT_LOG_ENV, 0), 0)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
@Test
|
|
34
|
+
func testDefaultLogPathUsesAppNameAndPlatformDir(): Unit {
|
|
35
|
+
let p = defaultLogPath("music")
|
|
36
|
+
// 分隔符按平台给,所以不整体比对路径;钉的是「文件名 = 应用名」、「落在 cj-tauri 目录下」
|
|
37
|
+
@Expect(p.endsWith("music.log"), true)
|
|
38
|
+
@Expect(p.contains("cj-tauri"), true)
|
|
39
|
+
// 应用没给名字时走兜底:不能拼出没有主名的 ".log"
|
|
40
|
+
@Expect(defaultLogPath("").endsWith("app.log"), true)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@Test
|
|
44
|
+
func testDefaultLogDirIsNonEmptyAndConventional(): Unit {
|
|
45
|
+
let d = defaultLogDir()
|
|
46
|
+
@Expect(d.size > 0, true)
|
|
47
|
+
@Expect(d.contains("cj-tauri"), true)
|
|
48
|
+
// 默认预算是个能落地的值:>0 才会轮转,keep>0 才有档案可留
|
|
49
|
+
@Expect(CJ_LOG_DEFAULT_MAX_KB > 0, true)
|
|
50
|
+
@Expect(CJ_LOG_DEFAULT_KEEP > 0, true)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Test
|
|
54
|
+
func testLogPathOrDefaultPrefersEnvOverPlatformDir(): Unit {
|
|
55
|
+
// 报障路径优先:环境变量给了路径就用它,平台惯例目录被忽略(人家不必知道目录在哪)
|
|
56
|
+
@Expect(logPathOrDefault("/tmp/from-env.log", "music"), "/tmp/from-env.log")
|
|
57
|
+
// 空串=未设置,才落平台惯例目录,文件名跟应用名走(与 logEnvOr 同口径)
|
|
58
|
+
let d = logPathOrDefault("", "music")
|
|
59
|
+
@Expect(d.endsWith("music.log"), true)
|
|
60
|
+
@Expect(d.contains("cj-tauri"), true)
|
|
61
|
+
@Expect(d == defaultLogPath("music"), true)
|
|
62
|
+
}
|
|
@@ -1,251 +1,251 @@
|
|
|
1
|
-
package cjTauri.tests
|
|
2
|
-
|
|
3
|
-
import cjTauri.*
|
|
4
|
-
import cjTauri.platform.*
|
|
5
|
-
import std.collection.*
|
|
6
|
-
import std.unittest.*
|
|
7
|
-
import std.unittest.testmacro.*
|
|
8
|
-
|
|
9
|
-
/*
|
|
10
|
-
* 平台契约骨架测试(RFC-002 批次 0)
|
|
11
|
-
* 不创建窗口、不依赖宿主:TargetInfo / 特性探测 / PageSource / 宿主注册表,纯逻辑。
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
@Test
|
|
15
|
-
func targetInfoDescribe(): Unit {
|
|
16
|
-
let t = TargetInfo("Linux", "x86_64", "webkitgtk", "desktop")
|
|
17
|
-
@Expect(t.os, "Linux")
|
|
18
|
-
@Expect(t.describe(), "Linux/x86_64 host=webkitgtk display=desktop")
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
@Test
|
|
22
|
-
func appContextFeatureProbe(): Unit {
|
|
23
|
-
let fs = ArrayList<PlatformFeature>()
|
|
24
|
-
fs.add(PlatformFeature.Tray)
|
|
25
|
-
fs.add(PlatformFeature.MultiWindow)
|
|
26
|
-
let ctx = AppContext(
|
|
27
|
-
AppPaths("/tmp/d", "/tmp/c", "/tmp/t"),
|
|
28
|
-
TargetInfo("Linux", "x86_64", "webkitgtk", "desktop"),
|
|
29
|
-
false, fs)
|
|
30
|
-
@Expect(ctx.supports(PlatformFeature.Tray), true)
|
|
31
|
-
@Expect(ctx.supports(PlatformFeature.FilePicker), false)
|
|
32
|
-
@Expect(ctx.dev, false)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
@Test
|
|
36
|
-
func pageSourceMatch(): Unit {
|
|
37
|
-
let src = PageSource.Asset("ui/index.html")
|
|
38
|
-
match (src) {
|
|
39
|
-
case Asset(name) => @Expect(name, "ui/index.html")
|
|
40
|
-
case _ => @Expect(false, true) // 不应到达:Asset 之外的分支
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
@Test
|
|
45
|
-
func desktopDiscoverDefaults(): Unit {
|
|
46
|
-
let ctx = Platform.desktop("com.example.test", "x86_64")
|
|
47
|
-
@Expect(ctx.paths.dataDir.endsWith("com.example.test"), true)
|
|
48
|
-
@Expect(ctx.supports(PlatformFeature.Tray), true)
|
|
49
|
-
@Expect(ctx.target.display, "desktop")
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
@Test
|
|
53
|
-
func mobileDiscoverSandbox(): Unit {
|
|
54
|
-
let ctx = Platform.mobile("/data/app/el2/base/files", "aarch64")
|
|
55
|
-
@Expect(ctx.paths.dataDir, "/data/app/el2/base/files")
|
|
56
|
-
@Expect(ctx.dev, false)
|
|
57
|
-
@Expect(ctx.supports(PlatformFeature.SoftKeyboardInset), true)
|
|
58
|
-
@Expect(ctx.supports(PlatformFeature.Tray), false)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** 优先级最高者胜;create 返回假宿主(pickHost 会真实调用它) */
|
|
62
|
-
class HighPrioProvider <: HostProvider {
|
|
63
|
-
public func priority(target: TargetInfo): Int64 {
|
|
64
|
-
return 10
|
|
65
|
-
}
|
|
66
|
-
public func create(label: String, target: TargetInfo): WebViewHost {
|
|
67
|
-
return FakeHost(label)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
class LowPrioProvider <: HostProvider {
|
|
72
|
-
public func priority(target: TargetInfo): Int64 {
|
|
73
|
-
return 1
|
|
74
|
-
}
|
|
75
|
-
public func create(label: String, target: TargetInfo): WebViewHost {
|
|
76
|
-
return FakeHost(label)
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
class NullProvider <: HostProvider {
|
|
81
|
-
public func priority(target: TargetInfo): Int64 {
|
|
82
|
-
return 0
|
|
83
|
-
}
|
|
84
|
-
public func create(label: String, target: TargetInfo): WebViewHost {
|
|
85
|
-
return FakeHost(label)
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
@Test
|
|
90
|
-
func hostRegistryPicksHighestPriority(): Unit {
|
|
91
|
-
let before = hostProviderCount()
|
|
92
|
-
registerHost(NullProvider())
|
|
93
|
-
registerHost(LowPrioProvider())
|
|
94
|
-
registerHost(HighPrioProvider())
|
|
95
|
-
@Expect(hostProviderCount(), before + 3)
|
|
96
|
-
let target = TargetInfo("Linux", "x86_64", "webkitgtk", "desktop")
|
|
97
|
-
let picked = pickHost("main", target)
|
|
98
|
-
@Expect(picked.isSome(), true)
|
|
99
|
-
// 胜出者必须是优先级 10 的实现(create 真实执行,label 透传)
|
|
100
|
-
@Expect(picked.getOrThrow().label(), "main")
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ---- 批次 1:PageSource → PageLoad 决策 ----
|
|
104
|
-
|
|
105
|
-
@Test
|
|
106
|
-
func resolvePageInlineAndUrl(): Unit {
|
|
107
|
-
match (resolvePage(PageSource.Inline("<p>hi</p>"), None)) {
|
|
108
|
-
case StartHtml(html) => @Expect(html, "<p>hi</p>")
|
|
109
|
-
case _ => @Expect(false, true)
|
|
110
|
-
}
|
|
111
|
-
match (resolvePage(PageSource.Url("https://example.com"), None)) {
|
|
112
|
-
case StartUrl(url) => @Expect(url, "https://example.com")
|
|
113
|
-
case _ => @Expect(false, true)
|
|
114
|
-
}
|
|
115
|
-
// DevServer 与 Url 同路:都走 startUrl
|
|
116
|
-
match (resolvePage(PageSource.DevServer("http://127.0.0.1:5173"), None)) {
|
|
117
|
-
case StartUrl(url) => @Expect(url, "http://127.0.0.1:5173")
|
|
118
|
-
case _ => @Expect(false, true)
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/** 返回固定内容的解析器 */
|
|
123
|
-
class StubResolver <: AssetResolver {
|
|
124
|
-
let table: HashMap<String, String>
|
|
125
|
-
init(table: HashMap<String, String>) {
|
|
126
|
-
this.table = table
|
|
127
|
-
}
|
|
128
|
-
public func readText(name: String): Option<String> {
|
|
129
|
-
return table.get(name)
|
|
130
|
-
}
|
|
131
|
-
public func exists(name: String): Bool {
|
|
132
|
-
return table.contains(name)
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
@Test
|
|
137
|
-
func resolvePageAssetViaResolver(): Unit {
|
|
138
|
-
let t = HashMap<String, String>()
|
|
139
|
-
t.add("ui/index.html", "<html>ok</html>")
|
|
140
|
-
let src = resolvePage(PageSource.Asset("ui/index.html"), Some(StubResolver(t)))
|
|
141
|
-
match (src) {
|
|
142
|
-
case StartHtml(html) => @Expect(html, "<html>ok</html>")
|
|
143
|
-
case _ => @Expect(false, true)
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
@Test
|
|
148
|
-
func resolvePageAssetMissingThrowsReadableError(): Unit {
|
|
149
|
-
let t = HashMap<String, String>()
|
|
150
|
-
var msg = ""
|
|
151
|
-
try {
|
|
152
|
-
let _ = resolvePage(PageSource.Asset("no/such.html"), Some(StubResolver(t)))
|
|
153
|
-
} catch (e: Exception) {
|
|
154
|
-
msg = e.message
|
|
155
|
-
}
|
|
156
|
-
@Expect(msg.contains("no/such.html"), true)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
@Test
|
|
160
|
-
func resolvePageAssetNoResolverReadsFromCwd(): Unit {
|
|
161
|
-
// 未注入解析器:按 cwd 相对路径读盘(与模板 textOrFile 桌面行为等价)。
|
|
162
|
-
// 测试 cwd = 仓库根,读一个确定存在的文件验证「读到了」而非白屏。
|
|
163
|
-
let src = resolvePage(PageSource.Asset("cjpm.toml"), None)
|
|
164
|
-
match (src) {
|
|
165
|
-
case StartHtml(html) => @Expect(html.size > 0, true)
|
|
166
|
-
case _ => @Expect(false, true)
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
// ---- 批次 1 评审修正:devMode 自动改写(RFC §4.3)----
|
|
171
|
-
|
|
172
|
-
@Test
|
|
173
|
-
func rewriteForDevOnlyTouchesAsset(): Unit {
|
|
174
|
-
// Asset + devUrl → DevServer
|
|
175
|
-
match (rewriteForDev(PageSource.Asset("ui/index.html"), Some("http://127.0.0.1:5173"))) {
|
|
176
|
-
case DevServer(url) => @Expect(url, "http://127.0.0.1:5173")
|
|
177
|
-
case _ => @Expect(false, true)
|
|
178
|
-
}
|
|
179
|
-
// 显式 Inline 是业务意图,不改写
|
|
180
|
-
match (rewriteForDev(PageSource.Inline("<p/>"), Some("http://d"))) {
|
|
181
|
-
case Inline(html) => @Expect(html, "<p/>")
|
|
182
|
-
case _ => @Expect(false, true)
|
|
183
|
-
}
|
|
184
|
-
// 无 devUrl 原样返回
|
|
185
|
-
match (rewriteForDev(PageSource.Asset("ui/index.html"), None)) {
|
|
186
|
-
case Asset(name) => @Expect(name, "ui/index.html")
|
|
187
|
-
case _ => @Expect(false, true)
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
// ---- 批次 2:DirectoryAssetResolver ----
|
|
192
|
-
|
|
193
|
-
@Test
|
|
194
|
-
func directoryAssetResolverReadsRepo(): Unit {
|
|
195
|
-
let r = DirectoryAssetResolver(".")
|
|
196
|
-
let t = r.readText("cjpm.toml")
|
|
197
|
-
@Expect(t.isSome(), true)
|
|
198
|
-
@Expect(t.getOrThrow().size > 0, true)
|
|
199
|
-
@Expect(r.exists("cjpm.toml"), true)
|
|
200
|
-
@Expect(r.readText("no/such/file.txt").isSome(), false)
|
|
201
|
-
@Expect(r.exists("no/such/file.txt"), false)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// ---- 批次 1:guessTarget 与 resolveHost 接线 ----
|
|
205
|
-
|
|
206
|
-
@Test
|
|
207
|
-
func guessTargetDesktopFields(): Unit {
|
|
208
|
-
let t = Platform.guessTarget()
|
|
209
|
-
@Expect(t.display, "desktop")
|
|
210
|
-
@Expect(t.hostKind, expectedDesktopHostKind())
|
|
211
|
-
@Expect(t.os, expectedDesktopOs())
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// 桌面两平台的宿主形态与系统名本来就不同(Windows = webview2 / Windows,Linux = webkitgtk / Linux)。
|
|
215
|
-
// 期望值写死一套会让用例在另一个平台上恒红——本机 Windows 上这条一直是红的(写死成 Linux 那套)。
|
|
216
|
-
@When[os == "Windows"]
|
|
217
|
-
func expectedDesktopHostKind(): String {
|
|
218
|
-
return "webview2"
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
@When[os != "Windows"]
|
|
222
|
-
func expectedDesktopHostKind(): String {
|
|
223
|
-
return "webkitgtk"
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
@When[os == "Windows"]
|
|
227
|
-
func expectedDesktopOs(): String {
|
|
228
|
-
return "Windows"
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
@When[os != "Windows"]
|
|
232
|
-
func expectedDesktopOs(): String {
|
|
233
|
-
return "Linux"
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/** 注册表有匹配时,resolveHost 必须取注册表而不是内置宿主 */
|
|
237
|
-
class WinProvider <: HostProvider {
|
|
238
|
-
public func priority(target: TargetInfo): Int64 {
|
|
239
|
-
return 5
|
|
240
|
-
}
|
|
241
|
-
public func create(label: String, target: TargetInfo): WebViewHost {
|
|
242
|
-
return FakeHost(label)
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
@Test
|
|
247
|
-
func resolveHostPrefersRegistryOverBuiltin(): Unit {
|
|
248
|
-
registerHost(WinProvider())
|
|
249
|
-
let h = resolveHost("registry-win")
|
|
250
|
-
@Expect(h.label(), "registry-win")
|
|
251
|
-
}
|
|
1
|
+
package cjTauri.tests
|
|
2
|
+
|
|
3
|
+
import cjTauri.*
|
|
4
|
+
import cjTauri.platform.*
|
|
5
|
+
import std.collection.*
|
|
6
|
+
import std.unittest.*
|
|
7
|
+
import std.unittest.testmacro.*
|
|
8
|
+
|
|
9
|
+
/*
|
|
10
|
+
* 平台契约骨架测试(RFC-002 批次 0)
|
|
11
|
+
* 不创建窗口、不依赖宿主:TargetInfo / 特性探测 / PageSource / 宿主注册表,纯逻辑。
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
@Test
|
|
15
|
+
func targetInfoDescribe(): Unit {
|
|
16
|
+
let t = TargetInfo("Linux", "x86_64", "webkitgtk", "desktop")
|
|
17
|
+
@Expect(t.os, "Linux")
|
|
18
|
+
@Expect(t.describe(), "Linux/x86_64 host=webkitgtk display=desktop")
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
@Test
|
|
22
|
+
func appContextFeatureProbe(): Unit {
|
|
23
|
+
let fs = ArrayList<PlatformFeature>()
|
|
24
|
+
fs.add(PlatformFeature.Tray)
|
|
25
|
+
fs.add(PlatformFeature.MultiWindow)
|
|
26
|
+
let ctx = AppContext(
|
|
27
|
+
AppPaths("/tmp/d", "/tmp/c", "/tmp/t"),
|
|
28
|
+
TargetInfo("Linux", "x86_64", "webkitgtk", "desktop"),
|
|
29
|
+
false, fs)
|
|
30
|
+
@Expect(ctx.supports(PlatformFeature.Tray), true)
|
|
31
|
+
@Expect(ctx.supports(PlatformFeature.FilePicker), false)
|
|
32
|
+
@Expect(ctx.dev, false)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@Test
|
|
36
|
+
func pageSourceMatch(): Unit {
|
|
37
|
+
let src = PageSource.Asset("ui/index.html")
|
|
38
|
+
match (src) {
|
|
39
|
+
case Asset(name) => @Expect(name, "ui/index.html")
|
|
40
|
+
case _ => @Expect(false, true) // 不应到达:Asset 之外的分支
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@Test
|
|
45
|
+
func desktopDiscoverDefaults(): Unit {
|
|
46
|
+
let ctx = Platform.desktop("com.example.test", "x86_64")
|
|
47
|
+
@Expect(ctx.paths.dataDir.endsWith("com.example.test"), true)
|
|
48
|
+
@Expect(ctx.supports(PlatformFeature.Tray), true)
|
|
49
|
+
@Expect(ctx.target.display, "desktop")
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@Test
|
|
53
|
+
func mobileDiscoverSandbox(): Unit {
|
|
54
|
+
let ctx = Platform.mobile("/data/app/el2/base/files", "aarch64")
|
|
55
|
+
@Expect(ctx.paths.dataDir, "/data/app/el2/base/files")
|
|
56
|
+
@Expect(ctx.dev, false)
|
|
57
|
+
@Expect(ctx.supports(PlatformFeature.SoftKeyboardInset), true)
|
|
58
|
+
@Expect(ctx.supports(PlatformFeature.Tray), false)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** 优先级最高者胜;create 返回假宿主(pickHost 会真实调用它) */
|
|
62
|
+
class HighPrioProvider <: HostProvider {
|
|
63
|
+
public func priority(target: TargetInfo): Int64 {
|
|
64
|
+
return 10
|
|
65
|
+
}
|
|
66
|
+
public func create(label: String, target: TargetInfo): WebViewHost {
|
|
67
|
+
return FakeHost(label)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
class LowPrioProvider <: HostProvider {
|
|
72
|
+
public func priority(target: TargetInfo): Int64 {
|
|
73
|
+
return 1
|
|
74
|
+
}
|
|
75
|
+
public func create(label: String, target: TargetInfo): WebViewHost {
|
|
76
|
+
return FakeHost(label)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
class NullProvider <: HostProvider {
|
|
81
|
+
public func priority(target: TargetInfo): Int64 {
|
|
82
|
+
return 0
|
|
83
|
+
}
|
|
84
|
+
public func create(label: String, target: TargetInfo): WebViewHost {
|
|
85
|
+
return FakeHost(label)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
@Test
|
|
90
|
+
func hostRegistryPicksHighestPriority(): Unit {
|
|
91
|
+
let before = hostProviderCount()
|
|
92
|
+
registerHost(NullProvider())
|
|
93
|
+
registerHost(LowPrioProvider())
|
|
94
|
+
registerHost(HighPrioProvider())
|
|
95
|
+
@Expect(hostProviderCount(), before + 3)
|
|
96
|
+
let target = TargetInfo("Linux", "x86_64", "webkitgtk", "desktop")
|
|
97
|
+
let picked = pickHost("main", target)
|
|
98
|
+
@Expect(picked.isSome(), true)
|
|
99
|
+
// 胜出者必须是优先级 10 的实现(create 真实执行,label 透传)
|
|
100
|
+
@Expect(picked.getOrThrow().label(), "main")
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ---- 批次 1:PageSource → PageLoad 决策 ----
|
|
104
|
+
|
|
105
|
+
@Test
|
|
106
|
+
func resolvePageInlineAndUrl(): Unit {
|
|
107
|
+
match (resolvePage(PageSource.Inline("<p>hi</p>"), None)) {
|
|
108
|
+
case StartHtml(html) => @Expect(html, "<p>hi</p>")
|
|
109
|
+
case _ => @Expect(false, true)
|
|
110
|
+
}
|
|
111
|
+
match (resolvePage(PageSource.Url("https://example.com"), None)) {
|
|
112
|
+
case StartUrl(url) => @Expect(url, "https://example.com")
|
|
113
|
+
case _ => @Expect(false, true)
|
|
114
|
+
}
|
|
115
|
+
// DevServer 与 Url 同路:都走 startUrl
|
|
116
|
+
match (resolvePage(PageSource.DevServer("http://127.0.0.1:5173"), None)) {
|
|
117
|
+
case StartUrl(url) => @Expect(url, "http://127.0.0.1:5173")
|
|
118
|
+
case _ => @Expect(false, true)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** 返回固定内容的解析器 */
|
|
123
|
+
class StubResolver <: AssetResolver {
|
|
124
|
+
let table: HashMap<String, String>
|
|
125
|
+
init(table: HashMap<String, String>) {
|
|
126
|
+
this.table = table
|
|
127
|
+
}
|
|
128
|
+
public func readText(name: String): Option<String> {
|
|
129
|
+
return table.get(name)
|
|
130
|
+
}
|
|
131
|
+
public func exists(name: String): Bool {
|
|
132
|
+
return table.contains(name)
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
@Test
|
|
137
|
+
func resolvePageAssetViaResolver(): Unit {
|
|
138
|
+
let t = HashMap<String, String>()
|
|
139
|
+
t.add("ui/index.html", "<html>ok</html>")
|
|
140
|
+
let src = resolvePage(PageSource.Asset("ui/index.html"), Some(StubResolver(t)))
|
|
141
|
+
match (src) {
|
|
142
|
+
case StartHtml(html) => @Expect(html, "<html>ok</html>")
|
|
143
|
+
case _ => @Expect(false, true)
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
@Test
|
|
148
|
+
func resolvePageAssetMissingThrowsReadableError(): Unit {
|
|
149
|
+
let t = HashMap<String, String>()
|
|
150
|
+
var msg = ""
|
|
151
|
+
try {
|
|
152
|
+
let _ = resolvePage(PageSource.Asset("no/such.html"), Some(StubResolver(t)))
|
|
153
|
+
} catch (e: Exception) {
|
|
154
|
+
msg = e.message
|
|
155
|
+
}
|
|
156
|
+
@Expect(msg.contains("no/such.html"), true)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
@Test
|
|
160
|
+
func resolvePageAssetNoResolverReadsFromCwd(): Unit {
|
|
161
|
+
// 未注入解析器:按 cwd 相对路径读盘(与模板 textOrFile 桌面行为等价)。
|
|
162
|
+
// 测试 cwd = 仓库根,读一个确定存在的文件验证「读到了」而非白屏。
|
|
163
|
+
let src = resolvePage(PageSource.Asset("cjpm.toml"), None)
|
|
164
|
+
match (src) {
|
|
165
|
+
case StartHtml(html) => @Expect(html.size > 0, true)
|
|
166
|
+
case _ => @Expect(false, true)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ---- 批次 1 评审修正:devMode 自动改写(RFC §4.3)----
|
|
171
|
+
|
|
172
|
+
@Test
|
|
173
|
+
func rewriteForDevOnlyTouchesAsset(): Unit {
|
|
174
|
+
// Asset + devUrl → DevServer
|
|
175
|
+
match (rewriteForDev(PageSource.Asset("ui/index.html"), Some("http://127.0.0.1:5173"))) {
|
|
176
|
+
case DevServer(url) => @Expect(url, "http://127.0.0.1:5173")
|
|
177
|
+
case _ => @Expect(false, true)
|
|
178
|
+
}
|
|
179
|
+
// 显式 Inline 是业务意图,不改写
|
|
180
|
+
match (rewriteForDev(PageSource.Inline("<p/>"), Some("http://d"))) {
|
|
181
|
+
case Inline(html) => @Expect(html, "<p/>")
|
|
182
|
+
case _ => @Expect(false, true)
|
|
183
|
+
}
|
|
184
|
+
// 无 devUrl 原样返回
|
|
185
|
+
match (rewriteForDev(PageSource.Asset("ui/index.html"), None)) {
|
|
186
|
+
case Asset(name) => @Expect(name, "ui/index.html")
|
|
187
|
+
case _ => @Expect(false, true)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// ---- 批次 2:DirectoryAssetResolver ----
|
|
192
|
+
|
|
193
|
+
@Test
|
|
194
|
+
func directoryAssetResolverReadsRepo(): Unit {
|
|
195
|
+
let r = DirectoryAssetResolver(".")
|
|
196
|
+
let t = r.readText("cjpm.toml")
|
|
197
|
+
@Expect(t.isSome(), true)
|
|
198
|
+
@Expect(t.getOrThrow().size > 0, true)
|
|
199
|
+
@Expect(r.exists("cjpm.toml"), true)
|
|
200
|
+
@Expect(r.readText("no/such/file.txt").isSome(), false)
|
|
201
|
+
@Expect(r.exists("no/such/file.txt"), false)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ---- 批次 1:guessTarget 与 resolveHost 接线 ----
|
|
205
|
+
|
|
206
|
+
@Test
|
|
207
|
+
func guessTargetDesktopFields(): Unit {
|
|
208
|
+
let t = Platform.guessTarget()
|
|
209
|
+
@Expect(t.display, "desktop")
|
|
210
|
+
@Expect(t.hostKind, expectedDesktopHostKind())
|
|
211
|
+
@Expect(t.os, expectedDesktopOs())
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// 桌面两平台的宿主形态与系统名本来就不同(Windows = webview2 / Windows,Linux = webkitgtk / Linux)。
|
|
215
|
+
// 期望值写死一套会让用例在另一个平台上恒红——本机 Windows 上这条一直是红的(写死成 Linux 那套)。
|
|
216
|
+
@When[os == "Windows"]
|
|
217
|
+
func expectedDesktopHostKind(): String {
|
|
218
|
+
return "webview2"
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
@When[os != "Windows"]
|
|
222
|
+
func expectedDesktopHostKind(): String {
|
|
223
|
+
return "webkitgtk"
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
@When[os == "Windows"]
|
|
227
|
+
func expectedDesktopOs(): String {
|
|
228
|
+
return "Windows"
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
@When[os != "Windows"]
|
|
232
|
+
func expectedDesktopOs(): String {
|
|
233
|
+
return "Linux"
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** 注册表有匹配时,resolveHost 必须取注册表而不是内置宿主 */
|
|
237
|
+
class WinProvider <: HostProvider {
|
|
238
|
+
public func priority(target: TargetInfo): Int64 {
|
|
239
|
+
return 5
|
|
240
|
+
}
|
|
241
|
+
public func create(label: String, target: TargetInfo): WebViewHost {
|
|
242
|
+
return FakeHost(label)
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
@Test
|
|
247
|
+
func resolveHostPrefersRegistryOverBuiltin(): Unit {
|
|
248
|
+
registerHost(WinProvider())
|
|
249
|
+
let h = resolveHost("registry-win")
|
|
250
|
+
@Expect(h.label(), "registry-win")
|
|
251
|
+
}
|