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,302 @@
1
+ package io.yourname.androidproject
2
+
3
+ import android.content.Context
4
+ import android.net.Uri
5
+ import android.webkit.CookieManager
6
+ import org.junit.After
7
+ import org.junit.Assert.assertFalse
8
+ import org.junit.Assert.assertNotNull
9
+ import org.junit.Assert.assertNull
10
+ import org.junit.Assert.assertTrue
11
+ import org.junit.Before
12
+ import org.junit.Test
13
+ import org.mockito.MockedStatic
14
+ import org.mockito.Mockito.mockStatic
15
+ import org.mockito.kotlin.any
16
+ import org.mockito.kotlin.doReturn
17
+ import org.mockito.kotlin.mock
18
+ import org.mockito.kotlin.whenever
19
+ import java.io.File
20
+ import kotlin.io.path.createTempDirectory
21
+
22
+ /**
23
+ * #413: first Mockito-based test in this template, covering
24
+ * OfflineCacheService — previously untested because it takes a real
25
+ * android.content.Context in its constructor and calls the unmocked
26
+ * android.net.Uri / android.webkit.CookieManager Android-stub classes
27
+ * internally (JVM unit tests run against the SDK's stub jar, which throws
28
+ * on any call unless testOptions.unitTests.isReturnDefaultValues is set,
29
+ * or the class is mocked — see app/build.gradle.kts).
30
+ *
31
+ * Uri.parse(...) is mocked statically per test via Mockito's inline
32
+ * mock-maker (the mockito-core 5.x default) so isHttpUrl/isOfflineRouteUrl/
33
+ * shouldCacheAssetUrl exercise real logic against fake-but-realistic parsed
34
+ * URIs, rather than the SDK stub's default null/false behavior.
35
+ */
36
+ class OfflineCacheServiceTest {
37
+
38
+ private lateinit var cacheDir: File
39
+ private lateinit var context: Context
40
+ private lateinit var uriMock: MockedStatic<Uri>
41
+ private lateinit var cookieManagerMock: MockedStatic<CookieManager>
42
+
43
+ @Before
44
+ fun setUp() {
45
+ cacheDir = createTempDirectory(prefix = "catalyst-offline-test").toFile()
46
+ context = mock {
47
+ on { getCacheDir() } doReturn cacheDir
48
+ }
49
+
50
+ uriMock = mockStatic(Uri::class.java)
51
+ uriMock.`when`<Uri> { Uri.parse(any()) }.thenAnswer { invocation ->
52
+ fakeUriFor(invocation.getArgument(0))
53
+ }
54
+
55
+ // CookieManager.getInstance() has no real webview in a JVM unit
56
+ // test; stub it to a mock that reports no cookies, matching what
57
+ // production sees on a fresh device before any cookies are set.
58
+ val cookieManager = mock<CookieManager> {
59
+ on { getCookie(any()) } doReturn null
60
+ }
61
+ cookieManagerMock = mockStatic(CookieManager::class.java)
62
+ cookieManagerMock.`when`<CookieManager> { CookieManager.getInstance() }.thenReturn(cookieManager)
63
+ }
64
+
65
+ @After
66
+ fun tearDown() {
67
+ uriMock.close()
68
+ cookieManagerMock.close()
69
+ cacheDir.deleteRecursively()
70
+ }
71
+
72
+ // A tiny fake of what Uri.parse() would return for the http(s) URLs
73
+ // this class actually deals with — just enough of the real contract
74
+ // (scheme/authority/path/fragment stripping) for the code under test.
75
+ private fun fakeUriFor(url: String): Uri {
76
+ val schemeEnd = url.indexOf("://")
77
+ val scheme = if (schemeEnd >= 0) url.substring(0, schemeEnd) else null
78
+ val afterScheme = if (schemeEnd >= 0) url.substring(schemeEnd + 3) else url
79
+ val pathStart = afterScheme.indexOf('/').let { if (it < 0) afterScheme.length else it }
80
+ val authority = if (schemeEnd >= 0) afterScheme.substring(0, pathStart) else null
81
+ val rawPath = afterScheme.substring(pathStart).substringBefore('#').ifEmpty { "/" }
82
+ val withoutFragment = url.substringBefore('#')
83
+
84
+ val withoutFragmentUri = mock<Uri>()
85
+ whenever(withoutFragmentUri.toString()) doReturn withoutFragment
86
+
87
+ val builder = mock<Uri.Builder>()
88
+ whenever(builder.fragment(org.mockito.kotlin.anyOrNull())) doReturn builder
89
+ whenever(builder.build()) doReturn withoutFragmentUri
90
+
91
+ val uri = mock<Uri>()
92
+ whenever(uri.scheme) doReturn scheme
93
+ whenever(uri.authority) doReturn authority
94
+ whenever(uri.path) doReturn rawPath
95
+ whenever(uri.toString()) doReturn withoutFragment
96
+ whenever(uri.buildUpon()) doReturn builder
97
+ return uri
98
+ }
99
+
100
+ @Test
101
+ fun `shouldCacheAssetUrl accepts known static-asset extensions over http(s)`() {
102
+ val service = OfflineCacheService(context)
103
+
104
+ assertTrue(service.shouldCacheAssetUrl("https://example.com/static/app.js"))
105
+ assertTrue(service.shouldCacheAssetUrl("https://example.com/static/app.CSS"))
106
+ assertFalse(service.shouldCacheAssetUrl("https://example.com/api/data"))
107
+ }
108
+
109
+ @Test
110
+ fun `shouldCacheAssetUrl rejects non-http(s) schemes`() {
111
+ val service = OfflineCacheService(context)
112
+
113
+ assertFalse(service.shouldCacheAssetUrl("file:///static/app.js"))
114
+ }
115
+
116
+ @Test
117
+ fun `hasRouteSnapshot is false when no manifest has been loaded`() {
118
+ val service = OfflineCacheService(context)
119
+
120
+ assertFalse(service.hasRouteSnapshot("https://example.com/some/route"))
121
+ }
122
+
123
+ @Test
124
+ fun `getRouteSnapshotResponse returns null when route is not offline-eligible`() {
125
+ val service = OfflineCacheService(context)
126
+
127
+ assertNull(service.getRouteSnapshotResponse("https://example.com/not-cached"))
128
+ }
129
+
130
+ @Test
131
+ fun `clearAll resets cached manifest state without throwing`() {
132
+ val service = OfflineCacheService(context)
133
+
134
+ service.clearAll()
135
+
136
+ assertFalse(service.hasRouteSnapshot("https://example.com/some/route"))
137
+ assertTrue(cacheDir.exists())
138
+ }
139
+
140
+ @Test
141
+ fun `context getCacheDir is consulted exactly once at construction`() {
142
+ OfflineCacheService(context)
143
+
144
+ org.mockito.kotlin.verify(context).getCacheDir()
145
+ }
146
+
147
+ // ============================================================
148
+ // Manifest-driven routing (batch 4 extension)
149
+ //
150
+ // The manifest is loaded once at construction time via
151
+ // loadCachedManifest() -> parseManifest(), reading a plain
152
+ // manifest.json file under context.cacheDir/catalyst_offline/ — no
153
+ // network involved. Pre-writing that file before constructing
154
+ // OfflineCacheService unlocks isOfflineRouteUrl/getRouteSnapshotResponse/
155
+ // hasRouteSnapshot's real-match branches, previously only exercised
156
+ // via their "no manifest loaded" empty-state paths above.
157
+ // ============================================================
158
+
159
+ private fun writeManifest(buildId: String, routes: List<Pair<String, String>>) {
160
+ val rootDir = File(cacheDir, "catalyst_offline")
161
+ rootDir.mkdirs()
162
+ val routesJson = routes.joinToString(",") { (pattern, regex) ->
163
+ """{"pattern":"$pattern","regex":"$regex"}"""
164
+ }
165
+ val json = """{"buildId":"$buildId","routes":[$routesJson]}"""
166
+ File(rootDir, "manifest.json").writeText(json)
167
+ }
168
+
169
+ @Test
170
+ fun `isOfflineRouteUrl matches a route whose regex matches the url path`() {
171
+ writeManifest("build-123", listOf("/docs/*" to "^/docs/.*$"))
172
+ val service = OfflineCacheService(context)
173
+
174
+ assertTrue(service.isOfflineRouteUrl("https://example.com/docs/getting-started"))
175
+ }
176
+
177
+ @Test
178
+ fun `isOfflineRouteUrl returns false when no route regex matches`() {
179
+ writeManifest("build-123", listOf("/docs/*" to "^/docs/.*$"))
180
+ val service = OfflineCacheService(context)
181
+
182
+ assertFalse(service.isOfflineRouteUrl("https://example.com/api/data"))
183
+ }
184
+
185
+ @Test
186
+ fun `isOfflineRouteUrl returns false for a non-http(s) url even with a loaded manifest`() {
187
+ writeManifest("build-123", listOf("/docs/*" to "^/docs/.*$"))
188
+ val service = OfflineCacheService(context)
189
+
190
+ assertFalse(service.isOfflineRouteUrl("file:///docs/local.html"))
191
+ }
192
+
193
+ @Test
194
+ fun `isOfflineRouteUrl treats an unparseable route regex as a non-match, not a crash`() {
195
+ // "[" is an invalid regex -- Regex(...).matches() should throw,
196
+ // caught internally and treated as "this route doesn't match"
197
+ // rather than propagating.
198
+ writeManifest("build-123", listOf("/broken" to "["))
199
+ val service = OfflineCacheService(context)
200
+
201
+ assertFalse(service.isOfflineRouteUrl("https://example.com/broken"))
202
+ }
203
+
204
+ @Test
205
+ fun `hasRouteSnapshot is true only when the route is eligible AND a snapshot file exists on disk`() {
206
+ writeManifest("build-123", listOf("/docs/*" to "^/docs/.*$"))
207
+ val service = OfflineCacheService(context)
208
+ val url = "https://example.com/docs/getting-started"
209
+
210
+ // Route is eligible per the manifest, but no snapshot has been
211
+ // written to disk yet.
212
+ assertFalse(service.hasRouteSnapshot(url))
213
+ }
214
+
215
+ private fun sha256Hex(value: String): String {
216
+ val digest = java.security.MessageDigest.getInstance("SHA-256").digest(value.toByteArray())
217
+ return digest.joinToString("") { "%02x".format(it) }
218
+ }
219
+
220
+ @Test
221
+ fun `getRouteSnapshotResponse serves a real snapshot file for an eligible, snapshotted route`() {
222
+ writeManifest("build-123", listOf("/docs/*" to "^/docs/.*$"))
223
+ val url = "https://example.com/docs/getting-started"
224
+
225
+ // snapshotFileForUrl/hash are private; reproduce their (simple,
226
+ // stable) SHA-256-based path layout here to place a snapshot file
227
+ // exactly where the production code will look for it:
228
+ // routes/<hash(origin:buildId)>/<hash(normalizedUrl)>.html
229
+ val namespace = "https://example.com:build-123"
230
+ val namespaceDir = File(cacheDir, "catalyst_offline/routes/${sha256Hex(namespace)}")
231
+ namespaceDir.mkdirs()
232
+ File(namespaceDir, "${sha256Hex(url)}.html").writeText("<html>cached docs page</html>")
233
+
234
+ val service = OfflineCacheService(context)
235
+
236
+ // WebResourceResponse is an android.webkit SDK stub class: its
237
+ // constructor is a no-op under the mockable jar and its getters
238
+ // return defaults regardless of what was passed in (confirmed
239
+ // empirically earlier this session, see WebCacheManagerTest) --
240
+ // only "a response object was returned" is assertable here, not
241
+ // its field contents.
242
+ assertTrue(service.hasRouteSnapshot(url))
243
+ assertNotNull(service.getRouteSnapshotResponse(url))
244
+ }
245
+
246
+ @Test
247
+ fun `getRouteSnapshotResponse returns null when the route is eligible but no snapshot file exists yet`() {
248
+ writeManifest("build-123", listOf("/docs/*" to "^/docs/.*$"))
249
+ val service = OfflineCacheService(context)
250
+ val url = "https://example.com/docs/getting-started"
251
+
252
+ assertFalse(service.hasRouteSnapshot(url))
253
+ assertNull(service.getRouteSnapshotResponse(url))
254
+ }
255
+
256
+ @Test
257
+ fun `clearAll removes a manifest loaded from a previous instance`() {
258
+ writeManifest("build-123", listOf("/docs/*" to "^/docs/.*$"))
259
+ val service = OfflineCacheService(context)
260
+ assertTrue(service.isOfflineRouteUrl("https://example.com/docs/x"))
261
+
262
+ service.clearAll()
263
+
264
+ // manifest is nulled out by clearAll(); route matching should
265
+ // fall back to "no manifest" behavior immediately, in the same
266
+ // instance, without needing to reconstruct the service.
267
+ assertFalse(service.isOfflineRouteUrl("https://example.com/docs/x"))
268
+ }
269
+
270
+ @Test
271
+ fun `a second OfflineCacheService instance loads the manifest persisted by a previous one`() {
272
+ writeManifest("build-456", listOf("/blog/*" to "^/blog/.*$"))
273
+
274
+ // First instance's constructor already ran loadCachedManifest();
275
+ // this constructs a second instance against the SAME cacheDir to
276
+ // confirm the on-disk manifest.json (not just in-memory state) is
277
+ // what's actually being read.
278
+ OfflineCacheService(context)
279
+ val secondInstance = OfflineCacheService(context)
280
+
281
+ assertTrue(secondInstance.isOfflineRouteUrl("https://example.com/blog/post-1"))
282
+ }
283
+
284
+ @Test
285
+ fun `a malformed manifest json on disk is treated as no manifest, not a crash`() {
286
+ val rootDir = File(cacheDir, "catalyst_offline")
287
+ rootDir.mkdirs()
288
+ File(rootDir, "manifest.json").writeText("not valid json {{{")
289
+
290
+ val service = OfflineCacheService(context)
291
+
292
+ assertFalse(service.isOfflineRouteUrl("https://example.com/docs/x"))
293
+ }
294
+
295
+ @Test
296
+ fun `a manifest with an empty routes array loads without matching anything`() {
297
+ writeManifest("build-789", emptyList())
298
+ val service = OfflineCacheService(context)
299
+
300
+ assertFalse(service.isOfflineRouteUrl("https://example.com/anything"))
301
+ }
302
+ }
@@ -0,0 +1,133 @@
1
+ package io.yourname.androidproject
2
+
3
+ import io.yourname.androidproject.utils.PerfEventBuffer
4
+ import org.json.JSONObject
5
+ import org.junit.After
6
+ import org.junit.Assert.*
7
+ import org.junit.Before
8
+ import org.junit.Test
9
+
10
+ /**
11
+ * Unit tests for PerfEventBuffer, previously entirely untested (0%).
12
+ *
13
+ * Scope: configure/isEnabled/add/bridgeCallReceived/bridgeCallDispatched/
14
+ * reset are exercised directly — pure state plus a ConcurrentLinkedQueue,
15
+ * no Handler/Looper dispatch involved. scheduleFlush/flushNow/
16
+ * startPeriodicFlush/the deliver-retry pipeline post through a real
17
+ * android.os.Handler(Looper.getMainLooper()) constructed at object-init
18
+ * time — Looper.getMainLooper() has no real Looper in a JVM unit test, so
19
+ * calling those methods either throws or silently never runs the posted
20
+ * work. Left uncovered here rather than chased with Robolectric, per the
21
+ * plan's Mockito-only scope.
22
+ *
23
+ * PerfEventBuffer is a singleton object — tests reset() in tearDown so
24
+ * state doesn't leak into other test classes running in the same JVM.
25
+ */
26
+ class PerfEventBufferTest {
27
+
28
+ @Before
29
+ fun setUp() {
30
+ PerfEventBuffer.reset()
31
+ }
32
+
33
+ @After
34
+ fun tearDown() {
35
+ PerfEventBuffer.configure(enabled = false)
36
+ PerfEventBuffer.reset()
37
+ }
38
+
39
+ @Test
40
+ fun `configure(true) enables the buffer in a debug build`() {
41
+ // configure(true) only actually enables the buffer when
42
+ // BuildConfig.DEBUG is true — assert that precondition explicitly
43
+ // (via Assume) rather than just asserting isEnabled() and relying
44
+ // on an unchecked comment that this suite always runs under the
45
+ // debug variant. This task runs as testDebugUnitTest, so it does
46
+ // today, but making the check explicit means a future variant
47
+ // change surfaces as a skip, not a silent false-negative pass.
48
+ org.junit.Assume.assumeTrue(
49
+ "This test only applies to the debug variant",
50
+ io.yourname.androidproject.BuildConfig.DEBUG
51
+ )
52
+ PerfEventBuffer.configure(enabled = true)
53
+ assertTrue(PerfEventBuffer.isEnabled())
54
+ }
55
+
56
+ @Test
57
+ fun `configure(false) disables the buffer`() {
58
+ PerfEventBuffer.configure(enabled = true)
59
+ PerfEventBuffer.configure(enabled = false)
60
+ assertFalse(PerfEventBuffer.isEnabled())
61
+ }
62
+
63
+ @Test
64
+ fun `add is a no-op when disabled`() {
65
+ PerfEventBuffer.configure(enabled = false)
66
+ // Should not throw even though nothing is buffered.
67
+ PerfEventBuffer.add(JSONObject().apply { put("type", "test") })
68
+ }
69
+
70
+ @Test
71
+ fun `add does not throw when enabled`() {
72
+ PerfEventBuffer.configure(enabled = true)
73
+ PerfEventBuffer.add(JSONObject().apply { put("type", "test-event") })
74
+ }
75
+
76
+ @Test
77
+ fun `bridgeCallReceived followed by bridgeCallDispatched does not throw`() {
78
+ PerfEventBuffer.configure(enabled = true)
79
+
80
+ PerfEventBuffer.bridgeCallReceived("call-1", "getDeviceInfo")
81
+ PerfEventBuffer.bridgeCallDispatched("call-1")
82
+ }
83
+
84
+ @Test
85
+ fun `bridgeCallDispatched for an unknown callId is a no-op`() {
86
+ PerfEventBuffer.configure(enabled = true)
87
+
88
+ // No matching bridgeCallReceived — pendingCalls has no entry for
89
+ // "unknown-call", so this should return early without throwing.
90
+ PerfEventBuffer.bridgeCallDispatched("unknown-call")
91
+ }
92
+
93
+ @Test
94
+ fun `bridgeCallReceived and bridgeCallDispatched are no-ops when disabled`() {
95
+ PerfEventBuffer.configure(enabled = false)
96
+
97
+ PerfEventBuffer.bridgeCallReceived("call-1", "getDeviceInfo")
98
+ PerfEventBuffer.bridgeCallDispatched("call-1")
99
+ }
100
+
101
+ @Test
102
+ fun `reset disables flushed state and clears without throwing`() {
103
+ PerfEventBuffer.configure(enabled = true)
104
+ PerfEventBuffer.add(JSONObject().apply { put("type", "test") })
105
+
106
+ PerfEventBuffer.reset()
107
+
108
+ // Buffer cleared, no exception — isEnabled() state is untouched by
109
+ // reset() itself (only configure() changes it).
110
+ assertTrue(PerfEventBuffer.isEnabled())
111
+ }
112
+
113
+ @Test
114
+ fun `add with a cache-hit-memory type does not throw while updating the cache summary tally`() {
115
+ PerfEventBuffer.configure(enabled = true)
116
+
117
+ PerfEventBuffer.add(JSONObject().apply {
118
+ put("type", "cache-hit-memory")
119
+ put("url", "https://example.com/app.js")
120
+ })
121
+ }
122
+
123
+ @Test
124
+ fun `add with a network-fetch-complete type does not throw while updating the top-slow tracker`() {
125
+ PerfEventBuffer.configure(enabled = true)
126
+
127
+ PerfEventBuffer.add(JSONObject().apply {
128
+ put("type", "network-fetch-complete")
129
+ put("url", "https://example.com/big-asset.bin")
130
+ put("durationMs", 1234)
131
+ })
132
+ }
133
+ }
@@ -0,0 +1,152 @@
1
+ package io.yourname.androidproject
2
+
3
+ import android.net.Uri
4
+ import org.junit.After
5
+ import org.junit.Assert.assertFalse
6
+ import org.junit.Assert.assertTrue
7
+ import org.junit.Before
8
+ import org.junit.Test
9
+ import org.mockito.MockedStatic
10
+ import org.mockito.Mockito.mockStatic
11
+ import org.mockito.kotlin.any
12
+ import org.mockito.kotlin.mock
13
+ import org.mockito.kotlin.whenever
14
+
15
+ /**
16
+ * Unit tests for the top-level matchesCachePattern function in Utils.kt
17
+ * (Android coverage batch 4), previously entirely untested (0/18 lines,
18
+ * reported under the synthetic class name UtilsKt in jacoco).
19
+ *
20
+ * matchesCachePattern calls android.net.Uri.parse(url) internally, which
21
+ * returns null under the mockable android.jar (isReturnDefaultValues=true)
22
+ * unless statically mocked -- following the exact Uri.parse mockStatic
23
+ * pattern established in OfflineCacheServiceTest. Unlike that fake, this
24
+ * one only needs Uri.path stubbed (matchesCachePattern reads nothing else
25
+ * off the parsed Uri), and it deliberately strips both '?' and '#' when
26
+ * deriving the path -- matching what the real Uri.parse/getPath contract
27
+ * does (query and fragment are never part of getPath()), which is required
28
+ * for the KDoc's own "?v=123" and "#section" examples to actually match.
29
+ *
30
+ * Covered: the empty-cachePatterns short-circuit, the null-path guard (via
31
+ * a designated fake Uri with a null path), wildcard (*) pattern matching
32
+ * including hashed/versioned filenames and query/fragment stripping,
33
+ * case-insensitivity, exact (non-wildcard) pattern matching, and the
34
+ * empty-filename guard (trailing-slash URL). Not covered: the outer
35
+ * try/catch's Exception branch -- Uri.parse is mocked to never throw here,
36
+ * and the real Android Uri.parse implementation practically never throws
37
+ * (it does lenient parsing), so there is no reachable production input that
38
+ * exercises that branch; this mirrors DownloadUtilsTest's documented stance
39
+ * on similar defensively-written but practically unreachable catch blocks.
40
+ */
41
+ class UtilsKtTest {
42
+
43
+ private lateinit var uriMock: MockedStatic<Uri>
44
+
45
+ @Before
46
+ fun setUp() {
47
+ uriMock = mockStatic(Uri::class.java)
48
+ uriMock.`when`<Uri> { Uri.parse(any()) }.thenAnswer { invocation ->
49
+ fakeUriFor(invocation.getArgument(0))
50
+ }
51
+ }
52
+
53
+ @After
54
+ fun tearDown() {
55
+ uriMock.close()
56
+ }
57
+
58
+ // Minimal fake of what Uri.parse(url).path would return: everything
59
+ // after the authority, with query params and fragment stripped, same
60
+ // as the real android.net.Uri contract. A url containing the sentinel
61
+ // "NULLPATH" simulates a Uri whose path is null (e.g. an opaque URI),
62
+ // to exercise matchesCachePattern's `?: return false` guard.
63
+ private fun fakeUriFor(url: String): Uri {
64
+ val uri = mock<Uri>()
65
+ if (url.contains("NULLPATH")) {
66
+ whenever(uri.path).thenReturn(null)
67
+ return uri
68
+ }
69
+ val schemeEnd = url.indexOf("://")
70
+ val afterScheme = if (schemeEnd >= 0) url.substring(schemeEnd + 3) else url
71
+ val pathStart = afterScheme.indexOf('/').let { if (it < 0) afterScheme.length else it }
72
+ val rawPath = afterScheme.substring(pathStart)
73
+ .substringBefore('?')
74
+ .substringBefore('#')
75
+ whenever(uri.path).thenReturn(rawPath.ifEmpty { "/" })
76
+ return uri
77
+ }
78
+
79
+ @Test
80
+ fun `returns false immediately when cachePatterns is empty`() {
81
+ assertFalse(matchesCachePattern("https://example.com/app.css", emptyList()))
82
+ }
83
+
84
+ @Test
85
+ fun `returns false when the parsed uri has a null path`() {
86
+ assertFalse(matchesCachePattern("opaque:NULLPATH", listOf("*.css")))
87
+ }
88
+
89
+ @Test
90
+ fun `wildcard pattern matches a plain filename`() {
91
+ assertTrue(matchesCachePattern("http://example.com/app.css", listOf("*.css")))
92
+ }
93
+
94
+ @Test
95
+ fun `wildcard pattern matches a hashed versioned filename`() {
96
+ assertTrue(matchesCachePattern("http://example.com/app.d4e5dea6.css", listOf("*.css")))
97
+ }
98
+
99
+ @Test
100
+ fun `wildcard pattern matches with query parameters stripped`() {
101
+ assertTrue(matchesCachePattern("http://example.com/app.css?v=123", listOf("*.css")))
102
+ }
103
+
104
+ @Test
105
+ fun `wildcard pattern matches with a fragment stripped`() {
106
+ assertTrue(matchesCachePattern("http://example.com/path/to/style.css#section", listOf("*.css")))
107
+ }
108
+
109
+ @Test
110
+ fun `wildcard pattern matches js bundle variants`() {
111
+ assertTrue(matchesCachePattern("http://example.com/bundle.js", listOf("*.js")))
112
+ assertTrue(matchesCachePattern("http://example.com/bundle.abc123.js", listOf("*.js")))
113
+ assertTrue(matchesCachePattern("http://example.com/bundle.js?t=456", listOf("*.js")))
114
+ }
115
+
116
+ @Test
117
+ fun `wildcard pattern matching is case-insensitive`() {
118
+ assertTrue(matchesCachePattern("http://example.com/APP.CSS", listOf("*.css")))
119
+ assertTrue(matchesCachePattern("http://example.com/app.css", listOf("*.CSS")))
120
+ }
121
+
122
+ @Test
123
+ fun `wildcard pattern does not match an unrelated extension`() {
124
+ assertFalse(matchesCachePattern("http://example.com/app.png", listOf("*.css")))
125
+ }
126
+
127
+ @Test
128
+ fun `exact pattern matches full filename case-insensitively`() {
129
+ assertTrue(matchesCachePattern("http://example.com/manifest.json", listOf("manifest.json")))
130
+ assertTrue(matchesCachePattern("http://example.com/MANIFEST.JSON", listOf("manifest.json")))
131
+ }
132
+
133
+ @Test
134
+ fun `exact pattern does not match a different filename`() {
135
+ assertFalse(matchesCachePattern("http://example.com/other.json", listOf("manifest.json")))
136
+ }
137
+
138
+ @Test
139
+ fun `returns false when the path has an empty filename (trailing slash)`() {
140
+ assertFalse(matchesCachePattern("http://example.com/", listOf("*.css")))
141
+ }
142
+
143
+ @Test
144
+ fun `matches if any pattern in the list matches`() {
145
+ assertTrue(matchesCachePattern("http://example.com/app.css", listOf("*.js", "*.css", "*.png")))
146
+ }
147
+
148
+ @Test
149
+ fun `returns false when no pattern in the list matches`() {
150
+ assertFalse(matchesCachePattern("http://example.com/app.css", listOf("*.js", "*.png")))
151
+ }
152
+ }