Add tests, docs, example

This commit is contained in:
Jimmy Somsanith 2019-06-11 23:21:34 +02:00
parent a8e6d1bcd9
commit 82f241e2c0
9 changed files with 249 additions and 21 deletions

View File

@ -1,7 +1,7 @@
import React from 'react';
import { addons, types } from '@storybook/addons';
import { ADDON_ID, PANEL_ID } from './constants';
import { ADDON_ID, PANEL_ID, PARAM_KEY } from './constants';
import { CssResourcePanel } from './css-resource-panel';
addons.register(ADDON_ID, api => {
@ -10,5 +10,6 @@ addons.register(ADDON_ID, api => {
type: types.PANEL,
title: 'CSS resources',
render: ({ active }) => <CssResourcePanel key={PANEL_ID} api={api} active={active} />,
paramKey: PARAM_KEY,
});
});

View File

@ -1,7 +1,7 @@
import * as React from 'react';
import addons, { types } from '@storybook/addons';
import { ADDON_ID, PANEL_ID } from './shared';
import { ADDON_ID, PANEL_ID, PARAM_KEY } from './shared';
// TODO: fix eslint in tslint (igor said he fixed it, should ask him)
import Panel from './Panel';
@ -14,6 +14,7 @@ export default function register(type: types) {
route: ({ storyId }) => `/info/${storyId}`, // todo add type
match: ({ viewMode }) => viewMode === 'info', // todo add type
render: ({ active }) => <Panel api={api} active={active} />,
paramKey: PARAM_KEY,
});
});
}

View File

@ -65,6 +65,29 @@ Then you'll be able to see those notes when you are viewing the story.
![Stories with notes](../static/stories-with-notes.png)
## Disable the addon
You can disable an addon panel for a story by adding a `disabled` parameter.
```js
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import Button from './Button';
storiesOf('Button', module).add(
'with some emoji',
() => (
<Button onClick={action('clicked')}>
<span role="img" aria-label="so cool">
😀 😎 👍 💯
</span>
</Button>
),
{ notes: { disabled: true } }
);
```
## Global Configuration
Sometimes you might want to configure an addon globally, as in the case of collocating stories with components, or just simply to keep your stories file cleaner. To do that, you can add your decorators to a config file, typically in `.storybook/config.js`. Here's an example of how you might do that.

View File

