From a6c2a85183e3701c8f73003b0df4d62c540ce771 Mon Sep 17 00:00:00 2001 From: Michael Shilman Date: Mon, 20 Jan 2020 12:34:18 +0800 Subject: [PATCH] Core: Fix legacy story URLs --- lib/client-api/src/story_store.ts | 16 ++++++++++++---- lib/core/src/client/preview/start.js | 2 +- lib/core/src/client/preview/url.js | 23 +++++++++++++++++++---- lib/core/src/client/preview/url.test.js | 23 +++++++++++++++++------ 4 files changed, 49 insertions(+), 15 deletions(-) diff --git a/lib/client-api/src/story_store.ts b/lib/client-api/src/story_store.ts index 70400e1d2b66..684c8570c836 100644 --- a/lib/client-api/src/story_store.ts +++ b/lib/client-api/src/story_store.ts @@ -280,6 +280,18 @@ export default class StoryStore extends EventEmitter { } }, 0); + // Unlike a bunch of deprecated APIs below, these lookup functions + // use the `_data` member, which is the new data structure. They should + // be the preferred way of looking up stories in the future. + + getStoriesForKind(kind: string) { + return this.raw().filter(story => story.kind === kind); + } + + getRawStory(kind: string, name: string) { + return this.getStoriesForKind(kind).find(s => s.name === name); + } + // OLD apis getRevision() { return this._revision; @@ -339,10 +351,6 @@ export default class StoryStore extends EventEmitter { .map(info => info.name); } - getStoriesForKind(kind: string) { - return this.raw().filter(story => story.kind === kind); - } - getStoryFileName(kind: string) { const key = toKey(kind); const storiesKind = this._legacydata[key as string]; diff --git a/lib/core/src/client/preview/start.js b/lib/core/src/client/preview/start.js index a12fdf661704..c765bf171dd2 100644 --- a/lib/core/src/client/preview/start.js +++ b/lib/core/src/client/preview/start.js @@ -311,7 +311,7 @@ export default function start(render, { decorateStory } = {}) { } storyStore.on(Events.STORY_INIT, () => { - const { storyId, viewMode } = initializePath(); + const { storyId, viewMode } = initializePath(storyStore); storyStore.setSelection({ storyId, viewMode }); }); diff --git a/lib/core/src/client/preview/url.js b/lib/core/src/client/preview/url.js index 93f81c42678a..680e664555ce 100644 --- a/lib/core/src/client/preview/url.js +++ b/lib/core/src/client/preview/url.js @@ -22,19 +22,34 @@ export const setPath = ({ storyId, viewMode }) => { history.replaceState({}, '', newPath); }; -export const getIdFromLegacyQuery = ({ path, selectedKind, selectedStory }) => - (path && pathToId(path)) || (selectedKind && selectedStory && toId(selectedKind, selectedStory)); +export const getIdFromLegacyQuery = ({ path, selectedKind, selectedStory }, storyStore) => { + if (path) { + return pathToId(path); + } + if (selectedKind && selectedStory) { + // Look up the story ID inside the story store, since as of 5.3, the + // Story ID is not necessarily a direct function of its kind/name. + const story = storyStore.getRawStory(selectedKind, selectedStory); + if (story) { + return story.id; + } + // this will preserve existing behavior of showing a "not found" screen, + // but the inputs will be preserved in the query param to help debugging + return toId(selectedKind, selectedStory); + } + return undefined; +}; export const parseQueryParameters = search => { const { id } = qs.parse(search, { ignoreQueryPrefix: true }); return id; }; -export const initializePath = () => { +export const initializePath = storyStore => { const query = qs.parse(document.location.search, { ignoreQueryPrefix: true }); let { id: storyId, viewMode } = query; // eslint-disable-line prefer-const if (!storyId) { - storyId = getIdFromLegacyQuery(query); + storyId = getIdFromLegacyQuery(query, storyStore); if (storyId) { setPath({ storyId, viewMode }); } diff --git a/lib/core/src/client/preview/url.test.js b/lib/core/src/client/preview/url.test.js index 0a33955e4c71..411b2fce144b 100644 --- a/lib/core/src/client/preview/url.test.js +++ b/lib/core/src/client/preview/url.test.js @@ -42,16 +42,26 @@ describe('url', () => { }); describe('getIdFromLegacyQuery', () => { + const store = { getRawStory: () => null }; it('should parse story paths', () => { - expect(getIdFromLegacyQuery({ path: '/story/story--id' })).toBe('story--id'); + expect(getIdFromLegacyQuery({ path: '/story/story--id' }, store)).toBe('story--id'); }); - it('should parse legacy queries', () => { + it('should use legacy parameters to look up custom story ids', () => { + const customStore = { + getRawStory: () => ({ id: 'custom--id' }), + }; expect( - getIdFromLegacyQuery({ path: null, selectedKind: 'kind', selectedStory: 'story' }) + getIdFromLegacyQuery({ selectedKind: 'kind', selectedStory: 'story' }, customStore) + ).toBe('custom--id'); + }); + it('should use fall-back behavior for legacy queries for unknown stories', () => { + expect( + getIdFromLegacyQuery({ path: null, selectedKind: 'kind', selectedStory: 'story' }, store) ).toBe('kind--story'); }); + it('should not parse non-queries', () => { - expect(getIdFromLegacyQuery({})).toBeUndefined(); + expect(getIdFromLegacyQuery({}, store)).toBeUndefined(); }); }); @@ -65,14 +75,15 @@ describe('url', () => { }); describe('initializePath', () => { + const store = { getRawStory: () => null }; it('should handle id queries', () => { document.location.search = '?id=story--id'; - expect(initializePath()).toEqual({ storyId: 'story--id' }); + expect(initializePath(store)).toEqual({ storyId: 'story--id' }); expect(history.replaceState).not.toHaveBeenCalled(); }); it('should redirect legacy queries', () => { document.location.search = '?selectedKind=kind&selectedStory=story'; - expect(initializePath()).toEqual({ storyId: 'kind--story' }); + expect(initializePath(store)).toEqual({ storyId: 'kind--story' }); expect(history.replaceState).toHaveBeenCalledWith({}, '', 'pathname?id=kind--story'); }); });