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

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.
Files changed (54) hide show
  1. package/dist/cjs/cli/index.js +9 -0
  2. package/dist/cjs/runtime/lifecycle.js +7 -7
  3. package/dist/cjs/runtime/prefetchLink.js +2 -7
  4. package/dist/cjs/runtime/register.js +2 -2
  5. package/dist/cjs/runtime/router.js +25 -9
  6. package/dist/cjs/runtime/state.js +2 -2
  7. package/dist/esm/cli/index.mjs +9 -0
  8. package/dist/esm/runtime/lifecycle.mjs +1 -1
  9. package/dist/esm/runtime/prefetchLink.mjs +1 -6
  10. package/dist/esm/runtime/register.mjs +1 -1
  11. package/dist/esm/runtime/router.mjs +27 -11
  12. package/dist/esm/runtime/state.mjs +1 -1
  13. package/dist/esm-node/cli/index.mjs +9 -0
  14. package/dist/esm-node/runtime/lifecycle.mjs +1 -1
  15. package/dist/esm-node/runtime/prefetchLink.mjs +1 -6
  16. package/dist/esm-node/runtime/register.mjs +1 -1
  17. package/dist/esm-node/runtime/router.mjs +27 -11
  18. package/dist/esm-node/runtime/state.mjs +1 -1
  19. package/dist/types/runtime/lifecycle.d.ts +9 -2
  20. package/dist/types/runtime/register.d.ts +1 -1
  21. package/dist/types/runtime/router.d.ts +2 -13
  22. package/dist/types/runtime/state.d.ts +1 -1
  23. package/dist/types/runtime/types.d.ts +2 -1
  24. package/package.json +12 -10
  25. package/rstest.config.mts +0 -6
  26. package/src/cli/index.ts +10 -0
  27. package/src/runtime/lifecycle.ts +14 -3
  28. package/src/runtime/plugin.node.tsx +1 -1
  29. package/src/runtime/prefetchLink.tsx +1 -23
  30. package/src/runtime/register.ts +1 -1
  31. package/src/runtime/router.ts +25 -40
  32. package/src/runtime/state.ts +1 -1
  33. package/src/runtime/types.ts +2 -2
  34. package/tests/realm-isolation.test.ts +8 -2
  35. package/tests/router/cli.test.ts +105 -8
  36. package/tests/router/dataMutation.test.tsx +17 -121
  37. package/tests/router/flightSerialization.roundtrip.test.tsx +9 -6
  38. package/tests/router/generateRouteArtifacts.test.ts +69 -29
  39. package/tests/router/hooks.test.ts +1 -18
  40. package/tests/router/loaderBridge.test.ts +0 -77
  41. package/tests/router/prefetchLink.test.tsx +28 -16
  42. package/tests/router/prefetchLinkPreload.test.tsx +21 -28
  43. package/tests/router/register.test.ts +1 -1
  44. package/tests/router/routeHooks.test.ts +41 -105
  45. package/tests/router/routeTree.test.ts +0 -212
  46. package/tests/router/routerCompatibility.test.ts +32 -9
  47. package/tests/router/rsc.test.tsx +118 -136
  48. package/tests/router/rscDecoderSecurity.test.ts +24 -53
  49. package/tests/router/tanstackTypes.test.ts +24 -364
  50. package/tests/router/fastDefaults.test.ts +0 -32
  51. package/tests/router/hydrationBoundary.test.tsx +0 -23
  52. package/tests/router/packageSurface.test.ts +0 -24
  53. package/tests/router/preloadRedirect.test.ts +0 -64
  54. package/tests/router/rscPayloadRouterMatrix.test.ts +0 -197
@@ -3,12 +3,10 @@ import { createRequire } from 'node:module';
3
3
  import path from 'node:path';
4
4
 
5
5
  type DecoderScenario =
6
- | 'action-load'
7
6
  | 'async-iterator'
8
7
  | 'blob-type'
9
8
  | 'cycle'
10
- | 'form-data-roundtrip'
11
- | 'referenced-form-data';
9
+ | 'form-data-roundtrip';
12
10
 
