@bleedingdev/modern-js-plugin-tanstack 3.9.0-ultramodern.5 → 3.9.0-ultramodern.7

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.
@@ -1,5 +1,4 @@
1
1
  import type { RouteObject } from '@modern-js/runtime-utils/router';
2
- import type { NestedRoute } from '@modern-js/types';
3
2
  import { createMemoryHistory } from '@tanstack/history';
4
3
  import {
5
4
  createRouter,
@@ -11,104 +10,38 @@ import type { ComponentType } from 'react';
11
10
  import { createElement, lazy } from 'react';
12
11
  import { renderToStaticMarkup, renderToString } from 'react-dom/server';
13
12
  import { Outlet as ModernOutlet } from '../../src/runtime/outlet';
14
- import {
15
- createRouteTreeFromRouteObjects,
16
- getModernRouteIdsFromMatches,
17
- } from '../../src/runtime/routeTree';
18
- import { __setTanstackRscPayloadDecoderForTests } from '../../src/runtime/rsc/payloadRouter';
19
- import { createTanstackRouteObjectsFromConfig } from '../../src/runtime/utils';
13
+ import { createRouteTreeFromRouteObjects } from '../../src/runtime/routeTree';
20
14
 
21
- type LoaderArgs = {
22
- params: Record<string, string>;
23
- };
15
+ type LoaderArgs = { params: Record<string, string> };
24
16
 
25
17
  type TestRouteObject = RouteObject & {
26
18
  children?: TestRouteObject[];
27
- config?: {
28
- handle?: Record<string, unknown>;
29
- };
30
- hasAction?: boolean;
31
- hasClientLoader?: boolean;
32
- hasLoader?: boolean;
33
- inValidSSRRoute?: boolean;
34
- isClientComponent?: boolean;
35
19
  lazyImport?: () => Promise<unknown>;
36
- loaderDeps?: unknown;
37
- validateSearch?: unknown;
38
- };
39
-
40
- type TestNestedRoute = NestedRoute & {
41
- children?: TestNestedRoute[];
42
- hasAction?: boolean;
43
- hasClientLoader?: boolean;
44
- hasLoader?: boolean;
45
- loaderDeps?: unknown;
46
- validateSearch?: unknown;
47
- };
48
-
49
- type ShouldRevalidateArgs = {
50
- nextUrl: URL;
51
- };
52
-
53
- type ShouldReloadArgs = {
54
- context: {
55
- request: Request;
56
- };
57
- location: {
58
- href: string;
59
- };
60
- params: Record<string, string>;
61
- };
62
-
63
- type TestRoute = {
64
- options: {
65
- component?: unknown;
66
- shouldReload?: (args: ShouldReloadArgs) => boolean | undefined;
67
- ssr?: boolean;
68
- staticData: Record<string, unknown>;
69
- loaderDeps?: unknown;
70
- validateSearch?: unknown;
71
- wrapInSuspense?: unknown;
72
- };
73
- };
74
-
75
- type PreloadableTestComponent = {
76
- load?: () => Promise<unknown>;
77
- preload?: () => Promise<unknown>;
78
20
  };
79
21
 
80
22
  type TestRouter = {
81
23
  _serverResult?:
82
- | {
83
- type: 'redirect';
84
- redirect: Response;
85
- }
86
- | {
87
- type: 'render';
88
- status: 200 | 404 | 500;
89
- };
90
- load: () => Promise<void>;
91
- routesById: Partial<Record<string, TestRoute>>;
24
+ | { type: 'redirect'; redirect: Response }
25
+ | { type: 'render'; status: number };
92
26
  state: {
93
- matches: Array<{
94
- error?: unknown;
95
- loaderData?: unknown;
96
- routeId: string;
97
- }>;
27
+ matches: Array<{ error?: unknown; loaderData?: unknown; routeId: string }>;
98
28
  };
99
29
  };
100
30
 
101
- type TestRouteTree = ReturnType<typeof createRouteTreeFromRouteObjects>;
31
+ const nullComponent = () => null;
32
+
33
+ const root = (
34
+ children: TestRouteObject[],
35
+ Component: RouteObject['Component'] = nullComponent,
36
+ ): TestRouteObject[] => [{ id: 'root', path: '/', Component, children }];
102
37
 
103
38
  async function loadRouteTree(
104
- routeTree: TestRouteTree,
39
+ routes: TestRouteObject[],
105
40
  pathname: string,
106
41
  ): Promise<TestRouter> {
107
42
  const router = createRouter({
108
- routeTree,
109
- history: createMemoryHistory({
110
- initialEntries: [pathname],
111
- }),
43
+ routeTree: createRouteTreeFromRouteObjects(routes as RouteObject[]),
44
+ history: createMemoryHistory({ initialEntries: [pathname] }),
112
45
  context: {
113
46
  request: new Request(`http://localhost${pathname}`),
114
47
  requestContext: {},
@@ -119,153 +52,85 @@ async function loadRouteTree(
119
52
  return router as unknown as TestRouter;
120
53
  }
121
54
 
122
- function getRoute(router: TestRouter, id: string): TestRoute {
123
- const route = router.routesById[id];
124
- if (!route) {
125
- throw new Error(`Expected TanStack route ${id} to exist`);
126
- }
127
- return route;
128
- }
129
-
130
- function getRouteByModernRouteId(
131
- router: TestRouter,
132
- modernRouteId: string,
133
- ): TestRoute {
134
- const route = Object.values(router.routesById).find(
135
- route => route?.options.staticData?.modernRouteId === modernRouteId,
136
- );
137
- if (!route) {
138
- throw new Error(`Expected Modern route ${modernRouteId} to exist`);
139
- }
140
- return route;
141
- }
55
+ const matchOf = (router: TestRouter, routeId: string) =>
56
+ router.state.matches.find(match => match.routeId === routeId);
142
57
 
143
58
  describe('tanstack route tree from RouteObject[]', () => {
144
- afterEach(() => {
145
- __setTanstackRscPayloadDecoderForTests();
146
- rstest.restoreAllMocks();
147
- });
148
-
149
- test('maps root loader and dynamic params', async () => {
150
- const routes: RouteObject[] = [
151
- {
152
- id: 'root',
153
- path: '/',
154
- loader: () => ({ root: 'ok' }),
155
- Component: () => null,
156
- children: [
157
- {
158
- id: 'user',
159
- path: 'user/:id',
160
- loader: ({ params }: LoaderArgs) => ({ id: params.id }),
161
- Component: () => null,
162
- },
163
- ],
164
- },
165
- ];
166
-
167
- const routeTree = createRouteTreeFromRouteObjects(routes);
168
- const router = await loadRouteTree(routeTree, '/user/123');
169
-
170
- const rootMatch = router.state.matches.find(
171
- match => match.routeId === '__root__',
172
- );
173
- const userMatch = router.state.matches.find(
174
- 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,
175
76
  );
77
+ routes[0].loader = () => ({ root: 'ok' });
176
78
 
177
- expect(rootMatch?.loaderData).toEqual({ root: 'ok' });
178
- expect(userMatch?.loaderData).toEqual({ id: '123' });
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' });
82
+
83
+ const splatRouter = await loadRouteTree(routes, '/files/a/b/c');
84
+ expect(matchOf(splatRouter, '/files/$')?.loaderData).toEqual({
85
+ value: 'a/b/c',
86
+ });
179
87
  });
180
88
 
181
89
  test('keeps unnamed sibling pathless layouts distinct', async () => {
182
- const routes: RouteObject[] = [
183
- {
184
- Component: () => null,
185
- children: [
186
- {
187
- path: 'alpha',
188
- loader: () => ({ value: 'alpha' }),
189
- Component: () => null,
190
- },
191
- ],
192
- },
193
- {
194
- Component: () => null,
195
- children: [
196
- {
197
- path: 'beta',
198
- loader: () => ({ value: 'beta' }),
199
- Component: () => null,
200
- },
201
- ],
202
- },
203
- ];
204
-
205
- const routeTree = createRouteTreeFromRouteObjects(routes);
206
- 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
+ });
207
96
 
208
- const matchedLoaderData = router.state.matches
97
+ const router = await loadRouteTree(
98
+ [pathless('alpha'), pathless('beta')],
99
+ '/beta',
100
+ );
101
+ const loaderData = router.state.matches
209
102
  .map(match => match.loaderData)
210
103
  .filter(Boolean);
211
104
 
212
- expect(matchedLoaderData).toContainEqual({ value: 'beta' });
213
- expect(matchedLoaderData).not.toContainEqual({ value: 'alpha' });
105
+ expect(loaderData).toContainEqual({ value: 'beta' });
106
+ expect(loaderData).not.toContainEqual({ value: 'alpha' });
214
107
  });
215
108
 
216
109
  test('reports native TanStack unknown routes as HTTP 404', async () => {
217
- const routes: RouteObject[] = [
218
- {
219
- id: 'root',
220
- path: '/',
221
- Component: () => null,
222
- children: [
223
- {
224
- id: 'known',
225
- path: 'known',
226
- Component: () => null,
227
- },
228
- ],
229
- },
230
- ];
231
-
232
- const routeTree = createRouteTreeFromRouteObjects(routes);
233
- const router = await loadRouteTree(routeTree, '/missing');
110
+ const router = await loadRouteTree(
111
+ root([{ id: 'known', path: 'known', Component: nullComponent }]),
112
+ '/missing',
113
+ );
234
114
 
235
- expect(router._serverResult).toMatchObject({
236
- type: 'render',
237
- status: 404,
238
- });
115
+ expect(router._serverResult).toMatchObject({ type: 'render', status: 404 });
239
116
  });
240
117
 
241
118
  test('reports native TanStack loader redirects as server responses', async () => {
242
- const routes: RouteObject[] = [
243
- {
244
- id: 'root',
245
- path: '/',
246
- Component: () => null,
247
- children: [
248
- {
249
- id: 'redirect',
250
- path: 'redirect',
251
- loader: () => {
252
- throw redirect({ to: '/target' });
253
- },
254
- Component: () => null,
255
- },
256
- {
257
- id: 'target',
258
- path: 'target',
259
- Component: () => null,
260
- },
261
- ],
262
- },
263
- ];
264
-
265
- const routeTree = createRouteTreeFromRouteObjects(routes);
266
- 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
+ );
267
133
 
268
- expect(router._serverResult?.type).toBe('redirect');
269
134
  if (router._serverResult?.type !== 'redirect') {
270
135
  throw new Error('Expected a TanStack redirect server result');
271
136
  }
@@ -276,363 +141,77 @@ describe('tanstack route tree from RouteObject[]', () => {
276
141
  });
277
142
 
278
143
  test('renders Modern Outlet through TanStack native outlet', async () => {
279
- const routes: RouteObject[] = [
280
- {
281
- id: 'root',
282
- path: '/',
283
- Component: () =>
284
- createElement('section', null, createElement(ModernOutlet)),
285
- children: [
144
+ const router = await loadRouteTree(
145
+ root(
146
+ [
286
147
  {
287
148
  id: 'plain',
288
149
  path: 'plain',
289
150
  Component: () => createElement('main', null, 'Plain child route'),
290
151
  },
291
152
  ],
292
- },
293
- ];
294
-
295
- const routeTree = createRouteTreeFromRouteObjects(routes);
296
- const router = await loadRouteTree(routeTree, '/plain');
297
- const markup = renderToString(
298
- createElement(RouterProvider, { router } as never),
299
- );
300
- expect(markup).toContain('Plain child route');
301
- });
302
-
303
- test('resolves matched Modern route ids from TanStack route registry fallback', () => {
304
- const router = {
305
- state: {
306
- matches: [
307
- { routeId: '__root__' },
308
- { routeId: '/$lang' },
309
- { routeId: '/$lang/tractors' },
310
- {
311
- route: {
312
- options: {
313
- staticData: {
314
- modernRouteId: '(lang)/stores/page',
315
- },
316
- },
317
- },
318
- routeId: '/$lang/stores',
319
- },
320
- ],
321
- },
322
- routesById: {
323
- __root__: {
324
- options: {
325
- staticData: {
326
- modernRouteId: 'layout',
327
- },
328
- },
329
- },
330
- '/$lang': {
331
- options: {
332
- staticData: {
333
- modernRouteId: '(lang)/page',
334
- },
335
- },
336
- },
337
- '/$lang/tractors': {
338
- options: {
339
- staticData: {
340
- modernRouteId: '(lang)/tractors/page',
341
- },
342
- },
343
- },
344
- },
345
- };
346
-
347
- expect(getModernRouteIdsFromMatches(router as never)).toEqual([
348
- 'layout',
349
- '(lang)/page',
350
- '(lang)/tractors/page',
351
- '(lang)/stores/page',
352
- ]);
353
- });
354
-
355
- test('uses TanStack route ids when loading RSC payload route data', async () => {
356
- const rootLoader = rstest.fn(() => ({ source: 'modern-root' }));
357
- const userLoader = rstest.fn(() => ({ source: 'modern-user' }));
358
- const routes: RouteObject[] = [
359
- {
360
- id: 'root',
361
- path: '/',
362
- loader: rootLoader,
363
- Component: () => null,
364
- children: [
365
- {
366
- id: 'user',
367
- path: 'user/:id',
368
- loader: userLoader,
369
- Component: () => null,
370
- },
371
- ],
372
- },
373
- ];
374
- const payload = {
375
- type: 'render',
376
- actionData: null,
377
- errors: null,
378
- loaderData: {
379
- __root__: { source: 'rsc-root' },
380
- '/user/$id': { source: 'rsc-user' },
381
- },
382
- location: { href: '/user/123' },
383
- routes: [
384
- { id: '__root__', hasLoader: true },
385
- { id: '/user/$id', hasLoader: true },
386
- ],
387
- };
388
- const fetchMock = rstest.fn(() => Promise.resolve(new Response('payload')));
389
- const decodeMock = rstest.fn(async () => payload);
390
- rstest.stubGlobal('fetch', fetchMock);
391
- rstest.stubGlobal('window', { origin: 'http://localhost' });
392
- __setTanstackRscPayloadDecoderForTests(decodeMock);
393
-
394
- const routeTree = createRouteTreeFromRouteObjects(routes, {
395
- rscPayloadRouter: true,
396
- });
397
- const router = await loadRouteTree(routeTree, '/user/123');
398
-
399
- const rootMatch = router.state.matches.find(
400
- match => match.routeId === '__root__',
153
+ () => createElement('section', null, createElement(ModernOutlet)),
154
+ ),
155
+ '/plain',
401
156
  );
402
- const userMatch = router.state.matches.find(
403
- match => match.routeId === '/user/$id',
404
- );
405
- expect(rootMatch?.loaderData).toEqual({ source: 'rsc-root' });
406
- expect(userMatch?.loaderData).toEqual({ source: 'rsc-user' });
407
- expect(rootLoader).not.toHaveBeenCalled();
408
- expect(userLoader).not.toHaveBeenCalled();
409
- expect(fetchMock).toHaveBeenCalledTimes(1);
410
- expect(decodeMock).toHaveBeenCalledTimes(1);
411
- });
412
-
413
- test('maps splat params', async () => {
414
- let splatParamValue = '';
415
- const routes: TestRouteObject[] = [
416
- {
417
- id: 'root',
418
- path: '/',
419
- Component: () => null,
420
- children: [
421
- {
422
- id: 'files',
423
- path: 'files/*',
424
- loader: ({ params }: LoaderArgs) => {
425
- splatParamValue = String(params['*'] || '');
426
- return { value: params['*'] };
427
- },
428
- Component: () => null,
429
- },
430
- ],
431
- },
432
- ];
433
-
434
- const routeTree = createRouteTreeFromRouteObjects(routes);
435
-
436
- const splatRouter = await loadRouteTree(routeTree, '/files/a/b/c');
437
- const filesMatch = splatRouter.state.matches.find(
438
- match => match.routeId === '/files/$',
439
- );
440
- expect(filesMatch?.loaderData).toEqual({ value: 'a/b/c' });
441
- expect(splatParamValue).toBe('a/b/c');
442
- });
443
-
444
- test('renders preloaded lazy child routes through TanStack router SSR', async () => {
445
- const LazyRouteComponent = () =>
446
- createElement('main', null, 'Lazy child route ready');
447
- const lazyImport = rstest.fn(async () => ({
448
- default: LazyRouteComponent,
449
- }));
450
- const routes: TestRouteObject[] = [
451
- {
452
- id: 'root',
453
- path: '/',
454
- Component: () => createElement('section', null, createElement(Outlet)),
455
- children: [
456
- {
457
- id: 'lazy',
458
- path: 'lazy',
459
- Component: lazy(lazyImport),
460
- lazyImport,
461
- },
462
- ],
463
- },
464
- ];
465
-
466
- const routeTree = createRouteTreeFromRouteObjects(routes);
467
- const router = await loadRouteTree(routeTree, '/lazy');
468
157
 
469
158
  expect(
470
- renderToStaticMarkup(createElement(RouterProvider, { router } as never)),
471
- ).toContain('Lazy child route ready');
472
- });
473
-
474
- test('exposes load-only Modern route components through TanStack preload', async () => {
475
- const load = rstest.fn(async () => 'route chunk loaded');
476
- const LoadOnlyRouteComponent = (() =>
477
- createElement('main', null, 'Load-only route ready')) as ComponentType & {
478
- load?: () => Promise<unknown>;
479
- preload?: () => Promise<unknown>;
480
- };
481
- LoadOnlyRouteComponent.load = load;
482
- const routes: TestRouteObject[] = [
483
- {
484
- id: 'root',
485
- path: '/',
486
- Component: () => createElement('section', null, createElement(Outlet)),
487
- children: [
488
- {
489
- id: 'load-only',
490
- path: 'load-only',
491
- Component: LoadOnlyRouteComponent,
492
- },
493
- ],
494
- },
495
- ];
496
-
497
- const routeTree = createRouteTreeFromRouteObjects(routes);
498
- const router = await loadRouteTree(routeTree, '/load-only');
499
- const loadOnlyRoute = getRoute(router, '/load-only');
500
- const loadOnlyComponent = loadOnlyRoute.options
501
- .component as PreloadableTestComponent;
502
-
503
- expect(typeof loadOnlyComponent.load).toBe('function');
504
- expect(typeof loadOnlyComponent.preload).toBe('function');
505
- expect(load).toHaveBeenCalledTimes(1);
506
- await loadOnlyComponent.preload?.();
507
- expect(load).toHaveBeenCalledTimes(2);
508
- });
509
-
510
- test('unwraps nested ESM route module defaults before server rendering', async () => {
511
- const LazyRouteComponent = () =>
512
- createElement('main', null, 'Nested lazy child route ready');
513
- const lazyImport = rstest.fn(async () => ({
514
- default: {
515
- default: LazyRouteComponent,
516
- },
517
- }));
518
- const routes: TestRouteObject[] = [
519
- {
520
- id: 'root',
521
- path: '/',
522
- Component: () => createElement('section', null, createElement(Outlet)),
523
- children: [
524
- {
525
- id: 'lazy',
526
- path: 'lazy',
527
- Component: lazy(
528
- lazyImport as () => Promise<{ default: ComponentType }>,
529
- ),
530
- lazyImport,
531
- },
532
- ],
533
- },
534
- ];
535
-
536
- const routeTree = createRouteTreeFromRouteObjects(routes);
537
- const router = await loadRouteTree(routeTree, '/lazy');
538
-
539
- expect(
540
- renderToStaticMarkup(createElement(RouterProvider, { router } as never)),
541
- ).toContain('Nested lazy child route ready');
542
- });
543
-
544
- test('preserves route handle and maps shouldRevalidate to shouldReload', async () => {
545
- const shouldRevalidate = rstest.fn(({ nextUrl }: ShouldRevalidateArgs) =>
546
- nextUrl.pathname.endsWith('/456'),
547
- );
548
- const routes: RouteObject[] = [
549
- {
550
- id: 'root',
551
- path: '/',
552
- config: {
553
- handle: {
554
- shell: true,
555
- },
556
- },
557
- Component: () => null,
558
- children: [
559
- {
560
- id: 'user',
561
- path: 'user/:id',
562
- handle: { auth: true },
563
- config: {
564
- handle: {
565
- role: 'admin',
566
- },
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,
567
184
  },
568
- shouldRevalidate,
569
- loader: ({ params }: LoaderArgs) => ({ id: params.id }),
570
- Component: () => null,
571
- },
572
- ],
573
- },
574
- ];
575
-
576
- const routeTree = createRouteTreeFromRouteObjects(routes);
577
- const router = await loadRouteTree(routeTree, '/user/123');
578
- const userRoute = getRoute(router, '/user/$id');
579
-
580
- expect(routeTree.options.staticData.modernRouteHandle).toEqual({
581
- shell: true,
582
- });
583
- expect(userRoute.options.staticData.modernRouteHandle).toEqual({
584
- auth: true,
585
- role: 'admin',
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');
586
196
  });
587
- expect(userRoute.options.staticData.modernRouteShouldRevalidate).toBe(
588
- shouldRevalidate,
589
- );
590
- expect(
591
- userRoute.options.shouldReload?.({
592
- location: { href: '/user/456' },
593
- params: { id: '456' },
594
- context: {
595
- request: new Request('http://localhost/user/456'),
596
- },
597
- }),
598
- ).toBe(true);
599
- expect(shouldRevalidate).toHaveBeenCalledWith(
600
- expect.objectContaining({
601
- currentParams: { id: '123' },
602
- nextParams: { id: '456' },
603
- }),
604
- );
605
- });
197
+ }
606
198
 
607
199
  test('normalizes Modern deferred loader data for TanStack SSR', async () => {
608
- const routes: TestRouteObject[] = [
609
- {
610
- id: 'root',
611
- path: '/',
612
- Component: () => null,
613
- children: [
614
- {
615
- id: 'deferred',
616
- path: 'deferred',
617
- loader: () => ({
618
- __modern_deferred: true,
619
- data: {
620
- immediate: 'ok',
621
- later: Promise.resolve('done'),
622
- },
623
- }),
624
- Component: () => null,
625
- },
626
- ],
627
- },
628
- ];
629
-
630
- const routeTree = createRouteTreeFromRouteObjects(routes);
631
- const router = await loadRouteTree(routeTree, '/deferred');
632
- const deferredMatch = router.state.matches.find(
633
- 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',
634
213
  );
635
- const loaderData = deferredMatch?.loaderData as
214
+ const loaderData = matchOf(router, '/deferred')?.loaderData as
636
215
  | { immediate: string; later: Promise<string> }
637
216
  | undefined;
638
217
 
@@ -642,79 +221,20 @@ describe('tanstack route tree from RouteObject[]', () => {
642
221
 
643
222
  test('preserves returned non-404 Response loaders as loader data', async () => {
644
223
  const response = new Response('route status payload', { status: 500 });
645
- const routes: TestRouteObject[] = [
646
- {
647
- id: 'root',
648
- path: '/',
649
- Component: () => null,
650
- children: [
651
- {
652
- id: 'broken',
653
- path: 'broken',
654
- loader: () => response,
655
- Component: () => null,
656
- },
657
- ],
658
- },
659
- ];
660
-
661
- const routeTree = createRouteTreeFromRouteObjects(routes);
662
- const router = await loadRouteTree(routeTree, '/broken');
663
- const brokenMatch = router.state.matches.find(
664
- 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',
665
234
  );
666
235
 
667
- expect(router._serverResult).toMatchObject({
668
- type: 'render',
669
- status: 200,
670
- });
671
- expect(brokenMatch?.loaderData).toBe(response);
672
- expect(brokenMatch?.error).toBeUndefined();
673
- });
674
-
675
- test('preserves generated client metadata through RouteObject conversion', () => {
676
- const modernRoutes: TestNestedRoute[] = [
677
- {
678
- type: 'nested',
679
- origin: 'config',
680
- id: 'root',
681
- isRoot: true,
682
- children: [
683
- {
684
- type: 'nested',
685
- origin: 'config',
686
- id: 'client',
687
- path: 'client',
688
- clientData: './client.data',
689
- data: './data',
690
- hasAction: true,
691
- hasClientLoader: true,
692
- hasLoader: true,
693
- inValidSSRRoute: true,
694
- isClientComponent: true,
695
- },
696
- ],
697
- },
698
- ];
699
- const routeObjects = createTanstackRouteObjectsFromConfig({
700
- routesConfig: { routes: modernRoutes },
701
- });
702
- const routeTree = createRouteTreeFromRouteObjects(routeObjects || []);
703
- const router = createRouter({
704
- routeTree,
705
- history: createMemoryHistory({
706
- initialEntries: ['/client'],
707
- }),
708
- context: {},
709
- }) as unknown as TestRouter;
710
- const clientRoute = getRouteByModernRouteId(router, 'client');
711
-
712
- expect(clientRoute.options.ssr).toBe(false);
713
- expect(clientRoute.options.staticData).toMatchObject({
714
- modernRouteHasAction: true,
715
- modernRouteHasClientLoader: true,
716
- modernRouteHasLoader: true,
717
- modernRouteIsClientComponent: true,
718
- });
236
+ expect(router._serverResult).toMatchObject({ type: 'render', status: 200 });
237
+ expect(matchOf(router, '/broken')?.loaderData).toBe(response);
238
+ expect(matchOf(router, '/broken')?.error).toBeUndefined();
719
239
  });
720
240
  });