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,10 @@
|
|
|
1
|
+
// Fixture server hooks for the SSR handler tests (#348). handler.jsx does
|
|
2
|
+
// `await import("@catalyst/template/server/index.js")` in a try/catch and
|
|
3
|
+
// wires whatever named exports exist (onRouteMatch, onFetcherSuccess,
|
|
4
|
+
// onFetcherError, onAppServerSideSuccess, onAppServerSideError,
|
|
5
|
+
// onRenderError, onRequestError).
|
|
6
|
+
//
|
|
7
|
+
// All left undefined by default so safeCall skips them. Tests that need a
|
|
8
|
+
// hook (e.g. to assert onRenderError fired) vi.spyOn / re-mock this
|
|
9
|
+
// module per-case.
|
|
10
|
+
export {}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { Outlet } from "react-router"
|
|
3
|
+
|
|
4
|
+
// Fixture App for the SSR handler tests (#348).
|
|
5
|
+
//
|
|
6
|
+
// Renders <Outlet /> exactly like every real create-catalyst-app template's
|
|
7
|
+
// App — that is how react-router hands the matched child route down. The
|
|
8
|
+
// earlier version rendered `{children}`, which react-router never populates
|
|
9
|
+
// for a nested route, so route components never rendered on the server.
|
|
10
|
+
//
|
|
11
|
+
// App.serverSideFunction is an implicit contract: handler.jsx calls it
|
|
12
|
+
// unconditionally (via tracedAppServerSideFunction) on every request. All
|
|
13
|
+
// 6 real templates define it, so the fixture must too — omitting it would
|
|
14
|
+
// make every test throw in the SERVER_SIDE_FUNCTION stage rather than
|
|
15
|
+
// exercising the render path.
|
|
16
|
+
//
|
|
17
|
+
// The tests swap `serverSideFunction` per-case (e.g. to make it throw) via
|
|
18
|
+
// vi.spyOn on this module.
|
|
19
|
+
function App() {
|
|
20
|
+
return (
|
|
21
|
+
<main data-testid="app">
|
|
22
|
+
<Outlet />
|
|
23
|
+
</main>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
App.serverSideFunction = async () => {
|
|
28
|
+
// no-op by default; tests override to test the SERVER_SIDE_FUNCTION
|
|
29
|
+
// error branch.
|
|
30
|
+
return undefined
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default App
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
|
|
3
|
+
// Fixture route table for the SSR handler tests (#348). Concrete routes
|
|
4
|
+
// plus a catch-all so tests can exercise the 200 / 404 / fetcher-error
|
|
5
|
+
// status branches in _handler and _renderMarkUp.
|
|
6
|
+
|
|
7
|
+
function HomePage() {
|
|
8
|
+
return <p data-testid="home">home</p>
|
|
9
|
+
}
|
|
10
|
+
// __cacheKey mirrors what Split.jsx stamps on split() components. The SSR
|
|
11
|
+
// handler *asset* tests (handler.assets.test.ts) mock manifestCache to
|
|
12
|
+
// return a manifest entry under this key, so preloadRouteCss resolves a
|
|
13
|
+
// real critical JS/CSS chunk for this route. Inert for handler.test.ts,
|
|
14
|
+
// which runs with empty manifests (no entry -> no assets).
|
|
15
|
+
HomePage.__cacheKey = "src/js/routes/HomePage"
|
|
16
|
+
|
|
17
|
+
// A route component that registers a deferred chunk during render the exact
|
|
18
|
+
// way Split.jsx does on the server (Split.jsx:53-54:
|
|
19
|
+
// `global.__CHUNK_EXTRACTOR__.addComponent(cacheKey)`). Split's own
|
|
20
|
+
// server-side behavior is unit-tested in Split.server.test.jsx (#454); this
|
|
21
|
+
// fixture exists so the *handler* asset tests can exercise what happens
|
|
22
|
+
// once a component has self-registered: getRenderedComponentKeys()
|
|
23
|
+
// serialization into window.__SSR_RENDERED_COMPONENTS__, the post-body
|
|
24
|
+
// deferred <style>/<script> flush, and the cross-request deferred-CSS
|
|
25
|
+
// cache. handler.assets.test.ts mocks manifestCache so WIDGET_CACHE_KEY
|
|
26
|
+
// resolves to a chunk with its own CSS.
|
|
27
|
+
export const WIDGET_CACHE_KEY = "src/js/components/Widget"
|
|
28
|
+
|
|
29
|
+
function WidgetPage() {
|
|
30
|
+
if (typeof global !== "undefined" && global.__CHUNK_EXTRACTOR__) {
|
|
31
|
+
global.__CHUNK_EXTRACTOR__.addComponent(WIDGET_CACHE_KEY)
|
|
32
|
+
}
|
|
33
|
+
return <p data-testid="widget">widget</p>
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function NotFoundPage() {
|
|
37
|
+
return <p data-testid="notfound">not found</p>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function FetcherErrorPage() {
|
|
41
|
+
return <p data-testid="fetcher-error">boom page</p>
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// serverDataFetcher only reads serverFetcher off a *loadable* component
|
|
45
|
+
// (`route.component.load()` -> module with a `default`). On the server
|
|
46
|
+
// (no window) fetchRouteData picks module.default.serverFetcher; when it
|
|
47
|
+
// rejects, serverDataFetcher records fetcherData[url].error, which
|
|
48
|
+
// _handler turns into an `err.status_code || 404` render.
|
|
49
|
+
const loadable = (mod) => {
|
|
50
|
+
const C = mod.default
|
|
51
|
+
C.load = () => Promise.resolve(mod)
|
|
52
|
+
return C
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const FetcherErrorLoadable = loadable({
|
|
56
|
+
default: Object.assign(FetcherErrorPage, {
|
|
57
|
+
serverFetcher: async () => {
|
|
58
|
+
const e = new Error("fetcher exploded")
|
|
59
|
+
e.status_code = 503
|
|
60
|
+
throw e
|
|
61
|
+
},
|
|
62
|
+
}),
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
const routes = [
|
|
66
|
+
{ path: "/", component: HomePage },
|
|
67
|
+
{ path: "/widget", component: WidgetPage },
|
|
68
|
+
{ path: "/fetcher-error", component: FetcherErrorLoadable },
|
|
69
|
+
{ path: "*", component: NotFoundPage },
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
export default routes
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import React from "react"
|
|
2
|
+
import { RouterDataProvider } from "catalyst-core"
|
|
3
|
+
import App from "../containers/App/index.jsx"
|
|
4
|
+
import routes from "./index.jsx"
|
|
5
|
+
|
|
6
|
+
// Fixture mirror of a scaffolded app's routes/utils. Same two exports the
|
|
7
|
+
// framework consumes: getRoutes() (raw table, used by handler.jsx via
|
|
8
|
+
// getCachedRoutes / NestedMatchRoutes) and preparedRoutes() (react-router
|
|
9
|
+
// shape, used by ServerRouter).
|
|
10
|
+
//
|
|
11
|
+
// The parent element renders <App />, which renders <Outlet /> — the same
|
|
12
|
+
// shape every real create-catalyst-app template uses. Without the Outlet,
|
|
13
|
+
// react-router never renders the matched child route, so route components
|
|
14
|
+
// (and anything they do during render, e.g. Split's addComponent) stay
|
|
15
|
+
// dark. handler.assets.test.ts's deferred-asset cases depend on the child
|
|
16
|
+
// actually rendering.
|
|
17
|
+
|
|
18
|
+
export const getRoutes = () => routes
|
|
19
|
+
|
|
20
|
+
export const preparedRoutes = ({ routerInitialState } = {}) => {
|
|
21
|
+
const prepare = (list) =>
|
|
22
|
+
list.map((route, index) => {
|
|
23
|
+
const Component = route.component
|
|
24
|
+
const prepared = { ...route, element: <Component key={index} /> }
|
|
25
|
+
if (route.children) prepared.children = prepare(route.children)
|
|
26
|
+
return prepared
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
return [
|
|
30
|
+
{
|
|
31
|
+
element: (
|
|
32
|
+
<RouterDataProvider config={{}} initialState={routerInitialState}>
|
|
33
|
+
<App />
|
|
34
|
+
</RouterDataProvider>
|
|
35
|
+
),
|
|
36
|
+
children: prepare(routes),
|
|
37
|
+
},
|
|
38
|
+
]
|
|
39
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Fixture Redux-ish store for the SSR handler tests (#348). handler.jsx
|
|
2
|
+
// calls `createStore({}, req, res)` when validateConfigureStore passes,
|
|
3
|
+
// then reads store.getState() during render. A minimal object with
|
|
4
|
+
// getState/dispatch/subscribe satisfies react-redux's <Provider>.
|
|
5
|
+
|
|
6
|
+
export default async function createStore(initialState = {}) {
|
|
7
|
+
let state = { ...initialState }
|
|
8
|
+
return {
|
|
9
|
+
getState: () => state,
|
|
10
|
+
dispatch: (action) => action,
|
|
11
|
+
subscribe: () => () => {},
|
|
12
|
+
replaceReducer: () => {},
|
|
13
|
+
// react-redux 8+ Provider checks for this symbol-less shape; the
|
|
14
|
+
// above four methods are enough for SSR render.
|
|
15
|
+
[Symbol.observable ?? "@@observable"]: () => ({
|
|
16
|
+
subscribe: () => ({ unsubscribe: () => {} }),
|
|
17
|
+
}),
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
import { Writable } from "node:stream"
|
|
2
|
+
import fs from "node:fs"
|
|
3
|
+
import os from "node:os"
|
|
4
|
+
import path from "node:path"
|
|
5
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"
|
|
6
|
+
|
|
7
|
+
// SSR handler — manifest → chunk → markup coverage (#348 follow-up).
|
|
8
|
+
//
|
|
9
|
+
// handler.test.ts proves the request lifecycle (200 / 404 / fetcher status,
|
|
10
|
+
// stream completion, error propagation) but runs with:
|
|
11
|
+
// - the fixture's CustomDocument always present, so handler.jsx:242 always
|
|
12
|
+
// short-circuits and the built-in Head/Body never render; and
|
|
13
|
+
// - NODE_ENV unset, so manifestCache returns null and _collectAssets
|
|
14
|
+
// builds a ChunkExtractor over {} / {} — every asset path executes with
|
|
15
|
+
// empty inputs and asserts nothing.
|
|
16
|
+
//
|
|
17
|
+
// This file drives the other side: no CustomDocument (built-in document),
|
|
18
|
+
// a populated fake manifest so preloadRouteCss / addComponent resolve real
|
|
19
|
+
// chunks, real CSS files on disk so readCssFromDisk returns content, and a
|
|
20
|
+
// bot UA so the !isBot gates in Head.jsx / registerDeferredAssetsForRoute
|
|
21
|
+
// are exercised end-to-end in a rendered response.
|
|
22
|
+
//
|
|
23
|
+
// Deliberately NOT here: real split() + lazy + Suspense streaming (that is
|
|
24
|
+
// Split.server.test.jsx, #454) and browser behavior (Playwright, #412).
|
|
25
|
+
// The fixture WidgetPage self-registers with global.__CHUNK_EXTRACTOR__ the
|
|
26
|
+
// exact way Split.jsx:53-54 does on the server, so the deferred-asset
|
|
27
|
+
// plumbing downstream of that call is covered without pulling the whole
|
|
28
|
+
// split() machinery in.
|
|
29
|
+
|
|
30
|
+
const TEMPLATE_DOC = "@catalyst/template/server/document"
|
|
31
|
+
const MANIFEST_CACHE = "../../src/server/manifestCache.js"
|
|
32
|
+
|
|
33
|
+
// Fake build manifest. HomePage's __cacheKey → a critical chunk with CSS;
|
|
34
|
+
// WIDGET_CACHE_KEY → a deferred chunk with its own CSS (registered during
|
|
35
|
+
// render via addComponent). `file` has no leading "assets/" — the emitted
|
|
36
|
+
// URL is PUBLIC_STATIC_ASSET_URL + PUBLIC_STATIC_ASSET_PATH + file.
|
|
37
|
+
const FAKE_MANIFEST = {
|
|
38
|
+
"src/js/routes/HomePage": { file: "home.abc123.js", css: ["home.abc123.css"] },
|
|
39
|
+
"src/js/components/Widget": { file: "widget.def456.js", css: ["widget.def456.css"] },
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function mockManifest() {
|
|
43
|
+
vi.doMock(MANIFEST_CACHE, () => ({
|
|
44
|
+
getManifest: () => FAKE_MANIFEST,
|
|
45
|
+
getAssetManifest: () => ({ ssrTrue: {}, ssrFalse: {}, essential: {} }),
|
|
46
|
+
}))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// A real Writable so renderToPipeableStream's Transform → res pipe works.
|
|
50
|
+
function makeReqRes(url = "/", userAgent = "test") {
|
|
51
|
+
const chunks: Buffer[] = []
|
|
52
|
+
let ended = false
|
|
53
|
+
|
|
54
|
+
const res: any = new Writable({
|
|
55
|
+
write(chunk, _enc, cb) {
|
|
56
|
+
chunks.push(Buffer.from(chunk))
|
|
57
|
+
cb()
|
|
58
|
+
},
|
|
59
|
+
final(cb) {
|
|
60
|
+
ended = true
|
|
61
|
+
cb()
|
|
62
|
+
},
|
|
63
|
+
})
|
|
64
|
+
const endPromise = new Promise<void>((resolve) => {
|
|
65
|
+
res.on("finish", resolve)
|
|
66
|
+
res.on("close", resolve)
|
|
67
|
+
})
|
|
68
|
+
res.headers = {}
|
|
69
|
+
res.statusCode = 200
|
|
70
|
+
res.headersSent = false
|
|
71
|
+
res.set = vi.fn((obj: Record<string, string>) => {
|
|
72
|
+
Object.assign(res.headers, obj)
|
|
73
|
+
return res
|
|
74
|
+
})
|
|
75
|
+
res.setHeader = vi.fn((k: string, v: string) => {
|
|
76
|
+
res.headers[k] = v
|
|
77
|
+
return res
|
|
78
|
+
})
|
|
79
|
+
res.status = vi.fn((code: number) => {
|
|
80
|
+
res.statusCode = code
|
|
81
|
+
return res
|
|
82
|
+
})
|
|
83
|
+
res.send = vi.fn(() => {
|
|
84
|
+
res.headersSent = true
|
|
85
|
+
return res
|
|
86
|
+
})
|
|
87
|
+
res.getHtml = () => Buffer.concat(chunks).toString("utf8")
|
|
88
|
+
res.waitForEnd = () => endPromise
|
|
89
|
+
res.isEnded = () => ended
|
|
90
|
+
|
|
91
|
+
const req: any = {
|
|
92
|
+
originalUrl: url,
|
|
93
|
+
url,
|
|
94
|
+
method: "GET",
|
|
95
|
+
query: {},
|
|
96
|
+
headers: { "user-agent": userAgent },
|
|
97
|
+
get(name: string) {
|
|
98
|
+
return this.headers[name.toLowerCase()]
|
|
99
|
+
},
|
|
100
|
+
}
|
|
101
|
+
return { req, res }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let buildDir: string
|
|
105
|
+
|
|
106
|
+
beforeEach(() => {
|
|
107
|
+
// A real build dir so readCssFromDisk (fs.readFileSync) returns content.
|
|
108
|
+
// src_path + BUILD_OUTPUT_PATH is how handler.jsx:205 computes it.
|
|
109
|
+
buildDir = fs.mkdtempSync(path.join(os.tmpdir(), "catalyst-assets-"))
|
|
110
|
+
fs.writeFileSync(path.join(buildDir, "home.abc123.css"), ".home{color:red}")
|
|
111
|
+
fs.writeFileSync(path.join(buildDir, "widget.def456.css"), ".widget{color:blue}")
|
|
112
|
+
|
|
113
|
+
process.env.src_path = path.dirname(buildDir)
|
|
114
|
+
process.env.BUILD_OUTPUT_PATH = path.basename(buildDir)
|
|
115
|
+
process.env.APPLICATION = "test"
|
|
116
|
+
process.env.PUBLIC_STATIC_ASSET_URL = "http://localhost"
|
|
117
|
+
process.env.PUBLIC_STATIC_ASSET_PATH = "/assets/"
|
|
118
|
+
vi.spyOn(console, "error").mockImplementation(() => {})
|
|
119
|
+
vi.spyOn(console, "log").mockImplementation(() => {})
|
|
120
|
+
vi.spyOn(console, "warn").mockImplementation(() => {})
|
|
121
|
+
})
|
|
122
|
+
|
|
123
|
+
afterEach(() => {
|
|
124
|
+
vi.restoreAllMocks()
|
|
125
|
+
vi.resetModules()
|
|
126
|
+
vi.doUnmock(TEMPLATE_DOC)
|
|
127
|
+
vi.doUnmock(MANIFEST_CACHE)
|
|
128
|
+
try {
|
|
129
|
+
fs.rmSync(buildDir, { recursive: true, force: true })
|
|
130
|
+
} catch {
|
|
131
|
+
// best-effort cleanup
|
|
132
|
+
}
|
|
133
|
+
// ChunkExtractor's constructor sets this and nothing clears it; a leak
|
|
134
|
+
// would let one test's extractor catch the next test's addComponent.
|
|
135
|
+
delete (global as any).__CHUNK_EXTRACTOR__
|
|
136
|
+
// extract.js keeps its deferred-route cache and CSS-body cache on
|
|
137
|
+
// `process` — reset so cross-request-cache tests start clean and the
|
|
138
|
+
// first render of a route never sees a prior run's deferred paths.
|
|
139
|
+
delete (process as any).deferredAssetsByRoute
|
|
140
|
+
delete (process as any).cssFileCache
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
describe("SSR handler — built-in document (no CustomDocument)", () => {
|
|
144
|
+
it("renders the built-in Head/Body when the app exports no CustomDocument", async () => {
|
|
145
|
+
vi.doMock(TEMPLATE_DOC, () => ({ default: undefined }))
|
|
146
|
+
mockManifest()
|
|
147
|
+
|
|
148
|
+
const { default: handler } = await import("../../src/server/renderer/handler.jsx")
|
|
149
|
+
const { req, res } = makeReqRes("/")
|
|
150
|
+
|
|
151
|
+
await handler(req, res)
|
|
152
|
+
await res.waitForEnd()
|
|
153
|
+
|
|
154
|
+
const html = res.getHtml()
|
|
155
|
+
// Built-in Head markers — never present when the fixture
|
|
156
|
+
// CustomDocument (which renders <title>fixture</title> +
|
|
157
|
+
// data-testid="doc-root") takes the branch.
|
|
158
|
+
expect(html).toContain('<meta charSet="utf-8"')
|
|
159
|
+
expect(html).toContain('<link rel="preconnect" href="http://localhost/assets/"')
|
|
160
|
+
expect(html).not.toContain('data-testid="doc-root"')
|
|
161
|
+
// Built-in Body marker.
|
|
162
|
+
expect(html).toContain("window.__SAFE_AREA_INITIAL__")
|
|
163
|
+
expect(res.isEnded()).toBe(true)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it("flags PREFLIGHT-021 and falls back to the built-in document when CustomDocument is not a function", async () => {
|
|
167
|
+
vi.doMock(TEMPLATE_DOC, () => ({ default: { not: "a function" } }))
|
|
168
|
+
mockManifest()
|
|
169
|
+
|
|
170
|
+
const { default: handler } = await import("../../src/server/renderer/handler.jsx")
|
|
171
|
+
const { req, res } = makeReqRes("/")
|
|
172
|
+
|
|
173
|
+
await handler(req, res)
|
|
174
|
+
await res.waitForEnd()
|
|
175
|
+
|
|
176
|
+
// validateCustomDocument -> handleError -> console.log (default
|
|
177
|
+
// output mode), not console.error.
|
|
178
|
+
const logged = (console.log as any).mock.calls.flat().join("\n")
|
|
179
|
+
expect(logged).toContain("PREFLIGHT-021")
|
|
180
|
+
|
|
181
|
+
// Fallback actually happened: built-in Head rendered, fixture
|
|
182
|
+
// CustomDocument did not.
|
|
183
|
+
const html = res.getHtml()
|
|
184
|
+
expect(html).toContain('<meta charSet="utf-8"')
|
|
185
|
+
expect(html).not.toContain('data-testid="doc-root"')
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
describe("SSR handler — critical asset collection", () => {
|
|
190
|
+
it("resolves the matched route's chunk from the manifest: inlines its CSS and emits its module script + modulepreload in <head>", async () => {
|
|
191
|
+
vi.doMock(TEMPLATE_DOC, () => ({ default: undefined }))
|
|
192
|
+
mockManifest()
|
|
193
|
+
|
|
194
|
+
const { default: handler } = await import("../../src/server/renderer/handler.jsx")
|
|
195
|
+
const { req, res } = makeReqRes("/")
|
|
196
|
+
|
|
197
|
+
await handler(req, res)
|
|
198
|
+
await res.waitForEnd()
|
|
199
|
+
|
|
200
|
+
const html = res.getHtml()
|
|
201
|
+
const headEnd = html.indexOf("</head>")
|
|
202
|
+
|
|
203
|
+
// Critical CSS inlined as <style> from disk.
|
|
204
|
+
expect(html).toContain("<style>.home{color:red}</style>")
|
|
205
|
+
expect(html.indexOf("<style>.home{color:red}</style>")).toBeLessThan(headEnd)
|
|
206
|
+
|
|
207
|
+
// Critical JS as <script type="module"> + a modulepreload link, both
|
|
208
|
+
// in <head>. URL = PUBLIC_STATIC_ASSET_URL + PUBLIC_STATIC_ASSET_PATH
|
|
209
|
+
// + manifest file.
|
|
210
|
+
const jsUrl = "http://localhost/assets/home.abc123.js"
|
|
211
|
+
expect(html).toContain(`<script type="module" src="${jsUrl}">`)
|
|
212
|
+
expect(html).toContain(`<link rel="modulepreload" href="${jsUrl}"`)
|
|
213
|
+
expect(html.indexOf(`<script type="module" src="${jsUrl}">`)).toBeLessThan(headEnd)
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it("suppresses module scripts and modulepreload links for a bot UA, but still inlines critical CSS", async () => {
|
|
217
|
+
vi.doMock(TEMPLATE_DOC, () => ({ default: undefined }))
|
|
218
|
+
mockManifest()
|
|
219
|
+
|
|
220
|
+
const { default: handler } = await import("../../src/server/renderer/handler.jsx")
|
|
221
|
+
const { req, res } = makeReqRes(
|
|
222
|
+
"/",
|
|
223
|
+
"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
await handler(req, res)
|
|
227
|
+
await res.waitForEnd()
|
|
228
|
+
|
|
229
|
+
const html = res.getHtml()
|
|
230
|
+
expect(html).toContain("window.__CATALYST_IS_BOT__=true")
|
|
231
|
+
// CSS is not JS — bots still get it inlined (no FOUC).
|
|
232
|
+
expect(html).toContain("<style>.home{color:red}</style>")
|
|
233
|
+
// !isBot gates in Head.jsx (jsScripts, criticalPreloadLinks).
|
|
234
|
+
expect(html).not.toContain('<script type="module" src="http://localhost/assets/home.abc123.js">')
|
|
235
|
+
expect(html).not.toContain('rel="modulepreload"')
|
|
236
|
+
})
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
describe("SSR handler — deferred assets discovered during render", () => {
|
|
240
|
+
it("serializes render-tracked component keys and flushes deferred CSS/JS after the document", async () => {
|
|
241
|
+
vi.doMock(TEMPLATE_DOC, () => ({ default: undefined }))
|
|
242
|
+
mockManifest()
|
|
243
|
+
|
|
244
|
+
const { default: handler } = await import("../../src/server/renderer/handler.jsx")
|
|
245
|
+
const { req, res } = makeReqRes("/widget")
|
|
246
|
+
|
|
247
|
+
await handler(req, res)
|
|
248
|
+
await res.waitForEnd()
|
|
249
|
+
|
|
250
|
+
const html = res.getHtml()
|
|
251
|
+
const htmlEnd = html.indexOf("</html>")
|
|
252
|
+
|
|
253
|
+
// WidgetPage called chunkExtractor.addComponent(WIDGET_CACHE_KEY)
|
|
254
|
+
// during render (the Split.jsx server path). getRenderedComponentKeys
|
|
255
|
+
// now reflects it.
|
|
256
|
+
expect(html).toContain('window.__SSR_RENDERED_COMPONENTS__=new Set(["src/js/components/Widget"])')
|
|
257
|
+
|
|
258
|
+
// Deferred chunk's CSS is flushed as a post-</html> <style> on the
|
|
259
|
+
// first visit (not yet route-cached), and its JS as a module script.
|
|
260
|
+
const deferredStyleIdx = html.indexOf("<style>.widget{color:blue}</style>")
|
|
261
|
+
expect(deferredStyleIdx).toBeGreaterThan(htmlEnd)
|
|
262
|
+
expect(html).toContain(
|
|
263
|
+
'<script type="module" src="http://localhost/assets/widget.def456.js"></script>',
|
|
264
|
+
)
|
|
265
|
+
expect(html.indexOf('src="http://localhost/assets/widget.def456.js"')).toBeGreaterThan(htmlEnd)
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it("inlines previously-deferred CSS in <head> on the second visit to the same route", async () => {
|
|
269
|
+
vi.doMock(TEMPLATE_DOC, () => ({ default: undefined }))
|
|
270
|
+
mockManifest()
|
|
271
|
+
|
|
272
|
+
const { default: handler } = await import("../../src/server/renderer/handler.jsx")
|
|
273
|
+
|
|
274
|
+
// First visit — deferred CSS learned, flushed after </html>.
|
|
275
|
+
const first = makeReqRes("/widget")
|
|
276
|
+
await handler(first.req, first.res)
|
|
277
|
+
await first.res.waitForEnd()
|
|
278
|
+
const firstHtml = first.res.getHtml()
|
|
279
|
+
expect(firstHtml.indexOf("<style>.widget{color:blue}</style>")).toBeGreaterThan(
|
|
280
|
+
firstHtml.indexOf("</html>"),
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
// Second visit — same route key. getCachedDeferredCssPathsForRoute
|
|
284
|
+
// now returns widget.def456.css, so it is inlined in <head> via
|
|
285
|
+
// deferredRouteInlineCss (Head.jsx:42) and NOT re-flushed after body
|
|
286
|
+
// (registerDeferredAssetsForRoute reports it as already seen).
|
|
287
|
+
const second = makeReqRes("/widget")
|
|
288
|
+
await handler(second.req, second.res)
|
|
289
|
+
await second.res.waitForEnd()
|
|
290
|
+
const secondHtml = second.res.getHtml()
|
|
291
|
+
|
|
292
|
+
const headEnd = secondHtml.indexOf("</head>")
|
|
293
|
+
const htmlEnd = secondHtml.indexOf("</html>")
|
|
294
|
+
const styleIdx = secondHtml.indexOf("<style>.widget{color:blue}</style>")
|
|
295
|
+
expect(styleIdx).toBeGreaterThan(-1)
|
|
296
|
+
expect(styleIdx).toBeLessThan(headEnd)
|
|
297
|
+
// No second post-body <style> for the same CSS.
|
|
298
|
+
expect(secondHtml.indexOf("<style>.widget{color:blue}</style>", htmlEnd)).toBe(-1)
|
|
299
|
+
|
|
300
|
+
// Warm-cache modulepreload for the deferred JS learned last visit
|
|
301
|
+
// (getDeferredPreloadScriptUrls -> deferredPreloadLinks, Head.jsx:33).
|
|
302
|
+
const preloadIdx = secondHtml.indexOf(
|
|
303
|
+
'<link rel="modulepreload" href="http://localhost/assets/widget.def456.js"',
|
|
304
|
+
)
|
|
305
|
+
expect(preloadIdx).toBeGreaterThan(-1)
|
|
306
|
+
expect(preloadIdx).toBeLessThan(headEnd)
|
|
307
|
+
})
|
|
308
|
+
|
|
309
|
+
it("does not flush deferred JS after the document for a bot UA", async () => {
|
|
310
|
+
vi.doMock(TEMPLATE_DOC, () => ({ default: undefined }))
|
|
311
|
+
mockManifest()
|
|
312
|
+
|
|
313
|
+
const { default: handler } = await import("../../src/server/renderer/handler.jsx")
|
|
314
|
+
const { req, res } = makeReqRes(
|
|
315
|
+
"/widget",
|
|
316
|
+
"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)",
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
await handler(req, res)
|
|
320
|
+
await res.waitForEnd()
|
|
321
|
+
|
|
322
|
+
const html = res.getHtml()
|
|
323
|
+
const htmlEnd = html.indexOf("</html>")
|
|
324
|
+
// Deferred CSS still flushed (bots get styles); deferred JS is gated
|
|
325
|
+
// behind !isBot in the tail flush (handler.jsx:312) and skipped in
|
|
326
|
+
// registerDeferredAssetsForRoute.
|
|
327
|
+
expect(html.indexOf("<style>.widget{color:blue}</style>")).toBeGreaterThan(htmlEnd)
|
|
328
|
+
expect(html).not.toContain("widget.def456.js")
|
|
329
|
+
})
|
|
330
|
+
})
|