@rangojs/router 0.0.0-experimental.31 → 0.0.0-experimental.3232cd17
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 +4 -0
- package/README.md +198 -44
- package/dist/bin/rango.js +287 -105
- package/dist/testing/vitest.js +82 -0
- package/dist/vite/index.js +3248 -1117
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +73 -21
- package/skills/api-client/SKILL.md +211 -0
- package/skills/breadcrumbs/SKILL.md +107 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +245 -21
- package/skills/caching/SKILL.md +302 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/css/SKILL.md +76 -0
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +270 -30
- package/skills/host-router/SKILL.md +82 -22
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +49 -5
- package/skills/layout/SKILL.md +35 -9
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +294 -30
- package/skills/middleware/SKILL.md +52 -13
- package/skills/migrate-nextjs/SKILL.md +584 -0
- package/skills/migrate-react-router/SKILL.md +769 -0
- package/skills/mime-routes/SKILL.md +27 -0
- package/skills/observability/SKILL.md +137 -0
- package/skills/parallel/SKILL.md +203 -7
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +250 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +122 -47
- package/skills/route/SKILL.md +97 -5
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +775 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/tailwind/SKILL.md +27 -3
- package/skills/testing/SKILL.md +129 -0
- package/skills/testing/bindings.md +89 -0
- package/skills/testing/cache-prerender.md +124 -0
- package/skills/testing/client-components.md +122 -0
- package/skills/testing/e2e-parity.md +125 -0
- package/skills/testing/flight.md +92 -0
- package/skills/testing/handles.md +129 -0
- package/skills/testing/loader.md +128 -0
- package/skills/testing/middleware.md +99 -0
- package/skills/testing/render-handler.md +121 -0
- package/skills/testing/response-routes.md +95 -0
- package/skills/testing/reverse-and-types.md +84 -0
- package/skills/testing/server-actions.md +107 -0
- package/skills/testing/server-tree.md +128 -0
- package/skills/testing/setup.md +120 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +36 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +116 -0
- package/src/__internal.ts +67 -40
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/action-fence.ts +47 -0
- package/src/browser/app-shell.ts +39 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/cookie-name.ts +140 -0
- package/src/browser/event-controller.ts +86 -147
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/invalidate-client-cache.ts +52 -0
- package/src/browser/link-interceptor.ts +4 -0
- package/src/browser/navigation-bridge.ts +148 -19
- package/src/browser/navigation-client.ts +187 -67
- package/src/browser/navigation-store-handle.ts +38 -0
- package/src/browser/navigation-store.ts +76 -67
- package/src/browser/navigation-transaction.ts +18 -66
- package/src/browser/partial-update.ts +123 -94
- package/src/browser/prefetch/cache.ts +214 -36
- package/src/browser/prefetch/fetch.ts +260 -38
- package/src/browser/prefetch/policy.ts +6 -0
- package/src/browser/prefetch/queue.ts +126 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +158 -76
- package/src/browser/react/Link.tsx +93 -11
- package/src/browser/react/NavigationProvider.tsx +115 -34
- package/src/browser/react/ScrollRestoration.tsx +10 -6
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +49 -7
- package/src/browser/react/index.ts +0 -48
- package/src/browser/react/location-state-shared.ts +166 -8
- package/src/browser/react/location-state.ts +39 -14
- package/src/browser/react/use-action.ts +6 -15
- package/src/browser/react/use-handle.ts +23 -69
- package/src/browser/react/use-link-status.ts +0 -4
- package/src/browser/react/use-navigation.ts +22 -5
- package/src/browser/react/use-params.ts +20 -10
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +46 -11
- package/src/browser/react/use-search-params.ts +0 -5
- package/src/browser/react/use-segments.ts +11 -21
- package/src/browser/response-adapter.ts +52 -1
- package/src/browser/rsc-router.tsx +215 -76
- package/src/browser/scroll-restoration.ts +46 -39
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +176 -50
- package/src/browser/types.ts +95 -11
- package/src/browser/validate-redirect-origin.ts +43 -16
- package/src/build/collect-fallback-refs.ts +107 -0
- package/src/build/generate-manifest.ts +65 -40
- package/src/build/generate-route-types.ts +5 -0
- package/src/build/index.ts +8 -2
- package/src/build/prefix-tree-utils.ts +123 -0
- package/src/build/route-trie.ts +137 -32
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/param-extraction.ts +6 -3
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -96
- package/src/build/route-types/scan-filter.ts +9 -2
- package/src/build/route-types/source-scan.ts +118 -0
- package/src/build/runtime-discovery.ts +9 -20
- package/src/cache/cache-error.ts +104 -0
- package/src/cache/cache-policy.ts +68 -28
- package/src/cache/cache-runtime.ts +149 -43
- package/src/cache/cache-scope.ts +148 -81
- package/src/cache/cache-tag.ts +98 -0
- package/src/cache/cf/cf-cache-store.ts +2550 -93
- package/src/cache/cf/index.ts +11 -17
- package/src/cache/document-cache.ts +78 -27
- package/src/cache/handle-snapshot.ts +63 -0
- package/src/cache/index.ts +23 -20
- package/src/cache/memory-segment-store.ts +136 -37
- package/src/cache/profile-registry.ts +6 -30
- package/src/cache/read-through-swr.ts +41 -11
- package/src/cache/segment-codec.ts +0 -16
- package/src/cache/tag-invalidation.ts +230 -0
- package/src/cache/taint.ts +55 -0
- package/src/cache/types.ts +33 -100
- package/src/cache/vercel/index.ts +11 -0
- package/src/cache/vercel/vercel-cache-store.ts +799 -0
- package/src/client.rsc.tsx +6 -21
- package/src/client.tsx +108 -290
- package/src/component-utils.ts +19 -0
- package/src/context-var.ts +84 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/defer.ts +196 -0
- package/src/deps/ssr.ts +0 -1
- package/src/errors.ts +30 -4
- package/src/handle.ts +70 -22
- package/src/handles/MetaTags.tsx +0 -14
- package/src/handles/breadcrumbs.ts +16 -5
- package/src/handles/meta.ts +0 -39
- package/src/host/cookie-handler.ts +0 -36
- package/src/host/errors.ts +0 -24
- package/src/host/index.ts +8 -2
- package/src/host/pattern-matcher.ts +7 -50
- package/src/host/router.ts +107 -99
- package/src/host/testing.ts +40 -27
- package/src/host/types.ts +37 -4
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +137 -22
- package/src/index.rsc.ts +52 -26
- package/src/index.ts +100 -38
- package/src/internal-debug.ts +2 -4
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +20 -13
- package/src/loader.ts +12 -11
- package/src/missing-id-error.ts +68 -0
- package/src/network-error-thrower.tsx +1 -6
- package/src/outlet-context.ts +1 -1
- package/src/outlet-provider.tsx +1 -5
- package/src/prerender/param-hash.ts +10 -11
- package/src/prerender/store.ts +37 -41
- package/src/prerender.ts +198 -82
- package/src/redirect-origin.ts +100 -0
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/root-error-boundary.tsx +1 -19
- package/src/route-content-wrapper.tsx +7 -72
- package/src/route-definition/dsl-helpers.ts +437 -274
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +113 -37
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +52 -10
- package/src/route-definition/resolve-handler-use.ts +161 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -17
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +108 -9
- package/src/router/error-handling.ts +13 -17
- package/src/router/find-match.ts +45 -22
- package/src/router/handler-context.ts +83 -41
- package/src/router/intercept-resolution.ts +25 -23
- package/src/router/lazy-includes.ts +19 -53
- package/src/router/loader-resolution.ts +213 -30
- package/src/router/logging.ts +5 -8
- package/src/router/manifest.ts +49 -45
- package/src/router/match-api.ts +121 -205
- package/src/router/match-context.ts +0 -22
- package/src/router/match-handlers.ts +58 -58
- package/src/router/match-middleware/background-revalidation.ts +27 -6
- package/src/router/match-middleware/cache-lookup.ts +205 -249
- package/src/router/match-middleware/cache-store.ts +45 -32
- package/src/router/match-middleware/intercept-resolution.ts +8 -28
- package/src/router/match-middleware/segment-resolution.ts +52 -18
- package/src/router/match-pipelines.ts +1 -42
- package/src/router/match-result.ts +104 -40
- package/src/router/metrics.ts +5 -34
- package/src/router/middleware-types.ts +13 -142
- package/src/router/middleware.ts +173 -143
- package/src/router/navigation-snapshot.ts +131 -0
- package/src/router/params-util.ts +23 -0
- package/src/router/pattern-matching.ts +109 -63
- package/src/router/prerender-match.ts +192 -54
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +276 -0
- package/src/router/revalidation.ts +63 -55
- package/src/router/route-snapshot.ts +244 -0
- package/src/router/router-context.ts +6 -28
- package/src/router/router-interfaces.ts +100 -35
- package/src/router/router-options.ts +91 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +242 -75
- package/src/router/segment-resolution/helpers.ts +64 -25
- package/src/router/segment-resolution/loader-cache.ts +41 -37
- package/src/router/segment-resolution/revalidation.ts +456 -372
- 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 +36 -0
- package/src/router/segment-resolution.ts +4 -1
- package/src/router/segment-wrappers.ts +2 -3
- package/src/router/state-cookie-name.ts +33 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry-otel.ts +0 -20
- package/src/router/telemetry.ts +96 -19
- package/src/router/timeout.ts +0 -20
- package/src/router/trie-matching.ts +91 -46
- package/src/router/types.ts +10 -63
- package/src/router/url-params.ts +44 -0
- package/src/router.ts +134 -43
- package/src/rsc/handler-context.ts +3 -2
- package/src/rsc/handler.ts +492 -383
- package/src/rsc/helpers.ts +162 -46
- package/src/rsc/index.ts +1 -1
- package/src/rsc/json-route-result.ts +38 -0
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +33 -42
- package/src/rsc/origin-guard.ts +39 -25
- package/src/rsc/progressive-enhancement.ts +30 -3
- package/src/rsc/redirect-guard.ts +99 -0
- package/src/rsc/response-error.ts +79 -12
- package/src/rsc/response-route-handler.ts +90 -63
- package/src/rsc/rsc-rendering.ts +56 -54
- package/src/rsc/runtime-warnings.ts +23 -10
- package/src/rsc/server-action.ts +74 -67
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +25 -6
- package/src/runtime-env.ts +18 -0
- package/src/search-params.ts +4 -20
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +134 -0
- package/src/segment-system.tsx +272 -129
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +309 -61
- package/src/server/cookie-store.ts +80 -5
- package/src/server/handle-store.ts +26 -24
- package/src/server/loader-registry.ts +10 -28
- package/src/server/request-context.ts +348 -128
- package/src/ssr/index.tsx +23 -15
- package/src/static-handler.ts +27 -18
- package/src/testing/cache-status.ts +162 -0
- package/src/testing/collect-handle.ts +40 -0
- package/src/testing/dispatch.ts +618 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +188 -0
- package/src/testing/e2e/index.ts +128 -0
- package/src/testing/e2e/matchers.ts +35 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +387 -0
- package/src/testing/e2e/server.ts +195 -0
- package/src/testing/flight-matchers.ts +97 -0
- package/src/testing/flight-normalize.ts +11 -0
- package/src/testing/flight-runtime.d.ts +57 -0
- package/src/testing/flight-tree.ts +682 -0
- package/src/testing/flight.entry.ts +52 -0
- package/src/testing/flight.ts +232 -0
- package/src/testing/generated-routes.ts +183 -0
- package/src/testing/index.ts +99 -0
- package/src/testing/internal/context.ts +348 -0
- 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 +330 -0
- package/src/testing/render-route.tsx +566 -0
- package/src/testing/run-loader.ts +378 -0
- package/src/testing/run-middleware.ts +205 -0
- package/src/testing/vitest-stubs/cloudflare-email.ts +9 -0
- package/src/testing/vitest-stubs/cloudflare-workers.ts +21 -0
- package/src/testing/vitest-stubs/plugin-rsc.ts +16 -0
- package/src/testing/vitest-stubs/version.ts +5 -0
- package/src/testing/vitest.ts +305 -0
- package/src/theme/ThemeProvider.tsx +0 -52
- package/src/theme/ThemeScript.tsx +0 -6
- package/src/theme/constants.ts +0 -12
- package/src/theme/index.ts +0 -7
- package/src/theme/theme-context.ts +1 -5
- package/src/theme/theme-script.ts +0 -14
- package/src/theme/use-theme.ts +0 -3
- package/src/types/boundaries.ts +0 -35
- package/src/types/cache-types.ts +17 -8
- package/src/types/error-types.ts +30 -90
- package/src/types/global-namespace.ts +54 -41
- package/src/types/handler-context.ts +233 -81
- package/src/types/index.ts +1 -10
- package/src/types/loader-types.ts +44 -15
- package/src/types/request-scope.ts +107 -0
- package/src/types/route-config.ts +6 -50
- package/src/types/route-entry.ts +19 -7
- package/src/types/segments.ts +37 -14
- package/src/urls/include-helper.ts +33 -70
- package/src/urls/index.ts +1 -11
- package/src/urls/path-helper-types.ts +58 -11
- package/src/urls/path-helper.ts +57 -111
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +58 -139
- package/src/urls/urls-function.ts +1 -18
- package/src/use-loader.tsx +346 -89
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +36 -38
- package/src/vite/discovery/discover-routers.ts +130 -85
- package/src/vite/discovery/discovery-errors.ts +194 -0
- package/src/vite/discovery/gate-state.ts +171 -0
- package/src/vite/discovery/prerender-collection.ts +192 -99
- package/src/vite/discovery/route-types-writer.ts +40 -84
- package/src/vite/discovery/self-gen-tracking.ts +27 -1
- package/src/vite/discovery/state.ts +51 -6
- package/src/vite/discovery/virtual-module-codegen.ts +14 -34
- package/src/vite/index.ts +8 -0
- package/src/vite/plugin-types.ts +187 -69
- package/src/vite/plugins/cjs-to-esm.ts +8 -18
- package/src/vite/plugins/client-ref-dedup.ts +16 -11
- package/src/vite/plugins/client-ref-hashing.ts +28 -15
- package/src/vite/plugins/cloudflare-protocol-loader-hook.d.mts +23 -0
- package/src/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/src/vite/plugins/cloudflare-protocol-stub.ts +194 -0
- package/src/vite/plugins/expose-action-id.ts +49 -98
- package/src/vite/plugins/expose-id-utils.ts +11 -50
- package/src/vite/plugins/expose-ids/export-analysis.ts +76 -34
- package/src/vite/plugins/expose-ids/handler-transform.ts +10 -48
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -20
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -16
- package/src/vite/plugins/expose-internal-ids.ts +554 -317
- package/src/vite/plugins/performance-tracks.ts +89 -0
- package/src/vite/plugins/refresh-cmd.ts +89 -27
- package/src/vite/plugins/use-cache-transform.ts +73 -83
- package/src/vite/plugins/vercel-output.ts +258 -0
- package/src/vite/plugins/version-injector.ts +21 -25
- package/src/vite/plugins/version-plugin.ts +41 -20
- package/src/vite/plugins/virtual-entries.ts +2 -17
- package/src/vite/rango.ts +257 -289
- package/src/vite/router-discovery.ts +930 -140
- package/src/vite/utils/ast-handler-extract.ts +15 -31
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +10 -15
- package/src/vite/utils/client-chunks.ts +184 -0
- package/src/vite/utils/forward-user-plugins.ts +171 -0
- package/src/vite/utils/manifest-utils.ts +4 -59
- package/src/vite/utils/package-resolution.ts +20 -52
- package/src/vite/utils/prerender-utils.ts +27 -29
- package/src/vite/utils/shared-utils.ts +92 -42
- package/src/browser/action-response-classifier.ts +0 -99
- 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/router/middleware-cookies.ts +0 -55
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# Testing a server action — runInRequestContext
|
|
2
|
+
|
|
3
|
+
**Layer:** unit (node) · **Import:** `@rangojs/router/testing` · **DSL it tests:** `"use server"` action (see `/server-actions`)
|
|
4
|
+
|
|
5
|
+
`runInRequestContext(fn, opts)` builds a real `RequestContext` (the same `createRequestContext` the RSC handler uses) AND enters it around `fn`, so an action that calls `getRequestContext()` / `cookies()` / `ctx.get(var)` runs with production fidelity. You SEED the request, env, and vars; the REAL machinery is cookie/header accumulation, location-state, and redirect/notFound throwing.
|
|
6
|
+
|
|
7
|
+
## API
|
|
8
|
+
|
|
9
|
+
### Options — `CreateTestContextOptions<TEnv>`
|
|
10
|
+
|
|
11
|
+
| Field | Type | Meaning |
|
|
12
|
+
| --------------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `env` | `TEnv` | Platform bindings the action reads (`ctx.env`). Default `{}`. Double them yourself (see `./bindings.md`). |
|
|
14
|
+
| `request` | `Request \| string` | The request to run under. A `string` becomes `new Request(url)`; pass a full `Request` to seed a `Cookie` header. Default origin `http://localhost/`. |
|
|
15
|
+
| `requestInit` | `RequestInit` | Init merged when `request` is a string (e.g. `{ method, headers, body }`). |
|
|
16
|
+
| `variables` | `Record<string, unknown>` | Raw backing store for `ctx.get()` / `ctx.set()`, pre-seeded from `vars`. |
|
|
17
|
+
| `vars` | `VarsInit` | Vars a prior middleware would have set (object or `[token, value]` list). |
|
|
18
|
+
| `routeMap` | `Record<string, string>` | Route name -> pattern map enabling `ctx.reverse()` without global state. |
|
|
19
|
+
| `routeName` | `string` | Current route name (drives `ctx.reverse()` self-references). |
|
|
20
|
+
| `params` | `Record<string, string>` | Route params on `ctx.params`. |
|
|
21
|
+
| `basename` | `string` | Router basename, normalized exactly like `createRouter({ basename })`; drives `redirect()` prefixing. Default `undefined`. |
|
|
22
|
+
| `cacheStore` | `SegmentCacheStore` | Backing store for `use cache` functions (same shape as `createRouter({ cache })`). Without it, cached functions run uncached and their guards never fire. |
|
|
23
|
+
| `cacheProfiles` | `Record<string, CacheProfile>` | Profiles for `use cache: "name"`, same shape as `createRouter({ cacheProfiles })`. An unknown profile throws. |
|
|
24
|
+
| `theme` | `ThemeConfig \| true` | Theme config (same shape as `createRouter({ theme })`). Without it `ctx.theme` / `ctx.setTheme` are inert. |
|
|
25
|
+
| `stateCookie` | `StateCookieSeed` (`{ prefix?, routerId?, version? }`) | Customize the rango state cookie an action calling `invalidateClientCache()` rotates. The name is ALWAYS seeded (default `rango-state_router_0`) so the rotation `Set-Cookie` fires like production; override `prefix`/`routerId` to match `createRouter({ stateCookiePrefix, id })`, or `version` (value is `{version}:{timestamp}`, default `"0"`). |
|
|
26
|
+
|
|
27
|
+
### Context — `RequestContext<TEnv>` (what your code receives)
|
|
28
|
+
|
|
29
|
+
`fn` receives `ctx`, the full entered `RequestContext`; the same object resolves via `getRequestContext()` inside `fn`. Notable fields:
|
|
30
|
+
|
|
31
|
+
| Field | Type | Meaning |
|
|
32
|
+
| ------------------------------ | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `env` | `TEnv` | The seeded platform bindings. |
|
|
34
|
+
| `request` | `Request` | The concrete request the run is bound to. |
|
|
35
|
+
| `cookies()` | `() => Record<string, string>` | @internal effective cookie view. To read or queue cookies inside the action, use the standalone `cookies()` from `@rangojs/router` (`cookies().get(name)` / `cookies().set(...)`), which returns a `CookieStore`. |
|
|
36
|
+
| `get(token)` / `set(token, v)` | accessor | Read/write request-scoped vars (seeded from `vars` / `variables`). |
|
|
37
|
+
| `params` | `Record<string, string>` | Seeded route params. |
|
|
38
|
+
| `reverse(name, params?)` | function | Build a URL from `routeMap` (when seeded). |
|
|
39
|
+
| `header(name, value)` | function | Queue a response header. |
|
|
40
|
+
| `setLocationState(...)` | function | Set the flash / location state the client reads. |
|
|
41
|
+
| `theme`/`setTheme` | — | Theme accessors, inert unless `theme` is seeded. |
|
|
42
|
+
|
|
43
|
+
### Returns — `RunInRequestContextResult<T>`
|
|
44
|
+
|
|
45
|
+
| Field | Type | Meaning |
|
|
46
|
+
| ----------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
47
|
+
| `result` | `T \| undefined` | `fn`'s awaited return, or `undefined` if it threw. |
|
|
48
|
+
| `thrown` | `unknown` | What `fn` threw (a redirect / `notFound` `Response` on the success path), or `undefined`. Captured, NOT re-thrown — assert on it. |
|
|
49
|
+
| `response` | `Response` | The merged `Response` (status + headers + Set-Cookie). On a thrown redirect, that redirect's `Location` merged with the accumulated cookies/headers. |
|
|
50
|
+
| `cookies` | `Record<string, string>` | Effective cookie view: request cookies + run mutations, last-write-wins. |
|
|
51
|
+
| `headers` | `Record<string, string>` | Response headers the run set (plus a thrown redirect's `Location`), EXCLUDING `set-cookie` (use `cookies`). Names lowercased. A `keepClientCache()` call shows here as `x-rango-keep-cache: "1"`. |
|
|
52
|
+
| `stateCookieName` | `string` | The resolved rango state cookie name this run seeded (default `rango-state_router_0`). Assert an `invalidateClientCache()` rotation against it without recomputing. |
|
|
53
|
+
| `locationState` | `Record<string, unknown>` | The flash set via `ctx.setLocationState()` / `redirect({ state })`, as the flat `{ key: value }` the client reads. |
|
|
54
|
+
|
|
55
|
+
Low-level variant: when you already hold a context from `createTestRequestContext(opts)`, call `runWithRequestContext(ctx, fn)` (re-exported from `@rangojs/router/testing`) to enter it directly. `runInRequestContext` is the one-call convenience over `createTestRequestContext` + `runWithRequestContext`.
|
|
56
|
+
|
|
57
|
+
## Recipe
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { it, expect } from "vitest";
|
|
61
|
+
import { runInRequestContext } from "@rangojs/router/testing";
|
|
62
|
+
import { loginAction } from "../src/actions/login"; // sets a session cookie + flash, then throw redirect("/app")
|
|
63
|
+
|
|
64
|
+
it("sets the session cookie + flash and redirects", async () => {
|
|
65
|
+
const { thrown, cookies, locationState } = await runInRequestContext(
|
|
66
|
+
() => loginAction(input),
|
|
67
|
+
{
|
|
68
|
+
env,
|
|
69
|
+
request: new Request("https://app.test/admin", {
|
|
70
|
+
headers: { Cookie: "sid=abc" },
|
|
71
|
+
}),
|
|
72
|
+
},
|
|
73
|
+
);
|
|
74
|
+
expect((thrown as Response).headers.get("Location")).toBe("/app"); // redirected
|
|
75
|
+
expect(cookies.session).toBeDefined(); // cookie set before the throw, no @internal cast
|
|
76
|
+
expect(locationState).toEqual({ flash: { text: "Welcome back" } });
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("asserts the client-cache directives an action issued", async () => {
|
|
80
|
+
// invalidateClientCache() rotates the state cookie -> a Set-Cookie on response.
|
|
81
|
+
const { response, stateCookieName } = await runInRequestContext(() =>
|
|
82
|
+
logoutAction(),
|
|
83
|
+
);
|
|
84
|
+
expect(
|
|
85
|
+
response.headers
|
|
86
|
+
.getSetCookie()
|
|
87
|
+
.some((c) => c.startsWith(stateCookieName + "=")),
|
|
88
|
+
).toBe(true);
|
|
89
|
+
|
|
90
|
+
// keepClientCache() sets the suppression directive header (no cookie).
|
|
91
|
+
const { headers } = await runInRequestContext(() => dismissBannerAction());
|
|
92
|
+
expect(headers["x-rango-keep-cache"]).toBe("1");
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Caveats
|
|
97
|
+
|
|
98
|
+
- The snapshot fires whether `fn` RETURNS or THROWS. A `throw redirect("/app")` on the success path is captured on `thrown` (NOT re-thrown), so no try/catch is needed; assert on `thrown` for a throwing action.
|
|
99
|
+
- There is no cookies / headers option. Seed a request cookie by passing a full `Request` with the `Cookie` header (as in the recipe).
|
|
100
|
+
- `runWithRequestContext(ctx, fn)` is the low-level entry when you already hold a context; `runInRequestContext` is the one-call convenience over `createTestRequestContext` + `runWithRequestContext`.
|
|
101
|
+
- Platform bindings are yours to double via `env` (see `./bindings.md`).
|
|
102
|
+
|
|
103
|
+
## See also
|
|
104
|
+
|
|
105
|
+
- `/server-actions` — the DSL this tests
|
|
106
|
+
- Siblings: `./render-handler.md`, `./middleware.md`, `./loader.md`, `./bindings.md`
|
|
107
|
+
- Long-form prose: [docs/testing.md](https://github.com/ivogt/vite-rsc/blob/main/packages/rangojs-router/docs/testing.md) — section "runInRequestContext — the handler / server-action test primitive"
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
# Inspecting the rendered tree — renderServerTree, findClientBoundaries, findElements
|
|
2
|
+
|
|
3
|
+
**Layer:** RSC unit (react-server project) · **Import:** `@rangojs/router/testing/flight` · **DSL it tests:** client islands across the boundary + server-rendered host content (see `/route`)
|
|
4
|
+
|
|
5
|
+
`renderServerTree` is the DEFAULT way to assert on a Flight render; `renderToFlightString` (see [`./flight.md`](./flight.md)) is the escape hatch for pinning the raw wire bytes. It serializes the real Flight (identical bytes to `renderToFlightString`) and then deserializes it back to an inspectable React element tree you traverse — that serialize/deserialize round-trip is REAL; what you SEED is the element you render plus the request context (`request`/`headers`/`params`/`vars`/`env`). The win over the wire string: a client boundary's props come back as real JS values (a `Date` is a `Date`, not the opaque `$D...` encoding) and you can confirm a `"use client"` component actually crossed the boundary (an `I` row) instead of being inlined. There is NO hydration and NO interaction — boundaries are inert placeholders carrying props.
|
|
6
|
+
|
|
7
|
+
## API
|
|
8
|
+
|
|
9
|
+
### Options — `RenderServerTreeOptions` (extends `RenderToFlightStringOptions`)
|
|
10
|
+
|
|
11
|
+
| Field | Type | Meaning |
|
|
12
|
+
| ------------------ | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `request` | `Request \| string` | The request the render runs under (absolute URL, path, or a `Request`). Defaults to `http://localhost/`. A server component reading `getRequestContext()` sees this url/cookies. A passed `Request`'s headers win; `headers` is then ignored. |
|
|
14
|
+
| `headers` | `HeadersInit` | Request headers (e.g. `Cookie`) visible to the server tree, when `request` is a string. |
|
|
15
|
+
| `env` | `unknown` | Env / bindings exposed as `ctx.env`. Defaults to `{}`. |
|
|
16
|
+
| `params` | `Record<string, string>` | Route params exposed via `ctx.params` and loader contexts. |
|
|
17
|
+
| `routeName` | `string` | Matched route name (drives `ctx.routeName` and scoped reverse). |
|
|
18
|
+
| `vars` | `VarsInit` | Context variables visible via `ctx.get(...)`, as a prior middleware would have set them. Object form (`{ user }`) or `[key, value]` tuples. |
|
|
19
|
+
| `clientComponents` | `Record<string, unknown>` | The `"use client"` components reachable from the tree, keyed by the boundary name to register each as a client reference (in place) so it serializes as an `I` row. Omit when `rangoUseClientTransform()` auto-discovers them, or for pure server-only trees. First-wins per worker; already-registered references are left untouched. |
|
|
20
|
+
|
|
21
|
+
### Context — what your code receives
|
|
22
|
+
|
|
23
|
+
A server component rendered here runs under a real request context: `getRequestContext()` resolves, `ctx.params`/`ctx.routeName`/`ctx.env` reflect the options, `ctx.get(MyVar)` reads a seeded `var`, and cookies come off the request. Same seeding as the handler-test primitives — you render an **element** you build (`<Page />`); to run a route **handler** `(ctx) => rsc` use `renderHandler` (see `./render-handler.md`).
|
|
24
|
+
|
|
25
|
+
### Returns — `RenderServerTreeResult`
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
renderServerTree(element, opts?): Promise<{ flight: string; tree: unknown }>
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
| Field | Type | Meaning |
|
|
32
|
+
| -------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
33
|
+
| `flight` | `string` | The raw Flight wire string (so `toMatchFlight` assertions still apply). |
|
|
34
|
+
| `tree` | `unknown` | The deserialized React element tree. Server elements are plain React elements; each client boundary is an inert placeholder whose `props` are the real deserialized JS values that crossed the boundary. |
|
|
35
|
+
|
|
36
|
+
#### `findClientBoundaries(tree, selector?) -> ClientBoundary[]`
|
|
37
|
+
|
|
38
|
+
Every client boundary in document order; always an array (no throw on zero/many — destructure `const [tag] = ...` and assert `.length` when the count matters; no match yields `[]`). `selector` is a STRING (match by export name) or a `BoundarySelector` object, criteria AND-ed.
|
|
39
|
+
|
|
40
|
+
| `BoundarySelector` | Type | Meaning |
|
|
41
|
+
| ------------------ | --------------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
42
|
+
| `name` | `string` | Match the boundary's export name (same as a bare string). |
|
|
43
|
+
| `testId` | `string` | Match `props["data-testid"]` exactly (a `data-testid` you passed AS A PROP to the island). |
|
|
44
|
+
| `props` | `Record<string, unknown>` | Subset deep-equal match (Date/Map/Set/array/nested-object aware); unlisted props ignored. |
|
|
45
|
+
| `where` | `(boundary: ClientBoundary) => boolean` | Arbitrary predicate. |
|
|
46
|
+
|
|
47
|
+
`ClientBoundary` = `{ id, name, props (excludes children), children, element }`.
|
|
48
|
+
|
|
49
|
+
#### `findElements(tree, selector?) -> FoundElement[]`
|
|
50
|
+
|
|
51
|
+
Every SERVER/HOST element a server component produced (`<article>`, `<h2>`), in document order; always an array. `selector` is a host TAG string (`"h2"`) or an `ElementSelector` object, criteria AND-ed.
|
|
52
|
+
|
|
53
|
+
| `ElementSelector` | Type | Meaning |
|
|
54
|
+
| ----------------- | ------------------------------------ | ---------------------------------------------------------------------------------- |
|
|
55
|
+
| `tag` | `string` | Match the host tag name (`"article"`, `"h2"`). |
|
|
56
|
+
| `testId` | `string` | Match `props["data-testid"]` exactly (on a host element). |
|
|
57
|
+
| `props` | `Record<string, unknown>` | Subset deep-equal match (Date/Map/Set/array/nested aware). |
|
|
58
|
+
| `text` | `string \| RegExp` | Match the element's text content (substring for a string, `.test()` for a RegExp). |
|
|
59
|
+
| `where` | `(element: FoundElement) => boolean` | Arbitrary predicate. |
|
|
60
|
+
|
|
61
|
+
`FoundElement` = `{ tag, props (excludes children), children, text, element }`.
|
|
62
|
+
|
|
63
|
+
#### `textContent(node) -> string`
|
|
64
|
+
|
|
65
|
+
Concatenates every string/number leaf of a node's subtree in document order — the clean way to assert rendered text, instead of `JSON.stringify(tree).toContain(...)`.
|
|
66
|
+
|
|
67
|
+
## Recipe
|
|
68
|
+
|
|
69
|
+
```tsx
|
|
70
|
+
import { it, expect } from "vitest";
|
|
71
|
+
import {
|
|
72
|
+
renderServerTree,
|
|
73
|
+
findClientBoundaries,
|
|
74
|
+
findElements,
|
|
75
|
+
textContent,
|
|
76
|
+
} from "@rangojs/router/testing/flight";
|
|
77
|
+
import { PriceTag } from "./PriceTag.js"; // a "use client" component (any filename)
|
|
78
|
+
|
|
79
|
+
async function ProductPanel({ amount, asOf }: { amount: number; asOf: Date }) {
|
|
80
|
+
await Promise.resolve();
|
|
81
|
+
return (
|
|
82
|
+
<article>
|
|
83
|
+
<h2>Wine</h2>
|
|
84
|
+
<PriceTag amount={amount} currency="USD" asOf={asOf} />
|
|
85
|
+
</article>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
it("client props survive the serialize -> deserialize round trip", async () => {
|
|
90
|
+
const { flight, tree } = await renderServerTree(
|
|
91
|
+
<ProductPanel amount={19.5} asOf={new Date("2026-01-02T00:00:00Z")} />,
|
|
92
|
+
// Omit clientComponents when rangoUseClientTransform() is wired (see ./setup.md);
|
|
93
|
+
// otherwise register islands explicitly:
|
|
94
|
+
{ clientComponents: { PriceTag } },
|
|
95
|
+
);
|
|
96
|
+
expect(flight).toMatchFlight("PriceTag"); // wire assertions still work
|
|
97
|
+
|
|
98
|
+
const [tag] = findClientBoundaries(tree, "PriceTag");
|
|
99
|
+
expect(tag.props.amount).toBe(19.5); // a real number
|
|
100
|
+
expect(tag.props.asOf).toBeInstanceOf(Date); // a real Date, not "$D..."
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("asserts the server-rendered host content", async () => {
|
|
104
|
+
const { tree } = await renderServerTree(
|
|
105
|
+
<ProductPanel amount={19.5} asOf={new Date("2026-01-02T00:00:00Z")} />,
|
|
106
|
+
{ clientComponents: { PriceTag } },
|
|
107
|
+
);
|
|
108
|
+
const [h2] = findElements(tree, "h2");
|
|
109
|
+
expect(h2.text).toBe("Wine");
|
|
110
|
+
expect(textContent(tree)).toContain("Wine"); // instead of JSON.stringify(tree)
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Caveats
|
|
115
|
+
|
|
116
|
+
- This renders an ELEMENT you build (`<Page />`). To test a route HANDLER (a `(ctx) => rsc` function registered via `path(...)`), use `renderHandler` (see [`./render-handler.md`](./render-handler.md)) — handlers have their own util. Do NOT wrap a handler in `createElement` and render it here: a handler is not a component, so React would invoke it with `props` as its argument instead of the real `HandlerContext`, and the seeded `params`/`vars` plus `ctx.use`/`ctx.reverse`/`ctx.get`/`cookies()` would all be absent.
|
|
117
|
+
- Island auto-discovery from the server tree's imports needs `rangoUseClientTransform()` in the rsc project (see `./setup.md`). Without it a plainly-imported island is just a function the serializer renders server-side — register islands explicitly via `{ clientComponents: { PriceTag } }`.
|
|
118
|
+
- Same alias requirement as `./flight.md`: a rendered component (or handler) that reads `getRequestContext()`/`cookies()` from the `@rangojs/router` barrel needs the `index.rsc.ts` alias (see `./setup.md`), or it hits the throwing out-of-react-server stub.
|
|
119
|
+
- A client boundary's props come back as REAL JS values after deserialization (a `Date` is a `Date`, not a `$D...` encoding) — but there is NO hydration and NO interaction; boundaries are inert placeholders carrying props.
|
|
120
|
+
- Server COMPONENTS do not survive Flight as identities (they are executed during serialization), so `findElements` matches the host elements they PRODUCED, not the component function. Client islands keep identity — use `findClientBoundaries` for those.
|
|
121
|
+
- `findClientBoundaries` finds islands (`I` rows); `findElements` finds host elements. A `testId` on an island matches with `findClientBoundaries`; a `testId` on a host element matches with `findElements`. Use `textContent(node)` in place of `JSON.stringify(tree).toContain`.
|
|
122
|
+
- A true interactive, clickable DOM `renderServer` is intentionally NOT shipped: in-process happy-dom hydration re-tests React more than your app and misses server/client divergence (the only hydration bug worth a dedicated test, which needs a real browser). Test interaction at e2e.
|
|
123
|
+
|
|
124
|
+
## See also
|
|
125
|
+
|
|
126
|
+
- `/route` — the DSL this tests
|
|
127
|
+
- Siblings: `./flight.md`, `./render-handler.md`, `./setup.md`
|
|
128
|
+
- Long-form prose: [docs/testing.md](https://github.com/ivogt/vite-rsc/blob/main/packages/rangojs-router/docs/testing.md) — section "renderServerTree — serialize then deserialize to an inspectable tree" (and the "findElements / textContent" subsection)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Testing setup — the two vitest projects
|
|
2
|
+
|
|
3
|
+
**Layer:** cross-cutting (vitest config) · **Import:** `@rangojs/router/testing/vitest`
|
|
4
|
+
|
|
5
|
+
Real machinery: Vite transpiles `@rangojs/router`'s shipped TS source and resolves the bare specifier to its react-server impls so your app's router / loaders / middleware import in a bare Vitest process. You SEED nothing here — this file only wires the two projects every other recipe builds on. The node/DOM project keeps React on its CLIENT build; the Flight project flips to the `react-server` condition.
|
|
6
|
+
|
|
7
|
+
## API
|
|
8
|
+
|
|
9
|
+
### Options — `RangoTestAliasOptions`
|
|
10
|
+
|
|
11
|
+
| Field | Type | Meaning |
|
|
12
|
+
| -------- | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
13
|
+
| `preset` | `"node" \| "cloudflare"` | Deployment preset, matching `rango({ preset })` in the Vite plugin. `"cloudflare"` additionally stubs the `cloudflare:workers` / `cloudflare:email` runtime virtuals a CF route tree imports. A string (not a boolean) so more presets can be added without an API change. Default `"node"`. |
|
|
14
|
+
|
|
15
|
+
### Functions
|
|
16
|
+
|
|
17
|
+
| Function | Returns | Use |
|
|
18
|
+
| --------------------------- | ----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| `rangoTestConfig(opts?)` | `{ alias, server: { deps: { inline } } }` | Recommended. Spread into the node/DOM project's `test` block. Bundles the resolve aliases AND `server.deps.inline`. |
|
|
20
|
+
| `rangoTestAliases(opts?)` | `TestAlias[]` (`{ find, replacement }[]`) | Lower-level. The bare `@rangojs/router` -> `index.rsc.ts` alias plus the `:version` / `@vitejs/plugin-rsc/rsc` stubs (and CF stubs under `preset:"cloudflare"`). Used in the rsc project's `resolve.alias`. |
|
|
21
|
+
| `rangoUseClientTransform()` | a Vite plugin (`{ name, transform }`) | Add to the rsc project `plugins`. Applies the `"use client"` transform so `renderServerTree` auto-discovers client islands from the server tree's imports. |
|
|
22
|
+
|
|
23
|
+
### Returns — `RangoTestConfig` (from `rangoTestConfig`)
|
|
24
|
+
|
|
25
|
+
```ts
|
|
26
|
+
interface RangoTestConfig {
|
|
27
|
+
alias: TestAlias[]; // -> test.alias
|
|
28
|
+
server: { deps: { inline: RegExp[] } }; // [/@rangojs[/\\]router/] -> test.server.deps.inline
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Recipe
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
// vitest.config.ts — the node + DOM project (keeps React on its CLIENT build)
|
|
36
|
+
import { defineConfig } from "vitest/config";
|
|
37
|
+
import { rangoTestConfig } from "@rangojs/router/testing/vitest";
|
|
38
|
+
|
|
39
|
+
export default defineConfig({
|
|
40
|
+
test: {
|
|
41
|
+
globals: true,
|
|
42
|
+
include: ["test/**/*.test.{ts,tsx}"],
|
|
43
|
+
environment: "node", // renderRoute tests add a `// @vitest-environment happy-dom` pragma
|
|
44
|
+
// `preset: "cloudflare"` also stubs cloudflare:workers / cloudflare:email (default "node").
|
|
45
|
+
...rangoTestConfig({ preset: "cloudflare" }),
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// vitest.rsc.config.ts — the Flight project (react-server condition)
|
|
52
|
+
import { defineConfig } from "vitest/config";
|
|
53
|
+
import {
|
|
54
|
+
rangoTestAliases,
|
|
55
|
+
rangoUseClientTransform,
|
|
56
|
+
} from "@rangojs/router/testing/vitest";
|
|
57
|
+
|
|
58
|
+
// Production React in this process AND any forked worker (forks inherit env).
|
|
59
|
+
process.env.NODE_ENV = "production";
|
|
60
|
+
|
|
61
|
+
export default defineConfig({
|
|
62
|
+
plugins: [rangoUseClientTransform()],
|
|
63
|
+
resolve: {
|
|
64
|
+
conditions: ["react-server"],
|
|
65
|
+
alias: rangoTestAliases({ preset: "cloudflare" }), // or { preset: "node" }
|
|
66
|
+
},
|
|
67
|
+
test: {
|
|
68
|
+
globals: true,
|
|
69
|
+
include: ["**/*.rsc-test.{ts,tsx}"],
|
|
70
|
+
pool: "forks",
|
|
71
|
+
execArgv: ["--conditions=react-server"], // or React throws "react-server condition must be enabled"
|
|
72
|
+
},
|
|
73
|
+
});
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
```ts
|
|
77
|
+
// example.test.ts — one test in the node/DOM project, importing real app code
|
|
78
|
+
import { describe, it, expect } from "vitest";
|
|
79
|
+
import { dispatch } from "@rangojs/router/testing";
|
|
80
|
+
import { createRouter } from "@rangojs/router";
|
|
81
|
+
import { apiPatterns } from "../src/api/urls"; // path.json(...) routes only, no Prerender()
|
|
82
|
+
|
|
83
|
+
const router = createRouter().routes(apiPatterns);
|
|
84
|
+
|
|
85
|
+
describe("api", () => {
|
|
86
|
+
it("serializes a JSON response route", async () => {
|
|
87
|
+
const res = await dispatch(router, { request: "/health" });
|
|
88
|
+
expect(res.status).toBe(200);
|
|
89
|
+
expect(await res.json()).toEqual({ status: "ok" });
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Scripts:
|
|
95
|
+
|
|
96
|
+
```jsonc
|
|
97
|
+
{
|
|
98
|
+
"scripts": {
|
|
99
|
+
"test:unit": "vitest run",
|
|
100
|
+
"test:unit:rsc": "vitest run --config vitest.rsc.config.ts",
|
|
101
|
+
},
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Caveats
|
|
106
|
+
|
|
107
|
+
- Node >= 23 requires `rangoTestConfig()`, not bare `rangoTestAliases()`. `@rangojs/router` is consumed as SOURCE (exports -> `./src/*.ts`), and Node >= 23 refuses to type-strip `.ts` under `node_modules` (`ERR_UNSUPPORTED_NODE_MODULES_TYPE_STRIPPING`). `rangoTestConfig` ships as compiled JS AND adds `server.deps.inline: [/@rangojs[/\\]router/]` so Vite (not Node) transpiles rango source. With bare `rangoTestAliases` you must wire `deps.inline` yourself.
|
|
108
|
+
- Two separate projects. The node/DOM project keeps React on its CLIENT build; the Flight project uses the `react-server` condition in a separate `vitest.rsc.config.ts`. The main project must NOT set `react-server` — it flips React to the no-hooks server build and breaks every `renderRoute` / client test.
|
|
109
|
+
- The rsc project needs BOTH `resolve.conditions: ["react-server"]` AND the bare `@rangojs/router` -> `index.rsc.ts` alias from `rangoTestAliases({ preset })`. `resolve.conditions` alone is not reliably applied to bare-package export resolution; without the alias a handler/component reading `getRequestContext()` / `cookies()` resolves the throwing out-of-react-server stub (symptom: `renderHandler` returns `tree: undefined`). `renderToFlightString` / `renderServerTree` now self-diagnose this exact misconfiguration — they reject with an actionable message naming `rangoTestAliases`, rather than surfacing the opaque stub error.
|
|
110
|
+
- `NODE_ENV` must be `"production"` in the rsc project. Dev `NODE_ENV` crashes the bare worker (jsxDEV owner-stack machinery uninitialized) and emits volatile debug rows that defeat stable Flight snapshots.
|
|
111
|
+
- The forked rsc worker (`pool: "forks"`) must force the condition via `execArgv: ["--conditions=react-server"]`, or React throws "the react-server condition must be enabled".
|
|
112
|
+
- The `@rangojs/router:version` and `@vitejs/plugin-rsc/rsc` virtuals must be stubbed; the preset does it. A bare router import without stubbing throws.
|
|
113
|
+
- The rango fragment goes under `test` (`test.alias` + `test.server.deps.inline`, both returned by `rangoTestConfig`), NOT under top-level `resolve`.
|
|
114
|
+
- Wire `rangoUseClientTransform()` into the rsc project `plugins` so islands auto-discover from the server tree imports (see `./server-tree.md`); without it, register islands explicitly with `clientComponents`.
|
|
115
|
+
|
|
116
|
+
## See also
|
|
117
|
+
|
|
118
|
+
- (cross-cutting)
|
|
119
|
+
- Siblings: `./flight.md`, `./server-tree.md`, `./render-handler.md`, `./response-routes.md`
|
|
120
|
+
- Long-form prose: [docs/testing.md](https://github.com/ivogt/vite-rsc/blob/main/packages/rangojs-router/docs/testing.md) — section "Setup" (and the subsections "Resolving @rangojs/router in a unit test — use the preset" and "Two vitest projects")
|