rwsdk 1.0.0-alpha.2 → 1.0.0-alpha.20-test.20250929144616
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/dist/lib/constants.mjs +1 -2
- package/dist/lib/e2e/browser.d.mts +10 -0
- package/dist/lib/e2e/browser.mjs +123 -0
- package/dist/lib/e2e/dev.d.mts +8 -0
- package/dist/lib/e2e/dev.mjs +242 -0
- package/dist/lib/e2e/environment.d.mts +10 -0
- package/dist/lib/e2e/environment.mjs +210 -0
- package/dist/lib/e2e/index.d.mts +8 -0
- package/dist/lib/e2e/index.mjs +8 -0
- package/dist/lib/e2e/poll.d.mts +8 -0
- package/dist/lib/e2e/poll.mjs +31 -0
- package/dist/lib/e2e/release.d.mts +56 -0
- package/dist/lib/e2e/release.mjs +559 -0
- package/dist/lib/e2e/retry.d.mts +4 -0
- package/dist/lib/e2e/retry.mjs +16 -0
- package/dist/lib/e2e/setup.d.mts +2 -0
- package/dist/lib/e2e/setup.mjs +1 -0
- package/dist/lib/e2e/tarball.d.mts +14 -0
- package/dist/lib/e2e/tarball.mjs +99 -0
- package/dist/lib/e2e/testHarness.d.mts +132 -0
- package/dist/lib/e2e/testHarness.mjs +436 -0
- package/dist/lib/e2e/types.d.mts +32 -0
- package/dist/lib/getShortName.mjs +6 -2
- package/dist/lib/getShortName.test.d.mts +1 -0
- package/dist/lib/getShortName.test.mjs +25 -0
- package/dist/lib/getSrcPaths.js +2 -2
- package/dist/lib/hasPkgScript.d.mts +4 -1
- package/dist/lib/hasPkgScript.mjs +9 -6
- package/dist/lib/hasPkgScript.test.d.mts +1 -0
- package/dist/lib/hasPkgScript.test.mjs +33 -0
- package/dist/lib/jsonUtils.mjs +3 -0
- package/dist/lib/jsonUtils.test.d.mts +1 -0
- package/dist/lib/jsonUtils.test.mjs +90 -0
- package/dist/lib/normalizeModulePath.d.mts +5 -0
- package/dist/lib/normalizeModulePath.mjs +1 -1
- package/dist/lib/normalizeModulePath.test.d.mts +1 -0
- package/dist/lib/{normalizeModulePath.test.js → normalizeModulePath.test.mjs} +21 -2
- package/dist/lib/setupEnvFiles.mjs +2 -2
- package/dist/lib/smokeTests/artifacts.mjs +2 -2
- package/dist/lib/smokeTests/browser.d.mts +1 -1
- package/dist/lib/smokeTests/browser.mjs +8 -100
- package/dist/lib/smokeTests/cleanup.mjs +6 -9
- package/dist/lib/smokeTests/codeUpdates.mjs +5 -5
- package/dist/lib/smokeTests/development.mjs +3 -224
- package/dist/lib/smokeTests/environment.d.mts +3 -11
- package/dist/lib/smokeTests/environment.mjs +17 -151
- package/dist/lib/smokeTests/release.d.mts +2 -49
- package/dist/lib/smokeTests/release.mjs +4 -504
- package/dist/lib/smokeTests/reporting.mjs +2 -2
- package/dist/lib/smokeTests/runSmokeTests.mjs +4 -4
- package/dist/lib/smokeTests/utils.mjs +3 -3
- package/dist/lib/testUtils/stubEnvVars.mjs +1 -1
- package/dist/llms/rules/middleware.d.ts +1 -1
- package/dist/llms/rules/middleware.js +4 -4
- package/dist/runtime/client/client.d.ts +2 -2
- package/dist/runtime/client/client.js +2 -2
- package/dist/runtime/client/navigation.test.js +1 -1
- package/dist/runtime/client/types.d.ts +1 -1
- package/dist/runtime/entries/client.d.ts +2 -2
- package/dist/runtime/entries/client.js +2 -2
- package/dist/runtime/entries/router.d.ts +1 -1
- package/dist/runtime/entries/router.js +1 -1
- package/dist/runtime/entries/worker.d.ts +5 -6
- package/dist/runtime/entries/worker.js +5 -6
- package/dist/runtime/imports/worker.js +1 -1
- package/dist/runtime/lib/auth/session.d.ts +2 -2
- package/dist/runtime/lib/auth/session.js +5 -5
- package/dist/runtime/lib/db/DOWorkerDialect.d.ts +1 -1
- package/dist/runtime/lib/db/DOWorkerDialect.js +1 -1
- package/dist/runtime/lib/db/SqliteDurableObject.js +2 -2
- package/dist/runtime/lib/db/index.d.ts +2 -2
- package/dist/runtime/lib/db/index.js +2 -2
- package/dist/runtime/lib/db/migrations.d.ts +1 -1
- package/dist/runtime/lib/db/typeInference/builders/alterTable.d.ts +3 -3
- package/dist/runtime/lib/db/typeInference/builders/columnDefinition.d.ts +1 -1
- package/dist/runtime/lib/db/typeInference/builders/createTable.d.ts +2 -2
- package/dist/runtime/lib/db/typeInference/builders/createView.d.ts +1 -1
- package/dist/runtime/lib/db/typeInference/builders/dropTable.d.ts +1 -1
- package/dist/runtime/lib/db/typeInference/builders/dropView.d.ts +1 -1
- package/dist/runtime/lib/db/typeInference/builders/schema.d.ts +3 -3
- package/dist/runtime/lib/db/typeInference/database.d.ts +2 -2
- package/dist/runtime/lib/memoizeOnId.test.d.ts +1 -0
- package/dist/runtime/lib/memoizeOnId.test.js +49 -0
- package/dist/runtime/lib/realtime/client.js +2 -2
- package/dist/runtime/lib/realtime/durableObject.js +1 -1
- package/dist/runtime/lib/realtime/protocol.test.d.ts +1 -0
- package/dist/runtime/lib/realtime/protocol.test.js +107 -0
- package/dist/runtime/lib/realtime/shared.test.d.ts +1 -0
- package/dist/runtime/lib/realtime/shared.test.js +18 -0
- package/dist/runtime/lib/realtime/validateUpgradeRequest.test.d.ts +1 -0
- package/dist/runtime/lib/realtime/validateUpgradeRequest.test.js +66 -0
- package/dist/runtime/lib/realtime/worker.d.ts +1 -1
- package/dist/runtime/lib/realtime/worker.js +2 -2
- package/dist/runtime/lib/router.d.ts +1 -1
- package/dist/runtime/lib/router.js +40 -22
- package/dist/runtime/lib/router.test.js +591 -3
- package/dist/runtime/lib/rwContext.d.ts +22 -0
- package/dist/runtime/lib/rwContext.js +1 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.d.ts +18 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.js +143 -0
- package/dist/runtime/lib/turnstile/useTurnstile.js +1 -1
- package/dist/runtime/lib/turnstile/verifyTurnstileToken.d.ts +2 -1
- package/dist/runtime/lib/turnstile/verifyTurnstileToken.js +6 -6
- package/dist/runtime/lib/turnstile/verifyTurnstileToken.test.d.ts +1 -0
- package/dist/runtime/lib/turnstile/verifyTurnstileToken.test.js +49 -0
- package/dist/runtime/register/worker.d.ts +1 -1
- package/dist/runtime/register/worker.js +34 -22
- package/dist/runtime/render/assembleDocument.d.ts +6 -0
- package/dist/runtime/render/assembleDocument.js +22 -0
- package/dist/runtime/render/createThenableFromReadableStream.d.ts +1 -0
- package/dist/runtime/render/createThenableFromReadableStream.js +9 -0
- package/dist/runtime/render/normalizeActionResult.d.ts +1 -0
- package/dist/runtime/render/normalizeActionResult.js +43 -0
- package/dist/runtime/render/preloads.d.ts +3 -3
- package/dist/runtime/render/preloads.js +2 -3
- package/dist/runtime/render/{renderRscThenableToHtmlStream.d.ts → renderDocumentHtmlStream.d.ts} +3 -3
- package/dist/runtime/render/renderDocumentHtmlStream.js +39 -0
- package/dist/runtime/render/renderHtmlStream.d.ts +7 -0
- package/dist/runtime/render/renderHtmlStream.js +31 -0
- package/dist/runtime/render/renderToRscStream.d.ts +5 -3
- package/dist/runtime/render/renderToRscStream.js +12 -41
- package/dist/runtime/render/renderToStream.d.ts +3 -2
- package/dist/runtime/render/renderToStream.js +17 -10
- package/dist/runtime/render/stylesheets.d.ts +2 -2
- package/dist/runtime/render/stylesheets.js +2 -3
- package/dist/runtime/requestInfo/types.d.ts +0 -2
- package/dist/runtime/requestInfo/worker.d.ts +1 -1
- package/dist/runtime/requestInfo/worker.js +1 -9
- package/dist/runtime/script.js +1 -1
- package/dist/runtime/ssrBridge.d.ts +3 -2
- package/dist/runtime/ssrBridge.js +3 -2
- package/dist/runtime/worker.d.ts +2 -1
- package/dist/runtime/worker.js +13 -16
- package/dist/scripts/addon.d.mts +1 -0
- package/dist/scripts/addon.mjs +75 -0
- package/dist/scripts/debug-sync.mjs +106 -137
- package/dist/scripts/ensure-deploy-env.mjs +6 -6
- package/dist/scripts/migrate-new.mjs +3 -4
- package/dist/scripts/smoke-test.mjs +2 -2
- package/dist/scripts/worker-run.mjs +7 -9
- package/dist/vite/buildApp.d.mts +2 -1
- package/dist/vite/buildApp.mjs +10 -6
- package/dist/vite/checkIsUsingPrisma.d.mts +4 -0
- package/dist/vite/checkIsUsingPrisma.mjs +2 -2
- package/dist/vite/checkIsUsingPrisma.test.d.mts +1 -0
- package/dist/vite/checkIsUsingPrisma.test.mjs +30 -0
- package/dist/vite/configPlugin.mjs +55 -15
- package/dist/vite/createDirectiveLookupPlugin.d.mts +9 -0
- package/dist/vite/createDirectiveLookupPlugin.mjs +34 -30
- package/dist/vite/createDirectiveLookupPlugin.test.d.mts +1 -0
- package/dist/vite/createDirectiveLookupPlugin.test.mjs +40 -0
- package/dist/vite/createViteAwareResolver.d.mts +1 -2
- package/dist/vite/createViteAwareResolver.mjs +1 -1
- package/dist/vite/directiveModulesDevPlugin.d.mts +4 -1
- package/dist/vite/directiveModulesDevPlugin.mjs +9 -8
- package/dist/vite/directiveModulesDevPlugin.test.d.mts +1 -0
- package/dist/vite/directiveModulesDevPlugin.test.mjs +59 -0
- package/dist/vite/directivesPlugin.d.mts +1 -0
- package/dist/vite/directivesPlugin.mjs +4 -4
- package/dist/vite/directivesPlugin.test.d.mts +1 -0
- package/dist/vite/directivesPlugin.test.mjs +24 -0
- package/dist/vite/ensureAliasArray.test.d.mts +1 -0
- package/dist/vite/ensureAliasArray.test.mjs +71 -0
- package/dist/vite/findSpecifiers.mjs +3 -2
- package/dist/vite/findSpecifiers.test.d.mts +1 -0
- package/dist/vite/findSpecifiers.test.mjs +202 -0
- package/dist/vite/findSsrSpecifiers.mjs +1 -1
- package/dist/vite/findSsrSpecifiers.test.d.mts +1 -0
- package/dist/vite/findSsrSpecifiers.test.mjs +99 -0
- package/dist/vite/getViteEsbuild.mjs +1 -1
- package/dist/vite/hasDirective.d.mts +6 -3
- package/dist/vite/hasDirective.mjs +43 -27
- package/dist/vite/hasDirective.test.d.mts +1 -0
- package/dist/vite/hasDirective.test.mjs +107 -0
- package/dist/vite/index.d.mts +1 -1
- package/dist/vite/invalidateCacheIfPrismaClientChanged.mjs +2 -2
- package/dist/vite/isJsFile.test.d.mts +1 -0
- package/dist/vite/isJsFile.test.mjs +38 -0
- package/dist/vite/{reactConditionsResolverPlugin.d.mts → knownDepsResolverPlugin.d.mts} +3 -3
- package/dist/vite/{reactConditionsResolverPlugin.mjs → knownDepsResolverPlugin.mjs} +29 -24
- package/dist/vite/linkerPlugin.d.mts +8 -0
- package/dist/vite/linkerPlugin.mjs +32 -24
- package/dist/vite/linkerPlugin.test.d.mts +1 -0
- package/dist/vite/linkerPlugin.test.mjs +41 -0
- package/dist/vite/miniflareHMRPlugin.d.mts +5 -0
- package/dist/vite/miniflareHMRPlugin.mjs +7 -7
- package/dist/vite/miniflareHMRPlugin.test.d.mts +1 -0
- package/dist/vite/miniflareHMRPlugin.test.mjs +42 -0
- package/dist/vite/prismaPlugin.mjs +1 -1
- package/dist/vite/redwoodPlugin.d.mts +9 -0
- package/dist/vite/redwoodPlugin.mjs +44 -20
- package/dist/vite/redwoodPlugin.test.d.mts +1 -0
- package/dist/vite/redwoodPlugin.test.mjs +34 -0
- package/dist/vite/resolveForcedPaths.d.mts +4 -0
- package/dist/vite/resolveForcedPaths.mjs +9 -0
- package/dist/vite/runDirectivesScan.d.mts +22 -1
- package/dist/vite/runDirectivesScan.mjs +109 -61
- package/dist/vite/runDirectivesScan.test.d.mts +1 -0
- package/dist/vite/runDirectivesScan.test.mjs +73 -0
- package/dist/vite/ssrBridgePlugin.mjs +10 -3
- package/dist/vite/transformClientComponents.mjs +8 -6
- package/dist/vite/transformClientComponents.test.mjs +117 -59
- package/dist/vite/transformJsxScriptTagsPlugin.mjs +1 -1
- package/dist/vite/transformJsxScriptTagsPlugin.test.mjs +2 -2
- package/dist/vite/transformServerFunctions.d.mts +1 -1
- package/dist/vite/transformServerFunctions.mjs +5 -5
- package/dist/vite/transformServerFunctions.test.mjs +3 -3
- package/package.json +61 -47
- package/dist/runtime/imports/resolveSSRValue.d.ts +0 -1
- package/dist/runtime/imports/resolveSSRValue.js +0 -8
- package/dist/runtime/render/renderRscThenableToHtmlStream.js +0 -54
- package/dist/runtime/render/transformRscToHtmlStream.d.ts +0 -8
- package/dist/runtime/render/transformRscToHtmlStream.js +0 -19
- /package/dist/lib/{normalizeModulePath.test.d.ts → e2e/types.mjs} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import { defineRoutes, layout, matchPath, prefix, render, route, } from "./router";
|
|
3
4
|
describe("matchPath", () => {
|
|
4
5
|
// Test case 1: Static paths
|
|
5
6
|
it("should match static paths", () => {
|
|
@@ -22,7 +23,7 @@ describe("matchPath", () => {
|
|
|
22
23
|
expect(matchPath("/files/*/", "/files/document.pdf/")).toEqual({
|
|
23
24
|
$0: "document.pdf",
|
|
24
25
|
});
|
|
25
|
-
expect(matchPath("/data
|
|
26
|
+
expect(matchPath("/data/*/content/", "/data/archive/content/")).toEqual({
|
|
26
27
|
$0: "archive",
|
|
27
28
|
});
|
|
28
29
|
expect(matchPath("/assets/*/*/", "/assets/images/pic.png/")).toEqual({
|
|
@@ -56,3 +57,590 @@ describe("matchPath", () => {
|
|
|
56
57
|
expect(() => matchPath("/type/**", "/type/a-thumb-drive")).toThrow();
|
|
57
58
|
});
|
|
58
59
|
});
|
|
60
|
+
describe("defineRoutes - Request Handling Behavior", () => {
|
|
61
|
+
// Helper to create mock dependencies using dependency injection
|
|
62
|
+
const createMockDependencies = () => {
|
|
63
|
+
const mockRequestInfo = {
|
|
64
|
+
request: new Request("http://localhost:3000/"),
|
|
65
|
+
params: {},
|
|
66
|
+
ctx: {},
|
|
67
|
+
rw: {
|
|
68
|
+
nonce: "test-nonce",
|
|
69
|
+
Document: () => React.createElement("html"),
|
|
70
|
+
rscPayload: true,
|
|
71
|
+
ssr: true,
|
|
72
|
+
databases: new Map(),
|
|
73
|
+
scriptsToBeLoaded: new Set(),
|
|
74
|
+
pageRouteResolved: undefined,
|
|
75
|
+
},
|
|
76
|
+
cf: {},
|
|
77
|
+
response: { headers: new Headers() },
|
|
78
|
+
isAction: false,
|
|
79
|
+
};
|
|
80
|
+
const mockRenderPage = async (requestInfo, Page, onError) => {
|
|
81
|
+
return new Response(`Rendered: ${Page.name || "Component"}`, {
|
|
82
|
+
headers: { "content-type": "text/html" },
|
|
83
|
+
});
|
|
84
|
+
};
|
|
85
|
+
const mockRscActionHandler = async (request) => {
|
|
86
|
+
return { actionResult: "test-action-result" };
|
|
87
|
+
};
|
|
88
|
+
const mockRunWithRequestInfoOverrides = async (overrides, fn) => {
|
|
89
|
+
// Merge overrides into the mock request info
|
|
90
|
+
Object.assign(mockRequestInfo, overrides);
|
|
91
|
+
return await fn();
|
|
92
|
+
};
|
|
93
|
+
return {
|
|
94
|
+
mockRequestInfo,
|
|
95
|
+
mockRenderPage,
|
|
96
|
+
mockRscActionHandler,
|
|
97
|
+
mockRunWithRequestInfoOverrides,
|
|
98
|
+
getRequestInfo: () => mockRequestInfo,
|
|
99
|
+
onError: (error) => {
|
|
100
|
+
throw error;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
describe("Sequential Route Evaluation", () => {
|
|
105
|
+
it("should process routes in the exact order they are defined", async () => {
|
|
106
|
+
const executionOrder = [];
|
|
107
|
+
const middleware1 = (requestInfo) => {
|
|
108
|
+
executionOrder.push("middleware1");
|
|
109
|
+
};
|
|
110
|
+
const middleware2 = (requestInfo) => {
|
|
111
|
+
executionOrder.push("middleware2");
|
|
112
|
+
};
|
|
113
|
+
const PageComponent = () => {
|
|
114
|
+
executionOrder.push("PageComponent");
|
|
115
|
+
return React.createElement("div", {}, "Page");
|
|
116
|
+
};
|
|
117
|
+
const router = defineRoutes([
|
|
118
|
+
middleware1,
|
|
119
|
+
middleware2,
|
|
120
|
+
route("/test/", PageComponent),
|
|
121
|
+
]);
|
|
122
|
+
const deps = createMockDependencies();
|
|
123
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
124
|
+
const request = new Request("http://localhost:3000/test/");
|
|
125
|
+
await router.handle({
|
|
126
|
+
request,
|
|
127
|
+
renderPage: deps.mockRenderPage,
|
|
128
|
+
getRequestInfo: deps.getRequestInfo,
|
|
129
|
+
onError: deps.onError,
|
|
130
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
131
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
132
|
+
});
|
|
133
|
+
expect(executionOrder).toEqual([
|
|
134
|
+
"middleware1",
|
|
135
|
+
"middleware2",
|
|
136
|
+
"PageComponent",
|
|
137
|
+
]);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
describe("Middleware Short-Circuiting", () => {
|
|
141
|
+
it("should stop processing when middleware returns a Response", async () => {
|
|
142
|
+
const executionOrder = [];
|
|
143
|
+
const middleware1 = (requestInfo) => {
|
|
144
|
+
executionOrder.push("middleware1");
|
|
145
|
+
};
|
|
146
|
+
const middleware2 = (requestInfo) => {
|
|
147
|
+
executionOrder.push("middleware2");
|
|
148
|
+
return new Response("Middleware2 Response", { status: 200 });
|
|
149
|
+
};
|
|
150
|
+
const middleware3 = (requestInfo) => {
|
|
151
|
+
executionOrder.push("middleware3");
|
|
152
|
+
};
|
|
153
|
+
const PageComponent = () => {
|
|
154
|
+
executionOrder.push("PageComponent");
|
|
155
|
+
return React.createElement("div", {}, "Page");
|
|
156
|
+
};
|
|
157
|
+
const router = defineRoutes([
|
|
158
|
+
middleware1,
|
|
159
|
+
middleware2,
|
|
160
|
+
middleware3,
|
|
161
|
+
route("/test/", PageComponent),
|
|
162
|
+
]);
|
|
163
|
+
const deps = createMockDependencies();
|
|
164
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
165
|
+
const request = new Request("http://localhost:3000/test/");
|
|
166
|
+
const response = await router.handle({
|
|
167
|
+
request,
|
|
168
|
+
renderPage: deps.mockRenderPage,
|
|
169
|
+
getRequestInfo: deps.getRequestInfo,
|
|
170
|
+
onError: deps.onError,
|
|
171
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
172
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
173
|
+
});
|
|
174
|
+
expect(executionOrder).toEqual(["middleware1", "middleware2"]);
|
|
175
|
+
expect(await response.text()).toBe("Middleware2 Response");
|
|
176
|
+
expect(response.status).toBe(200);
|
|
177
|
+
});
|
|
178
|
+
it("should stop processing when middleware returns a JSX element", async () => {
|
|
179
|
+
const executionOrder = [];
|
|
180
|
+
const middleware1 = (requestInfo) => {
|
|
181
|
+
executionOrder.push("middleware1");
|
|
182
|
+
};
|
|
183
|
+
const middleware2 = (requestInfo) => {
|
|
184
|
+
executionOrder.push("middleware2");
|
|
185
|
+
return React.createElement("div", {}, "Middleware JSX");
|
|
186
|
+
};
|
|
187
|
+
const PageComponent = () => {
|
|
188
|
+
executionOrder.push("PageComponent");
|
|
189
|
+
return React.createElement("div", {}, "Page");
|
|
190
|
+
};
|
|
191
|
+
const router = defineRoutes([
|
|
192
|
+
middleware1,
|
|
193
|
+
middleware2,
|
|
194
|
+
route("/test/", PageComponent),
|
|
195
|
+
]);
|
|
196
|
+
const deps = createMockDependencies();
|
|
197
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
198
|
+
const request = new Request("http://localhost:3000/test/");
|
|
199
|
+
const response = await router.handle({
|
|
200
|
+
request,
|
|
201
|
+
renderPage: deps.mockRenderPage,
|
|
202
|
+
getRequestInfo: deps.getRequestInfo,
|
|
203
|
+
onError: deps.onError,
|
|
204
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
205
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
206
|
+
});
|
|
207
|
+
expect(executionOrder).toEqual(["middleware1", "middleware2"]);
|
|
208
|
+
expect(await response.text()).toBe("Rendered: Element");
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
describe("Prefix Handling", () => {
|
|
212
|
+
it("should only run middleware within the specified prefix", async () => {
|
|
213
|
+
const executionOrder = [];
|
|
214
|
+
const prefixedMiddleware = () => {
|
|
215
|
+
executionOrder.push("prefixedMiddleware");
|
|
216
|
+
};
|
|
217
|
+
const PageComponent = () => {
|
|
218
|
+
executionOrder.push("PageComponent");
|
|
219
|
+
return React.createElement("div", {}, "Page");
|
|
220
|
+
};
|
|
221
|
+
const AdminPageComponent = () => {
|
|
222
|
+
executionOrder.push("AdminPageComponent");
|
|
223
|
+
return React.createElement("div", {}, "Admin Page");
|
|
224
|
+
};
|
|
225
|
+
const router = defineRoutes([
|
|
226
|
+
...prefix("/admin", [
|
|
227
|
+
prefixedMiddleware,
|
|
228
|
+
route("/", AdminPageComponent),
|
|
229
|
+
]),
|
|
230
|
+
route("/", PageComponent),
|
|
231
|
+
]);
|
|
232
|
+
const deps = createMockDependencies();
|
|
233
|
+
// Test 1: Request to a path outside the prefix
|
|
234
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/");
|
|
235
|
+
const request1 = new Request("http://localhost:3000/");
|
|
236
|
+
await router.handle({
|
|
237
|
+
request: request1,
|
|
238
|
+
renderPage: deps.mockRenderPage,
|
|
239
|
+
getRequestInfo: deps.getRequestInfo,
|
|
240
|
+
onError: deps.onError,
|
|
241
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
242
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
243
|
+
});
|
|
244
|
+
expect(executionOrder).toEqual(["PageComponent"]);
|
|
245
|
+
// Reset execution order
|
|
246
|
+
executionOrder.length = 0;
|
|
247
|
+
// Test 2: Request to a path inside the prefix
|
|
248
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/admin/");
|
|
249
|
+
const request2 = new Request("http://localhost:3000/admin/");
|
|
250
|
+
await router.handle({
|
|
251
|
+
request: request2,
|
|
252
|
+
renderPage: deps.mockRenderPage,
|
|
253
|
+
getRequestInfo: deps.getRequestInfo,
|
|
254
|
+
onError: deps.onError,
|
|
255
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
256
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
257
|
+
});
|
|
258
|
+
expect(executionOrder).toEqual([
|
|
259
|
+
"prefixedMiddleware",
|
|
260
|
+
"AdminPageComponent",
|
|
261
|
+
]);
|
|
262
|
+
});
|
|
263
|
+
it("should short-circuit from a prefixed middleware", async () => {
|
|
264
|
+
const executionOrder = [];
|
|
265
|
+
const prefixedMiddleware = () => {
|
|
266
|
+
executionOrder.push("prefixedMiddleware");
|
|
267
|
+
return new Response("From prefixed middleware");
|
|
268
|
+
};
|
|
269
|
+
const AdminPageComponent = () => {
|
|
270
|
+
executionOrder.push("AdminPageComponent");
|
|
271
|
+
return React.createElement("div", {}, "Admin Page");
|
|
272
|
+
};
|
|
273
|
+
const router = defineRoutes([
|
|
274
|
+
...prefix("/admin", [
|
|
275
|
+
prefixedMiddleware,
|
|
276
|
+
route("/", AdminPageComponent),
|
|
277
|
+
]),
|
|
278
|
+
]);
|
|
279
|
+
const deps = createMockDependencies();
|
|
280
|
+
// Request to a path inside the prefix
|
|
281
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/admin/");
|
|
282
|
+
const request = new Request("http://localhost:3000/admin/");
|
|
283
|
+
const response = await router.handle({
|
|
284
|
+
request,
|
|
285
|
+
renderPage: deps.mockRenderPage,
|
|
286
|
+
getRequestInfo: deps.getRequestInfo,
|
|
287
|
+
onError: deps.onError,
|
|
288
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
289
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
290
|
+
});
|
|
291
|
+
expect(executionOrder).toEqual(["prefixedMiddleware"]);
|
|
292
|
+
expect(await response.text()).toBe("From prefixed middleware");
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
describe("RSC Action Handling", () => {
|
|
296
|
+
it("should handle RSC actions before the first route definition", async () => {
|
|
297
|
+
const executionOrder = [];
|
|
298
|
+
const middleware1 = (requestInfo) => {
|
|
299
|
+
executionOrder.push("middleware1");
|
|
300
|
+
};
|
|
301
|
+
const PageComponent = () => {
|
|
302
|
+
executionOrder.push("PageComponent");
|
|
303
|
+
return React.createElement("div", {}, "Page");
|
|
304
|
+
};
|
|
305
|
+
const router = defineRoutes([
|
|
306
|
+
middleware1,
|
|
307
|
+
route("/test/", PageComponent),
|
|
308
|
+
]);
|
|
309
|
+
const deps = createMockDependencies();
|
|
310
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
|
|
311
|
+
deps.mockRscActionHandler = async (request) => {
|
|
312
|
+
executionOrder.push("rscActionHandler");
|
|
313
|
+
return { actionResult: "test-result" };
|
|
314
|
+
};
|
|
315
|
+
const request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
|
|
316
|
+
await router.handle({
|
|
317
|
+
request,
|
|
318
|
+
renderPage: deps.mockRenderPage,
|
|
319
|
+
getRequestInfo: deps.getRequestInfo,
|
|
320
|
+
onError: deps.onError,
|
|
321
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
322
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
323
|
+
});
|
|
324
|
+
expect(executionOrder).toEqual([
|
|
325
|
+
"middleware1",
|
|
326
|
+
"rscActionHandler",
|
|
327
|
+
"PageComponent",
|
|
328
|
+
]);
|
|
329
|
+
expect(deps.mockRequestInfo.rw.actionResult).toEqual({
|
|
330
|
+
actionResult: "test-result",
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
it("should not handle RSC actions multiple times for multiple routes", async () => {
|
|
334
|
+
const executionOrder = [];
|
|
335
|
+
const PageComponent1 = () => {
|
|
336
|
+
executionOrder.push("PageComponent1");
|
|
337
|
+
return React.createElement("div", {}, "Page1");
|
|
338
|
+
};
|
|
339
|
+
const PageComponent2 = () => {
|
|
340
|
+
executionOrder.push("PageComponent2");
|
|
341
|
+
return React.createElement("div", {}, "Page2");
|
|
342
|
+
};
|
|
343
|
+
const router = defineRoutes([
|
|
344
|
+
route("/other/", PageComponent1),
|
|
345
|
+
route("/test/", PageComponent2),
|
|
346
|
+
]);
|
|
347
|
+
const deps = createMockDependencies();
|
|
348
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
|
|
349
|
+
deps.mockRscActionHandler = async (request) => {
|
|
350
|
+
executionOrder.push("rscActionHandler");
|
|
351
|
+
return { actionResult: "test-result" };
|
|
352
|
+
};
|
|
353
|
+
const request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
|
|
354
|
+
await router.handle({
|
|
355
|
+
request,
|
|
356
|
+
renderPage: deps.mockRenderPage,
|
|
357
|
+
getRequestInfo: deps.getRequestInfo,
|
|
358
|
+
onError: deps.onError,
|
|
359
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
360
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
361
|
+
});
|
|
362
|
+
expect(executionOrder).toEqual(["rscActionHandler", "PageComponent2"]);
|
|
363
|
+
// Should only call action handler once, even though there are multiple routes
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
describe("Page Route Matching and Rendering", () => {
|
|
367
|
+
it("should match the first route that matches the path", async () => {
|
|
368
|
+
const executionOrder = [];
|
|
369
|
+
const PageComponent1 = () => {
|
|
370
|
+
executionOrder.push("PageComponent1");
|
|
371
|
+
return React.createElement("div", {}, "Page1");
|
|
372
|
+
};
|
|
373
|
+
const PageComponent2 = () => {
|
|
374
|
+
executionOrder.push("PageComponent2");
|
|
375
|
+
return React.createElement("div", {}, "Page2");
|
|
376
|
+
};
|
|
377
|
+
const router = defineRoutes([
|
|
378
|
+
route("/test/", PageComponent1),
|
|
379
|
+
route("/test/", PageComponent2), // This should never be reached
|
|
380
|
+
]);
|
|
381
|
+
const deps = createMockDependencies();
|
|
382
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
383
|
+
const request = new Request("http://localhost:3000/test/");
|
|
384
|
+
await router.handle({
|
|
385
|
+
request,
|
|
386
|
+
renderPage: deps.mockRenderPage,
|
|
387
|
+
getRequestInfo: deps.getRequestInfo,
|
|
388
|
+
onError: deps.onError,
|
|
389
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
390
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
391
|
+
});
|
|
392
|
+
expect(executionOrder).toEqual(["PageComponent1"]);
|
|
393
|
+
});
|
|
394
|
+
it("should continue to next route if path does not match", async () => {
|
|
395
|
+
const executionOrder = [];
|
|
396
|
+
const PageComponent1 = () => {
|
|
397
|
+
executionOrder.push("PageComponent1");
|
|
398
|
+
return React.createElement("div", {}, "Page1");
|
|
399
|
+
};
|
|
400
|
+
const PageComponent2 = () => {
|
|
401
|
+
executionOrder.push("PageComponent2");
|
|
402
|
+
return React.createElement("div", {}, "Page2");
|
|
403
|
+
};
|
|
404
|
+
const router = defineRoutes([
|
|
405
|
+
route("/other/", PageComponent1),
|
|
406
|
+
route("/test/", PageComponent2),
|
|
407
|
+
]);
|
|
408
|
+
const deps = createMockDependencies();
|
|
409
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
410
|
+
const request = new Request("http://localhost:3000/test/");
|
|
411
|
+
await router.handle({
|
|
412
|
+
request,
|
|
413
|
+
renderPage: deps.mockRenderPage,
|
|
414
|
+
getRequestInfo: deps.getRequestInfo,
|
|
415
|
+
onError: deps.onError,
|
|
416
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
417
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
418
|
+
});
|
|
419
|
+
expect(executionOrder).toEqual(["PageComponent2"]);
|
|
420
|
+
});
|
|
421
|
+
it("should return 404 when no routes match", async () => {
|
|
422
|
+
const PageComponent = () => React.createElement("div", {}, "Page");
|
|
423
|
+
const router = defineRoutes([route("/other/", PageComponent)]);
|
|
424
|
+
const deps = createMockDependencies();
|
|
425
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
426
|
+
const request = new Request("http://localhost:3000/test/");
|
|
427
|
+
const response = await router.handle({
|
|
428
|
+
request,
|
|
429
|
+
renderPage: deps.mockRenderPage,
|
|
430
|
+
getRequestInfo: deps.getRequestInfo,
|
|
431
|
+
onError: deps.onError,
|
|
432
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
433
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
434
|
+
});
|
|
435
|
+
expect(response.status).toBe(404);
|
|
436
|
+
expect(await response.text()).toBe("Not Found");
|
|
437
|
+
});
|
|
438
|
+
});
|
|
439
|
+
describe("Multiple Render Blocks with SSR Configuration", () => {
|
|
440
|
+
it("should short-circuit on first matching render block and not apply later configurations", async () => {
|
|
441
|
+
const executionOrder = [];
|
|
442
|
+
const ssrSettings = [];
|
|
443
|
+
const Document1 = () => React.createElement("html", {}, "Doc1");
|
|
444
|
+
const Document2 = () => React.createElement("html", {}, "Doc2");
|
|
445
|
+
const PageComponent1 = (requestInfo) => {
|
|
446
|
+
executionOrder.push("PageComponent1");
|
|
447
|
+
ssrSettings.push(requestInfo.rw.ssr);
|
|
448
|
+
return React.createElement("div", {}, "Page1");
|
|
449
|
+
};
|
|
450
|
+
const PageComponent2 = (requestInfo) => {
|
|
451
|
+
executionOrder.push("PageComponent2");
|
|
452
|
+
ssrSettings.push(requestInfo.rw.ssr);
|
|
453
|
+
return React.createElement("div", {}, "Page2");
|
|
454
|
+
};
|
|
455
|
+
const router = defineRoutes([
|
|
456
|
+
...render(Document1, [route("/test/", PageComponent1)], { ssr: true }),
|
|
457
|
+
...render(Document2, [route("/other/", PageComponent2)], {
|
|
458
|
+
ssr: false,
|
|
459
|
+
}),
|
|
460
|
+
]);
|
|
461
|
+
const deps = createMockDependencies();
|
|
462
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
463
|
+
const request = new Request("http://localhost:3000/test/");
|
|
464
|
+
await router.handle({
|
|
465
|
+
request,
|
|
466
|
+
renderPage: deps.mockRenderPage,
|
|
467
|
+
getRequestInfo: deps.getRequestInfo,
|
|
468
|
+
onError: deps.onError,
|
|
469
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
470
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
471
|
+
});
|
|
472
|
+
expect(executionOrder).toEqual(["PageComponent1"]);
|
|
473
|
+
expect(ssrSettings).toEqual([true]);
|
|
474
|
+
// The second render block's ssr: false should not have been applied
|
|
475
|
+
expect(deps.mockRequestInfo.rw.Document).toBe(Document1);
|
|
476
|
+
});
|
|
477
|
+
});
|
|
478
|
+
describe("Route-Specific Middleware", () => {
|
|
479
|
+
it("should execute route-specific middleware before the component", async () => {
|
|
480
|
+
const executionOrder = [];
|
|
481
|
+
const globalMiddleware = (requestInfo) => {
|
|
482
|
+
executionOrder.push("globalMiddleware");
|
|
483
|
+
};
|
|
484
|
+
const routeMiddleware = (requestInfo) => {
|
|
485
|
+
executionOrder.push("routeMiddleware");
|
|
486
|
+
};
|
|
487
|
+
const PageComponent = () => {
|
|
488
|
+
executionOrder.push("PageComponent");
|
|
489
|
+
return React.createElement("div", {}, "Page");
|
|
490
|
+
};
|
|
491
|
+
const router = defineRoutes([
|
|
492
|
+
globalMiddleware,
|
|
493
|
+
route("/test/", [routeMiddleware, PageComponent]),
|
|
494
|
+
]);
|
|
495
|
+
const deps = createMockDependencies();
|
|
496
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
497
|
+
const request = new Request("http://localhost:3000/test/");
|
|
498
|
+
await router.handle({
|
|
499
|
+
request,
|
|
500
|
+
renderPage: deps.mockRenderPage,
|
|
501
|
+
getRequestInfo: deps.getRequestInfo,
|
|
502
|
+
onError: deps.onError,
|
|
503
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
504
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
505
|
+
});
|
|
506
|
+
expect(executionOrder).toEqual([
|
|
507
|
+
"globalMiddleware",
|
|
508
|
+
"routeMiddleware",
|
|
509
|
+
"PageComponent",
|
|
510
|
+
]);
|
|
511
|
+
});
|
|
512
|
+
it("should short-circuit if route-specific middleware returns a Response", async () => {
|
|
513
|
+
const executionOrder = [];
|
|
514
|
+
const routeMiddleware = (requestInfo) => {
|
|
515
|
+
executionOrder.push("routeMiddleware");
|
|
516
|
+
return new Response("Route Middleware Response");
|
|
517
|
+
};
|
|
518
|
+
const PageComponent = () => {
|
|
519
|
+
executionOrder.push("PageComponent");
|
|
520
|
+
return React.createElement("div", {}, "Page");
|
|
521
|
+
};
|
|
522
|
+
const router = defineRoutes([
|
|
523
|
+
route("/test/", [routeMiddleware, PageComponent]),
|
|
524
|
+
]);
|
|
525
|
+
const deps = createMockDependencies();
|
|
526
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
527
|
+
const request = new Request("http://localhost:3000/test/");
|
|
528
|
+
const response = await router.handle({
|
|
529
|
+
request,
|
|
530
|
+
renderPage: deps.mockRenderPage,
|
|
531
|
+
getRequestInfo: deps.getRequestInfo,
|
|
532
|
+
onError: deps.onError,
|
|
533
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
534
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
535
|
+
});
|
|
536
|
+
expect(executionOrder).toEqual(["routeMiddleware"]);
|
|
537
|
+
expect(await response.text()).toBe("Route Middleware Response");
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
describe("Layout Handling", () => {
|
|
541
|
+
it("should wrap components with layouts", async () => {
|
|
542
|
+
const executionOrder = [];
|
|
543
|
+
const TestLayout = ({ children }) => {
|
|
544
|
+
executionOrder.push("TestLayout");
|
|
545
|
+
return React.createElement("div", { className: "layout" }, children);
|
|
546
|
+
};
|
|
547
|
+
const PageComponent = () => {
|
|
548
|
+
executionOrder.push("PageComponent");
|
|
549
|
+
return React.createElement("div", {}, "Page Content");
|
|
550
|
+
};
|
|
551
|
+
const router = defineRoutes([
|
|
552
|
+
...layout(TestLayout, [route("/test/", PageComponent)]),
|
|
553
|
+
]);
|
|
554
|
+
const deps = createMockDependencies();
|
|
555
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/");
|
|
556
|
+
// Mock renderPage to track layout wrapping
|
|
557
|
+
deps.mockRenderPage = async (requestInfo, WrappedComponent, onError) => {
|
|
558
|
+
// The component should be wrapped with layouts
|
|
559
|
+
const element = React.createElement(WrappedComponent);
|
|
560
|
+
return new Response(`Rendered with layouts`);
|
|
561
|
+
};
|
|
562
|
+
const request = new Request("http://localhost:3000/test/");
|
|
563
|
+
const response = await router.handle({
|
|
564
|
+
request,
|
|
565
|
+
renderPage: deps.mockRenderPage,
|
|
566
|
+
getRequestInfo: deps.getRequestInfo,
|
|
567
|
+
onError: deps.onError,
|
|
568
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
569
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
570
|
+
});
|
|
571
|
+
expect(await response.text()).toBe("Rendered with layouts");
|
|
572
|
+
});
|
|
573
|
+
});
|
|
574
|
+
describe("Parameter Extraction", () => {
|
|
575
|
+
it("should extract path parameters and make them available in request info", async () => {
|
|
576
|
+
let extractedParams = null;
|
|
577
|
+
const PageComponent = (requestInfo) => {
|
|
578
|
+
extractedParams = requestInfo.params;
|
|
579
|
+
return React.createElement("div", {}, `User: ${requestInfo.params.id}`);
|
|
580
|
+
};
|
|
581
|
+
const router = defineRoutes([route("/users/:id/", PageComponent)]);
|
|
582
|
+
const deps = createMockDependencies();
|
|
583
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/users/123/");
|
|
584
|
+
const request = new Request("http://localhost:3000/users/123/");
|
|
585
|
+
await router.handle({
|
|
586
|
+
request,
|
|
587
|
+
renderPage: deps.mockRenderPage,
|
|
588
|
+
getRequestInfo: deps.getRequestInfo,
|
|
589
|
+
onError: deps.onError,
|
|
590
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
591
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
592
|
+
});
|
|
593
|
+
expect(extractedParams).toEqual({ id: "123" });
|
|
594
|
+
});
|
|
595
|
+
});
|
|
596
|
+
describe("Edge Cases", () => {
|
|
597
|
+
it("should handle middleware-only apps with RSC actions", async () => {
|
|
598
|
+
const executionOrder = [];
|
|
599
|
+
const middleware1 = (requestInfo) => {
|
|
600
|
+
executionOrder.push("middleware1");
|
|
601
|
+
};
|
|
602
|
+
const middleware2 = (requestInfo) => {
|
|
603
|
+
executionOrder.push("middleware2");
|
|
604
|
+
return new Response("Middleware Response");
|
|
605
|
+
};
|
|
606
|
+
// No route definitions, only middleware
|
|
607
|
+
const router = defineRoutes([middleware1, middleware2]);
|
|
608
|
+
const deps = createMockDependencies();
|
|
609
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
|
|
610
|
+
deps.mockRscActionHandler = async (request) => {
|
|
611
|
+
executionOrder.push("rscActionHandler");
|
|
612
|
+
return { actionResult: "test-result" };
|
|
613
|
+
};
|
|
614
|
+
const request = new Request("http://localhost:3000/test/?__rsc_action_id=test");
|
|
615
|
+
const response = await router.handle({
|
|
616
|
+
request,
|
|
617
|
+
renderPage: deps.mockRenderPage,
|
|
618
|
+
getRequestInfo: deps.getRequestInfo,
|
|
619
|
+
onError: deps.onError,
|
|
620
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
621
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
622
|
+
});
|
|
623
|
+
// Action should still be handled even with no route definitions
|
|
624
|
+
expect(executionOrder).toEqual(["middleware1", "middleware2"]);
|
|
625
|
+
expect(await response.text()).toBe("Middleware Response");
|
|
626
|
+
});
|
|
627
|
+
it("should handle trailing slash normalization", async () => {
|
|
628
|
+
const PageComponent = () => React.createElement("div", {}, "Page");
|
|
629
|
+
const router = defineRoutes([route("/test/", PageComponent)]);
|
|
630
|
+
const deps = createMockDependencies();
|
|
631
|
+
// Request without trailing slash should be normalized
|
|
632
|
+
deps.mockRequestInfo.request = new Request("http://localhost:3000/test");
|
|
633
|
+
const request = new Request("http://localhost:3000/test");
|
|
634
|
+
const response = await router.handle({
|
|
635
|
+
request,
|
|
636
|
+
renderPage: deps.mockRenderPage,
|
|
637
|
+
getRequestInfo: deps.getRequestInfo,
|
|
638
|
+
onError: deps.onError,
|
|
639
|
+
runWithRequestInfoOverrides: deps.mockRunWithRequestInfoOverrides,
|
|
640
|
+
rscActionHandler: deps.mockRscActionHandler,
|
|
641
|
+
});
|
|
642
|
+
expect(response.status).not.toBe(404);
|
|
643
|
+
expect(await response.text()).toBe("Rendered: Element");
|
|
644
|
+
});
|
|
645
|
+
});
|
|
646
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type Kysely } from "kysely";
|
|
2
|
+
import { type RequestInfo } from "../requestInfo/types.js";
|
|
3
|
+
export type RwContext = {
|
|
4
|
+
nonce: string;
|
|
5
|
+
Document: React.FC<DocumentProps<any>>;
|
|
6
|
+
rscPayload: boolean;
|
|
7
|
+
ssr: boolean;
|
|
8
|
+
layouts?: React.FC<LayoutProps<any>>[];
|
|
9
|
+
databases: Map<string, Kysely<any>>;
|
|
10
|
+
scriptsToBeLoaded: Set<string>;
|
|
11
|
+
entryScripts: Set<string>;
|
|
12
|
+
inlineScripts: Set<string>;
|
|
13
|
+
pageRouteResolved: PromiseWithResolvers<void> | undefined;
|
|
14
|
+
actionResult?: unknown;
|
|
15
|
+
};
|
|
16
|
+
export type DocumentProps<T extends RequestInfo = RequestInfo> = T & {
|
|
17
|
+
children: React.ReactNode;
|
|
18
|
+
};
|
|
19
|
+
export type LayoutProps<T extends RequestInfo = RequestInfo> = {
|
|
20
|
+
children?: React.ReactNode;
|
|
21
|
+
requestInfo?: T;
|
|
22
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A utility to orchestrate and interleave two ReadableStreams (a document shell and an app shell)
|
|
3
|
+
* based on a set of markers within their content. This is designed to solve a specific
|
|
4
|
+
* race condition in streaming Server-Side Rendering (SSR) with Suspense.
|
|
5
|
+
*
|
|
6
|
+
* The logic is as follows:
|
|
7
|
+
* 1. Stream the document until a start marker is found.
|
|
8
|
+
* 2. Switch to the app stream and stream it until an end marker is found. This is the non-suspended shell.
|
|
9
|
+
* 3. Switch back to the document stream and stream it until the closing body tag. This sends the client script.
|
|
10
|
+
* 4. Switch back to the app stream and stream the remainder (the suspended content).
|
|
11
|
+
* 5. Switch back to the document stream and stream the remainder (closing body and html tags).
|
|
12
|
+
*
|
|
13
|
+
* @param outerHtml The stream for the document shell (`<Document>`).
|
|
14
|
+
* @param innerHtml The stream for the application's content.
|
|
15
|
+
* @param startMarker The marker in the document to start injecting the app.
|
|
16
|
+
* @param endMarker The marker in the app stream that signals the end of the initial, non-suspended render.
|
|
17
|
+
*/
|
|
18
|
+
export declare function stitchDocumentAndAppStreams(outerHtml: ReadableStream<Uint8Array>, innerHtml: ReadableStream<Uint8Array>, startMarker: string, endMarker: string): ReadableStream<Uint8Array>;
|