@bleedingdev/modern-js-plugin-tanstack 3.8.3-ultramodern.6 → 3.9.0-ultramodern.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/cli/index.js +9 -0
- package/dist/cjs/cli/tanstackTypes/source.js +3 -1
- package/dist/cjs/runtime/basepathRewrite.js +13 -6
- package/dist/cjs/runtime/lifecycle.js +7 -7
- package/dist/cjs/runtime/plugin.js +1 -1
- package/dist/cjs/runtime/plugin.node.js +5 -1
- package/dist/cjs/runtime/prefetchLink.js +2 -7
- package/dist/cjs/runtime/register.js +0 -2
- package/dist/cjs/runtime/routeTree/construction.js +2 -0
- package/dist/cjs/runtime/router.js +25 -9
- package/dist/cjs/runtime/state.js +2 -2
- package/dist/cjs/runtime/utils.js +2 -0
- package/dist/esm/cli/index.mjs +13 -5
- package/dist/esm/cli/tanstackTypes/source.mjs +3 -1
- package/dist/esm/runtime/basepathRewrite.mjs +10 -3
- package/dist/esm/runtime/lifecycle.mjs +1 -1
- package/dist/esm/runtime/plugin.mjs +1 -1
- package/dist/esm/runtime/plugin.node.mjs +5 -1
- package/dist/esm/runtime/prefetchLink.mjs +1 -6
- package/dist/esm/runtime/register.mjs +0 -2
- package/dist/esm/runtime/routeTree/construction.mjs +2 -0
- package/dist/esm/runtime/router.mjs +27 -11
- package/dist/esm/runtime/state.mjs +1 -1
- package/dist/esm/runtime/utils.mjs +2 -0
- package/dist/esm-node/cli/index.mjs +15 -5
- package/dist/esm-node/cli/tanstackTypes/source.mjs +3 -1
- package/dist/esm-node/runtime/basepathRewrite.mjs +10 -3
- package/dist/esm-node/runtime/lifecycle.mjs +1 -1
- package/dist/esm-node/runtime/plugin.mjs +1 -1
- package/dist/esm-node/runtime/plugin.node.mjs +5 -1
- package/dist/esm-node/runtime/prefetchLink.mjs +1 -6
- package/dist/esm-node/runtime/register.mjs +0 -2
- package/dist/esm-node/runtime/routeTree/construction.mjs +2 -0
- package/dist/esm-node/runtime/router.mjs +27 -11
- package/dist/esm-node/runtime/state.mjs +1 -1
- package/dist/esm-node/runtime/utils.mjs +2 -0
- package/dist/types/cli/index.d.ts +5 -5
- package/dist/types/cli/tanstackTypes/shared.d.ts +1 -1
- package/dist/types/cli/tanstackTypes.d.ts +3 -3
- package/dist/types/cli.d.ts +2 -2
- package/dist/types/runtime/basepathRewrite.d.ts +2 -1
- package/dist/types/runtime/dataMutation.d.ts +3 -3
- package/dist/types/runtime/index.d.ts +14 -14
- package/dist/types/runtime/lifecycle.d.ts +9 -2
- package/dist/types/runtime/loaderBridge.d.ts +2 -2
- package/dist/types/runtime/plugin.d.ts +2 -2
- package/dist/types/runtime/plugin.node.d.ts +2 -2
- package/dist/types/runtime/plugin.worker.d.ts +1 -1
- package/dist/types/runtime/pluginShared.d.ts +2 -2
- package/dist/types/runtime/register.d.ts +1 -9
- package/dist/types/runtime/routeTree/construction.d.ts +1 -1
- package/dist/types/runtime/routeTree/index.d.ts +2 -2
- package/dist/types/runtime/routeTree/loaders.d.ts +1 -1
- package/dist/types/runtime/routeTree/tanstackRoutes.d.ts +1 -1
- package/dist/types/runtime/router.d.ts +2 -13
- package/dist/types/runtime/rsc/CompositeComponent.d.ts +1 -1
- package/dist/types/runtime/rsc/RscNodeRenderer.d.ts +1 -1
- package/dist/types/runtime/rsc/client.d.ts +4 -4
- package/dist/types/runtime/rsc/createRscProxy.d.ts +1 -1
- package/dist/types/runtime/rsc/payloadFetch.d.ts +1 -1
- package/dist/types/runtime/rsc/payloadRouter.d.ts +2 -2
- package/dist/types/runtime/rsc/payloadStorage.d.ts +1 -1
- package/dist/types/runtime/rsc/server.d.ts +3 -3
- package/dist/types/runtime/rsc/shared.d.ts +1 -1
- package/dist/types/runtime/rsc/slotUsageSanitizer.d.ts +1 -1
- package/dist/types/runtime/ssrManagedTags.d.ts +1 -1
- package/dist/types/runtime/ssrPreload.d.ts +1 -1
- package/dist/types/runtime/state.d.ts +1 -1
- package/dist/types/runtime/submitAction.d.ts +1 -1
- package/dist/types/runtime/types.d.ts +2 -1
- package/dist/types/runtime/utils.d.ts +1 -1
- package/dist/types/runtime.d.ts +1 -1
- package/package.json +19 -16
- package/rstest.config.mts +1 -24
- package/src/cli/index.ts +10 -0
- package/src/cli/tanstackTypes/source.ts +3 -1
- package/src/runtime/basepathRewrite.ts +11 -2
- package/src/runtime/lifecycle.ts +14 -3
- package/src/runtime/plugin.node.tsx +9 -2
- package/src/runtime/plugin.tsx +5 -1
- package/src/runtime/prefetchLink.tsx +1 -23
- package/src/runtime/register.ts +1 -12
- package/src/runtime/routeTree/construction.ts +2 -0
- package/src/runtime/router.ts +25 -40
- package/src/runtime/state.ts +1 -1
- package/src/runtime/types.ts +2 -2
- package/src/runtime/utils.tsx +3 -1
- package/tests/router/cli.test.ts +148 -824
- package/tests/router/dataMutation.test.tsx +17 -85
- package/tests/router/flightSerialization.roundtrip.test.tsx +9 -6
- package/tests/router/generateRouteArtifacts.test.ts +68 -28
- package/tests/router/loaderBridge.test.ts +0 -25
- package/tests/router/localisedIdentity.test.tsx +95 -0
- package/tests/router/prefetchLink.test.tsx +17 -21
- package/tests/router/prefetchLinkPreload.test.tsx +11 -31
- package/tests/router/routeHooks.test.ts +41 -105
- package/tests/router/routeTree.test.ts +144 -836
- package/tests/router/rsc.test.tsx +131 -166
- package/tests/router/serverPlugin.test.ts +43 -1
- package/tests/router/tanstackTypes.test.ts +109 -973
- package/tests/realm-isolation.test.ts +0 -114
- package/tests/router/fastDefaults.test.ts +0 -32
- package/tests/router/fixtures/legacyRuntimeRouter.ts +0 -10
- package/tests/router/fixtures/rscDecoderSecurityChild.cjs +0 -162
- package/tests/router/hooks.test.ts +0 -40
- package/tests/router/hydrationBoundary.test.tsx +0 -23
- package/tests/router/packageSurface.test.ts +0 -24
- package/tests/router/preloadRedirect.test.ts +0 -64
- package/tests/router/register.test.ts +0 -8
- package/tests/router/routerCompatibility.test.ts +0 -239
- package/tests/router/rscDecoderSecurity.test.ts +0 -113
- package/tests/router/rscPayloadRouterMatrix.test.ts +0 -197
- /package/dist/esm/{rslib-runtime.mjs → rslib-runtime~1.mjs} +0 -0
- /package/dist/esm-node/{rslib-runtime.mjs → rslib-runtime~0.mjs} +0 -0
|
@@ -1,11 +1,4 @@
|
|
|
1
1
|
import { UNSAFE_ErrorResponseImpl as ErrorResponseImpl } from '@modern-js/runtime-utils/router';
|
|
2
|
-
import type React from 'react';
|
|
3
|
-
import { isValidElement } from 'react';
|
|
4
|
-
import { createRscProxy } from '../../src/runtime/rsc/createRscProxy';
|
|
5
|
-
import {
|
|
6
|
-
reviveTanstackRscFlightValues,
|
|
7
|
-
serializeTanstackRscFlightValues,
|
|
8
|
-
} from '../../src/runtime/rsc/flightSerialization';
|
|
9
2
|
import {
|
|
10
3
|
__setTanstackRscPayloadDecoderForTests,
|
|
11
4
|
createTanstackRscServerPayload,
|
|
@@ -13,12 +6,6 @@ import {
|
|
|
13
6
|
loadTanstackRscRouteData,
|
|
14
7
|
} from '../../src/runtime/rsc/payloadRouter';
|
|
15
8
|
import { ReplayableStream } from '../../src/runtime/rsc/ReplayableStream';
|
|
16
|
-
import {
|
|
17
|
-
RENDERABLE_RSC,
|
|
18
|
-
RSC_PROXY_PATH,
|
|
19
|
-
SERVER_COMPONENT_STREAM,
|
|
20
|
-
type ServerComponentStream,
|
|
21
|
-
} from '../../src/runtime/rsc/symbols';
|
|
22
9
|
|
|
23
10
|
async function readAll(stream: ReadableStream<Uint8Array>) {
|
|
24
11
|
const reader = stream.getReader();
|
|
@@ -69,75 +56,6 @@ describe('tanstack rsc runtime helpers', () => {
|
|
|
69
56
|
]);
|
|
70
57
|
});
|
|
71
58
|
|
|
72
|
-
test('revives shared structures that the Flight serializer preserves', async () => {
|
|
73
|
-
const shared = { label: 'shared' };
|
|
74
|
-
const serialized = serializeTanstackRscFlightValues({
|
|
75
|
-
left: shared,
|
|
76
|
-
right: shared,
|
|
77
|
-
}) as { left: unknown; right: unknown };
|
|
78
|
-
|
|
79
|
-
const revived = (await reviveTanstackRscFlightValues(serialized)) as {
|
|
80
|
-
left: unknown;
|
|
81
|
-
right: unknown;
|
|
82
|
-
};
|
|
83
|
-
|
|
84
|
-
expect(serialized.left).toBe(serialized.right);
|
|
85
|
-
expect(revived.left).toBe(revived.right);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
test('serializes TanStack RSC flight values nested in map and set containers', () => {
|
|
89
|
-
const stream: ServerComponentStream = {
|
|
90
|
-
createReplayStream: () => new ReadableStream<Uint8Array>(),
|
|
91
|
-
};
|
|
92
|
-
const proxy = createRscProxy(() => null, {
|
|
93
|
-
renderable: true,
|
|
94
|
-
stream,
|
|
95
|
-
}) as React.ReactElement & Record<PropertyKey, unknown>;
|
|
96
|
-
|
|
97
|
-
const serialized = serializeTanstackRscFlightValues({
|
|
98
|
-
map: new Map([['slot', proxy]]),
|
|
99
|
-
set: new Set([proxy]),
|
|
100
|
-
}) as {
|
|
101
|
-
map: Map<string, Record<string, unknown>>;
|
|
102
|
-
set: Set<Record<string, unknown>>;
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
const mapValue = serialized.map.get('slot');
|
|
106
|
-
const [setValue] = serialized.set;
|
|
107
|
-
|
|
108
|
-
expect(mapValue).not.toBe(proxy);
|
|
109
|
-
expect(setValue).not.toBe(proxy);
|
|
110
|
-
expect(mapValue).toMatchObject({
|
|
111
|
-
__modernTanstackRsc: true,
|
|
112
|
-
kind: 'renderable',
|
|
113
|
-
});
|
|
114
|
-
expect(setValue).toMatchObject({
|
|
115
|
-
__modernTanstackRsc: true,
|
|
116
|
-
kind: 'renderable',
|
|
117
|
-
});
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
test('renderable RSC proxies preserve React element behavior and metadata', () => {
|
|
121
|
-
const stream: ServerComponentStream = {
|
|
122
|
-
createReplayStream: () => new ReadableStream<Uint8Array>(),
|
|
123
|
-
};
|
|
124
|
-
const proxy = createRscProxy(() => ({ sidebar: 'ok' }), {
|
|
125
|
-
renderable: true,
|
|
126
|
-
stream,
|
|
127
|
-
}) as React.ReactElement & Record<PropertyKey, unknown>;
|
|
128
|
-
|
|
129
|
-
expect(isValidElement(proxy)).toBe(true);
|
|
130
|
-
expect(proxy[SERVER_COMPONENT_STREAM]).toBe(stream);
|
|
131
|
-
expect(proxy[RENDERABLE_RSC]).toBe(true);
|
|
132
|
-
expect(proxy.then).toBeUndefined();
|
|
133
|
-
expect('__SEROVAL_STREAM__' in proxy).toBe(false);
|
|
134
|
-
expect('__SEROVAL_SEQUENCE__' in proxy).toBe(false);
|
|
135
|
-
expect(Symbol.iterator in proxy).toBe(false);
|
|
136
|
-
|
|
137
|
-
const sidebar = proxy.sidebar as Record<PropertyKey, unknown>;
|
|
138
|
-
expect(sidebar[RSC_PROXY_PATH]).toEqual(['sidebar']);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
59
|
test('creates TanStack RSC server payload and omits client-loader data during RSC navigation', () => {
|
|
142
60
|
const payload = createTanstackRscServerPayload(
|
|
143
61
|
{
|
|
@@ -212,7 +130,7 @@ describe('tanstack rsc runtime helpers', () => {
|
|
|
212
130
|
).toBeUndefined();
|
|
213
131
|
});
|
|
214
132
|
|
|
215
|
-
test('redacts production TanStack RSC server payload errors', () => {
|
|
133
|
+
test('redacts production TanStack RSC server payload errors (Error, RouteErrorResponse, and plain-object)', () => {
|
|
216
134
|
const routeError = new ErrorResponseImpl(
|
|
217
135
|
500,
|
|
218
136
|
'secret status text',
|
|
@@ -247,6 +165,14 @@ describe('tanstack rsc runtime helpers', () => {
|
|
|
247
165
|
},
|
|
248
166
|
routeId: '/products',
|
|
249
167
|
},
|
|
168
|
+
{
|
|
169
|
+
error: { message: 'plain secret', token: 'token secret' },
|
|
170
|
+
params: {},
|
|
171
|
+
pathname: '/plain',
|
|
172
|
+
pathnameBase: '/plain',
|
|
173
|
+
route: { id: '/plain', parentRoute: { id: '__root__' } },
|
|
174
|
+
routeId: '/plain',
|
|
175
|
+
},
|
|
250
176
|
],
|
|
251
177
|
},
|
|
252
178
|
}),
|
|
@@ -264,69 +190,54 @@ describe('tanstack rsc runtime helpers', () => {
|
|
|
264
190
|
data: 'Unexpected Server Error',
|
|
265
191
|
__type: 'RouteErrorResponse',
|
|
266
192
|
},
|
|
267
|
-
});
|
|
268
|
-
expect(JSON.stringify(payload.errors)).not.toContain('server secret');
|
|
269
|
-
expect(JSON.stringify(payload.errors)).not.toContain('route secret');
|
|
270
|
-
expect(JSON.stringify(payload.errors)).not.toContain('secret status text');
|
|
271
|
-
expect(JSON.stringify(payload.errors)).not.toContain('stack secret');
|
|
272
|
-
});
|
|
273
|
-
|
|
274
|
-
test('redacts production TanStack RSC non-Error server payload errors', () => {
|
|
275
|
-
const payload = withNodeEnv('production', () =>
|
|
276
|
-
createTanstackRscServerPayload({
|
|
277
|
-
state: {
|
|
278
|
-
location: { href: '/plain' },
|
|
279
|
-
matches: [
|
|
280
|
-
{
|
|
281
|
-
error: { message: 'plain secret', token: 'token secret' },
|
|
282
|
-
params: {},
|
|
283
|
-
pathname: '/plain',
|
|
284
|
-
pathnameBase: '/plain',
|
|
285
|
-
route: { id: '/plain' },
|
|
286
|
-
routeId: '/plain',
|
|
287
|
-
},
|
|
288
|
-
],
|
|
289
|
-
},
|
|
290
|
-
}),
|
|
291
|
-
);
|
|
292
|
-
|
|
293
|
-
expect(payload.errors).toMatchObject({
|
|
294
193
|
'/plain': {
|
|
295
194
|
message: 'Unexpected Server Error',
|
|
296
195
|
stack: undefined,
|
|
297
196
|
__type: 'Error',
|
|
298
197
|
},
|
|
299
198
|
});
|
|
199
|
+
expect(JSON.stringify(payload.errors)).not.toContain('server secret');
|
|
200
|
+
expect(JSON.stringify(payload.errors)).not.toContain('route secret');
|
|
201
|
+
expect(JSON.stringify(payload.errors)).not.toContain('secret status text');
|
|
202
|
+
expect(JSON.stringify(payload.errors)).not.toContain('stack secret');
|
|
300
203
|
expect(JSON.stringify(payload.errors)).not.toContain('plain secret');
|
|
301
204
|
expect(JSON.stringify(payload.errors)).not.toContain('token secret');
|
|
302
205
|
});
|
|
303
206
|
|
|
304
|
-
test('
|
|
305
|
-
const
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
const response = handleTanstackRscRedirect(
|
|
319
|
-
new Headers({ Location: '/shop/base/login' }),
|
|
320
|
-
'/base',
|
|
321
|
-
302,
|
|
322
|
-
);
|
|
207
|
+
test('preserves RSC redirect response status and headers in TanStack redirects', async () => {
|
|
208
|
+
const scenarios = [
|
|
209
|
+
{ location: '/base', name: 'exact basename', redirect: '/' },
|
|
210
|
+
{
|
|
211
|
+
location: '/base/login?from=%2Fbase',
|
|
212
|
+
name: 'leading basename',
|
|
213
|
+
redirect: '/login?from=%2Fbase',
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
location: '/shop/base/login',
|
|
217
|
+
name: 'mid-path basename',
|
|
218
|
+
redirect: '/shop/base/login',
|
|
219
|
+
},
|
|
220
|
+
] as const;
|
|
323
221
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
222
|
+
for (const scenario of scenarios) {
|
|
223
|
+
const response = handleTanstackRscRedirect(
|
|
224
|
+
new Headers({
|
|
225
|
+
Location: scenario.location,
|
|
226
|
+
'X-Trace': scenario.name,
|
|
227
|
+
}),
|
|
228
|
+
'/base',
|
|
229
|
+
307,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
expect(response.status).toBe(307);
|
|
233
|
+
expect(response.headers.get('Location')).toBeNull();
|
|
234
|
+
expect(response.headers.get('X-Modernjs-BaseUrl')).toBe('/base');
|
|
235
|
+
expect(response.headers.get('X-Modernjs-Redirect')).toBe(
|
|
236
|
+
scenario.redirect,
|
|
237
|
+
);
|
|
238
|
+
expect(response.headers.get('X-Trace')).toBe(scenario.name);
|
|
239
|
+
}
|
|
328
240
|
|
|
329
|
-
test('preserves RSC redirect response status and headers in TanStack redirects', async () => {
|
|
330
241
|
rstest.stubGlobal(
|
|
331
242
|
'fetch',
|
|
332
243
|
rstest.fn(() =>
|
|
@@ -362,47 +273,50 @@ describe('tanstack rsc runtime helpers', () => {
|
|
|
362
273
|
expect(thrown.options?.to).toBe('/login');
|
|
363
274
|
});
|
|
364
275
|
|
|
365
|
-
test('
|
|
366
|
-
const
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
276
|
+
test('separates RSC payload cache entries by URL and request method', async () => {
|
|
277
|
+
const payloads = [
|
|
278
|
+
{
|
|
279
|
+
type: 'render',
|
|
280
|
+
actionData: null,
|
|
281
|
+
errors: null,
|
|
282
|
+
loaderData: { '/method': { method: 'GET' } },
|
|
283
|
+
location: { href: '/method' },
|
|
284
|
+
routes: [{ id: '/method', hasLoader: true }],
|
|
373
285
|
},
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
286
|
+
{
|
|
287
|
+
type: 'render',
|
|
288
|
+
actionData: null,
|
|
289
|
+
errors: null,
|
|
290
|
+
loaderData: { '/method': { method: 'POST' } },
|
|
291
|
+
location: { href: '/method' },
|
|
292
|
+
routes: [{ id: '/method', hasLoader: true }],
|
|
293
|
+
},
|
|
294
|
+
];
|
|
295
|
+
let payloadIndex = 0;
|
|
296
|
+
const fetchMock = rstest.fn(() =>
|
|
297
|
+
Promise.resolve(new Response(JSON.stringify(payloads[payloadIndex++]))),
|
|
298
|
+
);
|
|
382
299
|
rstest.stubGlobal('fetch', fetchMock);
|
|
383
|
-
__setTanstackRscPayloadDecoderForTests(
|
|
300
|
+
__setTanstackRscPayloadDecoderForTests(async stream =>
|
|
301
|
+
JSON.parse(await new Response(stream).text()),
|
|
302
|
+
);
|
|
384
303
|
|
|
385
|
-
const
|
|
386
|
-
const [rootData, productData] = await Promise.all([
|
|
304
|
+
const [getData, postData] = await Promise.all([
|
|
387
305
|
loadTanstackRscRouteData({
|
|
388
|
-
loadClientData: async () => ({ fallback: '
|
|
389
|
-
request,
|
|
390
|
-
routeId: '
|
|
306
|
+
loadClientData: async () => ({ fallback: 'get' }),
|
|
307
|
+
request: new Request('http://localhost/method', { method: 'GET' }),
|
|
308
|
+
routeId: '/method',
|
|
391
309
|
}),
|
|
392
310
|
loadTanstackRscRouteData({
|
|
393
|
-
loadClientData: async () => ({ fallback: '
|
|
394
|
-
request,
|
|
395
|
-
routeId: '/
|
|
311
|
+
loadClientData: async () => ({ fallback: 'post' }),
|
|
312
|
+
request: new Request('http://localhost/method', { method: 'POST' }),
|
|
313
|
+
routeId: '/method',
|
|
396
314
|
}),
|
|
397
315
|
]);
|
|
398
316
|
|
|
399
|
-
expect(
|
|
400
|
-
expect(
|
|
401
|
-
expect(fetchMock).toHaveBeenCalledTimes(
|
|
402
|
-
expect(fetchMock.mock.calls[0]?.[1]?.headers.get('x-rsc-tree')).toBe(
|
|
403
|
-
'true',
|
|
404
|
-
);
|
|
405
|
-
expect(decodeMock).toHaveBeenCalledTimes(1);
|
|
317
|
+
expect(getData).toEqual({ method: 'GET' });
|
|
318
|
+
expect(postData).toEqual({ method: 'POST' });
|
|
319
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
406
320
|
});
|
|
407
321
|
|
|
408
322
|
test('separates RSC payload cache entries by forwarded request headers', async () => {
|
|
@@ -464,6 +378,57 @@ describe('tanstack rsc runtime helpers', () => {
|
|
|
464
378
|
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
465
379
|
});
|
|
466
380
|
|
|
381
|
+
test('rethrows matched route errors from the decoded payload', async () => {
|
|
382
|
+
const routeError = new Error('route loader failed');
|
|
383
|
+
const payload = {
|
|
384
|
+
type: 'render',
|
|
385
|
+
actionData: null,
|
|
386
|
+
errors: { '/broken': routeError },
|
|
387
|
+
loaderData: {},
|
|
388
|
+
location: { href: '/broken' },
|
|
389
|
+
routes: [{ id: '/broken', hasLoader: true }],
|
|
390
|
+
};
|
|
391
|
+
rstest.stubGlobal(
|
|
392
|
+
'fetch',
|
|
393
|
+
rstest.fn(() => Promise.resolve(new Response('payload'))),
|
|
394
|
+
);
|
|
395
|
+
__setTanstackRscPayloadDecoderForTests(async () => payload);
|
|
396
|
+
|
|
397
|
+
await expect(
|
|
398
|
+
loadTanstackRscRouteData({
|
|
399
|
+
loadClientData: async () => ({ fallback: true }),
|
|
400
|
+
request: new Request('http://localhost/broken'),
|
|
401
|
+
routeId: '/broken',
|
|
402
|
+
}),
|
|
403
|
+
).rejects.toBe(routeError);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test('returns undefined for a route id missing from the decoded payload', async () => {
|
|
407
|
+
const loadClientData = rstest.fn(async () => ({ fallback: true }));
|
|
408
|
+
const payload = {
|
|
409
|
+
type: 'render',
|
|
410
|
+
actionData: null,
|
|
411
|
+
errors: null,
|
|
412
|
+
loaderData: { '/known': { known: true } },
|
|
413
|
+
location: { href: '/known' },
|
|
414
|
+
routes: [{ id: '/known', hasLoader: true }],
|
|
415
|
+
};
|
|
416
|
+
rstest.stubGlobal(
|
|
417
|
+
'fetch',
|
|
418
|
+
rstest.fn(() => Promise.resolve(new Response('payload'))),
|
|
419
|
+
);
|
|
420
|
+
__setTanstackRscPayloadDecoderForTests(async () => payload);
|
|
421
|
+
|
|
422
|
+
await expect(
|
|
423
|
+
loadTanstackRscRouteData({
|
|
424
|
+
loadClientData,
|
|
425
|
+
request: new Request('http://localhost/missing'),
|
|
426
|
+
routeId: '/missing',
|
|
427
|
+
}),
|
|
428
|
+
).resolves.toBeUndefined();
|
|
429
|
+
expect(loadClientData).not.toHaveBeenCalled();
|
|
430
|
+
});
|
|
431
|
+
|
|
467
432
|
test('uses client loader data without requesting an RSC payload for client-loader routes', async () => {
|
|
468
433
|
const fetchMock = rstest.fn();
|
|
469
434
|
const loadClientData = rstest.fn(async () => ({ client: true }));
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
import type { TInternalRuntimeContext } from '@modern-js/runtime/context';
|
|
2
2
|
import { routerProviderRegistryHooks } from '@modern-js/runtime/context';
|
|
3
|
-
import {
|
|
3
|
+
import { type AnyRouter, RouterProvider } from '@tanstack/react-router';
|
|
4
|
+
import { createElement } from 'react';
|
|
5
|
+
import { renderToString } from 'react-dom/server';
|
|
6
|
+
import {
|
|
7
|
+
getRouterRuntimeState,
|
|
8
|
+
getRouterServerSnapshot,
|
|
9
|
+
} from '../../src/runtime/lifecycle';
|
|
4
10
|
import { tanstackRouterPlugin } from '../../src/runtime/plugin.node';
|
|
5
11
|
import type { TanstackRouterPluginAPI } from '../../src/runtime/pluginShared';
|
|
6
12
|
import type { RouterConfig } from '../../src/runtime/types';
|
|
@@ -50,6 +56,42 @@ describe('tanstack server plugin router results', () => {
|
|
|
50
56
|
rstest.restoreAllMocks();
|
|
51
57
|
});
|
|
52
58
|
|
|
59
|
+
test.each([
|
|
60
|
+
'/cs/login',
|
|
61
|
+
'/en/login',
|
|
62
|
+
])('renders native link active attributes during SSR at %s', async pathname => {
|
|
63
|
+
const { context } = createServerContext(pathname);
|
|
64
|
+
const beforeRender = collectBeforeRender(() => [
|
|
65
|
+
{
|
|
66
|
+
id: 'login',
|
|
67
|
+
path: '/:lang/login',
|
|
68
|
+
Component: () => {
|
|
69
|
+
const Link = context.router?.Link;
|
|
70
|
+
if (!Link) {
|
|
71
|
+
throw new Error('SSR router did not provide its native Link');
|
|
72
|
+
}
|
|
73
|
+
return createElement(Link, { to: '/cs' }, 'Home');
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
]);
|
|
77
|
+
await beforeRender(context, value => value);
|
|
78
|
+
|
|
79
|
+
expect(context.router?.Link).toBeTypeOf('function');
|
|
80
|
+
const html = renderToString(
|
|
81
|
+
createElement(RouterProvider, {
|
|
82
|
+
router: getRouterRuntimeState(context)?.instance as AnyRouter,
|
|
83
|
+
}),
|
|
84
|
+
);
|
|
85
|
+
expect(html).toContain('href="/cs"');
|
|
86
|
+
if (pathname === '/cs/login') {
|
|
87
|
+
expect(html).toContain('data-status="active"');
|
|
88
|
+
expect(html).toContain('aria-current="page"');
|
|
89
|
+
} else {
|
|
90
|
+
expect(html).not.toContain('data-status="active"');
|
|
91
|
+
expect(html).not.toContain('aria-current="page"');
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
53
95
|
test('uses the router render result as the HTTP and hydration status', async () => {
|
|
54
96
|
const beforeRender = collectBeforeRender(() => [
|
|
55
97
|
{
|