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.
Files changed (158) hide show
  1. package/README.md +113 -99
  2. package/bin/cj-tauri.js +287 -287
  3. package/cjpm.toml +63 -52
  4. package/cli/cjpm.toml +22 -22
  5. package/cli/src/doctor.cj +308 -306
  6. package/cli/src/main.cj +129 -129
  7. package/cli/src/manifest.cj +632 -558
  8. package/cli/src/manifest_test.cj +87 -0
  9. package/cli/src/project.cj +770 -770
  10. package/cli/src/resolve.cj +348 -348
  11. package/cli/src/scaffold.cj +369 -369
  12. package/cli/src/sync.cj +526 -526
  13. package/cli/src/util.cj +168 -168
  14. package/cli/templates/app/README.md +51 -48
  15. package/cli/templates/app/assets/capabilities/default.json +7 -7
  16. package/cli/templates/app/assets/ui/index.html +120 -118
  17. package/cli/templates/app/cj-tauri.toml +32 -22
  18. package/cli/templates/app/cjpm.toml +34 -34
  19. package/cli/templates/app/src/main.cj +101 -90
  20. package/cli/templates/app-react/README.md +56 -56
  21. package/cli/templates/app-react/assets/capabilities/default.json +7 -7
  22. package/cli/templates/app-react/assets/ui/index.html +25 -25
  23. package/cli/templates/app-react/assets/ui/package.json +19 -19
  24. package/cli/templates/app-react/assets/ui/src/App.css +67 -67
  25. package/cli/templates/app-react/assets/ui/src/App.jsx +124 -124
  26. package/cli/templates/app-react/assets/ui/src/main.jsx +6 -6
  27. package/cli/templates/app-react/assets/ui/vite.config.js +24 -24
  28. package/cli/templates/app-react/cj-tauri.toml +33 -23
  29. package/cli/templates/app-react/cjpm.toml +34 -34
  30. package/cli/templates/app-react/src/main.cj +91 -91
  31. package/cli/templates/app-vue/README.md +56 -56
  32. package/cli/templates/app-vue/assets/capabilities/default.json +7 -7
  33. package/cli/templates/app-vue/assets/ui/index.html +25 -25
  34. package/cli/templates/app-vue/assets/ui/package.json +18 -18
  35. package/cli/templates/app-vue/assets/ui/src/App.vue +176 -176
  36. package/cli/templates/app-vue/assets/ui/src/main.js +4 -4
  37. package/cli/templates/app-vue/assets/ui/vite.config.js +24 -24
  38. package/cli/templates/app-vue/cj-tauri.toml +33 -23
  39. package/cli/templates/app-vue/cjpm.toml +34 -34
  40. package/cli/templates/app-vue/src/main.cj +91 -91
  41. package/cli/templates/ohos-skeleton/AppScope/app.json5 +13 -13
  42. package/cli/templates/ohos-skeleton/AppScope/resources/base/element/string.json +8 -8
  43. package/cli/templates/ohos-skeleton/AppScope/resources/base/media/layered_image.json +6 -6
  44. package/cli/templates/ohos-skeleton/README.md +84 -79
  45. package/cli/templates/ohos-skeleton/build-hap.sh +15 -0
  46. package/cli/templates/ohos-skeleton/build-profile.json5 +46 -46
  47. package/cli/templates/ohos-skeleton/entry/build-profile.json5 +26 -26
  48. package/cli/templates/ohos-skeleton/entry/cjpm.toml +51 -43
  49. package/cli/templates/ohos-skeleton/entry/hvigorfile.ts +6 -6
  50. package/cli/templates/ohos-skeleton/entry/oh-package.json5 +9 -9
  51. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/ability_stage.cj +17 -17
  52. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/app_host.cj +94 -94
  53. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/dialog_host.cj +217 -0
  54. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/index.cj +527 -167
  55. package/cli/templates/ohos-skeleton/entry/src/main/cangjie/main_ability.cj +139 -139
  56. package/cli/templates/ohos-skeleton/entry/src/main/module.json5 +57 -49
  57. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/color.json +8 -8
  58. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/element/string.json +16 -16
  59. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/media/layered_image.json +6 -6
  60. package/cli/templates/ohos-skeleton/entry/src/main/resources/base/profile/main_pages.json +2 -2
  61. package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/capabilities.json +6 -6
  62. package/cli/templates/ohos-skeleton/entry/src/main/resources/rawfile/index.html +92 -92
  63. package/cli/templates/ohos-skeleton/hvigor/hvigor-config.json5 +5 -5
  64. package/cli/templates/ohos-skeleton/hvigorfile.ts +6 -6
  65. package/cli/templates/ohos-skeleton/oh-package.json5 +10 -10
  66. package/native/build_win.bat +62 -62
  67. package/native/core/bridge_core.c +1681 -1626
  68. package/native/core/bridge_core.h +675 -618
  69. package/native/core/bridge_js.h +108 -108
  70. package/native/platforms/harmonyos/README.md +115 -6
  71. package/native/platforms/linux/bridge_linux.c +2787 -2485
  72. package/native/platforms/windows/bridge_win.c +3740 -3248
  73. package/native/tests/gtk_threading_probe.c +143 -143
  74. package/native/tests/test_bridge_core.c +1339 -1228
  75. package/package.json +53 -53
  76. package/prebuilt/linux-x64/cj-tauri +0 -0
  77. package/src/api_system.cj +67 -67
  78. package/src/app.cj +325 -969
  79. package/src/app_events.cj +166 -0
  80. package/src/app_host_ops.cj +287 -0
  81. package/src/app_run.cj +283 -0
  82. package/src/app_spec.cj +76 -76
  83. package/src/arkweb_menu.cj +216 -0
  84. package/src/arkweb_script.cj +142 -142
  85. package/src/capability.cj +409 -409
  86. package/src/clipboard_bridge.cj +178 -0
  87. package/src/dialog_spec.cj +79 -0
  88. package/src/dragdrop.cj +26 -26
  89. package/src/embedded_assets.cj +98 -98
  90. package/src/host.cj +752 -607
  91. package/src/host_arkweb.cj +924 -778
  92. package/src/host_webkit.cj +663 -570
  93. package/src/host_webview2.cj +663 -570
  94. package/src/hotkey.cj +66 -66
  95. package/src/ipc_hub.cj +337 -337
  96. package/src/ipc_message.cj +286 -286
  97. package/src/launch.cj +62 -62
  98. package/src/logging.cj +127 -127
  99. package/src/menu.cj +197 -197
  100. package/src/platform/platform_app_context.cj +77 -77
  101. package/src/platform/platform_assets.cj +33 -33
  102. package/src/platform/platform_discover.cj +176 -176
  103. package/src/platform/platform_env.cj +29 -29
  104. package/src/platform/platform_feature.cj +31 -31
  105. package/src/platform/platform_page_source.cj +87 -87
  106. package/src/platform/platform_target_info.cj +32 -32
  107. package/src/platform_host_provider.cj +87 -87
  108. package/src/plugin.cj +333 -333
  109. package/src/plugins/clipboard/plugin_clipboard.cj +152 -152
  110. package/src/plugins/dialog/plugin_dialog.cj +209 -209
  111. package/src/plugins/fs/plugin_fs.cj +105 -1
  112. package/src/plugins/http/http_backend.cj +131 -0
  113. package/src/plugins/http/http_desktop.cj +195 -0
  114. package/src/plugins/http/http_ohos.cj +204 -0
  115. package/src/plugins/http/plugin_http.cj +310 -459
  116. package/src/plugins/log/plugin_log.cj +105 -105
  117. package/src/plugins/menu/plugin_menu.cj +158 -158
  118. package/src/plugins/notification/plugin_notification.cj +154 -154
  119. package/src/plugins/serial/plugin_serial.cj +458 -454
  120. package/src/plugins/serial/serial_bridge.cj +194 -194
  121. package/src/plugins/serial/serial_manager_ohos.cj +92 -0
  122. package/src/plugins/serial/serial_ohos.cj +529 -0
  123. package/src/plugins/shell/plugin_shell.cj +258 -258
  124. package/src/plugins/sqlite/plugin_sqlite.cj +705 -710
  125. package/src/plugins/sqlite/sqlite_ffi.cj +206 -211
  126. package/src/plugins/ws/plugin_ws.cj +488 -470
  127. package/src/process_hidden.cj +174 -174
  128. package/src/scope.cj +391 -391
  129. package/src/shell_event.cj +69 -69
  130. package/src/tests/app_spec_test.cj +126 -127
  131. package/src/tests/capability_test.cj +243 -243
  132. package/src/tests/dialog_spec_test.cj +73 -0
  133. package/src/tests/fake_host.cj +201 -201
  134. package/src/tests/ipc_hub_test.cj +487 -487
  135. package/src/tests/logging_test.cj +62 -62
  136. package/src/tests/platform_contract_test.cj +251 -251
  137. package/src/tests/plugin_clipboard_test.cj +222 -222
  138. package/src/tests/plugin_dialog_test.cj +237 -237
  139. package/src/tests/plugin_fs_test.cj +207 -0
  140. package/src/tests/plugin_http_test.cj +124 -124
  141. package/src/tests/plugin_notification_test.cj +205 -205
  142. package/src/tests/plugin_serial_test.cj +48 -0
  143. package/src/tests/plugin_test.cj +605 -585
  144. package/src/tests/plugin_ws_test.cj +40 -40
  145. package/src/tests/process_hidden_test.cj +129 -129
  146. package/src/tests/scope_test.cj +350 -350
  147. package/src/tests/system_host_test.cj +99 -81
  148. package/src/tests/urlutil_test.cj +53 -53
  149. package/src/tests/version_test.cj +75 -75
  150. package/src/tests/window_registry_test.cj +123 -123
  151. package/src/tray.cj +90 -90
  152. package/src/urlutil.cj +159 -159
  153. package/src/version.cj +44 -44
  154. package/src/window.cj +51 -51
  155. package/src/window_registry.cj +116 -116
  156. package/prebuilt/win32-x64/cj-tauri.exe +0 -0
  157. package/src/plugins/serial/serial_ohos_stub.cj +0 -61
  158. package/src/plugins/sqlite/sqlite_ohos_stub.cj +0 -91
