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, + }; +};