@rangojs/router 0.0.0-experimental.b9cb8739 → 0.0.0-experimental.bd6e11bc
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/README.md +196 -43
- package/dist/bin/rango.js +277 -99
- package/dist/testing/vitest.js +48 -0
- package/dist/vite/index.js +2779 -1064
- package/dist/vite/index.js.bak +5448 -0
- package/dist/vite/plugins/cloudflare-protocol-loader-hook.mjs +76 -0
- package/package.json +57 -11
- package/skills/breadcrumbs/SKILL.md +3 -1
- package/skills/bundle-analysis/SKILL.md +159 -0
- package/skills/cache-guide/SKILL.md +243 -21
- package/skills/caching/SKILL.md +155 -6
- package/skills/composability/SKILL.md +27 -2
- package/skills/document-cache/SKILL.md +78 -55
- package/skills/handler-use/SKILL.md +364 -0
- package/skills/hooks/SKILL.md +229 -20
- package/skills/host-router/SKILL.md +45 -20
- package/skills/i18n/SKILL.md +276 -0
- package/skills/intercept/SKILL.md +46 -4
- package/skills/layout/SKILL.md +28 -7
- package/skills/links/SKILL.md +249 -17
- package/skills/loader/SKILL.md +273 -53
- package/skills/middleware/SKILL.md +49 -12
- package/skills/migrate-nextjs/SKILL.md +562 -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 +197 -6
- package/skills/prerender/SKILL.md +123 -100
- package/skills/rango/SKILL.md +242 -22
- package/skills/react-compiler/SKILL.md +168 -0
- package/skills/response-routes/SKILL.md +66 -9
- package/skills/route/SKILL.md +88 -4
- package/skills/router-setup/SKILL.md +90 -5
- package/skills/server-actions/SKILL.md +751 -0
- package/skills/streams-and-websockets/SKILL.md +283 -0
- package/skills/testing/SKILL.md +716 -0
- package/skills/typesafety/SKILL.md +329 -27
- package/skills/use-cache/SKILL.md +34 -5
- package/skills/view-transitions/SKILL.md +294 -0
- package/src/__augment-tests__/augment.ts +81 -0
- package/src/__augment-tests__/augmented.check.ts +117 -0
- package/src/__internal.ts +1 -1
- package/src/browser/action-coordinator.ts +53 -36
- package/src/browser/app-shell.ts +52 -0
- package/src/browser/app-version.ts +14 -0
- package/src/browser/event-controller.ts +91 -70
- package/src/browser/history-state.ts +21 -0
- package/src/browser/index.ts +3 -3
- package/src/browser/navigation-bridge.ts +102 -16
- package/src/browser/navigation-client.ts +164 -59
- package/src/browser/navigation-store.ts +75 -17
- package/src/browser/navigation-transaction.ts +21 -37
- package/src/browser/partial-update.ts +139 -38
- package/src/browser/prefetch/cache.ts +175 -15
- package/src/browser/prefetch/fetch.ts +180 -33
- package/src/browser/prefetch/queue.ts +123 -20
- package/src/browser/prefetch/resource-ready.ts +77 -0
- package/src/browser/rango-state.ts +53 -13
- package/src/browser/react/Link.tsx +81 -9
- package/src/browser/react/NavigationProvider.tsx +110 -33
- package/src/browser/react/context.ts +7 -2
- package/src/browser/react/filter-segment-order.ts +51 -7
- package/src/browser/react/index.ts +3 -0
- package/src/browser/react/location-state-shared.ts +175 -4
- package/src/browser/react/location-state.ts +39 -13
- package/src/browser/react/use-handle.ts +23 -64
- package/src/browser/react/use-navigation.ts +22 -2
- package/src/browser/react/use-params.ts +20 -8
- package/src/browser/react/use-reverse.ts +106 -0
- package/src/browser/react/use-router.ts +43 -10
- package/src/browser/react/use-segments.ts +11 -8
- package/src/browser/response-adapter.ts +25 -0
- package/src/browser/rsc-router.tsx +191 -74
- package/src/browser/scroll-restoration.ts +41 -14
- package/src/browser/segment-reconciler.ts +36 -9
- package/src/browser/segment-structure-assert.ts +2 -2
- package/src/browser/server-action-bridge.ts +31 -36
- package/src/browser/types.ts +57 -5
- 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 +2 -0
- package/src/build/route-trie.ts +52 -25
- package/src/build/route-types/codegen.ts +4 -4
- package/src/build/route-types/include-resolution.ts +9 -2
- package/src/build/route-types/per-module-writer.ts +7 -4
- package/src/build/route-types/router-processing.ts +278 -88
- 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-runtime.ts +15 -11
- package/src/cache/cache-scope.ts +76 -49
- package/src/cache/cf/cf-cache-store.ts +501 -18
- package/src/cache/cf/index.ts +5 -1
- package/src/cache/document-cache.ts +17 -7
- package/src/cache/index.ts +1 -0
- package/src/cache/taint.ts +55 -0
- package/src/client.rsc.tsx +3 -0
- package/src/client.tsx +94 -238
- package/src/context-var.ts +72 -2
- package/src/debug.ts +2 -2
- package/src/decode-loader-results.ts +36 -0
- package/src/errors.ts +30 -1
- package/src/handle.ts +65 -12
- package/src/host/index.ts +2 -2
- package/src/host/router.ts +129 -57
- package/src/host/types.ts +31 -2
- package/src/host/utils.ts +1 -1
- package/src/href-client.ts +140 -20
- package/src/index.rsc.ts +12 -5
- package/src/index.ts +61 -11
- package/src/loader-store.ts +500 -0
- package/src/loader.rsc.ts +2 -5
- package/src/loader.ts +3 -10
- package/src/missing-id-error.ts +68 -0
- package/src/outlet-context.ts +1 -1
- package/src/prerender/store.ts +5 -4
- package/src/prerender.ts +141 -80
- package/src/response-utils.ts +37 -0
- package/src/reverse.ts +65 -15
- package/src/route-content-wrapper.tsx +6 -28
- package/src/route-definition/dsl-helpers.ts +435 -260
- package/src/route-definition/helper-factories.ts +29 -139
- package/src/route-definition/helpers-types.ts +110 -34
- package/src/route-definition/index.ts +3 -0
- package/src/route-definition/redirect.ts +11 -3
- package/src/route-definition/resolve-handler-use.ts +155 -0
- package/src/route-definition/use-item-types.ts +32 -0
- package/src/route-map-builder.ts +7 -1
- package/src/route-types.ts +37 -41
- package/src/router/basename.ts +14 -0
- package/src/router/content-negotiation.ts +113 -1
- package/src/router/error-handling.ts +1 -1
- package/src/router/find-match.ts +4 -2
- package/src/router/handler-context.ts +77 -38
- package/src/router/intercept-resolution.ts +15 -22
- package/src/router/lazy-includes.ts +12 -9
- package/src/router/loader-resolution.ts +174 -22
- package/src/router/logging.ts +5 -2
- package/src/router/manifest.ts +31 -16
- package/src/router/match-api.ts +128 -192
- package/src/router/match-handlers.ts +63 -20
- package/src/router/match-middleware/background-revalidation.ts +30 -2
- package/src/router/match-middleware/cache-lookup.ts +136 -106
- package/src/router/match-middleware/cache-store.ts +54 -10
- package/src/router/match-middleware/intercept-resolution.ts +9 -7
- package/src/router/match-middleware/segment-resolution.ts +61 -5
- package/src/router/match-result.ts +125 -10
- package/src/router/metrics.ts +7 -2
- package/src/router/middleware-types.ts +21 -34
- package/src/router/middleware.ts +103 -90
- package/src/router/navigation-snapshot.ts +182 -0
- package/src/router/pattern-matching.ts +101 -17
- package/src/router/prerender-match.ts +110 -10
- package/src/router/preview-match.ts +32 -102
- package/src/router/request-classification.ts +286 -0
- package/src/router/revalidation.ts +58 -2
- package/src/router/route-snapshot.ts +245 -0
- package/src/router/router-context.ts +6 -1
- package/src/router/router-interfaces.ts +77 -28
- package/src/router/router-options.ts +76 -11
- package/src/router/router-registry.ts +2 -5
- package/src/router/segment-resolution/fresh.ts +223 -24
- package/src/router/segment-resolution/helpers.ts +29 -24
- package/src/router/segment-resolution/loader-cache.ts +1 -0
- package/src/router/segment-resolution/revalidation.ts +466 -285
- package/src/router/segment-resolution/view-transition-default.ts +36 -0
- package/src/router/segment-wrappers.ts +2 -0
- package/src/router/substitute-pattern-params.ts +56 -0
- package/src/router/telemetry.ts +99 -0
- package/src/router/trie-matching.ts +18 -13
- package/src/router/types.ts +9 -0
- package/src/router/url-params.ts +49 -0
- package/src/router.ts +91 -23
- package/src/rsc/handler-context.ts +2 -2
- package/src/rsc/handler.ts +440 -381
- package/src/rsc/helpers.ts +91 -43
- package/src/rsc/index.ts +1 -1
- package/src/rsc/loader-fetch.ts +23 -3
- package/src/rsc/manifest-init.ts +5 -1
- package/src/rsc/origin-guard.ts +28 -10
- package/src/rsc/progressive-enhancement.ts +18 -2
- package/src/rsc/response-route-handler.ts +46 -53
- package/src/rsc/rsc-rendering.ts +41 -48
- package/src/rsc/runtime-warnings.ts +9 -10
- package/src/rsc/server-action.ts +25 -37
- package/src/rsc/ssr-setup.ts +18 -2
- package/src/rsc/types.ts +17 -3
- package/src/search-params.ts +4 -4
- package/src/segment-content-promise.ts +67 -0
- package/src/segment-loader-promise.ts +122 -0
- package/src/segment-system.tsx +219 -67
- package/src/serialize.ts +243 -0
- package/src/server/context.ts +277 -61
- package/src/server/cookie-store.ts +28 -4
- package/src/server/handle-store.ts +19 -0
- package/src/server/loader-registry.ts +9 -8
- package/src/server/request-context.ts +204 -60
- package/src/ssr/index.tsx +9 -1
- package/src/static-handler.ts +19 -7
- package/src/testing/cache-status.ts +166 -0
- package/src/testing/collect-handle.ts +63 -0
- package/src/testing/dispatch.ts +440 -0
- package/src/testing/dom.entry.ts +22 -0
- package/src/testing/e2e/fixture.ts +154 -0
- package/src/testing/e2e/index.ts +149 -0
- package/src/testing/e2e/matchers.ts +51 -0
- package/src/testing/e2e/page-helpers.ts +272 -0
- package/src/testing/e2e/parity.ts +306 -0
- package/src/testing/e2e/server.ts +183 -0
- package/src/testing/flight-matchers.ts +104 -0
- package/src/testing/flight-runtime.d.ts +21 -0
- package/src/testing/flight.entry.ts +22 -0
- package/src/testing/flight.ts +182 -0
- package/src/testing/generated-routes.ts +223 -0
- package/src/testing/index.ts +106 -0
- package/src/testing/internal/context.ts +255 -0
- package/src/testing/render-route.tsx +565 -0
- package/src/testing/run-loader.ts +296 -0
- package/src/testing/run-middleware.ts +179 -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 +183 -0
- package/src/types/cache-types.ts +4 -4
- package/src/types/global-namespace.ts +39 -26
- package/src/types/handler-context.ts +194 -72
- package/src/types/index.ts +1 -0
- package/src/types/loader-types.ts +41 -15
- package/src/types/request-scope.ts +126 -0
- package/src/types/route-entry.ts +19 -1
- package/src/types/segments.ts +37 -1
- package/src/urls/include-helper.ts +34 -67
- package/src/urls/index.ts +0 -3
- package/src/urls/path-helper-types.ts +50 -9
- package/src/urls/path-helper.ts +63 -63
- package/src/urls/pattern-types.ts +48 -19
- package/src/urls/response-types.ts +25 -22
- package/src/urls/type-extraction.ts +26 -116
- package/src/urls/urls-function.ts +1 -5
- package/src/use-loader.tsx +487 -44
- package/src/vite/debug.ts +185 -0
- package/src/vite/discovery/bundle-postprocess.ts +34 -37
- package/src/vite/discovery/discover-routers.ts +105 -51
- 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 +188 -93
- 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 +46 -6
- package/src/vite/discovery/virtual-module-codegen.ts +13 -23
- package/src/vite/index.ts +6 -0
- package/src/vite/plugin-types.ts +111 -72
- package/src/vite/plugins/cjs-to-esm.ts +8 -7
- package/src/vite/plugins/client-ref-dedup.ts +16 -0
- package/src/vite/plugins/client-ref-hashing.ts +28 -5
- 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 +214 -0
- package/src/vite/plugins/expose-action-id.ts +55 -33
- package/src/vite/plugins/expose-id-utils.ts +24 -8
- package/src/vite/plugins/expose-ids/export-analysis.ts +100 -20
- package/src/vite/plugins/expose-ids/handler-transform.ts +12 -35
- package/src/vite/plugins/expose-ids/loader-transform.ts +3 -5
- package/src/vite/plugins/expose-ids/router-transform.ts +20 -3
- package/src/vite/plugins/expose-internal-ids.ts +544 -317
- package/src/vite/plugins/performance-tracks.ts +92 -0
- package/src/vite/plugins/refresh-cmd.ts +88 -26
- package/src/vite/plugins/use-cache-transform.ts +65 -50
- package/src/vite/plugins/version-injector.ts +39 -23
- package/src/vite/plugins/version-plugin.ts +72 -3
- package/src/vite/plugins/virtual-entries.ts +2 -2
- package/src/vite/rango.ts +265 -226
- package/src/vite/router-discovery.ts +920 -137
- package/src/vite/utils/ast-handler-extract.ts +15 -15
- package/src/vite/utils/banner.ts +4 -4
- package/src/vite/utils/bundle-analysis.ts +4 -2
- package/src/vite/utils/client-chunks.ts +190 -0
- package/src/vite/utils/forward-user-plugins.ts +193 -0
- package/src/vite/utils/manifest-utils.ts +21 -5
- package/src/vite/utils/package-resolution.ts +41 -1
- package/src/vite/utils/prerender-utils.ts +38 -5
- package/src/vite/utils/shared-utils.ts +109 -27
- package/src/browser/action-response-classifier.ts +0 -99
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared internals for the consumer testing primitives.
|
|
3
|
+
*
|
|
4
|
+
* Builds a real RequestContext via the same createRequestContext the RSC
|
|
5
|
+
* handler uses, with test-friendly defaults, so loaders and middleware run
|
|
6
|
+
* with production-fidelity context (cookies, headers, get/set, use, reverse)
|
|
7
|
+
* instead of a hand-rolled mock.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
createRequestContext,
|
|
12
|
+
runWithRequestContext,
|
|
13
|
+
type RequestContext,
|
|
14
|
+
} from "../../server/request-context.js";
|
|
15
|
+
import { resolveLocationStateEntries } from "../../browser/react/location-state-shared.js";
|
|
16
|
+
import { createReverseFunction } from "../../router/handler-context.js";
|
|
17
|
+
import { normalizeBasename } from "../../router/basename.js";
|
|
18
|
+
import { contextSet, type ContextVar } from "../../context-var.js";
|
|
19
|
+
import type { ThemeConfig } from "../../theme/types.js";
|
|
20
|
+
import { resolveThemeConfig } from "../../theme/constants.js";
|
|
21
|
+
import type { SegmentCacheStore } from "../../cache/types.js";
|
|
22
|
+
import type { CacheProfile } from "../../cache/profile-registry.js";
|
|
23
|
+
|
|
24
|
+
const DEFAULT_ORIGIN = "http://localhost/";
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Initializer for seeded context variables (as a prior middleware would have
|
|
28
|
+
* set). Either a plain object keyed by var name (the common, best-inferring
|
|
29
|
+
* form: `{ user: u }`) or a list of `[key, value]` tuples where the key may be a
|
|
30
|
+
* `createVar()` handle or a string (`[[userVar, u], ["flag", true]]`).
|
|
31
|
+
*/
|
|
32
|
+
export type VarsInit =
|
|
33
|
+
| Record<string, unknown>
|
|
34
|
+
| ReadonlyArray<readonly [ContextVar<unknown> | string, unknown]>;
|
|
35
|
+
|
|
36
|
+
/** Normalize a Request | string | undefined into a concrete Request. */
|
|
37
|
+
export function toRequest(
|
|
38
|
+
request: Request | string | undefined,
|
|
39
|
+
init?: RequestInit,
|
|
40
|
+
): Request {
|
|
41
|
+
if (request instanceof Request) return request;
|
|
42
|
+
if (typeof request === "string") {
|
|
43
|
+
return new Request(new URL(request, DEFAULT_ORIGIN), init);
|
|
44
|
+
}
|
|
45
|
+
return new Request(DEFAULT_ORIGIN, init);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Preload variables as if set by upstream middleware. Accepts entries keyed by
|
|
50
|
+
* either a ContextVar (from createVar) or a string, matching ctx.set().
|
|
51
|
+
*/
|
|
52
|
+
export function seedVariables(
|
|
53
|
+
variables: Record<string, unknown>,
|
|
54
|
+
vars?: VarsInit,
|
|
55
|
+
): Record<string, unknown> {
|
|
56
|
+
if (!vars) return variables;
|
|
57
|
+
// Array/iterable form -> use the tuples as-is; plain object -> its entries.
|
|
58
|
+
const entries: Iterable<readonly [ContextVar<unknown> | string, unknown]> =
|
|
59
|
+
Symbol.iterator in (vars as object)
|
|
60
|
+
? (vars as ReadonlyArray<
|
|
61
|
+
readonly [ContextVar<unknown> | string, unknown]
|
|
62
|
+
>)
|
|
63
|
+
: Object.entries(vars as Record<string, unknown>);
|
|
64
|
+
for (const [key, value] of entries) {
|
|
65
|
+
contextSet(variables, key as ContextVar<unknown>, value);
|
|
66
|
+
}
|
|
67
|
+
return variables;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export interface CreateTestContextOptions<TEnv> {
|
|
71
|
+
env?: TEnv;
|
|
72
|
+
request?: Request | string;
|
|
73
|
+
requestInit?: RequestInit;
|
|
74
|
+
/** Backing store for ctx.get()/ctx.set(); pre-seeded from `vars`. */
|
|
75
|
+
variables?: Record<string, unknown>;
|
|
76
|
+
/** Variables a prior middleware would have set (object or [key, value] list). */
|
|
77
|
+
vars?: VarsInit;
|
|
78
|
+
/** Route name -> pattern map enabling ctx.reverse() without global state. */
|
|
79
|
+
routeMap?: Record<string, string>;
|
|
80
|
+
routeName?: string;
|
|
81
|
+
params?: Record<string, string>;
|
|
82
|
+
/**
|
|
83
|
+
* Router basename for this request (what the RSC handler stores on the
|
|
84
|
+
* context). Drives redirect() prefixing. Normalized exactly like
|
|
85
|
+
* createRouter({ basename }) (leading slash forced, trailing stripped, bare
|
|
86
|
+
* "/" -> undefined) so passing the same value your router takes yields the
|
|
87
|
+
* same redirect Location. Defaults to undefined (no basename).
|
|
88
|
+
*/
|
|
89
|
+
basename?: string;
|
|
90
|
+
/**
|
|
91
|
+
* Cache store backing `use cache` functions invoked during the test, the
|
|
92
|
+
* same shape `createRouter({ cache })` resolves. Without it,
|
|
93
|
+
* registerCachedFunction bypasses (it checks for a store FIRST), so a cached
|
|
94
|
+
* function runs uncached and its taint/profile guards never fire. Wire one
|
|
95
|
+
* (e.g. `new MemorySegmentCacheStore()`) to exercise real cache behavior.
|
|
96
|
+
*/
|
|
97
|
+
cacheStore?: SegmentCacheStore;
|
|
98
|
+
/**
|
|
99
|
+
* Cache profiles in the `createRouter({ cacheProfiles })` shape. Required for
|
|
100
|
+
* a `use cache: "profileName"` function to resolve its profile (an unknown
|
|
101
|
+
* profile throws), once a `cacheStore` is wired.
|
|
102
|
+
*/
|
|
103
|
+
cacheProfiles?: Record<string, CacheProfile>;
|
|
104
|
+
/**
|
|
105
|
+
* Theme config in the same shape `createRouter({ theme })` takes (resolved
|
|
106
|
+
* internally). Without it `ctx.theme`/`ctx.setTheme` are inert (undefined),
|
|
107
|
+
* mirroring an app with no theme configured. Pass one (e.g. `true`, or
|
|
108
|
+
* `{ themes: [...] }`) to exercise a handler that reads them.
|
|
109
|
+
*/
|
|
110
|
+
theme?: ThemeConfig | true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface TestRequestContext<TEnv> {
|
|
114
|
+
ctx: RequestContext<TEnv>;
|
|
115
|
+
request: Request;
|
|
116
|
+
url: URL;
|
|
117
|
+
variables: Record<string, unknown>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Create a real RequestContext for unit-testing loaders/middleware.
|
|
122
|
+
*
|
|
123
|
+
* The returned `ctx` must be ENTERED before use — wrap your call in
|
|
124
|
+
* `runWithRequestContext(ctx, fn)` (re-exported from `@rangojs/router/testing`)
|
|
125
|
+
* so that cookie/header mutations and `getRequestContext()` resolve. For the
|
|
126
|
+
* common case prefer {@link runInRequestContext}, which builds AND enters the
|
|
127
|
+
* context in a single call.
|
|
128
|
+
*/
|
|
129
|
+
export function createTestRequestContext<TEnv>(
|
|
130
|
+
opts: CreateTestContextOptions<TEnv> = {},
|
|
131
|
+
): TestRequestContext<TEnv> {
|
|
132
|
+
const request = toRequest(opts.request, opts.requestInit);
|
|
133
|
+
const url = new URL(request.url);
|
|
134
|
+
const variables = seedVariables(opts.variables ?? {}, opts.vars);
|
|
135
|
+
const ctx = createRequestContext<TEnv>({
|
|
136
|
+
env: (opts.env ?? {}) as TEnv,
|
|
137
|
+
request,
|
|
138
|
+
url,
|
|
139
|
+
variables,
|
|
140
|
+
themeConfig:
|
|
141
|
+
opts.theme === undefined ? undefined : resolveThemeConfig(opts.theme),
|
|
142
|
+
cacheStore: opts.cacheStore,
|
|
143
|
+
cacheProfiles: opts.cacheProfiles,
|
|
144
|
+
});
|
|
145
|
+
if (opts.basename !== undefined)
|
|
146
|
+
ctx._basename = normalizeBasename(opts.basename);
|
|
147
|
+
if (opts.params) ctx.params = opts.params;
|
|
148
|
+
if (opts.routeMap) {
|
|
149
|
+
ctx._routeName = opts.routeName;
|
|
150
|
+
ctx.reverse = createReverseFunction(
|
|
151
|
+
opts.routeMap,
|
|
152
|
+
opts.routeName,
|
|
153
|
+
opts.params ?? {},
|
|
154
|
+
) as RequestContext<TEnv>["reverse"];
|
|
155
|
+
}
|
|
156
|
+
return { ctx, request, url, variables };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* What a run accumulated on the request context, surfaced as PUBLIC values so a
|
|
161
|
+
* test never has to cast through the `@internal` `ctx.res` / `ctx.cookies()` to
|
|
162
|
+
* assert what an action produced.
|
|
163
|
+
*/
|
|
164
|
+
export interface RunInRequestContextResult<T> {
|
|
165
|
+
/** The value `fn` returned (awaited if it returned a promise). */
|
|
166
|
+
result: T;
|
|
167
|
+
/**
|
|
168
|
+
* A Response carrying the status, headers, and Set-Cookie cookies the run set
|
|
169
|
+
* on the request context (via `cookies().set()`, `ctx.header()`, etc.).
|
|
170
|
+
* Assert Set-Cookie with `response.headers.getSetCookie()`. This is the
|
|
171
|
+
* accumulated side-channel, NOT a Response `fn` itself returned (that is
|
|
172
|
+
* `result`).
|
|
173
|
+
*/
|
|
174
|
+
response: Response;
|
|
175
|
+
/**
|
|
176
|
+
* The effective cookie view after the run: request cookies merged with
|
|
177
|
+
* anything the run set or deleted (last-write-wins), as `{ name: value }`.
|
|
178
|
+
*/
|
|
179
|
+
cookies: Record<string, string>;
|
|
180
|
+
/**
|
|
181
|
+
* Location state the run set via `ctx.setLocationState()` / `redirect({ state })`,
|
|
182
|
+
* resolved to the flat `{ key: value }` shape the client reads off
|
|
183
|
+
* `history.state` (empty object when none) — so a post-action flash ("Saved!")
|
|
184
|
+
* is assertable at the unit layer.
|
|
185
|
+
*/
|
|
186
|
+
locationState: Record<string, unknown>;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Snapshot the observable effects a run left on `ctx` (cookies + location
|
|
191
|
+
* state). Reads the fields directly off the ctx object, so it works both inside
|
|
192
|
+
* and outside the AsyncLocalStorage scope (no `getRequestContext()`).
|
|
193
|
+
*/
|
|
194
|
+
export function snapshotRunEffects<TEnv>(ctx: RequestContext<TEnv>): {
|
|
195
|
+
cookies: Record<string, string>;
|
|
196
|
+
locationState: Record<string, unknown>;
|
|
197
|
+
} {
|
|
198
|
+
return {
|
|
199
|
+
cookies: { ...ctx.cookies() },
|
|
200
|
+
locationState: resolveLocationStateEntries(ctx._locationState ?? []),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Build a seeded RequestContext (via {@link createTestRequestContext}) and run
|
|
206
|
+
* `fn` inside it, so code under test that calls `getRequestContext()`,
|
|
207
|
+
* `cookies()`, or reads/mutates request headers resolves exactly as in
|
|
208
|
+
* production.
|
|
209
|
+
*
|
|
210
|
+
* This is the entry point for the advanced cases the unit wrappers
|
|
211
|
+
* (`runLoader` / `runMiddleware`) do not model — most notably a server ACTION
|
|
212
|
+
* that authenticates off the request cookie or sets a session cookie / flash:
|
|
213
|
+
* an action has no loader context, so `runLoader` is the wrong shape, yet it
|
|
214
|
+
* still needs a real request context to read the cookie and resolve
|
|
215
|
+
* `getRequestContext()`.
|
|
216
|
+
*
|
|
217
|
+
* Returns `{ result, response, cookies, locationState }` so the action's OUTPUT
|
|
218
|
+
* (Set-Cookie, headers, flash) is assertable without casting through the
|
|
219
|
+
* `@internal` `ctx.res` / `ctx.cookies()`. `fn` may be async — the context
|
|
220
|
+
* stays active across its awaits (AsyncLocalStorage), and the snapshot is taken
|
|
221
|
+
* after it settles.
|
|
222
|
+
*
|
|
223
|
+
* @example
|
|
224
|
+
* ```ts
|
|
225
|
+
* const { result, cookies, response } = await runInRequestContext(
|
|
226
|
+
* () => loginAction(input),
|
|
227
|
+
* {
|
|
228
|
+
* env,
|
|
229
|
+
* request: new Request("https://app.test/", {
|
|
230
|
+
* headers: { Cookie: "sid=abc" },
|
|
231
|
+
* }),
|
|
232
|
+
* },
|
|
233
|
+
* );
|
|
234
|
+
* expect(cookies.session).toBe("new-token");
|
|
235
|
+
* expect(response.headers.getSetCookie()).toContainEqual(
|
|
236
|
+
* expect.stringContaining("session="),
|
|
237
|
+
* );
|
|
238
|
+
* ```
|
|
239
|
+
*/
|
|
240
|
+
export async function runInRequestContext<T, TEnv = unknown>(
|
|
241
|
+
fn: (ctx: RequestContext<TEnv>) => T | Promise<T>,
|
|
242
|
+
opts: CreateTestContextOptions<TEnv> = {},
|
|
243
|
+
): Promise<RunInRequestContextResult<T>> {
|
|
244
|
+
const { ctx } = createTestRequestContext<TEnv>(opts);
|
|
245
|
+
const result = (await runWithRequestContext(ctx, () => fn(ctx))) as T;
|
|
246
|
+
const { cookies, locationState } = snapshotRunEffects(ctx);
|
|
247
|
+
// Snapshot the accumulated response from the stub directly (status + headers,
|
|
248
|
+
// incl. Set-Cookie). The Response constructor copies the Headers, so this is
|
|
249
|
+
// an immutable snapshot independent of later ctx mutations.
|
|
250
|
+
const response = new Response(null, {
|
|
251
|
+
status: ctx.res.status,
|
|
252
|
+
headers: ctx.res.headers,
|
|
253
|
+
});
|
|
254
|
+
return { result, response, cookies, locationState };
|
|
255
|
+
}
|