@ -110,6 +110,7 @@ addons.register(ADDON_ID, api => {
type: types.PANEL,
title,
render,
paramKey: PARAM_KEY,
});
});
```
@ -259,6 +260,34 @@ storiesOf('Button', module)
});
```
### Disabling an addon panel
It's possible to disable an addon panel for a particular story.
To offer that capability, you need to pass the paramKey when you register the panel
```js
addons.register(ADDON_ID, () => {
addons.add(PANEL_ID, {
type: types.PANEL,
title: 'My Addon',
render: () => <div>Addon tab content</div>,
paramKey: 'myAddon',
});
});
```
While adding a story, you can then pass a `disabled` parameter.
```js
storiesOf('Button', module)
.add('with text', () => <Button>Hello Button</Button>, {
myAddon: {
disabled: true,
},
});
```
## Styling your addon
We use [emotion](https://emotion.sh) for styling, AND we provide a theme which can be set by the user!

View File

@ -24,7 +24,6 @@ export interface Addon {
route?: (routeOptions: RouteOptions) => string;
match?: (matchOptions: MatchOptions) => boolean;
render: (renderOptions: RenderOptions) => ReactElement<any>;
paramKey?: string;
}
export type Loader = (api: API) => void;

View File

@ -29,6 +29,7 @@ export interface Addon {
route?: (routeOptions: RouteOptions) => string;
match?: (matchOptions: MatchOptions) => boolean;
render: (renderOptions: RenderOptions) => ReactElement<any>;
paramKey: string;
}
export interface Collection {
[key: string]: Addon;
@ -41,10 +42,15 @@ interface Panels {
export interface SubAPI {
getElements: (type: Types) => Collection;
getPanels: () => Collection;
getPanelsForStory: (storyParameters: StoryParameters) => Collection;
getSelectedPanel: () => string;
setSelectedPanel: (panelName: string) => void;
}
interface StoryParameters {
[key: string]: any;
}
export function ensurePanel(panels: Panels, selectedPanel?: string, currentPanel?: string) {
const keys = Object.keys(panels);
@ -62,6 +68,23 @@ export default ({ provider, store }: Module) => {
const api: SubAPI = {
getElements: type => provider.getElements(type),
getPanels: () => api.getElements(types.PANEL),
getPanelsForStory: (storyParameters: StoryParameters) => {
const panels = api.getPanels();
if (!panels || !storyParameters) {
return panels;
}
const filteredPanels: Collection = {};
Object.entries(panels).forEach(([id, panel]) => {
const { paramKey } = panel;
if (paramKey && storyParameters[paramKey] && storyParameters[paramKey].disabled) {
return;
}
filteredPanels[id] = panel;
});
return filteredPanels;
},
getSelectedPanel: () => {
const { selectedPanel } = store.getState();
return ensurePanel(api.getPanels(), selectedPanel, selectedPanel);

View File

@ -0,0 +1,147 @@
import initAddons, { types } from '../modules/addons';
const PANELS = {
a11y: {
title: 'Accessibility',
paramKey: 'a11y',
},
actions: {
title: 'Actions',
paramKey: 'actions',
},
knobs: {
title: 'Knobs',
paramKey: 'knobs',
},
};
const provider = {
getElements(type) {
if (type === types.PANEL) {
return PANELS;
}
return null;
},
};
const store = {
getState: () => ({
selectedPanel: '',
}),
setState: jest.fn(),
};
describe('Addons API', () => {
describe('#getElements', () => {
it('should return provider elements', () => {
// given
const { api } = initAddons({ provider, store });
// when
const panels = api.getElements(types.PANEL);
// then
expect(panels).toBe(PANELS);
});
});
describe('#getPanels', () => {
it('should return provider panels', () => {
// given
const { api } = initAddons({ provider, store });
// when
const panels = api.getPanels();
// then
expect(panels).toBe(PANELS);
});
});
describe('#getPanelsForStory', () => {
it('should return all panels by default', () => {
// given
const { api } = initAddons({ provider, store });
// when
const filteredPanels = api.getPanelsForStory();
// then
expect(filteredPanels).toBe(PANELS);
});
it('should filter disabled addons', () => {
// given
const { api } = initAddons({ provider, store });
const storyParameters = {
a11y: { disabled: true },
};
// when
const filteredPanels = api.getPanelsForStory(storyParameters);
// then
expect(filteredPanels).toEqual({
actions: PANELS.actions,
knobs: PANELS.knobs,
});
});
});
describe('#getSelectedPanel', () => {
it('should return provider panels', () => {
// given
const storeWithSelectedPanel = {
getState: () => ({
selectedPanel: 'actions',
}),
setState: jest.fn(),
};
const { api } = initAddons({ provider, store: storeWithSelectedPanel });
// when
const selectedPanel = api.getSelectedPanel();
// then
expect(selectedPanel).toBe('actions');
});
it('should return first panel when selected is not a panel', () => {
// given
const storeWithSelectedPanel = {
getState: () => ({
selectedPanel: 'unknown',
}),
setState: jest.fn(),
};
const { api } = initAddons({ provider, store: storeWithSelectedPanel });
// when
const selectedPanel = api.getSelectedPanel();
// then
expect(selectedPanel).toBe('a11y');
});
});
describe('#setSelectedPanel', () => {
it('should set value inn store', () => {
// given
const setState = jest.fn();
const storeWithSelectedPanel = {
getState: () => ({
selectedPanel: 'actions',
}),
setState,
};
const { api } = initAddons({ provider, store: storeWithSelectedPanel });
expect(setState).not.toHaveBeenCalled();
// when
api.setSelectedPanel('knobs');
// then
expect(setState).toHaveBeenCalledWith({ selectedPanel: 'knobs' }, { persistence: 'session' });
});
});
});

View File

@ -10,25 +10,8 @@ const createPanelActions = memoize(1)(api => ({
togglePosition: () => api.togglePanelPosition(),
}));
const filterPanel = (panels, storyParameters) => {
if (!panels || !storyParameters) {
return panels;
}
const filteredPanels = {};
Object.entries(panels).forEach(([id, panel]) => {
const { paramKey } = panel;
const panelParameters = paramKey && storyParameters[paramKey];
if (!panelParameters || !panelParameters.disabled) {
filteredPanels[id] = panel;
}
});
return filteredPanels;
};
const mapper = ({ state, api }) => ({
panels: filterPanel(api.getPanels(), api.getParameters(state.storyId)),
panels: api.getPanelsForStory(api.getParameters(state.storyId)),
selectedPanel: api.getSelectedPanel(),
panelPosition: state.layout.panelPosition,
actions: createPanelActions(api),

View File

@ -0,0 +1,22 @@
import React from 'react';
import { storiesOf } from '@storybook/react';
storiesOf('UI|Addon Panel', module)
.add('default', () => <div>By default all addon panels are rendered</div>)
.add(
'disable panel',
() => (
<div>
This story disables Actions and Accessibility panels
<pre>
{`storiesOf('UI|Addon Panel', module)
.add(
'my story',
<MyComponent />,
{ a11y: { disable: true }, actions: { disable: true } }
);`}
</pre>
</div>
),
{ a11y: { disabled: true }, actions: { disabled: true } }
);