diff --git a/app/angular/index.d.ts b/app/angular/index.d.ts index 1e067cc761f3..38ca5849e2c0 100644 --- a/app/angular/index.d.ts +++ b/app/angular/index.d.ts @@ -11,7 +11,15 @@ export interface IStoribookSection { stories: IStorybookStory[]; } -export type IGetStory = () => { +export interface IStoryContext { + kind: string; + name: string; + parameters: any; +} + +export type IGetStory = ( + IStoryContext +) => { props?: ICollection; moduleMetadata?: Partial; component?: any; @@ -21,13 +29,15 @@ export type IGetStory = () => { export interface IApi { kind: string; addDecorator: (decorator: any) => IApi; - add: (storyName: string, getStory: IGetStory) => IApi; + addParameters: (parameters: any) => IApi; + add: (storyName: string, getStory: IGetStory, parameters?: any) => IApi; } declare module '@storybook/angular' { export function storiesOf(kind: string, module: NodeModule): IApi; export function setAddon(addon: any): void; export function addDecorator(decorator: any): IApi; + export function addParameters(parameters: any): IApi; export function configure(loaders: () => NodeRequire, module: NodeModule): void; export function getStorybook(): IStoribookSection[]; } diff --git a/app/angular/src/client/index.js b/app/angular/src/client/index.js index e2e3fc59d028..85a2bda84e6a 100644 --- a/app/angular/src/client/index.js +++ b/app/angular/src/client/index.js @@ -1,3 +1,10 @@ -export { storiesOf, setAddon, addDecorator, configure, getStorybook } from './preview'; +export { + storiesOf, + setAddon, + addDecorator, + addParameters, + configure, + getStorybook, +} from './preview'; export { moduleMetadata } from './preview/angular/decorators'; diff --git a/app/angular/src/client/preview/angular/helpers.ts b/app/angular/src/client/preview/angular/helpers.ts index 00f703453464..1c75e4756654 100644 --- a/app/angular/src/client/preview/angular/helpers.ts +++ b/app/angular/src/client/preview/angular/helpers.ts @@ -8,8 +8,7 @@ import { NoPreviewComponent } from './components/no-preview.component'; import { STORY } from './app.token'; import { NgModuleMetadata, - IGetStoryWithContext, - IContext, + IGetStory, NgProvidedData, IRenderErrorFn, IRenderStoryFn, @@ -78,11 +77,10 @@ const createComponentFromTemplate = (template: string, styles: string[]): Functi }; const initModule = ( - currentStory: IGetStoryWithContext, - context: IContext, + currentStory: IGetStory, reRender: boolean = false ): Function => { - const storyObj = currentStory(context); + const storyObj = currentStory(); const { component, template, props, styles, moduleMetadata = {} } = storyObj; let AnnotatedComponent; @@ -154,6 +152,6 @@ export const renderNoPreview = debounce(() => { draw(Module); }); -export const renderNgApp = debounce((story, context, reRender) => { - draw(initModule(story, context, reRender), reRender); +export const renderNgApp = debounce((story, reRender) => { + draw(initModule(story, reRender), reRender); }); diff --git a/app/angular/src/client/preview/angular/types.ts b/app/angular/src/client/preview/angular/types.ts index d8da614648e3..692c3c5ad34b 100644 --- a/app/angular/src/client/preview/angular/types.ts +++ b/app/angular/src/client/preview/angular/types.ts @@ -26,15 +26,10 @@ export interface NgError { export type NgProvidedData = NgStory | NgError; -export interface IContext { - [p: string]: any; -} - -export type IGetStoryWithContext = (context: IContext) => NgStory; +export type IGetStory = () => NgStory; export type IRenderStoryFn = ( - story: IGetStoryWithContext, - context: IContext, + story: IGetStory, reRender?: boolean ) => void; export type IRenderErrorFn = (error: Error) => void; diff --git a/app/angular/src/client/preview/index.js b/app/angular/src/client/preview/index.js index 31342417f889..5f4e9bb8d165 100644 --- a/app/angular/src/client/preview/index.js +++ b/app/angular/src/client/preview/index.js @@ -25,7 +25,14 @@ const storyStore = new StoryStore(); const reduxStore = createStore(reducer); const context = { storyStore, reduxStore }; const clientApi = new ClientApi(context); -export const { storiesOf, setAddon, addDecorator, clearDecorators, getStorybook } = clientApi; +export const { + storiesOf, + setAddon, + addDecorator, + addParameters, + clearDecorators, + getStorybook, +} = clientApi; let channel; if (isBrowser) { diff --git a/app/angular/src/client/preview/render.js b/app/angular/src/client/preview/render.js index c188ebc3513d..bd095f6406ce 100644 --- a/app/angular/src/client/preview/render.js +++ b/app/angular/src/client/preview/render.js @@ -27,7 +27,7 @@ export function renderMain(data, storyStore, forceRender) { const { selectedKind, selectedStory } = data; - const story = storyStore.getStory(selectedKind, selectedStory); + const story = storyStore.getStoryWithContext(selectedKind, selectedStory); if (!story) { renderNoPreview(); return null; @@ -47,11 +47,7 @@ export function renderMain(data, storyStore, forceRender) { previousKind = selectedKind; previousStory = selectedStory; } - const context = { - kind: selectedKind, - story: selectedStory, - }; - return renderNgApp(story, context, reRender); + return renderNgApp(story, reRender); } export default function renderPreview({ reduxStore, storyStore }, forceRender = false) { diff --git a/app/polymer/src/client/index.js b/app/polymer/src/client/index.js index 925ffed29bb3..ff377f83f50d 100644 --- a/app/polymer/src/client/index.js +++ b/app/polymer/src/client/index.js @@ -1 +1,8 @@ -export { storiesOf, setAddon, addDecorator, configure, getStorybook } from './preview'; +export { + storiesOf, + setAddon, + addDecorator, + addParameters, + configure, + getStorybook, +} from './preview'; diff --git a/app/polymer/src/client/preview/index.js b/app/polymer/src/client/preview/index.js index fd70a0001a68..063e2edb5834 100644 --- a/app/polymer/src/client/preview/index.js +++ b/app/polymer/src/client/preview/index.js @@ -27,7 +27,14 @@ const reduxStore = createStore(reducer); const context = { storyStore, reduxStore }; const clientApi = new ClientApi(context); -export const { storiesOf, setAddon, addDecorator, clearDecorators, getStorybook } = clientApi; +export const { + storiesOf, + setAddon, + addDecorator, + addParameters, + clearDecorators, + getStorybook, +} = clientApi; let channel; if (isBrowser) { diff --git a/app/polymer/src/client/preview/render.js b/app/polymer/src/client/preview/render.js index 7087e278e433..09e2b20634a8 100644 --- a/app/polymer/src/client/preview/render.js +++ b/app/polymer/src/client/preview/render.js @@ -21,7 +21,7 @@ export function renderException(error) { export function renderMain(data, storyStore) { if (storyStore.size() === 0) return; const { selectedKind, selectedStory } = data; - const story = storyStore.getStory(selectedKind, selectedStory); + const story = storyStore.getStoryWithContext(selectedKind, selectedStory); if (selectedKind !== previousKind || previousStory !== selectedStory) { previousKind = selectedKind; @@ -29,11 +29,7 @@ export function renderMain(data, storyStore) { } else { return; } - const context = { - kind: selectedKind, - story: selectedStory, - }; - const component = story ? story(context) : nopreview; + const component = story ? story() : nopreview; if (!component) { renderError({ diff --git a/app/react-native/src/index.js b/app/react-native/src/index.js index 21298b6d5241..9873bd6f9db8 100644 --- a/app/react-native/src/index.js +++ b/app/react-native/src/index.js @@ -11,6 +11,7 @@ const preview = new Preview(); export const storiesOf = preview.storiesOf.bind(preview); export const setAddon = preview.setAddon.bind(preview); export const addDecorator = preview.addDecorator.bind(preview); +export const addParameters = preview.addParameters.bind(preview); export const clearDecorators = preview.clearDecorators.bind(preview); export const configure = preview.configure.bind(preview); export const getStorybook = preview.getStorybook.bind(preview); diff --git a/app/react-native/src/preview/components/StoryView/index.js b/app/react-native/src/preview/components/StoryView/index.js index f0d897929c6f..081ccae37622 100644 --- a/app/react-native/src/preview/components/StoryView/index.js +++ b/app/react-native/src/preview/components/StoryView/index.js @@ -44,10 +44,9 @@ export default class StoryView extends Component { return this.renderHelp(); } const { kind, story } = this.state.selection; - const context = { kind, story }; return ( - {this.state.storyFn(context)} + {this.state.storyFn()} ); } diff --git a/app/react-native/src/preview/index.js b/app/react-native/src/preview/index.js index 742945492120..a52bcf840cab 100644 --- a/app/react-native/src/preview/index.js +++ b/app/react-native/src/preview/index.js @@ -18,7 +18,14 @@ export default class Preview { this._stories = new StoryStore(); this._clientApi = new ClientApi({ storyStore: this._stories }); - ['storiesOf', 'setAddon', 'addDecorator', 'clearDecorators', 'getStorybook'].forEach(method => { + [ + 'storiesOf', + 'setAddon', + 'addDecorator', + 'addParameters', + 'clearDecorators', + 'getStorybook', + ].forEach(method => { this[method] = this._clientApi[method].bind(this._clientApi); }); } @@ -87,7 +94,7 @@ export default class Preview { _selectStory(selection) { const { kind, story } = selection; - const storyFn = this._stories.getStory(kind, story); + const storyFn = this._stories.getStoryWithContext(kind, story); this._events.emit('story', storyFn, selection); } } diff --git a/app/react/src/client/index.js b/app/react/src/client/index.js index 889ca11bfa29..3fd6981e0f65 100644 --- a/app/react/src/client/index.js +++ b/app/react/src/client/index.js @@ -8,6 +8,7 @@ export { storiesOf, setAddon, addDecorator, + addParameters, configure, getStorybook, forceReRender, diff --git a/app/react/src/client/preview/index.js b/app/react/src/client/preview/index.js index fd70a0001a68..063e2edb5834 100644 --- a/app/react/src/client/preview/index.js +++ b/app/react/src/client/preview/index.js @@ -27,7 +27,14 @@ const reduxStore = createStore(reducer); const context = { storyStore, reduxStore }; const clientApi = new ClientApi(context); -export const { storiesOf, setAddon, addDecorator, clearDecorators, getStorybook } = clientApi; +export const { + storiesOf, + setAddon, + addDecorator, + addParameters, + clearDecorators, + getStorybook, +} = clientApi; let channel; if (isBrowser) { diff --git a/app/react/src/client/preview/render.js b/app/react/src/client/preview/render.js index 4f003ce64721..c5d591e7915a 100644 --- a/app/react/src/client/preview/render.js +++ b/app/react/src/client/preview/render.js @@ -47,7 +47,7 @@ export function renderMain(data, storyStore, forceRender) { const { selectedKind, selectedStory } = data; const revision = storyStore.getRevision(); - const story = storyStore.getStory(selectedKind, selectedStory); + const story = storyStore.getStoryWithContext(selectedKind, selectedStory); if (!story) { ReactDOM.render(noPreview, rootEl); return null; @@ -77,12 +77,7 @@ export function renderMain(data, storyStore, forceRender) { previousStory = selectedStory; ReactDOM.unmountComponentAtNode(rootEl); - const context = { - kind: selectedKind, - story: selectedStory, - }; - - const element = story(context); + const element = story(); if (!element) { const error = { diff --git a/app/vue/src/client/index.js b/app/vue/src/client/index.js index 925ffed29bb3..ff377f83f50d 100644 --- a/app/vue/src/client/index.js +++ b/app/vue/src/client/index.js @@ -1 +1,8 @@ -export { storiesOf, setAddon, addDecorator, configure, getStorybook } from './preview'; +export { + storiesOf, + setAddon, + addDecorator, + addParameters, + configure, + getStorybook, +} from './preview'; diff --git a/app/vue/src/client/preview/index.js b/app/vue/src/client/preview/index.js index bad9df8c2cf9..c916f494d7a4 100644 --- a/app/vue/src/client/preview/index.js +++ b/app/vue/src/client/preview/index.js @@ -44,7 +44,14 @@ const decorateStory = (getStory, decorators) => ); const context = { storyStore, reduxStore, decorateStory }; const clientApi = new ClientApi(context); -export const { storiesOf, setAddon, addDecorator, clearDecorators, getStorybook } = clientApi; +export const { + storiesOf, + setAddon, + addDecorator, + addParameters, + clearDecorators, + getStorybook, +} = clientApi; let channel; if (isBrowser) { diff --git a/app/vue/src/client/preview/render.js b/app/vue/src/client/preview/render.js index caf6a9a8365c..9fc94714d113 100644 --- a/app/vue/src/client/preview/render.js +++ b/app/vue/src/client/preview/render.js @@ -19,7 +19,13 @@ function renderErrorDisplay(error) { return h( 'div', { attrs: { id: 'error-display' } }, - error ? [h(ErrorDisplay, { props: { message: error.message, stack: error.stack } })] : [] + error + ? [ + h(ErrorDisplay, { + props: { message: error.message, stack: error.stack }, + }), + ] + : [] ); }, }); @@ -54,7 +60,7 @@ export function renderMain(data, storyStore, forceRender) { const { selectedKind, selectedStory } = data; - const story = storyStore.getStory(selectedKind, selectedStory); + const story = storyStore.getStoryWithContext(selectedKind, selectedStory); // Unmount the previous story only if selectedKind or selectedStory has changed. // renderMain() gets executed after each action. Actions will cause the whole @@ -71,12 +77,7 @@ export function renderMain(data, storyStore, forceRender) { return; } - const context = { - kind: selectedKind, - story: selectedStory, - }; - - const component = story ? story(context) : NoPreview; + const component = story ? story() : NoPreview; if (!component) { const error = { diff --git a/examples/angular-cli/src/stories/__snapshots__/core.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/core.stories.storyshot new file mode 100644 index 000000000000..5f1150f55adb --- /dev/null +++ b/examples/angular-cli/src/stories/__snapshots__/core.stories.storyshot @@ -0,0 +1,23 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Core|Parameters passed to story 1`] = ` + + + + + + + + + +`; diff --git a/examples/angular-cli/src/stories/__snapshots__/custom-styles.stories.storyshot b/examples/angular-cli/src/stories/__snapshots__/custom-styles.stories.storyshot index 0b26e9da8a43..9999b7af6024 100644 --- a/examples/angular-cli/src/stories/__snapshots__/custom-styles.stories.storyshot +++ b/examples/angular-cli/src/stories/__snapshots__/custom-styles.stories.storyshot @@ -8,13 +8,13 @@ exports[`Storyshots Custom|Style Default 1`] = ` > @@ -32,17 +32,17 @@ exports[`Storyshots Custom|Style With Knobs 1`] = ` target={[Function ViewContainerRef_]} > diff --git a/examples/angular-cli/src/stories/core.stories.ts b/examples/angular-cli/src/stories/core.stories.ts new file mode 100644 index 000000000000..33858918d912 --- /dev/null +++ b/examples/angular-cli/src/stories/core.stories.ts @@ -0,0 +1,22 @@ +import { storiesOf, addParameters } from '@storybook/angular'; +import { Button } from '@storybook/angular/demo'; + +const globalParameter = 'globalParameter'; +const chapterParameter = 'chapterParameter'; +const storyParameter = 'storyParameter'; + +addParameters({ globalParameter }); + +storiesOf('Core|Parameters', module) + .addParameters({ chapterParameter }) + .add( + 'passed to story', + ({ parameters: { fileName, ...parameters } }) => ({ + component: Button, + props: { + text: `Parameters are ${JSON.stringify(parameters)}`, + onClick: () => 0, + }, + }), + { storyParameter } + ); diff --git a/examples/crna-kitchen-sink/storybook/stories/index.js b/examples/crna-kitchen-sink/storybook/stories/index.js index 2eea2d4a4e29..a9720029b43c 100644 --- a/examples/crna-kitchen-sink/storybook/stories/index.js +++ b/examples/crna-kitchen-sink/storybook/stories/index.js @@ -1,7 +1,7 @@ import React from 'react'; import { Text } from 'react-native'; -import { storiesOf } from '@storybook/react-native'; +import { storiesOf, addParameters } from '@storybook/react-native'; import { action } from '@storybook/addon-actions'; import { linkTo } from '@storybook/addon-links'; import { withKnobs } from '@storybook/addon-knobs'; @@ -29,3 +29,19 @@ storiesOf('Button', module) storiesOf('Knobs', module) .addDecorator(withKnobs) .add('with knobs', knobsWrapper); + +const globalParameter = 'globalParameter'; +const chapterParameter = 'chapterParameter'; +const storyParameter = 'storyParameter'; + +addParameters({ globalParameter }); + +storiesOf('Core|Parameters', module) + .addParameters({ chapterParameter }) + .add( + 'passed to story', + ({ parameters }) => Parameters are {JSON.stringify(parameters)}, + { + storyParameter, + } + ); diff --git a/examples/official-storybook/stories/__snapshots__/core.stories.storyshot b/examples/official-storybook/stories/__snapshots__/core.stories.storyshot new file mode 100644 index 000000000000..6e7bbf22c163 --- /dev/null +++ b/examples/official-storybook/stories/__snapshots__/core.stories.storyshot @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Core|Parameters passed to story 1`] = ` +
+ Parameters are {"globalParameter":"globalParameter","chapterParameter":"chapterParameter","storyParameter":"storyParameter"} +
+`; diff --git a/examples/official-storybook/stories/core.stories.js b/examples/official-storybook/stories/core.stories.js new file mode 100644 index 000000000000..41b2ea780bee --- /dev/null +++ b/examples/official-storybook/stories/core.stories.js @@ -0,0 +1,20 @@ +import React from 'react'; +import { storiesOf, addParameters } from '@storybook/react'; + +const globalParameter = 'globalParameter'; +const chapterParameter = 'chapterParameter'; +const storyParameter = 'storyParameter'; + +addParameters({ globalParameter }); + +storiesOf('Core|Parameters', module) + .addParameters({ chapterParameter }) + .add( + 'passed to story', + ({ parameters: { fileName, ...parameters } }) => ( +
Parameters are {JSON.stringify(parameters)}
+ ), + { + storyParameter, + } + ); diff --git a/examples/polymer-cli/src/stories/core.stories.js b/examples/polymer-cli/src/stories/core.stories.js new file mode 100644 index 000000000000..4fefac0a6df0 --- /dev/null +++ b/examples/polymer-cli/src/stories/core.stories.js @@ -0,0 +1,18 @@ +import { storiesOf, addParameters } from '@storybook/polymer'; + +const globalParameter = 'globalParameter'; +const chapterParameter = 'chapterParameter'; +const storyParameter = 'storyParameter'; + +addParameters({ globalParameter }); + +storiesOf('Core|Parameters', module) + .addParameters({ chapterParameter }) + .add( + 'passed to story', + ({ parameters: { fileName, ...parameters } }) => + `
Parameters are ${JSON.stringify(parameters)}
`, + { + storyParameter, + } + ); diff --git a/examples/vue-kitchen-sink/.babelrc b/examples/vue-kitchen-sink/.babelrc index 0e1b2524d3d2..14f0ab14540d 100644 --- a/examples/vue-kitchen-sink/.babelrc +++ b/examples/vue-kitchen-sink/.babelrc @@ -1,6 +1,7 @@ { "presets": [ ["env", { "modules": false }], + "stage-0", "vue" ], "env": { @@ -9,4 +10,3 @@ } } } - diff --git a/examples/vue-kitchen-sink/src/stories/__snapshots__/core.stories.storyshot b/examples/vue-kitchen-sink/src/stories/__snapshots__/core.stories.storyshot new file mode 100644 index 000000000000..6e7bbf22c163 --- /dev/null +++ b/examples/vue-kitchen-sink/src/stories/__snapshots__/core.stories.storyshot @@ -0,0 +1,7 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Storyshots Core|Parameters passed to story 1`] = ` +
+ Parameters are {"globalParameter":"globalParameter","chapterParameter":"chapterParameter","storyParameter":"storyParameter"} +
+`; diff --git a/examples/vue-kitchen-sink/src/stories/core.stories.js b/examples/vue-kitchen-sink/src/stories/core.stories.js new file mode 100644 index 000000000000..a03d25cb5a6c --- /dev/null +++ b/examples/vue-kitchen-sink/src/stories/core.stories.js @@ -0,0 +1,19 @@ +import { storiesOf, addParameters } from '@storybook/vue'; + +const globalParameter = 'globalParameter'; +const chapterParameter = 'chapterParameter'; +const storyParameter = 'storyParameter'; + +addParameters({ globalParameter }); + +storiesOf('Core|Parameters', module) + .addParameters({ chapterParameter }) + .add( + 'passed to story', + ({ parameters: { fileName, ...parameters } }) => ({ + template: `
Parameters are ${JSON.stringify(parameters)}
`, + }), + { + storyParameter, + } + ); diff --git a/lib/core/src/client/preview/client_api.js b/lib/core/src/client/preview/client_api.js index eb7f0ff81ec6..c63cbfd4e0af 100644 --- a/lib/core/src/client/preview/client_api.js +++ b/lib/core/src/client/preview/client_api.js @@ -15,6 +15,7 @@ export default class ClientApi { this._storyStore = storyStore; this._addons = {}; this._globalDecorators = []; + this._globalParameters = {}; this._decorateStory = decorateStory; } @@ -29,6 +30,10 @@ export default class ClientApi { this._globalDecorators.push(decorator); }; + addParameters = parameters => { + this._globalParameters = parameters; + }; + clearDecorators = () => { this._globalDecorators = []; }; @@ -52,6 +57,7 @@ export default class ClientApi { } const localDecorators = []; + let localParameters = {}; const api = { kind, }; @@ -65,7 +71,7 @@ export default class ClientApi { }; }); - api.add = (storyName, getStory) => { + api.add = (storyName, getStory, parameters) => { if (typeof storyName !== 'string') { throw new Error(`Invalid or missing storyName provided for a "${kind}" story.`); } @@ -82,12 +88,12 @@ export default class ClientApi { const fileName = m ? m.filename : null; // Add the fully decorated getStory function. - this._storyStore.addStory( - kind, - storyName, - this._decorateStory(getStory, decorators), - fileName - ); + this._storyStore.addStory(kind, storyName, this._decorateStory(getStory, decorators), { + ...this._globalParameters, + ...localParameters, + ...parameters, + fileName, + }); return api; }; @@ -96,6 +102,11 @@ export default class ClientApi { return api; }; + api.addParameters = parameters => { + localParameters = { ...localParameters, ...parameters }; + return api; + }; + return api; }; @@ -104,7 +115,7 @@ export default class ClientApi { const fileName = this._storyStore.getStoryFileName(kind); const stories = this._storyStore.getStories(kind).map(name => { - const render = this._storyStore.getStory(kind, name); + const render = this._storyStore.getStoryWithContext(kind, name); return { name, render }; }); diff --git a/lib/core/src/client/preview/client_api.test.js b/lib/core/src/client/preview/client_api.test.js index 0bda6c435e6c..05ba6cad47e2 100644 --- a/lib/core/src/client/preview/client_api.test.js +++ b/lib/core/src/client/preview/client_api.test.js @@ -189,7 +189,7 @@ describe('preview.client_api', () => { return ['a', 'b']; } - getStory(kind, name) { + getStoryWithContext(kind, name) { return `${kind}:${name}`; } } @@ -213,11 +213,15 @@ describe('preview.client_api', () => { describe('reads filename from module', () => { const api = new ClientAPI(); - const story = () => 0; + const story = jest.fn(); api.storiesOf('kind', { filename: 'foo.js' }).add('story', story); - expect(api.getStorybook()).toEqual([ - { kind: 'kind', fileName: 'foo.js', stories: [{ name: 'story', render: story }] }, + const storybook = api.getStorybook(); + expect(storybook).toEqual([ + { kind: 'kind', fileName: 'foo.js', stories: [{ name: 'story', render: expect.anything() }] }, ]); + + storybook[0].stories[0].render(); + expect(story).toHaveBeenCalled(); }); describe('hot module loading', () => { @@ -252,23 +256,29 @@ describe('preview.client_api', () => { expect(api.getStorybook()).toEqual([]); api.storiesOf('kind', module).add('story', stories[0]); - expect(api.getStorybook()).toEqual([ + const firstStorybook = api.getStorybook(); + expect(firstStorybook).toEqual([ { kind: 'kind', - stories: [{ name: 'story', render: stories[0] }], + stories: [{ name: 'story', render: expect.anything() }], }, ]); + firstStorybook[0].stories[0].render(); + expect(stories[0]).toHaveBeenCalled(); module.hot.reload(); expect(api.getStorybook()).toEqual([]); api.storiesOf('kind', module).add('story', stories[1]); - expect(api.getStorybook()).toEqual([ + const secondStorybook = api.getStorybook(); + expect(secondStorybook).toEqual([ { kind: 'kind', - stories: [{ name: 'story', render: stories[1] }], + stories: [{ name: 'story', render: expect.anything() }], }, ]); + secondStorybook[0].stories[0].render(); + expect(stories[1]).toHaveBeenCalled(); }); }); }); diff --git a/lib/core/src/client/preview/story_store.js b/lib/core/src/client/preview/story_store.js index 7302f8e5f497..3734fc02f022 100644 --- a/lib/core/src/client/preview/story_store.js +++ b/lib/core/src/client/preview/story_store.js @@ -23,11 +23,11 @@ export default class StoryStore extends EventEmitter { this._revision += 1; } - addStory(kind, name, fn, fileName) { + addStory(kind, name, fn, parameters = {}) { if (!this._data[kind]) { this._data[kind] = { kind, - fileName, + fileName: parameters.fileName, index: getId(), stories: {}, }; @@ -37,9 +37,10 @@ export default class StoryStore extends EventEmitter { name, index: getId(), fn, + parameters, }; - this.emit('storyAdded', kind, name, fn); + this.emit('storyAdded', kind, name, fn, parameters); } getStoryKinds() { @@ -70,7 +71,7 @@ export default class StoryStore extends EventEmitter { return storiesKind.fileName; } - getStory(kind, name) { + getStoryAndParameters(kind, name) { const storiesKind = this._data[kind]; if (!storiesKind) { return null; @@ -81,7 +82,31 @@ export default class StoryStore extends EventEmitter { return null; } - return storyInfo.fn; + const { fn, parameters } = storyInfo; + return { + story: fn, + parameters, + }; + } + + getStory(kind, name) { + const data = this.getStoryAndParameters(kind, name); + return data && data.story; + } + + getStoryWithContext(kind, name) { + const data = this.getStoryAndParameters(kind, name); + if (!data) { + return null; + } + + const { story, parameters } = data; + return () => + story({ + kind, + story: name, + parameters, + }); } removeStoryKind(kind) { diff --git a/lib/core/src/client/preview/story_store.test.js b/lib/core/src/client/preview/story_store.test.js index 3aabd5e937e9..b25f2715af07 100644 --- a/lib/core/src/client/preview/story_store.test.js +++ b/lib/core/src/client/preview/story_store.test.js @@ -31,9 +31,9 @@ describe('preview.story_store', () => { describe('getStoryFileName', () => { it('should return the filename of the first story passed for the kind', () => { const store = new StoryStore(); - store.addStory('kind-1', 'story-1.1', () => 0, 'foo.js'); - store.addStory('kind-1', 'story-1.2', () => 0, 'foo-2.js'); - store.addStory('kind-2', 'story-2.1', () => 0, 'bar.js'); + store.addStory('kind-1', 'story-1.1', () => 0, { fileName: 'foo.js' }); + store.addStory('kind-1', 'story-1.2', () => 0, { fileName: 'foo-2.js' }); + store.addStory('kind-2', 'story-2.1', () => 0, { fileName: 'bar.js' }); expect(store.getStoryFileName('kind-1')).toBe('foo.js'); expect(store.getStoryFileName('kind-2')).toBe('bar.js'); @@ -46,4 +46,41 @@ describe('preview.story_store', () => { store.removeStoryKind('kind'); }); }); + + describe('getStoryAndParameters', () => { + it('should return parameters that we passed in', () => { + const store = new StoryStore(); + const story = jest.fn(); + const parameters = { + fileName: 'foo.js', + parameter: 'value', + }; + store.addStory('kind', 'name', story, parameters); + + expect(store.getStoryAndParameters('kind', 'name')).toEqual({ + story, + parameters, + }); + }); + }); + + describe('getStoryWithContext', () => { + it('should return a function that calls the story with the context', () => { + const store = new StoryStore(); + const story = jest.fn(); + const parameters = { + fileName: 'foo.js', + parameter: 'value', + }; + store.addStory('kind', 'name', story, parameters); + + const storyWithContext = store.getStoryWithContext('kind', 'name'); + storyWithContext(); + expect(story).toHaveBeenCalledWith({ + kind: 'kind', + story: 'name', + parameters, + }); + }); + }); });