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.
Files changed (162) hide show
  1. package/README.md +137 -77
  2. package/bin/cj-tauri.js +287 -287
  3. package/cjpm.toml +13 -2
  4. package/cli/cjpm.toml +22 -13
  5. package/cli/src/doctor.cj +308 -304
  6. package/cli/src/main.cj +129 -121
  7. package/cli/src/manifest.cj +632 -0
  8. package/cli/src/manifest_test.cj +87 -0
  9. package/cli/src/project.cj +770 -754
  10. package/cli/src/resolve.cj +348 -348
  11. package/cli/src/scaffold.cj +46 -38
  12. package/cli/src/sync.cj +526 -0
  13. package/cli/src/util.cj +9 -0
  14. package/cli/templates/app/README.md +30 -59
  15. package/cli/templates/app/{ui → assets/ui}/index.html +2 -0
  16. package/cli/templates/app/cj-tauri.toml +32 -0
  17. package/cli/templates/app/cjpm.toml +21 -4
  18. package/cli/templates/app/src/main.cj +29 -16
  19. package/cli/templates/app-react/README.md +34 -45
  20. package/cli/templates/app-react/{ui → assets/ui}/index.html +25 -25
  21. package/cli/templates/app-react/{ui → assets/ui}/src/App.css +67 -67
  22. package/cli/templates/app-react/{ui → assets/ui}/src/App.jsx +124 -124
  23. package/cli/templates/app-react/cj-tauri.toml +33 -0
  24. package/cli/templates/app-react/cjpm.toml +21 -4
  25. package/cli/templates/app-react/src/main.cj +18 -29
  26. package/cli/templates/app-vue/README.md +34 -38
  27. package/cli/templates/app-vue/{ui → assets/ui}/index.html +25 -25
  28. package/cli/templates/app-vue/{ui → assets/ui}/src/App.vue +176 -176
  29. package/cli/templates/app-vue/cj-tauri.toml +33 -0
  30. package/cli/templates/app-vue/cjpm.toml +21 -4
  31. package/cli/templates/app-vue/src/main.cj +18 -29
  32. package/cli/templates/{ohos-pc → ohos-skeleton}/README.md +5 -0
  33. package/cli/templates/{ohos-pc → ohos-skeleton}/build-hap.sh +15 -0
  34. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/cjpm.toml +8 -0
  35. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/dialog_host.cj +217 -0
  36. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/index.cj +527 -0
  37. package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/module.json5 +8 -0
  38. package/native/build_win.bat +62 -62
  39. package/native/core/bridge_core.c +1681 -1626
  40. package/native/core/bridge_core.h +675 -618
  41. package/native/core/bridge_js.h +108 -108
  42. package/native/platforms/harmonyos/README.md +115 -6
  43. package/native/platforms/linux/bridge_linux.c +2787 -2485
  44. package/native/platforms/windows/bridge_win.c +3740 -3248
  45. package/native/tests/gtk_threading_probe.c +143 -143
  46. package/native/tests/test_bridge_core.c +1339 -1228
  47. package/package.json +53 -51
  48. package/prebuilt/linux-x64/cj-tauri +0 -0
  49. package/src/api_system.cj +67 -67
  50. package/src/app.cj +325 -940
  51. package/src/app_events.cj +166 -0
  52. package/src/app_host_ops.cj +287 -0
  53. package/src/app_run.cj +283 -0
  54. package/src/app_spec.cj +76 -0
  55. package/src/arkweb_menu.cj +216 -0
  56. package/src/arkweb_script.cj +0 -12
  57. package/src/capability.cj +409 -409
  58. package/src/clipboard_bridge.cj +178 -0
  59. package/src/dialog_spec.cj +79 -0
  60. package/src/dragdrop.cj +26 -26
  61. package/src/embedded_assets.cj +98 -98
  62. package/src/host.cj +752 -604
  63. package/src/host_arkweb.cj +201 -17
  64. package/src/host_webkit.cj +663 -548
  65. package/src/host_webview2.cj +663 -548
  66. package/src/hotkey.cj +66 -66
  67. package/src/ipc_hub.cj +337 -337
  68. package/src/ipc_message.cj +286 -286
  69. package/src/launch.cj +62 -0
  70. package/src/logging.cj +127 -127
  71. package/src/menu.cj +197 -197
  72. package/src/platform/platform_app_context.cj +77 -0
  73. package/src/platform/platform_assets.cj +33 -0
  74. package/src/platform/platform_discover.cj +176 -0
  75. package/src/platform/platform_env.cj +29 -0
  76. package/src/platform/platform_feature.cj +31 -0
  77. package/src/platform/platform_page_source.cj +87 -0
  78. package/src/platform/platform_target_info.cj +32 -0
  79. package/src/platform_host_provider.cj +87 -0
  80. package/src/plugin.cj +333 -299
  81. package/src/plugins/clipboard/plugin_clipboard.cj +152 -144
  82. package/src/plugins/dialog/plugin_dialog.cj +209 -209
  83. package/src/plugins/fs/plugin_fs.cj +105 -1
  84. package/src/plugins/http/http_backend.cj +131 -0
  85. package/src/plugins/http/http_desktop.cj +195 -0
  86. package/src/plugins/http/http_ohos.cj +204 -0
  87. package/src/plugins/http/plugin_http.cj +310 -459
  88. package/src/plugins/log/plugin_log.cj +105 -105
  89. package/src/plugins/menu/plugin_menu.cj +8 -0
  90. package/src/plugins/notification/plugin_notification.cj +154 -146
  91. package/src/plugins/serial/plugin_serial.cj +17 -1
  92. package/src/plugins/serial/serial_bridge.cj +9 -1
  93. package/src/plugins/serial/serial_manager_ohos.cj +92 -0
  94. package/src/plugins/serial/serial_ohos.cj +529 -0
  95. package/src/plugins/shell/plugin_shell.cj +29 -1
  96. package/src/plugins/sqlite/plugin_sqlite.cj +8 -0
  97. package/src/plugins/ws/plugin_ws.cj +488 -470
  98. package/src/process_hidden.cj +174 -174
  99. package/src/scope.cj +391 -391
  100. package/src/shell_event.cj +69 -69
  101. package/src/tests/app_spec_test.cj +126 -0
  102. package/src/tests/capability_test.cj +243 -243
  103. package/src/tests/dialog_spec_test.cj +73 -0
  104. package/src/tests/fake_host.cj +201 -186
  105. package/src/tests/ipc_hub_test.cj +487 -487
  106. package/src/tests/logging_test.cj +62 -62
  107. package/src/tests/platform_contract_test.cj +251 -0
  108. package/src/tests/plugin_clipboard_test.cj +222 -222
  109. package/src/tests/plugin_dialog_test.cj +237 -237
  110. package/src/tests/plugin_fs_test.cj +207 -0
  111. package/src/tests/plugin_http_test.cj +124 -124
  112. package/src/tests/plugin_notification_test.cj +205 -205
  113. package/src/tests/plugin_serial_test.cj +48 -0
  114. package/src/tests/plugin_test.cj +66 -3
  115. package/src/tests/plugin_ws_test.cj +40 -40
  116. package/src/tests/process_hidden_test.cj +129 -129
  117. package/src/tests/scope_test.cj +350 -350
  118. package/src/tests/system_host_test.cj +99 -81
  119. package/src/tests/urlutil_test.cj +53 -53
  120. package/src/tests/version_test.cj +75 -75
  121. package/src/tests/window_registry_test.cj +123 -123
  122. package/src/tray.cj +90 -90
  123. package/src/urlutil.cj +159 -159
  124. package/src/version.cj +44 -44
  125. package/src/window.cj +51 -51
  126. package/src/window_registry.cj +116 -116
  127. package/cli/templates/ohos-pc/entry/src/main/cangjie/index.cj +0 -167
  128. package/prebuilt/win32-x64/cj-tauri.exe +0 -0
  129. package/src/host_factory.cj +0 -48
  130. /package/cli/templates/app/{capabilities → assets/capabilities}/default.json +0 -0
  131. /package/cli/templates/app-react/{capabilities → assets/capabilities}/default.json +0 -0
  132. /package/cli/templates/app-react/{ui → assets/ui}/package.json +0 -0
  133. /package/cli/templates/app-react/{ui → assets/ui}/src/main.jsx +0 -0
  134. /package/cli/templates/app-react/{ui → assets/ui}/vite.config.js +0 -0
  135. /package/cli/templates/app-vue/{capabilities → assets/capabilities}/default.json +0 -0
  136. /package/cli/templates/app-vue/{ui → assets/ui}/package.json +0 -0
  137. /package/cli/templates/app-vue/{ui → assets/ui}/src/main.js +0 -0
  138. /package/cli/templates/app-vue/{ui → assets/ui}/vite.config.js +0 -0
  139. /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/app.json5 +0 -0
  140. /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/element/string.json +0 -0
  141. /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/background.png +0 -0
  142. /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/foreground.png +0 -0
  143. /package/cli/templates/{ohos-pc → ohos-skeleton}/AppScope/resources/base/media/layered_image.json +0 -0
  144. /package/cli/templates/{ohos-pc → ohos-skeleton}/build-profile.json5 +0 -0
  145. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/build-profile.json5 +0 -0
  146. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/hvigorfile.ts +0 -0
  147. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/oh-package.json5 +0 -0
  148. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/ability_stage.cj +0 -0
  149. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/app_host.cj +0 -0
  150. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/cangjie/main_ability.cj +0 -0
  151. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/color.json +0 -0
  152. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/element/string.json +0 -0
  153. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/background.png +0 -0
  154. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/foreground.png +0 -0
  155. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/layered_image.json +0 -0
  156. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/media/startIcon.png +0 -0
  157. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/base/profile/main_pages.json +0 -0
  158. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/capabilities.json +0 -0
  159. /package/cli/templates/{ohos-pc → ohos-skeleton}/entry/src/main/resources/rawfile/index.html +0 -0
  160. /package/cli/templates/{ohos-pc → ohos-skeleton}/hvigor/hvigor-config.json5 +0 -0
  161. /package/cli/templates/{ohos-pc → ohos-skeleton}/hvigorfile.ts +0 -0
  162. /package/cli/templates/{ohos-pc → ohos-skeleton}/oh-package.json5 +0 -0
