@storybook/tanstack-react 10.5.1 → 10.5.3
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/_browser-chunks/{chunk-WQHJSC2D.js → chunk-4W5ZAMIP.js} +45 -8
- package/dist/_browser-chunks/{chunk-XKNIZ3MM.js → chunk-LTDGLEVR.js} +9 -1
- package/dist/export-mocks/react-router.js +16 -12
- package/dist/export-mocks/start.d.ts +5 -1
- package/dist/export-mocks/start.js +3 -2
- package/dist/index.js +2 -2
- package/dist/node/index.js +6 -6
- package/dist/preset.js +16 -7
- package/dist/preview.js +2 -2
- package/package.json +6 -6
- package/template/stories/PathlessLayout.stories.tsx +150 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
normalizeFileRoutePath
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LTDGLEVR.js";
|
|
4
4
|
import {
|
|
5
5
|
__export
|
|
6
6
|
} from "./chunk-4BE7D4DS.js";
|
|
@@ -28,7 +28,12 @@ import {
|
|
|
28
28
|
} from "@tanstack/react-router";
|
|
29
29
|
|
|
30
30
|
// src/routing/duplicate-tree.ts
|
|
31
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
createRoute,
|
|
33
|
+
RootRoute,
|
|
34
|
+
createRootRouteWithContext,
|
|
35
|
+
joinPaths
|
|
36
|
+
} from "@tanstack/react-router";
|
|
32
37
|
var MAX_PARENT_WALK = 50;
|
|
33
38
|
function findRootRoute(route) {
|
|
34
39
|
let current = route;
|
|
@@ -50,9 +55,9 @@ function initSourceTree(route, counter) {
|
|
|
50
55
|
initSourceTree(child, counter);
|
|
51
56
|
}
|
|
52
57
|
function cloneChild(oldRoute, parent, overrides, byId) {
|
|
53
|
-
let options = oldRoute.options ?? {}, { id:
|
|
54
|
-
|
|
55
|
-
...
|
|
58
|
+
let options = oldRoute.options ?? {}, { id: originalId, getParentRoute: _g, ...rest } = options, override = getOverrideFor(overrides, oldRoute.id), merged = { ...rest, ...override }, cloned = createRoute({
|
|
59
|
+
...!merged.path && originalId != null ? { id: originalId } : {},
|
|
60
|
+
...merged,
|
|
56
61
|
getParentRoute: () => parent
|
|
57
62
|
});
|
|
58
63
|
byId.set(oldRoute.id, cloned);
|
|
@@ -81,6 +86,16 @@ function duplicateRouteTree(rootRoute, { overrides } = {}) {
|
|
|
81
86
|
}
|
|
82
87
|
return { root: newRoot, byId };
|
|
83
88
|
}
|
|
89
|
+
function mountPathFor(route) {
|
|
90
|
+
let segments = [], current = route;
|
|
91
|
+
for (let i = 0; i < MAX_PARENT_WALK && current; i += 1) {
|
|
92
|
+
let routePath = current.options?.path;
|
|
93
|
+
routePath && routePath !== "/" && segments.unshift(routePath);
|
|
94
|
+
let getParent = current.options?.getParentRoute;
|
|
95
|
+
current = typeof getParent == "function" ? getParent() : void 0;
|
|
96
|
+
}
|
|
97
|
+
return joinPaths(["/", ...segments]);
|
|
98
|
+
}
|
|
84
99
|
function resolveStoryLeaf(tree, { path, boundRouteId }) {
|
|
85
100
|
let { root, byId } = tree;
|
|
86
101
|
if (path) {
|
|
@@ -134,7 +149,7 @@ function createStoryRouter({
|
|
|
134
149
|
context,
|
|
135
150
|
routerContext
|
|
136
151
|
}) {
|
|
137
|
-
let routerParameters = context.parameters.tanstack?.router ?? {}, { tree, leaf } = resolveTree(Story, context), routeTree = tree.root, inferredPath = routerParameters?.path || leaf.fullPath || (leaf.id ? normalizeFileRoutePath(leaf.id) : void 0) ||
|
|
152
|
+
let routerParameters = context.parameters.tanstack?.router ?? {}, { tree, leaf } = resolveTree(Story, context), routeTree = tree.root, inferredPath = routerParameters?.path || leaf.fullPath || (leaf.id ? normalizeFileRoutePath(leaf.id) : void 0) || mountPathFor(leaf), resolvedPath = interpolatePath({
|
|
138
153
|
path: inferredPath,
|
|
139
154
|
params: routerParameters?.params ?? {}
|
|
140
155
|
}).interpolatedPath, search = routerParameters?.query ? defaultStringifySearch(routerParameters.query) : "";
|
|
@@ -154,6 +169,24 @@ function createStoryRouter({
|
|
|
154
169
|
context: routerContext
|
|
155
170
|
});
|
|
156
171
|
}
|
|
172
|
+
function ensureMatchableLeaf(tree, leaf) {
|
|
173
|
+
if (!(leaf !== tree.root && !leaf.options?.path && leaf.options?.id != null))
|
|
174
|
+
return leaf;
|
|
175
|
+
let existingIndexChild = (leaf.children ?? []).find(
|
|
176
|
+
(child) => child.options?.path === "/"
|
|
177
|
+
);
|
|
178
|
+
if (existingIndexChild)
|
|
179
|
+
return existingIndexChild;
|
|
180
|
+
let syntheticLeaf = createRoute2({
|
|
181
|
+
path: "/",
|
|
182
|
+
component: () => null,
|
|
183
|
+
getParentRoute: () => leaf
|
|
184
|
+
});
|
|
185
|
+
return leaf.addChildren([
|
|
186
|
+
...leaf.children ?? [],
|
|
187
|
+
syntheticLeaf
|
|
188
|
+
]), syntheticLeaf;
|
|
189
|
+
}
|
|
157
190
|
function injectStoryComponent(leaf, Story, overrides, leafId) {
|
|
158
191
|
let userOverride = overrides?.[leafId];
|
|
159
192
|
userOverride && "component" in userOverride && userOverride.component !== void 0 || leaf.update({ component: () => React.createElement(Story, null) });
|
|
@@ -165,7 +198,9 @@ function resolveTree(Story, context) {
|
|
|
165
198
|
path: routerParameters.path,
|
|
166
199
|
boundRouteId: resolvedRoute && resolvedRoute !== rootRoute ? resolvedRoute.id : void 0
|
|
167
200
|
});
|
|
168
|
-
|
|
201
|
+
injectStoryComponent(leaf, Story, routeOverrides, leaf.id);
|
|
202
|
+
let renderLeaf = ensureMatchableLeaf(tree, leaf);
|
|
203
|
+
return { tree, leaf: renderLeaf };
|
|
169
204
|
}
|
|
170
205
|
if (isRoute(routerParameterRoute)) {
|
|
171
206
|
let syntheticRoot2 = createRootRoute(
|
|
@@ -173,7 +208,9 @@ function resolveTree(Story, context) {
|
|
|
173
208
|
);
|
|
174
209
|
routerParameterRoute.update({ getParentRoute: () => syntheticRoot2 }), syntheticRoot2.addChildren([routerParameterRoute]);
|
|
175
210
|
let tree = duplicateRouteTree(syntheticRoot2, { overrides: routeOverrides }), leaf = tree.byId.get(routerParameterRoute.id) ?? tree.root;
|
|
176
|
-
|
|
211
|
+
injectStoryComponent(leaf, Story, routeOverrides, leaf.id);
|
|
212
|
+
let renderLeaf = ensureMatchableLeaf(tree, leaf);
|
|
213
|
+
return { tree, leaf: renderLeaf };
|
|
177
214
|
}
|
|
178
215
|
let plainOptions = routerParameterRoute ?? {}, {
|
|
179
216
|
path: plainRoutePath,
|
|
@@ -13,7 +13,15 @@ function removeUnderscores(s) {
|
|
|
13
13
|
function normalizeFileRoutePath(path) {
|
|
14
14
|
return removeGroups(removeUnderscores(removeLayoutSegments(path)) ?? "") || "/";
|
|
15
15
|
}
|
|
16
|
+
function isPathlessSegment(segment) {
|
|
17
|
+
return segment.startsWith("_") || segment.startsWith("(") && segment.endsWith(")");
|
|
18
|
+
}
|
|
19
|
+
function isPathlessFileRouteId(id) {
|
|
20
|
+
let segments = id.split("/").filter(Boolean), lastSegment = segments[segments.length - 1];
|
|
21
|
+
return lastSegment != null && isPathlessSegment(lastSegment);
|
|
22
|
+
}
|
|
16
23
|
|
|
17
24
|
export {
|
|
18
|
-
normalizeFileRoutePath
|
|
25
|
+
normalizeFileRoutePath,
|
|
26
|
+
isPathlessFileRouteId
|
|
19
27
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
|
+
isPathlessFileRouteId,
|
|
2
3
|
normalizeFileRoutePath
|
|
3
|
-
} from "../_browser-chunks/chunk-
|
|
4
|
+
} from "../_browser-chunks/chunk-LTDGLEVR.js";
|
|
4
5
|
import {
|
|
5
6
|
onNavigate
|
|
6
7
|
} from "../_browser-chunks/chunk-4CKCBIXI.js";
|
|
@@ -51,17 +52,20 @@ var useNavigate = fn(_useNavigate).mockName("@tanstack/react-router::useNavigate
|
|
|
51
52
|
);
|
|
52
53
|
};
|
|
53
54
|
function createFileRoute(path) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
55
|
+
return (options) => {
|
|
56
|
+
let pathless = isPathlessFileRouteId(path) && options?.path == null, routePath = options?.path ?? normalizeFileRoutePath(path);
|
|
57
|
+
return createRoute({
|
|
58
|
+
...pathless ? { id: path } : { path: routePath },
|
|
59
|
+
...options,
|
|
60
|
+
isRoot: !1
|
|
61
|
+
}).update({
|
|
62
|
+
// routeTree.gen re-updates these later; set them here so route files
|
|
63
|
+
// imported without the generated tree still carry their identity
|
|
64
|
+
id: path,
|
|
65
|
+
...pathless ? {} : { path: routePath, fullPath: routePath }
|
|
66
|
+
// any because tanstack router does that
|
|
67
|
+
});
|
|
68
|
+
};
|
|
65
69
|
}
|
|
66
70
|
export {
|
|
67
71
|
Link,
|
|
@@ -108,6 +108,10 @@ declare const Link: ({ to, children, ...props }: {
|
|
|
108
108
|
declare const Navigate: ({ to }: {
|
|
109
109
|
to: string;
|
|
110
110
|
}) => null;
|
|
111
|
+
declare const Hydrate: ({ children }: {
|
|
112
|
+
children?: React.ReactNode;
|
|
113
|
+
[key: string]: unknown;
|
|
114
|
+
}) => string | number | boolean | React.ReactElement<any, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null;
|
|
111
115
|
declare const notFound: () => never;
|
|
112
116
|
declare const createStart: () => {};
|
|
113
117
|
declare const clearCookieStore: () => void;
|
|
@@ -117,4 +121,4 @@ declare const _default: {
|
|
|
117
121
|
fetch: () => Promise<Response>;
|
|
118
122
|
};
|
|
119
123
|
|
|
120
|
-
export { HEADERS, Link, Navigate, StartServer, VIRTUAL_MODULES, attachRouterServerSsrUtils, clearCookieStore, clearResponseHeaders, clearSession, createRequestHandler, createServerFn, createStart, createStartHandler, _default as default, defaultRenderHandler, defaultStreamHandler, defineHandlerCallback, deleteCookie, fetchHandler as fetch, getCookie, getCookies, getRequest, getRequestHeader, getRequestHeaders, getRequestHost, getRequestIP, getRequestProtocol, getRequestUrl, getResponse, getResponseHeader, getResponseHeaders, getResponseStatus, getSession, getValidatedQuery, notFound, removeResponseHeader, requestHandler, sealSession, setCookie, setResponseHeader, setResponseHeaders, setResponseStatus, transformPipeableStreamWithRouter, transformReadableStreamWithRouter, unsealSession, updateSession, useServerFn, useSession };
|
|
124
|
+
export { HEADERS, Hydrate, Link, Navigate, StartServer, VIRTUAL_MODULES, attachRouterServerSsrUtils, clearCookieStore, clearResponseHeaders, clearSession, createRequestHandler, createServerFn, createStart, createStartHandler, _default as default, defaultRenderHandler, defaultStreamHandler, defineHandlerCallback, deleteCookie, fetchHandler as fetch, getCookie, getCookies, getRequest, getRequestHeader, getRequestHeaders, getRequestHost, getRequestIP, getRequestProtocol, getRequestUrl, getResponse, getResponseHeader, getResponseHeaders, getResponseStatus, getSession, getValidatedQuery, notFound, removeResponseHeader, requestHandler, sealSession, setCookie, setResponseHeader, setResponseHeaders, setResponseStatus, transformPipeableStreamWithRouter, transformReadableStreamWithRouter, unsealSession, updateSession, useServerFn, useSession };
|
|
@@ -262,7 +262,7 @@ function useServerFn(serverFn) {
|
|
|
262
262
|
}
|
|
263
263
|
function createMockServerFnBuilder() {
|
|
264
264
|
let builder = () => createMockServerFnBuilder(), _storedOptions;
|
|
265
|
-
return builder.options = (opts) => (_storedOptions = opts, builder), builder.middleware = () => createMockServerFnBuilder(), builder.inputValidator = () => createMockServerFnBuilder(), builder.handler = (handlerFn) => {
|
|
265
|
+
return builder.options = (opts) => (_storedOptions = opts, builder), builder.middleware = () => createMockServerFnBuilder(), builder.inputValidator = () => createMockServerFnBuilder(), builder.validator = () => createMockServerFnBuilder(), builder.handler = (handlerFn) => {
|
|
266
266
|
let mock = fn().mockName("@tanstack/start-client-core::createServerFn.handler()");
|
|
267
267
|
return handlerFn && mock.mockImplementation(async (opts) => handlerFn(opts)), mock;
|
|
268
268
|
}, builder._getOptions = () => _storedOptions, builder;
|
|
@@ -276,7 +276,7 @@ var createServerFn = (options) => {
|
|
|
276
276
|
...props
|
|
277
277
|
}) => React.createElement("a", { href: to, ...props }, children), Navigate = ({ to }) => (React.useEffect(() => {
|
|
278
278
|
onNavigate({ to });
|
|
279
|
-
}, [to]), null), notFound = () => {
|
|
279
|
+
}, [to]), null), Hydrate = ({ children }) => children ?? null, notFound = () => {
|
|
280
280
|
throw new Error("Not found");
|
|
281
281
|
}, createStart = () => ({}), clientCookieStore = /* @__PURE__ */ new Map(), clearCookieStore = () => {
|
|
282
282
|
clientCookieStore.clear();
|
|
@@ -284,6 +284,7 @@ var createServerFn = (options) => {
|
|
|
284
284
|
var start_default = { fetch: fetchHandler };
|
|
285
285
|
export {
|
|
286
286
|
HEADERS,
|
|
287
|
+
Hydrate,
|
|
287
288
|
Link,
|
|
288
289
|
Navigate,
|
|
289
290
|
StartServer,
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
preview_exports
|
|
3
|
-
} from "./_browser-chunks/chunk-
|
|
4
|
-
import "./_browser-chunks/chunk-
|
|
3
|
+
} from "./_browser-chunks/chunk-4W5ZAMIP.js";
|
|
4
|
+
import "./_browser-chunks/chunk-LTDGLEVR.js";
|
|
5
5
|
import "./_browser-chunks/chunk-4BE7D4DS.js";
|
|
6
6
|
|
|
7
7
|
// src/index.ts
|
package/dist/node/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import CJS_COMPAT_NODE_URL_j9pjcgat41 from 'node:url';
|
|
2
|
+
import CJS_COMPAT_NODE_PATH_j9pjcgat41 from 'node:path';
|
|
3
|
+
import CJS_COMPAT_NODE_MODULE_j9pjcgat41 from "node:module";
|
|
4
4
|
|
|
5
|
-
var __filename =
|
|
6
|
-
var __dirname =
|
|
7
|
-
var require =
|
|
5
|
+
var __filename = CJS_COMPAT_NODE_URL_j9pjcgat41.fileURLToPath(import.meta.url);
|
|
6
|
+
var __dirname = CJS_COMPAT_NODE_PATH_j9pjcgat41.dirname(__filename);
|
|
7
|
+
var require = CJS_COMPAT_NODE_MODULE_j9pjcgat41.createRequire(import.meta.url);
|
|
8
8
|
|
|
9
9
|
// ------------------------------------------------------------
|
|
10
10
|
// end of CJS compatibility banner, injected by Storybook's esbuild configuration
|
package/dist/preset.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
1
|
+
import CJS_COMPAT_NODE_URL_j9pjcgat41 from 'node:url';
|
|
2
|
+
import CJS_COMPAT_NODE_PATH_j9pjcgat41 from 'node:path';
|
|
3
|
+
import CJS_COMPAT_NODE_MODULE_j9pjcgat41 from "node:module";
|
|
4
4
|
|
|
5
|
-
var __filename =
|
|
6
|
-
var __dirname =
|
|
7
|
-
var require =
|
|
5
|
+
var __filename = CJS_COMPAT_NODE_URL_j9pjcgat41.fileURLToPath(import.meta.url);
|
|
6
|
+
var __dirname = CJS_COMPAT_NODE_PATH_j9pjcgat41.dirname(__filename);
|
|
7
|
+
var require = CJS_COMPAT_NODE_MODULE_j9pjcgat41.createRequire(import.meta.url);
|
|
8
8
|
|
|
9
9
|
// ------------------------------------------------------------
|
|
10
10
|
// end of CJS compatibility banner, injected by Storybook's esbuild configuration
|
|
@@ -31,7 +31,7 @@ var dirname = function(p) {
|
|
|
31
31
|
import { viteFinal as reactViteFinal } from "@storybook/react-vite/preset";
|
|
32
32
|
|
|
33
33
|
// src/plugins/server-code-elimination.ts
|
|
34
|
-
import {
|
|
34
|
+
import { types as t, transformSync } from "storybook/internal/babel";
|
|
35
35
|
var SERVER_FN_RE = /\bcreateServerFn\b/, MIDDLEWARE_RE = /\bcreateMiddleware\b/, ISOMORPHIC_FN_RE = /\bcreateIsomorphicFn\b/, SERVER_ONLY_FN_RE = /\bcreateServerOnlyFn\b/, CLIENT_ONLY_FN_RE = /\bcreateClientOnlyFn\b/, ROUTE_FACTORY_RE = /\b(createFileRoute|createRootRoute|createRootRouteWithContext|createRoute)\b/, ROUTE_FACTORIES = /* @__PURE__ */ new Set([
|
|
36
36
|
"createFileRoute",
|
|
37
37
|
"createRootRoute",
|
|
@@ -215,12 +215,21 @@ function stripServerOption(options) {
|
|
|
215
215
|
return (t.isIdentifier(key) ? key.name : void 0) !== "server";
|
|
216
216
|
}), options.properties.length !== initialLength;
|
|
217
217
|
}
|
|
218
|
+
function getJsxRootIdentifier(name) {
|
|
219
|
+
for (; t.isJSXMemberExpression(name); )
|
|
220
|
+
name = name.object;
|
|
221
|
+
return t.isJSXIdentifier(name) ? name : null;
|
|
222
|
+
}
|
|
218
223
|
function collectReferencedIdentifiers(programPath) {
|
|
219
224
|
let referenced = /* @__PURE__ */ new Set();
|
|
220
225
|
return programPath.traverse({
|
|
221
226
|
enter(path2) {
|
|
222
227
|
let { node } = path2;
|
|
223
228
|
!t.isIdentifier(node) || path2.isBindingIdentifier() || path2.findParent((p) => p.isImportDeclaration()) || referenced.add(node.name);
|
|
229
|
+
},
|
|
230
|
+
JSXOpeningElement(path2) {
|
|
231
|
+
let root = getJsxRootIdentifier(path2.node.name);
|
|
232
|
+
root && referenced.add(root.name);
|
|
224
233
|
}
|
|
225
234
|
}), referenced;
|
|
226
235
|
}
|
package/dist/preview.js
CHANGED
|
@@ -3,8 +3,8 @@ import {
|
|
|
3
3
|
loaders,
|
|
4
4
|
optimizeDeps,
|
|
5
5
|
parameters
|
|
6
|
-
} from "./_browser-chunks/chunk-
|
|
7
|
-
import "./_browser-chunks/chunk-
|
|
6
|
+
} from "./_browser-chunks/chunk-4W5ZAMIP.js";
|
|
7
|
+
import "./_browser-chunks/chunk-LTDGLEVR.js";
|
|
8
8
|
import "./_browser-chunks/chunk-4BE7D4DS.js";
|
|
9
9
|
export {
|
|
10
10
|
applyDecorators,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@storybook/tanstack-react",
|
|
3
|
-
"version": "10.5.
|
|
3
|
+
"version": "10.5.3",
|
|
4
4
|
"description": "Storybook for TanStack (React, Vite): Router and Start ready Storybook framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"storybook",
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
"!src/**/*"
|
|
76
76
|
],
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@storybook/builder-vite": "10.5.
|
|
79
|
-
"@storybook/react": "10.5.
|
|
80
|
-
"@storybook/react-vite": "10.5.
|
|
78
|
+
"@storybook/builder-vite": "10.5.3",
|
|
79
|
+
"@storybook/react": "10.5.3",
|
|
80
|
+
"@storybook/react-vite": "10.5.3"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@tanstack/react-router": "^1.168.10",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"@tanstack/router-core": "^1.168.9",
|
|
86
86
|
"@tanstack/start-client-core": "^1.167.9",
|
|
87
87
|
"@types/node": "^22.19.1",
|
|
88
|
-
"typescript": "^
|
|
88
|
+
"typescript": "^6.0.3",
|
|
89
89
|
"vite": "^7.0.4"
|
|
90
90
|
},
|
|
91
91
|
"peerDependencies": {
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
"@tanstack/start-client-core": "^1.167.9",
|
|
96
96
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
97
97
|
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
|
|
98
|
-
"storybook": "^10.5.
|
|
98
|
+
"storybook": "^10.5.3",
|
|
99
99
|
"vite": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0"
|
|
100
100
|
},
|
|
101
101
|
"peerDependenciesMeta": {
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
import type { Meta, StoryObj } from '@storybook/tanstack-react';
|
|
4
|
+
|
|
5
|
+
import { Outlet, createRootRoute, createRoute } from '@tanstack/react-router';
|
|
6
|
+
import { expect, fn, within } from 'storybook/test';
|
|
7
|
+
|
|
8
|
+
// Regression coverage for pathless layout routes (`_authed`-style underscore
|
|
9
|
+
// files and `(group)` directories). The layout must render and its
|
|
10
|
+
// `beforeLoad` must run, while contributing nothing to the URL: the story
|
|
11
|
+
// mounts at `/dashboard`, not `/_authed/dashboard`.
|
|
12
|
+
|
|
13
|
+
function LeafContent() {
|
|
14
|
+
return <p data-testid="pathless-leaf">leaf rendered under a pathless layout</p>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const onBeforeLoad = fn();
|
|
18
|
+
|
|
19
|
+
const RootRoute = createRootRoute({
|
|
20
|
+
component: () => (
|
|
21
|
+
<div data-testid="pathless-root">
|
|
22
|
+
<h1>root layout</h1>
|
|
23
|
+
<Outlet />
|
|
24
|
+
</div>
|
|
25
|
+
),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const PathlessLayout = createRoute({
|
|
29
|
+
id: 'authed',
|
|
30
|
+
getParentRoute: () => RootRoute,
|
|
31
|
+
beforeLoad: onBeforeLoad,
|
|
32
|
+
component: () => (
|
|
33
|
+
<section data-testid="pathless-layout">
|
|
34
|
+
<Outlet />
|
|
35
|
+
</section>
|
|
36
|
+
),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
const DashboardRoute = createRoute({
|
|
40
|
+
path: '/dashboard',
|
|
41
|
+
getParentRoute: () => PathlessLayout,
|
|
42
|
+
component: LeafContent,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
PathlessLayout.addChildren([DashboardRoute]);
|
|
46
|
+
|
|
47
|
+
// Regression coverage for a pathless layout nested under a pathful ancestor
|
|
48
|
+
// (e.g. `/products/_authed`): the layout's mount URL must be inferred from
|
|
49
|
+
// its pathful ancestor, not just '/'.
|
|
50
|
+
|
|
51
|
+
const onNestedBeforeLoad = fn();
|
|
52
|
+
|
|
53
|
+
function ProductsLayout() {
|
|
54
|
+
return (
|
|
55
|
+
<div data-testid="products-layout">
|
|
56
|
+
<Outlet />
|
|
57
|
+
</div>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const ProductsRoute = createRoute({
|
|
62
|
+
path: '/products',
|
|
63
|
+
getParentRoute: () => RootRoute,
|
|
64
|
+
component: ProductsLayout,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const NestedPathlessLayout = createRoute({
|
|
68
|
+
id: 'nested-authed',
|
|
69
|
+
getParentRoute: () => ProductsRoute,
|
|
70
|
+
beforeLoad: onNestedBeforeLoad,
|
|
71
|
+
component: () => (
|
|
72
|
+
<section data-testid="nested-pathless-layout">
|
|
73
|
+
<Outlet />
|
|
74
|
+
</section>
|
|
75
|
+
),
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const NestedSettingsRoute = createRoute({
|
|
79
|
+
path: '/settings',
|
|
80
|
+
getParentRoute: () => NestedPathlessLayout,
|
|
81
|
+
component: LeafContent,
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
NestedPathlessLayout.addChildren([NestedSettingsRoute]);
|
|
85
|
+
ProductsRoute.addChildren([NestedPathlessLayout]);
|
|
86
|
+
|
|
87
|
+
// A single `addChildren` call per parent: it replaces (not appends to) the
|
|
88
|
+
// children array, so `RootRoute`'s two top-level branches must be registered
|
|
89
|
+
// together.
|
|
90
|
+
RootRoute.addChildren([PathlessLayout, ProductsRoute]);
|
|
91
|
+
|
|
92
|
+
const meta = {
|
|
93
|
+
component: LeafContent,
|
|
94
|
+
parameters: { layout: 'fullscreen' },
|
|
95
|
+
} satisfies Meta<typeof LeafContent>;
|
|
96
|
+
|
|
97
|
+
export default meta;
|
|
98
|
+
|
|
99
|
+
type Story = StoryObj<typeof meta>;
|
|
100
|
+
|
|
101
|
+
/** The leaf mounts at the layout-free URL, with every ancestor layout rendered. */
|
|
102
|
+
export const RendersLeafThroughPathlessLayout: Story = {
|
|
103
|
+
parameters: {
|
|
104
|
+
tanstack: { router: { route: DashboardRoute, path: '/dashboard' } },
|
|
105
|
+
},
|
|
106
|
+
play: async ({ canvasElement }) => {
|
|
107
|
+
const canvas = within(canvasElement);
|
|
108
|
+
await expect(canvas.getByTestId('pathless-root')).toBeInTheDocument();
|
|
109
|
+
await expect(canvas.getByTestId('pathless-layout')).toBeInTheDocument();
|
|
110
|
+
await expect(canvas.getByTestId('pathless-leaf')).toBeInTheDocument();
|
|
111
|
+
await expect(onBeforeLoad).toHaveBeenCalled();
|
|
112
|
+
},
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
/** Binding the layout route itself (no path) mounts the story at the layout's position. */
|
|
116
|
+
export const BoundDirectlyToPathlessLayout: Story = {
|
|
117
|
+
parameters: {
|
|
118
|
+
tanstack: { router: { route: PathlessLayout } },
|
|
119
|
+
},
|
|
120
|
+
play: async ({ canvasElement }) => {
|
|
121
|
+
const canvas = within(canvasElement);
|
|
122
|
+
// injectStoryComponent replaces the layout's own `component` with the
|
|
123
|
+
// story's render (LeafContent), so `pathless-layout` itself is gone —
|
|
124
|
+
// assert the root layout plus the leaf content that now renders in its place.
|
|
125
|
+
await expect(canvas.getByTestId('pathless-root')).toBeInTheDocument();
|
|
126
|
+
await expect(canvas.getByTestId('pathless-leaf')).toBeInTheDocument();
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* A pathless layout nested under a pathful ancestor (`/products`). Binding
|
|
132
|
+
* the story directly to the nested layout must mount it at `/products`
|
|
133
|
+
* (inferred from the ancestor's path), with the root and the `/products`
|
|
134
|
+
* ancestor both rendered around the story's own content.
|
|
135
|
+
*/
|
|
136
|
+
export const BoundDirectlyToNestedPathlessLayout: Story = {
|
|
137
|
+
parameters: {
|
|
138
|
+
tanstack: { router: { route: NestedPathlessLayout } },
|
|
139
|
+
},
|
|
140
|
+
play: async ({ canvasElement }) => {
|
|
141
|
+
const canvas = within(canvasElement);
|
|
142
|
+
await expect(canvas.getByTestId('pathless-root')).toBeInTheDocument();
|
|
143
|
+
await expect(canvas.getByTestId('products-layout')).toBeInTheDocument();
|
|
144
|
+
// injectStoryComponent replaces the nested layout's own `component`, so
|
|
145
|
+
// `nested-pathless-layout` itself is gone — assert the leaf content that
|
|
146
|
+
// now renders in its place instead.
|
|
147
|
+
await expect(canvas.getByTestId('pathless-leaf')).toBeInTheDocument();
|
|
148
|
+
await expect(onNestedBeforeLoad).toHaveBeenCalled();
|
|
149
|
+
},
|
|
150
|
+
};
|