@rangojs/router 0.0.0-experimental.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +7 -0
- package/README.md +19 -0
- package/dist/vite/index.js +1298 -0
- package/package.json +140 -0
- package/skills/caching/SKILL.md +319 -0
- package/skills/document-cache/SKILL.md +152 -0
- package/skills/hooks/SKILL.md +359 -0
- package/skills/intercept/SKILL.md +292 -0
- package/skills/layout/SKILL.md +216 -0
- package/skills/loader/SKILL.md +365 -0
- package/skills/middleware/SKILL.md +442 -0
- package/skills/parallel/SKILL.md +255 -0
- package/skills/route/SKILL.md +141 -0
- package/skills/router-setup/SKILL.md +403 -0
- package/skills/theme/SKILL.md +54 -0
- package/skills/typesafety/SKILL.md +352 -0
- package/src/__mocks__/version.ts +6 -0
- package/src/__tests__/component-utils.test.ts +76 -0
- package/src/__tests__/route-definition.test.ts +63 -0
- package/src/__tests__/urls.test.tsx +436 -0
- package/src/browser/event-controller.ts +876 -0
- package/src/browser/index.ts +18 -0
- package/src/browser/link-interceptor.ts +121 -0
- package/src/browser/lru-cache.ts +69 -0
- package/src/browser/merge-segment-loaders.ts +126 -0
- package/src/browser/navigation-bridge.ts +893 -0
- package/src/browser/navigation-client.ts +162 -0
- package/src/browser/navigation-store.ts +823 -0
- package/src/browser/partial-update.ts +559 -0
- package/src/browser/react/Link.tsx +248 -0
- package/src/browser/react/NavigationProvider.tsx +275 -0
- package/src/browser/react/ScrollRestoration.tsx +94 -0
- package/src/browser/react/context.ts +53 -0
- package/src/browser/react/index.ts +52 -0
- package/src/browser/react/location-state-shared.ts +120 -0
- package/src/browser/react/location-state.ts +62 -0
- package/src/browser/react/use-action.ts +240 -0
- package/src/browser/react/use-client-cache.ts +56 -0
- package/src/browser/react/use-handle.ts +178 -0
- package/src/browser/react/use-href.tsx +208 -0
- package/src/browser/react/use-link-status.ts +134 -0
- package/src/browser/react/use-navigation.ts +150 -0
- package/src/browser/react/use-segments.ts +188 -0
- package/src/browser/request-controller.ts +164 -0
- package/src/browser/rsc-router.tsx +353 -0
- package/src/browser/scroll-restoration.ts +324 -0
- package/src/browser/server-action-bridge.ts +747 -0
- package/src/browser/shallow.ts +35 -0
- package/src/browser/types.ts +464 -0
- package/src/cache/__tests__/document-cache.test.ts +522 -0
- package/src/cache/__tests__/memory-segment-store.test.ts +487 -0
- package/src/cache/__tests__/memory-store.test.ts +484 -0
- package/src/cache/cache-scope.ts +565 -0
- package/src/cache/cf/__tests__/cf-cache-store.test.ts +428 -0
- package/src/cache/cf/cf-cache-store.ts +428 -0
- package/src/cache/cf/index.ts +19 -0
- package/src/cache/document-cache.ts +340 -0
- package/src/cache/index.ts +58 -0
- package/src/cache/memory-segment-store.ts +150 -0
- package/src/cache/memory-store.ts +253 -0
- package/src/cache/types.ts +387 -0
- package/src/client.rsc.tsx +88 -0
- package/src/client.tsx +621 -0
- package/src/component-utils.ts +76 -0
- package/src/components/DefaultDocument.tsx +23 -0
- package/src/default-error-boundary.tsx +88 -0
- package/src/deps/browser.ts +8 -0
- package/src/deps/html-stream-client.ts +2 -0
- package/src/deps/html-stream-server.ts +2 -0
- package/src/deps/rsc.ts +10 -0
- package/src/deps/ssr.ts +2 -0
- package/src/errors.ts +259 -0
- package/src/handle.ts +120 -0
- package/src/handles/MetaTags.tsx +193 -0
- package/src/handles/index.ts +6 -0
- package/src/handles/meta.ts +247 -0
- package/src/href-client.ts +128 -0
- package/src/href-context.ts +33 -0
- package/src/href.ts +177 -0
- package/src/index.rsc.ts +79 -0
- package/src/index.ts +87 -0
- package/src/loader.rsc.ts +204 -0
- package/src/loader.ts +47 -0
- package/src/network-error-thrower.tsx +21 -0
- package/src/outlet-context.ts +15 -0
- package/src/root-error-boundary.tsx +277 -0
- package/src/route-content-wrapper.tsx +198 -0
- package/src/route-definition.ts +1371 -0
- package/src/route-map-builder.ts +146 -0
- package/src/route-types.ts +198 -0
- package/src/route-utils.ts +89 -0
- package/src/router/__tests__/match-context.test.ts +104 -0
- package/src/router/__tests__/match-pipelines.test.ts +537 -0
- package/src/router/__tests__/match-result.test.ts +566 -0
- package/src/router/__tests__/on-error.test.ts +935 -0
- package/src/router/__tests__/pattern-matching.test.ts +577 -0
- package/src/router/error-handling.ts +287 -0
- package/src/router/handler-context.ts +158 -0
- package/src/router/loader-resolution.ts +326 -0
- package/src/router/manifest.ts +138 -0
- package/src/router/match-context.ts +264 -0
- package/src/router/match-middleware/background-revalidation.ts +236 -0
- package/src/router/match-middleware/cache-lookup.ts +261 -0
- package/src/router/match-middleware/cache-store.ts +266 -0
- package/src/router/match-middleware/index.ts +81 -0
- package/src/router/match-middleware/intercept-resolution.ts +268 -0
- package/src/router/match-middleware/segment-resolution.ts +174 -0
- package/src/router/match-pipelines.ts +214 -0
- package/src/router/match-result.ts +214 -0
- package/src/router/metrics.ts +62 -0
- package/src/router/middleware.test.ts +1355 -0
- package/src/router/middleware.ts +748 -0
- package/src/router/pattern-matching.ts +272 -0
- package/src/router/revalidation.ts +190 -0
- package/src/router/router-context.ts +299 -0
- package/src/router/types.ts +96 -0
- package/src/router.ts +3876 -0
- package/src/rsc/__tests__/helpers.test.ts +175 -0
- package/src/rsc/handler.ts +1060 -0
- package/src/rsc/helpers.ts +64 -0
- package/src/rsc/index.ts +56 -0
- package/src/rsc/nonce.ts +18 -0
- package/src/rsc/types.ts +237 -0
- package/src/segment-system.tsx +456 -0
- package/src/server/__tests__/request-context.test.ts +171 -0
- package/src/server/context.ts +417 -0
- package/src/server/handle-store.ts +230 -0
- package/src/server/loader-registry.ts +174 -0
- package/src/server/request-context.ts +554 -0
- package/src/server/root-layout.tsx +10 -0
- package/src/server/tsconfig.json +14 -0
- package/src/server.ts +146 -0
- package/src/ssr/__tests__/ssr-handler.test.tsx +188 -0
- package/src/ssr/index.tsx +234 -0
- package/src/theme/ThemeProvider.tsx +291 -0
- package/src/theme/ThemeScript.tsx +61 -0
- package/src/theme/__tests__/theme.test.ts +120 -0
- package/src/theme/constants.ts +55 -0
- package/src/theme/index.ts +58 -0
- package/src/theme/theme-context.ts +70 -0
- package/src/theme/theme-script.ts +152 -0
- package/src/theme/types.ts +182 -0
- package/src/theme/use-theme.ts +44 -0
- package/src/types.ts +1561 -0
- package/src/urls.ts +726 -0
- package/src/use-loader.tsx +346 -0
- package/src/vite/__tests__/expose-loader-id.test.ts +117 -0
- package/src/vite/expose-action-id.ts +344 -0
- package/src/vite/expose-handle-id.ts +209 -0
- package/src/vite/expose-loader-id.ts +357 -0
- package/src/vite/expose-location-state-id.ts +177 -0
- package/src/vite/index.ts +787 -0
- package/src/vite/package-resolution.ts +125 -0
- package/src/vite/version.d.ts +12 -0
- package/src/vite/virtual-entries.ts +109 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { urls } from "../urls.js";
|
|
3
|
+
import { RSCRouterContext, type EntryData } from "../server/context.js";
|
|
4
|
+
|
|
5
|
+
describe("urls()", () => {
|
|
6
|
+
describe("basic structure", () => {
|
|
7
|
+
it("should return UrlPatterns with handler function", () => {
|
|
8
|
+
const patterns = urls(({ path }) => [
|
|
9
|
+
path("/", () => <div>Home</div>),
|
|
10
|
+
]);
|
|
11
|
+
|
|
12
|
+
expect(typeof patterns.handler).toBe("function");
|
|
13
|
+
expect(patterns.definitions).toEqual([]);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it("should accept empty array", () => {
|
|
17
|
+
const patterns = urls(() => []);
|
|
18
|
+
|
|
19
|
+
expect(typeof patterns.handler).toBe("function");
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
describe("path() helper", () => {
|
|
24
|
+
let manifest: Map<string, EntryData>;
|
|
25
|
+
let patterns: Map<string, string>;
|
|
26
|
+
|
|
27
|
+
beforeEach(() => {
|
|
28
|
+
manifest = new Map();
|
|
29
|
+
patterns = new Map();
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
manifest.clear();
|
|
34
|
+
patterns.clear();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("should register route with pattern only", () => {
|
|
38
|
+
const urlPatterns = urls(({ path }) => [
|
|
39
|
+
path("/about", () => <div>About</div>),
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
// Execute the handler within context to register routes
|
|
43
|
+
RSCRouterContext.run(
|
|
44
|
+
{
|
|
45
|
+
manifest,
|
|
46
|
+
patterns,
|
|
47
|
+
namespace: "test",
|
|
48
|
+
parent: null,
|
|
49
|
+
counters: {},
|
|
50
|
+
},
|
|
51
|
+
() => urlPatterns.handler()
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// Should have registered a route
|
|
55
|
+
expect(manifest.size).toBeGreaterThan(0);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("should register route with name option", () => {
|
|
59
|
+
const urlPatterns = urls(({ path }) => [
|
|
60
|
+
path("/about", () => <div>About</div>, { name: "about" }),
|
|
61
|
+
]);
|
|
62
|
+
|
|
63
|
+
RSCRouterContext.run(
|
|
64
|
+
{
|
|
65
|
+
manifest,
|
|
66
|
+
patterns,
|
|
67
|
+
namespace: "test",
|
|
68
|
+
parent: null,
|
|
69
|
+
counters: {},
|
|
70
|
+
},
|
|
71
|
+
() => urlPatterns.handler()
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
// Should have registered route with name "about"
|
|
75
|
+
expect(manifest.has("about")).toBe(true);
|
|
76
|
+
expect(patterns.get("about")).toBe("/about");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("should store pattern on route entry", () => {
|
|
80
|
+
const urlPatterns = urls(({ path }) => [
|
|
81
|
+
path("/:slug", () => <div>Post</div>, { name: "post" }),
|
|
82
|
+
]);
|
|
83
|
+
|
|
84
|
+
RSCRouterContext.run(
|
|
85
|
+
{
|
|
86
|
+
manifest,
|
|
87
|
+
patterns,
|
|
88
|
+
namespace: "test",
|
|
89
|
+
parent: null,
|
|
90
|
+
counters: {},
|
|
91
|
+
},
|
|
92
|
+
() => urlPatterns.handler()
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
const entry = manifest.get("post");
|
|
96
|
+
expect(entry).toBeDefined();
|
|
97
|
+
expect((entry as any).pattern).toBe("/:slug");
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("should accept use callback as third argument", () => {
|
|
101
|
+
const urlPatterns = urls(({ path, middleware }) => [
|
|
102
|
+
path("/admin", () => <div>Admin</div>, () => [
|
|
103
|
+
middleware(() => {}),
|
|
104
|
+
]),
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
RSCRouterContext.run(
|
|
108
|
+
{
|
|
109
|
+
manifest,
|
|
110
|
+
patterns,
|
|
111
|
+
namespace: "test",
|
|
112
|
+
parent: null,
|
|
113
|
+
counters: {},
|
|
114
|
+
},
|
|
115
|
+
() => urlPatterns.handler()
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
// Should have a route registered
|
|
119
|
+
expect(manifest.size).toBeGreaterThan(0);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("should accept options and use callback", () => {
|
|
123
|
+
const urlPatterns = urls(({ path, middleware }) => [
|
|
124
|
+
path("/admin", () => <div>Admin</div>, { name: "admin" }, () => [
|
|
125
|
+
middleware(() => {}),
|
|
126
|
+
]),
|
|
127
|
+
]);
|
|
128
|
+
|
|
129
|
+
RSCRouterContext.run(
|
|
130
|
+
{
|
|
131
|
+
manifest,
|
|
132
|
+
patterns,
|
|
133
|
+
namespace: "test",
|
|
134
|
+
parent: null,
|
|
135
|
+
counters: {},
|
|
136
|
+
},
|
|
137
|
+
() => urlPatterns.handler()
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(manifest.has("admin")).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe("layout() helper", () => {
|
|
145
|
+
let manifest: Map<string, EntryData>;
|
|
146
|
+
let patterns: Map<string, string>;
|
|
147
|
+
|
|
148
|
+
beforeEach(() => {
|
|
149
|
+
manifest = new Map();
|
|
150
|
+
patterns = new Map();
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should work with path() inside layout", () => {
|
|
154
|
+
const urlPatterns = urls(({ path, layout }) => [
|
|
155
|
+
layout(() => <div>Layout</div>, () => [
|
|
156
|
+
path("/", () => <div>Home</div>, { name: "home" }),
|
|
157
|
+
path("/about", () => <div>About</div>, { name: "about" }),
|
|
158
|
+
]),
|
|
159
|
+
]);
|
|
160
|
+
|
|
161
|
+
RSCRouterContext.run(
|
|
162
|
+
{
|
|
163
|
+
manifest,
|
|
164
|
+
patterns,
|
|
165
|
+
namespace: "test",
|
|
166
|
+
parent: null,
|
|
167
|
+
counters: {},
|
|
168
|
+
},
|
|
169
|
+
() => urlPatterns.handler()
|
|
170
|
+
);
|
|
171
|
+
|
|
172
|
+
expect(manifest.has("home")).toBe(true);
|
|
173
|
+
expect(manifest.has("about")).toBe(true);
|
|
174
|
+
});
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
describe("trailingSlash option", () => {
|
|
178
|
+
it("should store trailingSlash config in context", () => {
|
|
179
|
+
const manifest = new Map();
|
|
180
|
+
const patterns = new Map();
|
|
181
|
+
const trailingSlash = new Map<string, "always" | "never" | "ignore">();
|
|
182
|
+
|
|
183
|
+
const urlPatterns = urls(({ path }) => [
|
|
184
|
+
path("/ts-always", () => <div>Always</div>, { name: "tsAlways", trailingSlash: "always" }),
|
|
185
|
+
path("/ts-never", () => <div>Never</div>, { name: "tsNever", trailingSlash: "never" }),
|
|
186
|
+
path("/ts-ignore", () => <div>Ignore</div>, { name: "tsIgnore", trailingSlash: "ignore" }),
|
|
187
|
+
]);
|
|
188
|
+
|
|
189
|
+
RSCRouterContext.run(
|
|
190
|
+
{
|
|
191
|
+
manifest,
|
|
192
|
+
patterns,
|
|
193
|
+
trailingSlash,
|
|
194
|
+
namespace: "test",
|
|
195
|
+
parent: null,
|
|
196
|
+
counters: {},
|
|
197
|
+
},
|
|
198
|
+
() => urlPatterns.handler()
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(trailingSlash.get("tsAlways")).toBe("always");
|
|
202
|
+
expect(trailingSlash.get("tsNever")).toBe("never");
|
|
203
|
+
expect(trailingSlash.get("tsIgnore")).toBe("ignore");
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
describe("include() helper", () => {
|
|
208
|
+
it("should return IncludeItem with correct structure", () => {
|
|
209
|
+
const blogPatterns = urls(({ path }) => [
|
|
210
|
+
path("/", () => <div>Blog Index</div>, { name: "index" }),
|
|
211
|
+
]);
|
|
212
|
+
|
|
213
|
+
// Verify include() returns the right structure directly
|
|
214
|
+
const manifest = new Map();
|
|
215
|
+
const patterns = new Map();
|
|
216
|
+
|
|
217
|
+
RSCRouterContext.run(
|
|
218
|
+
{
|
|
219
|
+
manifest,
|
|
220
|
+
patterns,
|
|
221
|
+
namespace: "test",
|
|
222
|
+
parent: null,
|
|
223
|
+
counters: {},
|
|
224
|
+
},
|
|
225
|
+
() => {
|
|
226
|
+
// Test include() directly within context
|
|
227
|
+
const urlPatterns = urls(({ include }) => {
|
|
228
|
+
const includeItem = include("/blog", blogPatterns, { name: "blog" });
|
|
229
|
+
|
|
230
|
+
// Verify include item structure
|
|
231
|
+
expect(includeItem.type).toBe("include");
|
|
232
|
+
expect(includeItem.prefix).toBe("/blog");
|
|
233
|
+
expect(includeItem.options?.name).toBe("blog");
|
|
234
|
+
expect(includeItem.patterns).toBe(blogPatterns);
|
|
235
|
+
|
|
236
|
+
return [includeItem];
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Execute handler to verify it doesn't throw
|
|
240
|
+
urlPatterns.handler();
|
|
241
|
+
}
|
|
242
|
+
);
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it("should apply URL prefix to nested patterns", () => {
|
|
246
|
+
const blogPatterns = urls(({ path }) => [
|
|
247
|
+
path("/", () => <div>Blog Index</div>, { name: "index" }),
|
|
248
|
+
path("/:slug", () => <div>Blog Post</div>, { name: "post" }),
|
|
249
|
+
]);
|
|
250
|
+
|
|
251
|
+
const manifest = new Map<string, EntryData>();
|
|
252
|
+
const patterns = new Map<string, string>();
|
|
253
|
+
|
|
254
|
+
RSCRouterContext.run(
|
|
255
|
+
{
|
|
256
|
+
manifest,
|
|
257
|
+
patterns,
|
|
258
|
+
namespace: "test",
|
|
259
|
+
parent: null,
|
|
260
|
+
counters: {},
|
|
261
|
+
},
|
|
262
|
+
() => {
|
|
263
|
+
const urlPatterns = urls(({ include }) => [
|
|
264
|
+
include("/blog", blogPatterns, { name: "blog" }),
|
|
265
|
+
]);
|
|
266
|
+
|
|
267
|
+
urlPatterns.handler();
|
|
268
|
+
}
|
|
269
|
+
);
|
|
270
|
+
|
|
271
|
+
// Routes should be prefixed with "blog."
|
|
272
|
+
expect(manifest.has("blog.index")).toBe(true);
|
|
273
|
+
expect(manifest.has("blog.post")).toBe(true);
|
|
274
|
+
|
|
275
|
+
// Patterns should be prefixed with "/blog"
|
|
276
|
+
expect(patterns.get("blog.index")).toBe("/blog");
|
|
277
|
+
expect(patterns.get("blog.post")).toBe("/blog/:slug");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it("should apply name prefix to nested route names", () => {
|
|
281
|
+
const shopPatterns = urls(({ path }) => [
|
|
282
|
+
path("/", () => <div>Shop</div>, { name: "index" }),
|
|
283
|
+
path("/cart", () => <div>Cart</div>, { name: "cart" }),
|
|
284
|
+
path("/product/:id", () => <div>Product</div>, { name: "product" }),
|
|
285
|
+
]);
|
|
286
|
+
|
|
287
|
+
const manifest = new Map<string, EntryData>();
|
|
288
|
+
const patterns = new Map<string, string>();
|
|
289
|
+
|
|
290
|
+
RSCRouterContext.run(
|
|
291
|
+
{
|
|
292
|
+
manifest,
|
|
293
|
+
patterns,
|
|
294
|
+
namespace: "test",
|
|
295
|
+
parent: null,
|
|
296
|
+
counters: {},
|
|
297
|
+
},
|
|
298
|
+
() => {
|
|
299
|
+
const urlPatterns = urls(({ include }) => [
|
|
300
|
+
include("/shop", shopPatterns, { name: "shop" }),
|
|
301
|
+
]);
|
|
302
|
+
|
|
303
|
+
urlPatterns.handler();
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
|
|
307
|
+
// All route names should be prefixed with "shop."
|
|
308
|
+
expect(manifest.has("shop.index")).toBe(true);
|
|
309
|
+
expect(manifest.has("shop.cart")).toBe(true);
|
|
310
|
+
expect(manifest.has("shop.product")).toBe(true);
|
|
311
|
+
|
|
312
|
+
// URL patterns should be prefixed with "/shop"
|
|
313
|
+
expect(patterns.get("shop.cart")).toBe("/shop/cart");
|
|
314
|
+
expect(patterns.get("shop.product")).toBe("/shop/product/:id");
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
it("should work without name prefix (routes keep local names)", () => {
|
|
318
|
+
const adminPatterns = urls(({ path }) => [
|
|
319
|
+
path("/", () => <div>Admin</div>, { name: "index" }),
|
|
320
|
+
path("/users", () => <div>Users</div>, { name: "users" }),
|
|
321
|
+
]);
|
|
322
|
+
|
|
323
|
+
const manifest = new Map<string, EntryData>();
|
|
324
|
+
const patterns = new Map<string, string>();
|
|
325
|
+
|
|
326
|
+
RSCRouterContext.run(
|
|
327
|
+
{
|
|
328
|
+
manifest,
|
|
329
|
+
patterns,
|
|
330
|
+
namespace: "test",
|
|
331
|
+
parent: null,
|
|
332
|
+
counters: {},
|
|
333
|
+
},
|
|
334
|
+
() => {
|
|
335
|
+
const urlPatterns = urls(({ include }) => [
|
|
336
|
+
// No name option - routes keep their local names
|
|
337
|
+
include("/admin", adminPatterns),
|
|
338
|
+
]);
|
|
339
|
+
|
|
340
|
+
urlPatterns.handler();
|
|
341
|
+
}
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
// Routes should keep local names (no prefix)
|
|
345
|
+
expect(manifest.has("index")).toBe(true);
|
|
346
|
+
expect(manifest.has("users")).toBe(true);
|
|
347
|
+
|
|
348
|
+
// But URL patterns should still be prefixed
|
|
349
|
+
expect(patterns.get("index")).toBe("/admin");
|
|
350
|
+
expect(patterns.get("users")).toBe("/admin/users");
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
it("should support nested includes", () => {
|
|
354
|
+
const postPatterns = urls(({ path }) => [
|
|
355
|
+
path("/", () => <div>Posts</div>, { name: "index" }),
|
|
356
|
+
path("/:id", () => <div>Post</div>, { name: "detail" }),
|
|
357
|
+
]);
|
|
358
|
+
|
|
359
|
+
const blogPatterns = urls(({ path, include }) => [
|
|
360
|
+
path("/", () => <div>Blog</div>, { name: "home" }),
|
|
361
|
+
include("/posts", postPatterns, { name: "posts" }),
|
|
362
|
+
]);
|
|
363
|
+
|
|
364
|
+
const manifest = new Map<string, EntryData>();
|
|
365
|
+
const patterns = new Map<string, string>();
|
|
366
|
+
|
|
367
|
+
RSCRouterContext.run(
|
|
368
|
+
{
|
|
369
|
+
manifest,
|
|
370
|
+
patterns,
|
|
371
|
+
namespace: "test",
|
|
372
|
+
parent: null,
|
|
373
|
+
counters: {},
|
|
374
|
+
},
|
|
375
|
+
() => {
|
|
376
|
+
const urlPatterns = urls(({ include }) => [
|
|
377
|
+
include("/blog", blogPatterns, { name: "blog" }),
|
|
378
|
+
]);
|
|
379
|
+
|
|
380
|
+
urlPatterns.handler();
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
// Top level blog routes
|
|
385
|
+
expect(manifest.has("blog.home")).toBe(true);
|
|
386
|
+
expect(patterns.get("blog.home")).toBe("/blog");
|
|
387
|
+
|
|
388
|
+
// Nested posts routes (blog.posts.index, blog.posts.detail)
|
|
389
|
+
expect(manifest.has("blog.posts.index")).toBe(true);
|
|
390
|
+
expect(manifest.has("blog.posts.detail")).toBe(true);
|
|
391
|
+
expect(patterns.get("blog.posts.index")).toBe("/blog/posts");
|
|
392
|
+
expect(patterns.get("blog.posts.detail")).toBe("/blog/posts/:id");
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("should reuse same patterns with different prefixes", () => {
|
|
396
|
+
// Same pattern module can be included multiple times with different prefixes
|
|
397
|
+
const contentPatterns = urls(({ path }) => [
|
|
398
|
+
path("/", () => <div>Index</div>, { name: "index" }),
|
|
399
|
+
path("/:slug", () => <div>Detail</div>, { name: "detail" }),
|
|
400
|
+
]);
|
|
401
|
+
|
|
402
|
+
const manifest = new Map<string, EntryData>();
|
|
403
|
+
const patterns = new Map<string, string>();
|
|
404
|
+
|
|
405
|
+
RSCRouterContext.run(
|
|
406
|
+
{
|
|
407
|
+
manifest,
|
|
408
|
+
patterns,
|
|
409
|
+
namespace: "test",
|
|
410
|
+
parent: null,
|
|
411
|
+
counters: {},
|
|
412
|
+
},
|
|
413
|
+
() => {
|
|
414
|
+
const urlPatterns = urls(({ include }) => [
|
|
415
|
+
include("/blog", contentPatterns, { name: "blog" }),
|
|
416
|
+
include("/news", contentPatterns, { name: "news" }),
|
|
417
|
+
]);
|
|
418
|
+
|
|
419
|
+
urlPatterns.handler();
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
|
|
423
|
+
// Both should have their own prefixed routes
|
|
424
|
+
expect(manifest.has("blog.index")).toBe(true);
|
|
425
|
+
expect(manifest.has("blog.detail")).toBe(true);
|
|
426
|
+
expect(manifest.has("news.index")).toBe(true);
|
|
427
|
+
expect(manifest.has("news.detail")).toBe(true);
|
|
428
|
+
|
|
429
|
+
// With different URL prefixes
|
|
430
|
+
expect(patterns.get("blog.index")).toBe("/blog");
|
|
431
|
+
expect(patterns.get("news.index")).toBe("/news");
|
|
432
|
+
expect(patterns.get("blog.detail")).toBe("/blog/:slug");
|
|
433
|
+
expect(patterns.get("news.detail")).toBe("/news/:slug");
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
});
|