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,167 @@
1
+ package io.yourname.androidproject.utils
2
+
3
+ import android.content.Context
4
+ import android.util.DisplayMetrics
5
+ import android.view.Display
6
+ import android.view.WindowManager
7
+ import io.yourname.androidproject.security.SecurityCheckManager
8
+ import org.junit.Assert.assertEquals
9
+ import org.junit.Assert.assertFalse
10
+ import org.junit.Assert.assertTrue
11
+ import org.junit.Test
12
+ import org.mockito.kotlin.any
13
+ import org.mockito.kotlin.doAnswer
14
+ import org.mockito.kotlin.doReturn
15
+ import org.mockito.kotlin.mock
16
+ import java.util.Properties
17
+
18
+ /**
19
+ * Unit tests for DeviceInfoUtils (Android coverage batch 4), previously
20
+ * entirely untested (0/~40 lines).
21
+ *
22
+ * NOTE: A root-package `io.yourname.androidproject.DeviceInfoUtilsTest`
23
+ * already existed before this batch (contract/shape assertions only, no
24
+ * mocking, doesn't call getDeviceInfo() at all). Per this task's package-
25
+ * matching rule, this new file lives in the `.utils` package alongside its
26
+ * source and is a distinct class from the old one -- both compile and run
27
+ * side by side. The old file is left untouched ("test files only, don't
28
+ * touch other files") but is a candidate for a follow-up cleanup/removal
29
+ * since this file supersedes it with real mock-driven coverage.
30
+ *
31
+ * Empirically confirmed facts this suite relies on (see task history):
32
+ * - Under this project's mockable android.jar
33
+ * (testOptions.unitTests.isReturnDefaultValues = true), Build.VERSION.SDK_INT
34
+ * is 0, so getDisplayMetrics() always takes the pre-R (`else`) branch:
35
+ * windowManager.defaultDisplay.getMetrics(displayMetrics). The R+
36
+ * WindowMetrics branch (Build.VERSION.SDK_INT >= R) is unreachable under
37
+ * this test setup and is NOT covered here -- Build.VERSION.SDK_INT is a
38
+ * static final int and not mockable (project-wide rule #1).
39
+ * - Build.MODEL / Build.MANUFACTURER are null under the mockable jar
40
+ * (also unmockable per rule #1). Verified empirically (see a throwaway
41
+ * org.json.JSONObject script) that `JSONObject.put(key, null)` does NOT
42
+ * store a null value -- it OMITS the key entirely (`has(key)` returns
43
+ * false afterwards). So this suite does NOT assert presence/absence of
44
+ * "model"/"manufacturer" one way or the other as a stable contract --
45
+ * only that the fields that DON'T depend on Build.* (screenWidth/
46
+ * screenHeight/screenDensity/appInfo/security) are correct.
47
+ * - SecurityCheckManager.getLatestSecurityResults(context) is exercised
48
+ * with a plain mocked Context (no SharedPreferences stubbing): its
49
+ * internal try/catch absorbs the NPE from an unstubbed
50
+ * getSharedPreferences() call and returns null, so getDeviceInfo() takes
51
+ * the "security pending" branch. This mirrors
52
+ * SecurityCheckManagerTest's own "returns null when nothing has been
53
+ * saved yet" / "throws" coverage, so SecurityCheckManager's internals
54
+ * are not re-tested here -- only that DeviceInfoUtils wires the pending
55
+ * fallback correctly.
56
+ */
57
+ class DeviceInfoUtilsTest {
58
+
59
+ private fun mockContext(widthPx: Int, heightPx: Int, density: Float): Context {
60
+ val display = mock<Display> {
61
+ on { getMetrics(any()) } doAnswer { invocation ->
62
+ val dm = invocation.getArgument<DisplayMetrics>(0)
63
+ dm.widthPixels = widthPx
64
+ dm.heightPixels = heightPx
65
+ dm.density = density
66
+ Unit
67
+ }
68
+ }
69
+ val windowManager = mock<WindowManager> {
70
+ on { getDefaultDisplay() } doReturn display
71
+ }
72
+ return mock {
73
+ on { getSystemService(Context.WINDOW_SERVICE) } doReturn windowManager
74
+ }
75
+ }
76
+
77
+ // ============================================================
78
+ // Screen dimension fields (via the pre-R WindowManager path)
79
+ // ============================================================
80
+
81
+ @Test
82
+ fun `getDeviceInfo populates screen fields from the deprecated defaultDisplay path`() {
83
+ val context = mockContext(widthPx = 1080, heightPx = 2280, density = 2.75f)
84
+
85
+ val info = DeviceInfoUtils.getDeviceInfo(context)
86
+
87
+ assertEquals(1080, info.getInt("screenWidth"))
88
+ assertEquals(2280, info.getInt("screenHeight"))
89
+ assertEquals(2.75, info.getDouble("screenDensity"), 0.001)
90
+ }
91
+
92
+ @Test
93
+ fun `getDeviceInfo always sets platform to android`() {
94
+ val context = mockContext(widthPx = 100, heightPx = 200, density = 1.0f)
95
+
96
+ val info = DeviceInfoUtils.getDeviceInfo(context)
97
+
98
+ assertEquals("android", info.getString("platform"))
99
+ }
100
+
101
+ // ============================================================
102
+ // appInfo (from optional Properties)
103
+ // ============================================================
104
+
105
+ @Test
106
+ fun `getDeviceInfo includes appInfo from properties when present`() {
107
+ val context = mockContext(widthPx = 100, heightPx = 200, density = 1.0f)
108
+ val properties = Properties().apply { setProperty("appInfo", "MyApp v1.2.3") }
109
+
110
+ val info = DeviceInfoUtils.getDeviceInfo(context, properties)
111
+
112
+ assertEquals("MyApp v1.2.3", info.getString("appInfo"))
113
+ }
114
+
115
+ @Test
116
+ fun `getDeviceInfo sets appInfo to JSON null when properties is null`() {
117
+ val context = mockContext(widthPx = 100, heightPx = 200, density = 1.0f)
118
+
119
+ val info = DeviceInfoUtils.getDeviceInfo(context, null)
120
+
121
+ assertTrue(info.has("appInfo"))
122
+ assertTrue(info.isNull("appInfo"))
123
+ }
124
+
125
+ @Test
126
+ fun `getDeviceInfo sets appInfo to JSON null when properties lacks the key`() {
127
+ val context = mockContext(widthPx = 100, heightPx = 200, density = 1.0f)
128
+ val properties = Properties().apply { setProperty("unrelated", "value") }
129
+
130
+ val info = DeviceInfoUtils.getDeviceInfo(context, properties)
131
+
132
+ assertTrue(info.isNull("appInfo"))
133
+ }
134
+
135
+ // ============================================================
136
+ // security field
137
+ // ============================================================
138
+
139
+ @Test
140
+ fun `getDeviceInfo marks security pending when no results are saved yet`() {
141
+ val context = mockContext(widthPx = 100, heightPx = 200, density = 1.0f)
142
+
143
+ // No SharedPreferences stubbing -- SecurityCheckManager.getLatestSecurityResults
144
+ // catches the resulting failure internally and returns null (matches
145
+ // SecurityCheckManagerTest's own "returns null" precedent).
146
+ val info = DeviceInfoUtils.getDeviceInfo(context)
147
+
148
+ assertTrue(info.has("security"))
149
+ assertTrue(info.getJSONObject("security").getBoolean("pending"))
150
+ }
151
+
152
+ // ============================================================
153
+ // Error path
154
+ // ============================================================
155
+
156
+ @Test
157
+ fun `getDeviceInfo returns an error object when getSystemService throws`() {
158
+ val context: Context = mock {
159
+ on { getSystemService(Context.WINDOW_SERVICE) } doAnswer { throw RuntimeException("boom") }
160
+ }
161
+
162
+ val info = DeviceInfoUtils.getDeviceInfo(context)
163
+
164
+ assertTrue(info.has("error"))
165
+ assertFalse(info.has("screenWidth"))
166
+ }
167
+ }
@@ -0,0 +1,197 @@
1
+ package io.yourname.androidproject.utils
2
+
3
+ import android.content.Context
4
+ import org.junit.Assert.*
5
+ import org.junit.Test
6
+ import org.mockito.kotlin.doReturn
7
+ import org.mockito.kotlin.mock
8
+ import java.io.File
9
+ import java.util.concurrent.TimeUnit
10
+ import kotlin.io.path.createTempDirectory
11
+
12
+ /**
13
+ * Unit tests for DownloadUtils (Android coverage batch 3), previously
14
+ * entirely untested (0/114 lines).
15
+ *
16
+ * Context.cacheDir is mocked to a real temp directory, matching
17
+ * OfflineCacheServiceTest/WebCacheManagerTest's established pattern.
18
+ *
19
+ * Deliberately out of scope: downloadFile/getRemoteFileSize (real
20
+ * java.net network calls) and downloadFileWithCallback/
21
+ * validateDownloadRequirements (both call downloadFile/getRemoteFileSize
22
+ * internally, so exercising them meaningfully would require the same real
23
+ * network dependency) -- consistent with this project's standing stance
24
+ * (see WebCacheManagerTest's fetchAndCacheResourceBlocking note) against
25
+ * mocking java.net deeply enough to test the mock instead of the code.
26
+ * Covered instead: isValidUrl, extractFileNameFromUrl,
27
+ * hasSufficientStorage, and cleanupDownloadedFiles -- all pure logic or
28
+ * simple Context.cacheDir-backed file I/O.
29
+ */
30
+ class DownloadUtilsTest {
31
+
32
+ private lateinit var cacheDir: File
33
+ private lateinit var context: Context
34
+
35
+ private fun freshContext(cacheDirOverride: File = cacheDir): Context = mock {
36
+ on { getCacheDir() } doReturn cacheDirOverride
37
+ }
38
+
39
+ @org.junit.Before
40
+ fun setUp() {
41
+ cacheDir = createTempDirectory(prefix = "catalyst-download-test").toFile()
42
+ context = freshContext()
43
+ }
44
+
45
+ @org.junit.After
46
+ fun tearDown() {
47
+ cacheDir.deleteRecursively()
48
+ }
49
+
50
+ // ============================================================
51
+ // isValidUrl
52
+ // ============================================================
53
+
54
+ @Test
55
+ fun `isValidUrl accepts a well-formed http url`() {
56
+ assertTrue(DownloadUtils.isValidUrl("http://example.com/file.pdf"))
57
+ }
58
+
59
+ @Test
60
+ fun `isValidUrl accepts a well-formed https url`() {
61
+ assertTrue(DownloadUtils.isValidUrl("https://example.com/file.pdf"))
62
+ }
63
+
64
+ @Test
65
+ fun `isValidUrl rejects a blank url`() {
66
+ assertFalse(DownloadUtils.isValidUrl(""))
67
+ assertFalse(DownloadUtils.isValidUrl(" "))
68
+ }
69
+
70
+ @Test
71
+ fun `isValidUrl rejects urls without http or https scheme`() {
72
+ assertFalse(DownloadUtils.isValidUrl("ftp://example.com/file.pdf"))
73
+ assertFalse(DownloadUtils.isValidUrl("file:///local/path"))
74
+ assertFalse(DownloadUtils.isValidUrl("example.com/file.pdf"))
75
+ }
76
+
77
+ @Test
78
+ fun `isValidUrl accepts a url with an empty host after the scheme`() {
79
+ // Verified against the real java.net.URL parser: "https://" alone
80
+ // parses successfully (an empty host is legal per the URL spec),
81
+ // and a space in the host doesn't throw either -- java.net.URL is
82
+ // far more lenient than the class's own KDoc ("throws if malformed")
83
+ // implies. Since isValidUrl already gates on startsWith("http://")/
84
+ // ("https://") before calling URL(...), there is no reachable input
85
+ // that passes that prefix check yet still throws inside URL(...) --
86
+ // the try/catch's catch branch is effectively unreachable for any
87
+ // string this method is actually called with. This test documents
88
+ // that finding rather than asserting a false-negative expectation.
89
+ assertTrue(DownloadUtils.isValidUrl("https://"))
90
+ }
91
+
92
+ // ============================================================
93
+ // extractFileNameFromUrl
94
+ // ============================================================
95
+
96
+ @Test
97
+ fun `extractFileNameFromUrl returns the last path segment`() {
98
+ assertEquals("file.pdf", DownloadUtils.extractFileNameFromUrl("https://example.com/path/to/file.pdf"))
99
+ }
100
+
101
+ @Test
102
+ fun `extractFileNameFromUrl strips query parameters`() {
103
+ assertEquals("file.pdf", DownloadUtils.extractFileNameFromUrl("https://example.com/file.pdf?token=abc123"))
104
+ }
105
+
106
+ @Test
107
+ fun `extractFileNameFromUrl falls back to a default name when the path is empty`() {
108
+ assertEquals("downloaded_file", DownloadUtils.extractFileNameFromUrl("https://example.com/"))
109
+ }
110
+
111
+ @Test
112
+ fun `extractFileNameFromUrl with no path segment returns the host as the filename`() {
113
+ // substringAfterLast("/") on "https://example.com" (no trailing
114
+ // slash, no path) returns "example.com" -- the whole string after
115
+ // the last "/" in the scheme separator itself. This is a real
116
+ // quirk of the current implementation, not a bug this batch is
117
+ // fixing; documented here rather than asserting the fallback that
118
+ // only actually triggers when the path segment is present but
119
+ // blank (covered by the trailing-slash case above).
120
+ assertEquals("example.com", DownloadUtils.extractFileNameFromUrl("https://example.com"))
121
+ }
122
+
123
+ // ============================================================
124
+ // hasSufficientStorage
125
+ // ============================================================
126
+
127
+ @Test
128
+ fun `hasSufficientStorage returns true when free space comfortably exceeds the requirement`() {
129
+ // Real temp dirs typically have plenty of free space on a CI/dev
130
+ // machine; request a tiny amount to make this deterministic.
131
+ assertTrue(DownloadUtils.hasSufficientStorage(context, requiredBytes = 1024L))
132
+ }
133
+
134
+ @Test
135
+ fun `hasSufficientStorage returns false when required bytes exceed available free space`() {
136
+ // File.freeSpace() reflects the real filesystem the temp dir lives
137
+ // on -- request far more than any real disk has to force the
138
+ // false branch deterministically rather than mocking File itself
139
+ // (File is a concrete java.io class, not mockable via Context).
140
+ val hugeRequirement = Long.MAX_VALUE / 2
141
+ assertFalse(DownloadUtils.hasSufficientStorage(context, requiredBytes = hugeRequirement))
142
+ }
143
+
144
+ // ============================================================
145
+ // cleanupDownloadedFiles
146
+ // ============================================================
147
+
148
+ @Test
149
+ fun `cleanupDownloadedFiles does nothing when the downloaded_files dir does not exist`() {
150
+ // No dir created -- should not throw.
151
+ DownloadUtils.cleanupDownloadedFiles(context)
152
+ }
153
+
154
+ @Test
155
+ fun `cleanupDownloadedFiles deletes files older than maxAgeMillis and keeps newer ones`() {
156
+ val downloadDir = File(cacheDir, "downloaded_files")
157
+ downloadDir.mkdirs()
158
+
159
+ val oldFile = File(downloadDir, "old.pdf")
160
+ oldFile.writeText("old content")
161
+ val fourDaysAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(4)
162
+ oldFile.setLastModified(fourDaysAgo)
163
+
164
+ val newFile = File(downloadDir, "new.pdf")
165
+ newFile.writeText("new content")
166
+
167
+ // Default maxAgeMillis is 3 days.
168
+ DownloadUtils.cleanupDownloadedFiles(context)
169
+
170
+ assertFalse("File older than 3 days should be deleted", oldFile.exists())
171
+ assertTrue("Recently-modified file should survive cleanup", newFile.exists())
172
+ }
173
+
174
+ @Test
175
+ fun `cleanupDownloadedFiles respects a custom maxAgeMillis`() {
176
+ val downloadDir = File(cacheDir, "downloaded_files")
177
+ downloadDir.mkdirs()
178
+
179
+ val file = File(downloadDir, "recent.pdf")
180
+ file.writeText("content")
181
+ val oneHourAgo = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1)
182
+ file.setLastModified(oneHourAgo)
183
+
184
+ // maxAgeMillis of 30 minutes -- the 1-hour-old file should be
185
+ // deleted even though it wouldn't be under the 3-day default.
186
+ DownloadUtils.cleanupDownloadedFiles(context, maxAgeMillis = TimeUnit.MINUTES.toMillis(30))
187
+
188
+ assertFalse(file.exists())
189
+ }
190
+
191
+ @Test
192
+ fun `cleanupDownloadedFiles on an empty downloaded_files dir does not throw`() {
193
+ val downloadDir = File(cacheDir, "downloaded_files")
194
+ downloadDir.mkdirs()
195
+ DownloadUtils.cleanupDownloadedFiles(context)
196
+ }
197
+ }
@@ -0,0 +1,271 @@
1
+ package io.yourname.androidproject.utils
2
+
3
+ import android.content.Context
4
+ import android.net.ConnectivityManager
5
+ import android.net.Network
6
+ import android.net.NetworkCapabilities
7
+ import android.net.NetworkRequest
8
+ import org.junit.After
9
+ import org.junit.Assert.assertEquals
10
+ import org.junit.Assert.assertTrue
11
+ import org.junit.Before
12
+ import org.junit.Test
13
+ import org.mockito.MockedConstruction
14
+ import org.mockito.Mockito.mockConstruction
15
+ import org.mockito.kotlin.any
16
+ import org.mockito.kotlin.anyOrNull
17
+ import org.mockito.kotlin.argumentCaptor
18
+ import org.mockito.kotlin.doReturn
19
+ import org.mockito.kotlin.mock
20
+ import org.mockito.kotlin.never
21
+ import org.mockito.kotlin.times
22
+ import org.mockito.kotlin.verify
23
+ import org.mockito.kotlin.whenever
24
+
25
+ /**
26
+ * Unit tests for NetworkMonitor (Android coverage batch 3), previously
27
+ * entirely untested (0/25 lines). NetworkUtils (the top-level
28
+ * getCurrentStatus/NetworkStatus in the same file) is exercised
29
+ * indirectly here since NetworkMonitor.start() calls it immediately, and
30
+ * again from each NetworkCallback override.
31
+ *
32
+ * ConnectivityManager/Network/NetworkCapabilities are all interfaces or
33
+ * mockable framework classes reachable via a mocked Context.
34
+ * NetworkRequest.Builder is a real android.jar builder class whose
35
+ * methods are stubbed to return null under the mockable jar
36
+ * (addCapability(...) returns null, so a real `.build()` call inside
37
+ * start() NPEs) -- rather than skip start() coverage entirely (it's the
38
+ * bulk of this class), Mockito.mockConstruction(NetworkRequest.Builder)
39
+ * is used (mirroring MetricsMonitorTest's mockStatic(Choreographer)
40
+ * pattern, applied to a constructor instead of a static). No real
41
+ * NetworkRequest.Builder is ever constructed, so this stays compliant
42
+ * with the project's "don't call .build() on real platform builders"
43
+ * rule -- the builder itself is fully faked. Kept open for every test's
44
+ * lifetime since start() constructs a fresh Builder on each call.
45
+ *
46
+ * Build.VERSION.SDK_INT resolves to 0 under the mockable android.jar
47
+ * (confirmed empirically), so `Build.VERSION.SDK_INT >= Build.VERSION_CODES.N`
48
+ * is always false in these tests -- the registerNetworkCallback(request,
49
+ * callback) branch is the only one structurally reachable here.
50
+ * registerDefaultNetworkCallback is never invoked and is not tested.
51
+ */
52
+ class NetworkMonitorTest {
53
+
54
+ private lateinit var connectivityManager: ConnectivityManager
55
+ private lateinit var appContext: Context
56
+ private lateinit var context: Context
57
+ private lateinit var requestBuilderMock: MockedConstruction<NetworkRequest.Builder>
58
+
59
+ @Before
60
+ fun setUp() {
61
+ connectivityManager = mock()
62
+ appContext = mock {
63
+ on { getSystemService(Context.CONNECTIVITY_SERVICE) } doReturn connectivityManager
64
+ }
65
+ context = mock {
66
+ on { getApplicationContext() } doReturn appContext
67
+ }
68
+
69
+ // Default: no active network -- getCurrentStatus resolves to offline
70
+ // unless a test overrides these.
71
+ whenever(connectivityManager.activeNetwork).thenReturn(null)
72
+ whenever(connectivityManager.getNetworkCapabilities(anyOrNull())).thenReturn(null)
73
+ whenever(connectivityManager.activeNetworkInfo).thenReturn(null)
74
+
75
+ requestBuilderMock = mockConstruction(NetworkRequest.Builder::class.java) { builderMock, _ ->
76
+ whenever(builderMock.addCapability(any())).thenReturn(builderMock)
77
+ whenever(builderMock.build()).thenReturn(mock<NetworkRequest>())
78
+ }
79
+ }
80
+
81
+ @After
82
+ fun tearDown() {
83
+ requestBuilderMock.close()
84
+ }
85
+
86
+ // ============================================================
87
+ // start()
88
+ // ============================================================
89
+
90
+ @Test
91
+ fun `start emits the current status immediately`() {
92
+ val statuses = mutableListOf<NetworkStatus>()
93
+ val monitor = NetworkMonitor(context) { statuses.add(it) }
94
+
95
+ monitor.start()
96
+
97
+ assertEquals(1, statuses.size)
98
+ assertEquals(false, statuses[0].isOnline)
99
+ }
100
+
101
+ @Test
102
+ fun `start emits an online status when capabilities report internet validated`() {
103
+ val capabilities = mock<NetworkCapabilities> {
104
+ on { hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) } doReturn true
105
+ on { hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) } doReturn true
106
+ on { hasTransport(any()) } doReturn false
107
+ }
108
+ val network = mock<Network>()
109
+ whenever(connectivityManager.activeNetwork).thenReturn(network)
110
+ whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities)
111
+
112
+ val statuses = mutableListOf<NetworkStatus>()
113
+ val monitor = NetworkMonitor(context) { statuses.add(it) }
114
+
115
+ monitor.start()
116
+
117
+ assertEquals(1, statuses.size)
118
+ assertTrue(statuses[0].isOnline)
119
+ }
120
+
121
+ @Test
122
+ fun `start registers a network callback via registerNetworkCallback`() {
123
+ val monitor = NetworkMonitor(context) { }
124
+
125
+ monitor.start()
126
+
127
+ verify(connectivityManager).registerNetworkCallback(
128
+ any<NetworkRequest>(),
129
+ any<ConnectivityManager.NetworkCallback>()
130
+ )
131
+ }
132
+
133
+ @Test
134
+ fun `calling start twice does not emit a second immediate status or re-register`() {
135
+ val statuses = mutableListOf<NetworkStatus>()
136
+ val monitor = NetworkMonitor(context) { statuses.add(it) }
137
+
138
+ monitor.start()
139
+ monitor.start()
140
+
141
+ // Second start() is a no-op guarded by `if (callback != null) return`.
142
+ assertEquals(1, statuses.size)
143
+ verify(connectivityManager, times(1)).registerNetworkCallback(
144
+ any<NetworkRequest>(),
145
+ any<ConnectivityManager.NetworkCallback>()
146
+ )
147
+ }
148
+
149
+ @Test
150
+ fun `start swallows a registration exception and resets callback to null`() {
151
+ whenever(
152
+ connectivityManager.registerNetworkCallback(
153
+ any<NetworkRequest>(),
154
+ any<ConnectivityManager.NetworkCallback>()
155
+ )
156
+ ).thenThrow(RuntimeException("boom"))
157
+
158
+ val statuses = mutableListOf<NetworkStatus>()
159
+ val monitor = NetworkMonitor(context) { statuses.add(it) }
160
+
161
+ // Should not throw -- try/catch swallows the registration failure
162
+ // and resets callback to null.
163
+ monitor.start()
164
+
165
+ // The immediate status emission still happened before the
166
+ // exception (it's emitted first, outside the try/catch).
167
+ assertEquals(1, statuses.size)
168
+
169
+ // Since callback was reset to null, a second start() is not
170
+ // treated as a no-op -- it emits a second immediate status and
171
+ // retries registration.
172
+ monitor.start()
173
+ assertEquals(2, statuses.size)
174
+ verify(connectivityManager, times(2)).registerNetworkCallback(
175
+ any<NetworkRequest>(),
176
+ any<ConnectivityManager.NetworkCallback>()
177
+ )
178
+ }
179
+
180
+ // ============================================================
181
+ // stop()
182
+ // ============================================================
183
+
184
+ @Test
185
+ fun `stop before start does not throw and does not unregister`() {
186
+ val monitor = NetworkMonitor(context) { }
187
+ monitor.stop()
188
+ verify(connectivityManager, never()).unregisterNetworkCallback(any<ConnectivityManager.NetworkCallback>())
189
+ }
190
+
191
+ @Test
192
+ fun `stop after start unregisters the callback`() {
193
+ val monitor = NetworkMonitor(context) { }
194
+ monitor.start()
195
+
196
+ monitor.stop()
197
+
198
+ verify(connectivityManager).unregisterNetworkCallback(any<ConnectivityManager.NetworkCallback>())
199
+ }
200
+
201
+ @Test
202
+ fun `stop swallows an unregister exception`() {
203
+ whenever(connectivityManager.unregisterNetworkCallback(any<ConnectivityManager.NetworkCallback>()))
204
+ .thenThrow(RuntimeException("boom"))
205
+ val monitor = NetworkMonitor(context) { }
206
+ monitor.start()
207
+
208
+ // Should not throw.
209
+ monitor.stop()
210
+ }
211
+
212
+ @Test
213
+ fun `calling stop twice only unregisters once`() {
214
+ val monitor = NetworkMonitor(context) { }
215
+ monitor.start()
216
+ monitor.stop()
217
+ monitor.stop()
218
+
219
+ // unregisterNetworkCallback should only be invoked once -- the
220
+ // second stop() sees callback == null and no-ops.
221
+ verify(connectivityManager, times(1)).unregisterNetworkCallback(any<ConnectivityManager.NetworkCallback>())
222
+ }
223
+
224
+ // ============================================================
225
+ // NetworkCallback overrides (onAvailable/onLost/onUnavailable) --
226
+ // captured via ArgumentCaptor and invoked directly, since there's no
227
+ // real ConnectivityManager driving them in a JVM unit test.
228
+ // ============================================================
229
+
230
+ @Test
231
+ fun `onAvailable re-emits the current status`() {
232
+ val statuses = mutableListOf<NetworkStatus>()
233
+ val monitor = NetworkMonitor(context) { statuses.add(it) }
234
+ monitor.start()
235
+
236
+ val captor = argumentCaptor<ConnectivityManager.NetworkCallback>()
237
+ verify(connectivityManager).registerNetworkCallback(any<NetworkRequest>(), captor.capture())
238
+
239
+ captor.firstValue.onAvailable(mock())
240
+
241
+ assertEquals(2, statuses.size)
242
+ }
243
+
244
+ @Test
245
+ fun `onLost re-emits the current status`() {
246
+ val statuses = mutableListOf<NetworkStatus>()
247
+ val monitor = NetworkMonitor(context) { statuses.add(it) }
248
+ monitor.start()
249
+
250
+ val captor = argumentCaptor<ConnectivityManager.NetworkCallback>()
251
+ verify(connectivityManager).registerNetworkCallback(any<NetworkRequest>(), captor.capture())
252
+
253
+ captor.firstValue.onLost(mock())
254
+
255
+ assertEquals(2, statuses.size)
256
+ }
257
+
258
+ @Test
259
+ fun `onUnavailable re-emits the current status`() {
260
+ val statuses = mutableListOf<NetworkStatus>()
261
+ val monitor = NetworkMonitor(context) { statuses.add(it) }
262
+ monitor.start()
263
+
264
+ val captor = argumentCaptor<ConnectivityManager.NetworkCallback>()
265
+ verify(connectivityManager).registerNetworkCallback(any<NetworkRequest>(), captor.capture())
266
+
267
+ captor.firstValue.onUnavailable()
268
+
269
+ assertEquals(2, statuses.size)
270
+ }
271
+ }