@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.12 → 3.2.0-ultramodern.120
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/cjs/cli/index.js +47 -9
- package/dist/cjs/cli/routeSplitting.js +87 -0
- package/dist/cjs/cli/tanstackTypes.js +230 -63
- package/dist/cjs/cli.js +12 -8
- package/dist/cjs/runtime/DefaultNotFound.js +9 -5
- package/dist/cjs/runtime/basepathRewrite.js +12 -8
- package/dist/cjs/runtime/dataMutation.js +9 -5
- package/dist/cjs/runtime/hooks.js +9 -5
- package/dist/cjs/runtime/hydrationBoundary.js +48 -0
- package/dist/cjs/runtime/index.js +330 -74
- package/dist/cjs/runtime/lifecycle.js +15 -11
- package/dist/cjs/runtime/outlet.js +58 -0
- package/dist/cjs/runtime/plugin.js +203 -98
- package/dist/cjs/runtime/plugin.node.js +38 -16
- package/dist/cjs/runtime/plugin.worker.js +53 -0
- package/dist/cjs/runtime/prefetchLink.js +10 -6
- package/dist/cjs/runtime/routeTree.js +81 -17
- package/dist/cjs/runtime/rsc/ClientSlot.js +9 -5
- package/dist/cjs/runtime/rsc/CompositeComponent.js +9 -5
- package/dist/cjs/runtime/rsc/ReplayableStream.js +14 -9
- package/dist/cjs/runtime/rsc/RscNodeRenderer.js +9 -5
- package/dist/cjs/runtime/rsc/SlotContext.js +9 -5
- package/dist/cjs/runtime/rsc/client.js +9 -5
- package/dist/cjs/runtime/rsc/createRscProxy.js +9 -5
- package/dist/cjs/runtime/rsc/index.js +9 -5
- package/dist/cjs/runtime/rsc/payloadRouter.js +9 -5
- package/dist/cjs/runtime/rsc/server.js +9 -5
- package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +9 -5
- package/dist/cjs/runtime/rsc/symbols.js +20 -15
- package/dist/cjs/runtime/types.js +31 -1
- package/dist/cjs/runtime/utils.js +9 -5
- package/dist/cjs/runtime.js +9 -5
- package/dist/esm/cli/index.mjs +28 -6
- package/dist/esm/cli/routeSplitting.mjs +43 -0
- package/dist/esm/cli/tanstackTypes.mjs +219 -59
- package/dist/esm/runtime/hydrationBoundary.mjs +10 -0
- package/dist/esm/runtime/index.mjs +3 -2
- package/dist/esm/runtime/outlet.mjs +17 -0
- package/dist/esm/runtime/plugin.mjs +197 -96
- package/dist/esm/runtime/plugin.node.mjs +30 -12
- package/dist/esm/runtime/plugin.worker.mjs +1 -0
- package/dist/esm/runtime/prefetchLink.mjs +1 -1
- package/dist/esm/runtime/routeTree.mjs +73 -13
- package/dist/esm/runtime/types.mjs +7 -0
- package/dist/esm-node/cli/index.mjs +28 -6
- package/dist/esm-node/cli/routeSplitting.mjs +44 -0
- package/dist/esm-node/cli/tanstackTypes.mjs +219 -59
- package/dist/esm-node/runtime/hydrationBoundary.mjs +11 -0
- package/dist/esm-node/runtime/index.mjs +3 -2
- package/dist/esm-node/runtime/outlet.mjs +18 -0
- package/dist/esm-node/runtime/plugin.mjs +197 -96
- package/dist/esm-node/runtime/plugin.node.mjs +30 -12
- package/dist/esm-node/runtime/plugin.worker.mjs +2 -0
- package/dist/esm-node/runtime/prefetchLink.mjs +1 -1
- package/dist/esm-node/runtime/routeTree.mjs +73 -13
- package/dist/esm-node/runtime/types.mjs +7 -0
- package/dist/types/cli/index.d.ts +7 -1
- package/dist/types/cli/routeSplitting.d.ts +29 -0
- package/dist/types/cli/tanstackTypes.d.ts +9 -0
- package/dist/types/runtime/hooks.d.ts +9 -24
- package/dist/types/runtime/hydrationBoundary.d.ts +2 -0
- package/dist/types/runtime/index.d.ts +5 -2
- package/dist/types/runtime/outlet.d.ts +2 -0
- package/dist/types/runtime/plugin.d.ts +1 -1
- package/dist/types/runtime/plugin.node.d.ts +1 -1
- package/dist/types/runtime/plugin.worker.d.ts +1 -0
- package/dist/types/runtime/types.d.ts +7 -0
- package/package.json +20 -20
- package/src/cli/index.ts +59 -2
- package/src/cli/routeSplitting.ts +81 -0
- package/src/cli/tanstackTypes.ts +347 -67
- package/src/runtime/hydrationBoundary.tsx +12 -0
- package/src/runtime/index.tsx +107 -2
- package/src/runtime/outlet.tsx +48 -0
- package/src/runtime/plugin.node.tsx +58 -8
- package/src/runtime/plugin.tsx +372 -157
- package/src/runtime/plugin.worker.tsx +4 -0
- package/src/runtime/prefetchLink.tsx +1 -1
- package/src/runtime/routeTree.ts +194 -23
- package/src/runtime/ssr-shim.d.ts +1 -3
- package/src/runtime/types.ts +13 -0
- package/tests/router/cli.test.ts +315 -0
- package/tests/router/fastDefaults.test.ts +25 -0
- package/tests/router/hydrationBoundary.test.tsx +23 -0
- package/tests/router/prefetchLink.test.tsx +43 -7
- package/tests/router/routeTree.test.ts +416 -1
- package/tests/router/tanstackTypes.test.ts +415 -1
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { RouteObject } from '@modern-js/runtime-utils/router';
|
|
2
2
|
import type { NestedRoute } from '@modern-js/types';
|
|
3
3
|
import { createMemoryHistory } from '@tanstack/history';
|
|
4
|
-
import { createRouter } from '@tanstack/react-router';
|
|
4
|
+
import { createRouter, Outlet, RouterProvider } from '@tanstack/react-router';
|
|
5
|
+
import type { ComponentType } from 'react';
|
|
6
|
+
import { createElement, lazy } from 'react';
|
|
7
|
+
import { renderToStaticMarkup, renderToString } from 'react-dom/server';
|
|
8
|
+
import { Outlet as PublicOutlet } from '../../src/runtime';
|
|
9
|
+
import { Outlet as ModernOutlet } from '../../src/runtime/outlet';
|
|
5
10
|
import {
|
|
6
11
|
createRouteTreeFromModernRoutes,
|
|
7
12
|
createRouteTreeFromRouteObjects,
|
|
13
|
+
getModernRouteIdsFromMatches,
|
|
8
14
|
} from '../../src/runtime/routeTree';
|
|
9
15
|
import { __setTanstackRscPayloadDecoderForTests } from '../../src/runtime/rsc/payloadRouter';
|
|
10
16
|
import { createRouteObjectsFromConfig } from '../../src/runtime/utils';
|
|
@@ -23,6 +29,9 @@ type TestRouteObject = RouteObject & {
|
|
|
23
29
|
hasLoader?: boolean;
|
|
24
30
|
inValidSSRRoute?: boolean;
|
|
25
31
|
isClientComponent?: boolean;
|
|
32
|
+
lazyImport?: () => Promise<unknown>;
|
|
33
|
+
loaderDeps?: unknown;
|
|
34
|
+
validateSearch?: unknown;
|
|
26
35
|
};
|
|
27
36
|
|
|
28
37
|
type TestNestedRoute = NestedRoute & {
|
|
@@ -30,6 +39,8 @@ type TestNestedRoute = NestedRoute & {
|
|
|
30
39
|
hasAction?: boolean;
|
|
31
40
|
hasClientLoader?: boolean;
|
|
32
41
|
hasLoader?: boolean;
|
|
42
|
+
loaderDeps?: unknown;
|
|
43
|
+
validateSearch?: unknown;
|
|
33
44
|
};
|
|
34
45
|
|
|
35
46
|
type ShouldRevalidateArgs = {
|
|
@@ -48,12 +59,21 @@ type ShouldReloadArgs = {
|
|
|
48
59
|
|
|
49
60
|
type TestRoute = {
|
|
50
61
|
options: {
|
|
62
|
+
component?: unknown;
|
|
51
63
|
shouldReload?: (args: ShouldReloadArgs) => boolean | undefined;
|
|
52
64
|
ssr?: boolean;
|
|
53
65
|
staticData: Record<string, unknown>;
|
|
66
|
+
loaderDeps?: unknown;
|
|
67
|
+
validateSearch?: unknown;
|
|
68
|
+
wrapInSuspense?: unknown;
|
|
54
69
|
};
|
|
55
70
|
};
|
|
56
71
|
|
|
72
|
+
type PreloadableTestComponent = {
|
|
73
|
+
load?: () => Promise<unknown>;
|
|
74
|
+
preload?: () => Promise<unknown>;
|
|
75
|
+
};
|
|
76
|
+
|
|
57
77
|
type TestRouter = {
|
|
58
78
|
load: () => Promise<void>;
|
|
59
79
|
looseRoutesById: Partial<Record<string, TestRoute>>;
|
|
@@ -107,6 +127,16 @@ function getLooseRouteByModernRouteId(
|
|
|
107
127
|
return route;
|
|
108
128
|
}
|
|
109
129
|
|
|
130
|
+
function countCompletedSuspenseBoundaries(markup: string) {
|
|
131
|
+
return markup.match(/<!--\$-->/g)?.length || 0;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
describe('tanstack runtime public exports', () => {
|
|
135
|
+
test('exports the Modern Outlet implementation from the runtime entrypoint', () => {
|
|
136
|
+
expect(PublicOutlet).toBe(ModernOutlet);
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
|
|
110
140
|
describe('tanstack route tree from RouteObject[]', () => {
|
|
111
141
|
afterEach(() => {
|
|
112
142
|
__setTanstackRscPayloadDecoderForTests();
|
|
@@ -145,6 +175,151 @@ describe('tanstack route tree from RouteObject[]', () => {
|
|
|
145
175
|
expect(userMatch?.loaderData).toEqual({ id: '123' });
|
|
146
176
|
});
|
|
147
177
|
|
|
178
|
+
test('does not force Suspense wrappers for ordinary generated routes', async () => {
|
|
179
|
+
const routes: RouteObject[] = [
|
|
180
|
+
{
|
|
181
|
+
id: 'root',
|
|
182
|
+
path: '/',
|
|
183
|
+
Component: () => createElement(Outlet),
|
|
184
|
+
children: [
|
|
185
|
+
{
|
|
186
|
+
id: 'plain',
|
|
187
|
+
path: 'plain',
|
|
188
|
+
Component: () => null,
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
},
|
|
192
|
+
];
|
|
193
|
+
|
|
194
|
+
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
195
|
+
const router = await loadRouteTree(routeTree, '/plain');
|
|
196
|
+
|
|
197
|
+
expect(routeTree.options.wrapInSuspense).toBeUndefined();
|
|
198
|
+
expect(
|
|
199
|
+
getLooseRoute(router, '/plain').options.wrapInSuspense,
|
|
200
|
+
).toBeUndefined();
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test('renders Modern Outlet through TanStack native outlet', async () => {
|
|
204
|
+
const routes: RouteObject[] = [
|
|
205
|
+
{
|
|
206
|
+
id: 'root',
|
|
207
|
+
path: '/',
|
|
208
|
+
Component: () =>
|
|
209
|
+
createElement('section', null, createElement(ModernOutlet)),
|
|
210
|
+
children: [
|
|
211
|
+
{
|
|
212
|
+
id: 'plain',
|
|
213
|
+
path: 'plain',
|
|
214
|
+
Component: () => createElement('main', null, 'Plain child route'),
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
},
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
221
|
+
const router = await loadRouteTree(routeTree, '/plain');
|
|
222
|
+
const markup = renderToString(
|
|
223
|
+
createElement(RouterProvider, { router } as never),
|
|
224
|
+
);
|
|
225
|
+
const suspenseBoundaryCount = countCompletedSuspenseBoundaries(markup);
|
|
226
|
+
|
|
227
|
+
expect(markup).toContain('Plain child route');
|
|
228
|
+
expect(suspenseBoundaryCount).toBe(1);
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
test('resolves matched Modern route ids from TanStack route registry fallback', () => {
|
|
232
|
+
const router = {
|
|
233
|
+
state: {
|
|
234
|
+
matches: [
|
|
235
|
+
{ routeId: '__root__' },
|
|
236
|
+
{ routeId: '/$lang' },
|
|
237
|
+
{ routeId: '/$lang/tractors' },
|
|
238
|
+
{
|
|
239
|
+
route: {
|
|
240
|
+
options: {
|
|
241
|
+
staticData: {
|
|
242
|
+
modernRouteId: '(lang)/stores/page',
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
},
|
|
246
|
+
routeId: '/$lang/stores',
|
|
247
|
+
},
|
|
248
|
+
],
|
|
249
|
+
},
|
|
250
|
+
routesById: {
|
|
251
|
+
__root__: {
|
|
252
|
+
options: {
|
|
253
|
+
staticData: {
|
|
254
|
+
modernRouteId: 'layout',
|
|
255
|
+
},
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
'/$lang': {
|
|
259
|
+
options: {
|
|
260
|
+
staticData: {
|
|
261
|
+
modernRouteId: '(lang)/page',
|
|
262
|
+
},
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
'/$lang/tractors': {
|
|
266
|
+
options: {
|
|
267
|
+
staticData: {
|
|
268
|
+
modernRouteId: '(lang)/tractors/page',
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
},
|
|
272
|
+
},
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
expect(getModernRouteIdsFromMatches(router as never)).toEqual([
|
|
276
|
+
'layout',
|
|
277
|
+
'(lang)/page',
|
|
278
|
+
'(lang)/tractors/page',
|
|
279
|
+
'(lang)/stores/page',
|
|
280
|
+
]);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
test('preserves TanStack search contracts from RouteObject routes', () => {
|
|
284
|
+
const rootValidateSearch = (search: unknown) => ({ root: search });
|
|
285
|
+
const rootLoaderDeps = ({ search }: { search: unknown }) => ({ search });
|
|
286
|
+
const childValidateSearch = (search: unknown) => ({ child: search });
|
|
287
|
+
const childLoaderDeps = ({ search }: { search: unknown }) => ({ search });
|
|
288
|
+
const routes: TestRouteObject[] = [
|
|
289
|
+
{
|
|
290
|
+
id: 'root',
|
|
291
|
+
path: '/',
|
|
292
|
+
validateSearch: rootValidateSearch,
|
|
293
|
+
loaderDeps: rootLoaderDeps,
|
|
294
|
+
Component: () => null,
|
|
295
|
+
children: [
|
|
296
|
+
{
|
|
297
|
+
id: 'search',
|
|
298
|
+
path: 'search',
|
|
299
|
+
validateSearch: childValidateSearch,
|
|
300
|
+
loaderDeps: childLoaderDeps,
|
|
301
|
+
Component: () => null,
|
|
302
|
+
},
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
];
|
|
306
|
+
|
|
307
|
+
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
308
|
+
const router = createRouter({
|
|
309
|
+
routeTree,
|
|
310
|
+
history: createMemoryHistory({
|
|
311
|
+
initialEntries: ['/search'],
|
|
312
|
+
}),
|
|
313
|
+
context: {},
|
|
314
|
+
}) as unknown as TestRouter;
|
|
315
|
+
const searchRoute = getLooseRoute(router, '/search');
|
|
316
|
+
|
|
317
|
+
expect(routeTree.options.validateSearch).toBe(rootValidateSearch);
|
|
318
|
+
expect(routeTree.options.loaderDeps).toBe(rootLoaderDeps);
|
|
319
|
+
expect(searchRoute.options.validateSearch).toBe(childValidateSearch);
|
|
320
|
+
expect(searchRoute.options.loaderDeps).toBe(childLoaderDeps);
|
|
321
|
+
});
|
|
322
|
+
|
|
148
323
|
test('uses TanStack route ids when loading RSC payload route data', async () => {
|
|
149
324
|
const rootLoader = rstest.fn(() => ({ source: 'modern-root' }));
|
|
150
325
|
const userLoader = rstest.fn(() => ({ source: 'modern-user' }));
|
|
@@ -234,6 +409,145 @@ describe('tanstack route tree from RouteObject[]', () => {
|
|
|
234
409
|
expect(splatParamValue).toBe('a/b/c');
|
|
235
410
|
});
|
|
236
411
|
|
|
412
|
+
test('preloads lazy Modern route components for server rendering', async () => {
|
|
413
|
+
const LazyRouteComponent = () =>
|
|
414
|
+
createElement('main', null, 'Lazy route ready');
|
|
415
|
+
const lazyImport = rstest.fn(async () => ({
|
|
416
|
+
default: LazyRouteComponent,
|
|
417
|
+
}));
|
|
418
|
+
const routes: TestRouteObject[] = [
|
|
419
|
+
{
|
|
420
|
+
id: 'root',
|
|
421
|
+
path: '/',
|
|
422
|
+
Component: () => null,
|
|
423
|
+
children: [
|
|
424
|
+
{
|
|
425
|
+
id: 'lazy',
|
|
426
|
+
path: 'lazy',
|
|
427
|
+
Component: lazy(lazyImport),
|
|
428
|
+
lazyImport,
|
|
429
|
+
},
|
|
430
|
+
],
|
|
431
|
+
},
|
|
432
|
+
];
|
|
433
|
+
|
|
434
|
+
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
435
|
+
const router = await loadRouteTree(routeTree, '/lazy');
|
|
436
|
+
const lazyRoute = getLooseRoute(router, '/lazy');
|
|
437
|
+
const component = lazyRoute.options.component as ComponentType & {
|
|
438
|
+
preload?: () => Promise<unknown>;
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
await component.preload?.();
|
|
442
|
+
|
|
443
|
+
expect(lazyImport).toHaveBeenCalled();
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
test('renders preloaded lazy child routes through TanStack router SSR', async () => {
|
|
447
|
+
const LazyRouteComponent = () =>
|
|
448
|
+
createElement('main', null, 'Lazy child route ready');
|
|
449
|
+
const lazyImport = rstest.fn(async () => ({
|
|
450
|
+
default: LazyRouteComponent,
|
|
451
|
+
}));
|
|
452
|
+
const routes: TestRouteObject[] = [
|
|
453
|
+
{
|
|
454
|
+
id: 'root',
|
|
455
|
+
path: '/',
|
|
456
|
+
Component: () => createElement('section', null, createElement(Outlet)),
|
|
457
|
+
children: [
|
|
458
|
+
{
|
|
459
|
+
id: 'lazy',
|
|
460
|
+
path: 'lazy',
|
|
461
|
+
Component: lazy(lazyImport),
|
|
462
|
+
lazyImport,
|
|
463
|
+
},
|
|
464
|
+
],
|
|
465
|
+
},
|
|
466
|
+
];
|
|
467
|
+
|
|
468
|
+
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
469
|
+
const router = await loadRouteTree(routeTree, '/lazy');
|
|
470
|
+
const lazyRoute = getLooseRoute(router, '/lazy');
|
|
471
|
+
const lazyComponent = lazyRoute.options
|
|
472
|
+
.component as PreloadableTestComponent;
|
|
473
|
+
|
|
474
|
+
expect(
|
|
475
|
+
renderToStaticMarkup(createElement(RouterProvider, { router } as never)),
|
|
476
|
+
).toContain('Lazy child route ready');
|
|
477
|
+
expect(typeof lazyComponent.load).toBe('function');
|
|
478
|
+
expect(typeof lazyComponent.preload).toBe('function');
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
test('exposes load-only Modern route components through TanStack preload', async () => {
|
|
482
|
+
const load = rstest.fn(async () => 'route chunk loaded');
|
|
483
|
+
const LoadOnlyRouteComponent = (() =>
|
|
484
|
+
createElement('main', null, 'Load-only route ready')) as ComponentType & {
|
|
485
|
+
load?: () => Promise<unknown>;
|
|
486
|
+
preload?: () => Promise<unknown>;
|
|
487
|
+
};
|
|
488
|
+
LoadOnlyRouteComponent.load = load;
|
|
489
|
+
const routes: TestRouteObject[] = [
|
|
490
|
+
{
|
|
491
|
+
id: 'root',
|
|
492
|
+
path: '/',
|
|
493
|
+
Component: () => createElement('section', null, createElement(Outlet)),
|
|
494
|
+
children: [
|
|
495
|
+
{
|
|
496
|
+
id: 'load-only',
|
|
497
|
+
path: 'load-only',
|
|
498
|
+
Component: LoadOnlyRouteComponent,
|
|
499
|
+
},
|
|
500
|
+
],
|
|
501
|
+
},
|
|
502
|
+
];
|
|
503
|
+
|
|
504
|
+
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
505
|
+
const router = await loadRouteTree(routeTree, '/load-only');
|
|
506
|
+
const loadOnlyRoute = getLooseRoute(router, '/load-only');
|
|
507
|
+
const loadOnlyComponent = loadOnlyRoute.options
|
|
508
|
+
.component as PreloadableTestComponent;
|
|
509
|
+
|
|
510
|
+
expect(typeof loadOnlyComponent.load).toBe('function');
|
|
511
|
+
expect(typeof loadOnlyComponent.preload).toBe('function');
|
|
512
|
+
expect(load).toHaveBeenCalledTimes(1);
|
|
513
|
+
await loadOnlyComponent.preload?.();
|
|
514
|
+
expect(load).toHaveBeenCalledTimes(2);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
test('unwraps nested ESM route module defaults before server rendering', async () => {
|
|
518
|
+
const LazyRouteComponent = () =>
|
|
519
|
+
createElement('main', null, 'Nested lazy child route ready');
|
|
520
|
+
const lazyImport = rstest.fn(async () => ({
|
|
521
|
+
default: {
|
|
522
|
+
default: LazyRouteComponent,
|
|
523
|
+
},
|
|
524
|
+
}));
|
|
525
|
+
const routes: TestRouteObject[] = [
|
|
526
|
+
{
|
|
527
|
+
id: 'root',
|
|
528
|
+
path: '/',
|
|
529
|
+
Component: () => createElement('section', null, createElement(Outlet)),
|
|
530
|
+
children: [
|
|
531
|
+
{
|
|
532
|
+
id: 'lazy',
|
|
533
|
+
path: 'lazy',
|
|
534
|
+
Component: lazy(
|
|
535
|
+
lazyImport as () => Promise<{ default: ComponentType }>,
|
|
536
|
+
),
|
|
537
|
+
lazyImport,
|
|
538
|
+
},
|
|
539
|
+
],
|
|
540
|
+
},
|
|
541
|
+
];
|
|
542
|
+
|
|
543
|
+
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
544
|
+
const router = await loadRouteTree(routeTree, '/lazy');
|
|
545
|
+
|
|
546
|
+
expect(
|
|
547
|
+
renderToStaticMarkup(createElement(RouterProvider, { router } as never)),
|
|
548
|
+
).toContain('Nested lazy child route ready');
|
|
549
|
+
});
|
|
550
|
+
|
|
237
551
|
test('preserves route handle and maps shouldRevalidate to shouldReload', async () => {
|
|
238
552
|
const shouldRevalidate = rstest.fn(({ nextUrl }: ShouldRevalidateArgs) =>
|
|
239
553
|
nextUrl.pathname.endsWith('/456'),
|
|
@@ -417,6 +731,107 @@ describe('tanstack route tree from RouteObject[]', () => {
|
|
|
417
731
|
});
|
|
418
732
|
});
|
|
419
733
|
|
|
734
|
+
test('preserves TanStack search contracts from Modern generated routes', () => {
|
|
735
|
+
const rootValidateSearch = (search: unknown) => ({ root: search });
|
|
736
|
+
const rootLoaderDeps = ({ search }: { search: unknown }) => ({ search });
|
|
737
|
+
const childValidateSearch = (search: unknown) => ({ child: search });
|
|
738
|
+
const childLoaderDeps = ({ search }: { search: unknown }) => ({ search });
|
|
739
|
+
const modernRoutes: TestNestedRoute[] = [
|
|
740
|
+
{
|
|
741
|
+
type: 'nested',
|
|
742
|
+
origin: 'config',
|
|
743
|
+
id: 'root',
|
|
744
|
+
isRoot: true,
|
|
745
|
+
validateSearch: rootValidateSearch,
|
|
746
|
+
loaderDeps: rootLoaderDeps,
|
|
747
|
+
children: [
|
|
748
|
+
{
|
|
749
|
+
type: 'nested',
|
|
750
|
+
origin: 'config',
|
|
751
|
+
id: 'search',
|
|
752
|
+
path: 'search',
|
|
753
|
+
validateSearch: childValidateSearch,
|
|
754
|
+
loaderDeps: childLoaderDeps,
|
|
755
|
+
},
|
|
756
|
+
],
|
|
757
|
+
},
|
|
758
|
+
];
|
|
759
|
+
const routeTree = createRouteTreeFromModernRoutes(modernRoutes);
|
|
760
|
+
const router = createRouter({
|
|
761
|
+
routeTree,
|
|
762
|
+
history: createMemoryHistory({
|
|
763
|
+
initialEntries: ['/search'],
|
|
764
|
+
}),
|
|
765
|
+
context: {},
|
|
766
|
+
}) as unknown as TestRouter;
|
|
767
|
+
const searchRoute = getLooseRouteByModernRouteId(router, 'search');
|
|
768
|
+
|
|
769
|
+
expect(routeTree.options.validateSearch).toBe(rootValidateSearch);
|
|
770
|
+
expect(routeTree.options.loaderDeps).toBe(rootLoaderDeps);
|
|
771
|
+
expect(searchRoute.options.validateSearch).toBe(childValidateSearch);
|
|
772
|
+
expect(searchRoute.options.loaderDeps).toBe(childLoaderDeps);
|
|
773
|
+
});
|
|
774
|
+
|
|
775
|
+
test('does not force Suspense wrappers for ordinary Modern routes', async () => {
|
|
776
|
+
const modernRoutes: TestNestedRoute[] = [
|
|
777
|
+
{
|
|
778
|
+
type: 'nested',
|
|
779
|
+
origin: 'config',
|
|
780
|
+
id: 'root',
|
|
781
|
+
isRoot: true,
|
|
782
|
+
component: () => createElement(Outlet),
|
|
783
|
+
children: [
|
|
784
|
+
{
|
|
785
|
+
type: 'nested',
|
|
786
|
+
origin: 'config',
|
|
787
|
+
id: 'plain',
|
|
788
|
+
path: 'plain',
|
|
789
|
+
component: () => null,
|
|
790
|
+
},
|
|
791
|
+
],
|
|
792
|
+
},
|
|
793
|
+
];
|
|
794
|
+
|
|
795
|
+
const routeTree = createRouteTreeFromModernRoutes(modernRoutes);
|
|
796
|
+
const router = await loadRouteTree(routeTree, '/plain');
|
|
797
|
+
const plain = getLooseRouteByModernRouteId(router, 'plain');
|
|
798
|
+
|
|
799
|
+
expect(routeTree.options.wrapInSuspense).toBeUndefined();
|
|
800
|
+
expect(plain.options.wrapInSuspense).toBeUndefined();
|
|
801
|
+
});
|
|
802
|
+
|
|
803
|
+
test('renders Modern generated Outlet through TanStack native outlet', async () => {
|
|
804
|
+
const modernRoutes: TestNestedRoute[] = [
|
|
805
|
+
{
|
|
806
|
+
type: 'nested',
|
|
807
|
+
origin: 'config',
|
|
808
|
+
id: 'root',
|
|
809
|
+
isRoot: true,
|
|
810
|
+
component: () =>
|
|
811
|
+
createElement('section', null, createElement(ModernOutlet)),
|
|
812
|
+
children: [
|
|
813
|
+
{
|
|
814
|
+
type: 'nested',
|
|
815
|
+
origin: 'config',
|
|
816
|
+
id: 'plain',
|
|
817
|
+
path: 'plain',
|
|
818
|
+
component: () => createElement('main', null, 'Plain child route'),
|
|
819
|
+
},
|
|
820
|
+
],
|
|
821
|
+
},
|
|
822
|
+
];
|
|
823
|
+
|
|
824
|
+
const routeTree = createRouteTreeFromModernRoutes(modernRoutes);
|
|
825
|
+
const router = await loadRouteTree(routeTree, '/plain');
|
|
826
|
+
const markup = renderToString(
|
|
827
|
+
createElement(RouterProvider, { router } as never),
|
|
828
|
+
);
|
|
829
|
+
const suspenseBoundaryCount = countCompletedSuspenseBoundaries(markup);
|
|
830
|
+
|
|
831
|
+
expect(markup).toContain('Plain child route');
|
|
832
|
+
expect(suspenseBoundaryCount).toBe(1);
|
|
833
|
+
});
|
|
834
|
+
|
|
420
835
|
test('preserves Modern generated client route metadata', () => {
|
|
421
836
|
const modernRoutes: TestNestedRoute[] = [
|
|
422
837
|
{
|