diff --git a/addons/links/package.json b/addons/links/package.json index 64cc9cc660d1..03fa11b56f19 100644 --- a/addons/links/package.json +++ b/addons/links/package.json @@ -30,6 +30,7 @@ }, "dependencies": { "@storybook/addons": "5.3.0-alpha.1", + "@storybook/client-logger": "5.3.0-alpha.1", "@storybook/core-events": "5.3.0-alpha.1", "@storybook/router": "5.3.0-alpha.1", "common-tags": "^1.8.0", diff --git a/addons/links/src/preview.test.js b/addons/links/src/preview.test.js index 2f9ba57677f6..49fa1bba24c6 100644 --- a/addons/links/src/preview.test.js +++ b/addons/links/src/preview.test.js @@ -1,8 +1,35 @@ import addons from '@storybook/addons'; import { SELECT_STORY } from '@storybook/core-events'; + +import { __STORYBOOK_STORY_STORE__ } from 'global'; import { linkTo, hrefTo } from './preview'; jest.mock('@storybook/addons'); +jest.mock('global', () => ({ + document: global.document, + __STORYBOOK_STORY_STORE__: { + getSelection: jest.fn(() => ({ + storyId: 'name', + kind: 'kind', + })), + fromId: jest.fn(() => ({ + story: 'name', + kind: 'kind', + })), + }, + __STORYBOOK_CLIENT_API__: { + raw: jest.fn(() => [ + { + story: 'name', + kind: 'kind', + }, + { + story: 'namekind', + kind: 'kindname', + }, + ]), + }, +})); export const mockChannel = () => { return { @@ -27,9 +54,67 @@ describe('preview', () => { }); }); + it('should select the kind (only) provided', () => { + const channel = { emit: jest.fn() }; + addons.getChannel.mockReturnValue(channel); + __STORYBOOK_STORY_STORE__.fromId.mockImplementation(input => null); + + const handler = linkTo('kind'); + handler(); + + expect(channel.emit).toHaveBeenCalledWith(SELECT_STORY, { + kind: 'kind', + story: 'name', + }); + }); + + it('should select the story (only) provided', () => { + const channel = { emit: jest.fn() }; + addons.getChannel.mockReturnValue(channel); + // simulate a currently selected, but not found as ID + __STORYBOOK_STORY_STORE__.fromId.mockImplementation(input => + !input + ? { + kind: 'kind', + story: 'name', + } + : null + ); + + const handler = linkTo(undefined, 'kind'); + handler(); + + expect(channel.emit).toHaveBeenCalledWith(SELECT_STORY, { + kind: 'kind', + story: 'name', + }); + }); + + it('should select the id provided', () => { + const channel = { emit: jest.fn() }; + addons.getChannel.mockReturnValue(channel); + __STORYBOOK_STORY_STORE__.fromId.mockImplementation(input => + input === 'kind--story' + ? { + story: 'name', + kind: 'kind', + } + : null + ); + + const handler = linkTo('kind--story'); + handler(); + + expect(channel.emit).toHaveBeenCalledWith(SELECT_STORY, { + kind: 'kind', + story: 'name', + }); + }); + it('should handle functions returning strings', () => { const channel = { emit: jest.fn() }; addons.getChannel.mockReturnValue(channel); + __STORYBOOK_STORY_STORE__.fromId.mockImplementation(input => null); const handler = linkTo((a, b) => a + b, (a, b) => b + a); handler('kind', 'name'); diff --git a/addons/links/src/preview.ts b/addons/links/src/preview.ts index 3a80caedcc9d..5fc7eb652bc6 100644 --- a/addons/links/src/preview.ts +++ b/addons/links/src/preview.ts @@ -1,8 +1,14 @@ -import { document, HTMLElement } from 'global'; +import { + document, + HTMLElement, + __STORYBOOK_STORY_STORE__ as storyStore, + __STORYBOOK_CLIENT_API__ as clientApi, +} from 'global'; import qs from 'qs'; import addons from '@storybook/addons'; import { STORY_CHANGED, SELECT_STORY } from '@storybook/core-events'; import { toId } from '@storybook/router/utils'; +import { logger } from '@storybook/client-logger'; interface ParamsId { storyId: string; @@ -27,19 +33,50 @@ const generateUrl = (id: string) => { const valueOrCall = (args: string[]) => (value: string | ((...args: string[]) => string)) => typeof value === 'function' ? value(...args) : value; -export const linkTo = (kind: string, story?: string) => (...args: string[]) => { +export const linkTo = (idOrKindInput: string, storyInput?: string) => (...args: string[]) => { const resolver = valueOrCall(args); + const { storyId } = storyStore.getSelection(); + const current = storyStore.fromId(storyId) || {}; + const kindVal = resolver(idOrKindInput); + const storyVal = resolver(storyInput); - navigate({ - kind: resolver(kind), - story: resolver(story), - }); + const fromid = storyStore.fromId(kindVal); + + const item = + fromid || + clientApi.raw().find((i: any) => { + if (kindVal && storyVal) { + return i.kind === kindVal && i.story === storyVal; + } + if (!kindVal && storyVal) { + return i.kind === current.kind && i.story === storyVal; + } + if (kindVal && !storyVal) { + return i.kind === kindVal; + } + if (!kindVal && !storyVal) { + return i.kind === current.kind; + } + return false; + }); + + if (item) { + navigate({ + kind: item.kind, + story: item.story, + }); + } else { + logger.error('could not navigate to provided story'); + } }; -export const hrefTo = (kind: string, name: string): Promise => - new Promise(resolve => { - resolve(generateUrl(toId(kind, name))); +export const hrefTo = (kind: string, name: string): Promise => { + return new Promise(resolve => { + const { storyId } = storyStore.getSelection(); + const current = storyStore.fromId(storyId); + resolve(generateUrl(toId(kind || current.kind, name))); }); +}; const linksListener = (e: Event) => { const { target } = e;