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
@@ -1,304 +1,300 @@
1
1
  package io.yourname.androidproject
2
2
 
3
+ import android.content.ContentResolver
4
+ import android.content.Context
5
+ import android.database.Cursor
6
+ import android.net.Uri
7
+ import android.provider.OpenableColumns
8
+ import android.util.Base64
9
+ import android.webkit.MimeTypeMap
3
10
  import io.yourname.androidproject.utils.FileUtils
11
+ import org.junit.After
4
12
  import org.junit.Assert.*
13
+ import org.junit.Before
5
14
  import org.junit.Test
15
+ import org.mockito.MockedStatic
16
+ import org.mockito.Mockito.mockStatic
17
+ import org.mockito.kotlin.any
18
+ import org.mockito.kotlin.doReturn
19
+ import org.mockito.kotlin.eq
20
+ import org.mockito.kotlin.isNull
21
+ import org.mockito.kotlin.mock
22
+ import org.mockito.kotlin.whenever
23
+ import java.io.ByteArrayInputStream
24
+ import kotlin.io.path.createTempDirectory
6
25
 
7
26
  /**
8
- * Unit tests for FileUtils
27
+ * Unit tests for FileUtils.
9
28
  *
10
- * Tests cover:
11
- * - File size calculation (3 tests)
12
- * - MIME type detection (4 tests)
13
- * - URI to file path conversion (3 tests)
14
- * - File validation (2 tests)
29
+ * The previous version of this file imported FileUtils but never called
30
+ * a single method on it — assertions ran against a locally redefined
31
+ * formatFileSize/MIME-lookup instead of the real class, so it passed
32
+ * while contributing 0% coverage. Rewritten to call FileUtils directly,
33
+ * following the mockStatic(...) + mocked Context/Cursor pattern
34
+ * established in OfflineCacheServiceTest.kt.
15
35
  *
16
- * Total: 12 tests
17
- *
18
- * Note: Tests focus on testable logic and algorithms without Android Context
19
- * following the same pattern as CustomWebviewTest.kt
36
+ * Base64 and MimeTypeMap are Android SDK statics with no real
37
+ * implementation in a JVM unit test — mocked here so
38
+ * convertUriToBase64/getMimeType exercise FileUtils' actual logic.
20
39
  */
