@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.
- package/dist/cjs/cli/index.js +9 -0
- package/dist/cjs/runtime/lifecycle.js +7 -7
- package/dist/cjs/runtime/prefetchLink.js +2 -7
- package/dist/cjs/runtime/register.js +2 -2
- package/dist/cjs/runtime/router.js +25 -9
- package/dist/cjs/runtime/state.js +2 -2
- package/dist/esm/cli/index.mjs +9 -0
- package/dist/esm/runtime/lifecycle.mjs +1 -1
- package/dist/esm/runtime/prefetchLink.mjs +1 -6
- package/dist/esm/runtime/register.mjs +1 -1
- package/dist/esm/runtime/router.mjs +27 -11
- package/dist/esm/runtime/state.mjs +1 -1
- package/dist/esm-node/cli/index.mjs +9 -0
- package/dist/esm-node/runtime/lifecycle.mjs +1 -1
- package/dist/esm-node/runtime/prefetchLink.mjs +1 -6
- package/dist/esm-node/runtime/register.mjs +1 -1
- package/dist/esm-node/runtime/router.mjs +27 -11
- package/dist/esm-node/runtime/state.mjs +1 -1
- package/dist/types/runtime/lifecycle.d.ts +9 -2
- package/dist/types/runtime/register.d.ts +1 -1
- package/dist/types/runtime/router.d.ts +2 -13
- package/dist/types/runtime/state.d.ts +1 -1
- package/dist/types/runtime/types.d.ts +2 -1
- package/package.json +12 -10
- package/rstest.config.mts +0 -6
- package/src/cli/index.ts +10 -0
- package/src/runtime/lifecycle.ts +14 -3
- package/src/runtime/plugin.node.tsx +1 -1
- package/src/runtime/prefetchLink.tsx +1 -23
- package/src/runtime/register.ts +1 -1
- package/src/runtime/router.ts +25 -40
- package/src/runtime/state.ts +1 -1
- package/src/runtime/types.ts +2 -2
- package/tests/realm-isolation.test.ts +8 -2
- package/tests/router/cli.test.ts +105 -8
- package/tests/router/dataMutation.test.tsx +17 -121
- package/tests/router/flightSerialization.roundtrip.test.tsx +9 -6
- package/tests/router/generateRouteArtifacts.test.ts +69 -29
- package/tests/router/hooks.test.ts +1 -18
- package/tests/router/loaderBridge.test.ts +0 -77
- package/tests/router/prefetchLink.test.tsx +28 -16
- package/tests/router/prefetchLinkPreload.test.tsx +21 -28
- package/tests/router/register.test.ts +1 -1
- package/tests/router/routeHooks.test.ts +41 -105
- package/tests/router/routeTree.test.ts +0 -212
- package/tests/router/routerCompatibility.test.ts +32 -9
- package/tests/router/rsc.test.tsx +118 -136
- package/tests/router/rscDecoderSecurity.test.ts +24 -53
- package/tests/router/tanstackTypes.test.ts +24 -364
- package/tests/router/fastDefaults.test.ts +0 -32
- 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/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:
|
|
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.
|
|
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
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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: {
|
|
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 = {
|
|
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:
|
|
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
|
-
});
|