Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor story_store #6382

Merged
merged 3 commits into from
Jun 17, 2019
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 2 additions & 33 deletions lib/client-api/src/story_store.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
/* eslint no-underscore-dangle: 0 */
import { history, document } from 'global';
import EventEmitter from 'eventemitter3';
import qs from 'qs';
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';

// TODO: these are copies from components/nav/lib
// refactor to DRY
Expand Down Expand Up @@ -49,9 +44,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 @@ -61,19 +53,6 @@ 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 = qs.parse(document.location.search, { ignoreQueryPrefix: true });
storyId = getIdFromLegacyQuery(query);
if (storyId) {
const { path, selectedKind, selectedStory, ...rest } = query;
this.setPath(storyId, rest);
}
}
this.setSelection(this.fromId(storyId));
});
}

setChannel = channel => {
Expand All @@ -82,16 +61,6 @@ export default class StoryStore extends EventEmitter {

// NEW apis

getIdOnPath = () => {
const { id } = qs.parse(document.location.search, { ignoreQueryPrefix: true });
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 @@ -126,8 +95,8 @@ export default class StoryStore extends EventEmitter {
);
}

setSelection = data => {
this._selection = data;
setSelection = ({ storyId }) => {
this._selection = { storyId };
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
setTimeout(() => this.emit(Events.STORY_RENDER), 1);
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
};

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 @@ -114,8 +115,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 @@ -135,21 +147,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 @@ -187,10 +194,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 });
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
});

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

storyStore.on(Events.STORY_INIT, () => {
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
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 } from 'global';
/* eslint-disable no-underscore-dangle */
import { history, document } 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: {
addEventListener: jest.fn(),
Expand Down Expand Up @@ -85,3 +88,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) {
ndelangen marked this conversation as resolved.
Show resolved Hide resolved
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