Skip to content

Commit

Permalink
Refactor story_store (#6382)
Browse files Browse the repository at this point in the history
Refactor story_store
  • Loading branch information
ndelangen authored Jun 17, 2019
2 parents 17d676e + 1e81ea4 commit a0b5c9c
Show file tree
Hide file tree
Showing 8 changed files with 190 additions and 112 deletions.
File renamed without changes.
37 changes: 2 additions & 35 deletions lib/client-api/src/story_store.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
/* eslint no-underscore-dangle: 0 */
import { history, document } from 'global';
import qs from 'qs';
import EventEmitter from 'eventemitter3';
import memoize from 'memoizerific';
import debounce from 'lodash/debounce';
import { stripIndents } from 'common-tags';

import Events from '@storybook/core-events';
import { logger } from '@storybook/client-logger';
import { toId } from '@storybook/router/utils';

import pathToId from './pathToId';
import { getQueryParams } from './queryparams';

// TODO: these are copies from components/nav/lib
// refactor to DRY
Expand Down Expand Up @@ -39,9 +33,6 @@ const toExtracted = obj =>
return Object.assign(acc, { [key]: value });
}, {});

const getIdFromLegacyQuery = ({ path, selectedKind, selectedStory }) =>
(path && pathToId(path)) || (selectedKind && selectedStory && toId(selectedKind, selectedStory));

