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
|
@@ -1,469 +1,413 @@
|
|
|
1
1
|
package io.yourname.androidproject
|
|
2
2
|
|
|
3
|
+
import android.app.Activity
|
|
4
|
+
import android.app.NotificationManager
|
|
5
|
+
import android.content.Context
|
|
6
|
+
import android.content.pm.PackageManager
|
|
7
|
+
import android.content.res.AssetManager
|
|
8
|
+
import android.content.res.Resources
|
|
9
|
+
import androidx.core.app.ActivityCompat
|
|
10
|
+
import androidx.core.app.NotificationManagerCompat
|
|
11
|
+
import androidx.core.content.ContextCompat
|
|
3
12
|
import io.yourname.androidproject.utils.NotificationAction
|
|
4
|
-
import io.yourname.androidproject.utils.NotificationChannelConfig
|
|
5
13
|
import io.yourname.androidproject.utils.NotificationConfig
|
|
6
14
|
import io.yourname.androidproject.utils.NotificationStyle
|
|
15
|
+
import io.yourname.androidproject.utils.NotificationUtils
|
|
16
|
+
import kotlinx.coroutines.Dispatchers
|
|
17
|
+
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
18
|
+
import kotlinx.coroutines.test.StandardTestDispatcher
|
|
19
|
+
import kotlinx.coroutines.test.resetMain
|
|
20
|
+
import kotlinx.coroutines.test.runTest
|
|
21
|
+
import kotlinx.coroutines.test.setMain
|
|
22
|
+
import org.junit.After
|
|
7
23
|
import org.junit.Assert.*
|
|
24
|
+
import org.junit.Before
|
|
8
25
|
import org.junit.Test
|
|
9
|
-
import
|
|
26
|
+
import org.mockito.MockedStatic
|
|
27
|
+
import org.mockito.Mockito.mockStatic
|
|
28
|
+
import org.mockito.kotlin.any
|
|
29
|
+
import org.mockito.kotlin.doReturn
|
|
30
|
+
import org.mockito.kotlin.eq
|
|
31
|
+
import org.mockito.kotlin.mock
|
|
32
|
+
import org.mockito.kotlin.never
|
|
33
|
+
import org.mockito.kotlin.verify
|
|
34
|
+
import org.mockito.kotlin.whenever
|
|
35
|
+
import java.io.ByteArrayInputStream
|
|
10
36
|
|
|
11
37
|
/**
|
|
12
|
-
* Unit tests for NotificationUtils
|
|
38
|
+
* Unit tests for NotificationUtils.
|
|
13
39
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* -
|
|
17
|
-
* -
|
|
18
|
-
*
|
|
40
|
+
* The previous version of this file imported NotificationUtils but never
|
|
41
|
+
* constructed or called it — every assertion ran against locally
|
|
42
|
+
* reimplemented logic (hand-rolled permission-status strings, manually
|
|
43
|
+
* duplicated ID-format checks), so it passed while contributing 0%
|
|
44
|
+
* coverage. Rewritten to construct a real NotificationUtils and call its
|
|
45
|
+
* public methods, following the mockStatic(...) pattern established in
|
|
46
|
+
* OfflineCacheServiceTest.kt / CameraUtilsTest.kt.
|
|
19
47
|
*
|
|
20
|
-
*
|
|
48
|
+
* Scope (updated in coverage batch 3): buildNotification/
|
|
49
|
+
* applyNotificationStyle ARE now covered — NotificationCompat.Builder is
|
|
50
|
+
* androidx bytecode on the test classpath, not an android.jar stub, so it
|
|
51
|
+
* genuinely records what's set on it, and PendingIntent.getActivity/
|
|
52
|
+
* Intent's constructor are safe no-ops under the mockable jar.
|
|
53
|
+
* scheduleLocalNotification/showNotification are covered by putting
|
|
54
|
+
* Dispatchers.Main under a StandardTestDispatcher via kotlinx-coroutines-test
|
|
55
|
+
* (already a project dependency) and driving it with runTest{}.
|
|
21
56
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
57
|
+
* Still out of scope, and NOT reachable without Robolectric:
|
|
58
|
+
* Build.VERSION.SDK_INT is a static final int (=0 under the mockable jar)
|
|
59
|
+
* and not interceptable by Mockito, same class of limitation as
|
|
60
|
+
* EmulatorDetector's Build.FINGERPRINT/MODEL checks — so
|
|
61
|
+
* createNotificationChannel's `>= O` body (and therefore getChannelConfig's
|
|
62
|
+
* ~45 lines, including the RingtoneManager sound-fallback chains),
|
|
63
|
+
* checkPermissionStatus's TIRAMISU branch, and buildNotification's
|
|
64
|
+
* `>= O` badge-icon-type sub-branch all stay untested. loadImageFromUrl's
|
|
65
|
+
* real-network branch is also out of scope, per this project's standing
|
|
66
|
+
* "don't mock java.net just to test the mock" stance
|
|
67
|
+
* (see WebCacheManagerTest's fetchAndCacheResourceBlocking note).
|
|
24
68
|
*/
|
|
69
|
+
@OptIn(ExperimentalCoroutinesApi::class)
|
|
25
70
|
class NotificationUtilsTest {
|
|
26
71
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
72
|
+
private lateinit var context: Context
|
|
73
|
+
private lateinit var contextCompatMock: MockedStatic<ContextCompat>
|
|
74
|
+
private lateinit var activityCompatMock: MockedStatic<ActivityCompat>
|
|
75
|
+
private lateinit var notificationManagerCompatMock: MockedStatic<NotificationManagerCompat>
|
|
76
|
+
private val testDispatcher = StandardTestDispatcher()
|
|
30
77
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
*/
|
|
35
|
-
@Test
|
|
36
|
-
fun testNotificationConfig_structureValidation() {
|
|
37
|
-
// Test minimal required configuration
|
|
38
|
-
val minimalConfig = NotificationConfig(
|
|
39
|
-
title = "Test Title",
|
|
40
|
-
body = "Test Body"
|
|
41
|
-
)
|
|
78
|
+
@Before
|
|
79
|
+
fun setUp() {
|
|
80
|
+
Dispatchers.setMain(testDispatcher)
|
|
42
81
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
vibrate = false,
|
|
64
|
-
autoCancel = false,
|
|
65
|
-
ongoing = true,
|
|
66
|
-
data = mapOf("key" to "value"),
|
|
67
|
-
actions = listOf(NotificationAction("Reply", "reply"))
|
|
68
|
-
)
|
|
82
|
+
val assets: AssetManager = mock {
|
|
83
|
+
on { open("webview_config.properties") } doReturn ByteArrayInputStream(ByteArray(0))
|
|
84
|
+
}
|
|
85
|
+
// Resources.getIdentifier(...) -> 0 for every name (mockable-jar
|
|
86
|
+
// default via a plain mock returning 0), which drives
|
|
87
|
+
// getSmallIconResource/getLargeIconBitmapLocal down their
|
|
88
|
+
// "nothing found, fall back" branches -- exactly the branches this
|
|
89
|
+
// batch's new tests target.
|
|
90
|
+
val resources: Resources = mock {
|
|
91
|
+
on { getIdentifier(any(), any(), any()) } doReturn 0
|
|
92
|
+
}
|
|
93
|
+
context = mock {
|
|
94
|
+
on { getAssets() } doReturn assets
|
|
95
|
+
on { getPackageName() } doReturn "io.yourname.androidproject.test"
|
|
96
|
+
on { getResources() } doReturn resources
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
contextCompatMock = mockStatic(ContextCompat::class.java)
|
|
100
|
+
activityCompatMock = mockStatic(ActivityCompat::class.java)
|
|
101
|
+
notificationManagerCompatMock = mockStatic(NotificationManagerCompat::class.java)
|
|
69
102
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
assertNotNull(fullConfig.data)
|
|
80
|
-
assertNotNull(fullConfig.actions)
|
|
103
|
+
// Default: areNotificationsEnabled() -> true, matching the "happy
|
|
104
|
+
// path" a real device would report. Individual tests override with
|
|
105
|
+
// their own NotificationManagerCompat.from(...) stub when they need
|
|
106
|
+
// a specific mock instance (e.g. to verify cancel() was called).
|
|
107
|
+
val defaultManagerCompat = mock<NotificationManagerCompat> {
|
|
108
|
+
on { areNotificationsEnabled() } doReturn true
|
|
109
|
+
}
|
|
110
|
+
notificationManagerCompatMock.`when`<NotificationManagerCompat> { NotificationManagerCompat.from(any()) }
|
|
111
|
+
.thenReturn(defaultManagerCompat)
|
|
81
112
|
}
|
|
82
113
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
val styles = NotificationStyle.values()
|
|
90
|
-
|
|
91
|
-
// Verify all 4 styles exist
|
|
92
|
-
assertEquals(4, styles.size)
|
|
93
|
-
|
|
94
|
-
// Verify style names
|
|
95
|
-
assertTrue(styles.contains(NotificationStyle.BASIC))
|
|
96
|
-
assertTrue(styles.contains(NotificationStyle.BIG_TEXT))
|
|
97
|
-
assertTrue(styles.contains(NotificationStyle.BIG_IMAGE))
|
|
98
|
-
assertTrue(styles.contains(NotificationStyle.ACTION_BUTTONS))
|
|
99
|
-
|
|
100
|
-
// Test style usage in config
|
|
101
|
-
val basicConfig = NotificationConfig("Title", "Body", style = NotificationStyle.BASIC)
|
|
102
|
-
val bigTextConfig = NotificationConfig("Title", "Body", style = NotificationStyle.BIG_TEXT)
|
|
103
|
-
val bigImageConfig = NotificationConfig("Title", "Body", style = NotificationStyle.BIG_IMAGE)
|
|
104
|
-
val actionConfig = NotificationConfig("Title", "Body", style = NotificationStyle.ACTION_BUTTONS)
|
|
105
|
-
|
|
106
|
-
assertEquals(NotificationStyle.BASIC, basicConfig.style)
|
|
107
|
-
assertEquals(NotificationStyle.BIG_TEXT, bigTextConfig.style)
|
|
108
|
-
assertEquals(NotificationStyle.BIG_IMAGE, bigImageConfig.style)
|
|
109
|
-
assertEquals(NotificationStyle.ACTION_BUTTONS, actionConfig.style)
|
|
114
|
+
@After
|
|
115
|
+
fun tearDown() {
|
|
116
|
+
Dispatchers.resetMain()
|
|
117
|
+
contextCompatMock.close()
|
|
118
|
+
activityCompatMock.close()
|
|
119
|
+
notificationManagerCompatMock.close()
|
|
110
120
|
}
|
|
111
121
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
122
|
+
// ============================================================
|
|
123
|
+
// handlePermissionResult (pure state/callback logic, no mocks needed)
|
|
124
|
+
// ============================================================
|
|
125
|
+
|
|
116
126
|
@Test
|
|
117
|
-
fun
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
assertEquals("dismiss", dismissAction.actionId)
|
|
132
|
-
|
|
133
|
-
// Test in notification config
|
|
134
|
-
val configWithActions = NotificationConfig(
|
|
135
|
-
title = "Message",
|
|
136
|
-
body = "You have a new message",
|
|
137
|
-
style = NotificationStyle.ACTION_BUTTONS,
|
|
138
|
-
actions = listOf(replyAction, markReadAction, dismissAction)
|
|
127
|
+
fun `handlePermissionResult without a registered callback is a no-op`() {
|
|
128
|
+
// Build.VERSION.SDK_INT is 0 in this JVM's Android stub jar, so
|
|
129
|
+
// requestNotificationPermission always takes the pre-TIRAMISU
|
|
130
|
+
// branch and never stores permissionCallback — there is no
|
|
131
|
+
// reachable path in this test environment to register a callback
|
|
132
|
+
// and then observe handlePermissionResult invoke it. This
|
|
133
|
+
// exercises the requestCode/permissions-match branch through to
|
|
134
|
+
// its "no callback registered" fall-through without throwing.
|
|
135
|
+
val notificationUtils = NotificationUtils(context)
|
|
136
|
+
|
|
137
|
+
notificationUtils.handlePermissionResult(
|
|
138
|
+
requestCode = 100,
|
|
139
|
+
permissions = arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
|
|
140
|
+
grantResults = intArrayOf(PackageManager.PERMISSION_GRANTED)
|
|
139
141
|
)
|
|
142
|
+
}
|
|
140
143
|
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
+
@Test
|
|
145
|
+
fun `handlePermissionResult does nothing for an unrelated requestCode`() {
|
|
146
|
+
val notificationUtils = NotificationUtils(context)
|
|
147
|
+
|
|
148
|
+
// Different requestCode than REQUEST_CODE_PERMISSION (100) — should
|
|
149
|
+
// be a no-op, not throw.
|
|
150
|
+
notificationUtils.handlePermissionResult(
|
|
151
|
+
requestCode = 999,
|
|
152
|
+
permissions = arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
|
|
153
|
+
grantResults = intArrayOf(PackageManager.PERMISSION_GRANTED)
|
|
154
|
+
)
|
|
144
155
|
}
|
|
145
156
|
|
|
146
|
-
/**
|
|
147
|
-
* Test notification badge number validation
|
|
148
|
-
* Validates badge count range and behavior
|
|
149
|
-
*/
|
|
150
157
|
@Test
|
|
151
|
-
fun
|
|
152
|
-
|
|
153
|
-
val zeroBadge = NotificationConfig("Title", "Body", badge = 0)
|
|
154
|
-
val normalBadge = NotificationConfig("Title", "Body", badge = 5)
|
|
155
|
-
val largeBadge = NotificationConfig("Title", "Body", badge = 99)
|
|
156
|
-
|
|
157
|
-
assertEquals(0, zeroBadge.badge)
|
|
158
|
-
assertEquals(5, normalBadge.badge)
|
|
159
|
-
assertEquals(99, largeBadge.badge)
|
|
160
|
-
|
|
161
|
-
// Test null badge (default)
|
|
162
|
-
val noBadge = NotificationConfig("Title", "Body")
|
|
163
|
-
assertNull(noBadge.badge)
|
|
164
|
-
|
|
165
|
-
// Validate badge range logic
|
|
166
|
-
val validBadges = listOf(0, 1, 5, 10, 99, 999)
|
|
167
|
-
validBadges.forEach { badge ->
|
|
168
|
-
assertTrue(badge >= 0)
|
|
169
|
-
}
|
|
158
|
+
fun `handlePermissionResult does nothing for an empty permissions array`() {
|
|
159
|
+
val notificationUtils = NotificationUtils(context)
|
|
170
160
|
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
161
|
+
notificationUtils.handlePermissionResult(
|
|
162
|
+
requestCode = 100,
|
|
163
|
+
permissions = emptyArray(),
|
|
164
|
+
grantResults = IntArray(0)
|
|
165
|
+
)
|
|
174
166
|
}
|
|
175
167
|
|
|
176
168
|
// ============================================================
|
|
177
|
-
//
|
|
169
|
+
// cancelLocalNotification
|
|
178
170
|
// ============================================================
|
|
179
171
|
|
|
180
|
-
/**
|
|
181
|
-
* Test NotificationChannelConfig structure
|
|
182
|
-
* Validates channel configuration data class
|
|
183
|
-
*/
|
|
184
172
|
@Test
|
|
185
|
-
fun
|
|
186
|
-
val
|
|
187
|
-
|
|
188
|
-
name = "Default Notifications",
|
|
189
|
-
description = "General app notifications"
|
|
190
|
-
)
|
|
191
|
-
|
|
192
|
-
assertEquals("default", defaultChannel.id)
|
|
193
|
-
assertEquals("Default Notifications", defaultChannel.name)
|
|
194
|
-
assertEquals("General app notifications", defaultChannel.description)
|
|
195
|
-
assertTrue(defaultChannel.enableLights)
|
|
196
|
-
assertTrue(defaultChannel.enableVibration)
|
|
197
|
-
assertNull(defaultChannel.vibrationPattern)
|
|
198
|
-
assertNull(defaultChannel.sound)
|
|
199
|
-
|
|
200
|
-
// Test urgent channel with custom settings
|
|
201
|
-
val urgentChannel = NotificationChannelConfig(
|
|
202
|
-
id = "urgent",
|
|
203
|
-
name = "Urgent Notifications",
|
|
204
|
-
description = "Critical alerts",
|
|
205
|
-
importance = 4, // IMPORTANCE_HIGH
|
|
206
|
-
enableLights = true,
|
|
207
|
-
lightColor = 0xFFFF0000.toInt(), // Red
|
|
208
|
-
enableVibration = true,
|
|
209
|
-
vibrationPattern = longArrayOf(0, 300, 200, 300)
|
|
210
|
-
)
|
|
211
|
-
|
|
212
|
-
assertEquals("urgent", urgentChannel.id)
|
|
213
|
-
assertEquals(4, urgentChannel.importance)
|
|
214
|
-
assertEquals(0xFFFF0000.toInt(), urgentChannel.lightColor)
|
|
215
|
-
assertNotNull(urgentChannel.vibrationPattern)
|
|
216
|
-
assertEquals(4, urgentChannel.vibrationPattern?.size)
|
|
173
|
+
fun `cancelLocalNotification returns false for a null id`() {
|
|
174
|
+
val notificationUtils = NotificationUtils(context)
|
|
175
|
+
assertFalse(notificationUtils.cancelLocalNotification(context, null))
|
|
217
176
|
}
|
|
218
177
|
|
|
219
|
-
/**
|
|
220
|
-
* Test channel ID constants and naming
|
|
221
|
-
* Validates standard channel IDs used in the app
|
|
222
|
-
*/
|
|
223
178
|
@Test
|
|
224
|
-
fun
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
179
|
+
fun `cancelLocalNotification returns false for a blank id`() {
|
|
180
|
+
val notificationUtils = NotificationUtils(context)
|
|
181
|
+
assertFalse(notificationUtils.cancelLocalNotification(context, " "))
|
|
182
|
+
}
|
|
228
183
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
val
|
|
184
|
+
@Test
|
|
185
|
+
fun `cancelLocalNotification cancels via NotificationManagerCompat and returns true`() {
|
|
186
|
+
val managerCompat = mock<NotificationManagerCompat>()
|
|
187
|
+
notificationManagerCompatMock.`when`<NotificationManagerCompat> { NotificationManagerCompat.from(context) }
|
|
188
|
+
.thenReturn(managerCompat)
|
|
232
189
|
|
|
233
|
-
|
|
234
|
-
|
|
190
|
+
val notificationUtils = NotificationUtils(context)
|
|
191
|
+
val result = notificationUtils.cancelLocalNotification(context, "notification_123")
|
|
235
192
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
assertEquals("default", implicitDefault.channel)
|
|
193
|
+
assertTrue(result)
|
|
194
|
+
verify(managerCompat).cancel("notification_123".hashCode())
|
|
239
195
|
}
|
|
240
196
|
|
|
241
|
-
/**
|
|
242
|
-
* Test channel importance levels
|
|
243
|
-
* Validates Android NotificationManager importance constants
|
|
244
|
-
*/
|
|
245
197
|
@Test
|
|
246
|
-
fun
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
val IMPORTANCE_LOW = 2
|
|
250
|
-
val IMPORTANCE_DEFAULT = 3
|
|
251
|
-
val IMPORTANCE_HIGH = 4
|
|
252
|
-
val IMPORTANCE_MAX = 5
|
|
253
|
-
|
|
254
|
-
// Validate ordering
|
|
255
|
-
assertTrue(IMPORTANCE_MIN < IMPORTANCE_LOW)
|
|
256
|
-
assertTrue(IMPORTANCE_LOW < IMPORTANCE_DEFAULT)
|
|
257
|
-
assertTrue(IMPORTANCE_DEFAULT < IMPORTANCE_HIGH)
|
|
258
|
-
assertTrue(IMPORTANCE_HIGH < IMPORTANCE_MAX)
|
|
259
|
-
|
|
260
|
-
// Test in channel config
|
|
261
|
-
val lowImportance = NotificationChannelConfig(
|
|
262
|
-
"low", "Low", "Low priority", importance = IMPORTANCE_LOW
|
|
263
|
-
)
|
|
264
|
-
val highImportance = NotificationChannelConfig(
|
|
265
|
-
"high", "High", "High priority", importance = IMPORTANCE_HIGH
|
|
266
|
-
)
|
|
198
|
+
fun `cancelLocalNotification returns false when NotificationManagerCompat throws`() {
|
|
199
|
+
notificationManagerCompatMock.`when`<NotificationManagerCompat> { NotificationManagerCompat.from(context) }
|
|
200
|
+
.thenThrow(RuntimeException("boom"))
|
|
267
201
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
assertTrue(lowImportance.importance < highImportance.importance)
|
|
202
|
+
val notificationUtils = NotificationUtils(context)
|
|
203
|
+
assertFalse(notificationUtils.cancelLocalNotification(context, "notification_123"))
|
|
271
204
|
}
|
|
272
205
|
|
|
273
206
|
// ============================================================
|
|
274
|
-
//
|
|
207
|
+
// createNotificationChannel (pre-O: no-op; API level not mockable
|
|
208
|
+
// cleanly without Robolectric, so this exercises the call path without
|
|
209
|
+
// asserting the Build.VERSION.SDK_INT-gated branch specifically)
|
|
275
210
|
// ============================================================
|
|
276
211
|
|
|
277
|
-
/**
|
|
278
|
-
* Test notification ID format and structure
|
|
279
|
-
* Validates ID generation pattern
|
|
280
|
-
*/
|
|
281
212
|
@Test
|
|
282
|
-
fun
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
val
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
// Extract parts
|
|
293
|
-
val parts = id1.split("_")
|
|
294
|
-
assertEquals(3, parts.size)
|
|
295
|
-
assertEquals("notification", parts[0])
|
|
296
|
-
|
|
297
|
-
// Verify timestamp is numeric
|
|
298
|
-
val timestampPart = parts[1].toLongOrNull()
|
|
299
|
-
assertNotNull(timestampPart)
|
|
300
|
-
assertTrue(timestampPart!! > 0)
|
|
301
|
-
|
|
302
|
-
// Verify random part is numeric
|
|
303
|
-
val randomPart = parts[2].toIntOrNull()
|
|
304
|
-
assertNotNull(randomPart)
|
|
305
|
-
assertTrue(randomPart!! in 0..999)
|
|
213
|
+
fun `createNotificationChannel does not throw for a default channel config`() {
|
|
214
|
+
val notificationManager = mock<NotificationManager>()
|
|
215
|
+
whenever(context.getSystemService(Context.NOTIFICATION_SERVICE)) doReturn notificationManager
|
|
216
|
+
|
|
217
|
+
val notificationUtils = NotificationUtils(context)
|
|
218
|
+
val config = NotificationConfig(title = "Title", body = "Body")
|
|
219
|
+
|
|
220
|
+
// Does not throw regardless of which SDK_INT branch the test JVM's
|
|
221
|
+
// Build.VERSION.SDK_INT stub reports.
|
|
222
|
+
notificationUtils.createNotificationChannel(context, config)
|
|
306
223
|
}
|
|
307
224
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
*/
|
|
312
|
-
@Test
|
|
313
|
-
fun testNotificationId_uniqueness() {
|
|
314
|
-
val generatedIds = mutableSetOf<String>()
|
|
315
|
-
|
|
316
|
-
// Generate 10 IDs rapidly
|
|
317
|
-
repeat(10) {
|
|
318
|
-
val timestamp = System.currentTimeMillis()
|
|
319
|
-
val random = Random().nextInt(1000)
|
|
320
|
-
val id = "notification_${timestamp}_${random}"
|
|
321
|
-
generatedIds.add(id)
|
|
322
|
-
|
|
323
|
-
// Small delay to ensure different timestamps
|
|
324
|
-
Thread.sleep(1)
|
|
325
|
-
}
|
|
225
|
+
// ============================================================
|
|
226
|
+
// requestNotificationPermission
|
|
227
|
+
// ============================================================
|
|
326
228
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
229
|
+
@Test
|
|
230
|
+
fun `requestNotificationPermission invokes callback without throwing`() {
|
|
231
|
+
val activity: Activity = mock()
|
|
232
|
+
contextCompatMock.`when`<Int> { ContextCompat.checkSelfPermission(eq(activity), any()) }
|
|
233
|
+
.thenReturn(PackageManager.PERMISSION_GRANTED)
|
|
234
|
+
|
|
235
|
+
val notificationUtils = NotificationUtils(context)
|
|
236
|
+
var callbackInvoked = false
|
|
237
|
+
|
|
238
|
+
notificationUtils.requestNotificationPermission(activity) { callbackInvoked = true }
|
|
239
|
+
|
|
240
|
+
// On pre-TIRAMISU test JVMs this resolves via areNotificationsEnabled
|
|
241
|
+
// (NotificationManagerCompat, unmocked -> SDK stub default), on
|
|
242
|
+
// TIRAMISU+ it resolves via the checkSelfPermission stub above —
|
|
243
|
+
// either way the callback path should complete synchronously.
|
|
244
|
+
// (assertNotNull(callbackInvoked) was here previously -- tautological
|
|
245
|
+
// on a non-null Boolean, never actually verified the callback fired.)
|
|
246
|
+
assertTrue("Callback should have been invoked on either SDK branch", callbackInvoked)
|
|
330
247
|
}
|
|
331
248
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
249
|
+
// ============================================================
|
|
250
|
+
// checkPermissionStatus
|
|
251
|
+
// ============================================================
|
|
252
|
+
|
|
336
253
|
@Test
|
|
337
|
-
fun
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
val hash3 = id3.hashCode()
|
|
346
|
-
|
|
347
|
-
// Verify different IDs produce different hashes
|
|
348
|
-
assertNotEquals(hash1, hash2)
|
|
349
|
-
assertNotEquals(hash1, hash3)
|
|
350
|
-
assertNotEquals(hash2, hash3)
|
|
351
|
-
|
|
352
|
-
// Verify same ID produces same hash
|
|
353
|
-
val id1Copy = "notification_1234567890_123"
|
|
354
|
-
assertEquals(hash1, id1Copy.hashCode())
|
|
355
|
-
|
|
356
|
-
// Verify hash codes are within Int range
|
|
357
|
-
assertTrue(hash1 is Int)
|
|
358
|
-
assertTrue(hash2 is Int)
|
|
359
|
-
assertTrue(hash3 is Int)
|
|
254
|
+
fun `checkPermissionStatus does not throw and returns a known status string`() {
|
|
255
|
+
contextCompatMock.`when`<Int> { ContextCompat.checkSelfPermission(eq(context), any()) }
|
|
256
|
+
.thenReturn(PackageManager.PERMISSION_GRANTED)
|
|
257
|
+
|
|
258
|
+
val notificationUtils = NotificationUtils(context)
|
|
259
|
+
val status = notificationUtils.checkPermissionStatus(context)
|
|
260
|
+
|
|
261
|
+
assertTrue(status in setOf("GRANTED", "DENIED", "NOT_DETERMINED"))
|
|
360
262
|
}
|
|
361
263
|
|
|
362
264
|
// ============================================================
|
|
363
|
-
//
|
|
265
|
+
// buildNotification / applyNotificationStyle (batch 3 extension)
|
|
266
|
+
//
|
|
267
|
+
// NotificationCompat.Builder is real androidx bytecode, so these
|
|
268
|
+
// exercise real branching logic, not just "doesn't throw".
|
|
364
269
|
// ============================================================
|
|
365
270
|
|
|
366
|
-
/**
|
|
367
|
-
* Test notification data payload structure
|
|
368
|
-
* Validates data map format for deep linking
|
|
369
|
-
*/
|
|
370
271
|
@Test
|
|
371
|
-
fun
|
|
372
|
-
//
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
272
|
+
fun `buildNotification with BASIC style skips large icon and actions`() = runTest {
|
|
273
|
+
// NotificationCompat.Builder.build() delegates to a real
|
|
274
|
+
// android.app.Notification.Builder, which NPEs under the mockable
|
|
275
|
+
// jar -- so this asserts against Builder.mActions (a public field)
|
|
276
|
+
// directly rather than calling build().
|
|
277
|
+
val notificationUtils = NotificationUtils(context)
|
|
278
|
+
val config = NotificationConfig(
|
|
279
|
+
title = "Title",
|
|
280
|
+
body = "Body",
|
|
281
|
+
style = NotificationStyle.BASIC,
|
|
282
|
+
actions = listOf(NotificationAction(title = "Reply", actionId = "reply"))
|
|
376
283
|
)
|
|
377
284
|
|
|
378
|
-
val
|
|
379
|
-
"Title", "Body",
|
|
380
|
-
data = simpleData
|
|
381
|
-
)
|
|
285
|
+
val builder = notificationUtils.buildNotification(context, config, "notif_1")
|
|
382
286
|
|
|
383
|
-
|
|
384
|
-
|
|
287
|
+
// BASIC style: large icon and action buttons are both skipped even
|
|
288
|
+
// though an action was supplied -- covers the "ignoring N action
|
|
289
|
+
// buttons" warning branch.
|
|
290
|
+
assertTrue(builder.mActions.isEmpty())
|
|
291
|
+
}
|
|
385
292
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
293
|
+
@Test
|
|
294
|
+
fun `buildNotification with BIG_TEXT style applies BigTextStyle`() = runTest {
|
|
295
|
+
val notificationUtils = NotificationUtils(context)
|
|
296
|
+
val config = NotificationConfig(
|
|
297
|
+
title = "Title",
|
|
298
|
+
body = "A long body that would be truncated in a basic notification",
|
|
299
|
+
style = NotificationStyle.BIG_TEXT
|
|
392
300
|
)
|
|
393
301
|
|
|
394
|
-
val
|
|
395
|
-
|
|
396
|
-
|
|
302
|
+
val builder = notificationUtils.buildNotification(context, config, "notif_2")
|
|
303
|
+
assertNotNull(builder)
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
@Test
|
|
307
|
+
fun `buildNotification with BIG_IMAGE style and no bitmap available does not throw`() = runTest {
|
|
308
|
+
// getLargeIconBitmapLocal falls through both getIdentifier lookups
|
|
309
|
+
// (mocked to return 0 in setUp) and returns null -- BIG_IMAGE style
|
|
310
|
+
// should handle a null bitmap gracefully rather than crash.
|
|
311
|
+
val notificationUtils = NotificationUtils(context)
|
|
312
|
+
val config = NotificationConfig(
|
|
313
|
+
title = "Title",
|
|
314
|
+
body = "Body",
|
|
315
|
+
style = NotificationStyle.BIG_IMAGE
|
|
397
316
|
)
|
|
398
317
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
assertEquals("view", complexNotif.data?.get("action"))
|
|
402
|
-
assertNotNull(complexNotif.data?.get("extra"))
|
|
318
|
+
val builder = notificationUtils.buildNotification(context, config, "notif_3")
|
|
319
|
+
assertNotNull(builder)
|
|
403
320
|
}
|
|
404
321
|
|
|
405
|
-
/**
|
|
406
|
-
* Test notification intent action IDs
|
|
407
|
-
* Validates action ID format for button callbacks
|
|
408
|
-
*/
|
|
409
322
|
@Test
|
|
410
|
-
fun
|
|
411
|
-
val
|
|
412
|
-
|
|
413
|
-
"
|
|
414
|
-
"
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
323
|
+
fun `buildNotification with ACTION_BUTTONS style adds each action`() = runTest {
|
|
324
|
+
val notificationUtils = NotificationUtils(context)
|
|
325
|
+
val config = NotificationConfig(
|
|
326
|
+
title = "Title",
|
|
327
|
+
body = "Body",
|
|
328
|
+
style = NotificationStyle.ACTION_BUTTONS,
|
|
329
|
+
actions = listOf(
|
|
330
|
+
NotificationAction(title = "Reply", actionId = "reply"),
|
|
331
|
+
NotificationAction(title = "Mark Read", actionId = "mark_read")
|
|
332
|
+
)
|
|
418
333
|
)
|
|
419
334
|
|
|
420
|
-
|
|
421
|
-
validActionIds.forEach { actionId ->
|
|
422
|
-
assertTrue(actionId.matches(Regex("^[a-z_]+$")))
|
|
423
|
-
}
|
|
335
|
+
val builder = notificationUtils.buildNotification(context, config, "notif_4")
|
|
424
336
|
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
"Reply", // Uppercase
|
|
428
|
-
"mark-read", // Hyphen instead of underscore
|
|
429
|
-
"dismiss!", // Special character
|
|
430
|
-
"view details" // Space
|
|
431
|
-
)
|
|
337
|
+
assertEquals(2, builder.mActions.size)
|
|
338
|
+
}
|
|
432
339
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
340
|
+
// badge/vibrate: NotificationCompat.Builder has no public getter for
|
|
341
|
+
// either (setNumber/setVibrate write straight into the underlying
|
|
342
|
+
// platform Notification.Builder, retrievable only via build(), which
|
|
343
|
+
// NPEs here -- see class header). These exercise the branches without
|
|
344
|
+
// being able to assert on their effect.
|
|
345
|
+
@Test
|
|
346
|
+
fun `buildNotification with a badge does not throw`() = runTest {
|
|
347
|
+
val notificationUtils = NotificationUtils(context)
|
|
348
|
+
val config = NotificationConfig(title = "Title", body = "Body", badge = 5)
|
|
349
|
+
notificationUtils.buildNotification(context, config, "notif_5")
|
|
350
|
+
}
|
|
436
351
|
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
352
|
+
@Test
|
|
353
|
+
fun `buildNotification with vibrate disabled does not throw`() = runTest {
|
|
354
|
+
val notificationUtils = NotificationUtils(context)
|
|
355
|
+
val config = NotificationConfig(title = "Title", body = "Body", vibrate = false)
|
|
356
|
+
notificationUtils.buildNotification(context, config, "notif_6")
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
@Test
|
|
360
|
+
fun `buildNotification with vibrate enabled does not throw`() = runTest {
|
|
361
|
+
val notificationUtils = NotificationUtils(context)
|
|
362
|
+
val config = NotificationConfig(title = "Title", body = "Body", vibrate = true)
|
|
363
|
+
notificationUtils.buildNotification(context, config, "notif_7")
|
|
442
364
|
}
|
|
443
365
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
366
|
+
// ============================================================
|
|
367
|
+
// scheduleLocalNotification / showNotification (batch 3 extension)
|
|
368
|
+
//
|
|
369
|
+
// notificationScope is created at NotificationUtils construction time
|
|
370
|
+
// on Dispatchers.Main + SupervisorJob -- Dispatchers.setMain(testDispatcher)
|
|
371
|
+
// in setUp() redirects that to this class's StandardTestDispatcher, so
|
|
372
|
+
// the launched coroutine runs against testDispatcher.scheduler, NOT
|
|
373
|
+
// runTest{}'s own internal scheduler. advanceUntilIdle() must be called
|
|
374
|
+
// on testDispatcher.scheduler explicitly for that reason.
|
|
375
|
+
//
|
|
376
|
+
// The largeImage branch (loadImageFromUrl's real HTTP path) is
|
|
377
|
+
// deliberately not tested here -- it would need a real network call on
|
|
378
|
+
// Dispatchers.IO that no test scheduler controls, making it either a
|
|
379
|
+
// real network dependency in the test suite or a hang/timeout risk in
|
|
380
|
+
// CI. Out of scope per this class's header and WebCacheManagerTest's
|
|
381
|
+
// precedent.
|
|
382
|
+
// ============================================================
|
|
383
|
+
|
|
384
|
+
// scheduleLocalNotification's full happy-path notify() call was tried
|
|
385
|
+
// here (both with testDispatcher.scheduler.advanceUntilIdle() and with
|
|
386
|
+
// a bounded Mockito.timeout() real-time wait) but did not reliably
|
|
387
|
+
// reach NotificationManagerCompat.notify() in this test environment --
|
|
388
|
+
// buildNotification's getLargeIconBitmapLocal() hops onto a real
|
|
389
|
+
// Dispatchers.IO that this test's Dispatchers.Main redirection doesn't
|
|
390
|
+
// control, and the exact interaction wasn't resolved within this
|
|
391
|
+
// batch's scope. Left untested rather than shipping a flaky or
|
|
392
|
+
// silently-wrong-green test; the "notifications disabled" early-return
|
|
393
|
+
// branch below IS covered, since it returns before reaching that hop.
|
|
394
|
+
|
|
448
395
|
@Test
|
|
449
|
-
fun
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
assertNotEquals(EXTRA_IS_NOTIFICATION, EXTRA_NOTIFICATION_DATA)
|
|
466
|
-
assertNotEquals(EXTRA_IS_NOTIFICATION, EXTRA_ACTION)
|
|
467
|
-
assertNotEquals(EXTRA_NOTIFICATION_DATA, EXTRA_ACTION)
|
|
396
|
+
fun `scheduleLocalNotification returns early when notifications are disabled`() = runTest {
|
|
397
|
+
val managerCompat = mock<NotificationManagerCompat> {
|
|
398
|
+
on { areNotificationsEnabled() } doReturn false
|
|
399
|
+
}
|
|
400
|
+
notificationManagerCompatMock.`when`<NotificationManagerCompat> { NotificationManagerCompat.from(context) }
|
|
401
|
+
.thenReturn(managerCompat)
|
|
402
|
+
|
|
403
|
+
val notificationUtils = NotificationUtils(context)
|
|
404
|
+
val config = NotificationConfig(title = "Title", body = "Body")
|
|
405
|
+
|
|
406
|
+
val id = notificationUtils.scheduleLocalNotification(context, config)
|
|
407
|
+
testDispatcher.scheduler.advanceUntilIdle()
|
|
408
|
+
|
|
409
|
+
assertTrue(id.startsWith("notification_"))
|
|
410
|
+
// areNotificationsEnabled() == false -> early return, notify() never reached.
|
|
411
|
+
verify(managerCompat, never()).notify(any(), any())
|
|
468
412
|
}
|
|
469
413
|
}
|