21
40
  class FileUtilsTest {
22
41
 
42
+ private lateinit var cacheDir: java.io.File
43
+ private lateinit var context: Context
44
+ private lateinit var contentResolver: ContentResolver
45
+ private lateinit var uri: Uri
46
+ private lateinit var base64Mock: MockedStatic<Base64>
47
+
48
+ @Before
49
+ fun setUp() {
50
+ cacheDir = createTempDirectory(prefix = "catalyst-fileutils-test").toFile()
51
+ contentResolver = mock()
52
+ context = mock {
53
+ on { getContentResolver() } doReturn contentResolver
54
+ on { getCacheDir() } doReturn cacheDir
55
+ }
56
+ uri = mock {
57
+ on { toString() } doReturn "content://fake/file"
58
+ }
59
+
60
+ base64Mock = mockStatic(Base64::class.java)
61
+ base64Mock.`when`<String> { Base64.encodeToString(any(), any()) }
62
+ .thenAnswer { invocation ->
63
+ val bytes = invocation.getArgument<ByteArray>(0)
64
+ java.util.Base64.getEncoder().encodeToString(bytes)
65
+ }
66
+ }
67
+
68
+ @After
69
+ fun tearDown() {
70
+ base64Mock.close()
71
+ cacheDir.deleteRecursively()
72
+ }
73
+
74
+ private fun stubQueryCursor(name: String?, size: Long?) {
75
+ val cursor: Cursor = mock {
76
+ on { getColumnIndex(OpenableColumns.DISPLAY_NAME) } doReturn (if (name != null) 0 else -1)
77
+ on { getColumnIndex(OpenableColumns.SIZE) } doReturn (if (size != null) 1 else -1)
78
+ on { moveToFirst() } doReturn true
79
+ on { getString(0) } doReturn name
80
+ on { getLong(1) } doReturn (size ?: 0L)
81
+ }
82
+ whenever(contentResolver.query(eq(uri), isNull(), isNull(), isNull(), isNull())) doReturn cursor
83
+ }
84
+
23
85
  // ============================================================
24
- // CATEGORY 1: File Size Calculation (3 tests)
86
+ // getFileSize
25
87
  // ============================================================
26
88
 
27
- /**
28
- * Test file size formatting helper
29
- * Validates correct conversion from bytes to human-readable format
30
- */
31
89
  @Test
32
- fun testFileSizeFormatting_bytes() {
33
- // Test bytes
34
- val size1 = 512L
35
- val formatted1 = formatFileSize(size1)
36
- assertEquals("512 B", formatted1)
37
-
38
- // Test KB
39
- val size2 = 2048L // 2 KB
40
- val formatted2 = formatFileSize(size2)
41
- assertEquals("2.00 KB", formatted2)
42
-
43
- // Test MB
44
- val size3 = 5242880L // 5 MB
45
- val formatted3 = formatFileSize(size3)
46
- assertEquals("5.00 MB", formatted3)
90
+ fun `getFileSize returns the size reported by the content resolver cursor`() {
91
+ stubQueryCursor(name = "photo.jpg", size = 5242880L)
92
+ assertEquals(5242880L, FileUtils.getFileSize(context, uri))
47
93
  }
48
94
 
49
- /**
50
- * Test file size limits validation
51
- * Validates max file size constraints (50 MB)
52
- */
53
95
  @Test
54
- fun testFileSizeLimits_withinLimit() {
55
- val maxSizeBytes = 50 * 1024 * 1024L // 50 MB
96
+ fun `getFileSize returns 0 when the cursor has no SIZE column`() {
97
+ stubQueryCursor(name = "photo.jpg", size = null)
98
+ assertEquals(0L, FileUtils.getFileSize(context, uri))
99
+ }
56
100
 
57
- // Test valid sizes
58
- val validSize1 = 1024L // 1 KB - should be valid
59
- assertTrue(validSize1 <= maxSizeBytes)
101
+ @Test
102
+ fun `getFileSize returns 0 when the query itself throws`() {
103
+ whenever(contentResolver.query(eq(uri), isNull(), isNull(), isNull(), isNull()))
104
+ .thenThrow(RuntimeException("boom"))
105
+ assertEquals(0L, FileUtils.getFileSize(context, uri))
106
+ }
60
107
 
61
- val validSize2 = 10 * 1024 * 1024L // 10 MB - should be valid
62
- assertTrue(validSize2 <= maxSizeBytes)
108
+ // ============================================================
109
+ // getFileName / getDisplayName
110
+ // ============================================================
63
111
 
64
- val validSize3 = maxSizeBytes // Exactly at limit - should be valid
65
- assertTrue(validSize3 <= maxSizeBytes)
112
+ @Test
113
+ fun `getFileName returns the display name reported by the cursor`() {
114
+ stubQueryCursor(name = "report.pdf", size = 100L)
115
+ assertEquals("report.pdf", FileUtils.getFileName(context, uri))
66
116
  }
67
117
 
68
- /**
69
- * Test file size exceeding limits
70
- * Validates rejection of files larger than 50 MB
71
- */
72
118
  @Test
73
- fun testFileSizeLimits_exceedsLimit() {
74
- val maxSizeBytes = 50 * 1024 * 1024L // 50 MB
75
-
76
- // Test invalid sizes
77
- val invalidSize1 = 51 * 1024 * 1024L // 51 MB - should be invalid
78
- assertFalse(invalidSize1 <= maxSizeBytes)
119
+ fun `getFileName returns unknown_file when the cursor has no DISPLAY_NAME column`() {
120
+ stubQueryCursor(name = null, size = 100L)
121
+ assertEquals("unknown_file", FileUtils.getFileName(context, uri))
122
+ }
79
123
 
80
- val invalidSize2 = 100 * 1024 * 1024L // 100 MB - should be invalid
81
- assertFalse(invalidSize2 <= maxSizeBytes)
124
+ @Test
125
+ fun `getDisplayName delegates to getFileName`() {
126
+ stubQueryCursor(name = "vacation.png", size = 10L)
127
+ assertEquals(FileUtils.getFileName(context, uri), FileUtils.getDisplayName(context, uri))
82
128
  }
83
129
 
84
130
  // ============================================================
85
- // CATEGORY 2: MIME Type Detection (4 tests)
131
+ // getMimeType
86
132
  // ============================================================
87
133
 
88
- /**
89
- * Test MIME type detection for common image formats
90
- * Validates detection of PNG, JPG, JPEG, GIF
91
- */
92
134
  @Test
93
- fun testDetectMimeType_imageFormats() {
94
- // Note: MimeTypeMap is mocked in unit tests, so we test the logic structure
95
- // In real scenarios, these would return actual MIME types
96
-
97
- val pngPath = "test_image.png"
98
- val jpgPath = "test_image.jpg"
99
- val jpegPath = "test_image.jpeg"
100
- val gifPath = "test_image.gif"
101
-
102
- // Test extension extraction logic
103
- assertEquals("png", pngPath.substringAfterLast(".", ""))
104
- assertEquals("jpg", jpgPath.substringAfterLast(".", ""))
105
- assertEquals("jpeg", jpegPath.substringAfterLast(".", ""))
106
- assertEquals("gif", gifPath.substringAfterLast(".", ""))
135
+ fun `getMimeType returns the content resolver's reported type when present`() {
136
+ whenever(contentResolver.getType(uri)) doReturn "image/png"
137
+ assertEquals("image/png", FileUtils.getMimeType(context, uri))
107
138
  }
108
139
 
109
- /**
110
- * Test MIME type detection for document formats
111
- * Validates detection of PDF, DOC, DOCX, TXT
112
- */
113
140
  @Test
114
- fun testDetectMimeType_documentFormats() {
115
- val pdfPath = "document.pdf"
116
- val docPath = "document.doc"
117
- val docxPath = "document.docx"
118
- val txtPath = "document.txt"
119
-
120
- // Test extension extraction logic
121
- assertEquals("pdf", pdfPath.substringAfterLast(".", ""))
122
- assertEquals("doc", docPath.substringAfterLast(".", ""))
123
- assertEquals("docx", docxPath.substringAfterLast(".", ""))
124
- assertEquals("txt", txtPath.substringAfterLast(".", ""))
141
+ fun `getMimeType falls back to extension lookup when resolver has no type`() {
142
+ whenever(contentResolver.getType(uri)) doReturn null
143
+ stubQueryCursor(name = "document.pdf", size = 10L)
144
+
145
+ val mimeTypeMap: MimeTypeMap = mock {
146
+ on { getMimeTypeFromExtension("pdf") } doReturn "application/pdf"
147
+ }
148
+ val mimeTypeMapMock = mockStatic(MimeTypeMap::class.java)
149
+ mimeTypeMapMock.`when`<MimeTypeMap> { MimeTypeMap.getSingleton() } doReturn mimeTypeMap
150
+ try {
151
+ assertEquals("application/pdf", FileUtils.getMimeType(context, uri))
152
+ } finally {
153
+ mimeTypeMapMock.close()
154
+ }
125
155
  }
126
156
 
127
- /**
128
- * Test MIME type detection for unknown/missing extensions
129
- * Validates fallback to star/star for unknown types
130
- */
131
157
  @Test
132
- fun testDetectMimeType_unknownExtension() {
133
- val noExtension = "filename_without_extension"
134
- val unknownExt = "file.xyz123"
158
+ fun `getMimeType returns star-star when resolver throws`() {
159
+ whenever(contentResolver.getType(uri)).thenThrow(RuntimeException("boom"))
160
+ assertEquals("*/*", FileUtils.getMimeType(context, uri))
161
+ }
135
162
 
136
- // Test fallback behavior for files without extensions
137
- val ext1 = noExtension.substringAfterLast(".", "")
138
- assertEquals("", ext1) // No extension found
163
+ // ============================================================
164
+ // convertUriToBase64
165
+ // ============================================================
139
166
 
140
- // Test extension extraction for unknown types
141
- val ext2 = unknownExt.substringAfterLast(".", "")
142
- assertEquals("xyz123", ext2)
167
+ @Test
168
+ fun `convertUriToBase64 encodes the stream contents`() {
169
+ val content = "hello world".toByteArray()
170
+ whenever(contentResolver.openInputStream(uri)) doReturn ByteArrayInputStream(content)
171
+
172
+ val result = FileUtils.convertUriToBase64(context, uri)
173
+
174
+ assertEquals(java.util.Base64.getEncoder().encodeToString(content), result)
143
175
  }
144
176
 
145
- /**
146
- * Test MIME type detection with special characters
147
- * Validates handling of complex file paths with dots
148
- */
149
177
  @Test
150
- fun testDetectMimeType_specialCases() {
151
- // Multiple dots in filename
152
- val multipleDots = "my.file.name.pdf"
153
- assertEquals("pdf", multipleDots.substringAfterLast(".", ""))
154
-
155
- // Hidden file (starts with dot)
156
- val hiddenFile = ".gitignore"
157
- assertEquals("gitignore", hiddenFile.substringAfterLast(".", ""))
158
-
159
- // Path with directories containing dots
160
- val pathWithDots = "/path/to.folder/file.jpg"
161
- assertEquals("jpg", pathWithDots.substringAfterLast(".", ""))
162
-
163
- // Uppercase extension
164
- val uppercaseExt = "IMAGE.PNG"
165
- assertEquals("PNG", uppercaseExt.substringAfterLast(".", ""))
178
+ fun `convertUriToBase64 returns null when the stream cannot be opened`() {
179
+ whenever(contentResolver.openInputStream(uri)) doReturn null
180
+ assertNull(FileUtils.convertUriToBase64(context, uri))
181
+ }
182
+
183
+ @Test
184
+ fun `convertUriToBase64 returns null when opening the stream throws`() {
185
+ whenever(contentResolver.openInputStream(uri)).thenThrow(RuntimeException("boom"))
186
+ assertNull(FileUtils.convertUriToBase64(context, uri))
166
187
  }
167
188
 
168
189
  // ============================================================
169
- // CATEGORY 3: URI to File Path Conversion (3 tests)
190
+ // cleanupTempFiles
170
191
  // ============================================================
171
192
 
172
- /**
173
- * Test filename cleaning for filesystem safety
174
- * Validates removal of special characters
175
- */
176
193
  @Test
177
- fun testFilenameCleaning_specialCharacters() {
178
- val unsafeFilename1 = "file name with spaces.pdf"
179
- val cleaned1 = unsafeFilename1.replace("[^a-zA-Z0-9._-]".toRegex(), "_")
180
- assertEquals("file_name_with_spaces.pdf", cleaned1)
181
-
182
- val unsafeFilename2 = "file@#$%name!.jpg"
183
- val cleaned2 = unsafeFilename2.replace("[^a-zA-Z0-9._-]".toRegex(), "_")
184
- assertEquals("file____name_.jpg", cleaned2)
185
-
186
- val unsafeFilename3 = "file/name\\with:slashes.txt"
187
- val cleaned3 = unsafeFilename3.replace("[^a-zA-Z0-9._-]".toRegex(), "_")
188
- assertEquals("file_name_with_slashes.txt", cleaned3)
189
- }
194
+ fun `cleanupTempFiles deletes files older than maxAgeMillis under accessible_files`() {
195
+ val accessibleDir = java.io.File(cacheDir, "accessible_files").apply { mkdirs() }
196
+ val oldFile = java.io.File(accessibleDir, "temp_old.bin").apply {
197
+ createNewFile()
198
+ setLastModified(System.currentTimeMillis() - (48 * 60 * 60 * 1000L))
199
+ }
190
200
 
191
- /**
192
- * Test temporary file naming with timestamp
193
- * Validates unique filename generation
194
- */
195
- @Test
196
- fun testTempFileNaming_withTimestamp() {
197
- val baseFilename = "upload.pdf"
198
- val timestamp1 = System.currentTimeMillis()
199
- val tempName1 = "temp_${timestamp1}_$baseFilename"
201
+ FileUtils.cleanupTempFiles(context, maxAgeMillis = 24 * 60 * 60 * 1000L)
200
202
 
201
- // Wait 1ms to ensure different timestamp
202
- Thread.sleep(1)
203
+ assertFalse(oldFile.exists())
204
+ }
203
205
 
204
- val timestamp2 = System.currentTimeMillis()
205
- val tempName2 = "temp_${timestamp2}_$baseFilename"
206
+ @Test
207
+ fun `cleanupTempFiles keeps files younger than maxAgeMillis`() {
208
+ val accessibleDir = java.io.File(cacheDir, "accessible_files").apply { mkdirs() }
209
+ val recentFile = java.io.File(accessibleDir, "temp_recent.bin").apply {
210
+ createNewFile()
211
+ setLastModified(System.currentTimeMillis() - (1 * 60 * 60 * 1000L))
212
+ }
206
213
 
207
- // Verify timestamps are different
208
- assertNotEquals(timestamp1, timestamp2)
209
- assertNotEquals(tempName1, tempName2)
214
+ FileUtils.cleanupTempFiles(context, maxAgeMillis = 24 * 60 * 60 * 1000L)
210
215
 
211
- // Verify format is correct
212
- assertTrue(tempName1.startsWith("temp_"))
213
- assertTrue(tempName1.endsWith("_upload.pdf"))
216
+ assertTrue(recentFile.exists())
217
+ recentFile.delete()
214
218
  }
215
219
 
216
- /**
217
- * Test directory structure for temporary files
218
- * Validates cache directory organization
219
- */
220
220
  @Test
221
- fun testTempFileDirectory_structure() {
222
- // Test accessible files directory structure
223
- val cacheDir = "/data/data/com.app/cache"
224
- val accessibleDir = "$cacheDir/accessible_files"
225
-
226
- assertTrue(accessibleDir.contains("cache"))
227
- assertTrue(accessibleDir.endsWith("accessible_files"))
228
-
229
- // Test downloaded files directory structure
230
- val downloadedDir = "$cacheDir/downloaded_files"
231
- assertTrue(downloadedDir.contains("cache"))
232
- assertTrue(downloadedDir.endsWith("downloaded_files"))
221
+ fun `cleanupTempFiles does nothing when accessible_files does not exist`() {
222
+ // No accessible_files dir created — should not throw.
223
+ FileUtils.cleanupTempFiles(context, maxAgeMillis = 24 * 60 * 60 * 1000L)
233
224
  }
234
225
 
235
226
  // ============================================================
236
- // CATEGORY 4: File Validation (2 tests)
227
+ // createTempFile
237
228
  // ============================================================
238
229
 
239
- /**
240
- * Test base64 size limit validation
241
- * Validates 10 MB limit for base64 conversion
242
- */
243
230
  @Test
244
- fun testBase64SizeLimit_validation() {
245
- val base64Limit = 10 * 1024 * 1024L // 10 MB
231
+ fun `createTempFile sanitizes unsafe characters from the filename`() {
232
+ val file = FileUtils.createTempFile(context, "my file (final)!.txt")
233
+ assertEquals("my_file__final__.txt", file.name)
234
+ assertEquals("downloaded_files", file.parentFile?.name)
235
+ }
236
+
237
+ @Test
238
+ fun `createTempFile respects a custom subDir`() {
239
+ val file = FileUtils.createTempFile(context, "a.txt", subDir = "custom_dir")
240
+ assertEquals("custom_dir", file.parentFile?.name)
241
+ }
246
242
 
247
- // Small file - should be eligible for base64
248
- val smallFile = 1024L // 1 KB
249
- assertTrue(smallFile <= base64Limit)
243
+ // ============================================================
244
+ // uriToFile
245
+ // ============================================================
250
246
 
251
- // Medium file - should be eligible for base64
252
- val mediumFile = 5 * 1024 * 1024L // 5 MB
253
- assertTrue(mediumFile <= base64Limit)
247
+ @Test
248
+ fun `uriToFile copies the stream into a real temp file`() {
249
+ val content = "file contents".toByteArray()
250
+ whenever(contentResolver.query(eq(uri), isNull(), isNull(), isNull(), isNull())) doReturn null
251
+ whenever(contentResolver.openInputStream(uri)) doReturn ByteArrayInputStream(content)
254
252
 
255
- // Exactly at limit - should be eligible
256
- val atLimit = base64Limit
257
- assertTrue(atLimit <= base64Limit)
253
+ val result = FileUtils.uriToFile(context, uri)
258
254
 
259
- // Large file - should NOT be eligible for base64
260
- val largeFile = 15 * 1024 * 1024L // 15 MB
261
- assertFalse(largeFile <= base64Limit)
255
+ assertNotNull(result)
256
+ assertTrue(result!!.exists())
257
+ assertArrayEquals(content, result.readBytes())
258
+ result.delete()
262
259
  }
263
260
 
264
- /**
265
- * Test cleanup of old temporary files
266
- * Validates 24-hour age threshold logic
267
- */
268
261
  @Test
269
- fun testTempFileCleanup_ageThreshold() {
270
- val maxAgeMillis = 24 * 60 * 60 * 1000L // 24 hours
271
- val currentTime = System.currentTimeMillis()
272
-
273
- // Recent file - should NOT be deleted
274
- val recentFileAge = currentTime - (1 * 60 * 60 * 1000L) // 1 hour old
275
- val recentFileAgeMillis = currentTime - recentFileAge
276
- assertTrue(recentFileAgeMillis < maxAgeMillis)
277
-
278
- // File at threshold - should NOT be deleted
279
- val thresholdFileAge = currentTime - maxAgeMillis
280
- val thresholdFileAgeMillis = currentTime - thresholdFileAge
281
- assertFalse(thresholdFileAgeMillis > maxAgeMillis) // Not old enough
282
-
283
- // Old file - should be deleted
284
- val oldFileAge = currentTime - (48 * 60 * 60 * 1000L) // 48 hours old
285
- val oldFileAgeMillis = currentTime - oldFileAge
286
- assertTrue(oldFileAgeMillis > maxAgeMillis)
262
+ fun `uriToFile returns null when opening the stream throws`() {
263
+ whenever(contentResolver.query(eq(uri), isNull(), isNull(), isNull(), isNull())) doReturn null
264
+ whenever(contentResolver.openInputStream(uri)).thenThrow(RuntimeException("boom"))
265
+
266
+ assertNull(FileUtils.uriToFile(context, uri))
287
267
  }
288
268
 
289
269
  // ============================================================
290
- // Helper Methods
270
+ // detectMimeType (pure, no Context)
291
271
  // ============================================================
292
272
 
293
- /**
294
- * Helper: Format file size in human-readable format
295
- * Mirrors BridgeUtils.formatFileSize logic
296
- */
297
- private fun formatFileSize(bytes: Long): String {
298
- return when {
299
- bytes < 1024 -> "$bytes B"
300
- bytes < 1024 * 1024 -> String.format("%.2f KB", bytes / 1024.0)
301
- else -> String.format("%.2f MB", bytes / (1024.0 * 1024.0))
273
+ @Test
274
+ fun `detectMimeType resolves a known extension via MimeTypeMap`() {
275
+ val mimeTypeMap: MimeTypeMap = mock {
276
+ on { getMimeTypeFromExtension("jpg") } doReturn "image/jpeg"
277
+ }
278
+ val mimeTypeMapMock = mockStatic(MimeTypeMap::class.java)
279
+ mimeTypeMapMock.`when`<MimeTypeMap> { MimeTypeMap.getSingleton() } doReturn mimeTypeMap
280
+ try {
281
+ assertEquals("image/jpeg", FileUtils.detectMimeType("/path/to/photo.jpg"))
282
+ } finally {
283
+ mimeTypeMapMock.close()
284
+ }
285
+ }
286
+
287
+ @Test
288
+ fun `detectMimeType falls back to star-star for an unrecognized extension`() {
289
+ val mimeTypeMap: MimeTypeMap = mock {
290
+ on { getMimeTypeFromExtension(any()) } doReturn null
291
+ }
292
+ val mimeTypeMapMock = mockStatic(MimeTypeMap::class.java)
293
+ mimeTypeMapMock.`when`<MimeTypeMap> { MimeTypeMap.getSingleton() } doReturn mimeTypeMap
294
+ try {
295
+ assertEquals("*/*", FileUtils.detectMimeType("/path/to/file.unknownext"))
296
+ } finally {
297
+ mimeTypeMapMock.close()
302
298
  }
303
299
  }
304
300
  }