cj-tauri 2.0.0 → 2.1.1

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 (157) hide show
  1. package/README.md +206 -139
  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 +643 -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 +638 -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 +3745 -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/src/api_system.cj +67 -67
  77. package/src/app.cj +335 -969
  78. package/src/app_events.cj +166 -0
  79. package/src/app_host_ops.cj +287 -0
  80. package/src/app_run.cj +283 -0
  81. package/src/app_spec.cj +76 -76
  82. package/src/arkweb_menu.cj +216 -0
  83. package/src/arkweb_script.cj +142 -142
  84. package/src/capability.cj +409 -409
  85. package/src/clipboard_bridge.cj +178 -0
  86. package/src/dialog_spec.cj +79 -0
  87. package/src/dragdrop.cj +26 -26
  88. package/src/embedded_assets.cj +98 -98
  89. package/src/host.cj +752 -607
  90. package/src/host_arkweb.cj +924 -778
  91. package/src/host_webkit.cj +663 -570
  92. package/src/host_webview2.cj +663 -570
  93. package/src/hotkey.cj +66 -66
  94. package/src/ipc_hub.cj +401 -337
  95. package/src/ipc_message.cj +286 -286
  96. package/src/launch.cj +62 -62
  97. package/src/logging.cj +127 -127
  98. package/src/menu.cj +197 -197
  99. package/src/platform/platform_app_context.cj +77 -77
  100. package/src/platform/platform_assets.cj +33 -33
  101. package/src/platform/platform_discover.cj +176 -176
  102. package/src/platform/platform_env.cj +29 -29
  103. package/src/platform/platform_feature.cj +31 -31
  104. package/src/platform/platform_page_source.cj +87 -87
  105. package/src/platform/platform_target_info.cj +32 -32
  106. package/src/platform_host_provider.cj +87 -87
  107. package/src/plugin.cj +333 -333
  108. package/src/plugins/clipboard/plugin_clipboard.cj +152 -152
  109. package/src/plugins/dialog/plugin_dialog.cj +209 -209
  110. package/src/plugins/fs/plugin_fs.cj +105 -1
  111. package/src/plugins/http/http_backend.cj +131 -0
  112. package/src/plugins/http/http_desktop.cj +195 -0
  113. package/src/plugins/http/http_ohos.cj +204 -0
  114. package/src/plugins/http/plugin_http.cj +310 -459
  115. package/src/plugins/log/plugin_log.cj +105 -105
  116. package/src/plugins/menu/plugin_menu.cj +158 -158
  117. package/src/plugins/notification/plugin_notification.cj +154 -154
  118. package/src/plugins/serial/plugin_serial.cj +458 -454
  119. package/src/plugins/serial/serial_bridge.cj +194 -194
  120. package/src/plugins/serial/serial_manager_ohos.cj +92 -0
  121. package/src/plugins/serial/serial_ohos.cj +529 -0
  122. package/src/plugins/shell/plugin_shell.cj +258 -258
  123. package/src/plugins/sqlite/plugin_sqlite.cj +705 -710
  124. package/src/plugins/sqlite/sqlite_ffi.cj +206 -211
  125. package/src/plugins/ws/plugin_ws.cj +488 -470
  126. package/src/process_hidden.cj +174 -174
  127. package/src/scope.cj +391 -391
  128. package/src/shell_event.cj +69 -69
  129. package/src/tests/app_spec_test.cj +130 -127
  130. package/src/tests/capability_test.cj +243 -243
  131. package/src/tests/dialog_spec_test.cj +73 -0
  132. package/src/tests/fake_host.cj +201 -201
  133. package/src/tests/ipc_hub_test.cj +563 -487
  134. package/src/tests/logging_test.cj +62 -62
  135. package/src/tests/platform_contract_test.cj +264 -251
  136. package/src/tests/plugin_clipboard_test.cj +222 -222
  137. package/src/tests/plugin_dialog_test.cj +237 -237
  138. package/src/tests/plugin_fs_test.cj +207 -0
  139. package/src/tests/plugin_http_test.cj +124 -124
  140. package/src/tests/plugin_notification_test.cj +205 -205
  141. package/src/tests/plugin_serial_test.cj +48 -0
  142. package/src/tests/plugin_test.cj +605 -585
  143. package/src/tests/plugin_ws_test.cj +40 -40
  144. package/src/tests/process_hidden_test.cj +129 -129
  145. package/src/tests/scope_test.cj +350 -350
  146. package/src/tests/system_host_test.cj +99 -81
  147. package/src/tests/urlutil_test.cj +53 -53
  148. package/src/tests/version_test.cj +75 -75
  149. package/src/tests/window_registry_test.cj +123 -123
  150. package/src/tray.cj +90 -90
  151. package/src/urlutil.cj +159 -159
  152. package/src/version.cj +44 -44
  153. package/src/window.cj +51 -51
  154. package/src/window_registry.cj +116 -116
  155. package/prebuilt/win32-x64/cj-tauri.exe +0 -0
  156. package/src/plugins/serial/serial_ohos_stub.cj +0 -61
  157. 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,130 @@
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
+ let _ = target
44
+ return 100
45
+ }
46
+
47
+ public func create(label: String, target: TargetInfo): WebViewHost {
48
+ let _ = target
49
+ this.last = FakeHost(label)
50
+ return this.last
51
+ }
52
+ }
53
+
54
+ @Test
55
+ func appSpecAssembleWiresApp(): Unit {
56
+ let prov = AppSpecProbeProvider()
57
+ registerHost(prov)
58
+ let box = WireBox()
59
+ let spec = AppSpec()
60
+ .page(PageSource.Inline("<p/>"))
61
+ .window(WindowConfig("spec窗", 640, 480))
62
+ .plugin(StorePlugin())
63
+ .wire({ _ =>
64
+ box.on = true
65
+ })
66
+ let app = assemble(spec)
67
+ @Expect(box.on, true)
68
+ @Expect(app.hostOf("main").isSome(), true)
69
+ @Expect(prov.last.label(), "main")
70
+ }
71
+
72
+ // ---- 批次 3-PR3:load(source) 收敛——装配层把 PageSource 原样交给宿主,resolver 随装配进宿主 ----
73
+
74
+ @Test
75
+ func fakeHostLoadDispatchesPageSource(): Unit {
76
+ let h = FakeHost("main")
77
+ h.load(PageSource.Inline("<p>hi</p>"))
78
+ @Expect(h.shownHtml, "<p>hi</p>")
79
+ @Expect(h.startedUrl, "")
80
+ h.load(PageSource.Url("https://example.com"))
81
+ @Expect(h.startedUrl, "https://example.com")
82
+ h.load(PageSource.DevServer("http://127.0.0.1:5173"))
83
+ @Expect(h.startedUrl, "http://127.0.0.1:5173")
84
+ h.load(PageSource.Asset("ui/index.html"))
85
+ @Expect(h.loadedSource, "asset:ui/index.html")
86
+ }
87
+
88
+ /** run 路径探针:优先级压过既有注册表里的桩,保证本用例拿到的是自己造的宿主 */
89
+ class RunProbeProvider <: HostProvider {
90
+ public var last: FakeHost = FakeHost("seed")
91
+
92
+ public func priority(target: TargetInfo): Int64 {
93
+ let _ = target
94
+ return 200
95
+ }
96
+
97
+ public func create(label: String, target: TargetInfo): WebViewHost {
98
+ let _ = target
99
+ this.last = FakeHost(label)
100
+ return this.last
101
+ }
102
+ }
103
+
104
+ /**
105
+ * run() 路径:resolver 注入与页面来源交接发生在同一次装配(runWith)里。
106
+ * 宿主用 FakeHost 桩(不建窗口);run() 放后台线程,观测到交接后置 quitNow 让它返回。
107
+ */
108
+ @Test
109
+ func runHandsResolverAndSourceToHost(): Unit {
110
+ let prov = RunProbeProvider()
111
+ let provIdx = registerHost(prov)
112
+ let spec = AppSpec()
113
+ .page(PageSource.Asset("ui/index.html"))
114
+ .window(WindowConfig("t", 640, 480))
115
+ .assets(StubResolver(HashMap()))
116
+ spawn {
117
+ let app = assemble(spec)
118
+ app.run(spec.pageSource)
119
+ }
120
+ var polls = 0
121
+ while (polls < 1000 && prov.last.loadedSource != "asset:ui/index.html") {
122
+ sleep(Duration.millisecond * 2)
123
+ polls += 1
124
+ }
125
+ @Expect(prov.last.loadedSource, "asset:ui/index.html")
126
+ @Expect(prov.last.resolverInjected.isSome(), true)
127
+ prov.last.quitNow = true
128
+ // 探针宿主清出全局注册表,免得压过后续用例自己的桩
129
+ unregisterHost(provIdx)
130
+ }