@bleedingdev/modern-js-plugin-tanstack 3.5.0-ultramodern.3 → 3.5.0-ultramodern.31
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/artifacts.js +157 -0
- package/dist/cjs/cli/index.js +8 -79
- package/dist/cjs/cli/tanstackTypes/canonicalRoutes.js +113 -0
- package/dist/cjs/cli/tanstackTypes/shared.js +130 -0
- package/dist/cjs/cli/tanstackTypes/source.js +251 -0
- package/dist/cjs/cli/tanstackTypes.js +4 -350
- package/dist/cjs/runtime/blockingSubscribe.js +64 -0
- package/dist/cjs/runtime/{pluginCore.js → clientBasepath.js} +12 -19
- package/dist/cjs/runtime/clientHydration.js +129 -0
- package/dist/cjs/runtime/dataMutation.js +11 -176
- package/dist/cjs/runtime/formData.js +116 -0
- package/dist/cjs/runtime/loaderBridge.js +4 -1
- package/dist/cjs/runtime/plugin.js +21 -125
- package/dist/cjs/runtime/plugin.node.js +45 -125
- package/dist/cjs/runtime/pluginShared.js +90 -0
- package/dist/cjs/runtime/prefetchLink.js +15 -2
- package/dist/cjs/runtime/routeTree/components.js +102 -0
- package/dist/cjs/runtime/routeTree/construction.js +152 -0
- package/dist/cjs/runtime/routeTree/index.js +47 -0
- package/dist/cjs/runtime/routeTree/loaders.js +158 -0
- package/dist/cjs/runtime/routeTree/paths.js +57 -0
- package/dist/cjs/runtime/routeTree/staticData.js +63 -0
- package/dist/cjs/runtime/routeTree/tanstackRoutes.js +57 -0
- package/dist/cjs/runtime/routeTree/types.js +18 -0
- package/dist/cjs/runtime/routeTree.js +7 -320
- package/dist/cjs/runtime/rsc/CompositeComponent.js +18 -13
- package/dist/cjs/runtime/rsc/RscNodeRenderer.js +5 -16
- package/dist/cjs/runtime/rsc/client.js +8 -25
- package/dist/cjs/runtime/rsc/flightSerialization.js +132 -0
- package/dist/cjs/runtime/rsc/payloadErrors.js +99 -0
- package/dist/cjs/runtime/rsc/payloadFetch.js +117 -0
- package/dist/cjs/runtime/rsc/payloadRouter.js +21 -142
- package/dist/cjs/runtime/rsc/payloadRoutes.js +106 -0
- package/dist/cjs/runtime/rsc/payloadStorage.js +58 -0
- package/dist/cjs/runtime/rsc/server.js +14 -18
- package/dist/cjs/runtime/rsc/shared.js +71 -0
- package/dist/cjs/runtime/rsc/treeGetter.js +72 -0
- package/dist/cjs/runtime/ssrManagedTags.js +103 -0
- package/dist/cjs/runtime/ssrPreload.js +89 -0
- package/dist/cjs/runtime/ssrTypes.js +18 -0
- package/dist/cjs/runtime/submitAction.js +161 -0
- package/dist/cjs/runtime/utils.js +49 -86
- package/dist/esm/cli/artifacts.mjs +103 -0
- package/dist/esm/cli/index.mjs +4 -78
- package/dist/esm/cli/tanstackTypes/canonicalRoutes.mjs +75 -0
- package/dist/esm/cli/tanstackTypes/shared.mjs +68 -0
- package/dist/esm/cli/tanstackTypes/source.mjs +203 -0
- package/dist/esm/cli/tanstackTypes.mjs +2 -339
- package/dist/esm/runtime/blockingSubscribe.mjs +19 -0
- package/dist/esm/runtime/clientBasepath.mjs +10 -0
- package/dist/esm/runtime/clientHydration.mjs +70 -0
- package/dist/esm/runtime/dataMutation.mjs +2 -167
- package/dist/esm/runtime/formData.mjs +63 -0
- package/dist/esm/runtime/loaderBridge.mjs +4 -1
- package/dist/esm/runtime/plugin.mjs +14 -118
- package/dist/esm/runtime/plugin.node.mjs +41 -121
- package/dist/esm/runtime/pluginShared.mjs +40 -0
- package/dist/esm/runtime/prefetchLink.mjs +18 -5
- package/dist/esm/runtime/routeTree/components.mjs +55 -0
- package/dist/esm/runtime/routeTree/construction.mjs +111 -0
- package/dist/esm/runtime/routeTree/index.mjs +2 -0
- package/dist/esm/runtime/routeTree/loaders.mjs +117 -0
- package/dist/esm/runtime/routeTree/paths.mjs +16 -0
- package/dist/esm/runtime/routeTree/staticData.mjs +22 -0
- package/dist/esm/runtime/routeTree/tanstackRoutes.mjs +13 -0
- package/dist/esm/runtime/routeTree/types.mjs +0 -0
- package/dist/esm/runtime/routeTree.mjs +1 -318
- package/dist/esm/runtime/rsc/CompositeComponent.mjs +18 -13
- package/dist/esm/runtime/rsc/RscNodeRenderer.mjs +4 -15
- package/dist/esm/runtime/rsc/client.mjs +6 -26
- package/dist/esm/runtime/rsc/flightSerialization.mjs +88 -0
- package/dist/esm/runtime/rsc/payloadErrors.mjs +49 -0
- package/dist/esm/runtime/rsc/payloadFetch.mjs +61 -0
- package/dist/esm/runtime/rsc/payloadRouter.mjs +12 -133
- package/dist/esm/runtime/rsc/payloadRoutes.mjs +50 -0
- package/dist/esm/runtime/rsc/payloadStorage.mjs +14 -0
- package/dist/esm/runtime/rsc/server.mjs +17 -21
- package/dist/esm/runtime/rsc/shared.mjs +21 -0
- package/dist/esm/runtime/rsc/treeGetter.mjs +34 -0
- package/dist/esm/runtime/ssrManagedTags.mjs +53 -0
- package/dist/esm/runtime/ssrPreload.mjs +51 -0
- package/dist/esm/runtime/ssrTypes.mjs +0 -0
- package/dist/esm/runtime/submitAction.mjs +120 -0
- package/dist/esm/runtime/utils.mjs +47 -75
- package/dist/esm-node/cli/artifacts.mjs +104 -0
- package/dist/esm-node/cli/index.mjs +4 -78
- package/dist/esm-node/cli/tanstackTypes/canonicalRoutes.mjs +76 -0
- package/dist/esm-node/cli/tanstackTypes/shared.mjs +69 -0
- package/dist/esm-node/cli/tanstackTypes/source.mjs +204 -0
- package/dist/esm-node/cli/tanstackTypes.mjs +2 -339
- package/dist/esm-node/runtime/blockingSubscribe.mjs +20 -0
- package/dist/esm-node/runtime/clientBasepath.mjs +11 -0
- package/dist/esm-node/runtime/clientHydration.mjs +71 -0
- package/dist/esm-node/runtime/dataMutation.mjs +2 -167
- package/dist/esm-node/runtime/formData.mjs +64 -0
- package/dist/esm-node/runtime/loaderBridge.mjs +4 -1
- package/dist/esm-node/runtime/plugin.mjs +14 -118
- package/dist/esm-node/runtime/plugin.node.mjs +41 -121
- package/dist/esm-node/runtime/pluginShared.mjs +41 -0
- package/dist/esm-node/runtime/prefetchLink.mjs +18 -5
- package/dist/esm-node/runtime/routeTree/components.mjs +56 -0
- package/dist/esm-node/runtime/routeTree/construction.mjs +112 -0
- package/dist/esm-node/runtime/routeTree/index.mjs +3 -0
- package/dist/esm-node/runtime/routeTree/loaders.mjs +118 -0
- package/dist/esm-node/runtime/routeTree/paths.mjs +17 -0
- package/dist/esm-node/runtime/routeTree/staticData.mjs +23 -0
- package/dist/esm-node/runtime/routeTree/tanstackRoutes.mjs +14 -0
- package/dist/esm-node/runtime/routeTree/types.mjs +1 -0
- package/dist/esm-node/runtime/routeTree.mjs +1 -318
- package/dist/esm-node/runtime/rsc/CompositeComponent.mjs +18 -13
- package/dist/esm-node/runtime/rsc/RscNodeRenderer.mjs +4 -15
- package/dist/esm-node/runtime/rsc/client.mjs +6 -26
- package/dist/esm-node/runtime/rsc/flightSerialization.mjs +89 -0
- package/dist/esm-node/runtime/rsc/payloadErrors.mjs +50 -0
- package/dist/esm-node/runtime/rsc/payloadFetch.mjs +62 -0
- package/dist/esm-node/runtime/rsc/payloadRouter.mjs +12 -133
- package/dist/esm-node/runtime/rsc/payloadRoutes.mjs +51 -0
- package/dist/esm-node/runtime/rsc/payloadStorage.mjs +15 -0
- package/dist/esm-node/runtime/rsc/server.mjs +17 -21
- package/dist/esm-node/runtime/rsc/shared.mjs +22 -0
- package/dist/esm-node/runtime/rsc/treeGetter.mjs +35 -0
- package/dist/esm-node/runtime/ssrManagedTags.mjs +54 -0
- package/dist/esm-node/runtime/ssrPreload.mjs +52 -0
- package/dist/esm-node/runtime/ssrTypes.mjs +1 -0
- package/dist/esm-node/runtime/submitAction.mjs +121 -0
- package/dist/esm-node/runtime/utils.mjs +47 -75
- package/dist/types/cli/artifacts.d.ts +33 -0
- package/dist/types/cli/index.d.ts +2 -22
- package/dist/types/cli/tanstackTypes/canonicalRoutes.d.ts +22 -0
- package/dist/types/cli/tanstackTypes/shared.d.ts +20 -0
- package/dist/types/cli/tanstackTypes/source.d.ts +10 -0
- package/dist/types/cli/tanstackTypes.d.ts +3 -31
- package/dist/types/runtime/blockingSubscribe.d.ts +3 -0
- package/dist/types/runtime/clientBasepath.d.ts +1 -0
- package/dist/types/runtime/clientHydration.d.ts +19 -0
- package/dist/types/runtime/dataMutation.d.ts +3 -12
- package/dist/types/runtime/formData.d.ts +23 -0
- package/dist/types/runtime/plugin.d.ts +1 -1
- package/dist/types/runtime/plugin.node.d.ts +1 -1
- package/dist/types/runtime/pluginShared.d.ts +39 -0
- package/dist/types/runtime/routeTree/components.d.ts +7 -0
- package/dist/types/runtime/routeTree/construction.d.ts +5 -0
- package/dist/types/runtime/routeTree/index.d.ts +2 -0
- package/dist/types/runtime/routeTree/loaders.d.ts +4 -0
- package/dist/types/runtime/routeTree/paths.d.ts +3 -0
- package/dist/types/runtime/routeTree/staticData.d.ts +19 -0
- package/dist/types/runtime/routeTree/tanstackRoutes.d.ts +4 -0
- package/dist/types/runtime/routeTree/types.d.ts +75 -0
- package/dist/types/runtime/routeTree.d.ts +1 -9
- package/dist/types/runtime/rsc/CompositeComponent.d.ts +1 -2
- package/dist/types/runtime/rsc/RscNodeRenderer.d.ts +2 -3
- package/dist/types/runtime/rsc/client.d.ts +2 -0
- package/dist/types/runtime/rsc/flightSerialization.d.ts +3 -0
- package/dist/types/runtime/rsc/payloadErrors.d.ts +6 -0
- package/dist/types/runtime/rsc/payloadFetch.d.ts +10 -0
- package/dist/types/runtime/rsc/payloadRouter.d.ts +2 -10
- package/dist/types/runtime/rsc/payloadRoutes.d.ts +43 -0
- package/dist/types/runtime/rsc/payloadStorage.d.ts +5 -0
- package/dist/types/runtime/rsc/shared.d.ts +19 -0
- package/dist/types/runtime/rsc/treeGetter.d.ts +1 -0
- package/dist/types/runtime/ssrManagedTags.d.ts +7 -0
- package/dist/types/runtime/ssrPreload.d.ts +2 -0
- package/dist/types/runtime/ssrTypes.d.ts +43 -0
- package/dist/types/runtime/state.d.ts +1 -1
- package/dist/types/runtime/submitAction.d.ts +14 -0
- package/dist/types/runtime/types.d.ts +4 -9
- package/dist/types/runtime/utils.d.ts +4 -24
- package/package.json +30 -8
- package/rstest.config.mts +3 -0
- package/src/cli/artifacts.ts +255 -0
- package/src/cli/index.ts +7 -185
- package/src/cli/tanstackTypes/canonicalRoutes.ts +157 -0
- package/src/cli/tanstackTypes/shared.ts +119 -0
- package/src/cli/tanstackTypes/source.ts +405 -0
- package/src/cli/tanstackTypes.ts +4 -647
- package/src/runtime/blockingSubscribe.ts +48 -0
- package/src/runtime/clientBasepath.ts +12 -0
- package/src/runtime/clientHydration.ts +151 -0
- package/src/runtime/dataMutation.tsx +9 -306
- package/src/runtime/formData.ts +153 -0
- package/src/runtime/loaderBridge.ts +8 -3
- package/src/runtime/plugin.node.tsx +76 -291
- package/src/runtime/plugin.tsx +21 -257
- package/src/runtime/pluginShared.ts +112 -0
- package/src/runtime/prefetchLink.tsx +78 -15
- package/src/runtime/routeTree/components.ts +130 -0
- package/src/runtime/routeTree/construction.ts +238 -0
- package/src/runtime/routeTree/index.ts +7 -0
- package/src/runtime/routeTree/loaders.ts +232 -0
- package/src/runtime/routeTree/paths.ts +36 -0
- package/src/runtime/routeTree/staticData.ts +66 -0
- package/src/runtime/routeTree/tanstackRoutes.ts +36 -0
- package/src/runtime/routeTree/types.ts +87 -0
- package/src/runtime/routeTree.ts +5 -750
- package/src/runtime/rsc/CompositeComponent.tsx +29 -20
- package/src/runtime/rsc/RscNodeRenderer.tsx +9 -27
- package/src/runtime/rsc/client.tsx +12 -40
- package/src/runtime/rsc/flightSerialization.ts +158 -0
- package/src/runtime/rsc/payloadErrors.ts +73 -0
- package/src/runtime/rsc/payloadFetch.ts +97 -0
- package/src/runtime/rsc/payloadRouter.ts +29 -234
- package/src/runtime/rsc/payloadRoutes.ts +122 -0
- package/src/runtime/rsc/payloadStorage.ts +35 -0
- package/src/runtime/rsc/server.tsx +20 -29
- package/src/runtime/rsc/shared.ts +70 -0
- package/src/runtime/rsc/treeGetter.tsx +52 -0
- package/src/runtime/ssrManagedTags.ts +107 -0
- package/src/runtime/ssrPreload.ts +125 -0
- package/src/runtime/ssrTypes.ts +51 -0
- package/src/runtime/state.ts +4 -2
- package/src/runtime/submitAction.ts +194 -0
- package/src/runtime/types.ts +6 -13
- package/src/runtime/utils.tsx +76 -110
- package/src/types/react-server-dom-rspack.d.ts +56 -0
- package/tests/castRatchet.test.ts +85 -0
- package/tests/router/cli.test.ts +7 -0
- package/tests/router/dataMutation.test.tsx +59 -0
- package/tests/router/fixtures/router-gen.golden.txt +72 -0
- package/tests/router/generateRouteArtifacts.test.ts +106 -0
- package/tests/router/loaderBridge.test.ts +21 -0
- package/tests/router/prefetchLink.test.tsx +106 -59
- package/tests/router/prefetchLinkPreload.test.tsx +85 -0
- package/tests/router/routeTree.test.ts +46 -11
- package/tests/router/rsc.test.tsx +189 -0
- package/tests/router/ssrPreload.test.ts +30 -0
- package/tests/router/tanstackTypes.test.ts +254 -1
- package/dist/esm/runtime/pluginCore.mjs +0 -14
- package/dist/esm-node/runtime/pluginCore.mjs +0 -15
- package/dist/types/runtime/pluginCore.d.ts +0 -21
- package/src/runtime/pluginCore.ts +0 -48
|
@@ -1,79 +1,126 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { createMemoryHistory } from '@tanstack/history';
|
|
2
|
+
import {
|
|
3
|
+
createRootRoute,
|
|
4
|
+
createRoute,
|
|
5
|
+
createRouter,
|
|
6
|
+
RouterProvider,
|
|
7
|
+
} from '@tanstack/react-router';
|
|
8
|
+
import { render, waitFor } from '@testing-library/react';
|
|
2
9
|
import React from 'react';
|
|
3
10
|
import { Link, NavLink } from '../../src/runtime/prefetchLink';
|
|
4
11
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
// Real-router harness (no module mocking): STATIC_ACTIVE_PROPS (the
|
|
13
|
+
// TanStack behavior this adapter works around) is injected by
|
|
14
|
+
// useLinkProps() based on live route match state, which a mocked
|
|
15
|
+
// useLinkProps() cannot reproduce faithfully. We tried spying on
|
|
16
|
+
// useLinkProps via rstest.mock()+importActual while re-exporting the rest
|
|
17
|
+
// of the module untouched, but rstest's module-mock hoisting resolves the
|
|
18
|
+
// "actual" import for a module through its own in-progress mock
|
|
19
|
+
// registration, so every named export (createRootRoute included) comes
|
|
20
|
+
// back undefined -- see the sibling prefetchLinkPreload.test.tsx file for
|
|
21
|
+
// the fully-mocked preload-mapping coverage instead. This file renders
|
|
22
|
+
// through an actual createRouter()/RouterProvider tree with in-memory
|
|
23
|
+
// history so the active/inactive aria-current behavior is exercised for
|
|
24
|
+
// real.
|
|
25
|
+
function buildRouter(options: {
|
|
26
|
+
initialPath: string;
|
|
27
|
+
linkProps?: Record<string, unknown>;
|
|
28
|
+
useNavLink?: boolean;
|
|
29
|
+
children?:
|
|
30
|
+
| React.ReactNode
|
|
31
|
+
| ((state: { isActive: boolean }) => React.ReactNode);
|
|
32
|
+
}) {
|
|
33
|
+
// The Link lives on the always-rendered root component (not a route
|
|
34
|
+
// that's only matched at "/settings") so the "inactive link" case -
|
|
35
|
+
// sitting at "/" while linking to "/settings" - actually mounts it.
|
|
36
|
+
const rootRoute = createRootRoute({
|
|
37
|
+
component: () => {
|
|
38
|
+
const Component = options.useNavLink ? NavLink : Link;
|
|
39
|
+
return (
|
|
40
|
+
<Component to="/settings" {...(options.linkProps ?? {})}>
|
|
41
|
+
{options.children ?? 'Settings'}
|
|
42
|
+
</Component>
|
|
43
|
+
);
|
|
44
|
+
},
|
|
22
45
|
});
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
46
|
+
const homeRoute = createRoute({
|
|
47
|
+
getParentRoute: () => rootRoute,
|
|
48
|
+
path: '/',
|
|
49
|
+
component: () => null,
|
|
50
|
+
});
|
|
51
|
+
const settingsRoute = createRoute({
|
|
52
|
+
getParentRoute: () => rootRoute,
|
|
53
|
+
path: '/settings',
|
|
54
|
+
component: () => null,
|
|
28
55
|
});
|
|
29
56
|
|
|
30
|
-
|
|
31
|
-
render(
|
|
32
|
-
<Link to="/settings" prefetch="render" preload="intent">
|
|
33
|
-
Settings
|
|
34
|
-
</Link>,
|
|
35
|
-
);
|
|
57
|
+
const routeTree = rootRoute.addChildren([homeRoute, settingsRoute]);
|
|
36
58
|
|
|
37
|
-
|
|
59
|
+
return createRouter({
|
|
60
|
+
routeTree,
|
|
61
|
+
history: createMemoryHistory({ initialEntries: [options.initialPath] }),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function renderLink(options: Parameters<typeof buildRouter>[0]) {
|
|
66
|
+
const router = buildRouter(options);
|
|
67
|
+
const utils = render(<RouterProvider router={router} />);
|
|
68
|
+
|
|
69
|
+
const anchor = await waitFor(() => {
|
|
70
|
+
const found = utils.container.querySelector('a[href="/settings"]');
|
|
71
|
+
if (!found) {
|
|
72
|
+
throw new Error('anchor not rendered yet');
|
|
73
|
+
}
|
|
74
|
+
return found as HTMLAnchorElement;
|
|
38
75
|
});
|
|
39
76
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
<Link to="/settings" prefetch="render" preload={false}>
|
|
43
|
-
Settings
|
|
44
|
-
</Link>,
|
|
45
|
-
);
|
|
77
|
+
return { anchor, ...utils };
|
|
78
|
+
}
|
|
46
79
|
|
|
47
|
-
|
|
80
|
+
describe('tanstack prefetch link adapter - aria-current override', () => {
|
|
81
|
+
it('defaults active link to aria-current="page" when caller passes nothing', async () => {
|
|
82
|
+
const { anchor } = await renderLink({ initialPath: '/settings' });
|
|
83
|
+
expect(anchor.getAttribute('aria-current')).toBe('page');
|
|
48
84
|
});
|
|
49
85
|
|
|
50
|
-
it('
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
);
|
|
56
|
-
|
|
57
|
-
expect(capturedPreloads).toEqual([false]);
|
|
86
|
+
it('lets caller aria-current="true" win over the TanStack-forced value', async () => {
|
|
87
|
+
const { anchor } = await renderLink({
|
|
88
|
+
initialPath: '/settings',
|
|
89
|
+
linkProps: { 'aria-current': 'true' },
|
|
90
|
+
});
|
|
91
|
+
expect(anchor.getAttribute('aria-current')).toBe('true');
|
|
92
|
+
expect(anchor.outerHTML.match(/aria-current=/g)).toHaveLength(1);
|
|
58
93
|
});
|
|
59
94
|
|
|
60
|
-
it
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Settings
|
|
68
|
-
</Link>,
|
|
69
|
-
);
|
|
95
|
+
it('suppresses aria-current entirely when caller passes false', async () => {
|
|
96
|
+
const { anchor } = await renderLink({
|
|
97
|
+
initialPath: '/settings',
|
|
98
|
+
linkProps: { 'aria-current': false },
|
|
99
|
+
});
|
|
100
|
+
expect(anchor.hasAttribute('aria-current')).toBe(false);
|
|
101
|
+
});
|
|
70
102
|
|
|
71
|
-
|
|
103
|
+
it('leaves an inactive link untouched when caller passes nothing', async () => {
|
|
104
|
+
const { anchor } = await renderLink({ initialPath: '/' });
|
|
105
|
+
expect(anchor.hasAttribute('aria-current')).toBe(false);
|
|
106
|
+
expect(anchor.getAttribute('data-status')).not.toBe('active');
|
|
72
107
|
});
|
|
73
108
|
|
|
74
|
-
it('
|
|
75
|
-
|
|
109
|
+
it('resolves render-prop children with the active state like TanStack Link', async () => {
|
|
110
|
+
const { anchor } = await renderLink({
|
|
111
|
+
initialPath: '/settings',
|
|
112
|
+
children: ({ isActive }: { isActive: boolean }) =>
|
|
113
|
+
isActive ? 'Active settings' : 'Settings',
|
|
114
|
+
});
|
|
115
|
+
expect(anchor.textContent).toBe('Active settings');
|
|
116
|
+
});
|
|
76
117
|
|
|
77
|
-
|
|
118
|
+
it('resolves render-prop children as inactive on a non-matching route', async () => {
|
|
119
|
+
const { anchor } = await renderLink({
|
|
120
|
+
initialPath: '/',
|
|
121
|
+
children: ({ isActive }: { isActive: boolean }) =>
|
|
122
|
+
isActive ? 'Active settings' : 'Settings',
|
|
123
|
+
});
|
|
124
|
+
expect(anchor.textContent).toBe('Settings');
|
|
78
125
|
});
|
|
79
126
|
});
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { render } from '@testing-library/react';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
import { Link, NavLink } from '../../src/runtime/prefetchLink';
|
|
4
|
+
|
|
5
|
+
type CapturedOptions = {
|
|
6
|
+
preload?: unknown;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
type MockLinkAnchorProps = Record<string, unknown> & {
|
|
10
|
+
href?: string;
|
|
11
|
+
ref?: unknown;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
let capturedOptions: CapturedOptions[] = [];
|
|
15
|
+
let mockReturnProps: MockLinkAnchorProps = { href: '/settings' };
|
|
16
|
+
|
|
17
|
+
rstest.mock('@tanstack/react-router', () => ({
|
|
18
|
+
useLinkProps: (options: CapturedOptions) => {
|
|
19
|
+
capturedOptions.push(options);
|
|
20
|
+
return mockReturnProps;
|
|
21
|
+
},
|
|
22
|
+
}));
|
|
23
|
+
|
|
24
|
+
describe('tanstack prefetch link adapter - preload mapping', () => {
|
|
25
|
+
beforeEach(() => {
|
|
26
|
+
capturedOptions = [];
|
|
27
|
+
mockReturnProps = { href: '/settings' };
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('defaults TanStack preload to viewport', () => {
|
|
31
|
+
render(<Link to="/settings">Settings</Link>);
|
|
32
|
+
|
|
33
|
+
expect(capturedOptions.map(o => o.preload)).toEqual(['viewport']);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it('preserves explicit preload', () => {
|
|
37
|
+
render(
|
|
38
|
+
<Link to="/settings" prefetch="render" preload="intent">
|
|
39
|
+
Settings
|
|
40
|
+
</Link>,
|
|
41
|
+
);
|
|
42
|
+
|
|
43
|
+
expect(capturedOptions.map(o => o.preload)).toEqual(['intent']);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it('preserves explicit disabled preload', () => {
|
|
47
|
+
render(
|
|
48
|
+
<Link to="/settings" prefetch="render" preload={false}>
|
|
49
|
+
Settings
|
|
50
|
+
</Link>,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
expect(capturedOptions.map(o => o.preload)).toEqual([false]);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('maps none prefetch to disabled TanStack preload', () => {
|
|
57
|
+
render(
|
|
58
|
+
<Link to="/settings" prefetch="none">
|
|
59
|
+
Settings
|
|
60
|
+
</Link>,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
expect(capturedOptions.map(o => o.preload)).toEqual([false]);
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it.each([
|
|
67
|
+
'intent',
|
|
68
|
+
'render',
|
|
69
|
+
'viewport',
|
|
70
|
+
] as const)('maps %s prefetch to TanStack preload', prefetch => {
|
|
71
|
+
render(
|
|
72
|
+
<Link to="/settings" prefetch={prefetch}>
|
|
73
|
+
Settings
|
|
74
|
+
</Link>,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
expect(capturedOptions.map(o => o.preload)).toEqual([prefetch]);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('defaults NavLink preload to viewport', () => {
|
|
81
|
+
render(<NavLink to="/settings">Settings</NavLink>);
|
|
82
|
+
|
|
83
|
+
expect(capturedOptions.map(o => o.preload)).toEqual(['viewport']);
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -15,7 +15,7 @@ import {
|
|
|
15
15
|
getModernRouteIdsFromMatches,
|
|
16
16
|
} from '../../src/runtime/routeTree';
|
|
17
17
|
import { __setTanstackRscPayloadDecoderForTests } from '../../src/runtime/rsc/payloadRouter';
|
|
18
|
-
import {
|
|
18
|
+
import { createTanstackRouteObjectsFromConfig } from '../../src/runtime/utils';
|
|
19
19
|
|
|
20
20
|
type LoaderArgs = {
|
|
21
21
|
params: Record<string, string>;
|
|
@@ -151,15 +151,15 @@ describe('tanstack runtime public exports', () => {
|
|
|
151
151
|
};
|
|
152
152
|
|
|
153
153
|
expect(packageJson.exports['./runtime/rsc']).toBeUndefined();
|
|
154
|
-
expect(packageJson.exports['./runtime/rsc/client']).
|
|
155
|
-
expect(packageJson.exports['./runtime/rsc/server']).
|
|
154
|
+
expect(packageJson.exports['./runtime/rsc/client']).toBeDefined();
|
|
155
|
+
expect(packageJson.exports['./runtime/rsc/server']).toBeDefined();
|
|
156
156
|
expect(packageJson.typesVersions?.['*']?.['runtime/rsc']).toBeUndefined();
|
|
157
|
-
expect(
|
|
158
|
-
|
|
159
|
-
)
|
|
160
|
-
expect(
|
|
161
|
-
|
|
162
|
-
)
|
|
157
|
+
expect(packageJson.typesVersions?.['*']?.['runtime/rsc/client']).toEqual([
|
|
158
|
+
'./dist/types/runtime/rsc/client.d.ts',
|
|
159
|
+
]);
|
|
160
|
+
expect(packageJson.typesVersions?.['*']?.['runtime/rsc/server']).toEqual([
|
|
161
|
+
'./dist/types/runtime/rsc/server.d.ts',
|
|
162
|
+
]);
|
|
163
163
|
});
|
|
164
164
|
});
|
|
165
165
|
|
|
@@ -201,6 +201,41 @@ describe('tanstack route tree from RouteObject[]', () => {
|
|
|
201
201
|
expect(userMatch?.loaderData).toEqual({ id: '123' });
|
|
202
202
|
});
|
|
203
203
|
|
|
204
|
+
test('keeps unnamed sibling pathless layouts distinct', async () => {
|
|
205
|
+
const routes: RouteObject[] = [
|
|
206
|
+
{
|
|
207
|
+
Component: () => null,
|
|
208
|
+
children: [
|
|
209
|
+
{
|
|
210
|
+
path: 'alpha',
|
|
211
|
+
loader: () => ({ value: 'alpha' }),
|
|
212
|
+
Component: () => null,
|
|
213
|
+
},
|
|
214
|
+
],
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
Component: () => null,
|
|
218
|
+
children: [
|
|
219
|
+
{
|
|
220
|
+
path: 'beta',
|
|
221
|
+
loader: () => ({ value: 'beta' }),
|
|
222
|
+
Component: () => null,
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
},
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
const routeTree = createRouteTreeFromRouteObjects(routes);
|
|
229
|
+
const router = await loadRouteTree(routeTree, '/beta');
|
|
230
|
+
|
|
231
|
+
const matchedLoaderData = router.state.matches
|
|
232
|
+
.map(match => match.loaderData)
|
|
233
|
+
.filter(Boolean);
|
|
234
|
+
|
|
235
|
+
expect(matchedLoaderData).toContainEqual({ value: 'beta' });
|
|
236
|
+
expect(matchedLoaderData).not.toContainEqual({ value: 'alpha' });
|
|
237
|
+
});
|
|
238
|
+
|
|
204
239
|
test('reports native TanStack unknown routes as HTTP 404', async () => {
|
|
205
240
|
const routes: RouteObject[] = [
|
|
206
241
|
{
|
|
@@ -780,7 +815,7 @@ describe('tanstack route tree from RouteObject[]', () => {
|
|
|
780
815
|
],
|
|
781
816
|
},
|
|
782
817
|
];
|
|
783
|
-
const routeObjects =
|
|
818
|
+
const routeObjects = createTanstackRouteObjectsFromConfig({
|
|
784
819
|
routesConfig: { routes: modernRoutes },
|
|
785
820
|
});
|
|
786
821
|
const routeTree = createRouteTreeFromRouteObjects(routeObjects || []);
|
|
@@ -825,7 +860,7 @@ describe('tanstack route tree from RouteObject[]', () => {
|
|
|
825
860
|
},
|
|
826
861
|
];
|
|
827
862
|
|
|
828
|
-
const routeObjects =
|
|
863
|
+
const routeObjects = createTanstackRouteObjectsFromConfig({
|
|
829
864
|
routesConfig: { routes: modernRoutes },
|
|
830
865
|
});
|
|
831
866
|
const rootRoute = routeObjects?.[0];
|
|
@@ -2,6 +2,10 @@ import { UNSAFE_ErrorResponseImpl as ErrorResponseImpl } from '@modern-js/runtim
|
|
|
2
2
|
import type React from 'react';
|
|
3
3
|
import { isValidElement } from 'react';
|
|
4
4
|
import { createRscProxy } from '../../src/runtime/rsc/createRscProxy';
|
|
5
|
+
import {
|
|
6
|
+
reviveTanstackRscFlightValues,
|
|
7
|
+
serializeTanstackRscFlightValues,
|
|
8
|
+
} from '../../src/runtime/rsc/flightSerialization';
|
|
5
9
|
import {
|
|
6
10
|
__setTanstackRscPayloadDecoderForTests,
|
|
7
11
|
createTanstackRscServerPayload,
|
|
@@ -65,6 +69,54 @@ describe('tanstack rsc runtime helpers', () => {
|
|
|
65
69
|
]);
|
|
66
70
|
});
|
|
67
71
|
|
|
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
|
+
|
|
68
120
|
test('renderable RSC proxies preserve React element behavior and metadata', () => {
|
|
69
121
|
const stream: ServerComponentStream = {
|
|
70
122
|
createReplayStream: () => new ReadableStream<Uint8Array>(),
|
|
@@ -219,6 +271,36 @@ describe('tanstack rsc runtime helpers', () => {
|
|
|
219
271
|
expect(JSON.stringify(payload.errors)).not.toContain('stack secret');
|
|
220
272
|
});
|
|
221
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
|
+
'/plain': {
|
|
295
|
+
message: 'Unexpected Server Error',
|
|
296
|
+
stack: undefined,
|
|
297
|
+
__type: 'Error',
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
expect(JSON.stringify(payload.errors)).not.toContain('plain secret');
|
|
301
|
+
expect(JSON.stringify(payload.errors)).not.toContain('token secret');
|
|
302
|
+
});
|
|
303
|
+
|
|
222
304
|
test('converts TanStack RSC redirects to Modern RSC navigation headers', () => {
|
|
223
305
|
const response = handleTanstackRscRedirect(
|
|
224
306
|
new Headers({ Location: '/base/login' }),
|
|
@@ -232,6 +314,54 @@ describe('tanstack rsc runtime helpers', () => {
|
|
|
232
314
|
expect(response.headers.get('X-Modernjs-BaseUrl')).toBe('/base');
|
|
233
315
|
});
|
|
234
316
|
|
|
317
|
+
test('preserves TanStack RSC redirect paths that do not start with the basename', () => {
|
|
318
|
+
const response = handleTanstackRscRedirect(
|
|
319
|
+
new Headers({ Location: '/shop/base/login' }),
|
|
320
|
+
'/base',
|
|
321
|
+
302,
|
|
322
|
+
);
|
|
323
|
+
|
|
324
|
+
expect(response.headers.get('X-Modernjs-Redirect')).toBe(
|
|
325
|
+
'/shop/base/login',
|
|
326
|
+
);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
test('preserves RSC redirect response status and headers in TanStack redirects', async () => {
|
|
330
|
+
rstest.stubGlobal(
|
|
331
|
+
'fetch',
|
|
332
|
+
rstest.fn(() =>
|
|
333
|
+
Promise.resolve(
|
|
334
|
+
new Response(null, {
|
|
335
|
+
headers: {
|
|
336
|
+
'X-Modernjs-Redirect': '/login',
|
|
337
|
+
'X-Trace': 'preserved',
|
|
338
|
+
},
|
|
339
|
+
status: 308,
|
|
340
|
+
}),
|
|
341
|
+
),
|
|
342
|
+
),
|
|
343
|
+
);
|
|
344
|
+
|
|
345
|
+
const thrown = (await loadTanstackRscRouteData({
|
|
346
|
+
loadClientData: async () => ({ fallback: true }),
|
|
347
|
+
request: new Request('http://localhost/products'),
|
|
348
|
+
routeId: '/products',
|
|
349
|
+
}).then(
|
|
350
|
+
() => {
|
|
351
|
+
throw new Error('expected redirect');
|
|
352
|
+
},
|
|
353
|
+
err => err,
|
|
354
|
+
)) as Response & {
|
|
355
|
+
options?: { statusCode?: number; to?: string };
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
expect(thrown).toBeInstanceOf(Response);
|
|
359
|
+
expect(thrown.status).toBe(308);
|
|
360
|
+
expect(thrown.headers.get('X-Trace')).toBe('preserved');
|
|
361
|
+
expect(thrown.options?.statusCode).toBe(308);
|
|
362
|
+
expect(thrown.options?.to).toBe('/login');
|
|
363
|
+
});
|
|
364
|
+
|
|
235
365
|
test('loads one RSC payload for multiple server route loaders', async () => {
|
|
236
366
|
const payload = {
|
|
237
367
|
type: 'render',
|
|
@@ -275,6 +405,65 @@ describe('tanstack rsc runtime helpers', () => {
|
|
|
275
405
|
expect(decodeMock).toHaveBeenCalledTimes(1);
|
|
276
406
|
});
|
|
277
407
|
|
|
408
|
+
test('separates RSC payload cache entries by forwarded request headers', async () => {
|
|
409
|
+
const payloadByUser: Record<string, unknown> = {
|
|
410
|
+
alice: {
|
|
411
|
+
type: 'render',
|
|
412
|
+
actionData: null,
|
|
413
|
+
errors: null,
|
|
414
|
+
loaderData: {
|
|
415
|
+
'/profile': { user: 'alice' },
|
|
416
|
+
},
|
|
417
|
+
location: { href: '/profile' },
|
|
418
|
+
routes: [{ id: '/profile', hasLoader: true }],
|
|
419
|
+
},
|
|
420
|
+
bob: {
|
|
421
|
+
type: 'render',
|
|
422
|
+
actionData: null,
|
|
423
|
+
errors: null,
|
|
424
|
+
loaderData: {
|
|
425
|
+
'/profile': { user: 'bob' },
|
|
426
|
+
},
|
|
427
|
+
location: { href: '/profile' },
|
|
428
|
+
routes: [{ id: '/profile', hasLoader: true }],
|
|
429
|
+
},
|
|
430
|
+
};
|
|
431
|
+
const fetchMock = rstest.fn(
|
|
432
|
+
(_url: string | URL | Request, init?: RequestInit) => {
|
|
433
|
+
const user =
|
|
434
|
+
init?.headers instanceof Headers ? init.headers.get('x-user') : null;
|
|
435
|
+
return Promise.resolve(
|
|
436
|
+
new Response(JSON.stringify(payloadByUser[user || ''])),
|
|
437
|
+
);
|
|
438
|
+
},
|
|
439
|
+
);
|
|
440
|
+
rstest.stubGlobal('fetch', fetchMock);
|
|
441
|
+
__setTanstackRscPayloadDecoderForTests(async stream =>
|
|
442
|
+
JSON.parse(await new Response(stream).text()),
|
|
443
|
+
);
|
|
444
|
+
|
|
445
|
+
const [aliceData, bobData] = await Promise.all([
|
|
446
|
+
loadTanstackRscRouteData({
|
|
447
|
+
loadClientData: async () => ({ fallback: 'alice' }),
|
|
448
|
+
request: new Request('http://localhost/profile', {
|
|
449
|
+
headers: { 'x-user': 'alice' },
|
|
450
|
+
}),
|
|
451
|
+
routeId: '/profile',
|
|
452
|
+
}),
|
|
453
|
+
loadTanstackRscRouteData({
|
|
454
|
+
loadClientData: async () => ({ fallback: 'bob' }),
|
|
455
|
+
request: new Request('http://localhost/profile', {
|
|
456
|
+
headers: { 'x-user': 'bob' },
|
|
457
|
+
}),
|
|
458
|
+
routeId: '/profile',
|
|
459
|
+
}),
|
|
460
|
+
]);
|
|
461
|
+
|
|
462
|
+
expect(aliceData).toEqual({ user: 'alice' });
|
|
463
|
+
expect(bobData).toEqual({ user: 'bob' });
|
|
464
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
465
|
+
});
|
|
466
|
+
|
|
278
467
|
test('uses client loader data without requesting an RSC payload for client-loader routes', async () => {
|
|
279
468
|
const fetchMock = rstest.fn();
|
|
280
469
|
const loadClientData = rstest.fn(async () => ({ client: true }));
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { preloadMatchedRouteComponents } from '../../src/runtime/ssrPreload';
|
|
2
|
+
|
|
3
|
+
describe('preloadMatchedRouteComponents', () => {
|
|
4
|
+
test('preloads each component identity once per SSR pass', async () => {
|
|
5
|
+
const sharedComponent = Object.assign(() => null, {
|
|
6
|
+
preload: rstest.fn(),
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
await preloadMatchedRouteComponents({
|
|
10
|
+
routesById: {
|
|
11
|
+
child: {
|
|
12
|
+
options: {
|
|
13
|
+
component: sharedComponent,
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
root: {
|
|
17
|
+
options: {
|
|
18
|
+
component: sharedComponent,
|
|
19
|
+
pendingComponent: sharedComponent,
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
state: {
|
|
24
|
+
matches: [{ routeId: 'root' }, { routeId: 'child' }],
|
|
25
|
+
},
|
|
26
|
+
} as never);
|
|
27
|
+
|
|
28
|
+
expect(sharedComponent.preload).toHaveBeenCalledTimes(1);
|
|
29
|
+
});
|
|
30
|
+
});
|