@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
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
import { createMemoryHistory } from '@tanstack/history';
|
|
2
|
-
import {
|
|
3
|
-
createRootRoute,
|
|
4
|
-
createRoute,
|
|
5
|
-
createRouter,
|
|
6
|
-
redirect,
|
|
7
|
-
} from '@tanstack/react-router';
|
|
8
|
-
|
|
9
|
-
describe('tanstack router-core preload redirects', () => {
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
rstest.restoreAllMocks();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
test('resolves concurrent preload redirects without stale loader work', async () => {
|
|
15
|
-
const consoleErrorSpy = rstest
|
|
16
|
-
.spyOn(console, 'error')
|
|
17
|
-
.mockImplementation(() => {});
|
|
18
|
-
const rootRoute = createRootRoute();
|
|
19
|
-
const indexRoute = createRoute({
|
|
20
|
-
getParentRoute: () => rootRoute,
|
|
21
|
-
path: '/',
|
|
22
|
-
component: () => null,
|
|
23
|
-
});
|
|
24
|
-
let redirectLoaderCalls = 0;
|
|
25
|
-
const redirectRoute = createRoute({
|
|
26
|
-
getParentRoute: () => rootRoute,
|
|
27
|
-
path: '/redirect',
|
|
28
|
-
loader: async () => {
|
|
29
|
-
redirectLoaderCalls += 1;
|
|
30
|
-
await Promise.resolve();
|
|
31
|
-
throw redirect({ to: '/target' });
|
|
32
|
-
},
|
|
33
|
-
component: () => null,
|
|
34
|
-
});
|
|
35
|
-
const targetLoader = rstest.fn(async () => 'target');
|
|
36
|
-
const targetRoute = createRoute({
|
|
37
|
-
getParentRoute: () => rootRoute,
|
|
38
|
-
path: '/target',
|
|
39
|
-
loader: targetLoader,
|
|
40
|
-
component: () => null,
|
|
41
|
-
});
|
|
42
|
-
const router = createRouter({
|
|
43
|
-
routeTree: rootRoute.addChildren([
|
|
44
|
-
indexRoute,
|
|
45
|
-
redirectRoute,
|
|
46
|
-
targetRoute,
|
|
47
|
-
]),
|
|
48
|
-
history: createMemoryHistory({
|
|
49
|
-
initialEntries: ['/'],
|
|
50
|
-
}),
|
|
51
|
-
defaultStructuralSharing: true,
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
await Promise.all([
|
|
55
|
-
router.preloadRoute({ to: '/redirect' }),
|
|
56
|
-
router.preloadRoute({ to: '/redirect' }),
|
|
57
|
-
]);
|
|
58
|
-
|
|
59
|
-
expect(consoleErrorSpy).not.toHaveBeenCalled();
|
|
60
|
-
expect(router.options.defaultStructuralSharing).toBe(true);
|
|
61
|
-
expect(redirectLoaderCalls).toBe(1);
|
|
62
|
-
expect(targetLoader).toHaveBeenCalledTimes(1);
|
|
63
|
-
});
|
|
64
|
-
});
|
|
@@ -1,197 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
__setTanstackRscPayloadDecoderForTests,
|
|
3
|
-
createTanstackRscServerPayload,
|
|
4
|
-
handleTanstackRscRedirect,
|
|
5
|
-
loadTanstackRscRouteData,
|
|
6
|
-
} from '../../src/runtime/rsc/payloadRouter';
|
|
7
|
-
|
|
8
|
-
function createPayload(
|
|
9
|
-
routeId: string,
|
|
10
|
-
loaderData: unknown,
|
|
11
|
-
options: { errors?: Record<string, unknown> | null } = {},
|
|
12
|
-
) {
|
|
13
|
-
return {
|
|
14
|
-
type: 'render',
|
|
15
|
-
actionData: null,
|
|
16
|
-
errors: options.errors ?? null,
|
|
17
|
-
loaderData: loaderData === undefined ? {} : { [routeId]: loaderData },
|
|
18
|
-
location: { href: routeId },
|
|
19
|
-
routes: [{ id: routeId, hasLoader: true }],
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function decodeJsonPayload(stream: ReadableStream<Uint8Array>) {
|
|
24
|
-
return JSON.parse(await new Response(stream).text());
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
describe('tanstack rsc payload router matrix', () => {
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
__setTanstackRscPayloadDecoderForTests();
|
|
30
|
-
rstest.restoreAllMocks();
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
test('normalizes redirect basenames while preserving status and non-location headers', () => {
|
|
34
|
-
const scenarios = [
|
|
35
|
-
{
|
|
36
|
-
location: '/base',
|
|
37
|
-
name: 'exact basename',
|
|
38
|
-
redirect: '/',
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
location: '/base/login?from=%2Fbase',
|
|
42
|
-
name: 'leading basename',
|
|
43
|
-
redirect: '/login?from=%2Fbase',
|
|
44
|
-
},
|
|
45
|
-
{
|
|
46
|
-
location: '/shop/base/login',
|
|
47
|
-
name: 'mid-path basename',
|
|
48
|
-
redirect: '/shop/base/login',
|
|
49
|
-
},
|
|
50
|
-
] as const;
|
|
51
|
-
|
|
52
|
-
for (const scenario of scenarios) {
|
|
53
|
-
const response = handleTanstackRscRedirect(
|
|
54
|
-
new Headers({
|
|
55
|
-
Location: scenario.location,
|
|
56
|
-
'X-Trace': scenario.name,
|
|
57
|
-
}),
|
|
58
|
-
'/base',
|
|
59
|
-
307,
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
expect(response.status).toBe(307);
|
|
63
|
-
expect(response.headers.get('Location')).toBeNull();
|
|
64
|
-
expect(response.headers.get('X-Modernjs-BaseUrl')).toBe('/base');
|
|
65
|
-
expect(response.headers.get('X-Modernjs-Redirect')).toBe(
|
|
66
|
-
scenario.redirect,
|
|
67
|
-
);
|
|
68
|
-
expect(response.headers.get('X-Trace')).toBe(scenario.name);
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
test('separates payload fetch cache entries by URL and request method', async () => {
|
|
73
|
-
const payloads = [
|
|
74
|
-
createPayload('/resource', { method: 'GET' }),
|
|
75
|
-
createPayload('/resource', { method: 'POST' }),
|
|
76
|
-
];
|
|
77
|
-
let payloadIndex = 0;
|
|
78
|
-
const fetchMock = rstest.fn(() =>
|
|
79
|
-
Promise.resolve(new Response(JSON.stringify(payloads[payloadIndex++]))),
|
|
80
|
-
);
|
|
81
|
-
rstest.stubGlobal('fetch', fetchMock);
|
|
82
|
-
__setTanstackRscPayloadDecoderForTests(decodeJsonPayload);
|
|
83
|
-
|
|
84
|
-
const [getData, postData] = await Promise.all([
|
|
85
|
-
loadTanstackRscRouteData({
|
|
86
|
-
loadClientData: async () => ({ fallback: 'get' }),
|
|
87
|
-
request: new Request('http://localhost/resource', { method: 'GET' }),
|
|
88
|
-
routeId: '/resource',
|
|
89
|
-
}),
|
|
90
|
-
loadTanstackRscRouteData({
|
|
91
|
-
loadClientData: async () => ({ fallback: 'post' }),
|
|
92
|
-
request: new Request('http://localhost/resource', { method: 'POST' }),
|
|
93
|
-
routeId: '/resource',
|
|
94
|
-
}),
|
|
95
|
-
]);
|
|
96
|
-
|
|
97
|
-
expect(getData).toEqual({ method: 'GET' });
|
|
98
|
-
expect(postData).toEqual({ method: 'POST' });
|
|
99
|
-
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
100
|
-
expect(fetchMock.mock.calls.map(call => call[0])).toEqual([
|
|
101
|
-
'http://localhost/resource',
|
|
102
|
-
'http://localhost/resource',
|
|
103
|
-
]);
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
test('rethrows matched route errors from the decoded payload', async () => {
|
|
107
|
-
const routeError = new Error('route loader failed');
|
|
108
|
-
const payload = createPayload('/broken', undefined, {
|
|
109
|
-
errors: { '/broken': routeError },
|
|
110
|
-
});
|
|
111
|
-
rstest.stubGlobal(
|
|
112
|
-
'fetch',
|
|
113
|
-
rstest.fn(() => Promise.resolve(new Response('payload'))),
|
|
114
|
-
);
|
|
115
|
-
__setTanstackRscPayloadDecoderForTests(async () => payload);
|
|
116
|
-
|
|
117
|
-
await expect(
|
|
118
|
-
loadTanstackRscRouteData({
|
|
119
|
-
loadClientData: async () => ({ fallback: true }),
|
|
120
|
-
request: new Request('http://localhost/broken'),
|
|
121
|
-
routeId: '/broken',
|
|
122
|
-
}),
|
|
123
|
-
).rejects.toBe(routeError);
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test('returns undefined for route ids missing from the decoded payload', async () => {
|
|
127
|
-
const loadClientData = rstest.fn(async () => ({ fallback: true }));
|
|
128
|
-
const payload = createPayload('/known', { known: true });
|
|
129
|
-
rstest.stubGlobal(
|
|
130
|
-
'fetch',
|
|
131
|
-
rstest.fn(() => Promise.resolve(new Response('payload'))),
|
|
132
|
-
);
|
|
133
|
-
__setTanstackRscPayloadDecoderForTests(async () => payload);
|
|
134
|
-
|
|
135
|
-
await expect(
|
|
136
|
-
loadTanstackRscRouteData({
|
|
137
|
-
loadClientData,
|
|
138
|
-
request: new Request('http://localhost/missing'),
|
|
139
|
-
routeId: '/missing',
|
|
140
|
-
}),
|
|
141
|
-
).resolves.toBeUndefined();
|
|
142
|
-
expect(loadClientData).not.toHaveBeenCalled();
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
test('omits server payload matches with no route identity', () => {
|
|
146
|
-
const payload = createTanstackRscServerPayload({
|
|
147
|
-
state: {
|
|
148
|
-
location: { href: '/kept' },
|
|
149
|
-
matches: [
|
|
150
|
-
{
|
|
151
|
-
loaderData: { omitted: true },
|
|
152
|
-
params: {},
|
|
153
|
-
pathname: '/missing-id',
|
|
154
|
-
pathnameBase: '/missing-id',
|
|
155
|
-
route: {
|
|
156
|
-
options: {
|
|
157
|
-
path: 'missing-id',
|
|
158
|
-
staticData: { modernRouteHasLoader: true },
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
},
|
|
162
|
-
{
|
|
163
|
-
loaderData: { kept: true },
|
|
164
|
-
params: {},
|
|
165
|
-
pathname: '/kept',
|
|
166
|
-
pathnameBase: '/kept',
|
|
167
|
-
route: {
|
|
168
|
-
id: '/kept',
|
|
169
|
-
options: {
|
|
170
|
-
path: 'kept',
|
|
171
|
-
staticData: { modernRouteHasLoader: true },
|
|
172
|
-
},
|
|
173
|
-
},
|
|
174
|
-
},
|
|
175
|
-
],
|
|
176
|
-
},
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
expect(payload.loaderData).toEqual({ '/kept': { kept: true } });
|
|
180
|
-
expect(payload.routes).toEqual([
|
|
181
|
-
{
|
|
182
|
-
handle: undefined,
|
|
183
|
-
hasAction: false,
|
|
184
|
-
hasClientLoader: false,
|
|
185
|
-
hasErrorBoundary: false,
|
|
186
|
-
hasLoader: true,
|
|
187
|
-
id: '/kept',
|
|
188
|
-
index: undefined,
|
|
189
|
-
params: {},
|
|
190
|
-
parentId: undefined,
|
|
191
|
-
path: 'kept',
|
|
192
|
-
pathname: '/kept',
|
|
193
|
-
pathnameBase: '/kept',
|
|
194
|
-
},
|
|
195
|
-
]);
|
|
196
|
-
});
|
|
197
|
-
});
|