@bleedingdev/modern-js-plugin-tanstack 3.2.0-ultramodern.12 → 3.2.0-ultramodern.121
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 +89 -31
- package/dist/cjs/cli/routeSplitting.js +55 -0
- package/dist/cjs/cli/tanstackTypes.js +172 -170
- package/dist/cjs/cli.js +12 -8
- package/dist/cjs/runtime/basepathRewrite.js +12 -8
- package/dist/cjs/runtime/dataMutation.js +9 -5
- package/dist/cjs/runtime/hooks.js +20 -19
- package/dist/cjs/runtime/hydrationBoundary.js +48 -0
- package/dist/cjs/runtime/index.js +79 -35
- package/dist/cjs/runtime/lifecycle.js +21 -91
- package/dist/cjs/runtime/loaderBridge.js +173 -0
- package/dist/cjs/runtime/outlet.js +58 -0
- package/dist/cjs/runtime/plugin.js +195 -114
- package/dist/cjs/runtime/plugin.node.js +45 -45
- package/dist/cjs/runtime/plugin.worker.js +53 -0
- package/dist/cjs/runtime/pluginCore.js +55 -0
- package/dist/cjs/runtime/prefetchLink.js +10 -6
- package/dist/cjs/runtime/register.js +56 -0
- package/dist/cjs/runtime/routeTree.js +74 -207
- package/dist/cjs/runtime/router.js +41 -0
- package/dist/cjs/runtime/rsc/ClientSlot.js +9 -5
- package/dist/cjs/runtime/rsc/CompositeComponent.js +9 -5
- package/dist/cjs/runtime/rsc/ReplayableStream.js +14 -9
- package/dist/cjs/runtime/rsc/RscNodeRenderer.js +9 -5
- package/dist/cjs/runtime/rsc/SlotContext.js +9 -5
- package/dist/cjs/runtime/rsc/client.js +9 -5
- package/dist/cjs/runtime/rsc/createRscProxy.js +9 -5
- package/dist/cjs/runtime/rsc/index.js +9 -5
- package/dist/cjs/runtime/rsc/payloadRouter.js +44 -6
- package/dist/cjs/runtime/rsc/server.js +9 -5
- package/dist/cjs/runtime/rsc/slotUsageSanitizer.js +9 -5
- package/dist/cjs/runtime/rsc/symbols.js +20 -15
- package/dist/cjs/runtime/state.js +45 -0
- package/dist/cjs/runtime/types.js +31 -1
- package/dist/cjs/runtime/utils.js +9 -10
- package/dist/cjs/runtime.js +9 -5
- package/dist/esm/cli/index.mjs +75 -27
- package/dist/esm/cli/routeSplitting.mjs +14 -0
- package/dist/esm/cli/tanstackTypes.mjs +158 -160
- package/dist/esm/runtime/hooks.mjs +1 -8
- package/dist/esm/runtime/hydrationBoundary.mjs +10 -0
- package/dist/esm/runtime/index.mjs +5 -2
- package/dist/esm/runtime/lifecycle.mjs +1 -82
- package/dist/esm/runtime/loaderBridge.mjs +114 -0
- package/dist/esm/runtime/outlet.mjs +17 -0
- package/dist/esm/runtime/plugin.mjs +191 -114
- package/dist/esm/runtime/plugin.node.mjs +40 -44
- package/dist/esm/runtime/plugin.worker.mjs +1 -0
- package/dist/esm/runtime/pluginCore.mjs +14 -0
- package/dist/esm/runtime/prefetchLink.mjs +1 -1
- package/dist/esm/runtime/register.mjs +18 -0
- package/dist/esm/runtime/routeTree.mjs +59 -193
- package/dist/esm/runtime/router.mjs +2 -0
- package/dist/esm/runtime/rsc/payloadRouter.mjs +35 -1
- package/dist/esm/runtime/state.mjs +7 -0
- package/dist/esm/runtime/types.mjs +7 -0
- package/dist/esm/runtime/utils.mjs +0 -5
- package/dist/esm-node/cli/index.mjs +75 -27
- package/dist/esm-node/cli/routeSplitting.mjs +15 -0
- package/dist/esm-node/cli/tanstackTypes.mjs +158 -160
- package/dist/esm-node/runtime/hooks.mjs +1 -8
- package/dist/esm-node/runtime/hydrationBoundary.mjs +11 -0
- package/dist/esm-node/runtime/index.mjs +5 -2
- package/dist/esm-node/runtime/lifecycle.mjs +1 -82
- package/dist/esm-node/runtime/loaderBridge.mjs +115 -0
- package/dist/esm-node/runtime/outlet.mjs +18 -0
- package/dist/esm-node/runtime/plugin.mjs +191 -114
- package/dist/esm-node/runtime/plugin.node.mjs +40 -44
- package/dist/esm-node/runtime/plugin.worker.mjs +2 -0
- package/dist/esm-node/runtime/pluginCore.mjs +15 -0
- package/dist/esm-node/runtime/prefetchLink.mjs +1 -1
- package/dist/esm-node/runtime/register.mjs +19 -0
- package/dist/esm-node/runtime/routeTree.mjs +59 -193
- package/dist/esm-node/runtime/router.mjs +3 -0
- package/dist/esm-node/runtime/rsc/payloadRouter.mjs +35 -1
- package/dist/esm-node/runtime/state.mjs +8 -0
- package/dist/esm-node/runtime/types.mjs +7 -0
- package/dist/esm-node/runtime/utils.mjs +0 -5
- package/dist/types/cli/index.d.ts +14 -1
- package/dist/types/cli/routeSplitting.d.ts +20 -0
- package/dist/types/cli/tanstackTypes.d.ts +21 -1
- package/dist/types/runtime/hooks.d.ts +8 -33
- package/dist/types/runtime/hydrationBoundary.d.ts +2 -0
- package/dist/types/runtime/index.d.ts +8 -3
- package/dist/types/runtime/lifecycle.d.ts +7 -22
- package/dist/types/runtime/loaderBridge.d.ts +48 -0
- package/dist/types/runtime/outlet.d.ts +2 -0
- package/dist/types/runtime/plugin.d.ts +2 -15
- package/dist/types/runtime/plugin.node.d.ts +2 -15
- package/dist/types/runtime/plugin.worker.d.ts +1 -0
- package/dist/types/runtime/pluginCore.d.ts +21 -0
- package/dist/types/runtime/register.d.ts +9 -0
- package/dist/types/runtime/routeTree.d.ts +0 -2
- package/dist/types/runtime/router.d.ts +14 -0
- package/dist/types/runtime/state.d.ts +16 -0
- package/dist/types/runtime/types.d.ts +14 -53
- package/package.json +42 -40
- package/rstest.config.mts +6 -0
- package/src/cli/index.ts +162 -23
- package/src/cli/routeSplitting.ts +43 -0
- package/src/cli/tanstackTypes.ts +331 -187
- package/src/runtime/hooks.ts +10 -27
- package/src/runtime/hydrationBoundary.tsx +12 -0
- package/src/runtime/index.tsx +17 -7
- package/src/runtime/lifecycle.ts +16 -151
- package/src/runtime/loaderBridge.ts +257 -0
- package/src/runtime/outlet.tsx +48 -0
- package/src/runtime/plugin.node.tsx +72 -85
- package/src/runtime/plugin.tsx +361 -206
- package/src/runtime/plugin.worker.tsx +4 -0
- package/src/runtime/pluginCore.ts +48 -0
- package/src/runtime/prefetchLink.tsx +1 -1
- package/src/runtime/register.ts +58 -0
- package/src/runtime/routeTree.ts +163 -354
- package/src/runtime/router.ts +15 -0
- package/src/runtime/rsc/payloadRouter.ts +45 -2
- package/src/runtime/ssr-shim.d.ts +1 -3
- package/src/runtime/state.ts +29 -0
- package/src/runtime/types.ts +32 -66
- package/src/runtime/utils.tsx +3 -6
- package/tests/router/cli.test.ts +586 -5
- package/tests/router/fastDefaults.test.ts +25 -0
- package/tests/router/hooks.test.ts +26 -0
- package/tests/router/hydrationBoundary.test.tsx +23 -0
- package/tests/router/loaderBridge.test.ts +211 -0
- package/tests/router/packageSurface.test.ts +24 -0
- package/tests/router/prefetchLink.test.tsx +43 -7
- package/tests/router/register.test.ts +46 -0
- package/tests/router/routeTree.test.ts +381 -81
- package/tests/router/rsc.test.tsx +70 -0
- package/tests/router/tanstackTypes.test.ts +573 -1
- package/dist/cjs/runtime/DefaultNotFound.js +0 -47
- package/dist/esm/runtime/DefaultNotFound.mjs +0 -13
- package/dist/esm-node/runtime/DefaultNotFound.mjs +0 -14
- package/dist/types/runtime/DefaultNotFound.d.ts +0 -2
- package/src/runtime/DefaultNotFound.tsx +0 -15
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
});
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { isNotFound, isRedirect } from '@tanstack/react-router';
|
|
2
|
+
import {
|
|
3
|
+
createRouteStaticData,
|
|
4
|
+
isAbsoluteUrl,
|
|
5
|
+
mapSplatParamsForModernLoader,
|
|
6
|
+
modernLoaderToTanstack,
|
|
7
|
+
throwTanstackRedirect,
|
|
8
|
+
} from '../../src/runtime/loaderBridge';
|
|
9
|
+
|
|
10
|
+
type RedirectLike = {
|
|
11
|
+
options?: {
|
|
12
|
+
href?: string;
|
|
13
|
+
to?: string;
|
|
14
|
+
};
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
function catchThrown(fn: () => unknown): unknown {
|
|
18
|
+
try {
|
|
19
|
+
fn();
|
|
20
|
+
} catch (err) {
|
|
21
|
+
return err;
|
|
22
|
+
}
|
|
23
|
+
throw new Error('expected the function to throw');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe('throwTanstackRedirect', () => {
|
|
27
|
+
test('absolute URLs redirect via href (external), not via to', () => {
|
|
28
|
+
// The old inline codegen handler threw `redirect({ href })` INSIDE a
|
|
29
|
+
// try block whose catch replaced it with `redirect({ to: absoluteUrl })`,
|
|
30
|
+
// making TanStack treat the absolute URL as an internal path.
|
|
31
|
+
const thrown = catchThrown(() =>
|
|
32
|
+
throwTanstackRedirect('https://example.com/external'),
|
|
33
|
+
) as RedirectLike;
|
|
34
|
+
|
|
35
|
+
expect(isRedirect(thrown)).toBe(true);
|
|
36
|
+
expect(thrown.options?.href).toBe('https://example.com/external');
|
|
37
|
+
expect(thrown.options?.to).toBeUndefined();
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
test('relative paths redirect via to so the basepath rewrite applies', () => {
|
|
41
|
+
const thrown = catchThrown(() =>
|
|
42
|
+
throwTanstackRedirect('/dashboard'),
|
|
43
|
+
) as RedirectLike;
|
|
44
|
+
|
|
45
|
+
expect(isRedirect(thrown)).toBe(true);
|
|
46
|
+
expect(thrown.options?.to).toBe('/dashboard');
|
|
47
|
+
expect(thrown.options?.href).toBeUndefined();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test('empty location falls back to /', () => {
|
|
51
|
+
const thrown = catchThrown(() => throwTanstackRedirect('')) as RedirectLike;
|
|
52
|
+
expect(thrown.options?.to).toBe('/');
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
describe('isAbsoluteUrl', () => {
|
|
57
|
+
test('detects absolute and relative URLs', () => {
|
|
58
|
+
expect(isAbsoluteUrl('https://example.com/a')).toBe(true);
|
|
59
|
+
expect(isAbsoluteUrl('mailto:x@example.com')).toBe(true);
|
|
60
|
+
expect(isAbsoluteUrl('/internal/path')).toBe(false);
|
|
61
|
+
expect(isAbsoluteUrl('relative')).toBe(false);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
describe('mapSplatParamsForModernLoader', () => {
|
|
66
|
+
test('maps TanStack _splat to React Router * only for splat routes', () => {
|
|
67
|
+
expect(
|
|
68
|
+
mapSplatParamsForModernLoader({ _splat: 'a/b', id: '1' }, true),
|
|
69
|
+
).toEqual({ '*': 'a/b', id: '1' });
|
|
70
|
+
expect(
|
|
71
|
+
mapSplatParamsForModernLoader({ _splat: 'a/b', id: '1' }, false),
|
|
72
|
+
).toEqual({ _splat: 'a/b', id: '1' });
|
|
73
|
+
expect(mapSplatParamsForModernLoader({ id: '1' }, true)).toEqual({
|
|
74
|
+
id: '1',
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('createRouteStaticData', () => {
|
|
80
|
+
test('drops empty fields', () => {
|
|
81
|
+
const loader = () => null;
|
|
82
|
+
expect(createRouteStaticData({})).toEqual({});
|
|
83
|
+
expect(createRouteStaticData({ modernRouteId: '' })).toEqual({});
|
|
84
|
+
expect(
|
|
85
|
+
createRouteStaticData({
|
|
86
|
+
modernRouteId: 'page',
|
|
87
|
+
modernRouteLoader: loader,
|
|
88
|
+
}),
|
|
89
|
+
).toEqual({ modernRouteId: 'page', modernRouteLoader: loader });
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('modernLoaderToTanstack', () => {
|
|
94
|
+
const baseCtx = {
|
|
95
|
+
location: { href: 'http://localhost/products/1' },
|
|
96
|
+
params: { id: '1' },
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
test('passes request/params/context through to the modern loader', async () => {
|
|
100
|
+
const seen: { request?: Request; params?: unknown; context?: unknown } = {};
|
|
101
|
+
const loader = modernLoaderToTanstack({ hasSplat: false }, (args: any) => {
|
|
102
|
+
seen.request = args.request;
|
|
103
|
+
seen.params = args.params;
|
|
104
|
+
seen.context = args.context;
|
|
105
|
+
return { ok: true };
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
await expect(
|
|
109
|
+
loader({
|
|
110
|
+
...baseCtx,
|
|
111
|
+
context: { requestContext: { user: 'u1' } },
|
|
112
|
+
}),
|
|
113
|
+
).resolves.toEqual({ ok: true });
|
|
114
|
+
expect(seen.request).toBeInstanceOf(Request);
|
|
115
|
+
expect(seen.request?.url).toBe('http://localhost/products/1');
|
|
116
|
+
expect(seen.params).toEqual({ id: '1' });
|
|
117
|
+
expect(seen.context).toEqual({ user: 'u1' });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
test('translates an absolute-URL redirect Response into redirect({ href })', async () => {
|
|
121
|
+
const loader = modernLoaderToTanstack({ hasSplat: false }, () =>
|
|
122
|
+
Response.redirect('https://example.com/away', 302),
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const thrown = (await loader(baseCtx).then(
|
|
126
|
+
() => {
|
|
127
|
+
throw new Error('expected redirect');
|
|
128
|
+
},
|
|
129
|
+
(err: unknown) => err,
|
|
130
|
+
)) as RedirectLike;
|
|
131
|
+
|
|
132
|
+
expect(isRedirect(thrown)).toBe(true);
|
|
133
|
+
expect(thrown.options?.href).toBe('https://example.com/away');
|
|
134
|
+
expect(thrown.options?.to).toBeUndefined();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
test('translates a relative redirect Response into redirect({ to })', async () => {
|
|
138
|
+
const loader = modernLoaderToTanstack(
|
|
139
|
+
{ hasSplat: false },
|
|
140
|
+
() =>
|
|
141
|
+
new Response(null, { status: 302, headers: { Location: '/login' } }),
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
const thrown = (await loader(baseCtx).then(
|
|
145
|
+
() => {
|
|
146
|
+
throw new Error('expected redirect');
|
|
147
|
+
},
|
|
148
|
+
(err: unknown) => err,
|
|
149
|
+
)) as RedirectLike;
|
|
150
|
+
|
|
151
|
+
expect(isRedirect(thrown)).toBe(true);
|
|
152
|
+
expect(thrown.options?.to).toBe('/login');
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test('translates a 404 Response into notFound()', async () => {
|
|
156
|
+
const loader = modernLoaderToTanstack(
|
|
157
|
+
{ hasSplat: false },
|
|
158
|
+
() => new Response(null, { status: 404 }),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
const thrown = await loader(baseCtx).then(
|
|
162
|
+
() => {
|
|
163
|
+
throw new Error('expected notFound');
|
|
164
|
+
},
|
|
165
|
+
(err: unknown) => err,
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
expect(isNotFound(thrown)).toBe(true);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
test('preserves returned non-404 error Responses as loader results', async () => {
|
|
172
|
+
const response = new Response('loader exploded', { status: 500 });
|
|
173
|
+
const loader = modernLoaderToTanstack({ hasSplat: false }, () => response);
|
|
174
|
+
|
|
175
|
+
await expect(loader(baseCtx)).resolves.toBe(response);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
test('translates redirect Responses thrown synchronously by the loader', () => {
|
|
179
|
+
const loader = modernLoaderToTanstack({ hasSplat: false }, () => {
|
|
180
|
+
throw new Response(null, {
|
|
181
|
+
status: 301,
|
|
182
|
+
headers: { Location: 'https://example.com/moved' },
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// A synchronous loader throw surfaces synchronously (TanStack handles
|
|
187
|
+
// thrown redirects from the loader call itself).
|
|
188
|
+
const thrown = catchThrown(() => loader(baseCtx)) as RedirectLike;
|
|
189
|
+
|
|
190
|
+
expect(isRedirect(thrown)).toBe(true);
|
|
191
|
+
expect(thrown.options?.href).toBe('https://example.com/moved');
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
test('re-throws TanStack redirects thrown by the loader untouched', async () => {
|
|
195
|
+
const loader = modernLoaderToTanstack({ hasSplat: false }, async () => {
|
|
196
|
+
throwTanstackRedirect('/inner');
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
const thrown = (await loader(baseCtx).then(
|
|
200
|
+
() => {
|
|
201
|
+
throw new Error('expected redirect');
|
|
202
|
+
},
|
|
203
|
+
(err: unknown) => err,
|
|
204
|
+
)) as RedirectLike;
|
|
205
|
+
|
|
206
|
+
// The bridge must not re-translate its own redirect (a Response without
|
|
207
|
+
// a Location header) — that used to collapse internal targets to '/'.
|
|
208
|
+
expect(isRedirect(thrown)).toBe(true);
|
|
209
|
+
expect(thrown.options?.to).toBe('/inner');
|
|
210
|
+
});
|
|
211
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
});
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { render } from '@testing-library/react';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { Link } from '../../src/runtime/prefetchLink';
|
|
3
|
+
import { Link, NavLink } from '../../src/runtime/prefetchLink';
|
|
4
4
|
|
|
5
5
|
type MockLinkProps = {
|
|
6
6
|
children?: React.ReactNode;
|
|
@@ -21,23 +21,59 @@ describe('tanstack prefetch link adapter', () => {
|
|
|
21
21
|
capturedPreloads = [];
|
|
22
22
|
});
|
|
23
23
|
|
|
24
|
-
it('
|
|
24
|
+
it('defaults TanStack preload to viewport', () => {
|
|
25
|
+
render(<Link to="/settings">Settings</Link>);
|
|
26
|
+
|
|
27
|
+
expect(capturedPreloads).toEqual(['viewport']);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('preserves explicit preload', () => {
|
|
25
31
|
render(
|
|
26
|
-
<Link to="/settings" prefetch="
|
|
32
|
+
<Link to="/settings" prefetch="render" preload="intent">
|
|
27
33
|
Settings
|
|
28
34
|
</Link>,
|
|
29
35
|
);
|
|
30
36
|
|
|
31
|
-
expect(capturedPreloads).toEqual(['
|
|
37
|
+
expect(capturedPreloads).toEqual(['intent']);
|
|
32
38
|
});
|
|
33
39
|
|
|
34
|
-
it('
|
|
40
|
+
it('preserves explicit disabled preload', () => {
|
|
35
41
|
render(
|
|
36
|
-
<Link to="/settings" prefetch="
|
|
42
|
+
<Link to="/settings" prefetch="render" preload={false}>
|
|
37
43
|
Settings
|
|
38
44
|
</Link>,
|
|
39
45
|
);
|
|
40
46
|
|
|
41
|
-
expect(capturedPreloads).toEqual([
|
|
47
|
+
expect(capturedPreloads).toEqual([false]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('maps none prefetch to disabled TanStack preload', () => {
|
|
51
|
+
render(
|
|
52
|
+
<Link to="/settings" prefetch="none">
|
|
53
|
+
Settings
|
|
54
|
+
</Link>,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(capturedPreloads).toEqual([false]);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it.each([
|
|
61
|
+
'intent',
|
|
62
|
+
'render',
|
|
63
|
+
'viewport',
|
|
64
|
+
] as const)('maps %s prefetch to TanStack preload', prefetch => {
|
|
65
|
+
render(
|
|
66
|
+
<Link to="/settings" prefetch={prefetch}>
|
|
67
|
+
Settings
|
|
68
|
+
</Link>,
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
expect(capturedPreloads).toEqual([prefetch]);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('defaults NavLink preload to viewport', () => {
|
|
75
|
+
render(<NavLink to="/settings">Settings</NavLink>);
|
|
76
|
+
|
|
77
|
+
expect(capturedPreloads).toEqual(['viewport']);
|
|
42
78
|
});
|
|
43
79
|
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { resolveRouterProvider } from '@modern-js/runtime/context';
|
|
2
|
+
import {
|
|
3
|
+
Form,
|
|
4
|
+
Link,
|
|
5
|
+
NavLink,
|
|
6
|
+
Outlet,
|
|
7
|
+
RouteActionResponseError,
|
|
8
|
+
useFetcher,
|
|
9
|
+
} from '../../src/runtime';
|
|
10
|
+
import { tanstackRouterCompatBindings } from '../../src/runtime/register';
|
|
11
|
+
|
|
12
|
+
const COMPAT_BINDINGS_SLOT = Symbol.for(
|
|
13
|
+
'@modern-js/plugin-tanstack:runtime-compat-bindings',
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
describe("'@modern-js/plugin-tanstack/runtime' import side effects", () => {
|
|
17
|
+
it('registers the tanstack router provider', () => {
|
|
18
|
+
expect(typeof resolveRouterProvider('tanstack')).toBe('function');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("publishes the compat bindings consumed by '@modern-js/runtime/tanstack-router'", () => {
|
|
22
|
+
const bindings = (globalThis as Record<symbol, unknown>)[
|
|
23
|
+
COMPAT_BINDINGS_SLOT
|
|
24
|
+
] as typeof tanstackRouterCompatBindings;
|
|
25
|
+
|
|
26
|
+
expect(bindings).toBeDefined();
|
|
27
|
+
expect(bindings).toBe(tanstackRouterCompatBindings);
|
|
28
|
+
expect(bindings.Form).toBe(Form);
|
|
29
|
+
expect(bindings.Link).toBe(Link);
|
|
30
|
+
expect(bindings.NavLink).toBe(NavLink);
|
|
31
|
+
expect(bindings.Outlet).toBe(Outlet);
|
|
32
|
+
expect(bindings.RouteActionResponseError).toBe(RouteActionResponseError);
|
|
33
|
+
expect(bindings.useFetcher).toBe(useFetcher);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('keeps the first published bindings when a duplicate module copy evaluates', () => {
|
|
37
|
+
// Simulates a Module Federation remote evaluating its own copy of
|
|
38
|
+
// register.ts: `??=` must keep the established bindings.
|
|
39
|
+
const host = globalThis as Record<symbol, unknown>;
|
|
40
|
+
const established = host[COMPAT_BINDINGS_SLOT];
|
|
41
|
+
expect(established).toBeDefined();
|
|
42
|
+
|
|
43
|
+
host[COMPAT_BINDINGS_SLOT] ??= { duplicate: true };
|
|
44
|
+
expect(host[COMPAT_BINDINGS_SLOT]).toBe(established);
|
|
45
|
+
});
|
|
46
|
+
});
|