catalyst-core 0.3.0-beta.5 → 0.3.0-canary.2

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 (316) hide show
  1. package/changelog.md +185 -71
  2. package/dist/errors/generateDocs.js +100 -0
  3. package/dist/errors/index.js +243 -0
  4. package/dist/errors/registry.js +483 -0
  5. package/dist/errors-index.json +506 -0
  6. package/dist/globals.d.ts +62 -0
  7. package/dist/index.d.ts +5 -0
  8. package/dist/index.jsx +5 -8
  9. package/dist/logger.js +1 -1
  10. package/dist/native/androidProject/app/build.gradle.kts +142 -7
  11. package/dist/native/androidProject/app/src/main/AndroidManifest.xml +4 -0
  12. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +2 -2
  13. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/BarcodeDetector.kt +3 -0
  14. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/CameraSessionManager.kt +3 -0
  15. package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +43 -8
  16. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/BridgeUtilsTest.kt +181 -1
  17. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CameraUtilsTest.kt +156 -236
  18. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileSizeRouterUtilsTest.kt +471 -0
  19. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileUtilsTest.kt +222 -226
  20. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FrameworkServerUtilsTest.kt +300 -0
  21. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/IntentUtilsTest.kt +244 -0
  22. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/MetricsMonitorTest.kt +359 -0
  23. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NativeBridgeTest.kt +108 -63
  24. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NotificationUtilsTest.kt +329 -385
  25. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/OfflineCacheServiceTest.kt +302 -0
  26. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/PerfEventBufferTest.kt +133 -0
  27. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/UtilsKtTest.kt +152 -0
  28. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/WebCacheManagerTest.kt +145 -324
  29. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/BarcodeDetectorTest.kt +132 -0
  30. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/HoldControllerTest.kt +155 -0
  31. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/TorchControllerTest.kt +150 -0
  32. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/VideoStreamStateMachineTest.kt +120 -0
  33. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/VideoStreamStateTest.kt +62 -0
  34. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/ViewfinderMapperTest.kt +143 -0
  35. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/ZoomControllerTest.kt +421 -0
  36. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/design/DesignTokensTest.kt +207 -0
  37. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/plugins/PluginBridgeTest.kt +368 -0
  38. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/FridaDetectorTest.kt +193 -0
  39. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/SecurityCheckManagerTest.kt +260 -0
  40. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/SecurityCheckSchedulerTest.kt +166 -0
  41. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/DeviceInfoUtilsTest.kt +167 -0
  42. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/DownloadUtilsTest.kt +197 -0
  43. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/NetworkMonitorTest.kt +271 -0
  44. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/PushNotificationUtilsTest.kt +119 -0
  45. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/SafeAreaUtilsTest.kt +108 -0
  46. package/dist/native/androidProject/build.gradle.kts +1 -0
  47. package/dist/native/androidProject/gradle/libs.versions.toml +26 -0
  48. package/dist/native/androidProject/settings.gradle.kts +11 -8
  49. package/dist/native/androidSetup.js +2 -2
  50. package/dist/native/bridge/WebBridge.d.ts +98 -0
  51. package/dist/native/bridge/WebBridge.js +40 -6
  52. package/dist/native/bridge/constants/NativeInterfaces.d.ts +391 -0
  53. package/dist/native/bridge/errors.d.ts +67 -0
  54. package/dist/native/bridge/errors.js +66 -151
  55. package/dist/native/bridge/hooks/index.d.ts +13 -0
  56. package/dist/native/bridge/hooks/index.js +0 -6
  57. package/dist/native/bridge/hooks/useAI.d.ts +1 -0
  58. package/dist/native/bridge/hooks/useAI.js +3 -1
  59. package/dist/native/bridge/hooks/useCamera.d.ts +86 -0
  60. package/dist/native/bridge/hooks/useCamera.js +35 -6
  61. package/dist/native/bridge/hooks/useDataProtection.d.ts +17 -0
  62. package/dist/native/bridge/hooks/useDeviceInfo.d.ts +34 -0
  63. package/dist/native/bridge/hooks/useDeviceInfo.js +5 -0
  64. package/dist/native/bridge/hooks/useFilePicker.d.ts +53 -0
  65. package/dist/native/bridge/hooks/useFilePicker.js +13 -0
  66. package/dist/native/bridge/hooks/useGoogleSignIn.d.ts +12 -0
  67. package/dist/native/bridge/hooks/useHapticFeedback.d.ts +76 -0
  68. package/dist/native/bridge/hooks/useHapticFeedback.js +6 -1
  69. package/dist/native/bridge/hooks/useIntent.d.ts +33 -0
  70. package/dist/native/bridge/hooks/useIntent.js +2 -0
  71. package/dist/native/bridge/hooks/useNativeTransition.d.ts +53 -0
  72. package/dist/native/bridge/hooks/useNativeTransition.js +12 -5
  73. package/dist/native/bridge/hooks/useNetworkStatus.d.ts +22 -0
  74. package/dist/native/bridge/hooks/useNetworkStatus.js +7 -0
  75. package/dist/native/bridge/hooks/useNotification.d.ts +83 -0
  76. package/dist/native/bridge/hooks/useNotification.js +81 -34
  77. package/dist/native/bridge/hooks/useSafeArea.d.ts +12 -0
  78. package/dist/native/bridge/hooks/useSafeArea.js +8 -0
  79. package/dist/native/bridge/hooks/useVideoStream.d.ts +67 -0
  80. package/dist/native/bridge/hooks/useVideoStream.js +38 -52
  81. package/dist/native/bridge/hooks/utils.d.ts +4 -0
  82. package/dist/native/bridge/hooks.d.ts +1 -0
  83. package/dist/native/bridge/perf/collectors/ApiCollector.d.ts +30 -0
  84. package/dist/native/bridge/perf/collectors/ApiCollector.js +4 -0
  85. package/dist/native/bridge/perf/collectors/CacheCollector.d.ts +43 -0
  86. package/dist/native/bridge/perf/collectors/CacheCollector.js +4 -0
  87. package/dist/native/bridge/perf/collectors/HookCollector.d.ts +37 -0
  88. package/dist/native/bridge/perf/collectors/HookCollector.js +4 -0
  89. package/dist/native/bridge/perf/collectors/InsightsCollector.d.ts +103 -0
  90. package/dist/native/bridge/perf/collectors/InsightsCollector.js +4 -0
  91. package/dist/native/bridge/perf/collectors/InteractionCollector.d.ts +53 -0
  92. package/dist/native/bridge/perf/collectors/InteractionCollector.js +4 -0
  93. package/dist/native/bridge/perf/collectors/KeyboardCollector.d.ts +30 -0
  94. package/dist/native/bridge/perf/collectors/KeyboardCollector.js +4 -0
  95. package/dist/native/bridge/perf/collectors/NavigationCollector.d.ts +37 -0
  96. package/dist/native/bridge/perf/collectors/NavigationCollector.js +4 -0
  97. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.d.ts +30 -0
  98. package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +4 -0
  99. package/dist/native/bridge/perf/collectors/RenderCollector.d.ts +47 -0
  100. package/dist/native/bridge/perf/collectors/RenderCollector.js +8 -0
  101. package/dist/native/bridge/perf/collectors/ScrollCollector.d.ts +29 -0
  102. package/dist/native/bridge/perf/collectors/ScrollCollector.js +4 -0
  103. package/dist/native/bridge/perf/collectors/ThreadCollector.d.ts +31 -0
  104. package/dist/native/bridge/perf/collectors/ThreadCollector.js +4 -0
  105. package/dist/native/bridge/perf/core/constants.d.ts +76 -0
  106. package/dist/native/bridge/perf/core/measure.d.ts +39 -0
  107. package/dist/native/bridge/perf/core/measure.js +4 -0
  108. package/dist/native/bridge/perf/core/session.d.ts +49 -0
  109. package/dist/native/bridge/perf/core/session.js +4 -0
  110. package/dist/native/bridge/perf/core/store.d.ts +37 -0
  111. package/dist/native/bridge/perf/core/store.js +4 -0
  112. package/dist/native/bridge/perf/core/summary.d.ts +60 -0
  113. package/dist/native/bridge/perf/core/traceExport.d.ts +10 -0
  114. package/dist/native/bridge/perf/core/waterfalls.d.ts +2 -0
  115. package/dist/native/bridge/perf/index.d.ts +63 -0
  116. package/dist/native/bridge/perf/index.js +4 -0
  117. package/dist/native/bridge/safeArea.d.ts +11 -0
  118. package/dist/native/bridge/safeArea.js +2 -0
  119. package/dist/native/bridge/useBaseHook.d.ts +137 -0
  120. package/dist/native/bridge/useBaseHook.js +34 -4
  121. package/dist/native/bridge/utils/CameraUtils.d.ts +94 -0
  122. package/dist/native/bridge/utils/CameraUtils.js +4 -0
  123. package/dist/native/bridge/utils/FileObjectConverter.d.ts +36 -0
  124. package/dist/native/bridge/utils/NativeBridge.d.ts +270 -0
  125. package/dist/native/bridge/utils/NativeBridge.js +10 -0
  126. package/dist/native/buildAndroid/index.js +1 -1
  127. package/dist/native/buildAppAndroid.js +9 -0
  128. package/dist/native/buildAppIos.js +16 -1
  129. package/dist/native/buildErrorFormat.js +57 -0
  130. package/dist/native/buildIos/config.js +15 -1
  131. package/dist/native/buildIos/index.js +1 -1
  132. package/dist/native/buildIos/plugins.js +10 -1
  133. package/dist/native/iosnativeWebView/CoreLogic/Package.swift +36 -0
  134. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Camera/VideoStreamState.swift +1 -1
  135. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Camera/VideoStreamStateMachine.swift +6 -4
  136. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/BootTimingUtility.swift +3 -3
  137. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/FrameworkServerUtils.swift +14 -9
  138. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NetworkMonitor.swift +11 -9
  139. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NotificationHandlerProvider.swift +1 -1
  140. package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/URLWhitelistManager.swift +7 -7
  141. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/AnyCodableTests.swift +154 -0
  142. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/BootTimingUtilityTests.swift +23 -5
  143. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/CatalystConstantsTests.swift +192 -0
  144. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/ConfigMappingTests.swift +1 -1
  145. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/FrameworkServerUtilsErrorPathTests.swift +106 -0
  146. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/FrameworkServerUtilsLoopbackTests.swift +243 -0
  147. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/FrameworkServerUtilsTests.swift +1 -1
  148. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/NetworkMonitorTests.swift +119 -0
  149. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/NotificationHandlerProviderTests.swift +59 -0
  150. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/NotificationHandlerTests.swift +1 -1
  151. package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/URLWhitelistManagerTests.swift +1 -1
  152. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/VideoStreamStateMachineTests.swift +171 -0
  153. package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/VideoStreamStateTests.swift +91 -0
  154. package/dist/native/iosnativeWebView/Package.swift +10 -9
  155. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/LocalNotificationHandler.swift +7 -2
  156. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/NotificationManager.swift +1 -2
  157. package/dist/native/iosnativeWebView/Sources/CatalystNotifications/PushNotificationHandler.swift +5 -4
  158. package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraSessionManager.swift +1 -0
  159. package/dist/native/iosnativeWebView/Sources/Core/Camera/HoldController.swift +1 -0
  160. package/dist/native/iosnativeWebView/Sources/Core/Camera/NativeCameraManager.swift +6 -5
  161. package/dist/native/iosnativeWebView/Sources/Core/Camera/ZoomController.swift +1 -0
  162. package/dist/native/iosnativeWebView/Sources/Core/Plugins/PluginBridge.swift +1 -0
  163. package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +31 -30
  164. package/dist/native/iosnativeWebView/Sources/Core/Utils/CameraTransportUtils.swift +2 -1
  165. package/dist/native/iosnativeWebView/Sources/Core/Utils/DeviceInfoUtils.swift +1 -1
  166. package/dist/native/iosnativeWebView/Sources/Core/Utils/FilePickerHandler.swift +22 -19
  167. package/dist/native/iosnativeWebView/Sources/Core/Utils/ImageHandler.swift +40 -39
  168. package/dist/native/iosnativeWebView/Sources/Core/Utils/PerfEventBuffer.swift +1 -0
  169. package/dist/native/iosnativeWebView/Sources/Core/Utils/ResourceURLProtocol.swift +15 -15
  170. package/dist/native/iosnativeWebView/Sources/Core/Utils/WebKitConfig.swift +4 -3
  171. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeCommandHandler.swift +6 -1
  172. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeDelegateHandler.swift +1 -0
  173. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeFileHandler.swift +2 -1
  174. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeJavaScriptInterface.swift +2 -1
  175. package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +3 -5
  176. package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +20 -13
  177. package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashView.swift +6 -5
  178. package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashViewModel.swift +14 -13
  179. package/dist/native/iosnativeWebView/Sources/Core/WebView/TransitionManager.swift +3 -3
  180. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +9 -8
  181. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewModel.swift +7 -6
  182. package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +24 -25
  183. package/dist/native/iosnativeWebView/iosnativeWebView/AppDelegate.swift +6 -1
  184. package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +1 -0
  185. package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebViewApp.swift +4 -0
  186. package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +2 -22
  187. package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeMessageValidatorTests.swift +1 -0
  188. package/dist/native/iosnativeWebView/iosnativeWebViewTests/CacheManagerTests.swift +1 -0
  189. package/dist/native/iosnativeWebView/iosnativeWebViewTests/FilePickerHandlerTests.swift +2 -1
  190. package/dist/native/iosnativeWebView/iosnativeWebViewTests/NativeBridgeTests.swift +15 -9
  191. package/dist/native/iosnativeWebView/iosnativeWebViewTests/WebViewTests.swift +1 -0
  192. package/dist/native/setupEmulatorIos.js +2 -2
  193. package/dist/native/terminalProgress.js +17 -17
  194. package/dist/router/ClientRouter.d.ts +8 -0
  195. package/dist/router/ClientRouter.js +13 -11
  196. package/dist/router/ServerRouter.d.ts +13 -0
  197. package/dist/router/ServerRouter.js +19 -7
  198. package/dist/scripts/build.js +14 -2
  199. package/dist/scripts/loadEnvironmentVariables.js +3 -2
  200. package/dist/scripts/plugins.js +1 -1
  201. package/dist/scripts/preServerInit.js +49 -3
  202. package/dist/scripts/preflight.js +111 -0
  203. package/dist/scripts/scriptUtils.js +46 -1
  204. package/dist/scripts/serve.js +7 -3
  205. package/dist/scripts/start.js +8 -3
  206. package/dist/server/expressServer.d.ts +1 -0
  207. package/dist/server/expressServer.js +247 -271
  208. package/dist/server/manifestCache.d.ts +2 -0
  209. package/dist/server/manifestCache.js +25 -33
  210. package/dist/server/renderer/ChunkExtractor.d.ts +44 -0
  211. package/dist/server/renderer/ChunkExtractor.js +113 -125
  212. package/dist/server/renderer/document/Body.d.ts +21 -0
  213. package/dist/server/renderer/document/Body.jsx +45 -45
  214. package/dist/server/renderer/document/FastRefresh.d.ts +3 -0
  215. package/dist/server/renderer/document/FastRefresh.jsx +7 -13
  216. package/dist/server/renderer/document/Head.d.ts +20 -0
  217. package/dist/server/renderer/document/Head.jsx +32 -32
  218. package/dist/server/renderer/document/index.d.ts +2 -0
  219. package/dist/server/renderer/document/index.jsx +2 -2
  220. package/dist/server/renderer/extract.d.ts +67 -0
  221. package/dist/server/renderer/extract.js +92 -89
  222. package/dist/server/renderer/handler.d.ts +2 -0
  223. package/dist/server/renderer/handler.jsx +401 -404
  224. package/dist/server/renderer/index.d.ts +2 -0
  225. package/dist/server/renderer/index.js +5 -8
  226. package/dist/server/renderer/render.d.ts +35 -0
  227. package/dist/server/renderer/render.js +25 -32
  228. package/dist/server/utils/botDetectionMiddleware.d.ts +1 -0
  229. package/dist/server/utils/botDetectionMiddleware.js +33 -23
  230. package/dist/server/utils/cjsRequire.d.ts +8 -0
  231. package/dist/server/utils/cjsRequire.js +2 -2
  232. package/dist/server/utils/userAgentUtil.d.ts +7 -0
  233. package/dist/server/utils/userAgentUtil.js +68 -71
  234. package/dist/server/utils/validator.d.ts +31 -0
  235. package/dist/server/utils/validator.js +133 -158
  236. package/dist/vite/inject-cache-key-plugin.js +3 -27
  237. package/dist/vite/moduleAliases.js +36 -0
  238. package/dist/vite/node-loader.mjs +2 -29
  239. package/dist/vite/resolveDevServerConfig.js +4 -1
  240. package/dist/vite/vite.config.js +2 -29
  241. package/dist/web-router/components/MetaTag.d.ts +4 -0
  242. package/dist/web-router/components/MetaTag.jsx +26 -28
  243. package/dist/web-router/components/MetaTag.test.jsx +100 -0
  244. package/dist/web-router/components/RouterDataProvider.d.ts +96 -0
  245. package/dist/web-router/components/RouterDataProvider.jsx +260 -259
  246. package/dist/web-router/components/RouterDataProvider.server.test.jsx +98 -0
  247. package/dist/web-router/components/RouterDataProvider.test.jsx +310 -0
  248. package/dist/web-router/components/Split.d.ts +74 -0
  249. package/dist/web-router/components/Split.jsx +147 -156
  250. package/dist/web-router/components/Split.server.test.jsx +52 -0
  251. package/dist/web-router/components/Split.test.jsx +167 -0
  252. package/dist/web-router/components/SplitInview.d.ts +22 -0
  253. package/dist/web-router/components/SplitInview.jsx +78 -89
  254. package/dist/web-router/components/SplitInview.test.jsx +112 -0
  255. package/dist/web-router/components/SsrRequestContext.d.ts +20 -0
  256. package/dist/web-router/components/SsrRequestContext.jsx +17 -7
  257. package/dist/web-router/components/SsrRequestContext.test.jsx +53 -0
  258. package/dist/web-router/context.d.ts +15 -0
  259. package/dist/web-router/context.jsx +5 -5
  260. package/dist/web-router/context.test.jsx +10 -0
  261. package/dist/web-router/utils/metaDataUtils.d.ts +24 -0
  262. package/dist/web-router/utils/metaDataUtils.jsx +86 -86
  263. package/dist/web-router/utils/metaDataUtils.test.jsx +188 -0
  264. package/dist/web-router/vitest.setup.js +12 -0
  265. package/mcp_v2/knowledge-base.json +115 -17
  266. package/mcp_v2/mcp.js +141 -3
  267. package/mcp_v2/package-lock.json +2031 -0
  268. package/mcp_v2/package.json +13 -2
  269. package/mcp_v2/scripts/copyErrorsIndex.cjs +34 -0
  270. package/mcp_v2/test/errors.test.ts +123 -0
  271. package/mcp_v2/test/github.test.ts +177 -0
  272. package/mcp_v2/tools/errors.d.ts +20 -0
  273. package/mcp_v2/tools/errors.js +63 -0
  274. package/mcp_v2/tools/github.d.ts +41 -0
  275. package/mcp_v2/tools/github.js +231 -0
  276. package/mcp_v2/vitest.config.mjs +22 -0
  277. package/package.json +60 -11
  278. package/test/errors/docsDrift.test.ts +113 -0
  279. package/test/errors/index.test.ts +200 -0
  280. package/test/errors/registry.test.ts +108 -0
  281. package/test/scripts/preflight.test.ts +118 -0
  282. package/test/server/ChunkExtractor.test.ts +97 -0
  283. package/test/server/extract.test.ts +128 -0
  284. package/test/server/fixtures/template/server/document.jsx +19 -0
  285. package/test/server/fixtures/template/server/index.js +10 -0
  286. package/test/server/fixtures/template/src/js/containers/App/index.jsx +33 -0
  287. package/test/server/fixtures/template/src/js/routes/index.jsx +72 -0
  288. package/test/server/fixtures/template/src/js/routes/utils.jsx +39 -0
  289. package/test/server/fixtures/template/src/js/store/index.js +19 -0
  290. package/test/server/handler.assets.test.ts +330 -0
  291. package/test/server/handler.test.ts +208 -0
  292. package/test/server/manifestCache.test.ts +71 -0
  293. package/test/server/render.test.ts +71 -0
  294. package/test/server/scriptUtils.test.ts +103 -0
  295. package/test/server/userAgentUtil.test.ts +48 -0
  296. package/test/server/validator.test.ts +225 -0
  297. package/test/vite/resolveDevServerConfig.test.js +4 -1
  298. package/tsconfig.test.json +23 -0
  299. package/vitest.config.ts +93 -0
  300. package/dist/caching.js +0 -1
  301. package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/DeviceInfoUtilsTest.kt +0 -187
  302. package/dist/native/bridge/standardizedHooks.js +0 -611
  303. package/dist/router.js +0 -1
  304. package/dist/scripts/validator.js +0 -149
  305. package/dist/web-router/hooks.jsx +0 -72
  306. package/dist/web-router/types/components/MetaTag.d.ts +0 -2
  307. package/dist/web-router/types/components/RouterDataProvider.d.ts +0 -95
  308. package/dist/web-router/types/context.d.ts +0 -14
  309. package/dist/web-router/types/hooks.d.ts +0 -1
  310. package/dist/web-router/types/index.d.ts +0 -3
  311. package/dist/web-router/types/utils/metaDataUtils.d.ts +0 -40
  312. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Constants/CatalystConstants.swift +0 -0
  313. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Constants/ConfigConstants.swift +0 -0
  314. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Notifications/NotificationModels.swift +0 -0
  315. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Protocols/NotificationHandlerProtocol.swift +0 -0
  316. /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NullNotificationHandler.swift +0 -0
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+
3
+ const pc = require("ansis");
4
+
5
+ // src/native is a CJS-only subtree (see src/native/package.json) and cannot
6
+ // synchronously require() the ESM errors/index.js module under Node 20, so
7
+ // output-mode formatting for buildAppAndroid.js/buildAppIos.js is
8
+ // hand-rolled here rather than shared with errors/index.js's box/formatVerbose/
9
+ // formatDebug — those two files are the only callers, and both native build
10
+ // failure sites need the same minimal shape.
11
+
12
+ function resolveOutputMode() {
13
+ if (process.argv.includes("--debug")) return "debug";
14
+ if (process.argv.includes("--verbose")) return "verbose";
15
+ return "default";
16
+ }
17
+ function box(bodyLines) {
18
+ const width = 70;
19
+ const top = pc.red(`┌${"─".repeat(width)}`);
20
+ const bottom = pc.red(`└${"─".repeat(width)}`);
21
+ const body = bodyLines.map(line => line === "" ? pc.red("│") : `${pc.red("│")} ${line}`);
22
+ return [top, ...body, bottom].join("\n");
23
+ }
24
+
25
+ /**
26
+ * Format a native build failure for terminal output. `code`/`category`
27
+ * identify the generic upstream-toolchain wrapper (ANDROID-000/IOS-000 —
28
+ * see errors/ANDROID/ANDROID-000.md and errors/IOS/IOS-000.md); the
29
+ * upstream message is always shown verbatim, never reinterpreted.
30
+ */
31
+ function formatBuildError({
32
+ code,
33
+ category,
34
+ upstreamName,
35
+ error
36
+ }) {
37
+ // Xcode/Gradle tooling (via execSync and friends) can reject with a plain
38
+ // string, null, or undefined instead of an Error instance — normalize
39
+ // before touching .message/.stack so a non-Error failure still prints
40
+ // something useful instead of throwing or rendering "undefined".
41
+ const message = error instanceof Error ? error.message : String(error);
42
+ const stack = error instanceof Error ? error.stack || "(no stack available)" : "(no stack available)";
43
+ const mode = resolveOutputMode();
44
+ if (mode === "default") {
45
+ return `[${code}] Build failed (upstream: ${upstreamName})\n→ ${message}`;
46
+ }
47
+ const lines = [`❌ Build failed (upstream: ${upstreamName})`, `Code: ${code}`, `Category: ${category}`, `Time: ${new Date().toISOString()}`, "", `Problem: ${message}`];
48
+ if (mode === "debug") {
49
+ lines.push("", "Environment:", ` node: ${process.version}`, ` platform: ${process.platform}`);
50
+ lines.push("", "Stack trace:", ...stack.split("\n").map(l => ` ${l}`));
51
+ }
52
+ return box(lines);
53
+ }
54
+ module.exports = {
55
+ resolveOutputMode,
56
+ formatBuildError
57
+ };
@@ -121,15 +121,29 @@ module.exports = function createConfigPhase(ctx) {
121
121
  async function generateConfigConstants() {
122
122
  progress.start("config");
123
123
  try {
124
+ // CatalystCoreLogic (#432): ConfigConstants moved into the nested
125
+ // CoreLogic/ package so it can be consumed by swift-test-only code
126
+ // without pulling in UIKit. This generator must write to the new
127
+ // location, not the old Sources/Core/Constants/ path.
124
128
  const appConfigPath = path.join(PROJECT_DIR,
125
129
  // nosemgrep: javascript.lang.security.audit.path-traversal.path-join-resolve-traversal.path-join-resolve-traversal - PROJECT_DIR is the fixed generated iOS project directory.
126
- "Sources", "Core", "Constants", "ConfigConstants.swift");
130
+ "CoreLogic", "Sources", "CatalystCoreLogic", "Constants", "ConfigConstants.swift");
127
131
  const appConfigDir = path.dirname(appConfigPath);
128
132
  if (!fs.existsSync(appConfigDir)) fs.mkdirSync(appConfigDir, {
129
133
  recursive: true
130
134
  });
135
+
136
+ // CatalystCoreLogic's Package.swift declares platforms: [.iOS(.v17),
137
+ // .macOS(.v13)] so `swift test` can run this file on plain macOS (no
138
+ // simulator). UIKit doesn't exist there, so CGFloat needs the same
139
+ // canImport guard already used in the checked-in CatalystConstants.swift.
131
140
  let configContent = `// This file is auto-generated. Do not edit.
132
141
  import Foundation
142
+ #if canImport(UIKit)
143
+ import UIKit
144
+ #elseif canImport(CoreGraphics)
145
+ import CoreGraphics
146
+ #endif
133
147
 
134
148
  public enum ConfigConstants {
135
149
  public static let url = "${url}"`;
@@ -6,7 +6,7 @@ const {
6
6
  } = require("child_process");
7
7
  const fs = require("fs");
8
8
  const path = require("path");
9
- const TerminalProgress = require("../TerminalProgress.js").default;
9
+ const TerminalProgress = require("../terminalProgress.js").default;
10
10
  const createConfigPhase = require("./config.js");
11
11
  const createPluginsPhase = require("./plugins.js");
12
12
  const createAssetsPhase = require("./assets.js");
@@ -158,15 +158,23 @@ import PackageDescription
158
158
 
159
159
  let package = Package(
160
160
  name: "iosnativeWebView",
161
- platforms: [.iOS(.v17)],
161
+ platforms: [.iOS(.v17), .macOS(.v13)],
162
162
  products: [
163
163
  .library(name: "CatalystCore", targets: ["CatalystCore"])`;
164
164
  if (isNotificationsEnabled) {
165
165
  packageContent += `,\n .library(name: "CatalystNotifications", targets: ["CatalystNotifications"])`;
166
166
  }
167
+
168
+ // CatalystCoreLogic (#432) is a local nested package (CoreLogic/), not a
169
+ // remote dependency, so it has no `url` and cannot flow through
170
+ // mergePackageDependency/formatSwiftPackageEntries (both keyed on url).
171
+ // Emitted as a literal .package(path:) entry instead. Its own
172
+ // Package.swift is static/checked-in (no config-dependent content), so
173
+ // it is never itself regenerated here.
167
174
  packageContent += `
168
175
  ],
169
176
  dependencies: [
177
+ .package(path: "CoreLogic"),
170
178
  ${formatSwiftPackageEntries(packageDependencies).join(",\n")}
171
179
  ],
172
180
  targets: [
@@ -175,6 +183,7 @@ ${formatSwiftPackageEntries(packageDependencies).join(",\n")}
175
183
  .target(
176
184
  name: "CatalystCore",
177
185
  dependencies: [
186
+ .product(name: "CatalystCoreLogic", package: "CoreLogic"),
178
187
  ${formatSwiftProductEntries(coreDependencies).join(",\n")}
179
188
  ],
180
189
  path: "Sources/Core"
@@ -0,0 +1,36 @@
1
+ // swift-tools-version: 5.9
2
+ // Nested package for UIKit-free logic (#432).
3
+ // Static/checked-in — never generated. Unlike the parent Package.swift,
4
+ // this file has no config-dependent content (no plugin manifests, no
5
+ // notifications gate), so buildIos/plugins.js does not regenerate it.
6
+ //
7
+ // Kept as a genuinely separate SPM package (not a target inside the parent
8
+ // package) because `swift test`/`swift build` at the parent package root
9
+ // always resolves and builds every target in the graph regardless of
10
+ // --target/--filter scoping — verified directly: with CatalystCoreLogic as
11
+ // a target of the parent package, `swift test --filter
12
+ // BootTimingUtilityTests` still compiled CatalystCore and failed on
13
+ // `import UIKit`. A separate package with its own root sidesteps this:
14
+ // `swift test --package-path CoreLogic` only resolves this package's own
15
+ // graph (zero dependencies), never touches the parent package or its
16
+ // UIKit/GoogleSignIn-importing files.
17
+ import PackageDescription
18
+
19
+ let package = Package(
20
+ name: "CatalystCoreLogic",
21
+ platforms: [.iOS(.v17), .macOS(.v13)],
22
+ products: [
23
+ .library(name: "CatalystCoreLogic", targets: ["CatalystCoreLogic"])
24
+ ],
25
+ targets: [
26
+ .target(
27
+ name: "CatalystCoreLogic",
28
+ path: "Sources/CatalystCoreLogic"
29
+ ),
30
+ .testTarget(
31
+ name: "CatalystCoreLogicTests",
32
+ dependencies: ["CatalystCoreLogic"],
33
+ path: "Tests/CatalystCoreLogicTests"
34
+ )
35
+ ]
36
+ )
@@ -15,7 +15,7 @@ import Foundation
15
15
  // HOLD → STOPPING (stop() called during hold)
16
16
  // STOPPING → IDLE (session torn down)
17
17
 
18
- enum VideoStreamState {
18
+ public enum VideoStreamState {
19
19
  case idle
20
20
  case starting
21
21
  case streaming
@@ -8,19 +8,21 @@ private final class WeakListenerBox {
8
8
  init(_ value: VideoStreamStateListener) { self.value = value }
9
9
  }
10
10
 
11
- class VideoStreamStateMachine {
11
+ public class VideoStreamStateMachine {
12
12
 
13
13
  private var _state: VideoStreamState = .idle
14
14
  private var listeners: [WeakListenerBox] = []
15
15
  private let lock = NSLock()
16
16
 
17
- var state: VideoStreamState {
17
+ public init() {}
18
+
19
+ public var state: VideoStreamState {
18
20
  lock.lock()
19
21
  defer { lock.unlock() }
20
22
  return _state
21
23
  }
22
24
 
23
- var isActive: Bool {
25
+ public var isActive: Bool {
24
26
  lock.lock()
25
27
  defer { lock.unlock() }
26
28
  return _state.isActive
@@ -39,7 +41,7 @@ class VideoStreamStateMachine {
39
41
  }
40
42
 
41
43
  @discardableResult
42
- func transition(to next: VideoStreamState) -> Bool {
44
+ public func transition(to next: VideoStreamState) -> Bool {
43
45
  lock.lock()
44
46
  let prev = _state
45
47
  guard prev.canTransitionTo(next) else {
@@ -9,13 +9,13 @@
9
9
  import Foundation
10
10
  import os
11
11
 
12
- enum AppBoot {
13
- static let nativeStartMs = Int64(ProcessInfo.processInfo.systemUptime * 1000)
12
+ public enum AppBoot {
13
+ public static let nativeStartMs = Int64(ProcessInfo.processInfo.systemUptime * 1000)
14
14
  }
15
15
 
16
16
  #if DEBUG
17
17
  // Global app launch timestamp for all timing measurements
18
- let APP_LAUNCH_TIME = CFAbsoluteTimeGetCurrent()
18
+ public let APP_LAUNCH_TIME = CFAbsoluteTimeGetCurrent()
19
19
 
20
20
  private let timingLogger = Logger(subsystem: Bundle.main.bundleIdentifier ?? "com.app", category: "Timing")
21
21
 
@@ -90,7 +90,7 @@ private class ConnectionWrapper: Hashable {
90
90
 
91
91
  // MARK: - Framework Server Utils
92
92
 
93
- class FrameworkServerUtils {
93
+ public class FrameworkServerUtils {
94
94
 
95
95
  // Constants from unified configuration
96
96
  static let FRAMEWORK_PORT_RANGE_START: UInt16 = CatalystConstants.NetworkServer.portRangeStart
@@ -98,7 +98,7 @@ class FrameworkServerUtils {
98
98
  static let SESSION_TIMEOUT_SECONDS: TimeInterval = CatalystConstants.NetworkServer.sessionTimeout
99
99
 
100
100
  // Singleton instance
101
- static let shared = FrameworkServerUtils()
101
+ public static let shared = FrameworkServerUtils()
102
102
 
103
103
  // Server state
104
104
  private var listener: NWListener?
@@ -134,7 +134,7 @@ class FrameworkServerUtils {
134
134
  * Initialize and start the framework server
135
135
  * @return Boolean indicating success/failure of server startup
136
136
  */
137
- func startServer() -> Bool {
137
+ public func startServer() -> Bool {
138
138
  logger.debug("Starting framework server...")
139
139
 
140
140
  if self.isServerRunning {
@@ -283,7 +283,7 @@ class FrameworkServerUtils {
283
283
  * @param mimeType MIME type
284
284
  * @return URL where the file can be accessed, or nil if failed
285
285
  */
286
- func copyAndServeFile(originalFile: URL, fileName: String, mimeType: String) -> String? {
286
+ public func copyAndServeFile(originalFile: URL, fileName: String, mimeType: String) -> String? {
287
287
  guard isServerRunning, let cacheDirectory = cacheDirectory else {
288
288
  logger.error("Cannot copy and serve file - server not running or cache not initialized")
289
289
  return nil
@@ -328,7 +328,7 @@ class FrameworkServerUtils {
328
328
  /**
329
329
  * Check if server is running
330
330
  */
331
- func isRunning() -> Bool {
331
+ public func isRunning() -> Bool {
332
332
  return isServerRunning
333
333
  }
334
334
 
@@ -484,10 +484,15 @@ class FrameworkServerUtils {
484
484
  return nil
485
485
  }
486
486
 
487
- guard let identity = firstItem[kSecImportItemIdentity as String] as! SecIdentity? else {
487
+ guard let identityAny = firstItem[kSecImportItemIdentity as String],
488
+ CFGetTypeID(identityAny as CFTypeRef) == SecIdentityGetTypeID() else {
488
489
  logger.error("No identity found in PKCS#12 import")
489
490
  return nil
490
491
  }
492
+ let identity = identityAny as! SecIdentity // swiftlint:disable:this force_cast
493
+ // ^ SecIdentity is a CoreFoundation type with no Swift-native
494
+ // bridge; `as?` can't express it. The CFTypeID check above is the
495
+ // real guard.
491
496
 
492
497
  logger.info("✅ Successfully loaded identity from PKCS#12 file")
493
498
  return identity
@@ -502,13 +507,13 @@ class FrameworkServerUtils {
502
507
  if let identity = loadIdentityFromP12(),
503
508
  let secIdentity = sec_identity_create(identity) {
504
509
  let tlsOptions = NWProtocolTLS.Options()
505
-
510
+
506
511
  // Set the identity
507
512
  sec_protocol_options_set_local_identity(tlsOptions.securityProtocolOptions, secIdentity)
508
-
513
+
509
514
  // Set minimum TLS version
510
515
  sec_protocol_options_set_min_tls_protocol_version(tlsOptions.securityProtocolOptions, .TLSv12)
511
-
516
+
512
517
  // Add TLS to the protocol stack
513
518
  parameters.defaultProtocolStack.applicationProtocols.insert(tlsOptions, at: 0)
514
519
 
@@ -2,13 +2,13 @@ import Foundation
2
2
  import Network
3
3
  import os
4
4
 
5
- struct NetworkStatus {
6
- let isOnline: Bool
7
- let type: String?
5
+ public struct NetworkStatus {
6
+ public let isOnline: Bool
7
+ public let type: String?
8
8
  }
9
9
 
10
- final class NetworkMonitor {
11
- static let shared = NetworkMonitor()
10
+ public final class NetworkMonitor {
11
+ public static let shared = NetworkMonitor()
12
12
 
13
13
  private let monitor: NWPathMonitor
14
14
  private let monitorQueue = DispatchQueue(label: "com.catalyst.network.monitor")
@@ -32,12 +32,12 @@ final class NetworkMonitor {
32
32
  }
33
33
  }
34
34
 
35
- var currentStatus: NetworkStatus {
35
+ public var currentStatus: NetworkStatus {
36
36
  stateQueue.sync { latestStatus }
37
37
  }
38
38
 
39
39
  @discardableResult
40
- func addListener(_ listener: @escaping (NetworkStatus) -> Void) -> UUID {
40
+ public func addListener(_ listener: @escaping (NetworkStatus) -> Void) -> UUID {
41
41
  let id = UUID()
42
42
 
43
43
  stateQueue.async { [weak self] in
@@ -55,7 +55,7 @@ final class NetworkMonitor {
55
55
  return id
56
56
  }
57
57
 
58
- func removeListener(_ id: UUID) {
58
+ public func removeListener(_ id: UUID) {
59
59
  stateQueue.async { [weak self] in
60
60
  self?.listeners.removeValue(forKey: id)
61
61
  }
@@ -87,7 +87,9 @@ final class NetworkMonitor {
87
87
  }
88
88
  }
89
89
 
90
- private static func mapPathToStatus(_ path: NWPath) -> NetworkStatus {
90
+ // Internal (not private) so @testable import can exercise this pure
91
+ // mapping logic directly without needing a live NWPathMonitor callback.
92
+ static func mapPathToStatus(_ path: NWPath) -> NetworkStatus {
91
93
  let isOnline = path.status == .satisfied
92
94
 
93
95
  let type: String?
@@ -11,7 +11,7 @@ import Foundation
11
11
  /// Allows the App target to inject the real NotificationManager when notifications are enabled
12
12
  public class NotificationHandlerProvider {
13
13
  public static var shared: NotificationHandlerProtocol = NullNotificationHandler.shared
14
-
14
+
15
15
  /// Inject the notification handler at app startup
16
16
  /// Call this from the App target when notifications are enabled
17
17
  public static func inject(_ handler: NotificationHandlerProtocol) {
@@ -22,8 +22,8 @@ private struct CategorizedPatterns {
22
22
  /// - Query parameters and hash fragments are stripped before matching
23
23
  /// - Matching is case-insensitive
24
24
  /// - Ports are kept as-is (user must include port in pattern if URL has explicit port)
25
- class URLWhitelistManager {
26
- static let shared = URLWhitelistManager()
25
+ public class URLWhitelistManager {
26
+ public static let shared = URLWhitelistManager()
27
27
 
28
28
  private var accessControlEnabled: Bool = false
29
29
  private var patterns: CategorizedPatterns?
@@ -66,7 +66,7 @@ class URLWhitelistManager {
66
66
  }
67
67
 
68
68
  /// Check if URL whitelisting is enabled
69
- var isAccessControlEnabled: Bool {
69
+ public var isAccessControlEnabled: Bool {
70
70
  return syncQueue.sync { self.accessControlEnabled }
71
71
  }
72
72
 
@@ -74,7 +74,7 @@ class URLWhitelistManager {
74
74
  /// If access control is disabled, all URLs are allowed
75
75
  /// - Parameter url: The URL to check
76
76
  /// - Returns: true if URL is allowed, false otherwise
77
- func isUrlAllowed(_ url: URL) -> Bool {
77
+ public func isUrlAllowed(_ url: URL) -> Bool {
78
78
  return isUrlAllowed(url.absoluteString)
79
79
  }
80
80
 
@@ -82,7 +82,7 @@ class URLWhitelistManager {
82
82
  /// If access control is disabled, all URLs are allowed
83
83
  /// - Parameter urlString: The URL string to check
84
84
  /// - Returns: true if URL is allowed, false otherwise
85
- func isUrlAllowed(_ urlString: String) -> Bool {
85
+ public func isUrlAllowed(_ urlString: String) -> Bool {
86
86
  return syncQueue.sync {
87
87
  // If access control is disabled, allow everything
88
88
  if !self.accessControlEnabled {
@@ -139,7 +139,7 @@ class URLWhitelistManager {
139
139
  /// If access control is disabled, nothing is considered external
140
140
  /// - Parameter url: The URL to check
141
141
  /// - Returns: true if URL is external, false if it's whitelisted
142
- func isExternalDomain(_ url: URL) -> Bool {
142
+ public func isExternalDomain(_ url: URL) -> Bool {
143
143
  return isExternalDomain(url.absoluteString)
144
144
  }
145
145
 
@@ -147,7 +147,7 @@ class URLWhitelistManager {
147
147
  /// If access control is disabled, nothing is considered external
148
148
  /// - Parameter urlString: The URL string to check
149
149
  /// - Returns: true if URL is external, false if it's whitelisted
150
- func isExternalDomain(_ urlString: String) -> Bool {
150
+ public func isExternalDomain(_ urlString: String) -> Bool {
151
151
  // If access control is disabled, nothing is external
152
152
  if !self.accessControlEnabled {
153
153
  return false
@@ -0,0 +1,154 @@
1
+ import XCTest
2
+ @testable import CatalystCoreLogic
3
+
4
+ /**
5
+ * Unit tests for AnyCodable
6
+ *
7
+ * AnyCodable's encode/decode both try a fixed sequence of types (bool, int,
8
+ * double, string, array, dictionary) — these tests exercise every branch of
9
+ * both chains directly, plus the NotificationConfig.data field (the actual
10
+ * production use site) round-tripping through it.
11
+ */
12
+ final class AnyCodableTests: XCTestCase {
13
+
14
+ // MARK: - Decode: each branch of the type-sniffing chain
15
+
16
+ func testDecode_Bool() throws {
17
+ let json = "true"
18
+ let decoded = try JSONDecoder().decode(AnyCodable.self, from: Data(json.utf8))
19
+ XCTAssertEqual(decoded.value as? Bool, true)
20
+ }
21
+
22
+ func testDecode_Int() throws {
23
+ let json = "42"
24
+ let decoded = try JSONDecoder().decode(AnyCodable.self, from: Data(json.utf8))
25
+ XCTAssertEqual(decoded.value as? Int, 42)
26
+ }
27
+
28
+ func testDecode_Double() throws {
29
+ let json = "3.14"
30
+ let decoded = try JSONDecoder().decode(AnyCodable.self, from: Data(json.utf8))
31
+ XCTAssertEqual(decoded.value as? Double, 3.14)
32
+ }
33
+
34
+ func testDecode_String() throws {
35
+ let json = "\"hello\""
36
+ let decoded = try JSONDecoder().decode(AnyCodable.self, from: Data(json.utf8))
37
+ XCTAssertEqual(decoded.value as? String, "hello")
38
+ }
39
+
40
+ func testDecode_Array() throws {
41
+ let json = "[1, 2, 3]"
42
+ let decoded = try JSONDecoder().decode(AnyCodable.self, from: Data(json.utf8))
43
+ let array = try XCTUnwrap(decoded.value as? [Any])
44
+ XCTAssertEqual(array.count, 3)
45
+ XCTAssertEqual(array[0] as? Int, 1)
46
+ }
47
+
48
+ func testDecode_Dictionary() throws {
49
+ let json = #"{"key": "value", "count": 5}"#
50
+ let decoded = try JSONDecoder().decode(AnyCodable.self, from: Data(json.utf8))
51
+ let dict = try XCTUnwrap(decoded.value as? [String: Any])
52
+ XCTAssertEqual(dict["key"] as? String, "value")
53
+ XCTAssertEqual(dict["count"] as? Int, 5)
54
+ }
55
+
56
+ func testDecode_UnsupportedType_ThrowsDataCorrupted() {
57
+ // `null` isn't handled by any branch (no explicit null case), so
58
+ // decoding should fail rather than silently succeed with a wrong value.
59
+ let json = "null"
60
+ XCTAssertThrowsError(
61
+ try JSONDecoder().decode(AnyCodable.self, from: Data(json.utf8))
62
+ )
63
+ }
64
+
65
+ // MARK: - Encode: each branch of the type-checking chain
66
+
67
+ func testEncode_Bool() throws {
68
+ let encodable = AnyCodable(true)
69
+ let data = try JSONEncoder().encode(encodable)
70
+ XCTAssertEqual(String(data: data, encoding: .utf8), "true")
71
+ }
72
+
73
+ func testEncode_Int() throws {
74
+ let encodable = AnyCodable(42)
75
+ let data = try JSONEncoder().encode(encodable)
76
+ XCTAssertEqual(String(data: data, encoding: .utf8), "42")
77
+ }
78
+
79
+ func testEncode_Double() throws {
80
+ let encodable = AnyCodable(3.14)
81
+ let data = try JSONEncoder().encode(encodable)
82
+ XCTAssertEqual(String(data: data, encoding: .utf8), "3.14")
83
+ }
84
+
85
+ func testEncode_String() throws {
86
+ let encodable = AnyCodable("hello")
87
+ let data = try JSONEncoder().encode(encodable)
88
+ XCTAssertEqual(String(data: data, encoding: .utf8), "\"hello\"")
89
+ }
90
+
91
+ func testEncode_Array() throws {
92
+ let encodable = AnyCodable([1, 2, 3])
93
+ let data = try JSONEncoder().encode(encodable)
94
+ let decoded = try JSONSerialization.jsonObject(with: data) as? [Int]
95
+ XCTAssertEqual(decoded, [1, 2, 3])
96
+ }
97
+
98
+ func testEncode_Dictionary() throws {
99
+ let encodable = AnyCodable(["key": "value"])
100
+ let data = try JSONEncoder().encode(encodable)
101
+ let decoded = try JSONSerialization.jsonObject(with: data) as? [String: String]
102
+ XCTAssertEqual(decoded, ["key": "value"])
103
+ }
104
+
105
+ func testEncode_UnsupportedType_Throws() {
106
+ struct NotCodableAtAll {}
107
+ let encodable = AnyCodable(NotCodableAtAll())
108
+ XCTAssertThrowsError(try JSONEncoder().encode(encodable))
109
+ }
110
+
111
+ // MARK: - Round trip via NotificationConfig.data (the real production use site)
112
+
113
+ func testNotificationConfig_DataFieldRoundTripsMixedTypes() throws {
114
+ let config = NotificationConfig(
115
+ title: "Test",
116
+ body: "Body",
117
+ data: [
118
+ "flag": true,
119
+ "count": 7,
120
+ "label": "hello",
121
+ "list": [1, 2, 3],
122
+ ]
123
+ )
124
+
125
+ let json = try XCTUnwrap(config.toJSON())
126
+ let restored = try XCTUnwrap(NotificationConfig.fromJSON(json))
127
+ let data = try XCTUnwrap(restored.data)
128
+
129
+ XCTAssertEqual(data["flag"]?.value as? Bool, true)
130
+ XCTAssertEqual(data["count"]?.value as? Int, 7)
131
+ XCTAssertEqual(data["label"]?.value as? String, "hello")
132
+ XCTAssertEqual((data["list"]?.value as? [Any])?.count, 3)
133
+ }
134
+
135
+ func testNotificationConfig_NilDataFieldRoundTrips() throws {
136
+ let config = NotificationConfig(title: "Test", body: "Body", data: nil)
137
+
138
+ let json = try XCTUnwrap(config.toJSON())
139
+ let restored = try XCTUnwrap(NotificationConfig.fromJSON(json))
140
+
141
+ XCTAssertNil(restored.data)
142
+ }
143
+
144
+ func testNotificationConfig_FromJSON_InvalidJSON_ReturnsNil() {
145
+ XCTAssertNil(NotificationConfig.fromJSON("{not valid json"))
146
+ }
147
+
148
+ func testNotificationConfig_FromJSON_NonUTF8_ReturnsNil() {
149
+ // fromJSON's own guard (data(using: .utf8)) is defensive — exercised
150
+ // indirectly since almost all Swift Strings are representable as
151
+ // UTF8. Documented as effectively unreachable in practice, not
152
+ // asserted here.
153
+ }
154
+ }
@@ -1,6 +1,6 @@
1
1
  import XCTest
2
2
  import Foundation
3
- @testable import CatalystCore
3
+ @testable import CatalystCoreLogic
4
4
 
5
5
  /**
6
6
  * Unit tests for BootTimingUtility
@@ -94,10 +94,17 @@ final class BootTimingUtilityTests: XCTestCase {
94
94
  XCTAssertGreaterThan(elapsedAfter, elapsedBefore,
95
95
  "Elapsed time should increase over time")
96
96
 
97
- // Verify the difference is approximately 10ms (with tolerance for system variance)
97
+ // Verify the difference is at least the ~10ms sleep, with a floor
98
+ // to catch a broken/zero calculation. No upper bound: this was
99
+ // previously capped at 50ms "accounting for system load", but
100
+ // that's asserting real OS scheduler latency for Thread.sleep,
101
+ // which a shared/loaded CI runner can legitimately exceed (seen:
102
+ // 62ms) with zero relation to whether APP_LAUNCH_TIME-based
103
+ // elapsed-time math is correct. The floor + the
104
+ // elapsedAfter > elapsedBefore check above already cover the
105
+ // logic this test exists to verify.
98
106
  let difference = elapsedAfter - elapsedBefore
99
107
  XCTAssertGreaterThan(difference, 5.0, "Time difference should be at least 5ms")
100
- XCTAssertLessThan(difference, 50.0, "Time difference should be less than 50ms (accounting for system load)")
101
108
  #else
102
109
  XCTAssertTrue(true, "Skipping DEBUG-only test in RELEASE build")
103
110
  #endif
@@ -193,14 +200,25 @@ final class BootTimingUtilityTests: XCTestCase {
193
200
  let expectation = self.expectation(description: "Concurrent logging completes")
194
201
  expectation.expectedFulfillmentCount = 10
195
202
 
203
+ // Raising the wait timeout alone did not fix this test: it failed at
204
+ // exactly 10s, then again at exactly 60s after that bump -- a hit-the-
205
+ // timeout-precisely pattern is scheduling starvation, not slowness.
206
+ // `.background` QoS is deliberately deprioritized by the OS and can be
207
+ // starved indefinitely under CPU contention on a busy CI host, so no
208
+ // timeout is safe against it. `.userInitiated` still exercises the
209
+ // thing this test actually verifies (logWithTimestamp handles
210
+ // concurrent calls without crashing/corrupting) without depending on
211
+ // background-priority scheduling ever getting CPU time.
196
212
  for i in 0..<10 {
197
- DispatchQueue.global(qos: .background).async {
213
+ DispatchQueue.global(qos: .userInitiated).async {
198
214
  logWithTimestamp("Concurrent log \(i)")
199
215
  expectation.fulfill()
200
216
  }
201
217
  }
202
218
 
203
- waitForExpectations(timeout: 10.0) { error in
219
+ // Kept generous as headroom for host variance (~15-20% build/test time
220
+ // swings observed run-to-run on shared CI runners), not as the fix.
221
+ waitForExpectations(timeout: 60.0) { error in
204
222
  XCTAssertNil(error, "Concurrent logging should complete without errors")
205
223
  }
206
224
  }