diff --git a/lib/api/src/modules/stories.ts b/lib/api/src/modules/stories.ts
index de9ecf450690..e8c98d175f3c 100644
--- a/lib/api/src/modules/stories.ts
+++ b/lib/api/src/modules/stories.ts
@@ -1,4 +1,5 @@
-import { toId, sanitize } from '@storybook/router/dist/utils';
+// FIXME: we shouldn't import from dist but there are no types otherwise
+import { toId, sanitize, parseKind } from '@storybook/router';
import { Module } from '../index';
import merge from '../lib/merge';
@@ -27,11 +28,6 @@ export interface SubAPI {
getParameters: (storyId: StoryId, parameterName?: ParameterName) => Story['parameters'] | any;
}
-interface SeparatorOptions {
- rootSeparator: string | RegExp;
- groupSeparator: string | RegExp;
-}
-
interface Group {
id: StoryId;
name: string;
@@ -164,17 +160,6 @@ const initStoriesApi = ({
navigate(`/${viewMode || 'story'}/${result}`);
};
- const splitPath = (kind: string, { rootSeparator, groupSeparator }: SeparatorOptions) => {
- const [root, remainder] = kind.split(rootSeparator, 2);
- const groups = (remainder || kind).split(groupSeparator).filter(i => !!i);
-
- // when there's no remainder, it means the root wasn't found/split
- return {
- root: remainder ? root : null,
- groups,
- };
- };
-
const toKey = (input: string) =>
input.replace(/[^a-z0-9]+([a-z0-9])/gi, (...params) => params[1].toUpperCase());
@@ -192,11 +177,11 @@ const initStoriesApi = ({
hierarchyRootSeparator: rootSeparator,
hierarchySeparator: groupSeparator,
} = (parameters && parameters.options) || {
- hierarchyRootSeparator: '/',
+ hierarchyRootSeparator: '|',
hierarchySeparator: '/',
};
- const { root, groups } = splitPath(kind, { rootSeparator, groupSeparator });
+ const { root, groups } = parseKind(kind, { rootSeparator, groupSeparator });
const rootAndGroups = []
.concat(root || [])
@@ -208,6 +193,15 @@ const initStoriesApi = ({
const { name } = group;
const parent = index > 0 && soFar[index - 1].id;
const id = sanitize(parent ? `${parent}-${name}` : name);
+ if (parent === id) {
+ throw new Error(
+ `
+Invalid part '${name}', leading to id === parentId ('${id}'), inside kind '${kind}'
+
+Did you create a path that uses the separator char accidentally, such as 'Vue ' where '/' is a separator char? See https://github.com/storybooks/storybook/issues/6128
+ `.trim()
+ );
+ }
const result: Group = {
...group,
diff --git a/lib/client-api/src/index.js b/lib/client-api/src/index.js
index 357f98112b73..23934980e126 100644
--- a/lib/client-api/src/index.js
+++ b/lib/client-api/src/index.js
@@ -1,5 +1,5 @@
import ClientApi, { defaultDecorateStory } from './client_api';
-import StoryStore, { splitPath } from './story_store';
+import StoryStore from './story_store';
import ConfigApi from './config_api';
import subscriptionsStore from './subscriptions_store';
import pathToId from './pathToId';
@@ -13,7 +13,6 @@ export {
subscriptionsStore,
defaultDecorateStory,
pathToId,
- splitPath,
getQueryParams,
getQueryParam,
};
diff --git a/lib/client-api/src/story_store.js b/lib/client-api/src/story_store.js
index cdc6a6d9c337..a72e696a1782 100644
--- a/lib/client-api/src/story_store.js
+++ b/lib/client-api/src/story_store.js
@@ -21,17 +21,6 @@ const toKey = input =>
const toChild = it => ({ ...it });
-export const splitPath = (path, { rootSeparator, groupSeparator }) => {
- const [root, remainder] = path.split(rootSeparator, 2);
- const groups = (remainder || path).split(groupSeparator).filter(i => !!i);
-
- // when there's no remainder, it means the root wasn't found/split
- return {
- root: remainder ? root : null,
- groups,
- };
-};
-
let count = 0;
function getId() {
diff --git a/lib/router/src/router.tsx b/lib/router/src/router.tsx
index 7a1bdcdb0187..be408369c682 100644
--- a/lib/router/src/router.tsx
+++ b/lib/router/src/router.tsx
@@ -3,7 +3,7 @@ import React from 'react';
import { Link, Location, navigate, LocationProvider, RouteComponentProps } from '@reach/router';
import { ToggleVisibility } from './visibility';
-import { queryFromString, storyDataFromString, getMatch } from './utils';
+import { queryFromString, parsePath, getMatch } from './utils';
interface Other {
viewMode?: string;
@@ -56,7 +56,7 @@ const QueryLocation = ({ children }: QueryLocationProps) => (
{({ location }: RouteComponentProps): React.ReactNode => {
const { path } = queryFromString(location.search);
- const { viewMode, storyId } = storyDataFromString(path);
+ const { viewMode, storyId } = parsePath(path);
return children({ path, location, navigate: queryNavigate, viewMode, storyId });
}}
diff --git a/lib/router/src/utils.ts b/lib/router/src/utils.ts
index 6b2056a9d432..5a5a3f9fe074 100644
--- a/lib/router/src/utils.ts
+++ b/lib/router/src/utils.ts
@@ -6,8 +6,13 @@ interface StoryData {
storyId?: string;
}
+interface SeparatorOptions {
+ rootSeparator: string | RegExp;
+ groupSeparator: string | RegExp;
+}
+
export const knownNonViewModesRegex = /(settings)/;
-const splitPath = /\/([^/]+)\/([^/]+)?/;
+const splitPathRegex = /\/([^/]+)\/([^/]+)?/;
// Remove punctuation https://gist.github.com/davidjrice/9d2af51100e41c6c4b4a
export const sanitize = (string: string) => {
@@ -30,7 +35,7 @@ const sanitizeSafe = (string: string, part: string) => {
export const toId = (kind: string, name: string) =>
`${sanitizeSafe(kind, 'kind')}--${sanitizeSafe(name, 'name')}`;
-export const storyDataFromString: (path?: string) => StoryData = memoize(1000)(
+export const parsePath: (path?: string) => StoryData = memoize(1000)(
(path: string | undefined | null) => {
const result: StoryData = {
viewMode: undefined,
@@ -38,7 +43,7 @@ export const storyDataFromString: (path?: string) => StoryData = memoize(1000)(
};
if (path) {
- const [, viewMode, storyId] = path.match(splitPath) || [undefined, undefined, undefined];
+ const [, viewMode, storyId] = path.match(splitPathRegex) || [undefined, undefined, undefined];
if (viewMode && !viewMode.match(knownNonViewModesRegex)) {
Object.assign(result, {
viewMode,
@@ -73,3 +78,14 @@ export const getMatch = memoize(1000)(
return null;
}
);
+
+export const parseKind = (kind: string, { rootSeparator, groupSeparator }: SeparatorOptions) => {
+ const [root, remainder] = kind.split(rootSeparator, 2);
+ const groups = (remainder || kind).split(groupSeparator).filter(i => !!i);
+
+ // when there's no remainder, it means the root wasn't found/split
+ return {
+ root: remainder ? root : null,
+ groups,
+ };
+};