13
11
  const resolvePackage = createRequire(
14
12
  path.join(__dirname, 'rsc-decoder-security-resolver.cjs'),
@@ -21,7 +19,6 @@ const childPath = path.join(
21
19
 
22
20
  function runDecoderScenario(options: {
23
21
  entry: 'edge' | 'node';
24
- nodeEnv: 'development' | 'production';
25
22
  scenario: DecoderScenario;
26
23
  }): Record<string, unknown> {
27
24
  const result = spawnSync(
@@ -35,7 +32,7 @@ function runDecoderScenario(options: {
35
32
  ],
36
33
  {
37
34
  encoding: 'utf8',
38
- env: { ...process.env, NODE_ENV: options.nodeEnv },
35
+ env: { ...process.env, NODE_ENV: 'production' },
39
36
  timeout: 5_000,
40
37
  },
41
38
  );
@@ -43,7 +40,7 @@ function runDecoderScenario(options: {
43
40
  if (result.error || result.status !== 0) {
44
41
  throw new Error(
45
42
  [
46
- `RSC decoder child failed for ${options.entry}/${options.nodeEnv}/${options.scenario}`,
43
+ `RSC decoder child failed for ${options.entry}/production/${options.scenario}`,
47
44
  result.stdout,
48
45
  result.stderr,
49
46
  ]
@@ -57,57 +54,31 @@ function runDecoderScenario(options: {
57
54
  }
58
55
 
59
56
  for (const entry of ['node', 'edge'] as const) {
60
- for (const nodeEnv of ['development', 'production'] as const) {
61
- describe(`${entry} ${nodeEnv} Flight decoder security`, () => {
62
- test('bounds cyclic Map reconstruction before invoking constructors', () => {
63
- expect(
64
- runDecoderScenario({ entry, nodeEnv, scenario: 'cycle' }),
65
- ).toEqual({
66
- mapArrayConstructions: 2,
67
- rejected: true,
68
- });
69
- });
70
-
71
- test('decodes referenced FormData with one backing-store enumeration', () => {
72
- expect(
73
- runDecoderScenario({
74
- entry,
75
- nodeEnv,
76
- scenario: 'referenced-form-data',
77
- }),
78
- ).toEqual({ keyIterations: 1, values: ['first', 'second'] });
79
- });
57
+ describe(`${entry} production Flight decoder security`, () => {
58
+ test('bounds cyclic Map reconstruction before invoking constructors', () => {
59
+ const result = runDecoderScenario({ entry, scenario: 'cycle' });
80
60
 
81
- test('round-trips referenced FormData through the public client and server APIs', () => {
82
- expect(
83
- runDecoderScenario({
84
- entry,
85
- nodeEnv,
86
- scenario: 'form-data-roundtrip',
87
- }),
88
- ).toEqual({ values: ['first', 'second'] });
89
- });
61
+ expect(result.rejected).toBe(true);
62
+ expect(result.mapArrayConstructions).toBeLessThanOrEqual(2);
63
+ });
90
64
 
91
- test('rejects a Blob reference backed by a string', () => {
92
- expect(
93
- runDecoderScenario({ entry, nodeEnv, scenario: 'blob-type' }),
94
- ).toEqual({
95
- rejected: true,
96
- returnedString: false,
97
- });
98
- });
65
+ test('round-trips referenced FormData through the public client and server APIs', () => {
66
+ expect(
67
+ runDecoderScenario({ entry, scenario: 'form-data-roundtrip' }),
68
+ ).toEqual({ values: ['first', 'second'] });
69
+ });
99
70
 
100
- test('does not recursively throw into a rejecting async iterator', () => {
101
- expect(
102
- runDecoderScenario({ entry, nodeEnv, scenario: 'async-iterator' }),
103
- ).toEqual({ throwCalls: 1 });
71
+ test('rejects a Blob reference backed by a string', () => {
72
+ expect(runDecoderScenario({ entry, scenario: 'blob-type' })).toEqual({
73
+ rejected: true,
74
+ returnedString: false,
104
75
  });
76
+ });
105
77
 
106
- test('loads one server module for eight distinct action identifiers', () => {
107
- expect(
108
- runDecoderScenario({ entry, nodeEnv, scenario: 'action-load' }),
109
- ).toEqual({ moduleLoads: 1, submittedEntries: [] });
110
- });
78
+ test('does not recursively throw into a rejecting async iterator', () => {
79
+ expect(runDecoderScenario({ entry, scenario: 'async-iterator' })).toEqual(
80
+ { throwCalls: 1 },
81
+ );
111
82
  });
112
- }
83
+ });
113
84
  }
@@ -36,15 +36,6 @@ import {
36
36
  } from '../../src/cli/tanstackTypes';
37
37
 
38
38
  const execFileAsync = promisify(execFile);
39
- const routerGenOxlintConfigPath = path.join(
40
- __dirname,
41
- 'fixtures',
42
- 'router-gen.oxlint.json',
43
- );
44
- const oxlintCliPath = path.resolve(
45
- __dirname,
46
- '../../../../toolkit/code-tools/node_modules/oxlint/bin/oxlint',
47
- );
48
39
  const strictestTsconfigPath = path.resolve(
49
40
  __dirname,
50
41
  '../../node_modules/@tsconfig/strictest/tsconfig.json',
@@ -96,7 +87,7 @@ async function writeRuntimeTestPackage(projectDirectory: string) {
96
87
  '};',
97
88
  'export const runtimeState: { adapterCalls: Array<{ hasSplat: boolean; modernLoader: unknown }> };',
98
89
  'export function createMemoryHistory<TOptions>(options: TOptions): TOptions;',
99
- 'export const modernTanstackRouterFastDefaults: { defaultPreload: "intent" };',
90
+ 'export const modernTanstackRouterFastDefaults: { readonly defaultStructuralSharing: true };',
100
91
  'export function createRootRouteWithContext<TContext extends ModernRouterContext>(): <const TOptions extends RouteOptions>(options: TOptions) => Route<TOptions>;',
101
92
  'export function createRoute<const TOptions extends RouteOptions>(options: TOptions): Route<TOptions>;',
102
93
  'export function createRouter<const TOptions extends { context: ModernRouterContext; history: unknown; routeTree: unknown }>(options: TOptions): TOptions;',
@@ -116,7 +107,7 @@ async function writeRuntimeTestPackage(projectDirectory: string) {
116
107
  ' },',
117
108
  '});',
118
109
  'const createMemoryHistory = options => options;',
119
- 'const modernTanstackRouterFastDefaults = { defaultPreload: "intent" };',
110
+ 'const modernTanstackRouterFastDefaults = { defaultStructuralSharing: true };',
120
111
  'const createRootRouteWithContext = () => options => createRouteRecord(options);',
121
112
  'const createRoute = options => createRouteRecord(options);',
122
113
  'const createRouter = options => options;',
@@ -493,137 +484,6 @@ describe('tanstack router type generation', () => {
493
484
  }
494
485
  });
495
486
 
496
- test('strictly compiles and executes a loader-free conventional route tree', async () => {
497
- tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
498
- const srcDirectory = path.join(tempDir, 'src');
499
- const files = new Map([
500
- [
501
- 'routes/layout.tsx',
502
- 'export default function Layout() { return null; }',
503
- ],
504
- ['routes/page.tsx', 'export default function Home() { return null; }'],
505
- [
506
- 'routes/reviews/[scanId]/page.tsx',
507
- 'export default function Review() { return null; }',
508
- ],
509
- ]);
510
-
511
- for (const [relativePath, contents] of files) {
512
- const filePath = path.join(srcDirectory, relativePath);
513
- await mkdir(path.dirname(filePath), { recursive: true });
514
- await writeFile(filePath, contents);
515
- }
516
-
517
- const { routerGenTs } = await generateTanstackRouterTypesSourceForEntry({
518
- appContext: {
519
- srcDirectory,
520
- internalSrcAlias: '@/_',
521
- } as any,
522
- entryName: 'index',
523
- routes: [
524
- {
525
- type: 'nested',
526
- id: 'layout',
527
- isRoot: true,
528
- _component: '@/_/routes/layout',
529
- children: [
530
- {
531
- type: 'nested',
532
- id: 'page',
533
- index: true,
534
- _component: '@/_/routes/page',
535
- },
536
- {
537
- type: 'nested',
538
- id: 'reviews/(scanId)/page',
539
- path: 'reviews/:scanId',
540
- _component: '@/_/routes/reviews/[scanId]/page',
541
- },
542
- ],
543
- },
544
- ] as any,
545
- });
546
-
547
- await compileAndRunGeneratedRouter({
548
- projectDirectory: tempDir,
549
- routerGenTs,
550
- runtimeCheck: [
551
- "import { runtimeState } from '@modern-js/plugin-tanstack/runtime';",
552
- "import Layout from './routes/layout';",
553
- "import Home from './routes/page';",
554
- "import Review from './routes/reviews/[scanId]/page';",
555
- "import { rootRoute, routeTree, router } from './modern-tanstack/index/router.gen';",
556
- '',
557
- 'if (runtimeState.adapterCalls.length !== 0) throw new Error("loader adapter registered without a loader");',
558
- 'if (Object.keys(router.context).length !== 0) throw new Error("router context is not empty");',
559
- 'if (rootRoute.options.component !== Layout) throw new Error("root component was not wired");',
560
- 'const [homeRoute, reviewRoute] = routeTree.children;',
561
- 'if (homeRoute.options.component !== Home) throw new Error("index component was not wired");',
562
- 'if (reviewRoute.options.component !== Review) throw new Error("review component was not wired");',
563
- 'if (reviewRoute.options.path !== "reviews/$scanId") throw new Error("route params were not translated");',
564
- ].join('\n'),
565
- });
566
- });
567
-
568
- test('executes generated loader and action wiring through route static data', async () => {
569
- tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
570
- const srcDirectory = path.join(tempDir, 'src');
571
- const routeDir = path.join(srcDirectory, 'routes', 'mf');
572
- await mkdir(routeDir, { recursive: true });
573
- await writeFile(
574
- path.join(routeDir, 'page.data.ts'),
575
- [
576
- 'export const loader = () => ({ count: 0 });',
577
- 'export const action = () => Response.json({ count: 1 });',
578
- ].join('\n'),
579
- );
580
-
581
- const { routerGenTs } = await generateTanstackRouterTypesSourceForEntry({
582
- appContext: {
583
- srcDirectory,
584
- internalSrcAlias: '@/_',
585
- } as any,
586
- entryName: 'index',
587
- routes: [
588
- {
589
- type: 'nested',
590
- id: 'layout',
591
- isRoot: true,
592
- children: [
593
- {
594
- type: 'nested',
595
- id: 'mf/page',
596
- path: 'mf',
597
- data: '@/_/routes/mf/page.data',
598
- action: '@/_/routes/mf/page.data',
599
- },
600
- ],
601
- },
602
- ] as any,
603
- });
604
-
605
- await compileAndRunGeneratedRouter({
606
- projectDirectory: tempDir,
607
- routerGenTs,
608
- runtimeCheck: [
609
- "import { runtimeState } from '@modern-js/plugin-tanstack/runtime';",
610
- "import { action, loader } from './routes/mf/page.data';",
611
- "import { routeTree } from './modern-tanstack/index/router.gen';",
612
- '',
613
- 'async function main() {',
614
- 'const [mfRoute] = routeTree.children;',
615
- 'if (runtimeState.adapterCalls.length !== 1) throw new Error("loader adapter was not registered exactly once");',
616
- 'if (runtimeState.adapterCalls[0]?.modernLoader !== loader) throw new Error("wrong loader was adapted");',
617
- 'const result = await mfRoute.options.loader({});',
618
- 'if (result.count !== 0) throw new Error("adapted loader returned the wrong value");',
619
- 'if (mfRoute.options.staticData.modernRouteLoader !== loader) throw new Error("loader static data was not wired");',
620
- 'if (mfRoute.options.staticData.modernRouteAction !== action) throw new Error("action static data was not wired");',
621
- '}',
622
- 'void main();',
623
- ].join('\n'),
624
- });
625
- });
626
-
627
487
  test('executes component sharing correctly when module resolution races', async () => {
628
488
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
629
489
  const srcDirectory = path.join(tempDir, 'src');
@@ -750,101 +610,6 @@ describe('tanstack router type generation', () => {
750
610
  routerGenTs,
751
611
  });
752
612
  });
753
-
754
- test('passes the generated-artifact lint contract', async () => {
755
- tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
756
- const srcDirectory = path.join(tempDir, 'src');
757
- const routerGenTs = await generateComprehensiveRouterGen(srcDirectory);
758
- const routerGenPath = path.join(
759
- srcDirectory,
760
- 'modern-tanstack',
761
- 'golden',
762
- 'router.gen.ts',
763
- );
764
-
765
- await mkdir(path.dirname(routerGenPath), { recursive: true });
766
- await writeFile(routerGenPath, routerGenTs);
767
-
768
- const { stderr, stdout } = await execFileAsync(
769
- process.execPath,
770
- [
771
- oxlintCliPath,
772
- routerGenPath,
773
- '--config',
774
- routerGenOxlintConfigPath,
775
- '--no-ignore',
776
- '--report-unused-disable-directives-severity',
777
- 'error',
778
- '--format',
779
- 'unix',
780
- ],
781
- { cwd: tempDir },
782
- );
783
-
784
- expect(`${stdout}${stderr}`).toBe('');
785
- });
786
-
787
- test('executes localized aliases with typed children and correct parents', async () => {
788
- tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-types-'));
789
- const srcDirectory = path.join(tempDir, 'src');
790
-
791
- const { routerGenTs } = await generateTanstackRouterTypesSourceForEntry({
792
- appContext: {
793
- srcDirectory,
794
- internalSrcAlias: '@/_',
795
- } as any,
796
- entryName: 'index',
797
- routes: [
798
- {
799
- type: 'nested',
800
- id: 'layout',
801
- isRoot: true,
802
- children: [
803
- {
804
- type: 'nested',
805
- id: '(lang)/layout',
806
- path: ':lang',
807
- children: [
808
- {
809
- type: 'nested',
810
- id: '(lang)/products/(slug)/page',
811
- path: 'products/:slug',
812
- },
813
- {
814
- type: 'nested',
815
- id: '(lang)/products/(slug)/page__localised_produkty_slug',
816
- path: 'produkty/:slug',
817
- },
818
- {
819
- type: 'nested',
820
- id: '(lang)/optional/(slug$)/page__localised_volitelne_slug',
821
- path: 'volitelne/:slug?',
822
- },
823
- ],
824
- },
825
- ],
826
- },
827
- ] as any,
828
- });
829
-
830
- await compileAndRunGeneratedRouter({
831
- projectDirectory: tempDir,
832
- routerGenTs,
833
- runtimeCheck: [
834
- "import { routeTree } from './modern-tanstack/index/router.gen';",
835
- '',
836
- 'const [localeRoute] = routeTree.children;',
837
- 'const [productRoute, localizedProductRoute, optionalRoute] = localeRoute.children;',
838
- 'if (localeRoute.options.path !== "$lang") throw new Error("locale param was not translated");',
839
- 'if (productRoute.options.path !== "products/$slug") throw new Error("product param was not translated");',
840
- 'if (localizedProductRoute.options.path !== "produkty/$slug") throw new Error("localized param was not translated");',
841
- 'if (optionalRoute.options.path !== "volitelne/{-$slug}") throw new Error("optional param was not translated");',
842
- 'for (const child of localeRoute.children) {',
843
- ' if (child.options.getParentRoute() !== localeRoute) throw new Error("localized child points at the wrong parent");',
844
- '}',
845
- ].join('\n'),
846
- });
847
- });
848
613
  });
849
614
 
850
615
  describe('collectCanonicalRoutesForEntry', () => {
@@ -1000,61 +765,6 @@ describe('collectCanonicalRoutesForEntry', () => {
1000
765
 
1001
766
  expect(result).not.toBeNull();
1002
767
  expect(Object.keys(result!)).toEqual(['/', '/about']);
1003
-
1004
- tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-canonical-'));
1005
- await typecheckCanonicalRegisterContract({
1006
- canonicalRoutes: result!,
1007
- contractLines: [
1008
- "const rootParams: UltramodernCanonicalRoutes['/'] = {};",
1009
- "const aboutParams: UltramodernCanonicalRoutes['/about'] = {};",
1010
- '// @ts-expect-error index canonical routes reject unexpected params',
1011
- "const invalidRootParams: UltramodernCanonicalRoutes['/'] = { slug: 'unexpected' };",
1012
- 'void rootParams;',
1013
- 'void aboutParams;',
1014
- 'void invalidRootParams;',
1015
- ],
1016
- projectDirectory: tempDir,
1017
- });
1018
- });
1019
-
1020
- test('converts :slug to $slug with required params type', async () => {
1021
- const result = collectCanonicalRoutesForEntry([
1022
- {
1023
- type: 'nested',
1024
- id: 'layout',
1025
- isRoot: true,
1026
- children: [
1027
- {
1028
- type: 'nested',
1029
- id: '(lang)/layout',
1030
- path: ':lang',
1031
- children: [
1032
- {
1033
- type: 'nested',
1034
- id: '(lang)/products/(slug)/page',
1035
- path: 'products/:slug',
1036
- },
1037
- ],
1038
- },
1039
- ],
1040
- },
1041
- ] as any);
1042
-
1043
- expect(result).not.toBeNull();
1044
- expect(Object.keys(result!)).toEqual(['/products/$slug']);
1045
-
1046
- tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-canonical-'));
1047
- await typecheckCanonicalRegisterContract({
1048
- canonicalRoutes: result!,
1049
- contractLines: [
1050
- "const productParams: UltramodernCanonicalRoutes['/products/$slug'] = { slug: 'tractor' };",
1051
- '// @ts-expect-error a required slug cannot be omitted',
1052
- "const missingProductParams: UltramodernCanonicalRoutes['/products/$slug'] = {};",
1053
- 'void productParams;',
1054
- 'void missingProductParams;',
1055
- ],
1056
- projectDirectory: tempDir,
1057
- });
1058
768
  });
1059
769
 
1060
770
  test('converts :slug? to optional {-$slug} with optional params type', async () => {
@@ -1082,21 +792,6 @@ describe('collectCanonicalRoutesForEntry', () => {
1082
792
 
1083
793
  expect(result).not.toBeNull();
1084
794
  expect(Object.keys(result!)).toEqual(['/optional/{-$slug}']);
1085
-
1086
- tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-canonical-'));
1087
- await typecheckCanonicalRegisterContract({
1088
- canonicalRoutes: result!,
1089
- contractLines: [
1090
- "const omittedOptionalParams: UltramodernCanonicalRoutes['/optional/{-$slug}'] = {};",
1091
- "const presentOptionalParams: UltramodernCanonicalRoutes['/optional/{-$slug}'] = { slug: 'tractor' };",
1092
- '// @ts-expect-error optional canonical params still retain their string type',
1093
- "const invalidOptionalParams: UltramodernCanonicalRoutes['/optional/{-$slug}'] = { slug: 42 };",
1094
- 'void omittedOptionalParams;',
1095
- 'void presentOptionalParams;',
1096
- 'void invalidOptionalParams;',
1097
- ],
1098
- projectDirectory: tempDir,
1099
- });
1100
795
  });
1101
796
 
1102
797
  test('converts * splat to $ with optional _splat param', async () => {
@@ -1124,21 +819,6 @@ describe('collectCanonicalRoutesForEntry', () => {
1124
819
 
1125
820
  expect(result).not.toBeNull();
1126
821
  expect(Object.keys(result!)).toEqual(['/files/$']);
1127
-
1128
- tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-canonical-'));
1129
- await typecheckCanonicalRegisterContract({
1130
- canonicalRoutes: result!,
1131
- contractLines: [
1132
- "const emptySplatParams: UltramodernCanonicalRoutes['/files/$'] = {};",
1133
- "const splatParams: UltramodernCanonicalRoutes['/files/$'] = { _splat: 'guides/intro' };",
1134
- '// @ts-expect-error splat params retain their string type',
1135
- "const invalidSplatParams: UltramodernCanonicalRoutes['/files/$'] = { _splat: 42 };",
1136
- 'void emptySplatParams;',
1137
- 'void splatParams;',
1138
- 'void invalidSplatParams;',
1139
- ],
1140
- projectDirectory: tempDir,
1141
- });
1142
822
  });
1143
823
 
1144
824
  test('collapses localized variants with shared modernCanonicalPath to one canonical key', async () => {
@@ -1182,67 +862,47 @@ describe('collectCanonicalRoutesForEntry', () => {
1182
862
  expect(result).not.toBeNull();
1183
863
  // Two physical variants share the same canonical path — only one entry.
1184
864
  const keys = Object.keys(result!);
1185
- // /products/$slug and /optional/{-$slug} — exactly 2 keys with params
1186
- expect(keys.filter(k => k.startsWith('/products'))).toHaveLength(1);
1187
- expect(keys).toHaveLength(2);
1188
865
  expect(keys).toEqual(['/optional/{-$slug}', '/products/$slug']);
1189
866
  // The Czech localized path must not appear as a separate key.
1190
867
  expect('/produkty/$slug' in result!).toBe(false);
1191
868
 
1192
869
  tempDir = await mkdtemp(path.join(tmpdir(), 'modern-tanstack-canonical-'));
1193
870
  await typecheckCanonicalRegisterContract({
1194
- canonicalRoutes: result!,
871
+ canonicalRoutes: {
872
+ '/': 'Record<string, never>',
873
+ '/files/$': '{ _splat?: string }',
874
+ ...result!,
875
+ },
1195
876
  contractLines: [
877
+ "const rootParams: UltramodernCanonicalRoutes['/'] = {};",
878
+ '// @ts-expect-error index canonical routes reject unexpected params',
879
+ "const invalidRootParams: UltramodernCanonicalRoutes['/'] = { slug: 'unexpected' };",
1196
880
  "const productParams: UltramodernCanonicalRoutes['/products/$slug'] = { slug: 'tractor' };",
1197
881
  "const optionalParams: UltramodernCanonicalRoutes['/optional/{-$slug}'] = {};",
882
+ "const presentOptionalParams: UltramodernCanonicalRoutes['/optional/{-$slug}'] = { slug: 'tractor' };",
883
+ '// @ts-expect-error optional canonical params still retain their string type',
884
+ "const invalidOptionalParams: UltramodernCanonicalRoutes['/optional/{-$slug}'] = { slug: 42 };",
885
+ "const emptySplatParams: UltramodernCanonicalRoutes['/files/$'] = {};",
886
+ "const splatParams: UltramodernCanonicalRoutes['/files/$'] = { _splat: 'guides/intro' };",
887
+ '// @ts-expect-error splat params retain their string type',
888
+ "const invalidSplatParams: UltramodernCanonicalRoutes['/files/$'] = { _splat: 42 };",
1198
889
  '// @ts-expect-error localized physical variants do not become canonical keys',
1199
890
  "declare const localizedParams: UltramodernCanonicalRoutes['/produkty/$slug'];",
1200
891
  '// @ts-expect-error the deduplicated product route still requires its param',
1201
892
  "const missingProductParams: UltramodernCanonicalRoutes['/products/$slug'] = {};",
893
+ 'void rootParams;',
1202
894
  'void productParams;',
1203
895
  'void optionalParams;',
896
+ 'void presentOptionalParams;',
897
+ 'void emptySplatParams;',
898
+ 'void splatParams;',
899
+ 'void invalidRootParams;',
900
+ 'void invalidOptionalParams;',
901
+ 'void invalidSplatParams;',
1204
902
  'void localizedParams;',
1205
903
  'void missingProductParams;',
1206
904
  ],
1207
905
  projectDirectory: tempDir,
1208
906
  });
1209
907
  });
1210
-
1211
- test('output is sorted alphabetically by canonical key', () => {
1212
- const result = collectCanonicalRoutesForEntry([
1213
- {
1214
- type: 'nested',
1215
- id: 'layout',
1216
- isRoot: true,
1217
- children: [
1218
- {
1219
- type: 'nested',
1220
- id: '(lang)/layout',
1221
- path: ':lang',
1222
- children: [
1223
- {
1224
- type: 'nested',
1225
- id: '(lang)/products/(slug)/page',
1226
- path: 'products/:slug',
1227
- },
1228
- {
1229
- type: 'nested',
1230
- id: '(lang)/about/page',
1231
- path: 'about',
1232
- },
1233
- {
1234
- type: 'nested',
1235
- id: '(lang)/page',
1236
- index: true,
1237
- },
1238
- ],
1239
- },
1240
- ],
1241
- },
1242
- ] as any);
1243
-
1244
- expect(result).not.toBeNull();
1245
- const keys = Object.keys(result!);
1246
- expect(keys).toEqual([...keys].sort((a, b) => a.localeCompare(b)));
1247
- });
1248
908
  });
@@ -1,32 +0,0 @@
1
- import {
2
- getModernTanstackRouterFastDefaults,
3
- modernTanstackRouterFastDefaults,
4
- } from '../../src/runtime/types';
5
-
6
- describe('tanstack router fast defaults', () => {
7
- test('enables structural sharing by default', () => {
8
- expect(modernTanstackRouterFastDefaults).toEqual({
9
- defaultStructuralSharing: true,
10
- });
11
- expect(getModernTanstackRouterFastDefaults()).toEqual({
12
- defaultStructuralSharing: true,
13
- });
14
- });
15
-
16
- test('allows explicit structural sharing override', () => {
17
- expect(
18
- getModernTanstackRouterFastDefaults({
19
- defaultStructuralSharing: true,
20
- }),
21
- ).toEqual({
22
- defaultStructuralSharing: true,
23
- });
24
- expect(
25
- getModernTanstackRouterFastDefaults({
26
- defaultStructuralSharing: false,
27
- }),
28
- ).toEqual({
29
- defaultStructuralSharing: false,
30
- });
31
- });
32
- });
@@ -1,23 +0,0 @@
1
- import { isValidElement, Suspense } from 'react';
2
- import { wrapTanstackSsrHydrationBoundary } from '../../src/runtime/hydrationBoundary';
3
-
4
- describe('tanstack SSR hydration boundary', () => {
5
- it('wraps SSR hydration content in a Suspense boundary', () => {
6
- const routerContent = <main data-testid="router" />;
7
-
8
- const wrapped = wrapTanstackSsrHydrationBoundary(routerContent, true);
9
-
10
- expect(isValidElement(wrapped)).toBe(true);
11
- expect(wrapped.type).toBe(Suspense);
12
- expect(wrapped.props.fallback).toBe(null);
13
- expect(wrapped.props.children).toBe(routerContent);
14
- });
15
-
16
- it('keeps non-SSR router content unwrapped', () => {
17
- const routerContent = <main data-testid="router" />;
18
-
19
- expect(wrapTanstackSsrHydrationBoundary(routerContent, false)).toBe(
20
- routerContent,
21
- );
22
- });
23
- });
@@ -1,24 +0,0 @@
1
- import { readFileSync } from 'node:fs';
2
- import path from 'node:path';
3
-
4
- describe('tanstack package public surface', () => {
5
- test('package manifest exposes the runtime subpath used by app fixtures', () => {
6
- const packageJson = JSON.parse(
7
- readFileSync(path.join(__dirname, '../../package.json'), 'utf-8'),
8
- ) as {
9
- exports: Record<string, unknown>;
10
- typesVersions?: Record<string, Record<string, string[]>>;
11
- };
12
-
13
- expect(packageJson.exports['./runtime']).toEqual({
14
- types: './dist/types/runtime/index.d.ts',
15
- node: {
16
- module: './dist/esm/runtime/index.mjs',
17
- },
18
- default: './dist/esm/runtime/index.mjs',
19
- });
20
- expect(packageJson.typesVersions?.['*']?.runtime).toEqual([
21
- './dist/types/runtime/index.d.ts',
22
- ]);
23
- });
24
- });