@bleedingdev/modern-js-plugin-tanstack 3.8.3-ultramodern.6 → 3.9.0-ultramodern.10
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 +9 -0
- package/dist/cjs/cli/tanstackTypes/source.js +3 -1
- package/dist/cjs/runtime/basepathRewrite.js +13 -6
- package/dist/cjs/runtime/lifecycle.js +7 -7
- package/dist/cjs/runtime/plugin.js +1 -1
- package/dist/cjs/runtime/plugin.node.js +5 -1
- package/dist/cjs/runtime/prefetchLink.js +2 -7
- package/dist/cjs/runtime/register.js +0 -2
- package/dist/cjs/runtime/routeTree/construction.js +2 -0
- package/dist/cjs/runtime/router.js +25 -9
- package/dist/cjs/runtime/state.js +2 -2
- package/dist/cjs/runtime/utils.js +2 -0
- package/dist/esm/cli/index.mjs +13 -5
- package/dist/esm/cli/tanstackTypes/source.mjs +3 -1
- package/dist/esm/runtime/basepathRewrite.mjs +10 -3
- package/dist/esm/runtime/lifecycle.mjs +1 -1
- package/dist/esm/runtime/plugin.mjs +1 -1
- package/dist/esm/runtime/plugin.node.mjs +5 -1
- package/dist/esm/runtime/prefetchLink.mjs +1 -6
- package/dist/esm/runtime/register.mjs +0 -2
- package/dist/esm/runtime/routeTree/construction.mjs +2 -0
- package/dist/esm/runtime/router.mjs +27 -11
- package/dist/esm/runtime/state.mjs +1 -1
- package/dist/esm/runtime/utils.mjs +2 -0
- package/dist/esm-node/cli/index.mjs +15 -5
- package/dist/esm-node/cli/tanstackTypes/source.mjs +3 -1
- package/dist/esm-node/runtime/basepathRewrite.mjs +10 -3
- package/dist/esm-node/runtime/lifecycle.mjs +1 -1
- package/dist/esm-node/runtime/plugin.mjs +1 -1
- package/dist/esm-node/runtime/plugin.node.mjs +5 -1
- package/dist/esm-node/runtime/prefetchLink.mjs +1 -6
- package/dist/esm-node/runtime/register.mjs +0 -2
- package/dist/esm-node/runtime/routeTree/construction.mjs +2 -0
- package/dist/esm-node/runtime/router.mjs +27 -11
- package/dist/esm-node/runtime/state.mjs +1 -1
- package/dist/esm-node/runtime/utils.mjs +2 -0
- package/dist/types/cli/index.d.ts +5 -5
- package/dist/types/cli/tanstackTypes/shared.d.ts +1 -1
- package/dist/types/cli/tanstackTypes.d.ts +3 -3
- package/dist/types/cli.d.ts +2 -2
- package/dist/types/runtime/basepathRewrite.d.ts +2 -1
- package/dist/types/runtime/dataMutation.d.ts +3 -3
- package/dist/types/runtime/index.d.ts +14 -14
- package/dist/types/runtime/lifecycle.d.ts +9 -2
- package/dist/types/runtime/loaderBridge.d.ts +2 -2
- package/dist/types/runtime/plugin.d.ts +2 -2
- package/dist/types/runtime/plugin.node.d.ts +2 -2
- package/dist/types/runtime/plugin.worker.d.ts +1 -1
- package/dist/types/runtime/pluginShared.d.ts +2 -2
- package/dist/types/runtime/register.d.ts +1 -9
- package/dist/types/runtime/routeTree/construction.d.ts +1 -1
- package/dist/types/runtime/routeTree/index.d.ts +2 -2
- package/dist/types/runtime/routeTree/loaders.d.ts +1 -1
- package/dist/types/runtime/routeTree/tanstackRoutes.d.ts +1 -1
- package/dist/types/runtime/router.d.ts +2 -13
- package/dist/types/runtime/rsc/CompositeComponent.d.ts +1 -1
- package/dist/types/runtime/rsc/RscNodeRenderer.d.ts +1 -1
- package/dist/types/runtime/rsc/client.d.ts +4 -4
- package/dist/types/runtime/rsc/createRscProxy.d.ts +1 -1
- package/dist/types/runtime/rsc/payloadFetch.d.ts +1 -1
- package/dist/types/runtime/rsc/payloadRouter.d.ts +2 -2
- package/dist/types/runtime/rsc/payloadStorage.d.ts +1 -1
- package/dist/types/runtime/rsc/server.d.ts +3 -3
- package/dist/types/runtime/rsc/shared.d.ts +1 -1
- package/dist/types/runtime/rsc/slotUsageSanitizer.d.ts +1 -1
- package/dist/types/runtime/ssrManagedTags.d.ts +1 -1
- package/dist/types/runtime/ssrPreload.d.ts +1 -1
- package/dist/types/runtime/state.d.ts +1 -1
- package/dist/types/runtime/submitAction.d.ts +1 -1
- package/dist/types/runtime/types.d.ts +2 -1
- package/dist/types/runtime/utils.d.ts +1 -1
- package/dist/types/runtime.d.ts +1 -1
- package/package.json +19 -16
- package/rstest.config.mts +1 -24
- package/src/cli/index.ts +10 -0
- package/src/cli/tanstackTypes/source.ts +3 -1
- package/src/runtime/basepathRewrite.ts +11 -2
- package/src/runtime/lifecycle.ts +14 -3
- package/src/runtime/plugin.node.tsx +9 -2
- package/src/runtime/plugin.tsx +5 -1
- package/src/runtime/prefetchLink.tsx +1 -23
- package/src/runtime/register.ts +1 -12
- package/src/runtime/routeTree/construction.ts +2 -0
- package/src/runtime/router.ts +25 -40
- package/src/runtime/state.ts +1 -1
- package/src/runtime/types.ts +2 -2
- package/src/runtime/utils.tsx +3 -1
- package/tests/router/cli.test.ts +148 -824
- package/tests/router/dataMutation.test.tsx +17 -85
- package/tests/router/flightSerialization.roundtrip.test.tsx +9 -6
- package/tests/router/generateRouteArtifacts.test.ts +68 -28
- package/tests/router/loaderBridge.test.ts +0 -25
- package/tests/router/localisedIdentity.test.tsx +95 -0
- package/tests/router/prefetchLink.test.tsx +17 -21
- package/tests/router/prefetchLinkPreload.test.tsx +11 -31
- package/tests/router/routeHooks.test.ts +41 -105
- package/tests/router/routeTree.test.ts +144 -836
- package/tests/router/rsc.test.tsx +131 -166
- package/tests/router/serverPlugin.test.ts +43 -1
- package/tests/router/tanstackTypes.test.ts +109 -973
- package/tests/realm-isolation.test.ts +0 -114
- package/tests/router/fastDefaults.test.ts +0 -32
- package/tests/router/fixtures/legacyRuntimeRouter.ts +0 -10
- package/tests/router/fixtures/rscDecoderSecurityChild.cjs +0 -162
- package/tests/router/hooks.test.ts +0 -40
- package/tests/router/hydrationBoundary.test.tsx +0 -23
- package/tests/router/packageSurface.test.ts +0 -24
- package/tests/router/preloadRedirect.test.ts +0 -64
- package/tests/router/register.test.ts +0 -8
- package/tests/router/routerCompatibility.test.ts +0 -239
- package/tests/router/rscDecoderSecurity.test.ts +0 -113
- package/tests/router/rscPayloadRouterMatrix.test.ts +0 -197
- /package/dist/esm/{rslib-runtime.mjs → rslib-runtime~1.mjs} +0 -0
- /package/dist/esm-node/{rslib-runtime.mjs → rslib-runtime~0.mjs} +0 -0
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
1
|
import type { RouteObject } from '@modern-js/runtime-utils/router';
|
|
4
|
-
import type { NestedRoute } from '@modern-js/types';
|
|
5
2
|
import { createMemoryHistory } from '@tanstack/history';
|
|
6
3
|
import {
|
|
7
4
|
createRouter,
|
|
@@ -12,107 +9,39 @@ import {
|
|
|
12
9
|
import type { ComponentType } from 'react';
|
|
13
10
|
import { createElement, lazy } from 'react';
|
|
14
11
|
import { renderToStaticMarkup, renderToString } from 'react-dom/server';
|
|
15
|
-
import * as TanstackRuntime from '../../src/runtime';
|
|
16
|
-
import { Outlet as PublicOutlet } from '../../src/runtime';
|
|
17
12
|
import { Outlet as ModernOutlet } from '../../src/runtime/outlet';
|
|
18
|
-
import {
|
|
19
|
-
createRouteTreeFromRouteObjects,
|
|
20
|
-
getModernRouteIdsFromMatches,
|
|
21
|
-
} from '../../src/runtime/routeTree';
|
|
22
|
-
import { __setTanstackRscPayloadDecoderForTests } from '../../src/runtime/rsc/payloadRouter';
|
|
23
|
-
import { createTanstackRouteObjectsFromConfig } from '../../src/runtime/utils';
|
|
13
|
+
import { createRouteTreeFromRouteObjects } from '../../src/runtime/routeTree';
|
|
24
14
|
|
|
25
|
-
type LoaderArgs = {
|
|
26
|
-
params: Record<string, string>;
|
|
27
|
-
};
|
|
15
|
+
type LoaderArgs = { params: Record<string, string> };
|
|
28
16
|
|
|
29
17
|
type TestRouteObject = RouteObject & {
|
|
30
18
|
children?: TestRouteObject[];
|
|
31
|
-
config?: {
|
|
32
|
-
handle?: Record<string, unknown>;
|
|
33
|
-
};
|
|
34
|
-
hasAction?: boolean;
|
|
35
|
-
hasClientLoader?: boolean;
|
|
36
|
-
hasLoader?: boolean;
|
|
37
|
-
inValidSSRRoute?: boolean;
|
|
38
|
-
isClientComponent?: boolean;
|
|
39
19
|
lazyImport?: () => Promise<unknown>;
|
|
40
|
-
loaderDeps?: unknown;
|
|
41
|
-
validateSearch?: unknown;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
type TestNestedRoute = NestedRoute & {
|
|
45
|
-
children?: TestNestedRoute[];
|
|
46
|
-
hasAction?: boolean;
|
|
47
|
-
hasClientLoader?: boolean;
|
|
48
|
-
hasLoader?: boolean;
|
|
49
|
-
loaderDeps?: unknown;
|
|
50
|
-
validateSearch?: unknown;
|
|
51
|
-
};
|
|
52
|
-
|
|
53
|
-
type ShouldRevalidateArgs = {
|
|
54
|
-
nextUrl: URL;
|
|
55
|
-
};
|
|
56
|
-
|
|
57
|
-
type ShouldReloadArgs = {
|
|
58
|
-
context: {
|
|
59
|
-
request: Request;
|
|
60
|
-
};
|
|
61
|
-
location: {
|
|
62
|
-
href: string;
|
|
63
|
-
};
|
|
64
|
-
params: Record<string, string>;
|
|
65
|
-
};
|
|
66
|
-
|
|
67
|
-
type TestRoute = {
|
|
68
|
-
options: {
|
|
69
|
-
component?: unknown;
|
|
70
|
-
shouldReload?: (args: ShouldReloadArgs) => boolean | undefined;
|
|
71
|
-
ssr?: boolean;
|
|
72
|
-
staticData: Record<string, unknown>;
|
|
73
|
-
loaderDeps?: unknown;
|
|
74
|
-
validateSearch?: unknown;
|
|
75
|
-
wrapInSuspense?: unknown;
|
|
76
|
-
};
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
type PreloadableTestComponent = {
|
|
80
|
-
load?: () => Promise<unknown>;
|
|
81
|
-
preload?: () => Promise<unknown>;
|
|
82
20
|
};
|
|
83
21
|
|
|
84
22
|
type TestRouter = {
|
|
85
23
|
_serverResult?:
|
|
86
|
-
| {
|
|
87
|
-
|
|
88
|
-
redirect: Response;
|
|
89
|
-
}
|
|
90
|
-
| {
|
|
91
|
-
type: 'render';
|
|
92
|
-
status: 200 | 404 | 500;
|
|
93
|
-
};
|
|
94
|
-
load: () => Promise<void>;
|
|
95
|
-
routesById: Partial<Record<string, TestRoute>>;
|
|
24
|
+
| { type: 'redirect'; redirect: Response }
|
|
25
|
+
| { type: 'render'; status: number };
|
|
96
26
|
state: {
|
|
97
|
-
matches: Array<{
|
|
98
|
-
error?: unknown;
|
|
99
|
-
loaderData?: unknown;
|
|
100
|
-
routeId: string;
|
|
101
|
-
}>;
|
|
27
|
+
matches: Array<{ error?: unknown; loaderData?: unknown; routeId: string }>;
|
|
102
28
|
};
|
|
103
29
|
};
|
|
104
30
|
|
|
105
|
-
|
|
31
|
+
const nullComponent = () => null;
|
|
32
|
+
|
|
33
|
+
const root = (
|
|
34
|
+
children: TestRouteObject[],
|
|
35
|
+
Component: RouteObject['Component'] = nullComponent,
|
|
36
|
+
): TestRouteObject[] => [{ id: 'root', path: '/', Component, children }];
|
|
106
37
|
|
|
107
38
|
async function loadRouteTree(
|
|
108
|
-
|
|
39
|
+
routes: TestRouteObject[],
|
|
109
40
|
pathname: string,
|
|
110
41
|
): Promise<TestRouter> {
|
|
111
42
|
const router = createRouter({
|
|
112
|
-
routeTree,
|
|
113
|
-
history: createMemoryHistory({
|
|
114
|
-
initialEntries: [pathname],
|
|
115
|
-
}),
|
|
43
|
+
routeTree: createRouteTreeFromRouteObjects(routes as RouteObject[]),
|
|
44
|
+
history: createMemoryHistory({ initialEntries: [pathname] }),
|
|
116
45
|
context: {
|
|
117
46
|
request: new Request(`http://localhost${pathname}`),
|
|
118
47
|
requestContext: {},
|
|
@@ -123,185 +52,85 @@ async function loadRouteTree(
|
|
|
123
52
|
return router as unknown as TestRouter;
|
|
124
53
|
}
|
|
125
54
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
if (!route) {
|
|
129
|
-
throw new Error(`Expected TanStack route ${id} to exist`);
|
|
130
|
-
}
|
|
131
|
-
return route;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function getRouteByModernRouteId(
|
|
135
|
-
router: TestRouter,
|
|
136
|
-
modernRouteId: string,
|
|
137
|
-
): TestRoute {
|
|
138
|
-
const route = Object.values(router.routesById).find(
|
|
139
|
-
route => route?.options.staticData?.modernRouteId === modernRouteId,
|
|
140
|
-
);
|
|
141
|
-
if (!route) {
|
|
142
|
-
throw new Error(`Expected Modern route ${modernRouteId} to exist`);
|
|
143
|
-
}
|
|
144
|
-
return route;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
function countCompletedSuspenseBoundaries(markup: string) {
|
|
148
|
-
return markup.match(/<!--\$-->/g)?.length || 0;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
describe('tanstack runtime public exports', () => {
|
|
152
|
-
test('exports the Modern Outlet implementation from the runtime entrypoint', () => {
|
|
153
|
-
expect(PublicOutlet).toBe(ModernOutlet);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test('does not expose the unowned composite RSC helper API', () => {
|
|
157
|
-
expect('CompositeComponent' in TanstackRuntime).toBe(false);
|
|
158
|
-
|
|
159
|
-
const packageJson = JSON.parse(
|
|
160
|
-
readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'),
|
|
161
|
-
) as {
|
|
162
|
-
exports: Record<string, unknown>;
|
|
163
|
-
typesVersions?: Record<string, Record<string, string[]>>;
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
expect(packageJson.exports['./runtime/rsc']).toBeUndefined();
|
|
167
|
-
expect(packageJson.exports['./runtime/rsc/client']).toBeDefined();
|
|
168
|
-
expect(packageJson.exports['./runtime/rsc/server']).toBeDefined();
|
|
169
|
-
expect(packageJson.typesVersions?.['*']?.['runtime/rsc']).toBeUndefined();
|
|
170
|
-
expect(packageJson.typesVersions?.['*']?.['runtime/rsc/client']).toEqual([
|
|
171
|
-
'./dist/types/runtime/rsc/client.d.ts',
|
|
172
|
-
]);
|
|
173
|
-
expect(packageJson.typesVersions?.['*']?.['runtime/rsc/server']).toEqual([
|
|
174
|
-
'./dist/types/runtime/rsc/server.d.ts',
|
|
175
|
-
]);
|
|
176
|
-
});
|
|
177
|
-
});
|
|
55
|
+
const matchOf = (router: TestRouter, routeId: string) =>
|
|
56
|
+
router.state.matches.find(match => match.routeId === routeId);
|
|
178
57
|
|
|
179
58
|
describe('tanstack route tree from RouteObject[]', () => {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
Component: () => null,
|
|
198
|
-
},
|
|
199
|
-
],
|
|
200
|
-
},
|
|
201
|
-
];
|
|
202
|
-
|
|
203
|
-
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
204
|
-
const router = await loadRouteTree(routeTree, '/user/123');
|
|
205
|
-
|
|
206
|
-
const rootMatch = router.state.matches.find(
|
|
207
|
-
match => match.routeId === '__root__',
|
|
208
|
-
);
|
|
209
|
-
const userMatch = router.state.matches.find(
|
|
210
|
-
match => match.routeId === '/user/$id',
|
|
59
|
+
test('resolves loader data for root, dynamic and splat params', async () => {
|
|
60
|
+
const routes: TestRouteObject[] = root(
|
|
61
|
+
[
|
|
62
|
+
{
|
|
63
|
+
id: 'user',
|
|
64
|
+
path: 'user/:id',
|
|
65
|
+
loader: ({ params }: LoaderArgs) => ({ id: params.id }),
|
|
66
|
+
Component: nullComponent,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: 'files',
|
|
70
|
+
path: 'files/*',
|
|
71
|
+
loader: ({ params }: LoaderArgs) => ({ value: params['*'] }),
|
|
72
|
+
Component: nullComponent,
|
|
73
|
+
},
|
|
74
|
+
],
|
|
75
|
+
nullComponent,
|
|
211
76
|
);
|
|
77
|
+
routes[0].loader = () => ({ root: 'ok' });
|
|
78
|
+
|
|
79
|
+
const userRouter = await loadRouteTree(routes, '/user/123');
|
|
80
|
+
expect(matchOf(userRouter, '__root__')?.loaderData).toEqual({ root: 'ok' });
|
|
81
|
+
expect(matchOf(userRouter, '/user/$id')?.loaderData).toEqual({ id: '123' });
|
|
212
82
|
|
|
213
|
-
|
|
214
|
-
expect(
|
|
83
|
+
const splatRouter = await loadRouteTree(routes, '/files/a/b/c');
|
|
84
|
+
expect(matchOf(splatRouter, '/files/$')?.loaderData).toEqual({
|
|
85
|
+
value: 'a/b/c',
|
|
86
|
+
});
|
|
215
87
|
});
|
|
216
88
|
|
|
217
89
|
test('keeps unnamed sibling pathless layouts distinct', async () => {
|
|
218
|
-
const
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
loader: () => ({ value: 'alpha' }),
|
|
225
|
-
Component: () => null,
|
|
226
|
-
},
|
|
227
|
-
],
|
|
228
|
-
},
|
|
229
|
-
{
|
|
230
|
-
Component: () => null,
|
|
231
|
-
children: [
|
|
232
|
-
{
|
|
233
|
-
path: 'beta',
|
|
234
|
-
loader: () => ({ value: 'beta' }),
|
|
235
|
-
Component: () => null,
|
|
236
|
-
},
|
|
237
|
-
],
|
|
238
|
-
},
|
|
239
|
-
];
|
|
240
|
-
|
|
241
|
-
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
242
|
-
const router = await loadRouteTree(routeTree, '/beta');
|
|
90
|
+
const pathless = (path: string): TestRouteObject => ({
|
|
91
|
+
Component: nullComponent,
|
|
92
|
+
children: [
|
|
93
|
+
{ path, loader: () => ({ value: path }), Component: nullComponent },
|
|
94
|
+
],
|
|
95
|
+
});
|
|
243
96
|
|
|
244
|
-
const
|
|
97
|
+
const router = await loadRouteTree(
|
|
98
|
+
[pathless('alpha'), pathless('beta')],
|
|
99
|
+
'/beta',
|
|
100
|
+
);
|
|
101
|
+
const loaderData = router.state.matches
|
|
245
102
|
.map(match => match.loaderData)
|
|
246
103
|
.filter(Boolean);
|
|
247
104
|
|
|
248
|
-
expect(
|
|
249
|
-
expect(
|
|
105
|
+
expect(loaderData).toContainEqual({ value: 'beta' });
|
|
106
|
+
expect(loaderData).not.toContainEqual({ value: 'alpha' });
|
|
250
107
|
});
|
|
251
108
|
|
|
252
109
|
test('reports native TanStack unknown routes as HTTP 404', async () => {
|
|
253
|
-
const
|
|
254
|
-
{
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
Component: () => null,
|
|
258
|
-
children: [
|
|
259
|
-
{
|
|
260
|
-
id: 'known',
|
|
261
|
-
path: 'known',
|
|
262
|
-
Component: () => null,
|
|
263
|
-
},
|
|
264
|
-
],
|
|
265
|
-
},
|
|
266
|
-
];
|
|
267
|
-
|
|
268
|
-
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
269
|
-
const router = await loadRouteTree(routeTree, '/missing');
|
|
110
|
+
const router = await loadRouteTree(
|
|
111
|
+
root([{ id: 'known', path: 'known', Component: nullComponent }]),
|
|
112
|
+
'/missing',
|
|
113
|
+
);
|
|
270
114
|
|
|
271
|
-
expect(router._serverResult).toMatchObject({
|
|
272
|
-
type: 'render',
|
|
273
|
-
status: 404,
|
|
274
|
-
});
|
|
115
|
+
expect(router._serverResult).toMatchObject({ type: 'render', status: 404 });
|
|
275
116
|
});
|
|
276
117
|
|
|
277
118
|
test('reports native TanStack loader redirects as server responses', async () => {
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
{
|
|
293
|
-
id: 'target',
|
|
294
|
-
path: 'target',
|
|
295
|
-
Component: () => null,
|
|
296
|
-
},
|
|
297
|
-
],
|
|
298
|
-
},
|
|
299
|
-
];
|
|
300
|
-
|
|
301
|
-
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
302
|
-
const router = await loadRouteTree(routeTree, '/redirect');
|
|
119
|
+
const router = await loadRouteTree(
|
|
120
|
+
root([
|
|
121
|
+
{
|
|
122
|
+
id: 'redirect',
|
|
123
|
+
path: 'redirect',
|
|
124
|
+
loader: () => {
|
|
125
|
+
throw redirect({ to: '/target' });
|
|
126
|
+
},
|
|
127
|
+
Component: nullComponent,
|
|
128
|
+
},
|
|
129
|
+
{ id: 'target', path: 'target', Component: nullComponent },
|
|
130
|
+
]),
|
|
131
|
+
'/redirect',
|
|
132
|
+
);
|
|
303
133
|
|
|
304
|
-
expect(router._serverResult?.type).toBe('redirect');
|
|
305
134
|
if (router._serverResult?.type !== 'redirect') {
|
|
306
135
|
throw new Error('Expected a TanStack redirect server result');
|
|
307
136
|
}
|
|
@@ -311,504 +140,78 @@ describe('tanstack route tree from RouteObject[]', () => {
|
|
|
311
140
|
);
|
|
312
141
|
});
|
|
313
142
|
|
|
314
|
-
test('does not force Suspense wrappers for ordinary generated routes', async () => {
|
|
315
|
-
const routes: RouteObject[] = [
|
|
316
|
-
{
|
|
317
|
-
id: 'root',
|
|
318
|
-
path: '/',
|
|
319
|
-
Component: () => createElement(Outlet),
|
|
320
|
-
children: [
|
|
321
|
-
{
|
|
322
|
-
id: 'plain',
|
|
323
|
-
path: 'plain',
|
|
324
|
-
Component: () => null,
|
|
325
|
-
},
|
|
326
|
-
],
|
|
327
|
-
},
|
|
328
|
-
];
|
|
329
|
-
|
|
330
|
-
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
331
|
-
const router = await loadRouteTree(routeTree, '/plain');
|
|
332
|
-
|
|
333
|
-
expect(routeTree.options.wrapInSuspense).toBeUndefined();
|
|
334
|
-
expect(getRoute(router, '/plain').options.wrapInSuspense).toBeUndefined();
|
|
335
|
-
});
|
|
336
|
-
|
|
337
143
|
test('renders Modern Outlet through TanStack native outlet', async () => {
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
path: '/',
|
|
342
|
-
Component: () =>
|
|
343
|
-
createElement('section', null, createElement(ModernOutlet)),
|
|
344
|
-
children: [
|
|
144
|
+
const router = await loadRouteTree(
|
|
145
|
+
root(
|
|
146
|
+
[
|
|
345
147
|
{
|
|
346
148
|
id: 'plain',
|
|
347
149
|
path: 'plain',
|
|
348
150
|
Component: () => createElement('main', null, 'Plain child route'),
|
|
349
151
|
},
|
|
350
152
|
],
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
355
|
-
const router = await loadRouteTree(routeTree, '/plain');
|
|
356
|
-
const markup = renderToString(
|
|
357
|
-
createElement(RouterProvider, { router } as never),
|
|
358
|
-
);
|
|
359
|
-
const suspenseBoundaryCount = countCompletedSuspenseBoundaries(markup);
|
|
360
|
-
|
|
361
|
-
expect(markup).toContain('Plain child route');
|
|
362
|
-
expect(suspenseBoundaryCount).toBe(1);
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
test('resolves matched Modern route ids from TanStack route registry fallback', () => {
|
|
366
|
-
const router = {
|
|
367
|
-
state: {
|
|
368
|
-
matches: [
|
|
369
|
-
{ routeId: '__root__' },
|
|
370
|
-
{ routeId: '/$lang' },
|
|
371
|
-
{ routeId: '/$lang/tractors' },
|
|
372
|
-
{
|
|
373
|
-
route: {
|
|
374
|
-
options: {
|
|
375
|
-
staticData: {
|
|
376
|
-
modernRouteId: '(lang)/stores/page',
|
|
377
|
-
},
|
|
378
|
-
},
|
|
379
|
-
},
|
|
380
|
-
routeId: '/$lang/stores',
|
|
381
|
-
},
|
|
382
|
-
],
|
|
383
|
-
},
|
|
384
|
-
routesById: {
|
|
385
|
-
__root__: {
|
|
386
|
-
options: {
|
|
387
|
-
staticData: {
|
|
388
|
-
modernRouteId: 'layout',
|
|
389
|
-
},
|
|
390
|
-
},
|
|
391
|
-
},
|
|
392
|
-
'/$lang': {
|
|
393
|
-
options: {
|
|
394
|
-
staticData: {
|
|
395
|
-
modernRouteId: '(lang)/page',
|
|
396
|
-
},
|
|
397
|
-
},
|
|
398
|
-
},
|
|
399
|
-
'/$lang/tractors': {
|
|
400
|
-
options: {
|
|
401
|
-
staticData: {
|
|
402
|
-
modernRouteId: '(lang)/tractors/page',
|
|
403
|
-
},
|
|
404
|
-
},
|
|
405
|
-
},
|
|
406
|
-
},
|
|
407
|
-
};
|
|
408
|
-
|
|
409
|
-
expect(getModernRouteIdsFromMatches(router as never)).toEqual([
|
|
410
|
-
'layout',
|
|
411
|
-
'(lang)/page',
|
|
412
|
-
'(lang)/tractors/page',
|
|
413
|
-
'(lang)/stores/page',
|
|
414
|
-
]);
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
test('preserves TanStack search contracts from RouteObject routes', () => {
|
|
418
|
-
const rootValidateSearch = (search: unknown) => ({ root: search });
|
|
419
|
-
const rootLoaderDeps = ({ search }: { search: unknown }) => ({ search });
|
|
420
|
-
const childValidateSearch = (search: unknown) => ({ child: search });
|
|
421
|
-
const childLoaderDeps = ({ search }: { search: unknown }) => ({ search });
|
|
422
|
-
const routes: TestRouteObject[] = [
|
|
423
|
-
{
|
|
424
|
-
id: 'root',
|
|
425
|
-
path: '/',
|
|
426
|
-
validateSearch: rootValidateSearch,
|
|
427
|
-
loaderDeps: rootLoaderDeps,
|
|
428
|
-
Component: () => null,
|
|
429
|
-
children: [
|
|
430
|
-
{
|
|
431
|
-
id: 'search',
|
|
432
|
-
path: 'search',
|
|
433
|
-
validateSearch: childValidateSearch,
|
|
434
|
-
loaderDeps: childLoaderDeps,
|
|
435
|
-
Component: () => null,
|
|
436
|
-
},
|
|
437
|
-
],
|
|
438
|
-
},
|
|
439
|
-
];
|
|
440
|
-
|
|
441
|
-
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
442
|
-
const router = createRouter({
|
|
443
|
-
routeTree,
|
|
444
|
-
history: createMemoryHistory({
|
|
445
|
-
initialEntries: ['/search'],
|
|
446
|
-
}),
|
|
447
|
-
context: {},
|
|
448
|
-
}) as unknown as TestRouter;
|
|
449
|
-
const searchRoute = getRoute(router, '/search');
|
|
450
|
-
|
|
451
|
-
expect(routeTree.options.validateSearch).toBe(rootValidateSearch);
|
|
452
|
-
expect(routeTree.options.loaderDeps).toBe(rootLoaderDeps);
|
|
453
|
-
expect(searchRoute.options.validateSearch).toBe(childValidateSearch);
|
|
454
|
-
expect(searchRoute.options.loaderDeps).toBe(childLoaderDeps);
|
|
455
|
-
});
|
|
456
|
-
|
|
457
|
-
test('uses TanStack route ids when loading RSC payload route data', async () => {
|
|
458
|
-
const rootLoader = rstest.fn(() => ({ source: 'modern-root' }));
|
|
459
|
-
const userLoader = rstest.fn(() => ({ source: 'modern-user' }));
|
|
460
|
-
const routes: RouteObject[] = [
|
|
461
|
-
{
|
|
462
|
-
id: 'root',
|
|
463
|
-
path: '/',
|
|
464
|
-
loader: rootLoader,
|
|
465
|
-
Component: () => null,
|
|
466
|
-
children: [
|
|
467
|
-
{
|
|
468
|
-
id: 'user',
|
|
469
|
-
path: 'user/:id',
|
|
470
|
-
loader: userLoader,
|
|
471
|
-
Component: () => null,
|
|
472
|
-
},
|
|
473
|
-
],
|
|
474
|
-
},
|
|
475
|
-
];
|
|
476
|
-
const payload = {
|
|
477
|
-
type: 'render',
|
|
478
|
-
actionData: null,
|
|
479
|
-
errors: null,
|
|
480
|
-
loaderData: {
|
|
481
|
-
__root__: { source: 'rsc-root' },
|
|
482
|
-
'/user/$id': { source: 'rsc-user' },
|
|
483
|
-
},
|
|
484
|
-
location: { href: '/user/123' },
|
|
485
|
-
routes: [
|
|
486
|
-
{ id: '__root__', hasLoader: true },
|
|
487
|
-
{ id: '/user/$id', hasLoader: true },
|
|
488
|
-
],
|
|
489
|
-
};
|
|
490
|
-
const fetchMock = rstest.fn(() => Promise.resolve(new Response('payload')));
|
|
491
|
-
const decodeMock = rstest.fn(async () => payload);
|
|
492
|
-
rstest.stubGlobal('fetch', fetchMock);
|
|
493
|
-
rstest.stubGlobal('window', { origin: 'http://localhost' });
|
|
494
|
-
__setTanstackRscPayloadDecoderForTests(decodeMock);
|
|
495
|
-
|
|
496
|
-
const routeTree = createRouteTreeFromRouteObjects(routes, {
|
|
497
|
-
rscPayloadRouter: true,
|
|
498
|
-
});
|
|
499
|
-
const router = await loadRouteTree(routeTree, '/user/123');
|
|
500
|
-
|
|
501
|
-
const rootMatch = router.state.matches.find(
|
|
502
|
-
match => match.routeId === '__root__',
|
|
503
|
-
);
|
|
504
|
-
const userMatch = router.state.matches.find(
|
|
505
|
-
match => match.routeId === '/user/$id',
|
|
506
|
-
);
|
|
507
|
-
expect(rootMatch?.loaderData).toEqual({ source: 'rsc-root' });
|
|
508
|
-
expect(userMatch?.loaderData).toEqual({ source: 'rsc-user' });
|
|
509
|
-
expect(rootLoader).not.toHaveBeenCalled();
|
|
510
|
-
expect(userLoader).not.toHaveBeenCalled();
|
|
511
|
-
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
512
|
-
expect(decodeMock).toHaveBeenCalledTimes(1);
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
test('maps splat params', async () => {
|
|
516
|
-
let splatParamValue = '';
|
|
517
|
-
const routes: TestRouteObject[] = [
|
|
518
|
-
{
|
|
519
|
-
id: 'root',
|
|
520
|
-
path: '/',
|
|
521
|
-
Component: () => null,
|
|
522
|
-
children: [
|
|
523
|
-
{
|
|
524
|
-
id: 'files',
|
|
525
|
-
path: 'files/*',
|
|
526
|
-
loader: ({ params }: LoaderArgs) => {
|
|
527
|
-
splatParamValue = String(params['*'] || '');
|
|
528
|
-
return { value: params['*'] };
|
|
529
|
-
},
|
|
530
|
-
Component: () => null,
|
|
531
|
-
},
|
|
532
|
-
],
|
|
533
|
-
},
|
|
534
|
-
];
|
|
535
|
-
|
|
536
|
-
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
537
|
-
|
|
538
|
-
const splatRouter = await loadRouteTree(routeTree, '/files/a/b/c');
|
|
539
|
-
const filesMatch = splatRouter.state.matches.find(
|
|
540
|
-
match => match.routeId === '/files/$',
|
|
153
|
+
() => createElement('section', null, createElement(ModernOutlet)),
|
|
154
|
+
),
|
|
155
|
+
'/plain',
|
|
541
156
|
);
|
|
542
|
-
expect(filesMatch?.loaderData).toEqual({ value: 'a/b/c' });
|
|
543
|
-
expect(splatParamValue).toBe('a/b/c');
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
test('preloads lazy Modern route components for server rendering', async () => {
|
|
547
|
-
const LazyRouteComponent = () =>
|
|
548
|
-
createElement('main', null, 'Lazy route ready');
|
|
549
|
-
const lazyImport = rstest.fn(async () => ({
|
|
550
|
-
default: LazyRouteComponent,
|
|
551
|
-
}));
|
|
552
|
-
const routes: TestRouteObject[] = [
|
|
553
|
-
{
|
|
554
|
-
id: 'root',
|
|
555
|
-
path: '/',
|
|
556
|
-
Component: () => null,
|
|
557
|
-
children: [
|
|
558
|
-
{
|
|
559
|
-
id: 'lazy',
|
|
560
|
-
path: 'lazy',
|
|
561
|
-
Component: lazy(lazyImport),
|
|
562
|
-
lazyImport,
|
|
563
|
-
},
|
|
564
|
-
],
|
|
565
|
-
},
|
|
566
|
-
];
|
|
567
|
-
|
|
568
|
-
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
569
|
-
const router = await loadRouteTree(routeTree, '/lazy');
|
|
570
|
-
const lazyRoute = getRoute(router, '/lazy');
|
|
571
|
-
const component = lazyRoute.options.component as ComponentType & {
|
|
572
|
-
preload?: () => Promise<unknown>;
|
|
573
|
-
};
|
|
574
|
-
|
|
575
|
-
await component.preload?.();
|
|
576
|
-
|
|
577
|
-
expect(lazyImport).toHaveBeenCalled();
|
|
578
|
-
});
|
|
579
|
-
|
|
580
|
-
test('renders preloaded lazy child routes through TanStack router SSR', async () => {
|
|
581
|
-
const LazyRouteComponent = () =>
|
|
582
|
-
createElement('main', null, 'Lazy child route ready');
|
|
583
|
-
const lazyImport = rstest.fn(async () => ({
|
|
584
|
-
default: LazyRouteComponent,
|
|
585
|
-
}));
|
|
586
|
-
const routes: TestRouteObject[] = [
|
|
587
|
-
{
|
|
588
|
-
id: 'root',
|
|
589
|
-
path: '/',
|
|
590
|
-
Component: () => createElement('section', null, createElement(Outlet)),
|
|
591
|
-
children: [
|
|
592
|
-
{
|
|
593
|
-
id: 'lazy',
|
|
594
|
-
path: 'lazy',
|
|
595
|
-
Component: lazy(lazyImport),
|
|
596
|
-
lazyImport,
|
|
597
|
-
},
|
|
598
|
-
],
|
|
599
|
-
},
|
|
600
|
-
];
|
|
601
|
-
|
|
602
|
-
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
603
|
-
const router = await loadRouteTree(routeTree, '/lazy');
|
|
604
|
-
const lazyRoute = getRoute(router, '/lazy');
|
|
605
|
-
const lazyComponent = lazyRoute.options
|
|
606
|
-
.component as PreloadableTestComponent;
|
|
607
|
-
|
|
608
|
-
expect(
|
|
609
|
-
renderToStaticMarkup(createElement(RouterProvider, { router } as never)),
|
|
610
|
-
).toContain('Lazy child route ready');
|
|
611
|
-
expect(typeof lazyComponent.load).toBe('function');
|
|
612
|
-
expect(typeof lazyComponent.preload).toBe('function');
|
|
613
|
-
});
|
|
614
|
-
|
|
615
|
-
test('exposes load-only Modern route components through TanStack preload', async () => {
|
|
616
|
-
const load = rstest.fn(async () => 'route chunk loaded');
|
|
617
|
-
const LoadOnlyRouteComponent = (() =>
|
|
618
|
-
createElement('main', null, 'Load-only route ready')) as ComponentType & {
|
|
619
|
-
load?: () => Promise<unknown>;
|
|
620
|
-
preload?: () => Promise<unknown>;
|
|
621
|
-
};
|
|
622
|
-
LoadOnlyRouteComponent.load = load;
|
|
623
|
-
const routes: TestRouteObject[] = [
|
|
624
|
-
{
|
|
625
|
-
id: 'root',
|
|
626
|
-
path: '/',
|
|
627
|
-
Component: () => createElement('section', null, createElement(Outlet)),
|
|
628
|
-
children: [
|
|
629
|
-
{
|
|
630
|
-
id: 'load-only',
|
|
631
|
-
path: 'load-only',
|
|
632
|
-
Component: LoadOnlyRouteComponent,
|
|
633
|
-
},
|
|
634
|
-
],
|
|
635
|
-
},
|
|
636
|
-
];
|
|
637
|
-
|
|
638
|
-
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
639
|
-
const router = await loadRouteTree(routeTree, '/load-only');
|
|
640
|
-
const loadOnlyRoute = getRoute(router, '/load-only');
|
|
641
|
-
const loadOnlyComponent = loadOnlyRoute.options
|
|
642
|
-
.component as PreloadableTestComponent;
|
|
643
|
-
|
|
644
|
-
expect(typeof loadOnlyComponent.load).toBe('function');
|
|
645
|
-
expect(typeof loadOnlyComponent.preload).toBe('function');
|
|
646
|
-
expect(load).toHaveBeenCalledTimes(1);
|
|
647
|
-
await loadOnlyComponent.preload?.();
|
|
648
|
-
expect(load).toHaveBeenCalledTimes(2);
|
|
649
|
-
});
|
|
650
|
-
|
|
651
|
-
test('unwraps nested ESM route module defaults before server rendering', async () => {
|
|
652
|
-
const LazyRouteComponent = () =>
|
|
653
|
-
createElement('main', null, 'Nested lazy child route ready');
|
|
654
|
-
const lazyImport = rstest.fn(async () => ({
|
|
655
|
-
default: {
|
|
656
|
-
default: LazyRouteComponent,
|
|
657
|
-
},
|
|
658
|
-
}));
|
|
659
|
-
const routes: TestRouteObject[] = [
|
|
660
|
-
{
|
|
661
|
-
id: 'root',
|
|
662
|
-
path: '/',
|
|
663
|
-
Component: () => createElement('section', null, createElement(Outlet)),
|
|
664
|
-
children: [
|
|
665
|
-
{
|
|
666
|
-
id: 'lazy',
|
|
667
|
-
path: 'lazy',
|
|
668
|
-
Component: lazy(
|
|
669
|
-
lazyImport as () => Promise<{ default: ComponentType }>,
|
|
670
|
-
),
|
|
671
|
-
lazyImport,
|
|
672
|
-
},
|
|
673
|
-
],
|
|
674
|
-
},
|
|
675
|
-
];
|
|
676
|
-
|
|
677
|
-
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
678
|
-
const router = await loadRouteTree(routeTree, '/lazy');
|
|
679
157
|
|
|
680
158
|
expect(
|
|
681
|
-
|
|
682
|
-
).toContain('
|
|
683
|
-
});
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
role: 'admin',
|
|
707
|
-
},
|
|
159
|
+
renderToString(createElement(RouterProvider, { router } as never)),
|
|
160
|
+
).toContain('Plain child route');
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
for (const [shape, wrap] of [
|
|
164
|
+
['flat', (Component: ComponentType) => ({ default: Component })],
|
|
165
|
+
[
|
|
166
|
+
'nested',
|
|
167
|
+
(Component: ComponentType) => ({ default: { default: Component } }),
|
|
168
|
+
],
|
|
169
|
+
] as const) {
|
|
170
|
+
test(`server renders lazy child routes from ${shape} ESM module defaults`, async () => {
|
|
171
|
+
const LazyRouteComponent = () =>
|
|
172
|
+
createElement('main', null, 'Lazy child route ready');
|
|
173
|
+
const lazyImport = () => Promise.resolve(wrap(LazyRouteComponent));
|
|
174
|
+
const router = await loadRouteTree(
|
|
175
|
+
root(
|
|
176
|
+
[
|
|
177
|
+
{
|
|
178
|
+
id: 'lazy',
|
|
179
|
+
path: 'lazy',
|
|
180
|
+
Component: lazy(
|
|
181
|
+
lazyImport as () => Promise<{ default: ComponentType }>,
|
|
182
|
+
),
|
|
183
|
+
lazyImport,
|
|
708
184
|
},
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
expect(routeTree.options.staticData.modernRouteHandle).toEqual({
|
|
722
|
-
shell: true,
|
|
723
|
-
});
|
|
724
|
-
expect(userRoute.options.staticData.modernRouteHandle).toEqual({
|
|
725
|
-
auth: true,
|
|
726
|
-
role: 'admin',
|
|
727
|
-
});
|
|
728
|
-
expect(userRoute.options.staticData.modernRouteShouldRevalidate).toBe(
|
|
729
|
-
shouldRevalidate,
|
|
730
|
-
);
|
|
731
|
-
expect(
|
|
732
|
-
userRoute.options.shouldReload?.({
|
|
733
|
-
location: { href: '/user/456' },
|
|
734
|
-
params: { id: '456' },
|
|
735
|
-
context: {
|
|
736
|
-
request: new Request('http://localhost/user/456'),
|
|
737
|
-
},
|
|
738
|
-
}),
|
|
739
|
-
).toBe(true);
|
|
740
|
-
expect(shouldRevalidate).toHaveBeenCalledWith(
|
|
741
|
-
expect.objectContaining({
|
|
742
|
-
currentParams: { id: '123' },
|
|
743
|
-
nextParams: { id: '456' },
|
|
744
|
-
}),
|
|
745
|
-
);
|
|
746
|
-
});
|
|
747
|
-
|
|
748
|
-
test('preserves client route metadata and disables invalid SSR routes', async () => {
|
|
749
|
-
const routes: TestRouteObject[] = [
|
|
750
|
-
{
|
|
751
|
-
id: 'root',
|
|
752
|
-
path: '/',
|
|
753
|
-
Component: () => null,
|
|
754
|
-
children: [
|
|
755
|
-
{
|
|
756
|
-
id: 'client',
|
|
757
|
-
path: 'client',
|
|
758
|
-
hasAction: true,
|
|
759
|
-
hasClientLoader: true,
|
|
760
|
-
hasLoader: true,
|
|
761
|
-
inValidSSRRoute: true,
|
|
762
|
-
isClientComponent: true,
|
|
763
|
-
loader: () => ({ ok: true }),
|
|
764
|
-
Component: () => null,
|
|
765
|
-
},
|
|
766
|
-
],
|
|
767
|
-
},
|
|
768
|
-
];
|
|
769
|
-
|
|
770
|
-
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
771
|
-
const router = await loadRouteTree(routeTree, '/client');
|
|
772
|
-
const clientRoute = getRoute(router, '/client');
|
|
773
|
-
|
|
774
|
-
expect(clientRoute.options.ssr).toBe(false);
|
|
775
|
-
expect(clientRoute.options.staticData).toMatchObject({
|
|
776
|
-
modernRouteHasAction: true,
|
|
777
|
-
modernRouteHasClientLoader: true,
|
|
778
|
-
modernRouteHasLoader: true,
|
|
779
|
-
modernRouteIsClientComponent: true,
|
|
185
|
+
],
|
|
186
|
+
() => createElement('section', null, createElement(Outlet)),
|
|
187
|
+
),
|
|
188
|
+
'/lazy',
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
expect(
|
|
192
|
+
renderToStaticMarkup(
|
|
193
|
+
createElement(RouterProvider, { router } as never),
|
|
194
|
+
),
|
|
195
|
+
).toContain('Lazy child route ready');
|
|
780
196
|
});
|
|
781
|
-
}
|
|
197
|
+
}
|
|
782
198
|
|
|
783
199
|
test('normalizes Modern deferred loader data for TanStack SSR', async () => {
|
|
784
|
-
const
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
later: Promise.resolve('done'),
|
|
798
|
-
},
|
|
799
|
-
}),
|
|
800
|
-
Component: () => null,
|
|
801
|
-
},
|
|
802
|
-
],
|
|
803
|
-
},
|
|
804
|
-
];
|
|
805
|
-
|
|
806
|
-
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
807
|
-
const router = await loadRouteTree(routeTree, '/deferred');
|
|
808
|
-
const deferredMatch = router.state.matches.find(
|
|
809
|
-
match => match.routeId === '/deferred',
|
|
200
|
+
const router = await loadRouteTree(
|
|
201
|
+
root([
|
|
202
|
+
{
|
|
203
|
+
id: 'deferred',
|
|
204
|
+
path: 'deferred',
|
|
205
|
+
loader: () => ({
|
|
206
|
+
__modern_deferred: true,
|
|
207
|
+
data: { immediate: 'ok', later: Promise.resolve('done') },
|
|
208
|
+
}),
|
|
209
|
+
Component: nullComponent,
|
|
210
|
+
},
|
|
211
|
+
]),
|
|
212
|
+
'/deferred',
|
|
810
213
|
);
|
|
811
|
-
const loaderData =
|
|
214
|
+
const loaderData = matchOf(router, '/deferred')?.loaderData as
|
|
812
215
|
| { immediate: string; later: Promise<string> }
|
|
813
216
|
| undefined;
|
|
814
217
|
|
|
@@ -818,115 +221,20 @@ describe('tanstack route tree from RouteObject[]', () => {
|
|
|
818
221
|
|
|
819
222
|
test('preserves returned non-404 Response loaders as loader data', async () => {
|
|
820
223
|
const response = new Response('route status payload', { status: 500 });
|
|
821
|
-
const
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
Component: () => null,
|
|
832
|
-
},
|
|
833
|
-
],
|
|
834
|
-
},
|
|
835
|
-
];
|
|
836
|
-
|
|
837
|
-
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
838
|
-
const router = await loadRouteTree(routeTree, '/broken');
|
|
839
|
-
const brokenMatch = router.state.matches.find(
|
|
840
|
-
match => match.routeId === '/broken',
|
|
224
|
+
const router = await loadRouteTree(
|
|
225
|
+
root([
|
|
226
|
+
{
|
|
227
|
+
id: 'broken',
|
|
228
|
+
path: 'broken',
|
|
229
|
+
loader: () => response,
|
|
230
|
+
Component: nullComponent,
|
|
231
|
+
},
|
|
232
|
+
]),
|
|
233
|
+
'/broken',
|
|
841
234
|
);
|
|
842
235
|
|
|
843
|
-
expect(router._serverResult).toMatchObject({
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
});
|
|
847
|
-
expect(brokenMatch?.loaderData).toBe(response);
|
|
848
|
-
expect(brokenMatch?.error).toBeUndefined();
|
|
849
|
-
});
|
|
850
|
-
|
|
851
|
-
test('preserves generated client metadata through RouteObject conversion', () => {
|
|
852
|
-
const modernRoutes: TestNestedRoute[] = [
|
|
853
|
-
{
|
|
854
|
-
type: 'nested',
|
|
855
|
-
origin: 'config',
|
|
856
|
-
id: 'root',
|
|
857
|
-
isRoot: true,
|
|
858
|
-
children: [
|
|
859
|
-
{
|
|
860
|
-
type: 'nested',
|
|
861
|
-
origin: 'config',
|
|
862
|
-
id: 'client',
|
|
863
|
-
path: 'client',
|
|
864
|
-
clientData: './client.data',
|
|
865
|
-
data: './data',
|
|
866
|
-
hasAction: true,
|
|
867
|
-
hasClientLoader: true,
|
|
868
|
-
hasLoader: true,
|
|
869
|
-
inValidSSRRoute: true,
|
|
870
|
-
isClientComponent: true,
|
|
871
|
-
},
|
|
872
|
-
],
|
|
873
|
-
},
|
|
874
|
-
];
|
|
875
|
-
const routeObjects = createTanstackRouteObjectsFromConfig({
|
|
876
|
-
routesConfig: { routes: modernRoutes },
|
|
877
|
-
});
|
|
878
|
-
const routeTree = createRouteTreeFromRouteObjects(routeObjects || []);
|
|
879
|
-
const router = createRouter({
|
|
880
|
-
routeTree,
|
|
881
|
-
history: createMemoryHistory({
|
|
882
|
-
initialEntries: ['/client'],
|
|
883
|
-
}),
|
|
884
|
-
context: {},
|
|
885
|
-
}) as unknown as TestRouter;
|
|
886
|
-
const clientRoute = getRouteByModernRouteId(router, 'client');
|
|
887
|
-
|
|
888
|
-
expect(clientRoute.options.ssr).toBe(false);
|
|
889
|
-
expect(clientRoute.options.staticData).toMatchObject({
|
|
890
|
-
modernRouteHasAction: true,
|
|
891
|
-
modernRouteHasClientLoader: true,
|
|
892
|
-
modernRouteHasLoader: true,
|
|
893
|
-
modernRouteIsClientComponent: true,
|
|
894
|
-
});
|
|
895
|
-
});
|
|
896
|
-
|
|
897
|
-
test('does not copy hasErrorBoundary into React Router route objects', () => {
|
|
898
|
-
function ErrorPage() {
|
|
899
|
-
return null;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
const modernRoutes: TestNestedRoute[] = [
|
|
903
|
-
{
|
|
904
|
-
type: 'nested',
|
|
905
|
-
origin: 'config',
|
|
906
|
-
id: 'root',
|
|
907
|
-
isRoot: true,
|
|
908
|
-
hasErrorBoundary: true,
|
|
909
|
-
children: [
|
|
910
|
-
{
|
|
911
|
-
type: 'nested',
|
|
912
|
-
origin: 'config',
|
|
913
|
-
id: 'child',
|
|
914
|
-
path: 'child',
|
|
915
|
-
hasErrorBoundary: true,
|
|
916
|
-
error: ErrorPage,
|
|
917
|
-
},
|
|
918
|
-
],
|
|
919
|
-
},
|
|
920
|
-
];
|
|
921
|
-
|
|
922
|
-
const routeObjects = createTanstackRouteObjectsFromConfig({
|
|
923
|
-
routesConfig: { routes: modernRoutes },
|
|
924
|
-
});
|
|
925
|
-
const rootRoute = routeObjects?.[0];
|
|
926
|
-
const childRoute = rootRoute?.children?.[0];
|
|
927
|
-
|
|
928
|
-
expect(rootRoute).not.toHaveProperty('hasErrorBoundary');
|
|
929
|
-
expect(childRoute).not.toHaveProperty('hasErrorBoundary');
|
|
930
|
-
expect(childRoute?.errorElement).toBeDefined();
|
|
236
|
+
expect(router._serverResult).toMatchObject({ type: 'render', status: 200 });
|
|
237
|
+
expect(matchOf(router, '/broken')?.loaderData).toBe(response);
|
|
238
|
+
expect(matchOf(router, '/broken')?.error).toBeUndefined();
|
|
931
239
|
});
|
|
932
240
|
});
|