Skip to content

Commit

Permalink
FIX the broken addon links.
Browse files Browse the repository at this point in the history
Functionality to support navigation by (storyId || kind || kind + storyName || storyName) is now implemented in the linkTo
itself.

It uses the global scope storyStore & clientApi, which isn't good, but we will refactor that later.

The navigate function now refuses to navigate to a story that does not exist.

hrefTo function also no longer throws an error when kind isn't provided

Add test to assert this functionality actually works
  • Loading branch information
ndelangen committed Sep 24, 2019
1 parent 4df4e13 commit 75fb3c0
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 9 deletions.
1 change: 1 addition & 0 deletions addons/links/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
85 changes: 85 additions & 0 deletions addons/links/src/preview.test.js
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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');
Expand Down
55 changes: 46 additions & 9 deletions addons/links/src/preview.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<string> =>
new Promise(resolve => {
resolve(generateUrl(toId(kind, name)));
export const hrefTo = (kind: string, name: string): Promise<string> => {
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;
Expand Down

0 comments on commit 75fb3c0

Please sign in to comment.