@@ -1,81 +1,99 @@
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
- * 内置命令 system:host 的用例。
11
- *
12
- * 覆盖的是「能力位 → 具名布尔 JSON」这一段:能力位从调用窗口的宿主来,经 hub 注入的取值通道
13
- * (生产环境里由 TauriApp 接到 hostCapabilities())变成 HostCapabilities。用例不建窗口,
14
- * 替身就是那条注入通道——顺带钉住「system:host 走的是通道,不是 hub 自己去摸窗口注册表」。
15
- *
16
- * 断言一律**解析 JSON 再取字段**(不靠子串):序列化格式(空白、键序)不是契约,字段值是。
17
- */
18
-
19
- /** 造一个「通道给 bits」的 hub + 会话上下文(window = main) */
20
- func hostCtxWith(bits: Int32, asked: ArrayList<String>): IpcContext {
21
- let hub = IpcHub()
22
- hub.hostCapabilitiesSink = { label =>
23
- asked.add(label)
24
- bits
25
- }
26
- return IpcContext(DEFAULT_WINDOW_LABEL, hub)
27
- }
28
-
29
- @Test
30
- func testSystemHostReportsNamedBooleansForInjectedBits(): Unit {
31
- let asked = ArrayList<String>()
32
- // 有菜单 + 有托盘、没有拖入:三位各自独立翻名,不能整体按 true/false 一刀切
33
- let ctx = hostCtxWith(CJ_CAP_MENU | CJ_CAP_TRAY, asked)
34
- let data = SystemCommands().handle("system:host", JsonObject(), ctx).asObject()
35
- @Expect(data.get("menu").getOrThrow().asBool().getValue(), true)
36
- @Expect(data.get("tray").getOrThrow().asBool().getValue(), true)
37
- @Expect(data.get("dragDrop").getOrThrow().asBool().getValue(), false)
38
- // platform 由仓颉侧条件编译给(windows / linux),不该是空串
39
- @Expect(data.get("platform").getOrThrow().asString().getValue().size > 0, true)
40
- // logFile(RFC-003 第 5 期):进程级字段,用例环境没开日志 → 空串(字段在、值才空)
41
- @Expect(data.get("logFile").getOrThrow().asString().getValue(), "")
42
- // 取的是**发起命令那个窗口**的能力位
43
- @Expect(asked[0], DEFAULT_WINDOW_LABEL)
44
- }
45
-
46
- @Test
47
- func testSystemHostReportsAllFalseWhenPlatformHasNothing(): Unit {
48
- let asked = ArrayList<String>()
49
- let ctx = hostCtxWith(0, asked)
50
- let data = SystemCommands().handle("system:host", JsonObject(), ctx).asObject()
51
- @Expect(data.get("menu").getOrThrow().asBool().getValue(), false)
52
- @Expect(data.get("tray").getOrThrow().asBool().getValue(), false)
53
- @Expect(data.get("dragDrop").getOrThrow().asBool().getValue(), false)
54
- }
55
-
56
- @Test
57
- func testSystemHostResolvesThroughHubWhenDeclared(): Unit {
58
- // 三处联动的第三处:清单里声明了 system:host,前端才调得到(走完整 IPC 链路 + 异步分发)
59
- let out = ArrayList<String>()
60
- let hub = hubWith(["system:host"], [], out)
61
- hub.hostCapabilitiesSink = { _ => CJ_CAP_MENU }
62
- hub.register("system:host", SystemCommands())
63
- hub.handleRawMessage("{\"type\":\"invoke\",\"id\":21,\"cmd\":\"system:host\"}")
64
- waitSink(out, 1)
65
- let reply = JsonValue.fromStr(out[0]).asObject()
66
- @Expect(reply.get("ok").getOrThrow().asBool().getValue(), true)
67
- let data = reply.get("data").getOrThrow().asObject()
68
- @Expect(data.get("menu").getOrThrow().asBool().getValue(), true)
69
- }
70
-
71
- @Test
72
- func testSystemHostIsDeniedWithoutCapability(): Unit {
73
- // 三处联动缺了清单那一处:能力清单没声明 system:host → 调用线程上同步拒绝
74
- let out = ArrayList<String>()
75
- let hub = hubWith([], [], out)
76
- hub.hostCapabilitiesSink = { _ => CJ_CAP_MENU }
77
- hub.register("system:host", SystemCommands())
78
- hub.handleRawMessage("{\"type\":\"invoke\",\"id\":23,\"cmd\":\"system:host\"}")
79
- waitSink(out, 1)
80
- @Expect(out[0].contains("command not allowed"), true)
81
- }
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
+ * 内置命令 system:host 的用例。
11
+ *
12
+ * 覆盖的是「能力位 → 具名布尔 JSON」这一段:能力位从调用窗口的宿主来,经 hub 注入的取值通道
13
+ * (生产环境里由 TauriApp 接到 hostCapabilities())变成 HostCapabilities。用例不建窗口,
14
+ * 替身就是那条注入通道——顺带钉住「system:host 走的是通道,不是 hub 自己去摸窗口注册表」。
15
+ *
16
+ * 断言一律**解析 JSON 再取字段**(不靠子串):序列化格式(空白、键序)不是契约,字段值是。
17
+ */
18
+
19
+ /** 造一个「通道给 bits」的 hub + 会话上下文(window = main) */
20
+ func hostCtxWith(bits: Int32, asked: ArrayList<String>): IpcContext {
21
+ let hub = IpcHub()
22
+ hub.hostCapabilitiesSink = { label =>
23
+ asked.add(label)
24
+ bits
25
+ }
26
+ return IpcContext(DEFAULT_WINDOW_LABEL, hub)
27
+ }
28
+
29
+ @Test
30
+ func testSystemHostReportsNamedBooleansForInjectedBits(): Unit {
31
+ let asked = ArrayList<String>()
32
+ // 有菜单 + 有托盘、没有拖入:三位各自独立翻名,不能整体按 true/false 一刀切
33
+ let ctx = hostCtxWith(CJ_CAP_MENU | CJ_CAP_TRAY, asked)
34
+ let data = SystemCommands().handle("system:host", JsonObject(), ctx).asObject()
35
+ @Expect(data.get("menu").getOrThrow().asBool().getValue(), true)
36
+ @Expect(data.get("tray").getOrThrow().asBool().getValue(), true)
37
+ @Expect(data.get("dragDrop").getOrThrow().asBool().getValue(), false)
38
+ // notification(CJ_CAP_NOTIFICATION = 8):与 dragDrop(4) / hotkey(16) 是三个独立的位;
39
+ // 这一位曾整条漏在仓颉侧(C 桥置了、可读形态里没有字段),所以单独钉一条
40
+ @Expect(data.get("notification").getOrThrow().asBool().getValue(), false)
41
+ // platform 由仓颉侧条件编译给(windows / linux),不该是空串
42
+ @Expect(data.get("platform").getOrThrow().asString().getValue().size > 0, true)
43
+ // logFile(RFC-003 第 5 期):进程级字段,用例环境没开日志 → 空串(字段在、值才空)
44
+ @Expect(data.get("logFile").getOrThrow().asString().getValue(), "")
45
+ // 取的是**发起命令那个窗口**的能力位
46
+ @Expect(asked[0], DEFAULT_WINDOW_LABEL)
47
+ }
48
+
49
+ @Test
50
+ func testSystemHostReportsAllFalseWhenPlatformHasNothing(): Unit {
51
+ let asked = ArrayList<String>()
52
+ let ctx = hostCtxWith(0, asked)
53
+ let data = SystemCommands().handle("system:host", JsonObject(), ctx).asObject()
54
+ @Expect(data.get("menu").getOrThrow().asBool().getValue(), false)
55
+ @Expect(data.get("tray").getOrThrow().asBool().getValue(), false)
56
+ @Expect(data.get("dragDrop").getOrThrow().asBool().getValue(), false)
57
+ @Expect(data.get("notification").getOrThrow().asBool().getValue(), false)
58
+ }
59
+
60
+ @Test
61
+ func testSystemHostMapsNotificationBitIndependently(): Unit {
62
+ // 只置通知位(8):它左右两个邻居 dragDrop(4) / hotkey(16) 必须仍是 false。
63
+ // 这一条正是那位曾经的缺口——C 侧 `CJ_CAP_NOTIFICATION` 置了、仓颉侧没有对应的位与字段,
64
+ // `system:host` 少了 notification 键(不是值为 false,是根本没这个键),页面读到的就是 undefined。
65
+ let asked = ArrayList<String>()
66
+ let ctx = hostCtxWith(CJ_CAP_NOTIFICATION, asked)
67
+ let data = SystemCommands().handle("system:host", JsonObject(), ctx).asObject()
68
+ @Expect(data.get("notification").getOrThrow().asBool().getValue(), true)
69
+ @Expect(data.get("dragDrop").getOrThrow().asBool().getValue(), false)
70
+ @Expect(data.get("hotkey").getOrThrow().asBool().getValue(), false)
71
+ @Expect(data.get("menu").getOrThrow().asBool().getValue(), false)
72
+ }
73
+
74
+ @Test
75
+ func testSystemHostResolvesThroughHubWhenDeclared(): Unit {
76
+ // 三处联动的第三处:清单里声明了 system:host,前端才调得到(走完整 IPC 链路 + 异步分发)
77
+ let out = ArrayList<String>()
78
+ let hub = hubWith(["system:host"], [], out)
79
+ hub.hostCapabilitiesSink = { _ => CJ_CAP_MENU }
80
+ hub.register("system:host", SystemCommands())
81
+ hub.handleRawMessage("{\"type\":\"invoke\",\"id\":21,\"cmd\":\"system:host\"}")
82
+ waitSink(out, 1)
83
+ let reply = JsonValue.fromStr(out[0]).asObject()
84
+ @Expect(reply.get("ok").getOrThrow().asBool().getValue(), true)
85
+ let data = reply.get("data").getOrThrow().asObject()
86
+ @Expect(data.get("menu").getOrThrow().asBool().getValue(), true)
87
+ }
88
+
89
+ @Test
90
+ func testSystemHostIsDeniedWithoutCapability(): Unit {
91
+ // 三处联动缺了清单那一处:能力清单没声明 system:host → 调用线程上同步拒绝
92
+ let out = ArrayList<String>()
93
+ let hub = hubWith([], [], out)
94
+ hub.hostCapabilitiesSink = { _ => CJ_CAP_MENU }
95
+ hub.register("system:host", SystemCommands())
96
+ hub.handleRawMessage("{\"type\":\"invoke\",\"id\":23,\"cmd\":\"system:host\"}")
97
+ waitSink(out, 1)
98
+ @Expect(out[0].contains("command not allowed"), true)
99
+ }
@@ -1,53 +1,53 @@
1
- package cjTauri.tests
2
-
3
- import cjTauri.*
4
- import std.unittest.*
5
- import std.unittest.testmacro.*
6
-
7
- /*
8
- * SNI 的目标怎么定:`urlHostOf` 取主机名,`isIpLiteralHost` 判「是不是 IP 字面量」——
9
- * 域名才带 `serverName`(stdx 默认不发 SNI,多租户 HTTPS 边缘会当场以 handshake failure 拒掉,
10
- * 实测 Cloudflare 后面的 api 域名,见 D-022);IP 字面量不带(RFC 6066 不许,且直连 IP 原本可用)。
11
- */
12
-
13
- @Test
14
- func testIpLiteralHostRecognizesIpv4AndIpv6(): Unit {
15
- @Expect(isIpLiteralHost("127.0.0.1"), true)
16
- @Expect(isIpLiteralHost("192.168.1.10"), true)
17
- @Expect(isIpLiteralHost("255.255.255.255"), true)
18
- @Expect(isIpLiteralHost("[::1]"), true)
19
- @Expect(isIpLiteralHost("::1"), true)
20
- @Expect(isIpLiteralHost("2606:4700:681a:e1a::1"), true)
21
- }
22
-
23
- @Test
24
- func testIpLiteralHostRejectsHostnames(): Unit {
25
- @Expect(isIpLiteralHost("ai.novacode.top"), false)
26
- @Expect(isIpLiteralHost("localhost"), false)
27
- // 全是十六进制字母的域名(含点)**不是** IP——按「四段十进制」判就不会误伤
28
- @Expect(isIpLiteralHost("dead.beef"), false)
29
- @Expect(isIpLiteralHost("127.0.0.1.evil.com"), false)
30
- @Expect(isIpLiteralHost("1.2.3"), false)
31
- @Expect(isIpLiteralHost("999.1.1.1"), false)
32
- @Expect(isIpLiteralHost("1.2.3.4.5"), false)
33
- @Expect(isIpLiteralHost(""), false)
34
- }
35
-
36
- @Test
37
- func testSniTargetDecisionFromUrl(): Unit {
38
- // 域名 → 带 SNI(这一条正是 ai.novacode.top 那轮修掉的东西)
39
- let named = match (urlHostOf("https://ai.novacode.top/v1/models")) {
40
- case Some(h) => h
41
- case None => ""
42
- }
43
- @Expect(named, "ai.novacode.top")
44
- @Expect(isIpLiteralHost(named), false)
45
-
46
- // 直连 IP(带端口)→ 不带 SNI,连接与验证照旧
47
- let byIp = match (urlHostOf("https://127.0.0.1:8765/api/health")) {
48
- case Some(h) => h
49
- case None => ""
50
- }
51
- @Expect(byIp, "127.0.0.1")
52
- @Expect(isIpLiteralHost(byIp), true)
53
- }
1
+ package cjTauri.tests
2
+
3
+ import cjTauri.*
4
+ import std.unittest.*
5
+ import std.unittest.testmacro.*
6
+
7
+ /*
8
+ * SNI 的目标怎么定:`urlHostOf` 取主机名,`isIpLiteralHost` 判「是不是 IP 字面量」——
9
+ * 域名才带 `serverName`(stdx 默认不发 SNI,多租户 HTTPS 边缘会当场以 handshake failure 拒掉,
10
+ * 实测 Cloudflare 后面的 api 域名,见 D-022);IP 字面量不带(RFC 6066 不许,且直连 IP 原本可用)。
11
+ */
12
+
13
+ @Test
14
+ func testIpLiteralHostRecognizesIpv4AndIpv6(): Unit {
15
+ @Expect(isIpLiteralHost("127.0.0.1"), true)
16
+ @Expect(isIpLiteralHost("192.168.1.10"), true)
17
+ @Expect(isIpLiteralHost("255.255.255.255"), true)
18
+ @Expect(isIpLiteralHost("[::1]"), true)
19
+ @Expect(isIpLiteralHost("::1"), true)
20
+ @Expect(isIpLiteralHost("2606:4700:681a:e1a::1"), true)
21
+ }
22
+
23
+ @Test
24
+ func testIpLiteralHostRejectsHostnames(): Unit {
25
+ @Expect(isIpLiteralHost("ai.novacode.top"), false)
26
+ @Expect(isIpLiteralHost("localhost"), false)
27
+ // 全是十六进制字母的域名(含点)**不是** IP——按「四段十进制」判就不会误伤
28
+ @Expect(isIpLiteralHost("dead.beef"), false)
29
+ @Expect(isIpLiteralHost("127.0.0.1.evil.com"), false)
30
+ @Expect(isIpLiteralHost("1.2.3"), false)
31
+ @Expect(isIpLiteralHost("999.1.1.1"), false)
32
+ @Expect(isIpLiteralHost("1.2.3.4.5"), false)
33
+ @Expect(isIpLiteralHost(""), false)
34
+ }
35
+
36
+ @Test
37
+ func testSniTargetDecisionFromUrl(): Unit {
38
+ // 域名 → 带 SNI(这一条正是 ai.novacode.top 那轮修掉的东西)
39
+ let named = match (urlHostOf("https://ai.novacode.top/v1/models")) {
40
+ case Some(h) => h
41
+ case None => ""
42
+ }
43
+ @Expect(named, "ai.novacode.top")
44
+ @Expect(isIpLiteralHost(named), false)
45
+
46
+ // 直连 IP(带端口)→ 不带 SNI,连接与验证照旧
47
+ let byIp = match (urlHostOf("https://127.0.0.1:8765/api/health")) {
48
+ case Some(h) => h
49
+ case None => ""
50
+ }
51
+ @Expect(byIp, "127.0.0.1")
52
+ @Expect(isIpLiteralHost(byIp), true)
53
+ }
@@ -1,75 +1,75 @@
1
- package cjTauri.tests
2
-
3
- import cjTauri.*
4
- import stdx.encoding.json.*
5
- import std.unittest.*
6
- import std.unittest.testmacro.*
7
-
8
- /*
9
- * 版本与内置命令的一致性测试。
10
- * 版本号的权威目标是 CHANGELOG.md,四处常量由 scripts/check-version.sh 巡检;
11
- * 这里只锁「代码里的单一来源不漂移」。
12
- */
13
-
14
- @Test
15
- func testVersionConstantNotEmpty(): Unit {
16
- @Expect(CJ_TAURI_VERSION.size > 0, true)
17
- }
18
-
19
- @Test
20
- func testSystemVersionReportsConstant(): Unit {
21
- let commands = SystemCommands()
22
- let v = commands.handle("system:version", JsonObject(), IpcContext("main", IpcHub()))
23
- let o = v.asObject()
24
- // 命令报告的版本必须就是常量本身(防止有人改了其中一处)
25
- @Expect(o.get("version").getOrThrow().asString().getValue(), CJ_TAURI_VERSION)
26
- @Expect(o.get("name").getOrThrow().asString().getValue(), "cj-tauri")
27
- @Expect(o.get("protocol").getOrThrow().asString().getValue(), CJ_IPC_PROTOCOL)
28
- @Expect(o.get("platform").getOrThrow().asString().getValue(), currentPlatform())
29
- @Expect(currentPlatform().size > 0, true)
30
- }
31
-
32
- @Test
33
- func testIpcProtocolConstantPresent(): Unit {
34
- // 协议版本常量:非空、纯数字(抬版本时按「删字段/改语义必须 +1」的纪律走)
35
- @Expect(CJ_IPC_PROTOCOL.size > 0, true)
36
- for (b in CJ_IPC_PROTOCOL.toArray()) {
37
- @Expect(b >= 48u8 && b <= 57u8, true)
38
- }
39
- }
40
-
41
- @Test
42
- func testSystemPingEchoAndUnknown(): Unit {
43
- let commands = SystemCommands()
44
- let ctx = IpcContext("main", IpcHub())
45
- @Expect(commands.handle("system:ping", JsonObject(), ctx).asString().getValue(), "pong")
46
-
47
- var args = JsonObject()
48
- args.put("who", JsonString("cj"))
49
- let echoed = commands.handle("system:echo", args, ctx).asObject()
50
- @Expect(echoed.get("who").getOrThrow().asString().getValue(), "cj")
51
-
52
- // 未定义的 system:* 走 CommandException,前端拿到的是这句原文
53
- var thrown = ""
54
- try {
55
- commands.handle("system:nope", JsonObject(), ctx)
56
- } catch (e: CommandException) {
57
- thrown = e.message
58
- }
59
- @Expect(thrown, "unknown system command: system:nope")
60
- }
61
-
62
- @Test
63
- func testWindowConfigDefaultsMatchBridgeHardcoding(): Unit {
64
- // 默认值必须与桥里此前的硬编码一致(标题 cj-tauri / 900x640),否则不调 window() 的应用会变样
65
- let def = WindowConfig()
66
- @Expect(def.title, "cj-tauri")
67
- @Expect(def.width, 900)
68
- @Expect(def.height, 640)
69
- @Expect(def.devTools, true)
70
-
71
- let custom = WindowConfig("我的应用", 1200, 800)
72
- @Expect(custom.title, "我的应用")
73
- @Expect(custom.width, 1200)
74
- @Expect(custom.height, 800)
75
- }
1
+ package cjTauri.tests
2
+
3
+ import cjTauri.*
4
+ import stdx.encoding.json.*
5
+ import std.unittest.*
6
+ import std.unittest.testmacro.*
7
+
8
+ /*
9
+ * 版本与内置命令的一致性测试。
10
+ * 版本号的权威目标是 CHANGELOG.md,四处常量由 scripts/check-version.sh 巡检;
11
+ * 这里只锁「代码里的单一来源不漂移」。
12
+ */
13
+
14
+ @Test
15
+ func testVersionConstantNotEmpty(): Unit {
16
+ @Expect(CJ_TAURI_VERSION.size > 0, true)
17
+ }
18
+
19
+ @Test
20
+ func testSystemVersionReportsConstant(): Unit {
21
+ let commands = SystemCommands()
22
+ let v = commands.handle("system:version", JsonObject(), IpcContext("main", IpcHub()))
23
+ let o = v.asObject()
24
+ // 命令报告的版本必须就是常量本身(防止有人改了其中一处)
25
+ @Expect(o.get("version").getOrThrow().asString().getValue(), CJ_TAURI_VERSION)
26
+ @Expect(o.get("name").getOrThrow().asString().getValue(), "cj-tauri")
27
+ @Expect(o.get("protocol").getOrThrow().asString().getValue(), CJ_IPC_PROTOCOL)
28
+ @Expect(o.get("platform").getOrThrow().asString().getValue(), currentPlatform())
29
+ @Expect(currentPlatform().size > 0, true)
30
+ }
31
+
32
+ @Test
33
+ func testIpcProtocolConstantPresent(): Unit {
34
+ // 协议版本常量:非空、纯数字(抬版本时按「删字段/改语义必须 +1」的纪律走)
35
+ @Expect(CJ_IPC_PROTOCOL.size > 0, true)
36
+ for (b in CJ_IPC_PROTOCOL.toArray()) {
37
+ @Expect(b >= 48u8 && b <= 57u8, true)
38
+ }
39
+ }
40
+
41
+ @Test
42
+ func testSystemPingEchoAndUnknown(): Unit {
43
+ let commands = SystemCommands()
44
+ let ctx = IpcContext("main", IpcHub())
45
+ @Expect(commands.handle("system:ping", JsonObject(), ctx).asString().getValue(), "pong")
46
+
47
+ var args = JsonObject()
48
+ args.put("who", JsonString("cj"))
49
+ let echoed = commands.handle("system:echo", args, ctx).asObject()
50
+ @Expect(echoed.get("who").getOrThrow().asString().getValue(), "cj")
51
+
52
+ // 未定义的 system:* 走 CommandException,前端拿到的是这句原文
53
+ var thrown = ""
54
+ try {
55
+ commands.handle("system:nope", JsonObject(), ctx)
56
+ } catch (e: CommandException) {
57
+ thrown = e.message
58
+ }
59
+ @Expect(thrown, "unknown system command: system:nope")
60
+ }
61
+
62
+ @Test
63
+ func testWindowConfigDefaultsMatchBridgeHardcoding(): Unit {
64
+ // 默认值必须与桥里此前的硬编码一致(标题 cj-tauri / 900x640),否则不调 window() 的应用会变样
65
+ let def = WindowConfig()
66
+ @Expect(def.title, "cj-tauri")
67
+ @Expect(def.width, 900)
68
+ @Expect(def.height, 640)
69
+ @Expect(def.devTools, true)
70
+
71
+ let custom = WindowConfig("我的应用", 1200, 800)
72
+ @Expect(custom.title, "我的应用")
73
+ @Expect(custom.width, 1200)
74
+ @Expect(custom.height, 800)
75
+ }