export default class StoryStore extends EventEmitter {
constructor(params) {
super();
Expand All @@ -51,37 +42,13 @@ export default class StoryStore extends EventEmitter {
this._revision = 0;
this._selection = {};
this._channel = params.channel;

this.on(Events.STORY_INIT, () => {
let storyId = this.getIdOnPath();
if (!storyId) {
const query = getQueryParams();
storyId = getIdFromLegacyQuery(query);
if (storyId) {
const { path, selectedKind, selectedStory, ...rest } = query;
this.setPath(storyId, rest);
}
}
this.setSelection(this.fromId(storyId));
});
}

setChannel = channel => {
this._channel = channel;
};

// NEW apis

getIdOnPath = () => {
const { id } = getQueryParams();
return id;
};

setPath = (storyId, params = {}) => {
const path = `${document.location.pathname}?${qs.stringify({ ...params, id: storyId })}`;
history.replaceState({}, '', path);
};

fromId = id => {
try {
const data = this._data[id];
Expand Down Expand Up @@ -116,8 +83,8 @@ export default class StoryStore extends EventEmitter {
);
}

setSelection = data => {
this._selection = data;
setSelection = ({ storyId }) => {
this._selection = { storyId };
setTimeout(() => this.emit(Events.STORY_RENDER), 1);
};

Expand Down
59 changes: 0 additions & 59 deletions lib/client-api/src/story_store.test.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,9 @@
import { history, document } from 'global';
import createChannel from '@storybook/channel-postmessage';
import Events from '@storybook/core-events';
import { toId } from '@storybook/router/utils';

import StoryStore from './story_store';
import { defaultDecorateStory } from './client_api';

jest.mock('global', () => ({
history: { replaceState: jest.fn() },
window: {
addEventListener: jest.fn(),
},
document: {
location: {
pathname: 'pathname',
search: '',
},
addEventListener: jest.fn(),
},
}));

jest.mock('@storybook/node-logger', () => ({
logger: {
info: jest.fn(),
Expand Down Expand Up @@ -148,47 +132,4 @@ describe('preview.story_store', () => {
});
});
});

describe('setPath', () => {
it('preserves custom URL params', () => {
const store = new StoryStore({ channel });

store.setPath('story--id', { foo: 'bar' });
expect(history.replaceState).toHaveBeenCalledWith({}, '', 'pathname?foo=bar&id=story--id');
});
});

describe('STORY_INIT', () => {
const storyFn = () => 0;

it('supports path params', () => {
document.location = {
pathname: 'pathname',
search: '?path=/story/kind--story&bar=baz',
};
const store = new StoryStore({ channel });
store.addStory(...make('kind', 'story', storyFn));
store.setSelection = jest.fn();

store.emit(Events.STORY_INIT);
expect(history.replaceState).toHaveBeenCalledWith({}, '', 'pathname?bar=baz&id=kind--story');
expect(store.setSelection).toHaveBeenCalled();
expect(store.setSelection.mock.calls[0][0].getDecorated()).toEqual(storyFn);
});

it('supports story kind/name params', () => {
document.location = {
pathname: 'pathname',
search: '?selectedKind=kind&selectedStory=story&bar=baz',
};
const store = new StoryStore({ channel });
store.addStory(...make('kind', 'story', storyFn));
store.setSelection = jest.fn();

store.emit(Events.STORY_INIT);
expect(history.replaceState).toHaveBeenCalledWith({}, '', 'pathname?bar=baz&id=kind--story');
expect(store.setSelection).toHaveBeenCalled();
expect(store.setSelection.mock.calls[0][0].getDecorated()).toEqual(storyFn);
});
});
});
42 changes: 26 additions & 16 deletions lib/core/src/client/preview/start.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { toId } from '@storybook/router/utils';
import { logger } from '@storybook/client-logger';
import Events from '@storybook/core-events';
import deprecate from 'util-deprecate';
import { initializePath, setPath } from './url';

const classes = {
MAIN: 'sb-show-main',
Expand Down Expand Up @@ -121,8 +122,19 @@ export default function start(render, { decorateStory } = {}) {

const renderMain = forceRender => {
const revision = storyStore.getRevision();
const selection = storyStore.getSelection();
const { kind, name, getDecorated, id } = selection || {};
const { storyId } = storyStore.getSelection();

const data = storyStore.fromId(storyId);

const { kind, name, getDecorated, id } = data || {};

const renderContext = {
...context,
...data,
selectedKind: kind,
selectedStory: name,
forceRender,
};

if (getDecorated) {
// Render story only if selectedKind or selectedStory have changed.
Expand All @@ -142,21 +154,16 @@ export default function start(render, { decorateStory } = {}) {
addons.getChannel().emit(Events.STORY_CHANGED, id);
}

render({
...context,
...selection,
selectedKind: kind,
selectedStory: name,
forceRender,
});
previousRevision = revision;
previousKind = kind;
previousStory = name;

render(renderContext);
addons.getChannel().emit(Events.STORY_RENDERED, id);
} else {
showNopreview();
addons.getChannel().emit(Events.STORY_MISSING, id);
}
previousRevision = revision;
previousKind = kind;
previousStory = name;

if (!forceRender) {
document.documentElement.scrollTop = 0;
Expand Down Expand Up @@ -194,10 +201,8 @@ export default function start(render, { decorateStory } = {}) {
storyId = deprecatedToId(kind, name);
}

const data = storyStore.fromId(storyId);

storyStore.setSelection(data);
storyStore.setPath(storyId);
storyStore.setSelection({ storyId });
setPath({ storyId });
});

// Handle keyboard shortcuts
Expand All @@ -212,6 +217,11 @@ export default function start(render, { decorateStory } = {}) {
};
}

storyStore.on(Events.STORY_INIT, () => {
const { storyId } = initializePath();
storyStore.setSelection({ storyId });
});

storyStore.on(Events.STORY_RENDER, renderUI);

if (typeof window !== 'undefined') {
Expand Down
39 changes: 38 additions & 1 deletion lib/core/src/client/preview/start.test.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { document, window } from 'global';
/* eslint-disable no-underscore-dangle */
import { history, document, window } from 'global';

import Events from '@storybook/core-events';
import start from './start';

jest.mock('@storybook/client-logger');
jest.mock('global', () => ({
history: { replaceState: jest.fn() },
navigator: { userAgent: 'browser', platform: '' },
window: {
__STORYBOOK_CLIENT_API__: undefined,
Expand Down Expand Up @@ -105,3 +108,37 @@ it('emits an error and shows error when your framework calls showError', () => {
expect(render).toHaveBeenCalled();
expect(document.body.classList.add).toHaveBeenCalledWith('sb-show-errordisplay');
});

describe('STORY_INIT', () => {
it('supports path params', () => {
document.location = {
pathname: 'pathname',
search: '?path=/story/kind--story&bar=baz',
};

const render = jest.fn();
const { clientApi } = start(render);
const store = clientApi._storyStore;
store.setSelection = jest.fn();
store.emit(Events.STORY_INIT);

store.emit();
expect(store.setSelection).toHaveBeenCalledWith({ storyId: 'kind--story' });
});

it('supports story kind/name params', () => {
document.location = {
pathname: 'pathname',
search: '?selectedKind=kind&selectedStory=story&bar=baz',
};

const render = jest.fn();
const { clientApi } = start(render);
const store = clientApi._storyStore;
store.setSelection = jest.fn();

store.emit(Events.STORY_INIT);
expect(history.replaceState).toHaveBeenCalledWith({}, '', 'pathname?bar=baz&id=kind--story');
expect(store.setSelection).toHaveBeenCalledWith({ storyId: 'kind--story' });
});
});
39 changes: 39 additions & 0 deletions lib/core/src/client/preview/url.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { history, document } from 'global';
import qs from 'qs';
import { toId } from '@storybook/router/utils';

export function pathToId(path) {
const match = (path || '').match(/^\/story\/(.+)/);
if (!match) {
throw new Error(`Invalid path '${path}', must start with '/story/'`);
}
return match[1];
}

export const setPath = ({ storyId }) => {
const { path, selectedKind, selectedStory, ...rest } = qs.parse(document.location.search, {
ignoreQueryPrefix: true,
});
const newPath = `${document.location.pathname}?${qs.stringify({ ...rest, id: storyId })}`;
history.replaceState({}, '', newPath);
};

export const getIdFromLegacyQuery = ({ path, selectedKind, selectedStory }) =>
(path && pathToId(path)) || (selectedKind && selectedStory && toId(selectedKind, selectedStory));

export const parseQueryParameters = search => {
const { id } = qs.parse(search, { ignoreQueryPrefix: true });
return id;
};

export const initializePath = () => {
const query = qs.parse(document.location.search, { ignoreQueryPrefix: true });
let { id: storyId } = query;
if (!storyId) {
storyId = getIdFromLegacyQuery(query);
if (storyId) {
setPath({ storyId });
}
}
return { storyId };
};
Loading

0 comments on commit a0b5c9c

Please sign in to comment.