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.
- package/changelog.md +185 -71
- package/dist/errors/generateDocs.js +100 -0
- package/dist/errors/index.js +243 -0
- package/dist/errors/registry.js +483 -0
- package/dist/errors-index.json +506 -0
- package/dist/globals.d.ts +62 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.jsx +5 -8
- package/dist/logger.js +1 -1
- package/dist/native/androidProject/app/build.gradle.kts +142 -7
- package/dist/native/androidProject/app/src/main/AndroidManifest.xml +4 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/NativeBridge.kt +2 -2
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/BarcodeDetector.kt +3 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/camera/CameraSessionManager.kt +3 -0
- package/dist/native/androidProject/app/src/main/java/io/yourname/androidproject/utils/FrameworkServerUtils.kt +43 -8
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/BridgeUtilsTest.kt +181 -1
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/CameraUtilsTest.kt +156 -236
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileSizeRouterUtilsTest.kt +471 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FileUtilsTest.kt +222 -226
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/FrameworkServerUtilsTest.kt +300 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/IntentUtilsTest.kt +244 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/MetricsMonitorTest.kt +359 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NativeBridgeTest.kt +108 -63
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/NotificationUtilsTest.kt +329 -385
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/OfflineCacheServiceTest.kt +302 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/PerfEventBufferTest.kt +133 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/UtilsKtTest.kt +152 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/WebCacheManagerTest.kt +145 -324
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/BarcodeDetectorTest.kt +132 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/HoldControllerTest.kt +155 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/TorchControllerTest.kt +150 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/VideoStreamStateMachineTest.kt +120 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/VideoStreamStateTest.kt +62 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/ViewfinderMapperTest.kt +143 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/camera/ZoomControllerTest.kt +421 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/design/DesignTokensTest.kt +207 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/plugins/PluginBridgeTest.kt +368 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/FridaDetectorTest.kt +193 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/SecurityCheckManagerTest.kt +260 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/security/SecurityCheckSchedulerTest.kt +166 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/DeviceInfoUtilsTest.kt +167 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/DownloadUtilsTest.kt +197 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/NetworkMonitorTest.kt +271 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/PushNotificationUtilsTest.kt +119 -0
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/utils/SafeAreaUtilsTest.kt +108 -0
- package/dist/native/androidProject/build.gradle.kts +1 -0
- package/dist/native/androidProject/gradle/libs.versions.toml +26 -0
- package/dist/native/androidProject/settings.gradle.kts +11 -8
- package/dist/native/androidSetup.js +2 -2
- package/dist/native/bridge/WebBridge.d.ts +98 -0
- package/dist/native/bridge/WebBridge.js +40 -6
- package/dist/native/bridge/constants/NativeInterfaces.d.ts +391 -0
- package/dist/native/bridge/errors.d.ts +67 -0
- package/dist/native/bridge/errors.js +66 -151
- package/dist/native/bridge/hooks/index.d.ts +13 -0
- package/dist/native/bridge/hooks/index.js +0 -6
- package/dist/native/bridge/hooks/useAI.d.ts +1 -0
- package/dist/native/bridge/hooks/useAI.js +3 -1
- package/dist/native/bridge/hooks/useCamera.d.ts +86 -0
- package/dist/native/bridge/hooks/useCamera.js +35 -6
- package/dist/native/bridge/hooks/useDataProtection.d.ts +17 -0
- package/dist/native/bridge/hooks/useDeviceInfo.d.ts +34 -0
- package/dist/native/bridge/hooks/useDeviceInfo.js +5 -0
- package/dist/native/bridge/hooks/useFilePicker.d.ts +53 -0
- package/dist/native/bridge/hooks/useFilePicker.js +13 -0
- package/dist/native/bridge/hooks/useGoogleSignIn.d.ts +12 -0
- package/dist/native/bridge/hooks/useHapticFeedback.d.ts +76 -0
- package/dist/native/bridge/hooks/useHapticFeedback.js +6 -1
- package/dist/native/bridge/hooks/useIntent.d.ts +33 -0
- package/dist/native/bridge/hooks/useIntent.js +2 -0
- package/dist/native/bridge/hooks/useNativeTransition.d.ts +53 -0
- package/dist/native/bridge/hooks/useNativeTransition.js +12 -5
- package/dist/native/bridge/hooks/useNetworkStatus.d.ts +22 -0
- package/dist/native/bridge/hooks/useNetworkStatus.js +7 -0
- package/dist/native/bridge/hooks/useNotification.d.ts +83 -0
- package/dist/native/bridge/hooks/useNotification.js +81 -34
- package/dist/native/bridge/hooks/useSafeArea.d.ts +12 -0
- package/dist/native/bridge/hooks/useSafeArea.js +8 -0
- package/dist/native/bridge/hooks/useVideoStream.d.ts +67 -0
- package/dist/native/bridge/hooks/useVideoStream.js +38 -52
- package/dist/native/bridge/hooks/utils.d.ts +4 -0
- package/dist/native/bridge/hooks.d.ts +1 -0
- package/dist/native/bridge/perf/collectors/ApiCollector.d.ts +30 -0
- package/dist/native/bridge/perf/collectors/ApiCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/CacheCollector.d.ts +43 -0
- package/dist/native/bridge/perf/collectors/CacheCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/HookCollector.d.ts +37 -0
- package/dist/native/bridge/perf/collectors/HookCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/InsightsCollector.d.ts +103 -0
- package/dist/native/bridge/perf/collectors/InsightsCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/InteractionCollector.d.ts +53 -0
- package/dist/native/bridge/perf/collectors/InteractionCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/KeyboardCollector.d.ts +30 -0
- package/dist/native/bridge/perf/collectors/KeyboardCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/NavigationCollector.d.ts +37 -0
- package/dist/native/bridge/perf/collectors/NavigationCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/NetworkTimingCollector.d.ts +30 -0
- package/dist/native/bridge/perf/collectors/NetworkTimingCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/RenderCollector.d.ts +47 -0
- package/dist/native/bridge/perf/collectors/RenderCollector.js +8 -0
- package/dist/native/bridge/perf/collectors/ScrollCollector.d.ts +29 -0
- package/dist/native/bridge/perf/collectors/ScrollCollector.js +4 -0
- package/dist/native/bridge/perf/collectors/ThreadCollector.d.ts +31 -0
- package/dist/native/bridge/perf/collectors/ThreadCollector.js +4 -0
- package/dist/native/bridge/perf/core/constants.d.ts +76 -0
- package/dist/native/bridge/perf/core/measure.d.ts +39 -0
- package/dist/native/bridge/perf/core/measure.js +4 -0
- package/dist/native/bridge/perf/core/session.d.ts +49 -0
- package/dist/native/bridge/perf/core/session.js +4 -0
- package/dist/native/bridge/perf/core/store.d.ts +37 -0
- package/dist/native/bridge/perf/core/store.js +4 -0
- package/dist/native/bridge/perf/core/summary.d.ts +60 -0
- package/dist/native/bridge/perf/core/traceExport.d.ts +10 -0
- package/dist/native/bridge/perf/core/waterfalls.d.ts +2 -0
- package/dist/native/bridge/perf/index.d.ts +63 -0
- package/dist/native/bridge/perf/index.js +4 -0
- package/dist/native/bridge/safeArea.d.ts +11 -0
- package/dist/native/bridge/safeArea.js +2 -0
- package/dist/native/bridge/useBaseHook.d.ts +137 -0
- package/dist/native/bridge/useBaseHook.js +34 -4
- package/dist/native/bridge/utils/CameraUtils.d.ts +94 -0
- package/dist/native/bridge/utils/CameraUtils.js +4 -0
- package/dist/native/bridge/utils/FileObjectConverter.d.ts +36 -0
- package/dist/native/bridge/utils/NativeBridge.d.ts +270 -0
- package/dist/native/bridge/utils/NativeBridge.js +10 -0
- package/dist/native/buildAndroid/index.js +1 -1
- package/dist/native/buildAppAndroid.js +9 -0
- package/dist/native/buildAppIos.js +16 -1
- package/dist/native/buildErrorFormat.js +57 -0
- package/dist/native/buildIos/config.js +15 -1
- package/dist/native/buildIos/index.js +1 -1
- package/dist/native/buildIos/plugins.js +10 -1
- package/dist/native/iosnativeWebView/CoreLogic/Package.swift +36 -0
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Camera/VideoStreamState.swift +1 -1
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Camera/VideoStreamStateMachine.swift +6 -4
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/BootTimingUtility.swift +3 -3
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/FrameworkServerUtils.swift +14 -9
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NetworkMonitor.swift +11 -9
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NotificationHandlerProvider.swift +1 -1
- package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/URLWhitelistManager.swift +7 -7
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/AnyCodableTests.swift +154 -0
- package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/BootTimingUtilityTests.swift +23 -5
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/CatalystConstantsTests.swift +192 -0
- package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/ConfigMappingTests.swift +1 -1
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/FrameworkServerUtilsErrorPathTests.swift +106 -0
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/FrameworkServerUtilsLoopbackTests.swift +243 -0
- package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/FrameworkServerUtilsTests.swift +1 -1
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/NetworkMonitorTests.swift +119 -0
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/NotificationHandlerProviderTests.swift +59 -0
- package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/NotificationHandlerTests.swift +1 -1
- package/dist/native/iosnativeWebView/{iosnativeWebViewTests → CoreLogic/Tests/CatalystCoreLogicTests}/URLWhitelistManagerTests.swift +1 -1
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/VideoStreamStateMachineTests.swift +171 -0
- package/dist/native/iosnativeWebView/CoreLogic/Tests/CatalystCoreLogicTests/VideoStreamStateTests.swift +91 -0
- package/dist/native/iosnativeWebView/Package.swift +10 -9
- package/dist/native/iosnativeWebView/Sources/CatalystNotifications/LocalNotificationHandler.swift +7 -2
- package/dist/native/iosnativeWebView/Sources/CatalystNotifications/NotificationManager.swift +1 -2
- package/dist/native/iosnativeWebView/Sources/CatalystNotifications/PushNotificationHandler.swift +5 -4
- package/dist/native/iosnativeWebView/Sources/Core/Camera/CameraSessionManager.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/HoldController.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/Camera/NativeCameraManager.swift +6 -5
- package/dist/native/iosnativeWebView/Sources/Core/Camera/ZoomController.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/Plugins/PluginBridge.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/CacheManager.swift +31 -30
- package/dist/native/iosnativeWebView/Sources/Core/Utils/CameraTransportUtils.swift +2 -1
- package/dist/native/iosnativeWebView/Sources/Core/Utils/DeviceInfoUtils.swift +1 -1
- package/dist/native/iosnativeWebView/Sources/Core/Utils/FilePickerHandler.swift +22 -19
- package/dist/native/iosnativeWebView/Sources/Core/Utils/ImageHandler.swift +40 -39
- package/dist/native/iosnativeWebView/Sources/Core/Utils/PerfEventBuffer.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/Utils/ResourceURLProtocol.swift +15 -15
- package/dist/native/iosnativeWebView/Sources/Core/Utils/WebKitConfig.swift +4 -3
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeCommandHandler.swift +6 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeDelegateHandler.swift +1 -0
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeFileHandler.swift +2 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeJavaScriptInterface.swift +2 -1
- package/dist/native/iosnativeWebView/Sources/Core/WebView/BridgeMessageValidator.swift +3 -5
- package/dist/native/iosnativeWebView/Sources/Core/WebView/NativeBridge.swift +20 -13
- package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashView.swift +6 -5
- package/dist/native/iosnativeWebView/Sources/Core/WebView/SplashViewModel.swift +14 -13
- package/dist/native/iosnativeWebView/Sources/Core/WebView/TransitionManager.swift +3 -3
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebView.swift +9 -8
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewModel.swift +7 -6
- package/dist/native/iosnativeWebView/Sources/Core/WebView/WebViewNavigationDelegate.swift +24 -25
- package/dist/native/iosnativeWebView/iosnativeWebView/AppDelegate.swift +6 -1
- package/dist/native/iosnativeWebView/iosnativeWebView/ContentView.swift +1 -0
- package/dist/native/iosnativeWebView/iosnativeWebView/iosnativeWebViewApp.swift +4 -0
- package/dist/native/iosnativeWebView/iosnativeWebView.xcodeproj/project.pbxproj +2 -22
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/BridgeMessageValidatorTests.swift +1 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/CacheManagerTests.swift +1 -0
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/FilePickerHandlerTests.swift +2 -1
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/NativeBridgeTests.swift +15 -9
- package/dist/native/iosnativeWebView/iosnativeWebViewTests/WebViewTests.swift +1 -0
- package/dist/native/setupEmulatorIos.js +2 -2
- package/dist/native/terminalProgress.js +17 -17
- package/dist/router/ClientRouter.d.ts +8 -0
- package/dist/router/ClientRouter.js +13 -11
- package/dist/router/ServerRouter.d.ts +13 -0
- package/dist/router/ServerRouter.js +19 -7
- package/dist/scripts/build.js +14 -2
- package/dist/scripts/loadEnvironmentVariables.js +3 -2
- package/dist/scripts/plugins.js +1 -1
- package/dist/scripts/preServerInit.js +49 -3
- package/dist/scripts/preflight.js +111 -0
- package/dist/scripts/scriptUtils.js +46 -1
- package/dist/scripts/serve.js +7 -3
- package/dist/scripts/start.js +8 -3
- package/dist/server/expressServer.d.ts +1 -0
- package/dist/server/expressServer.js +247 -271
- package/dist/server/manifestCache.d.ts +2 -0
- package/dist/server/manifestCache.js +25 -33
- package/dist/server/renderer/ChunkExtractor.d.ts +44 -0
- package/dist/server/renderer/ChunkExtractor.js +113 -125
- package/dist/server/renderer/document/Body.d.ts +21 -0
- package/dist/server/renderer/document/Body.jsx +45 -45
- package/dist/server/renderer/document/FastRefresh.d.ts +3 -0
- package/dist/server/renderer/document/FastRefresh.jsx +7 -13
- package/dist/server/renderer/document/Head.d.ts +20 -0
- package/dist/server/renderer/document/Head.jsx +32 -32
- package/dist/server/renderer/document/index.d.ts +2 -0
- package/dist/server/renderer/document/index.jsx +2 -2
- package/dist/server/renderer/extract.d.ts +67 -0
- package/dist/server/renderer/extract.js +92 -89
- package/dist/server/renderer/handler.d.ts +2 -0
- package/dist/server/renderer/handler.jsx +401 -404
- package/dist/server/renderer/index.d.ts +2 -0
- package/dist/server/renderer/index.js +5 -8
- package/dist/server/renderer/render.d.ts +35 -0
- package/dist/server/renderer/render.js +25 -32
- package/dist/server/utils/botDetectionMiddleware.d.ts +1 -0
- package/dist/server/utils/botDetectionMiddleware.js +33 -23
- package/dist/server/utils/cjsRequire.d.ts +8 -0
- package/dist/server/utils/cjsRequire.js +2 -2
- package/dist/server/utils/userAgentUtil.d.ts +7 -0
- package/dist/server/utils/userAgentUtil.js +68 -71
- package/dist/server/utils/validator.d.ts +31 -0
- package/dist/server/utils/validator.js +133 -158
- package/dist/vite/inject-cache-key-plugin.js +3 -27
- package/dist/vite/moduleAliases.js +36 -0
- package/dist/vite/node-loader.mjs +2 -29
- package/dist/vite/resolveDevServerConfig.js +4 -1
- package/dist/vite/vite.config.js +2 -29
- package/dist/web-router/components/MetaTag.d.ts +4 -0
- package/dist/web-router/components/MetaTag.jsx +26 -28
- package/dist/web-router/components/MetaTag.test.jsx +100 -0
- package/dist/web-router/components/RouterDataProvider.d.ts +96 -0
- package/dist/web-router/components/RouterDataProvider.jsx +260 -259
- package/dist/web-router/components/RouterDataProvider.server.test.jsx +98 -0
- package/dist/web-router/components/RouterDataProvider.test.jsx +310 -0
- package/dist/web-router/components/Split.d.ts +74 -0
- package/dist/web-router/components/Split.jsx +147 -156
- package/dist/web-router/components/Split.server.test.jsx +52 -0
- package/dist/web-router/components/Split.test.jsx +167 -0
- package/dist/web-router/components/SplitInview.d.ts +22 -0
- package/dist/web-router/components/SplitInview.jsx +78 -89
- package/dist/web-router/components/SplitInview.test.jsx +112 -0
- package/dist/web-router/components/SsrRequestContext.d.ts +20 -0
- package/dist/web-router/components/SsrRequestContext.jsx +17 -7
- package/dist/web-router/components/SsrRequestContext.test.jsx +53 -0
- package/dist/web-router/context.d.ts +15 -0
- package/dist/web-router/context.jsx +5 -5
- package/dist/web-router/context.test.jsx +10 -0
- package/dist/web-router/utils/metaDataUtils.d.ts +24 -0
- package/dist/web-router/utils/metaDataUtils.jsx +86 -86
- package/dist/web-router/utils/metaDataUtils.test.jsx +188 -0
- package/dist/web-router/vitest.setup.js +12 -0
- package/mcp_v2/knowledge-base.json +115 -17
- package/mcp_v2/mcp.js +141 -3
- package/mcp_v2/package-lock.json +2031 -0
- package/mcp_v2/package.json +13 -2
- package/mcp_v2/scripts/copyErrorsIndex.cjs +34 -0
- package/mcp_v2/test/errors.test.ts +123 -0
- package/mcp_v2/test/github.test.ts +177 -0
- package/mcp_v2/tools/errors.d.ts +20 -0
- package/mcp_v2/tools/errors.js +63 -0
- package/mcp_v2/tools/github.d.ts +41 -0
- package/mcp_v2/tools/github.js +231 -0
- package/mcp_v2/vitest.config.mjs +22 -0
- package/package.json +60 -11
- package/test/errors/docsDrift.test.ts +113 -0
- package/test/errors/index.test.ts +200 -0
- package/test/errors/registry.test.ts +108 -0
- package/test/scripts/preflight.test.ts +118 -0
- package/test/server/ChunkExtractor.test.ts +97 -0
- package/test/server/extract.test.ts +128 -0
- package/test/server/fixtures/template/server/document.jsx +19 -0
- package/test/server/fixtures/template/server/index.js +10 -0
- package/test/server/fixtures/template/src/js/containers/App/index.jsx +33 -0
- package/test/server/fixtures/template/src/js/routes/index.jsx +72 -0
- package/test/server/fixtures/template/src/js/routes/utils.jsx +39 -0
- package/test/server/fixtures/template/src/js/store/index.js +19 -0
- package/test/server/handler.assets.test.ts +330 -0
- package/test/server/handler.test.ts +208 -0
- package/test/server/manifestCache.test.ts +71 -0
- package/test/server/render.test.ts +71 -0
- package/test/server/scriptUtils.test.ts +103 -0
- package/test/server/userAgentUtil.test.ts +48 -0
- package/test/server/validator.test.ts +225 -0
- package/test/vite/resolveDevServerConfig.test.js +4 -1
- package/tsconfig.test.json +23 -0
- package/vitest.config.ts +93 -0
- package/dist/caching.js +0 -1
- package/dist/native/androidProject/app/src/test/java/io/yourname/androidproject/DeviceInfoUtilsTest.kt +0 -187
- package/dist/native/bridge/standardizedHooks.js +0 -611
- package/dist/router.js +0 -1
- package/dist/scripts/validator.js +0 -149
- package/dist/web-router/hooks.jsx +0 -72
- package/dist/web-router/types/components/MetaTag.d.ts +0 -2
- package/dist/web-router/types/components/RouterDataProvider.d.ts +0 -95
- package/dist/web-router/types/context.d.ts +0 -14
- package/dist/web-router/types/hooks.d.ts +0 -1
- package/dist/web-router/types/index.d.ts +0 -3
- package/dist/web-router/types/utils/metaDataUtils.d.ts +0 -40
- /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Constants/CatalystConstants.swift +0 -0
- /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Constants/ConfigConstants.swift +0 -0
- /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Notifications/NotificationModels.swift +0 -0
- /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Protocols/NotificationHandlerProtocol.swift +0 -0
- /package/dist/native/iosnativeWebView/{Sources/Core → CoreLogic/Sources/CatalystCoreLogic}/Utils/NullNotificationHandler.swift +0 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
package io.yourname.androidproject.utils
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.util.DisplayMetrics
|
|
5
|
+
import android.view.Display
|
|
6
|
+
import android.view.WindowManager
|
|
7
|
+
import io.yourname.androidproject.security.SecurityCheckManager
|
|
8
|
+
import org.junit.Assert.assertEquals
|
|
9
|
+
import org.junit.Assert.assertFalse
|
|
10
|
+
import org.junit.Assert.assertTrue
|
|
11
|
+
import org.junit.Test
|
|
12
|
+
import org.mockito.kotlin.any
|
|
13
|
+
import org.mockito.kotlin.doAnswer
|
|
14
|
+
import org.mockito.kotlin.doReturn
|
|
15
|
+
import org.mockito.kotlin.mock
|
|
16
|
+
import java.util.Properties
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Unit tests for DeviceInfoUtils (Android coverage batch 4), previously
|
|
20
|
+
* entirely untested (0/~40 lines).
|
|
21
|
+
*
|
|
22
|
+
* NOTE: A root-package `io.yourname.androidproject.DeviceInfoUtilsTest`
|
|
23
|
+
* already existed before this batch (contract/shape assertions only, no
|
|
24
|
+
* mocking, doesn't call getDeviceInfo() at all). Per this task's package-
|
|
25
|
+
* matching rule, this new file lives in the `.utils` package alongside its
|
|
26
|
+
* source and is a distinct class from the old one -- both compile and run
|
|
27
|
+
* side by side. The old file is left untouched ("test files only, don't
|
|
28
|
+
* touch other files") but is a candidate for a follow-up cleanup/removal
|
|
29
|
+
* since this file supersedes it with real mock-driven coverage.
|
|
30
|
+
*
|
|
31
|
+
* Empirically confirmed facts this suite relies on (see task history):
|
|
32
|
+
* - Under this project's mockable android.jar
|
|
33
|
+
* (testOptions.unitTests.isReturnDefaultValues = true), Build.VERSION.SDK_INT
|
|
34
|
+
* is 0, so getDisplayMetrics() always takes the pre-R (`else`) branch:
|
|
35
|
+
* windowManager.defaultDisplay.getMetrics(displayMetrics). The R+
|
|
36
|
+
* WindowMetrics branch (Build.VERSION.SDK_INT >= R) is unreachable under
|
|
37
|
+
* this test setup and is NOT covered here -- Build.VERSION.SDK_INT is a
|
|
38
|
+
* static final int and not mockable (project-wide rule #1).
|
|
39
|
+
* - Build.MODEL / Build.MANUFACTURER are null under the mockable jar
|
|
40
|
+
* (also unmockable per rule #1). Verified empirically (see a throwaway
|
|
41
|
+
* org.json.JSONObject script) that `JSONObject.put(key, null)` does NOT
|
|
42
|
+
* store a null value -- it OMITS the key entirely (`has(key)` returns
|
|
43
|
+
* false afterwards). So this suite does NOT assert presence/absence of
|
|
44
|
+
* "model"/"manufacturer" one way or the other as a stable contract --
|
|
45
|
+
* only that the fields that DON'T depend on Build.* (screenWidth/
|
|
46
|
+
* screenHeight/screenDensity/appInfo/security) are correct.
|
|
47
|
+
* - SecurityCheckManager.getLatestSecurityResults(context) is exercised
|
|
48
|
+
* with a plain mocked Context (no SharedPreferences stubbing): its
|
|
49
|
+
* internal try/catch absorbs the NPE from an unstubbed
|
|
50
|
+
* getSharedPreferences() call and returns null, so getDeviceInfo() takes
|
|
51
|
+
* the "security pending" branch. This mirrors
|
|
52
|
+
* SecurityCheckManagerTest's own "returns null when nothing has been
|
|
53
|
+
* saved yet" / "throws" coverage, so SecurityCheckManager's internals
|
|
54
|
+
* are not re-tested here -- only that DeviceInfoUtils wires the pending
|
|
55
|
+
* fallback correctly.
|
|
56
|
+
*/
|
|
57
|
+
class DeviceInfoUtilsTest {
|
|
58
|
+
|
|
59
|
+
private fun mockContext(widthPx: Int, heightPx: Int, density: Float): Context {
|
|
60
|
+
val display = mock<Display> {
|
|
61
|
+
on { getMetrics(any()) } doAnswer { invocation ->
|
|
62
|
+
val dm = invocation.getArgument<DisplayMetrics>(0)
|
|
63
|
+
dm.widthPixels = widthPx
|
|
64
|
+
dm.heightPixels = heightPx
|
|
65
|
+
dm.density = density
|
|
66
|
+
Unit
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
val windowManager = mock<WindowManager> {
|
|
70
|
+
on { getDefaultDisplay() } doReturn display
|
|
71
|
+
}
|
|
72
|
+
return mock {
|
|
73
|
+
on { getSystemService(Context.WINDOW_SERVICE) } doReturn windowManager
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ============================================================
|
|
78
|
+
// Screen dimension fields (via the pre-R WindowManager path)
|
|
79
|
+
// ============================================================
|
|
80
|
+
|
|
81
|
+
@Test
|
|
82
|
+
fun `getDeviceInfo populates screen fields from the deprecated defaultDisplay path`() {
|
|
83
|
+
val context = mockContext(widthPx = 1080, heightPx = 2280, density = 2.75f)
|
|
84
|
+
|
|
85
|
+
val info = DeviceInfoUtils.getDeviceInfo(context)
|
|
86
|
+
|
|
87
|
+
assertEquals(1080, info.getInt("screenWidth"))
|
|
88
|
+
assertEquals(2280, info.getInt("screenHeight"))
|
|
89
|
+
assertEquals(2.75, info.getDouble("screenDensity"), 0.001)
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
@Test
|
|
93
|
+
fun `getDeviceInfo always sets platform to android`() {
|
|
94
|
+
val context = mockContext(widthPx = 100, heightPx = 200, density = 1.0f)
|
|
95
|
+
|
|
96
|
+
val info = DeviceInfoUtils.getDeviceInfo(context)
|
|
97
|
+
|
|
98
|
+
assertEquals("android", info.getString("platform"))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ============================================================
|
|
102
|
+
// appInfo (from optional Properties)
|
|
103
|
+
// ============================================================
|
|
104
|
+
|
|
105
|
+
@Test
|
|
106
|
+
fun `getDeviceInfo includes appInfo from properties when present`() {
|
|
107
|
+
val context = mockContext(widthPx = 100, heightPx = 200, density = 1.0f)
|
|
108
|
+
val properties = Properties().apply { setProperty("appInfo", "MyApp v1.2.3") }
|
|
109
|
+
|
|
110
|
+
val info = DeviceInfoUtils.getDeviceInfo(context, properties)
|
|
111
|
+
|
|
112
|
+
assertEquals("MyApp v1.2.3", info.getString("appInfo"))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
@Test
|
|
116
|
+
fun `getDeviceInfo sets appInfo to JSON null when properties is null`() {
|
|
117
|
+
val context = mockContext(widthPx = 100, heightPx = 200, density = 1.0f)
|
|
118
|
+
|
|
119
|
+
val info = DeviceInfoUtils.getDeviceInfo(context, null)
|
|
120
|
+
|
|
121
|
+
assertTrue(info.has("appInfo"))
|
|
122
|
+
assertTrue(info.isNull("appInfo"))
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@Test
|
|
126
|
+
fun `getDeviceInfo sets appInfo to JSON null when properties lacks the key`() {
|
|
127
|
+
val context = mockContext(widthPx = 100, heightPx = 200, density = 1.0f)
|
|
128
|
+
val properties = Properties().apply { setProperty("unrelated", "value") }
|
|
129
|
+
|
|
130
|
+
val info = DeviceInfoUtils.getDeviceInfo(context, properties)
|
|
131
|
+
|
|
132
|
+
assertTrue(info.isNull("appInfo"))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ============================================================
|
|
136
|
+
// security field
|
|
137
|
+
// ============================================================
|
|
138
|
+
|
|
139
|
+
@Test
|
|
140
|
+
fun `getDeviceInfo marks security pending when no results are saved yet`() {
|
|
141
|
+
val context = mockContext(widthPx = 100, heightPx = 200, density = 1.0f)
|
|
142
|
+
|
|
143
|
+
// No SharedPreferences stubbing -- SecurityCheckManager.getLatestSecurityResults
|
|
144
|
+
// catches the resulting failure internally and returns null (matches
|
|
145
|
+
// SecurityCheckManagerTest's own "returns null" precedent).
|
|
146
|
+
val info = DeviceInfoUtils.getDeviceInfo(context)
|
|
147
|
+
|
|
148
|
+
assertTrue(info.has("security"))
|
|
149
|
+
assertTrue(info.getJSONObject("security").getBoolean("pending"))
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// ============================================================
|
|
153
|
+
// Error path
|
|
154
|
+
// ============================================================
|
|
155
|
+
|
|
156
|
+
@Test
|
|
157
|
+
fun `getDeviceInfo returns an error object when getSystemService throws`() {
|
|
158
|
+
val context: Context = mock {
|
|
159
|
+
on { getSystemService(Context.WINDOW_SERVICE) } doAnswer { throw RuntimeException("boom") }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
val info = DeviceInfoUtils.getDeviceInfo(context)
|
|
163
|
+
|
|
164
|
+
assertTrue(info.has("error"))
|
|
165
|
+
assertFalse(info.has("screenWidth"))
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
package io.yourname.androidproject.utils
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import org.junit.Assert.*
|
|
5
|
+
import org.junit.Test
|
|
6
|
+
import org.mockito.kotlin.doReturn
|
|
7
|
+
import org.mockito.kotlin.mock
|
|
8
|
+
import java.io.File
|
|
9
|
+
import java.util.concurrent.TimeUnit
|
|
10
|
+
import kotlin.io.path.createTempDirectory
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Unit tests for DownloadUtils (Android coverage batch 3), previously
|
|
14
|
+
* entirely untested (0/114 lines).
|
|
15
|
+
*
|
|
16
|
+
* Context.cacheDir is mocked to a real temp directory, matching
|
|
17
|
+
* OfflineCacheServiceTest/WebCacheManagerTest's established pattern.
|
|
18
|
+
*
|
|
19
|
+
* Deliberately out of scope: downloadFile/getRemoteFileSize (real
|
|
20
|
+
* java.net network calls) and downloadFileWithCallback/
|
|
21
|
+
* validateDownloadRequirements (both call downloadFile/getRemoteFileSize
|
|
22
|
+
* internally, so exercising them meaningfully would require the same real
|
|
23
|
+
* network dependency) -- consistent with this project's standing stance
|
|
24
|
+
* (see WebCacheManagerTest's fetchAndCacheResourceBlocking note) against
|
|
25
|
+
* mocking java.net deeply enough to test the mock instead of the code.
|
|
26
|
+
* Covered instead: isValidUrl, extractFileNameFromUrl,
|
|
27
|
+
* hasSufficientStorage, and cleanupDownloadedFiles -- all pure logic or
|
|
28
|
+
* simple Context.cacheDir-backed file I/O.
|
|
29
|
+
*/
|
|
30
|
+
class DownloadUtilsTest {
|
|
31
|
+
|
|
32
|
+
private lateinit var cacheDir: File
|
|
33
|
+
private lateinit var context: Context
|
|
34
|
+
|
|
35
|
+
private fun freshContext(cacheDirOverride: File = cacheDir): Context = mock {
|
|
36
|
+
on { getCacheDir() } doReturn cacheDirOverride
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@org.junit.Before
|
|
40
|
+
fun setUp() {
|
|
41
|
+
cacheDir = createTempDirectory(prefix = "catalyst-download-test").toFile()
|
|
42
|
+
context = freshContext()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
@org.junit.After
|
|
46
|
+
fun tearDown() {
|
|
47
|
+
cacheDir.deleteRecursively()
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ============================================================
|
|
51
|
+
// isValidUrl
|
|
52
|
+
// ============================================================
|
|
53
|
+
|
|
54
|
+
@Test
|
|
55
|
+
fun `isValidUrl accepts a well-formed http url`() {
|
|
56
|
+
assertTrue(DownloadUtils.isValidUrl("http://example.com/file.pdf"))
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
@Test
|
|
60
|
+
fun `isValidUrl accepts a well-formed https url`() {
|
|
61
|
+
assertTrue(DownloadUtils.isValidUrl("https://example.com/file.pdf"))
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
@Test
|
|
65
|
+
fun `isValidUrl rejects a blank url`() {
|
|
66
|
+
assertFalse(DownloadUtils.isValidUrl(""))
|
|
67
|
+
assertFalse(DownloadUtils.isValidUrl(" "))
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
@Test
|
|
71
|
+
fun `isValidUrl rejects urls without http or https scheme`() {
|
|
72
|
+
assertFalse(DownloadUtils.isValidUrl("ftp://example.com/file.pdf"))
|
|
73
|
+
assertFalse(DownloadUtils.isValidUrl("file:///local/path"))
|
|
74
|
+
assertFalse(DownloadUtils.isValidUrl("example.com/file.pdf"))
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@Test
|
|
78
|
+
fun `isValidUrl accepts a url with an empty host after the scheme`() {
|
|
79
|
+
// Verified against the real java.net.URL parser: "https://" alone
|
|
80
|
+
// parses successfully (an empty host is legal per the URL spec),
|
|
81
|
+
// and a space in the host doesn't throw either -- java.net.URL is
|
|
82
|
+
// far more lenient than the class's own KDoc ("throws if malformed")
|
|
83
|
+
// implies. Since isValidUrl already gates on startsWith("http://")/
|
|
84
|
+
// ("https://") before calling URL(...), there is no reachable input
|
|
85
|
+
// that passes that prefix check yet still throws inside URL(...) --
|
|
86
|
+
// the try/catch's catch branch is effectively unreachable for any
|
|
87
|
+
// string this method is actually called with. This test documents
|
|
88
|
+
// that finding rather than asserting a false-negative expectation.
|
|
89
|
+
assertTrue(DownloadUtils.isValidUrl("https://"))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ============================================================
|
|
93
|
+
// extractFileNameFromUrl
|
|
94
|
+
// ============================================================
|
|
95
|
+
|
|
96
|
+
@Test
|
|
97
|
+
fun `extractFileNameFromUrl returns the last path segment`() {
|
|
98
|
+
assertEquals("file.pdf", DownloadUtils.extractFileNameFromUrl("https://example.com/path/to/file.pdf"))
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@Test
|
|
102
|
+
fun `extractFileNameFromUrl strips query parameters`() {
|
|
103
|
+
assertEquals("file.pdf", DownloadUtils.extractFileNameFromUrl("https://example.com/file.pdf?token=abc123"))
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
@Test
|
|
107
|
+
fun `extractFileNameFromUrl falls back to a default name when the path is empty`() {
|
|
108
|
+
assertEquals("downloaded_file", DownloadUtils.extractFileNameFromUrl("https://example.com/"))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
@Test
|
|
112
|
+
fun `extractFileNameFromUrl with no path segment returns the host as the filename`() {
|
|
113
|
+
// substringAfterLast("/") on "https://example.com" (no trailing
|
|
114
|
+
// slash, no path) returns "example.com" -- the whole string after
|
|
115
|
+
// the last "/" in the scheme separator itself. This is a real
|
|
116
|
+
// quirk of the current implementation, not a bug this batch is
|
|
117
|
+
// fixing; documented here rather than asserting the fallback that
|
|
118
|
+
// only actually triggers when the path segment is present but
|
|
119
|
+
// blank (covered by the trailing-slash case above).
|
|
120
|
+
assertEquals("example.com", DownloadUtils.extractFileNameFromUrl("https://example.com"))
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// ============================================================
|
|
124
|
+
// hasSufficientStorage
|
|
125
|
+
// ============================================================
|
|
126
|
+
|
|
127
|
+
@Test
|
|
128
|
+
fun `hasSufficientStorage returns true when free space comfortably exceeds the requirement`() {
|
|
129
|
+
// Real temp dirs typically have plenty of free space on a CI/dev
|
|
130
|
+
// machine; request a tiny amount to make this deterministic.
|
|
131
|
+
assertTrue(DownloadUtils.hasSufficientStorage(context, requiredBytes = 1024L))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
@Test
|
|
135
|
+
fun `hasSufficientStorage returns false when required bytes exceed available free space`() {
|
|
136
|
+
// File.freeSpace() reflects the real filesystem the temp dir lives
|
|
137
|
+
// on -- request far more than any real disk has to force the
|
|
138
|
+
// false branch deterministically rather than mocking File itself
|
|
139
|
+
// (File is a concrete java.io class, not mockable via Context).
|
|
140
|
+
val hugeRequirement = Long.MAX_VALUE / 2
|
|
141
|
+
assertFalse(DownloadUtils.hasSufficientStorage(context, requiredBytes = hugeRequirement))
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// ============================================================
|
|
145
|
+
// cleanupDownloadedFiles
|
|
146
|
+
// ============================================================
|
|
147
|
+
|
|
148
|
+
@Test
|
|
149
|
+
fun `cleanupDownloadedFiles does nothing when the downloaded_files dir does not exist`() {
|
|
150
|
+
// No dir created -- should not throw.
|
|
151
|
+
DownloadUtils.cleanupDownloadedFiles(context)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
@Test
|
|
155
|
+
fun `cleanupDownloadedFiles deletes files older than maxAgeMillis and keeps newer ones`() {
|
|
156
|
+
val downloadDir = File(cacheDir, "downloaded_files")
|
|
157
|
+
downloadDir.mkdirs()
|
|
158
|
+
|
|
159
|
+
val oldFile = File(downloadDir, "old.pdf")
|
|
160
|
+
oldFile.writeText("old content")
|
|
161
|
+
val fourDaysAgo = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(4)
|
|
162
|
+
oldFile.setLastModified(fourDaysAgo)
|
|
163
|
+
|
|
164
|
+
val newFile = File(downloadDir, "new.pdf")
|
|
165
|
+
newFile.writeText("new content")
|
|
166
|
+
|
|
167
|
+
// Default maxAgeMillis is 3 days.
|
|
168
|
+
DownloadUtils.cleanupDownloadedFiles(context)
|
|
169
|
+
|
|
170
|
+
assertFalse("File older than 3 days should be deleted", oldFile.exists())
|
|
171
|
+
assertTrue("Recently-modified file should survive cleanup", newFile.exists())
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
@Test
|
|
175
|
+
fun `cleanupDownloadedFiles respects a custom maxAgeMillis`() {
|
|
176
|
+
val downloadDir = File(cacheDir, "downloaded_files")
|
|
177
|
+
downloadDir.mkdirs()
|
|
178
|
+
|
|
179
|
+
val file = File(downloadDir, "recent.pdf")
|
|
180
|
+
file.writeText("content")
|
|
181
|
+
val oneHourAgo = System.currentTimeMillis() - TimeUnit.HOURS.toMillis(1)
|
|
182
|
+
file.setLastModified(oneHourAgo)
|
|
183
|
+
|
|
184
|
+
// maxAgeMillis of 30 minutes -- the 1-hour-old file should be
|
|
185
|
+
// deleted even though it wouldn't be under the 3-day default.
|
|
186
|
+
DownloadUtils.cleanupDownloadedFiles(context, maxAgeMillis = TimeUnit.MINUTES.toMillis(30))
|
|
187
|
+
|
|
188
|
+
assertFalse(file.exists())
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@Test
|
|
192
|
+
fun `cleanupDownloadedFiles on an empty downloaded_files dir does not throw`() {
|
|
193
|
+
val downloadDir = File(cacheDir, "downloaded_files")
|
|
194
|
+
downloadDir.mkdirs()
|
|
195
|
+
DownloadUtils.cleanupDownloadedFiles(context)
|
|
196
|
+
}
|
|
197
|
+
}
|
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
package io.yourname.androidproject.utils
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import android.net.ConnectivityManager
|
|
5
|
+
import android.net.Network
|
|
6
|
+
import android.net.NetworkCapabilities
|
|
7
|
+
import android.net.NetworkRequest
|
|
8
|
+
import org.junit.After
|
|
9
|
+
import org.junit.Assert.assertEquals
|
|
10
|
+
import org.junit.Assert.assertTrue
|
|
11
|
+
import org.junit.Before
|
|
12
|
+
import org.junit.Test
|
|
13
|
+
import org.mockito.MockedConstruction
|
|
14
|
+
import org.mockito.Mockito.mockConstruction
|
|
15
|
+
import org.mockito.kotlin.any
|
|
16
|
+
import org.mockito.kotlin.anyOrNull
|
|
17
|
+
import org.mockito.kotlin.argumentCaptor
|
|
18
|
+
import org.mockito.kotlin.doReturn
|
|
19
|
+
import org.mockito.kotlin.mock
|
|
20
|
+
import org.mockito.kotlin.never
|
|
21
|
+
import org.mockito.kotlin.times
|
|
22
|
+
import org.mockito.kotlin.verify
|
|
23
|
+
import org.mockito.kotlin.whenever
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Unit tests for NetworkMonitor (Android coverage batch 3), previously
|
|
27
|
+
* entirely untested (0/25 lines). NetworkUtils (the top-level
|
|
28
|
+
* getCurrentStatus/NetworkStatus in the same file) is exercised
|
|
29
|
+
* indirectly here since NetworkMonitor.start() calls it immediately, and
|
|
30
|
+
* again from each NetworkCallback override.
|
|
31
|
+
*
|
|
32
|
+
* ConnectivityManager/Network/NetworkCapabilities are all interfaces or
|
|
33
|
+
* mockable framework classes reachable via a mocked Context.
|
|
34
|
+
* NetworkRequest.Builder is a real android.jar builder class whose
|
|
35
|
+
* methods are stubbed to return null under the mockable jar
|
|
36
|
+
* (addCapability(...) returns null, so a real `.build()` call inside
|
|
37
|
+
* start() NPEs) -- rather than skip start() coverage entirely (it's the
|
|
38
|
+
* bulk of this class), Mockito.mockConstruction(NetworkRequest.Builder)
|
|
39
|
+
* is used (mirroring MetricsMonitorTest's mockStatic(Choreographer)
|
|
40
|
+
* pattern, applied to a constructor instead of a static). No real
|
|
41
|
+
* NetworkRequest.Builder is ever constructed, so this stays compliant
|
|
42
|
+
* with the project's "don't call .build() on real platform builders"
|
|
43
|
+
* rule -- the builder itself is fully faked. Kept open for every test's
|
|
44
|
+
* lifetime since start() constructs a fresh Builder on each call.
|
|
45
|
+
*
|
|
46
|
+
* Build.VERSION.SDK_INT resolves to 0 under the mockable android.jar
|
|
47
|
+
* (confirmed empirically), so `Build.VERSION.SDK_INT >= Build.VERSION_CODES.N`
|
|
48
|
+
* is always false in these tests -- the registerNetworkCallback(request,
|
|
49
|
+
* callback) branch is the only one structurally reachable here.
|
|
50
|
+
* registerDefaultNetworkCallback is never invoked and is not tested.
|
|
51
|
+
*/
|
|
52
|
+
class NetworkMonitorTest {
|
|
53
|
+
|
|
54
|
+
private lateinit var connectivityManager: ConnectivityManager
|
|
55
|
+
private lateinit var appContext: Context
|
|
56
|
+
private lateinit var context: Context
|
|
57
|
+
private lateinit var requestBuilderMock: MockedConstruction<NetworkRequest.Builder>
|
|
58
|
+
|
|
59
|
+
@Before
|
|
60
|
+
fun setUp() {
|
|
61
|
+
connectivityManager = mock()
|
|
62
|
+
appContext = mock {
|
|
63
|
+
on { getSystemService(Context.CONNECTIVITY_SERVICE) } doReturn connectivityManager
|
|
64
|
+
}
|
|
65
|
+
context = mock {
|
|
66
|
+
on { getApplicationContext() } doReturn appContext
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Default: no active network -- getCurrentStatus resolves to offline
|
|
70
|
+
// unless a test overrides these.
|
|
71
|
+
whenever(connectivityManager.activeNetwork).thenReturn(null)
|
|
72
|
+
whenever(connectivityManager.getNetworkCapabilities(anyOrNull())).thenReturn(null)
|
|
73
|
+
whenever(connectivityManager.activeNetworkInfo).thenReturn(null)
|
|
74
|
+
|
|
75
|
+
requestBuilderMock = mockConstruction(NetworkRequest.Builder::class.java) { builderMock, _ ->
|
|
76
|
+
whenever(builderMock.addCapability(any())).thenReturn(builderMock)
|
|
77
|
+
whenever(builderMock.build()).thenReturn(mock<NetworkRequest>())
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@After
|
|
82
|
+
fun tearDown() {
|
|
83
|
+
requestBuilderMock.close()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ============================================================
|
|
87
|
+
// start()
|
|
88
|
+
// ============================================================
|
|
89
|
+
|
|
90
|
+
@Test
|
|
91
|
+
fun `start emits the current status immediately`() {
|
|
92
|
+
val statuses = mutableListOf<NetworkStatus>()
|
|
93
|
+
val monitor = NetworkMonitor(context) { statuses.add(it) }
|
|
94
|
+
|
|
95
|
+
monitor.start()
|
|
96
|
+
|
|
97
|
+
assertEquals(1, statuses.size)
|
|
98
|
+
assertEquals(false, statuses[0].isOnline)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@Test
|
|
102
|
+
fun `start emits an online status when capabilities report internet validated`() {
|
|
103
|
+
val capabilities = mock<NetworkCapabilities> {
|
|
104
|
+
on { hasCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED) } doReturn true
|
|
105
|
+
on { hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) } doReturn true
|
|
106
|
+
on { hasTransport(any()) } doReturn false
|
|
107
|
+
}
|
|
108
|
+
val network = mock<Network>()
|
|
109
|
+
whenever(connectivityManager.activeNetwork).thenReturn(network)
|
|
110
|
+
whenever(connectivityManager.getNetworkCapabilities(network)).thenReturn(capabilities)
|
|
111
|
+
|
|
112
|
+
val statuses = mutableListOf<NetworkStatus>()
|
|
113
|
+
val monitor = NetworkMonitor(context) { statuses.add(it) }
|
|
114
|
+
|
|
115
|
+
monitor.start()
|
|
116
|
+
|
|
117
|
+
assertEquals(1, statuses.size)
|
|
118
|
+
assertTrue(statuses[0].isOnline)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
@Test
|
|
122
|
+
fun `start registers a network callback via registerNetworkCallback`() {
|
|
123
|
+
val monitor = NetworkMonitor(context) { }
|
|
124
|
+
|
|
125
|
+
monitor.start()
|
|
126
|
+
|
|
127
|
+
verify(connectivityManager).registerNetworkCallback(
|
|
128
|
+
any<NetworkRequest>(),
|
|
129
|
+
any<ConnectivityManager.NetworkCallback>()
|
|
130
|
+
)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
@Test
|
|
134
|
+
fun `calling start twice does not emit a second immediate status or re-register`() {
|
|
135
|
+
val statuses = mutableListOf<NetworkStatus>()
|
|
136
|
+
val monitor = NetworkMonitor(context) { statuses.add(it) }
|
|
137
|
+
|
|
138
|
+
monitor.start()
|
|
139
|
+
monitor.start()
|
|
140
|
+
|
|
141
|
+
// Second start() is a no-op guarded by `if (callback != null) return`.
|
|
142
|
+
assertEquals(1, statuses.size)
|
|
143
|
+
verify(connectivityManager, times(1)).registerNetworkCallback(
|
|
144
|
+
any<NetworkRequest>(),
|
|
145
|
+
any<ConnectivityManager.NetworkCallback>()
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
@Test
|
|
150
|
+
fun `start swallows a registration exception and resets callback to null`() {
|
|
151
|
+
whenever(
|
|
152
|
+
connectivityManager.registerNetworkCallback(
|
|
153
|
+
any<NetworkRequest>(),
|
|
154
|
+
any<ConnectivityManager.NetworkCallback>()
|
|
155
|
+
)
|
|
156
|
+
).thenThrow(RuntimeException("boom"))
|
|
157
|
+
|
|
158
|
+
val statuses = mutableListOf<NetworkStatus>()
|
|
159
|
+
val monitor = NetworkMonitor(context) { statuses.add(it) }
|
|
160
|
+
|
|
161
|
+
// Should not throw -- try/catch swallows the registration failure
|
|
162
|
+
// and resets callback to null.
|
|
163
|
+
monitor.start()
|
|
164
|
+
|
|
165
|
+
// The immediate status emission still happened before the
|
|
166
|
+
// exception (it's emitted first, outside the try/catch).
|
|
167
|
+
assertEquals(1, statuses.size)
|
|
168
|
+
|
|
169
|
+
// Since callback was reset to null, a second start() is not
|
|
170
|
+
// treated as a no-op -- it emits a second immediate status and
|
|
171
|
+
// retries registration.
|
|
172
|
+
monitor.start()
|
|
173
|
+
assertEquals(2, statuses.size)
|
|
174
|
+
verify(connectivityManager, times(2)).registerNetworkCallback(
|
|
175
|
+
any<NetworkRequest>(),
|
|
176
|
+
any<ConnectivityManager.NetworkCallback>()
|
|
177
|
+
)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ============================================================
|
|
181
|
+
// stop()
|
|
182
|
+
// ============================================================
|
|
183
|
+
|
|
184
|
+
@Test
|
|
185
|
+
fun `stop before start does not throw and does not unregister`() {
|
|
186
|
+
val monitor = NetworkMonitor(context) { }
|
|
187
|
+
monitor.stop()
|
|
188
|
+
verify(connectivityManager, never()).unregisterNetworkCallback(any<ConnectivityManager.NetworkCallback>())
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
@Test
|
|
192
|
+
fun `stop after start unregisters the callback`() {
|
|
193
|
+
val monitor = NetworkMonitor(context) { }
|
|
194
|
+
monitor.start()
|
|
195
|
+
|
|
196
|
+
monitor.stop()
|
|
197
|
+
|
|
198
|
+
verify(connectivityManager).unregisterNetworkCallback(any<ConnectivityManager.NetworkCallback>())
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@Test
|
|
202
|
+
fun `stop swallows an unregister exception`() {
|
|
203
|
+
whenever(connectivityManager.unregisterNetworkCallback(any<ConnectivityManager.NetworkCallback>()))
|
|
204
|
+
.thenThrow(RuntimeException("boom"))
|
|
205
|
+
val monitor = NetworkMonitor(context) { }
|
|
206
|
+
monitor.start()
|
|
207
|
+
|
|
208
|
+
// Should not throw.
|
|
209
|
+
monitor.stop()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
@Test
|
|
213
|
+
fun `calling stop twice only unregisters once`() {
|
|
214
|
+
val monitor = NetworkMonitor(context) { }
|
|
215
|
+
monitor.start()
|
|
216
|
+
monitor.stop()
|
|
217
|
+
monitor.stop()
|
|
218
|
+
|
|
219
|
+
// unregisterNetworkCallback should only be invoked once -- the
|
|
220
|
+
// second stop() sees callback == null and no-ops.
|
|
221
|
+
verify(connectivityManager, times(1)).unregisterNetworkCallback(any<ConnectivityManager.NetworkCallback>())
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ============================================================
|
|
225
|
+
// NetworkCallback overrides (onAvailable/onLost/onUnavailable) --
|
|
226
|
+
// captured via ArgumentCaptor and invoked directly, since there's no
|
|
227
|
+
// real ConnectivityManager driving them in a JVM unit test.
|
|
228
|
+
// ============================================================
|
|
229
|
+
|
|
230
|
+
@Test
|
|
231
|
+
fun `onAvailable re-emits the current status`() {
|
|
232
|
+
val statuses = mutableListOf<NetworkStatus>()
|
|
233
|
+
val monitor = NetworkMonitor(context) { statuses.add(it) }
|
|
234
|
+
monitor.start()
|
|
235
|
+
|
|
236
|
+
val captor = argumentCaptor<ConnectivityManager.NetworkCallback>()
|
|
237
|
+
verify(connectivityManager).registerNetworkCallback(any<NetworkRequest>(), captor.capture())
|
|
238
|
+
|
|
239
|
+
captor.firstValue.onAvailable(mock())
|
|
240
|
+
|
|
241
|
+
assertEquals(2, statuses.size)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
@Test
|
|
245
|
+
fun `onLost re-emits the current status`() {
|
|
246
|
+
val statuses = mutableListOf<NetworkStatus>()
|
|
247
|
+
val monitor = NetworkMonitor(context) { statuses.add(it) }
|
|
248
|
+
monitor.start()
|
|
249
|
+
|
|
250
|
+
val captor = argumentCaptor<ConnectivityManager.NetworkCallback>()
|
|
251
|
+
verify(connectivityManager).registerNetworkCallback(any<NetworkRequest>(), captor.capture())
|
|
252
|
+
|
|
253
|
+
captor.firstValue.onLost(mock())
|
|
254
|
+
|
|
255
|
+
assertEquals(2, statuses.size)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
@Test
|
|
259
|
+
fun `onUnavailable re-emits the current status`() {
|
|
260
|
+
val statuses = mutableListOf<NetworkStatus>()
|
|
261
|
+
val monitor = NetworkMonitor(context) { statuses.add(it) }
|
|
262
|
+
monitor.start()
|
|
263
|
+
|
|
264
|
+
val captor = argumentCaptor<ConnectivityManager.NetworkCallback>()
|
|
265
|
+
verify(connectivityManager).registerNetworkCallback(any<NetworkRequest>(), captor.capture())
|
|
266
|
+
|
|
267
|
+
captor.firstValue.onUnavailable()
|
|
268
|
+
|
|
269
|
+
assertEquals(2, statuses.size)
|
|
270
|
+
}
|
|
271
|
+
}
|