@@ -1,69 +1,69 @@
1
- package cjTauri
2
-
3
- import stdx.encoding.json.*
4
-
5
- /**
6
- * shell 事件信封(C 桥 → 仓颉)的解析与语义映射:RFC-002 §5.5 / §5.6
7
- *
8
- * 信封形状(当前定稿,菜单):`{"kind":"menu","id":"file.save","enabled":true,"checked":false}`
9
- * 托盘 / 拖放落地后各带自己的 kind(`tray` / `drop`),kind 决定投给前端哪个事件名。
10
- *
11
- * 为什么单独成文件:这里是**纯函数**(不进窗口、不碰线程),能脱离宿主在单测里直接验。
12
- * 信封格式坏掉的表现是「事件静默不到」——没有纯函数边界时,只能靠实机点击去猜。
13
- */
14
-
15
- /** `kind` → 前端事件名;不认识的 kind 返回 None(调用方提示一句后丢弃,不抛异常) */
16
- public func shellEventNameFor(kind: String): Option<String> {
17
- match (kind) {
18
- case "menu" => return Some("menu:click")
19
- case "tray" => return Some("tray:click")
20
- case "drop" => return Some("dragdrop:files")
21
- case "hotkey" => return Some("hotkey:press")
22
- case _ => return None
23
- }
24
- }
25
-
26
- /**
27
- * 解析信封成对象;不是合法 JSON 对象就返回 None
28
- *
29
- * 不抛异常:数据从 C 桥来(**不是**页面可控输入),格式坏了宁可丢一条事件 + 留一行日志,
30
- * 也不要让异常穿到 FFI 边界——仓颉异常穿过 C 侧回调,进程状态就说不清了。
31
- */
32
- public func parseShellEnvelope(json: String): Option<JsonObject> {
33
- try {
34
- let v = JsonValue.fromStr(json)
35
- match (v.kind()) {
36
- case JsonKind.JsObject => return Some(v.asObject())
37
- case _ => return None
38
- }
39
- } catch (e: Exception) {
40
- return None
41
- }
42
- }
43
-
44
- /** 取信封里的字符串字段(缺失 / 类型不对都给 None) */
45
- public func shellString(obj: JsonObject, key: String): Option<String> {
46
- match (obj.get(key)) {
47
- case Some(v) => match (v.kind()) {
48
- case JsonKind.JsString => return Some(v.asString().getValue())
49
- case _ => return None
50
- }
51
- case None => return None
52
- }
53
- }
54
-
55
- /**
56
- * 取信封里的布尔字段(缺失 / 类型不对都给 None)
57
- *
58
- * 缺字段**不能**当 false 用:这两个值要回写到应用层菜单模型(见 `TauriApp` 的模型同步),
59
- * 拿默认值顶等于把「用户刚点出来的勾选」按默认值改掉——宁可不写。
60
- */
61
- public func shellBool(obj: JsonObject, key: String): Option<Bool> {
62
- match (obj.get(key)) {
63
- case Some(v) => match (v.kind()) {
64
- case JsonKind.JsBool => return Some(v.asBool().getValue())
65
- case _ => return None
66
- }
67
- case None => return None
68
- }
69
- }
1
+ package cjTauri
2
+
3
+ import stdx.encoding.json.*
4
+
5
+ /**
6
+ * shell 事件信封(C 桥 → 仓颉)的解析与语义映射:RFC-002 §5.5 / §5.6
7
+ *
8
+ * 信封形状(当前定稿,菜单):`{"kind":"menu","id":"file.save","enabled":true,"checked":false}`
9
+ * 托盘 / 拖放落地后各带自己的 kind(`tray` / `drop`),kind 决定投给前端哪个事件名。
10
+ *
11
+ * 为什么单独成文件:这里是**纯函数**(不进窗口、不碰线程),能脱离宿主在单测里直接验。
12
+ * 信封格式坏掉的表现是「事件静默不到」——没有纯函数边界时,只能靠实机点击去猜。
13
+ */
14
+
15
+ /** `kind` → 前端事件名;不认识的 kind 返回 None(调用方提示一句后丢弃,不抛异常) */
16
+ public func shellEventNameFor(kind: String): Option<String> {
17
+ match (kind) {
18
+ case "menu" => return Some("menu:click")
19
+ case "tray" => return Some("tray:click")
20
+ case "drop" => return Some("dragdrop:files")
21
+ case "hotkey" => return Some("hotkey:press")
22
+ case _ => return None
23
+ }
24
+ }
25
+
26
+ /**
27
+ * 解析信封成对象;不是合法 JSON 对象就返回 None
28
+ *
29
+ * 不抛异常:数据从 C 桥来(**不是**页面可控输入),格式坏了宁可丢一条事件 + 留一行日志,
30
+ * 也不要让异常穿到 FFI 边界——仓颉异常穿过 C 侧回调,进程状态就说不清了。
31
+ */
32
+ public func parseShellEnvelope(json: String): Option<JsonObject> {
33
+ try {
34
+ let v = JsonValue.fromStr(json)
35
+ match (v.kind()) {
36
+ case JsonKind.JsObject => return Some(v.asObject())
37
+ case _ => return None
38
+ }
39
+ } catch (e: Exception) {
40
+ return None
41
+ }
42
+ }
43
+
44
+ /** 取信封里的字符串字段(缺失 / 类型不对都给 None) */
45
+ public func shellString(obj: JsonObject, key: String): Option<String> {
46
+ match (obj.get(key)) {
47
+ case Some(v) => match (v.kind()) {
48
+ case JsonKind.JsString => return Some(v.asString().getValue())
49
+ case _ => return None
50
+ }
51
+ case None => return None
52
+ }
53
+ }
54
+
55
+ /**
56
+ * 取信封里的布尔字段(缺失 / 类型不对都给 None)
57
+ *
58
+ * 缺字段**不能**当 false 用:这两个值要回写到应用层菜单模型(见 `TauriApp` 的模型同步),
59
+ * 拿默认值顶等于把「用户刚点出来的勾选」按默认值改掉——宁可不写。
60
+ */
61
+ public func shellBool(obj: JsonObject, key: String): Option<Bool> {
62
+ match (obj.get(key)) {
63
+ case Some(v) => match (v.kind()) {
64
+ case JsonKind.JsBool => return Some(v.asBool().getValue())
65
+ case _ => return None
66
+ }
67
+ case None => return None
68
+ }
69
+ }
@@ -1,127 +1,126 @@
1
- package cjTauri.tests
2
-
3
- import cjTauri.*
4
- import cjTauri.platform.*
5
- import cjTauri.plugins.store.*
6
- import std.collection.*
7
- import std.time.*
8
- import std.unittest.*
9
- import std.unittest.testmacro.*
10
-
11
- // ---- 批次 3-PR1:AppSpec 构造器链与 assemble 装配 ----
12
-
13
- @Test
14
- func appSpecBuilderChain(): Unit {
15
- let spec = AppSpec()
16
- .page(PageSource.Inline("<p/>"))
17
- .window(WindowConfig("t", 800, 600))
18
- .capabilities("assets/capabilities")
19
- .capabilityJson("{\"identifier\":\"default\"}")
20
- @Expect(spec.appId, "")
21
- @Expect(spec.capabilitiesDir, "assets/capabilities")
22
- @Expect(spec.capabilityJsons.size, 1)
23
- @Expect(spec.windowConfig.title, "t")
24
- @Expect(spec.windowConfig.width, 800)
25
- @Expect(spec.assetResolver.isSome(), false)
26
- @Expect(spec.wireFn.isSome(), false)
27
- @Expect(spec.plugins.size, 0)
28
- match (spec.pageSource) {
29
- case Inline(html) => @Expect(html, "<p/>")
30
- case _ => @Expect(false, true)
31
- }
32
- }
33
-
34
- /** wire 闭包执行标记(仓颉闭包捕获 var 的语义不冒风险,用对象装状态) */
35
- class WireBox {
36
- public var on: Bool = false
37
- }
38
-
39
- /** 高优先级假宿主:TauriApp 字段初始化就调 resolveHost,无显示环境必须用注册表桩 */
40
- class AppSpecProbeProvider <: HostProvider {
41
- public var last: FakeHost = FakeHost("seed")
42
-
43
- public func priority(target: TargetInfo): Int64 {
44
- return 100
45
- }
46
-
47
- public func create(label: String, target: TargetInfo): WebViewHost {
48
- this.last = FakeHost(label)
49
- return this.last
50
- }
51
- }
52
-
53
- @Test
54
- func appSpecAssembleWiresApp(): Unit {
55
- let prov = AppSpecProbeProvider()
56
- registerHost(prov)
57
- let box = WireBox()
58
- let spec = AppSpec()
59
- .page(PageSource.Inline("<p/>"))
60
- .window(WindowConfig("spec窗", 640, 480))
61
- .plugin(StorePlugin())
62
- .wire({ app =>
63
- box.on = true
64
- })
65
- let app = assemble(spec)
66
- @Expect(box.on, true)
67
- @Expect(app.hostOf("main").isSome(), true)
68
- @Expect(prov.last.label(), "main")
69
- }
70
-
71
- // ---- 批次 3-PR3:load(source) 收敛——装配层把 PageSource 原样交给宿主,resolver 随装配进宿主 ----
72
-
73
- @Test
74
- func fakeHostLoadDispatchesPageSource(): Unit {
75
- let h = FakeHost("main")
76
- h.load(PageSource.Inline("<p>hi</p>"))
77
- @Expect(h.shownHtml, "<p>hi</p>")
78
- @Expect(h.startedUrl, "")
79
- h.load(PageSource.Url("https://example.com"))
80
- @Expect(h.startedUrl, "https://example.com")
81
- h.load(PageSource.DevServer("http://127.0.0.1:5173"))
82
- @Expect(h.startedUrl, "http://127.0.0.1:5173")
83
- h.load(PageSource.Asset("ui/index.html"))
84
- @Expect(h.loadedSource, "asset:ui/index.html")
85
- }
86
-
87
- /** run 路径探针:优先级压过既有注册表里的桩,保证本用例拿到的是自己造的宿主 */
88
- class RunProbeProvider <: HostProvider {
89
- public var last: FakeHost = FakeHost("seed")
90
-
91
- public func priority(target: TargetInfo): Int64 {
92
- return 200
93
- }
94
-
95
- public func create(label: String, target: TargetInfo): WebViewHost {
96
- this.last = FakeHost(label)
97
- return this.last
98
- }
99
- }
100
-
101
- /**
102
- * run() 路径:resolver 注入与页面来源交接发生在同一次装配(runWith)里。
103
- * 宿主用 FakeHost 桩(不建窗口);run() 放后台线程,观测到交接后置 quitNow 让它返回。
104
- */
105
- @Test
106
- func runHandsResolverAndSourceToHost(): Unit {
107
- let prov = RunProbeProvider()
108
- let provIdx = registerHost(prov)
109
- let spec = AppSpec()
110
- .page(PageSource.Asset("ui/index.html"))
111
- .window(WindowConfig("t", 640, 480))
112
- .assets(StubResolver(HashMap()))
113
- spawn {
114
- let app = assemble(spec)
115
- app.run(spec.pageSource)
116
- }
117
- var polls = 0
118
- while (polls < 1000 && prov.last.loadedSource != "asset:ui/index.html") {
119
- sleep(Duration.millisecond * 2)
120
- polls += 1
121
- }
122
- @Expect(prov.last.loadedSource, "asset:ui/index.html")
123
- @Expect(prov.last.resolverInjected.isSome(), true)
124
- prov.last.quitNow = true
125
- // 探针宿主清出全局注册表,免得压过后续用例自己的桩
126
- unregisterHost(provIdx)
127
- }
1
+ package cjTauri.tests
2
+
3
+ import cjTauri.*
4
+ import cjTauri.platform.*
5
+ import cjTauri.plugins.store.*
6
+ import std.collection.*
7
+ import std.unittest.*
8
+ import std.unittest.testmacro.*
9
+
10
+ // ---- 批次 3-PR1:AppSpec 构造器链与 assemble 装配 ----
11
+
12
+ @Test
13
+ func appSpecBuilderChain(): Unit {
14
+ let spec = AppSpec()
15
+ .page(PageSource.Inline("<p/>"))
16
+ .window(WindowConfig("t", 800, 600))
17
+ .capabilities("assets/capabilities")
18
+ .capabilityJson("{\"identifier\":\"default\"}")
19
+ @Expect(spec.appId, "")
20
+ @Expect(spec.capabilitiesDir, "assets/capabilities")
21
+ @Expect(spec.capabilityJsons.size, 1)
22
+ @Expect(spec.windowConfig.title, "t")
23
+ @Expect(spec.windowConfig.width, 800)
24
+ @Expect(spec.assetResolver.isSome(), false)
25
+ @Expect(spec.wireFn.isSome(), false)
26
+ @Expect(spec.plugins.size, 0)
27
+ match (spec.pageSource) {
28
+ case Inline(html) => @Expect(html, "<p/>")
29
+ case _ => @Expect(false, true)
30
+ }
31
+ }
32
+
33
+ /** wire 闭包执行标记(仓颉闭包捕获 var 的语义不冒风险,用对象装状态) */
34
+ class WireBox {
35
+ public var on: Bool = false
36
+ }
37
+
38
+ /** 高优先级假宿主:TauriApp 字段初始化就调 resolveHost,无显示环境必须用注册表桩 */
39
+ class AppSpecProbeProvider <: HostProvider {
40
+ public var last: FakeHost = FakeHost("seed")
41
+
42
+ public func priority(target: TargetInfo): Int64 {
43
+ return 100
44
+ }
45
+
46
+ public func create(label: String, target: TargetInfo): WebViewHost {
47
+ this.last = FakeHost(label)
48
+ return this.last
49
+ }
50
+ }
51
+
52
+ @Test
53
+ func appSpecAssembleWiresApp(): Unit {
54
+ let prov = AppSpecProbeProvider()
55
+ registerHost(prov)
56
+ let box = WireBox()
57
+ let spec = AppSpec()
58
+ .page(PageSource.Inline("<p/>"))
59
+ .window(WindowConfig("spec窗", 640, 480))
60
+ .plugin(StorePlugin())
61
+ .wire({ app =>
62
+ box.on = true
63
+ })
64
+ let app = assemble(spec)
65
+ @Expect(box.on, true)
66
+ @Expect(app.hostOf("main").isSome(), true)
67
+ @Expect(prov.last.label(), "main")
68
+ }
69
+
70
+ // ---- 批次 3-PR3:load(source) 收敛——装配层把 PageSource 原样交给宿主,resolver 随装配进宿主 ----
71
+
72
+ @Test
73
+ func fakeHostLoadDispatchesPageSource(): Unit {
74
+ let h = FakeHost("main")
75
+ h.load(PageSource.Inline("<p>hi</p>"))
76
+ @Expect(h.shownHtml, "<p>hi</p>")
77
+ @Expect(h.startedUrl, "")
78
+ h.load(PageSource.Url("https://example.com"))
79
+ @Expect(h.startedUrl, "https://example.com")
80
+ h.load(PageSource.DevServer("http://127.0.0.1:5173"))
81
+ @Expect(h.startedUrl, "http://127.0.0.1:5173")
82
+ h.load(PageSource.Asset("ui/index.html"))
83
+ @Expect(h.loadedSource, "asset:ui/index.html")
84
+ }
85
+
86
+ /** run 路径探针:优先级压过既有注册表里的桩,保证本用例拿到的是自己造的宿主 */
87
+ class RunProbeProvider <: HostProvider {
88
+ public var last: FakeHost = FakeHost("seed")
89
+
90
+ public func priority(target: TargetInfo): Int64 {
91
+ return 200
92
+ }
93
+
94
+ public func create(label: String, target: TargetInfo): WebViewHost {
95
+ this.last = FakeHost(label)
96
+ return this.last
97
+ }
98
+ }
99
+
100
+ /**
101
+ * run() 路径:resolver 注入与页面来源交接发生在同一次装配(runWith)里。
102
+ * 宿主用 FakeHost 桩(不建窗口);run() 放后台线程,观测到交接后置 quitNow 让它返回。
103
+ */
104
+ @Test
105
+ func runHandsResolverAndSourceToHost(): Unit {
106
+ let prov = RunProbeProvider()
107
+ let provIdx = registerHost(prov)
108
+ let spec = AppSpec()
109
+ .page(PageSource.Asset("ui/index.html"))
110
+ .window(WindowConfig("t", 640, 480))
111
+ .assets(StubResolver(HashMap()))
112
+ spawn {
113
+ let app = assemble(spec)
114
+ app.run(spec.pageSource)
115
+ }
116
+ var polls = 0
117
+ while (polls < 1000 && prov.last.loadedSource != "asset:ui/index.html") {
118
+ sleep(Duration.millisecond * 2)
119
+ polls += 1
120
+ }
121
+ @Expect(prov.last.loadedSource, "asset:ui/index.html")
122
+ @Expect(prov.last.resolverInjected.isSome(), true)
123
+ prov.last.quitNow = true
124
+ // 探针宿主清出全局注册表,免得压过后续用例自己的桩
125
+ unregisterHost(provIdx)
126
+ }