@rangojs/router 0.0.0-experimental.bd6e11bc → 0.0.0-experimental.bdaf10aa
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/AGENTS.md +8 -4
- package/README.md +296 -887
- package/dist/bin/rango.js +459 -91
- package/dist/testing/vitest.js +36 -2
- package/dist/vite/index.js +1708 -414
- package/package.json +35 -10
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +82 -5
- package/skills/bundle-analysis/SKILL.md +2 -2
- package/skills/cache-guide/SKILL.md +14 -9
- package/skills/caching/SKILL.md +221 -12
- package/skills/catalog.json +271 -0
- package/skills/comparison/SKILL.md +50 -0
- package/skills/comparison/agents/openai.yaml +4 -0
- package/skills/comparison/references/framework-comparison.md +837 -0
- package/skills/composability/SKILL.md +83 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/debug-manifest/SKILL.md +5 -3
- package/skills/defer-hydration/SKILL.md +235 -0
- package/skills/document-cache/SKILL.md +11 -3
- package/skills/fonts/SKILL.md +1 -1
- package/skills/handler-use/SKILL.md +9 -9
- package/skills/hooks/SKILL.md +73 -900
- package/skills/hooks/data.md +273 -0
- package/skills/hooks/handle-and-actions.md +103 -0
- package/skills/hooks/navigation.md +110 -0
- package/skills/hooks/outlets.md +41 -0
- package/skills/hooks/state.md +228 -0
- package/skills/hooks/urls.md +135 -0
- package/skills/host-router/SKILL.md +84 -7
- package/skills/i18n/SKILL.md +1 -1
- package/skills/intercept/SKILL.md +51 -17
- package/skills/layout/SKILL.md +38 -16
- package/skills/links/SKILL.md +1 -1
- package/skills/loader/SKILL.md +48 -20
- package/skills/middleware/SKILL.md +11 -5
- package/skills/migrate-nextjs/SKILL.md +203 -20
- package/skills/migrate-react-router/SKILL.md +59 -675
- package/skills/migrate-react-router/cloudflare-workers.md +129 -0
- package/skills/migrate-react-router/component-migration.md +196 -0
- package/skills/migrate-react-router/data-and-actions.md +225 -0
- package/skills/migrate-react-router/route-mapping.md +271 -0
- package/skills/mime-routes/SKILL.md +3 -3
- package/skills/observability/SKILL.md +70 -5
- package/skills/parallel/SKILL.md +32 -8
- package/skills/ppr/SKILL.md +622 -0
- package/skills/prerender/SKILL.md +59 -28
- package/skills/rango/SKILL.md +124 -50
- package/skills/response-routes/SKILL.md +78 -46
- package/skills/route/SKILL.md +85 -6
- package/skills/router-setup/SKILL.md +41 -6
- package/skills/scripts/SKILL.md +179 -0
- package/skills/server-actions/SKILL.md +28 -3
- package/skills/shell-manifest/SKILL.md +185 -0
- package/skills/streams-and-websockets/SKILL.md +1 -1
- package/skills/tailwind/SKILL.md +28 -4
- package/skills/testing/SKILL.md +68 -654
- package/skills/testing/bindings.md +103 -0
- package/skills/testing/cache-prerender.md +127 -0
- package/skills/testing/client-components.md +124 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +91 -0
- package/skills/testing/handles.md +131 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +122 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +85 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +123 -0
- package/skills/theme/SKILL.md +1 -1
- package/skills/typesafety/SKILL.md +45 -918
- package/skills/typesafety/env-and-bindings.md +254 -0
- package/skills/typesafety/generated-files-and-cli.md +335 -0
- package/skills/typesafety/params-and-search.md +153 -0
- package/skills/typesafety/route-types.md +209 -0
- package/skills/use-cache/SKILL.md +47 -17
- package/skills/vercel/SKILL.md +128 -0
- package/skills/view-transitions/SKILL.md +44 -1
- package/src/__augment-tests__/augmented.check.ts +2 -3
- package/src/__internal.ts +0 -65
- package/src/browser/action-coordinator.ts +1 -1
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +14 -27
- package/src/browser/connection-warmup.ts +134 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +178 -100
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/logging.ts +28 -0
- package/src/browser/merge-segment-loaders.ts +6 -4
- package/src/browser/navigation-bridge.ts +81 -68
- package/src/browser/navigation-client.ts +115 -70
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +153 -88
- package/src/browser/navigation-transaction.ts +0 -32
- package/src/browser/network-error-handler.ts +34 -7
- package/src/browser/partial-update.ts +157 -144
- package/src/browser/prefetch/cache.ts +148 -81
- package/src/browser/prefetch/fetch.ts +231 -51
- package/src/browser/prefetch/queue.ts +25 -7
- package/src/browser/rango-state.ts +157 -115
- package/src/browser/react/Link.tsx +40 -7
- package/src/browser/react/NavigationProvider.tsx +140 -99
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/filter-segment-order.ts +17 -2
- package/src/browser/react/index.ts +0 -51
- package/src/browser/react/location-state-shared.ts +14 -15
- package/src/browser/react/location-state.ts +0 -1
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +0 -5
- package/src/browser/react/use-href.tsx +8 -1
- package/src/browser/react/use-link-status.ts +33 -8
- package/src/browser/react/use-navigation.ts +10 -5
- package/src/browser/react/use-params.ts +0 -2
- package/src/browser/react/use-router.ts +6 -4
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +0 -13
- package/src/browser/response-adapter.ts +74 -8
- package/src/browser/rsc-router.tsx +97 -22
- package/src/browser/scroll-restoration.ts +15 -8
- package/src/browser/segment-reconciler.ts +31 -21
- package/src/browser/server-action-bridge.ts +216 -38
- package/src/browser/types.ts +94 -22
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/generate-manifest.ts +155 -131
- package/src/build/generate-route-types.ts +1 -1
- package/src/build/index.ts +11 -5
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +152 -22
- package/src/build/route-types/ast-route-extraction.ts +15 -8
- package/src/build/route-types/codegen.ts +12 -1
- package/src/build/route-types/include-resolution.ts +455 -61
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +15 -2
- package/src/build/route-types/router-processing.ts +77 -41
- package/src/build/route-types/source-scan.ts +105 -7
- package/src/build/runtime-discovery.ts +4 -1
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-key-utils.ts +58 -13
- package/src/cache/cache-policy.ts +108 -34
- package/src/cache/cache-runtime.ts +454 -101
- package/src/cache/cache-scope.ts +159 -54
- package/src/cache/cache-tag.ts +149 -0
- package/src/cache/cf/cf-base64.ts +33 -0
- package/src/cache/cf/cf-cache-constants.ts +127 -0
- package/src/cache/cf/cf-cache-store.ts +2170 -377
- package/src/cache/cf/cf-cache-types.ts +349 -0
- package/src/cache/cf/cf-kv-utils.ts +46 -0
- package/src/cache/cf/cf-tag-marker-memo.ts +105 -0
- package/src/cache/cf/index.ts +6 -16
- package/src/cache/document-cache.ts +126 -41
- package/src/cache/handle-snapshot.ts +70 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +243 -37
- package/src/cache/profile-registry.ts +46 -31
- package/src/cache/read-through-swr.ts +56 -12
- package/src/cache/segment-codec.ts +13 -21
- package/src/cache/shell-snapshot.ts +417 -0
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/types.ts +194 -99
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +1132 -0
- package/src/client.rsc.tsx +39 -22
- package/src/client.tsx +28 -58
- package/src/cloudflare/index.ts +11 -0
- package/src/cloudflare/tracing.ts +108 -0
- package/src/component-utils.ts +19 -0
- package/src/components/DefaultDocument.tsx +8 -2
- package/src/context-var.ts +13 -1
- package/src/decode-loader-results.ts +18 -2
- package/src/defer.ts +185 -0
- package/src/deps/ssr.ts +0 -1
- package/src/encode-kv.ts +49 -0
- package/src/errors.ts +0 -3
- package/src/escape-script.ts +52 -0
- package/src/handle.ts +57 -40
- package/src/handles/MetaTags.tsx +24 -53
- package/src/handles/Scripts.tsx +183 -0
- package/src/handles/breadcrumbs.ts +35 -8
- package/src/handles/deferred-resolution.ts +127 -0
- package/src/handles/is-thenable.ts +18 -0
- package/src/handles/meta.ts +14 -40
- package/src/handles/script.ts +244 -0
- package/src/host/cookie-handler.ts +9 -60
- package/src/host/errors.ts +13 -22
- package/src/host/index.ts +7 -0
- package/src/host/pattern-matcher.ts +23 -52
- package/src/host/router.ts +1 -65
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +6 -2
- package/src/href-client.ts +7 -12
- package/src/index.rsc.ts +88 -8
- package/src/index.ts +90 -16
- package/src/internal-debug.ts +11 -10
- package/src/loader.rsc.ts +19 -9
- package/src/loader.ts +12 -4
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +16 -16
- package/src/prerender/store.ts +32 -37
- package/src/prerender.ts +75 -7
- package/src/redirect-origin.ts +114 -0
- package/src/regex-escape.ts +8 -0
- package/src/render-error-thrower.tsx +20 -0
- package/src/response-utils.ts +25 -0
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +13 -49
- package/src/route-definition/dsl-helpers.ts +60 -53
- package/src/route-definition/helper-factories.ts +0 -2
- package/src/route-definition/helpers-types.ts +46 -46
- package/src/route-definition/index.ts +1 -2
- package/src/route-definition/redirect.ts +44 -11
- package/src/route-definition/resolve-handler-use.ts +6 -1
- package/src/route-definition/use-item-types.ts +3 -6
- package/src/route-map-builder.ts +41 -20
- package/src/route-types.ts +0 -5
- package/src/router/content-negotiation.ts +58 -23
- package/src/router/error-handling.ts +44 -17
- package/src/router/find-match.ts +129 -30
- package/src/router/handler-context.ts +6 -1
- package/src/router/instrument.ts +355 -0
- package/src/router/intercept-resolution.ts +35 -2
- package/src/router/lazy-includes.ts +79 -56
- package/src/router/loader-resolution.ts +151 -73
- package/src/router/logging.ts +0 -6
- package/src/router/manifest.ts +74 -40
- package/src/router/match-api.ts +76 -52
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +181 -178
- package/src/router/match-middleware/background-revalidation.ts +40 -24
- package/src/router/match-middleware/cache-lookup.ts +115 -194
- package/src/router/match-middleware/cache-store.ts +61 -50
- package/src/router/match-middleware/intercept-resolution.ts +0 -22
- package/src/router/match-middleware/segment-resolution.ts +0 -22
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +36 -67
- package/src/router/metrics.ts +0 -34
- package/src/router/middleware-types.ts +0 -116
- package/src/router/middleware.ts +231 -120
- package/src/router/navigation-snapshot.ts +7 -56
- package/src/router/params-util.ts +23 -0
- package/src/router/parse-pattern.ts +115 -0
- package/src/router/pattern-matching.ts +99 -152
- package/src/router/prefetch-cache-ttl.ts +51 -0
- package/src/router/prefetch-limits.ts +37 -0
- package/src/router/prerender-match.ts +111 -66
- package/src/router/preview-match.ts +3 -1
- package/src/router/request-classification.ts +47 -42
- package/src/router/revalidation.ts +75 -81
- package/src/router/route-snapshot.ts +14 -3
- package/src/router/router-context.ts +6 -29
- package/src/router/router-interfaces.ts +70 -8
- package/src/router/router-options.ts +126 -4
- package/src/router/segment-resolution/fresh.ts +104 -80
- package/src/router/segment-resolution/helpers.ts +86 -6
- package/src/router/segment-resolution/loader-cache.ts +155 -39
- package/src/router/segment-resolution/loader-mask.ts +60 -0
- package/src/router/segment-resolution/loader-snapshot.ts +259 -0
- package/src/router/segment-resolution/mask-nested.ts +83 -0
- package/src/router/segment-resolution/revalidation.ts +215 -304
- package/src/router/segment-resolution/static-store.ts +19 -5
- package/src/router/segment-resolution/streamed-handler-telemetry.ts +52 -0
- package/src/router/segment-resolution/view-transition-default.ts +35 -15
- package/src/router/segment-resolution.ts +5 -1
- package/src/router/segment-wrappers.ts +6 -5
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +54 -35
- package/src/router/telemetry-otel.ts +160 -200
- package/src/router/telemetry.ts +9 -23
- package/src/router/timeout.ts +0 -20
- package/src/router/tracing.ts +215 -0
- package/src/router/trie-matching.ts +171 -64
- package/src/router/types.ts +1 -63
- package/src/router/url-params.ts +13 -5
- package/src/router.ts +119 -48
- package/src/rsc/full-payload.ts +70 -0
- package/src/rsc/handler-context.ts +1 -0
- package/src/rsc/handler.ts +267 -152
- package/src/rsc/helpers.ts +78 -4
- package/src/rsc/index.ts +1 -4
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +114 -38
- package/src/rsc/manifest-init.ts +29 -42
- package/src/rsc/nonce.ts +10 -1
- package/src/rsc/origin-guard.ts +11 -15
- package/src/rsc/progressive-enhancement.ts +120 -13
- package/src/rsc/redirect-guard.ts +100 -0
- package/src/rsc/response-cache-serve.ts +238 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +58 -141
- package/src/rsc/rsc-rendering.ts +492 -49
- package/src/rsc/runtime-warnings.ts +14 -0
- package/src/rsc/server-action.ts +268 -82
- package/src/rsc/shell-capture.ts +1190 -0
- package/src/rsc/shell-serve.ts +181 -0
- package/src/rsc/transition-gate.ts +89 -0
- package/src/rsc/types.ts +45 -3
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +31 -26
- package/src/segment-loader-promise.ts +49 -4
- package/src/segment-system.tsx +260 -95
- package/src/server/context.ts +99 -9
- package/src/server/cookie-parse.ts +32 -0
- package/src/server/cookie-store.ts +125 -2
- package/src/server/handle-store.ts +21 -38
- package/src/server/loader-registry.ts +33 -42
- package/src/server/request-context.ts +379 -138
- package/src/ssr/index.tsx +491 -182
- package/src/ssr/inject-rsc-eager.ts +167 -0
- package/src/ssr/ssr-root.tsx +228 -0
- package/src/static-handler.ts +10 -13
- package/src/testing/cache-status.ts +44 -48
- package/src/testing/collect-handle.ts +14 -31
- package/src/testing/dispatch.ts +533 -160
- package/src/testing/e2e/fixture.ts +45 -11
- package/src/testing/e2e/index.ts +1 -22
- package/src/testing/e2e/matchers.ts +0 -16
- package/src/testing/e2e/parity.ts +85 -4
- package/src/testing/e2e/server.ts +12 -0
- package/src/testing/flight-matchers.ts +7 -14
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +36 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +30 -0
- package/src/testing/flight.ts +145 -70
- package/src/testing/generated-routes.ts +26 -50
- package/src/testing/index.ts +18 -19
- package/src/testing/internal/context.ts +184 -68
- package/src/testing/internal/flight-client-globals.ts +30 -0
- package/src/testing/internal/seed-vars.ts +54 -0
- package/src/testing/render-handler.ts +357 -0
- package/src/testing/render-route.tsx +134 -115
- package/src/testing/run-loader.ts +140 -51
- package/src/testing/run-middleware.ts +59 -33
- package/src/testing/run-transition-when.ts +164 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +1 -1
- package/src/testing/vitest-stubs/cloudflare-workers.ts +1 -1
- package/src/testing/vitest.ts +138 -16
- package/src/theme/ThemeProvider.tsx +56 -84
- package/src/theme/ThemeScript.tsx +7 -9
- package/src/theme/constants.ts +52 -13
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +22 -21
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +13 -4
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +15 -15
- package/src/types/handler-context.ts +45 -15
- package/src/types/index.ts +2 -10
- package/src/types/loader-types.ts +6 -3
- package/src/types/request-scope.ts +8 -22
- package/src/types/route-config.ts +20 -52
- package/src/types/route-entry.ts +0 -6
- package/src/types/segments.ts +100 -13
- package/src/urls/include-helper.ts +10 -12
- package/src/urls/include-provider.ts +71 -0
- package/src/urls/index.ts +2 -8
- package/src/urls/path-helper-types.ts +52 -14
- package/src/urls/path-helper.ts +5 -54
- package/src/urls/pattern-types.ts +36 -0
- package/src/urls/type-extraction.ts +76 -42
- package/src/urls/urls-function.ts +0 -14
- package/src/use-loader.tsx +0 -186
- package/src/vercel/index.ts +11 -0
- package/src/vercel/tracing.ts +88 -0
- package/src/vite/discovery/bundle-postprocess.ts +2 -1
- package/src/vite/discovery/dev-prerender-cache.ts +117 -0
- package/src/vite/discovery/discover-routers.ts +34 -43
- package/src/vite/discovery/discovery-errors.ts +61 -0
- package/src/vite/discovery/prerender-collection.ts +33 -46
- package/src/vite/discovery/state.ts +12 -1
- package/src/vite/discovery/virtual-module-codegen.ts +1 -11
- package/src/vite/index.ts +9 -0
- package/src/vite/inject-client-debug.ts +88 -0
- package/src/vite/plugin-types.ts +143 -10
- package/src/vite/plugins/cjs-to-esm.ts +8 -12
- package/src/vite/plugins/client-ref-dedup.ts +0 -11
- package/src/vite/plugins/client-ref-hashing.ts +0 -10
- package/src/vite/plugins/cloudflare-protocol-stub.ts +0 -20
- package/src/vite/plugins/expose-action-id.ts +2 -73
- package/src/vite/plugins/expose-id-utils.ts +85 -56
- package/src/vite/plugins/expose-ids/export-analysis.ts +30 -43
- package/src/vite/plugins/expose-ids/handler-transform.ts +5 -31
- package/src/vite/plugins/expose-ids/loader-transform.ts +12 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +98 -26
- package/src/vite/plugins/expose-internal-ids.ts +10 -1
- package/src/vite/plugins/performance-tracks.ts +0 -3
- package/src/vite/plugins/refresh-cmd.ts +1 -1
- package/src/vite/plugins/use-cache-transform.ts +21 -46
- package/src/vite/plugins/vercel-output.ts +384 -0
- package/src/vite/plugins/version-injector.ts +22 -27
- package/src/vite/plugins/version-plugin.ts +6 -66
- package/src/vite/plugins/virtual-entries.ts +137 -26
- package/src/vite/rango.ts +146 -135
- package/src/vite/router-discovery.ts +189 -48
- package/src/vite/utils/ast-handler-extract.ts +11 -20
- package/src/vite/utils/bundle-analysis.ts +6 -13
- package/src/vite/utils/client-chunks.ts +0 -6
- package/src/vite/utils/directive-prologue.ts +40 -0
- package/src/vite/utils/forward-user-plugins.ts +0 -22
- package/src/vite/utils/manifest-utils.ts +4 -75
- package/src/vite/utils/package-resolution.ts +1 -73
- package/src/vite/utils/prerender-utils.ts +71 -44
- package/src/vite/utils/shared-utils.ts +55 -37
- package/src/browser/react/use-client-cache.ts +0 -58
- package/src/browser/shallow.ts +0 -40
- package/src/handles/index.ts +0 -7
- package/src/network-error-thrower.tsx +0 -23
- package/src/router/middleware-cookies.ts +0 -55
|
@@ -14,14 +14,19 @@
|
|
|
14
14
|
* (de)serialization. Consequences:
|
|
15
15
|
* - It will NOT catch server/client boundary reference-identity remount bugs
|
|
16
16
|
* (a server-serialized component reference differing from the client
|
|
17
|
-
* reference). Use
|
|
17
|
+
* reference). Use renderServerTree / e2e for those.
|
|
18
18
|
* - It will NOT catch real Flight serialization errors (non-serializable
|
|
19
19
|
* props crossing the RSC boundary), loader execution on the server,
|
|
20
|
-
* middleware, or handler ordering. Those are
|
|
20
|
+
* middleware, or handler ordering. Those are renderServerTree / renderHandler
|
|
21
|
+
* / e2e territory.
|
|
21
22
|
* - Loader data, location state, and handle output are SEEDED directly into
|
|
22
23
|
* client context (see the `loaders` / `locationState` / `handles` options) —
|
|
23
24
|
* nothing is executed on the server. This exercises the read path
|
|
24
25
|
* (useLoader / useLocationState / useHandle from context), not the run path.
|
|
26
|
+
* - navigate() commits synchronously, so it does NOT drive the navigation
|
|
27
|
+
* lifecycle: useNavigation().state, useLinkStatus().pending, and
|
|
28
|
+
* useAction().state stay "idle". Assert pending/loading/submitting transition
|
|
29
|
+
* states with renderServerTree / e2e instead (navigate() warns once if used).
|
|
25
30
|
* What it DOES cover: client hooks that read NavigationProvider /
|
|
26
31
|
* OutletContext — useParams, useReverse, useHref, useMount, useNavigation,
|
|
27
32
|
* useRouter, usePathname, useSearchParams, Outlet nesting, useLoader /
|
|
@@ -41,17 +46,22 @@ import {
|
|
|
41
46
|
generateHistoryKey,
|
|
42
47
|
} from "../browser/navigation-store.js";
|
|
43
48
|
import { createEventController } from "../browser/event-controller.js";
|
|
49
|
+
import { resolveDeferredHandleValues } from "../handles/deferred-resolution.js";
|
|
44
50
|
import type { NavigationStore, NavigationBridge } from "../browser/types.js";
|
|
45
51
|
import type { EventController } from "../browser/event-controller.js";
|
|
46
52
|
import type { ResolvedSegment, RscMetadata } from "../browser/types.js";
|
|
47
53
|
import { NavigationProvider } from "../browser/react/NavigationProvider.js";
|
|
48
|
-
import {
|
|
54
|
+
import {
|
|
55
|
+
compilePattern,
|
|
56
|
+
buildParamsFromMatch,
|
|
57
|
+
} from "../router/pattern-matching.js";
|
|
49
58
|
import { normalizeBasename } from "../router/basename.js";
|
|
50
59
|
import type { LoaderDefinition } from "../types.js";
|
|
51
60
|
import type { LocationStateDefinition } from "../browser/react/location-state-shared.js";
|
|
52
61
|
import type { Handle } from "../handle.js";
|
|
53
62
|
import type { ThemeConfig } from "../theme/types.js";
|
|
54
63
|
import { resolveThemeConfig } from "../theme/constants.js";
|
|
64
|
+
import { isUnderTestRunner } from "../runtime-env.js";
|
|
55
65
|
|
|
56
66
|
const TEST_ORIGIN = "http://localhost";
|
|
57
67
|
|
|
@@ -62,11 +72,6 @@ const TEST_ORIGIN = "http://localhost";
|
|
|
62
72
|
*/
|
|
63
73
|
export type HandleDataSeed = Record<string, Record<string, unknown[]>>;
|
|
64
74
|
|
|
65
|
-
// Loaders and location-state defs carry an id (`$$id` / `__rsc_ls_key`) that the
|
|
66
|
-
// Vite plugin injects at build time; in a bare test it is "". These helpers
|
|
67
|
-
// assign a synthetic stable id (mutating the handle, tracked per-object) so that
|
|
68
|
-
// seeding by reference lines up with the read path (useLoader / useLocationState
|
|
69
|
-
// both read the id off the handle at call time).
|
|
70
75
|
const syntheticIds = new WeakMap<object, string>();
|
|
71
76
|
let syntheticIdCounter = 0;
|
|
72
77
|
|
|
@@ -85,7 +90,6 @@ function ensureSyntheticId(
|
|
|
85
90
|
return id;
|
|
86
91
|
}
|
|
87
92
|
|
|
88
|
-
/** One-level clone of a raw handle seed so we don't mutate the caller's object. */
|
|
89
93
|
function cloneHandleSeed(seed?: HandleDataSeed): HandleDataSeed {
|
|
90
94
|
const out: HandleDataSeed = {};
|
|
91
95
|
for (const [name, segMap] of Object.entries(seed ?? {})) {
|
|
@@ -94,17 +98,11 @@ function cloneHandleSeed(seed?: HandleDataSeed): HandleDataSeed {
|
|
|
94
98
|
return out;
|
|
95
99
|
}
|
|
96
100
|
|
|
97
|
-
/**
|
|
98
|
-
* One node of the route definition passed to renderRoute. The array models a
|
|
99
|
-
* single matched route plus its optional layout chain — element order is
|
|
100
|
-
* outermost layout first, the leaf route last (the same root-to-leaf order the
|
|
101
|
-
* real matcher produces).
|
|
102
|
-
*/
|
|
103
101
|
export interface RenderRouteSpec {
|
|
104
102
|
/**
|
|
105
103
|
* The route pattern this node matches, e.g. "/products/:productId". The LAST
|
|
106
104
|
* spec in the array is treated as the leaf route; earlier specs are layouts
|
|
107
|
-
* wrapping it. Only the leaf pattern is matched against `
|
|
105
|
+
* wrapping it. Only the leaf pattern is matched against the `request` URL to
|
|
108
106
|
* extract params; layout patterns are informational.
|
|
109
107
|
*/
|
|
110
108
|
path: string;
|
|
@@ -131,8 +129,13 @@ export interface RenderRouteSpec {
|
|
|
131
129
|
* Options for renderRoute.
|
|
132
130
|
*/
|
|
133
131
|
export interface RenderRouteOptions {
|
|
134
|
-
/**
|
|
135
|
-
|
|
132
|
+
/**
|
|
133
|
+
* The initial location to render at: a `Request`, or a URL string (absolute or
|
|
134
|
+
* path). Only the URL is read (this is a client render — headers/method are
|
|
135
|
+
* ignored); named `request` for parity with the other primitives. Defaults to
|
|
136
|
+
* the leaf spec's static prefix or "/".
|
|
137
|
+
*/
|
|
138
|
+
request?: Request | string;
|
|
136
139
|
/**
|
|
137
140
|
* Loader data to seed into client context, keyed by loader id ($$id). A
|
|
138
141
|
* component calling useLoader(SomeLoader) reads `loaderData[SomeLoader.$$id]`.
|
|
@@ -148,16 +151,28 @@ export interface RenderRouteOptions {
|
|
|
148
151
|
* Passing `[loader, data]` pairs lets renderRoute assign a synthetic stable id
|
|
149
152
|
* and wire `useLoader` to it. Prefer this over `loaderData` for real handles.
|
|
150
153
|
*
|
|
154
|
+
* NOTE: when a real handle has no `$$id`, renderRoute MUTATES it to assign a
|
|
155
|
+
* synthetic stable id (so repeat renders key consistently). This is a side
|
|
156
|
+
* effect on your input object; a handle reused across tests keeps that id.
|
|
157
|
+
*
|
|
151
158
|
* @example
|
|
159
|
+
* // useLoader returns an ENVELOPE — destructure `data`, it is not the bare value.
|
|
160
|
+
* function CartBadge() {
|
|
161
|
+
* const { data } = useLoader(CartLoader); // NOT `useLoader(CartLoader).itemCount`
|
|
162
|
+
* return <span>{data.itemCount}</span>;
|
|
163
|
+
* }
|
|
152
164
|
* renderRoute([{ path: "/cart", Component: CartBadge }], {
|
|
153
165
|
* loaders: [[CartLoader, { itemCount: 3, total: 89.97 }]],
|
|
154
166
|
* });
|
|
155
167
|
*/
|
|
156
168
|
loaders?: ReadonlyArray<readonly [LoaderDefinition<any>, unknown]>;
|
|
157
169
|
/**
|
|
158
|
-
* Explicit params. Merged over (and overriding) params extracted from
|
|
159
|
-
* `
|
|
160
|
-
* avoid relying on URL parsing.
|
|
170
|
+
* Explicit params. Merged over (and overriding) params extracted from the
|
|
171
|
+
* `request` URL. Use this when the URL alone cannot express the params, or to
|
|
172
|
+
* avoid relying on URL parsing. Supplying params also OPTS OUT of the
|
|
173
|
+
* request/leaf match check: a `request` whose pathname does not resolve the
|
|
174
|
+
* leaf is normally rejected under the test runner, but passing params here
|
|
175
|
+
* tells renderRoute the request is intentionally not the param source.
|
|
161
176
|
*/
|
|
162
177
|
params?: Record<string, string>;
|
|
163
178
|
/**
|
|
@@ -221,6 +236,10 @@ export interface RenderRouteOptions {
|
|
|
221
236
|
* exactly as `renderSegments` does in production (a segment whose `mountPath`
|
|
222
237
|
* is set is wrapped in a MountContextProvider). Normalized like a path prefix
|
|
223
238
|
* (leading slash forced, trailing stripped, bare "/" -> root). Defaults to "/".
|
|
239
|
+
* An explicitly-passed `request` must match the leaf `path` directly (paths are
|
|
240
|
+
* include-RELATIVE; the mount does NOT rewrite the request) — pass the relative
|
|
241
|
+
* path, not the mount-prefixed one, or renderRoute throws rather than silently
|
|
242
|
+
* rendering empty params.
|
|
224
243
|
*
|
|
225
244
|
* @example
|
|
226
245
|
* renderRoute([{ path: "/c/wine", Component: ProductPage }], { mount: "/shop" });
|
|
@@ -235,6 +254,20 @@ export interface RenderRouteOptions {
|
|
|
235
254
|
* component.
|
|
236
255
|
*/
|
|
237
256
|
theme?: ThemeConfig | true;
|
|
257
|
+
/**
|
|
258
|
+
* CSP nonce to seed via NonceContext, so a component calling `useNonce()`
|
|
259
|
+
* (e.g. an analytics/GTM head-script component) sees this value — mirroring
|
|
260
|
+
* what the SSR renderer provides per request. Defaults to undefined (the
|
|
261
|
+
* browser default), matching production client behavior.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* const { getByTestId } = await renderRoute(
|
|
265
|
+
* [{ path: "/", Component: NonceProbe }],
|
|
266
|
+
* { nonce: "test-nonce" },
|
|
267
|
+
* );
|
|
268
|
+
* expect(getByTestId("nonce").textContent).toBe("test-nonce");
|
|
269
|
+
*/
|
|
270
|
+
nonce?: string;
|
|
238
271
|
}
|
|
239
272
|
|
|
240
273
|
/**
|
|
@@ -266,11 +299,6 @@ interface ResolvedMatch {
|
|
|
266
299
|
pathname: string;
|
|
267
300
|
}
|
|
268
301
|
|
|
269
|
-
/**
|
|
270
|
-
* Match a pathname against the leaf spec's pattern and extract params.
|
|
271
|
-
* Returns null when the pattern does not match (params then fall back to the
|
|
272
|
-
* caller-provided `options.params`).
|
|
273
|
-
*/
|
|
274
302
|
function matchLeaf(
|
|
275
303
|
pattern: string,
|
|
276
304
|
pathname: string,
|
|
@@ -278,17 +306,11 @@ function matchLeaf(
|
|
|
278
306
|
const compiled = compilePattern(pattern);
|
|
279
307
|
const match = compiled.regex.exec(pathname);
|
|
280
308
|
if (!match) return null;
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
if (value !== undefined) {
|
|
285
|
-
params[name] = decodeURIComponent(value);
|
|
286
|
-
}
|
|
287
|
-
});
|
|
288
|
-
return params;
|
|
309
|
+
// Reuse the production param builder so the harness matches the real matcher
|
|
310
|
+
// exactly (named catch-all "" binding, decoding) instead of forking it.
|
|
311
|
+
return buildParamsFromMatch(match, compiled.paramNames, compiled.catchAll);
|
|
289
312
|
}
|
|
290
313
|
|
|
291
|
-
/** Derive a usable initial pathname from a leaf pattern when none is given. */
|
|
292
314
|
function staticPrefix(pattern: string): string {
|
|
293
315
|
const out: string[] = [];
|
|
294
316
|
for (const part of pattern.split("/")) {
|
|
@@ -299,13 +321,6 @@ function staticPrefix(pattern: string): string {
|
|
|
299
321
|
return "/" + out.join("/");
|
|
300
322
|
}
|
|
301
323
|
|
|
302
|
-
/**
|
|
303
|
-
* Build the synthetic ResolvedSegment[] for a matched route. Produces, in
|
|
304
|
-
* root-to-leaf order: one layout segment per non-leaf spec, then the leaf route
|
|
305
|
-
* segment, plus a loader segment for each seeded loader id attached to the
|
|
306
|
-
* owning spec. Segment ids follow the real convention (L0, L0L1, ..., the leaf
|
|
307
|
-
* route as L0...R{n}; loaders as {parentId}D{i}.{loaderId}).
|
|
308
|
-
*/
|
|
309
324
|
function buildSegments(
|
|
310
325
|
routes: RenderRouteSpec[],
|
|
311
326
|
params: Record<string, string>,
|
|
@@ -338,20 +353,13 @@ function buildSegments(
|
|
|
338
353
|
params,
|
|
339
354
|
belongsToRoute: true,
|
|
340
355
|
};
|
|
341
|
-
// Model an include() mount: every component segment in the chain shares the
|
|
342
|
-
// same prefix, so renderSegments wraps each in a MountContextProvider and
|
|
343
|
-
// useMount() resolves the mounted prefix (production sets mountPath on every
|
|
344
|
-
// segment of an included subtree). Must be applied identically at both
|
|
345
|
-
// buildSegments call sites or segment-structure-assert flags a remount.
|
|
346
356
|
if (mount) node.mountPath = mount;
|
|
347
|
-
// A leaf-owned layout component wraps the route via its own layout element.
|
|
348
357
|
if (isLeaf && spec.layout) {
|
|
349
358
|
const Layout = spec.layout;
|
|
350
359
|
node.layout = <Layout />;
|
|
351
360
|
}
|
|
352
361
|
segments.push(node);
|
|
353
362
|
|
|
354
|
-
// Determine which seeded loader ids this spec owns.
|
|
355
363
|
const ownedIds = spec.loaderIds
|
|
356
364
|
? spec.loaderIds.filter((id) => id in loaderData)
|
|
357
365
|
: isLeaf
|
|
@@ -375,30 +383,6 @@ function buildSegments(
|
|
|
375
383
|
return segments;
|
|
376
384
|
}
|
|
377
385
|
|
|
378
|
-
/**
|
|
379
|
-
* Render a CLIENT component (and its layout chain) inside the router's
|
|
380
|
-
* NavigationProvider for unit testing. Exported from `@rangojs/router/testing/dom`
|
|
381
|
-
* (its own entry, kept out of the main `@rangojs/router/testing` barrel so that
|
|
382
|
-
* barrel never references React/@testing-library/react). Async so the heavy
|
|
383
|
-
* @testing-library/react dependency is loaded only at call time.
|
|
384
|
-
*
|
|
385
|
-
* @example
|
|
386
|
-
* ```tsx
|
|
387
|
-
* // @vitest-environment happy-dom
|
|
388
|
-
* import { renderRoute } from "@rangojs/router/testing/dom";
|
|
389
|
-
*
|
|
390
|
-
* function Product() {
|
|
391
|
-
* const { productId } = useParams<{ productId: string }>();
|
|
392
|
-
* const reverse = useReverse({ product: "/products/:productId" });
|
|
393
|
-
* return <a href={reverse("product", { productId: "2" })}>{productId}</a>;
|
|
394
|
-
* }
|
|
395
|
-
*
|
|
396
|
-
* const { getByText, router } = await renderRoute(
|
|
397
|
-
* [{ path: "/products/:productId", Component: Product }],
|
|
398
|
-
* { initialUrl: "/products/1" },
|
|
399
|
-
* );
|
|
400
|
-
* ```
|
|
401
|
-
*/
|
|
402
386
|
export async function renderRoute(
|
|
403
387
|
routes: RenderRouteSpec[],
|
|
404
388
|
options: RenderRouteOptions = {},
|
|
@@ -406,37 +390,34 @@ export async function renderRoute(
|
|
|
406
390
|
if (routes.length === 0) {
|
|
407
391
|
throw new Error("renderRoute: `routes` must contain at least one entry");
|
|
408
392
|
}
|
|
393
|
+
if ("initialUrl" in options) {
|
|
394
|
+
throw new Error(
|
|
395
|
+
"renderRoute: the `initialUrl` option was renamed to `request`. " +
|
|
396
|
+
"Pass { request: <Request | url> } instead.",
|
|
397
|
+
);
|
|
398
|
+
}
|
|
409
399
|
|
|
410
400
|
const { render, act } = await import("@testing-library/react");
|
|
411
401
|
|
|
412
402
|
const leaf = routes[routes.length - 1];
|
|
413
|
-
const
|
|
403
|
+
const requestUrl =
|
|
404
|
+
options.request instanceof Request ? options.request.url : options.request;
|
|
405
|
+
const initialUrl = requestUrl ?? staticPrefix(leaf.path) ?? "/";
|
|
414
406
|
const url = new URL(initialUrl, TEST_ORIGIN);
|
|
415
407
|
|
|
416
|
-
// Seed loader data: explicit-id entries from `loaderData`, plus by-reference
|
|
417
|
-
// entries from `loaders` (assigning synthetic ids to real handles whose `$$id`
|
|
418
|
-
// is empty in a bare test).
|
|
419
408
|
const loaderData: Record<string, unknown> = { ...(options.loaderData ?? {}) };
|
|
420
409
|
for (const [loader, data] of options.loaders ?? []) {
|
|
421
410
|
loaderData[ensureSyntheticId(loader as object, "$$id")] = data;
|
|
422
411
|
}
|
|
423
412
|
|
|
424
|
-
// Seed location state into history.state so useLocationState(def) resolves.
|
|
425
|
-
// Keyed defs read history.state[def.__rsc_ls_key]; assign a synthetic key when
|
|
426
|
-
// the injected one is empty (bare test). RESET history.state to only this
|
|
427
|
-
// call's seeds (not a merge) so a previous render's seeded state does not leak
|
|
428
|
-
// into a later render in the same DOM environment.
|
|
429
413
|
if (typeof window !== "undefined") {
|
|
430
414
|
const stateObj: Record<string, unknown> = {};
|
|
431
415
|
for (const [def, value] of options.locationState ?? []) {
|
|
432
416
|
stateObj[ensureSyntheticId(def as object, "__rsc_ls_key")] = value;
|
|
433
417
|
}
|
|
434
|
-
// No URL arg: useLocationState reads history.state (not the URL), and passing
|
|
435
|
-
// a TEST_ORIGIN URL would trip the DOM env's same-origin check.
|
|
436
418
|
window.history.replaceState(stateObj, "");
|
|
437
419
|
}
|
|
438
420
|
|
|
439
|
-
// Resolve params: URL-extracted params first, explicit params override.
|
|
440
421
|
const resolve = (pathname: string): ResolvedMatch => {
|
|
441
422
|
const matched = matchLeaf(leaf.path, pathname) ?? {};
|
|
442
423
|
return {
|
|
@@ -446,11 +427,34 @@ export async function renderRoute(
|
|
|
446
427
|
};
|
|
447
428
|
const initialMatch = resolve(url.pathname);
|
|
448
429
|
|
|
449
|
-
// Reuse the real browser primitives so context shape matches production.
|
|
450
430
|
const historyKey = generateHistoryKey(url.href);
|
|
451
|
-
// Normalize the include() mount prefix once and apply it at BOTH buildSegments
|
|
452
|
-
// call sites (initial + navigate) so mountPath is consistent across renders.
|
|
453
431
|
const mount = normalizeBasename(options.mount);
|
|
432
|
+
// Fail loud on a request that cannot resolve the leaf route (a typo, or the
|
|
433
|
+
// mount-prefixed-vs-relative confusion) instead of silently rendering empty
|
|
434
|
+
// params (matchLeaf -> null -> {}). renderRoute paths are include-RELATIVE and
|
|
435
|
+
// resolve() matches the request against the leaf as-is, so the request must be
|
|
436
|
+
// the relative form — a mount does NOT rewrite it. Only checked when `request`
|
|
437
|
+
// was passed explicitly (a defaulted request is staticPrefix of the leaf and
|
|
438
|
+
// always matches). Skipped when explicit `params` are supplied: those are
|
|
439
|
+
// merged over the URL-extracted params in resolve(), so the request is
|
|
440
|
+
// intentionally not the param source and an empty matchLeaf is not the trap.
|
|
441
|
+
// Gated on the test runner so it can never affect production.
|
|
442
|
+
if (
|
|
443
|
+
options.request !== undefined &&
|
|
444
|
+
Object.keys(options.params ?? {}).length === 0 &&
|
|
445
|
+
isUnderTestRunner() &&
|
|
446
|
+
matchLeaf(leaf.path, url.pathname) === null
|
|
447
|
+
) {
|
|
448
|
+
throw new Error(
|
|
449
|
+
`renderRoute: request "${url.pathname}" does not match the leaf route ` +
|
|
450
|
+
`"${leaf.path}"${mount ? ` (mount "${mount}")` : ""}. renderRoute paths ` +
|
|
451
|
+
`are include-RELATIVE: pass a request that matches "${leaf.path}" ` +
|
|
452
|
+
`(e.g. "${staticPrefix(leaf.path)}"). A mount does NOT auto-rewrite the ` +
|
|
453
|
+
`request — pass the relative path, not the mount-prefixed one. If the ` +
|
|
454
|
+
`request URL intentionally does not carry the params, pass them ` +
|
|
455
|
+
`explicitly via the \`params\` option to bypass this check.`,
|
|
456
|
+
);
|
|
457
|
+
}
|
|
454
458
|
const initialSegments = buildSegments(
|
|
455
459
|
routes,
|
|
456
460
|
initialMatch.params,
|
|
@@ -464,36 +468,44 @@ export async function renderRoute(
|
|
|
464
468
|
initialSegments,
|
|
465
469
|
crossTabSync: false,
|
|
466
470
|
});
|
|
467
|
-
// Seed handle data: raw `handle` entries plus by-reference `handles` attached
|
|
468
|
-
// to the leaf route segment under each handle's id (so useHandle(handle)
|
|
469
|
-
// resolves the pushed values).
|
|
470
471
|
const leafRouteSegmentId =
|
|
471
472
|
[...initialSegments].reverse().find((s) => s.type === "route")?.id ??
|
|
472
473
|
initialSegments[initialSegments.length - 1]?.id;
|
|
473
474
|
const handleSeed: HandleDataSeed = cloneHandleSeed(options.handle);
|
|
474
475
|
for (const [handle, values] of options.handles ?? []) {
|
|
475
476
|
if (leafRouteSegmentId === undefined) continue;
|
|
476
|
-
// createHandle always has a non-empty $$id (the Vite plugin injects one, and
|
|
477
|
-
// createHandle assigns a runtime fallback otherwise) with its REAL collect
|
|
478
|
-
// registered — so seeding under handle.$$id makes useHandle(handle) run the
|
|
479
|
-
// handle's actual collect/accumulator (custom collects included), not just a
|
|
480
|
-
// default flatten.
|
|
481
477
|
const id = (handle as unknown as { $$id: string }).$$id;
|
|
482
478
|
(handleSeed[id] ??= {})[leafRouteSegmentId] = values;
|
|
483
479
|
}
|
|
484
480
|
|
|
485
481
|
const eventController = createEventController({ initialLocation: url });
|
|
486
482
|
eventController.setParams(initialMatch.params);
|
|
483
|
+
// Resolve-by-default: resolve any deferred (Promise) seeded handle values
|
|
484
|
+
// before applying, so the seeded handles reach collect/useHandle resolved —
|
|
485
|
+
// matching what the server/client do in a real app.
|
|
486
|
+
const resolvedSeed = await resolveDeferredHandleValues(handleSeed);
|
|
487
487
|
eventController.setHandleData(
|
|
488
|
-
|
|
488
|
+
resolvedSeed,
|
|
489
489
|
initialSegments.map((s) => s.id),
|
|
490
490
|
);
|
|
491
491
|
|
|
492
|
-
|
|
493
|
-
// re-render. No server fetch — only routes passed to renderRoute exist. The
|
|
494
|
-
// store update is flushed inside act() so React commits before callers
|
|
495
|
-
// assert, mirroring how a real navigation lands a single payload swap.
|
|
492
|
+
let warnedNavLifecycle = false;
|
|
496
493
|
const navigate = async (target: string): Promise<void> => {
|
|
494
|
+
// renderRoute commits navigations synchronously (no server fetch, no Flight
|
|
495
|
+
// stream), so it never drives the navigation lifecycle. The transition state
|
|
496
|
+
// useNavigation()/useLinkStatus()/useAction() read stays "idle" — asserting a
|
|
497
|
+
// pending/loading/submitting state here proves nothing. Warn once (per render)
|
|
498
|
+
// under the test runner so that false-confidence trap is loud, not silent.
|
|
499
|
+
if (isUnderTestRunner() && !warnedNavLifecycle) {
|
|
500
|
+
warnedNavLifecycle = true;
|
|
501
|
+
console.warn(
|
|
502
|
+
"renderRoute: navigate()/useRouter().push commit synchronously and do " +
|
|
503
|
+
"NOT drive the navigation lifecycle. useNavigation().state, " +
|
|
504
|
+
'useLinkStatus().pending, and useAction().state stay "idle" here. ' +
|
|
505
|
+
"Assert params/pathname/content after navigate(); use renderServerTree " +
|
|
506
|
+
"or e2e to assert pending/loading/submitting transition states.",
|
|
507
|
+
);
|
|
508
|
+
}
|
|
497
509
|
const nextUrl = new URL(target, TEST_ORIGIN);
|
|
498
510
|
const match = resolve(nextUrl.pathname);
|
|
499
511
|
const segments = buildSegments(routes, match.params, loaderData, mount);
|
|
@@ -515,7 +527,6 @@ export async function renderRoute(
|
|
|
515
527
|
registerLinkInterception: () => () => {},
|
|
516
528
|
getVersion: () => undefined,
|
|
517
529
|
updateVersion: () => {},
|
|
518
|
-
updateAppShell: () => {},
|
|
519
530
|
};
|
|
520
531
|
|
|
521
532
|
const initialMetadata = makeMetadata(
|
|
@@ -525,18 +536,27 @@ export async function renderRoute(
|
|
|
525
536
|
);
|
|
526
537
|
const initialTree = await renderSegments(initialSegments);
|
|
527
538
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
539
|
+
// Wrap render in an awaited async act so a tree that suspends (async loaders,
|
|
540
|
+
// loading states, deferred handle entries that arrive as a Promise) settles its
|
|
541
|
+
// Suspense within act — otherwise React orphans the resolution ("a component
|
|
542
|
+
// suspended inside an act scope, but the act call was not awaited") and the
|
|
543
|
+
// resolved content never reaches the asserted DOM.
|
|
544
|
+
let result!: Awaited<ReturnType<typeof render>>;
|
|
545
|
+
await act(async () => {
|
|
546
|
+
result = render(
|
|
547
|
+
<NavigationProvider
|
|
548
|
+
store={store}
|
|
549
|
+
eventController={eventController}
|
|
550
|
+
initialPayload={{ root: initialTree, metadata: initialMetadata }}
|
|
551
|
+
bridge={bridge}
|
|
552
|
+
basename={normalizeBasename(options.basename)}
|
|
553
|
+
themeConfig={
|
|
554
|
+
options.theme === undefined ? null : resolveThemeConfig(options.theme)
|
|
555
|
+
}
|
|
556
|
+
nonce={options.nonce}
|
|
557
|
+
/>,
|
|
558
|
+
);
|
|
559
|
+
});
|
|
540
560
|
|
|
541
561
|
const router: TestRouterHandle = {
|
|
542
562
|
navigate,
|
|
@@ -549,7 +569,6 @@ export async function renderRoute(
|
|
|
549
569
|
return Object.assign(result, { router });
|
|
550
570
|
}
|
|
551
571
|
|
|
552
|
-
/** Minimal RscMetadata for client-side re-renders (no server-only fields). */
|
|
553
572
|
function makeMetadata(
|
|
554
573
|
pathname: string,
|
|
555
574
|
segments: ResolvedSegment[],
|