@bleedingdev/modern-js-plugin-tanstack 3.9.0-ultramodern.8 → 3.9.3-ultramodern.1
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 +15 -1
- package/dist/cjs/cli/index.js +13 -2
- package/dist/cjs/cli/routeMetadata.js +131 -0
- package/dist/cjs/cli/rscRouteLayer.js +86 -0
- package/dist/cjs/cli/tanstackTypes/canonicalRoutes.js +7 -7
- package/dist/cjs/cli/tanstackTypes/shared.js +0 -14
- package/dist/cjs/cli/tanstackTypes/source.js +28 -63
- package/dist/cjs/runtime/lifecycle.js +0 -3
- package/dist/cjs/runtime/loaderBridge.js +10 -4
- package/dist/cjs/runtime/navigation.js +102 -0
- package/dist/cjs/runtime/plugin.js +10 -1
- package/dist/cjs/runtime/plugin.node.js +89 -64
- package/dist/cjs/runtime/routeTree/construction.js +19 -61
- package/dist/cjs/runtime/routeTree/loaders.js +1 -1
- package/dist/cjs/runtime/routeTree/paths.js +0 -6
- package/dist/cjs/runtime/ssrManagedTags.js +2 -12
- package/dist/cjs/runtime/utils.js +1 -0
- package/dist/cjs/shared/routeDescriptor.js +60 -0
- package/dist/esm/cli/artifacts.mjs +13 -2
- package/dist/esm/cli/index.mjs +13 -11
- package/dist/esm/cli/routeMetadata.mjs +71 -0
- package/dist/esm/cli/rscRouteLayer.mjs +35 -0
- package/dist/esm/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
- package/dist/esm/cli/tanstackTypes/shared.mjs +1 -9
- package/dist/esm/cli/tanstackTypes/source.mjs +29 -64
- package/dist/esm/runtime/lifecycle.mjs +1 -1
- package/dist/esm/runtime/loaderBridge.mjs +8 -5
- package/dist/esm/runtime/navigation.mjs +64 -0
- package/dist/esm/runtime/plugin.mjs +10 -1
- package/dist/esm/runtime/plugin.node.mjs +91 -66
- package/dist/esm/runtime/routeTree/construction.mjs +19 -61
- package/dist/esm/runtime/routeTree/loaders.mjs +2 -2
- package/dist/esm/runtime/routeTree/paths.mjs +1 -4
- package/dist/esm/runtime/ssrManagedTags.mjs +1 -8
- package/dist/esm/runtime/utils.mjs +1 -0
- package/dist/esm/shared/routeDescriptor.mjs +22 -0
- package/dist/esm-node/cli/artifacts.mjs +13 -2
- package/dist/esm-node/cli/index.mjs +13 -11
- package/dist/esm-node/cli/routeMetadata.mjs +72 -0
- package/dist/esm-node/cli/rscRouteLayer.mjs +36 -0
- package/dist/esm-node/cli/tanstackTypes/canonicalRoutes.mjs +7 -7
- package/dist/esm-node/cli/tanstackTypes/shared.mjs +1 -9
- package/dist/esm-node/cli/tanstackTypes/source.mjs +29 -64
- package/dist/esm-node/runtime/lifecycle.mjs +1 -1
- package/dist/esm-node/runtime/loaderBridge.mjs +8 -5
- package/dist/esm-node/runtime/navigation.mjs +65 -0
- package/dist/esm-node/runtime/plugin.mjs +10 -1
- package/dist/esm-node/runtime/plugin.node.mjs +91 -66
- package/dist/esm-node/runtime/routeTree/construction.mjs +19 -61
- package/dist/esm-node/runtime/routeTree/loaders.mjs +2 -2
- package/dist/esm-node/runtime/routeTree/paths.mjs +1 -4
- package/dist/esm-node/runtime/ssrManagedTags.mjs +1 -8
- package/dist/esm-node/runtime/utils.mjs +1 -0
- package/dist/esm-node/shared/routeDescriptor.mjs +23 -0
- package/dist/types/cli/artifacts.d.ts +16 -7
- package/dist/types/cli/index.d.ts +1 -1
- package/dist/types/cli/routeMetadata.d.ts +18 -0
- package/dist/types/cli/rscRouteLayer.d.ts +11 -0
- package/dist/types/cli/tanstackTypes/shared.d.ts +0 -2
- package/dist/types/cli/tanstackTypes/source.d.ts +3 -2
- package/dist/types/runtime/lifecycle.d.ts +1 -1
- package/dist/types/runtime/loaderBridge.d.ts +1 -0
- package/dist/types/runtime/navigation.d.ts +4 -0
- package/dist/types/runtime/pluginShared.d.ts +5 -0
- package/dist/types/runtime/routeTree/paths.d.ts +0 -2
- package/dist/types/runtime/ssrManagedTags.d.ts +0 -1
- package/dist/types/runtime/ssrTypes.d.ts +0 -7
- package/dist/types/shared/routeDescriptor.d.ts +26 -0
- package/package.json +32 -26
- package/src/cli/artifacts.ts +59 -8
- package/src/cli/index.ts +17 -4
- package/src/cli/routeMetadata.ts +121 -0
- package/src/cli/rscRouteLayer.ts +93 -0
- package/src/cli/tanstackTypes/canonicalRoutes.ts +17 -12
- package/src/cli/tanstackTypes/shared.ts +0 -14
- package/src/cli/tanstackTypes/source.ts +48 -120
- package/src/runtime/lifecycle.ts +0 -1
- package/src/runtime/loaderBridge.ts +9 -9
- package/src/runtime/navigation.ts +78 -0
- package/src/runtime/plugin.node.tsx +121 -91
- package/src/runtime/plugin.tsx +10 -0
- package/src/runtime/pluginShared.ts +12 -0
- package/src/runtime/routeTree/construction.ts +32 -112
- package/src/runtime/routeTree/loaders.ts +2 -4
- package/src/runtime/routeTree/paths.ts +0 -6
- package/src/runtime/ssrManagedTags.ts +0 -17
- package/src/runtime/ssrTypes.ts +0 -7
- package/src/runtime/utils.tsx +1 -0
- package/src/shared/routeDescriptor.ts +46 -0
- package/dist/esm/rslib-runtime~1.mjs +0 -18
- package/dist/esm-node/rslib-runtime~0.mjs +0 -19
- package/rslib.config.mts +0 -4
- package/rstest.config.mts +0 -53
- package/tests/router/cli.test.ts +0 -420
- package/tests/router/clientHydration.test.tsx +0 -200
- package/tests/router/dataMutation.test.tsx +0 -428
- package/tests/router/fixtures/router-gen.oxlint.json +0 -5
- package/tests/router/flightSerialization.roundtrip.test.tsx +0 -140
- package/tests/router/generateRouteArtifacts.test.ts +0 -146
- package/tests/router/loaderBridge.test.ts +0 -207
- package/tests/router/localisedIdentity.test.tsx +0 -95
- package/tests/router/prefetchLink.test.tsx +0 -122
- package/tests/router/prefetchLinkPreload.test.tsx +0 -65
- package/tests/router/routeHooks.test.ts +0 -49
- package/tests/router/routeTree.test.ts +0 -240
- package/tests/router/rsc.test.tsx +0 -480
- package/tests/router/serverPlugin.test.ts +0 -157
- package/tests/router/slotUsageSanitizer.test.ts +0 -23
- package/tests/router/ssrPreload.test.ts +0 -30
- package/tests/router/tanstackTypes.test.ts +0 -384
- package/tsconfig.json +0 -12
- package/tsconfig.tsgo.json +0 -6
|
@@ -1,122 +0,0 @@
|
|
|
1
|
-
import { createMemoryHistory } from '@tanstack/history';
|
|
2
|
-
import {
|
|
3
|
-
createRootRoute,
|
|
4
|
-
createRoute,
|
|
5
|
-
createRouter,
|
|
6
|
-
RouterProvider,
|
|
7
|
-
} from '@tanstack/react-router';
|
|
8
|
-
import { act, render, waitFor } from '@testing-library/react';
|
|
9
|
-
import React from 'react';
|
|
10
|
-
import { Link, NavLink } from '../../src/runtime/prefetchLink';
|
|
11
|
-
|
|
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
|
-
},
|
|
45
|
-
});
|
|
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,
|
|
55
|
-
});
|
|
56
|
-
|
|
57
|
-
const routeTree = rootRoute.addChildren([homeRoute, settingsRoute]);
|
|
58
|
-
|
|
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;
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
return { anchor, router, ...utils };
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
describe('tanstack prefetch link adapter - aria-current override', () => {
|
|
81
|
-
it('lets caller aria-current="true" win over the TanStack-forced value', async () => {
|
|
82
|
-
const { anchor } = await renderLink({
|
|
83
|
-
initialPath: '/settings',
|
|
84
|
-
linkProps: { 'aria-current': 'true' },
|
|
85
|
-
});
|
|
86
|
-
expect(anchor.getAttribute('aria-current')).toBe('true');
|
|
87
|
-
expect(anchor.outerHTML.match(/aria-current=/g)).toHaveLength(1);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('suppresses aria-current entirely when caller passes false', async () => {
|
|
91
|
-
const { anchor } = await renderLink({
|
|
92
|
-
initialPath: '/settings',
|
|
93
|
-
linkProps: { 'aria-current': false },
|
|
94
|
-
});
|
|
95
|
-
expect(anchor.hasAttribute('aria-current')).toBe(false);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it('updates render-prop children across an inactive-to-active transition', async () => {
|
|
99
|
-
const { anchor, router } = await renderLink({
|
|
100
|
-
initialPath: '/',
|
|
101
|
-
children: ({ isActive }: { isActive: boolean }) =>
|
|
102
|
-
isActive ? 'Active settings' : 'Settings',
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
expect(anchor.textContent).toBe('Settings');
|
|
106
|
-
|
|
107
|
-
await act(async () => {
|
|
108
|
-
await router.navigate({ to: '/settings' });
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
await waitFor(() => expect(anchor.textContent).toBe('Active settings'));
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
it('passes the native anchor ref through the router hook', async () => {
|
|
115
|
-
const ref = React.createRef<HTMLAnchorElement>();
|
|
116
|
-
const { anchor } = await renderLink({
|
|
117
|
-
initialPath: '/',
|
|
118
|
-
linkProps: { ref, prefetch: 'none' },
|
|
119
|
-
});
|
|
120
|
-
expect(ref.current).toBe(anchor);
|
|
121
|
-
});
|
|
122
|
-
});
|
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
import { render } from '@testing-library/react';
|
|
2
|
-
import React from 'react';
|
|
3
|
-
import { Link } 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('preserves an explicit false preload override', () => {
|
|
31
|
-
render(
|
|
32
|
-
<Link to="/settings" prefetch="render" preload={false}>
|
|
33
|
-
Settings
|
|
34
|
-
</Link>,
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
expect(capturedOptions[0]?.preload).toBe(false);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('defaults to viewport preload when no prefetch is given', () => {
|
|
41
|
-
render(<Link to="/settings">Settings</Link>);
|
|
42
|
-
|
|
43
|
-
expect(capturedOptions[0]?.preload).toBe('viewport');
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
it('maps none prefetch to disabled TanStack preload', () => {
|
|
47
|
-
render(
|
|
48
|
-
<Link to="/settings" prefetch="none">
|
|
49
|
-
Settings
|
|
50
|
-
</Link>,
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
expect(capturedOptions[0]?.preload).toBe(false);
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
it('lets explicit preload re-enable prefetch=none', () => {
|
|
57
|
-
render(
|
|
58
|
-
<Link to="/settings" prefetch="none" preload="render">
|
|
59
|
-
Settings
|
|
60
|
-
</Link>,
|
|
61
|
-
);
|
|
62
|
-
|
|
63
|
-
expect(capturedOptions.map(o => o.preload)).toEqual(['render']);
|
|
64
|
-
});
|
|
65
|
-
});
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { createMemoryHistory } from '@tanstack/history';
|
|
2
|
-
import {
|
|
3
|
-
createRootRoute,
|
|
4
|
-
createRoute,
|
|
5
|
-
createRouter,
|
|
6
|
-
RouterProvider,
|
|
7
|
-
} from '@tanstack/react-router';
|
|
8
|
-
import React from 'react';
|
|
9
|
-
import { renderToString } from 'react-dom/server';
|
|
10
|
-
import { useMatches } from '../../src/runtime/routeHooks';
|
|
11
|
-
|
|
12
|
-
function MatchIds() {
|
|
13
|
-
const matches = useMatches();
|
|
14
|
-
return React.createElement(
|
|
15
|
-
'output',
|
|
16
|
-
{ 'data-testid': 'route-ids' },
|
|
17
|
-
matches.map(match => match.routeId).join(','),
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
test('updates whole match objects after navigation with the structural-sharing guard', async () => {
|
|
22
|
-
const rootRoute = createRootRoute({ component: MatchIds });
|
|
23
|
-
const homeRoute = createRoute({
|
|
24
|
-
getParentRoute: () => rootRoute,
|
|
25
|
-
path: '/',
|
|
26
|
-
component: () => null,
|
|
27
|
-
});
|
|
28
|
-
const settingsRoute = createRoute({
|
|
29
|
-
getParentRoute: () => rootRoute,
|
|
30
|
-
path: '/settings',
|
|
31
|
-
component: () => null,
|
|
32
|
-
});
|
|
33
|
-
const router = createRouter({
|
|
34
|
-
routeTree: rootRoute.addChildren([homeRoute, settingsRoute]),
|
|
35
|
-
history: createMemoryHistory({ initialEntries: ['/'] }),
|
|
36
|
-
defaultStructuralSharing: true,
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
await router.load();
|
|
40
|
-
expect(
|
|
41
|
-
renderToString(React.createElement(RouterProvider, { router })),
|
|
42
|
-
).toContain('__root__');
|
|
43
|
-
|
|
44
|
-
await router.navigate({ to: '/settings' });
|
|
45
|
-
|
|
46
|
-
expect(
|
|
47
|
-
renderToString(React.createElement(RouterProvider, { router })),
|
|
48
|
-
).toContain('/settings');
|
|
49
|
-
});
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import type { RouteObject } from '@modern-js/runtime-utils/router';
|
|
2
|
-
import { createMemoryHistory } from '@tanstack/history';
|
|
3
|
-
import {
|
|
4
|
-
createRouter,
|
|
5
|
-
Outlet,
|
|
6
|
-
RouterProvider,
|
|
7
|
-
redirect,
|
|
8
|
-
} from '@tanstack/react-router';
|
|
9
|
-
import type { ComponentType } from 'react';
|
|
10
|
-
import { createElement, lazy } from 'react';
|
|
11
|
-
import { renderToStaticMarkup, renderToString } from 'react-dom/server';
|
|
12
|
-
import { Outlet as ModernOutlet } from '../../src/runtime/outlet';
|
|
13
|
-
import { createRouteTreeFromRouteObjects } from '../../src/runtime/routeTree';
|
|
14
|
-
|
|
15
|
-
type LoaderArgs = { params: Record<string, string> };
|
|
16
|
-
|
|
17
|
-
type TestRouteObject = RouteObject & {
|
|
18
|
-
children?: TestRouteObject[];
|
|
19
|
-
lazyImport?: () => Promise<unknown>;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
type TestRouter = {
|
|
23
|
-
_serverResult?:
|
|
24
|
-
| { type: 'redirect'; redirect: Response }
|
|
25
|
-
| { type: 'render'; status: number };
|
|
26
|
-
state: {
|
|
27
|
-
matches: Array<{ error?: unknown; loaderData?: unknown; routeId: string }>;
|
|
28
|
-
};
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
const nullComponent = () => null;
|
|
32
|
-
|
|
33
|
-
const root = (
|
|
34
|
-
children: TestRouteObject[],
|
|
35
|
-
Component: RouteObject['Component'] = nullComponent,
|
|
36
|
-
): TestRouteObject[] => [{ id: 'root', path: '/', Component, children }];
|
|
37
|
-
|
|
38
|
-
async function loadRouteTree(
|
|
39
|
-
routes: TestRouteObject[],
|
|
40
|
-
pathname: string,
|
|
41
|
-
): Promise<TestRouter> {
|
|
42
|
-
const router = createRouter({
|
|
43
|
-
routeTree: createRouteTreeFromRouteObjects(routes as RouteObject[]),
|
|
44
|
-
history: createMemoryHistory({ initialEntries: [pathname] }),
|
|
45
|
-
context: {
|
|
46
|
-
request: new Request(`http://localhost${pathname}`),
|
|
47
|
-
requestContext: {},
|
|
48
|
-
},
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
await router.load();
|
|
52
|
-
return router as unknown as TestRouter;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const matchOf = (router: TestRouter, routeId: string) =>
|
|
56
|
-
router.state.matches.find(match => match.routeId === routeId);
|
|
57
|
-
|
|
58
|
-
describe('tanstack route tree from RouteObject[]', () => {
|
|
59
|
-
test('resolves loader data for root, dynamic and splat params', async () => {
|
|
60
|
-
const routes: TestRouteObject[] = root(
|
|
61
|
-
[
|
|
62
|
-
{
|
|
63
|
-
id: 'user',
|
|
64
|
-
path: 'user/:id',
|
|
65
|
-
loader: ({ params }: LoaderArgs) => ({ id: params.id }),
|
|
66
|
-
Component: nullComponent,
|
|
67
|
-
},
|
|
68
|
-
{
|
|
69
|
-
id: 'files',
|
|
70
|
-
path: 'files/*',
|
|
71
|
-
loader: ({ params }: LoaderArgs) => ({ value: params['*'] }),
|
|
72
|
-
Component: nullComponent,
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
nullComponent,
|
|
76
|
-
);
|
|
77
|
-
routes[0].loader = () => ({ root: 'ok' });
|
|
78
|
-
|
|
79
|
-
const userRouter = await loadRouteTree(routes, '/user/123');
|
|
80
|
-
expect(matchOf(userRouter, '__root__')?.loaderData).toEqual({ root: 'ok' });
|
|
81
|
-
expect(matchOf(userRouter, '/user/$id')?.loaderData).toEqual({ id: '123' });
|
|
82
|
-
|
|
83
|
-
const splatRouter = await loadRouteTree(routes, '/files/a/b/c');
|
|
84
|
-
expect(matchOf(splatRouter, '/files/$')?.loaderData).toEqual({
|
|
85
|
-
value: 'a/b/c',
|
|
86
|
-
});
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
test('keeps unnamed sibling pathless layouts distinct', async () => {
|
|
90
|
-
const pathless = (path: string): TestRouteObject => ({
|
|
91
|
-
Component: nullComponent,
|
|
92
|
-
children: [
|
|
93
|
-
{ path, loader: () => ({ value: path }), Component: nullComponent },
|
|
94
|
-
],
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
const router = await loadRouteTree(
|
|
98
|
-
[pathless('alpha'), pathless('beta')],
|
|
99
|
-
'/beta',
|
|
100
|
-
);
|
|
101
|
-
const loaderData = router.state.matches
|
|
102
|
-
.map(match => match.loaderData)
|
|
103
|
-
.filter(Boolean);
|
|
104
|
-
|
|
105
|
-
expect(loaderData).toContainEqual({ value: 'beta' });
|
|
106
|
-
expect(loaderData).not.toContainEqual({ value: 'alpha' });
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
test('reports native TanStack unknown routes as HTTP 404', async () => {
|
|
110
|
-
const router = await loadRouteTree(
|
|
111
|
-
root([{ id: 'known', path: 'known', Component: nullComponent }]),
|
|
112
|
-
'/missing',
|
|
113
|
-
);
|
|
114
|
-
|
|
115
|
-
expect(router._serverResult).toMatchObject({ type: 'render', status: 404 });
|
|
116
|
-
});
|
|
117
|
-
|
|
118
|
-
test('reports native TanStack loader redirects as server responses', async () => {
|
|
119
|
-
const router = await loadRouteTree(
|
|
120
|
-
root([
|
|
121
|
-
{
|
|
122
|
-
id: 'redirect',
|
|
123
|
-
path: 'redirect',
|
|
124
|
-
loader: () => {
|
|
125
|
-
throw redirect({ to: '/target' });
|
|
126
|
-
},
|
|
127
|
-
Component: nullComponent,
|
|
128
|
-
},
|
|
129
|
-
{ id: 'target', path: 'target', Component: nullComponent },
|
|
130
|
-
]),
|
|
131
|
-
'/redirect',
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
if (router._serverResult?.type !== 'redirect') {
|
|
135
|
-
throw new Error('Expected a TanStack redirect server result');
|
|
136
|
-
}
|
|
137
|
-
expect(router._serverResult.redirect.status).toBe(307);
|
|
138
|
-
expect(router._serverResult.redirect.headers.get('Location')).toBe(
|
|
139
|
-
'/target',
|
|
140
|
-
);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
test('renders Modern Outlet through TanStack native outlet', async () => {
|
|
144
|
-
const router = await loadRouteTree(
|
|
145
|
-
root(
|
|
146
|
-
[
|
|
147
|
-
{
|
|
148
|
-
id: 'plain',
|
|
149
|
-
path: 'plain',
|
|
150
|
-
Component: () => createElement('main', null, 'Plain child route'),
|
|
151
|
-
},
|
|
152
|
-
],
|
|
153
|
-
() => createElement('section', null, createElement(ModernOutlet)),
|
|
154
|
-
),
|
|
155
|
-
'/plain',
|
|
156
|
-
);
|
|
157
|
-
|
|
158
|
-
expect(
|
|
159
|
-
renderToString(createElement(RouterProvider, { router } as never)),
|
|
160
|
-
).toContain('Plain child route');
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
for (const [shape, wrap] of [
|
|
164
|
-
['flat', (Component: ComponentType) => ({ default: Component })],
|
|
165
|
-
[
|
|
166
|
-
'nested',
|
|
167
|
-
(Component: ComponentType) => ({ default: { default: Component } }),
|
|
168
|
-
],
|
|
169
|
-
] as const) {
|
|
170
|
-
test(`server renders lazy child routes from ${shape} ESM module defaults`, async () => {
|
|
171
|
-
const LazyRouteComponent = () =>
|
|
172
|
-
createElement('main', null, 'Lazy child route ready');
|
|
173
|
-
const lazyImport = () => Promise.resolve(wrap(LazyRouteComponent));
|
|
174
|
-
const router = await loadRouteTree(
|
|
175
|
-
root(
|
|
176
|
-
[
|
|
177
|
-
{
|
|
178
|
-
id: 'lazy',
|
|
179
|
-
path: 'lazy',
|
|
180
|
-
Component: lazy(
|
|
181
|
-
lazyImport as () => Promise<{ default: ComponentType }>,
|
|
182
|
-
),
|
|
183
|
-
lazyImport,
|
|
184
|
-
},
|
|
185
|
-
],
|
|
186
|
-
() => createElement('section', null, createElement(Outlet)),
|
|
187
|
-
),
|
|
188
|
-
'/lazy',
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
expect(
|
|
192
|
-
renderToStaticMarkup(
|
|
193
|
-
createElement(RouterProvider, { router } as never),
|
|
194
|
-
),
|
|
195
|
-
).toContain('Lazy child route ready');
|
|
196
|
-
});
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
test('normalizes Modern deferred loader data for TanStack SSR', async () => {
|
|
200
|
-
const router = await loadRouteTree(
|
|
201
|
-
root([
|
|
202
|
-
{
|
|
203
|
-
id: 'deferred',
|
|
204
|
-
path: 'deferred',
|
|
205
|
-
loader: () => ({
|
|
206
|
-
__modern_deferred: true,
|
|
207
|
-
data: { immediate: 'ok', later: Promise.resolve('done') },
|
|
208
|
-
}),
|
|
209
|
-
Component: nullComponent,
|
|
210
|
-
},
|
|
211
|
-
]),
|
|
212
|
-
'/deferred',
|
|
213
|
-
);
|
|
214
|
-
const loaderData = matchOf(router, '/deferred')?.loaderData as
|
|
215
|
-
| { immediate: string; later: Promise<string> }
|
|
216
|
-
| undefined;
|
|
217
|
-
|
|
218
|
-
expect(loaderData?.immediate).toBe('ok');
|
|
219
|
-
await expect(loaderData?.later).resolves.toBe('done');
|
|
220
|
-
});
|
|
221
|
-
|
|
222
|
-
test('preserves returned non-404 Response loaders as loader data', async () => {
|
|
223
|
-
const response = new Response('route status payload', { status: 500 });
|
|
224
|
-
const router = await loadRouteTree(
|
|
225
|
-
root([
|
|
226
|
-
{
|
|
227
|
-
id: 'broken',
|
|
228
|
-
path: 'broken',
|
|
229
|
-
loader: () => response,
|
|
230
|
-
Component: nullComponent,
|
|
231
|
-
},
|
|
232
|
-
]),
|
|
233
|
-
'/broken',
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
expect(router._serverResult).toMatchObject({ type: 'render', status: 200 });
|
|
237
|
-
expect(matchOf(router, '/broken')?.loaderData).toBe(response);
|
|
238
|
-
expect(matchOf(router, '/broken')?.error).toBeUndefined();
|
|
239
|
-
});
|
|
240
|
-
});
|